2023-06-24 03:14:17 +02:00
|
|
|
/*
|
|
|
|
|
SCHEDULER.hpp - coordination of render activities under timing and dependency constraints
|
|
|
|
|
|
|
|
|
|
Copyright (C) Lumiera.org
|
|
|
|
|
2023, Hermann Vosseler <Ichthyostega@web.de>
|
|
|
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
|
|
|
modify it under the terms of the GNU General Public License as
|
|
|
|
|
published by the Free Software Foundation; either version 2 of
|
|
|
|
|
the License, or (at your option) any later version.
|
|
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
|
along with this program; if not, write to the Free Software
|
|
|
|
|
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** @file scheduler.hpp
|
|
|
|
|
** Service for coordination and dispatch of render activities.
|
|
|
|
|
** The implementation of scheduling services is provided by an integration
|
|
|
|
|
** of two layers of functionality:
|
|
|
|
|
** - Layer-1 allows to enqueue and prioritise render activity records
|
|
|
|
|
** - Layer-2 connects and coordinates activities to conduct complex calculations
|
|
|
|
|
**
|
2023-07-03 18:40:37 +02:00
|
|
|
** @see SchedulerUsage_test Component integration test
|
|
|
|
|
** @see scheduler.cpp implementation details
|
|
|
|
|
** @see SchedulerInvocation Layer-1
|
|
|
|
|
** @see SchedulerCommutator Layer-2
|
2023-06-24 03:14:17 +02:00
|
|
|
**
|
2023-10-22 16:45:13 +02:00
|
|
|
** @todo WIP-WIP 10/2023 »Playback Vertical Slice«
|
2023-06-24 03:14:17 +02:00
|
|
|
**
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#ifndef SRC_VAULT_GEAR_SCHEDULER_H_
|
|
|
|
|
#define SRC_VAULT_GEAR_SCHEDULER_H_
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#include "lib/error.hpp"
|
|
|
|
|
#include "vault/gear/block-flow.hpp"
|
2023-10-20 01:45:20 +02:00
|
|
|
#include "vault/gear/work-force.hpp"
|
|
|
|
|
#include "vault/gear/activity-lang.hpp"
|
2023-06-27 03:21:10 +02:00
|
|
|
#include "vault/gear/scheduler-commutator.hpp"
|
2023-06-24 03:14:17 +02:00
|
|
|
#include "vault/gear/scheduler-invocation.hpp"
|
2023-10-20 01:45:20 +02:00
|
|
|
#include "vault/gear/load-controller.hpp"
|
|
|
|
|
#include "vault/gear/engine-observer.hpp"
|
2023-10-25 17:27:18 +02:00
|
|
|
#include "vault/real-clock.hpp"
|
2023-06-24 03:14:17 +02:00
|
|
|
//#include "lib/symbol.hpp"
|
2023-10-27 03:37:24 +02:00
|
|
|
#include "lib/nocopy.hpp"
|
2023-06-24 03:14:17 +02:00
|
|
|
//#include "lib/util.hpp"
|
|
|
|
|
|
|
|
|
|
//#include <string>
|
2023-10-22 23:25:35 +02:00
|
|
|
#include <utility>
|
2023-06-24 03:14:17 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
namespace vault{
|
|
|
|
|
namespace gear {
|
|
|
|
|
|
|
|
|
|
// using util::isnil;
|
|
|
|
|
// using std::string;
|
2023-10-22 23:25:35 +02:00
|
|
|
using std::move;
|
2023-10-23 04:07:38 +02:00
|
|
|
using lib::time::Time;
|
|
|
|
|
using lib::time::FSecs;
|
|
|
|
|
using lib::time::Offset;
|
|
|
|
|
using lib::time::Duration;
|
2023-06-24 03:14:17 +02:00
|
|
|
|
2023-10-25 17:27:18 +02:00
|
|
|
namespace test { // declared friend for test access
|
|
|
|
|
class SchedulerService_test;
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-22 23:25:35 +02:00
|
|
|
namespace { // Scheduler default config
|
|
|
|
|
|
|
|
|
|
const auto IDLE_WAIT = 20ms; ///< sleep-recheck cycle for workers deemed _idle_
|
|
|
|
|
const size_t DISMISS_CYCLES = 100; ///< number of wait cycles before an idle worker terminates completely
|
|
|
|
|
Offset POLL_WAIT_DELAY{FSecs(1,1000)}; ///< delay until re-evaluating a condition previously found unsatisfied
|
|
|
|
|
}
|
2023-10-20 18:24:50 +02:00
|
|
|
|
|
|
|
|
|
2023-06-24 03:14:17 +02:00
|
|
|
|
2023-10-20 18:24:50 +02:00
|
|
|
|
|
|
|
|
/******************************************************//**
|
|
|
|
|
* »Scheduler-Service« : coordinate render activities.
|
2023-10-22 16:45:13 +02:00
|
|
|
* @todo WIP-WIP 10/2023
|
2023-07-03 18:40:37 +02:00
|
|
|
* @see BlockFlow
|
|
|
|
|
* @see SchedulerUsage_test
|
2023-06-24 03:14:17 +02:00
|
|
|
*/
|
|
|
|
|
class Scheduler
|
2023-10-20 01:45:20 +02:00
|
|
|
: util::NonCopyable
|
2023-06-24 03:14:17 +02:00
|
|
|
{
|
2023-10-20 18:24:50 +02:00
|
|
|
/** Binding of worker callbacks to the scheduler implementation */
|
|
|
|
|
struct Setup : work::Config
|
|
|
|
|
{
|
|
|
|
|
Scheduler& scheduler;
|
|
|
|
|
activity::Proc doWork() { return scheduler.getWork(); }
|
|
|
|
|
void finalHook (bool _) { scheduler.handleWorkerTermination(_);}
|
|
|
|
|
};
|
|
|
|
|
|
2023-10-20 01:45:20 +02:00
|
|
|
|
2023-06-26 02:16:50 +02:00
|
|
|
SchedulerInvocation layer1_;
|
2023-06-27 03:21:10 +02:00
|
|
|
SchedulerCommutator layer2_;
|
2023-10-20 18:24:50 +02:00
|
|
|
WorkForce<Setup> workForce_;
|
2023-10-20 01:45:20 +02:00
|
|
|
|
|
|
|
|
ActivityLang activityLang_;
|
|
|
|
|
LoadController loadControl_;
|
|
|
|
|
EngineObserver& engineObserver_;
|
2023-06-24 03:14:17 +02:00
|
|
|
|
2023-10-20 18:24:50 +02:00
|
|
|
|
2023-06-24 03:14:17 +02:00
|
|
|
public:
|
2023-10-20 01:45:20 +02:00
|
|
|
Scheduler (BlockFlowAlloc& activityAllocator
|
|
|
|
|
,EngineObserver& engineObserver)
|
2023-06-26 02:16:50 +02:00
|
|
|
: layer1_{}
|
|
|
|
|
, layer2_{}
|
2023-10-20 18:24:50 +02:00
|
|
|
, workForce_{Setup{IDLE_WAIT, DISMISS_CYCLES, *this}}
|
2023-10-20 01:45:20 +02:00
|
|
|
, activityLang_{activityAllocator}
|
2023-10-23 21:51:16 +02:00
|
|
|
, loadControl_{connectMonitoring()}
|
2023-10-20 01:45:20 +02:00
|
|
|
, engineObserver_{engineObserver}
|
2023-06-24 03:14:17 +02:00
|
|
|
{ }
|
|
|
|
|
|
2023-10-20 01:45:20 +02:00
|
|
|
|
2023-10-25 23:43:19 +02:00
|
|
|
bool
|
|
|
|
|
empty() const
|
|
|
|
|
{
|
|
|
|
|
return layer1_.empty();
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-22 16:45:13 +02:00
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
ignite()
|
|
|
|
|
{
|
|
|
|
|
UNIMPLEMENTED("suicide");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2023-10-20 01:45:20 +02:00
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
terminateProcessing()
|
|
|
|
|
{
|
|
|
|
|
UNIMPLEMENTED("suicide");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
double
|
|
|
|
|
getLoadIndicator()
|
|
|
|
|
{
|
|
|
|
|
UNIMPLEMENTED("load indicator");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
seedCalcStream()
|
|
|
|
|
{
|
|
|
|
|
UNIMPLEMENTED("get it going");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
buildJob()
|
|
|
|
|
{
|
|
|
|
|
UNIMPLEMENTED("wrap the ActivityTerm");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2023-10-22 23:25:35 +02:00
|
|
|
* The worker-Functor: called by the active Workers from the
|
|
|
|
|
* \ref WorkForce to pull / perform the actual render Activities.
|
2023-10-20 01:45:20 +02:00
|
|
|
*/
|
2023-10-22 23:25:35 +02:00
|
|
|
activity::Proc getWork();
|
|
|
|
|
|
2023-10-20 01:45:20 +02:00
|
|
|
|
|
|
|
|
private:
|
2023-10-20 18:24:50 +02:00
|
|
|
void
|
|
|
|
|
handleWorkerTermination (bool isFailure)
|
2023-10-20 01:45:20 +02:00
|
|
|
{
|
2023-10-20 18:24:50 +02:00
|
|
|
UNIMPLEMENTED("die harder");
|
2023-10-20 01:45:20 +02:00
|
|
|
}
|
2023-10-21 01:01:00 +02:00
|
|
|
|
|
|
|
|
|
2023-10-24 23:59:12 +02:00
|
|
|
/** send this thread into a targeted short-time wait. */
|
|
|
|
|
activity::Proc scatteredDelay (Time now, LoadController::Capacity);
|
2023-10-22 16:45:13 +02:00
|
|
|
|
|
|
|
|
|
2023-10-22 23:25:35 +02:00
|
|
|
/**
|
|
|
|
|
* monad-like step sequence: perform sequence of steps,
|
|
|
|
|
* as long as the result remains activity::PASS
|
|
|
|
|
*/
|
|
|
|
|
struct WorkerInstruction
|
|
|
|
|
{
|
|
|
|
|
activity::Proc lastResult = activity::PASS;
|
|
|
|
|
|
2023-10-24 23:59:12 +02:00
|
|
|
/** exposes the latest verdict as overall result
|
|
|
|
|
* @note returning activity::SKIP from the dispatch
|
|
|
|
|
* signals early exit, which is acquitted here. */
|
2023-10-22 23:25:35 +02:00
|
|
|
operator activity::Proc()
|
|
|
|
|
{
|
|
|
|
|
return activity::SKIP == lastResult? activity::PASS
|
|
|
|
|
: lastResult;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<class FUN>
|
|
|
|
|
WorkerInstruction
|
|
|
|
|
performStep (FUN step)
|
|
|
|
|
{
|
|
|
|
|
if (activity::PASS == lastResult)
|
|
|
|
|
lastResult = step();
|
|
|
|
|
return move(*this);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2023-10-23 21:51:16 +02:00
|
|
|
|
|
|
|
|
/** @internal connect state signals for use by the LoadController */
|
|
|
|
|
LoadController::Wiring
|
|
|
|
|
connectMonitoring()
|
|
|
|
|
{
|
|
|
|
|
LoadController::Wiring setup;
|
|
|
|
|
setup.maxCapacity = work::Config::COMPUTATION_CAPACITY;
|
|
|
|
|
return setup;
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-21 01:01:00 +02:00
|
|
|
/** @internal expose a binding for Activity execution */
|
|
|
|
|
class ExecutionCtx;
|
2023-10-25 17:27:18 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
/** open private backdoor for tests */
|
|
|
|
|
friend class test::SchedulerService_test;
|
2023-10-21 01:01:00 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @remark when due, the scheduled Activities are performed within the
|
|
|
|
|
* [Activity-Language execution environment](\ref ActivityLang::dispatchChain());
|
|
|
|
|
* some aspects of Activity _activation_ however require external functionality,
|
|
|
|
|
* which — for the purpose of language definition — was abstracted as _Execution-context._
|
|
|
|
|
* The implementation of these binding functions fills in relevant external effects and
|
2023-10-22 23:25:35 +02:00
|
|
|
* is in fact supplied by the implementation internals of the scheduler itself.
|
2023-10-21 01:01:00 +02:00
|
|
|
*/
|
|
|
|
|
class Scheduler::ExecutionCtx
|
|
|
|
|
: private Scheduler
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
static ExecutionCtx&
|
|
|
|
|
from (Scheduler& self)
|
|
|
|
|
{
|
|
|
|
|
return static_cast<ExecutionCtx&> (self);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ==== Implementation of the Concept ExecutionCtx ==== */
|
|
|
|
|
|
2023-10-22 00:55:56 +02:00
|
|
|
/**
|
|
|
|
|
* λ-post: enqueue for time-bound execution, possibly dispatch immediately.
|
|
|
|
|
* This is the »main entrance« to get some Activity scheduled.
|
|
|
|
|
* @remark the \a ctx argument is redundant (helpful for test/mocking)
|
|
|
|
|
*/
|
2023-10-21 01:01:00 +02:00
|
|
|
activity::Proc
|
2023-10-23 01:48:46 +02:00
|
|
|
post (Time when, Activity* chain, ExecutionCtx& ctx)
|
2023-10-21 01:01:00 +02:00
|
|
|
{
|
2023-10-23 01:48:46 +02:00
|
|
|
return layer2_.postDispatch (chain, when, ctx, layer1_);
|
2023-10-21 01:01:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
work (Time, size_t)
|
|
|
|
|
{
|
|
|
|
|
UNIMPLEMENTED ("λ-work");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
done (Time, size_t)
|
|
|
|
|
{
|
|
|
|
|
UNIMPLEMENTED ("λ-done");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
activity::Proc
|
|
|
|
|
tick (Time)
|
|
|
|
|
{
|
|
|
|
|
UNIMPLEMENTED ("λ-tick");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Offset
|
|
|
|
|
getWaitDelay()
|
|
|
|
|
{
|
|
|
|
|
return POLL_WAIT_DELAY;
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-25 17:27:18 +02:00
|
|
|
/** access high-resolution-clock, rounded to µ-Ticks */
|
2023-10-21 01:01:00 +02:00
|
|
|
Time
|
|
|
|
|
getSchedTime()
|
|
|
|
|
{
|
2023-10-25 17:27:18 +02:00
|
|
|
return RealClock::now();
|
2023-10-21 01:01:00 +02:00
|
|
|
}
|
2023-06-24 03:14:17 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2023-10-21 01:01:00 +02:00
|
|
|
|
2023-10-22 23:25:35 +02:00
|
|
|
/**
|
|
|
|
|
* @remarks this function is invoked from within the worker thread(s) and will
|
|
|
|
|
* - decide if and how the capacity of this worker shall be used right now
|
|
|
|
|
* - possibly go into a short targeted wait state to redirect capacity at a better time point
|
2023-10-24 23:59:12 +02:00
|
|
|
* - and most notably commence with dispatch of render Activities, to calculate media data.
|
2023-10-22 23:25:35 +02:00
|
|
|
* @return an instruction for the work::Worker how to proceed next:
|
|
|
|
|
* - activity::PROC causes the worker to poll again immediately
|
|
|
|
|
* - activity::SLEEP induces a sleep state
|
|
|
|
|
* - activity::HALT terminates the worker
|
|
|
|
|
*/
|
|
|
|
|
inline activity::Proc
|
|
|
|
|
Scheduler::getWork()
|
|
|
|
|
{
|
|
|
|
|
ExecutionCtx& ctx = ExecutionCtx::from(*this);
|
|
|
|
|
|
|
|
|
|
return WorkerInstruction{}
|
2023-10-25 02:53:11 +02:00
|
|
|
.performStep([&]{
|
|
|
|
|
Time now = ctx.getSchedTime();
|
|
|
|
|
Time head = layer1_.headTime();
|
|
|
|
|
return scatteredDelay(now,
|
|
|
|
|
loadControl_.markIncomingCapacity (head,now));
|
2023-10-22 23:25:35 +02:00
|
|
|
})
|
|
|
|
|
.performStep([&]{
|
2023-10-25 02:53:11 +02:00
|
|
|
Time now = ctx.getSchedTime();
|
2023-10-23 01:48:46 +02:00
|
|
|
Activity* act = layer2_.findWork (layer1_,now);
|
2023-10-27 03:37:24 +02:00
|
|
|
return ctx.post (now, act, ctx);
|
2023-10-22 23:25:35 +02:00
|
|
|
})
|
2023-10-25 02:53:11 +02:00
|
|
|
.performStep([&]{
|
|
|
|
|
Time now = ctx.getSchedTime();
|
|
|
|
|
Time head = layer1_.headTime();
|
|
|
|
|
return scatteredDelay(now,
|
|
|
|
|
loadControl_.markOutgoingCapacity (head,now));
|
2023-10-22 23:25:35 +02:00
|
|
|
})
|
|
|
|
|
;
|
|
|
|
|
}
|
2023-10-24 23:59:12 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* A worker [asking for work](\ref #getWork) constitutes free capacity,
|
|
|
|
|
* which can be redirected into a focused zone of the scheduler time axis
|
|
|
|
|
* where it is most likely to be useful, unless there is active work to
|
|
|
|
|
* be carried out right away.
|
|
|
|
|
* @param capacity classification of the capacity to employ this thread
|
|
|
|
|
* @return how to proceed further with this worker
|
2023-10-25 02:53:11 +02:00
|
|
|
* - activity::PASS indicates to proceed or call back immediately
|
|
|
|
|
* - activity::SKIP causes to exit this round, yet call back again
|
|
|
|
|
* - activity::WAIT exits and places the worker into sleep mode
|
2023-10-24 23:59:12 +02:00
|
|
|
* @note as part of the regular work processing, this function may
|
|
|
|
|
* place the current thread into a short-term targeted sleep.
|
|
|
|
|
*/
|
|
|
|
|
inline activity::Proc
|
|
|
|
|
Scheduler::scatteredDelay (Time now, LoadController::Capacity capacity)
|
|
|
|
|
{
|
|
|
|
|
switch (capacity) {
|
|
|
|
|
case LoadController::DISPATCH:
|
|
|
|
|
return activity::PASS;
|
|
|
|
|
case LoadController::SPINTIME:
|
|
|
|
|
std::this_thread::yield();
|
2023-10-25 02:53:11 +02:00
|
|
|
return activity::SKIP;
|
|
|
|
|
case LoadController::IDLEWAIT:
|
2023-10-24 23:59:12 +02:00
|
|
|
return activity::WAIT;
|
2023-10-25 02:53:11 +02:00
|
|
|
case LoadController::TENDNEXT:
|
|
|
|
|
{
|
|
|
|
|
Time head = layer1_.headTime();
|
|
|
|
|
auto self = std::this_thread::get_id();
|
|
|
|
|
if (not loadControl_.tendedNext(head)
|
|
|
|
|
and (layer2_.holdsGroomingToken(self)
|
|
|
|
|
or layer2_.acquireGoomingToken()))
|
|
|
|
|
loadControl_.tendNext(head);
|
|
|
|
|
}// Fall-through to perform targeted wait
|
|
|
|
|
// @suppress("No break at end of case")
|
2023-10-24 23:59:12 +02:00
|
|
|
default:
|
|
|
|
|
Offset targetedDelay = loadControl_.scatteredDelayTime (now, capacity);
|
|
|
|
|
std::this_thread::sleep_for (std::chrono::microseconds (_raw(targetedDelay)));
|
2023-10-25 02:53:11 +02:00
|
|
|
return activity::SKIP; // indicates to abort this processing-chain for good
|
2023-10-24 23:59:12 +02:00
|
|
|
}
|
|
|
|
|
}
|
2023-10-22 23:25:35 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2023-06-24 03:14:17 +02:00
|
|
|
}} // namespace vault::gear
|
|
|
|
|
#endif /*SRC_VAULT_GEAR_SCHEDULER_H_*/
|