AIStatefulTask ‐ Asynchronous, Stateful Task Scheduler library. Threads-like task objects evolving through user-defined states. |
Task queue and dispatcher.
This object dispatches tasks from mainloop().
Each of the member functions AIStatefulTask::run() end with a call to AIStatefulTask::reset()
which in turn calls AIStatefulTask::multiplex(initial_run)
. When a default engine was passed to run then multiplex adds the task to the queue of that engine. When a thread pool queue was passed to run
then the task is added to that queue of the thread pool. If the special AIQueueHandle immediate was passed to run
then the task is being run immediately in the thread that called run
and will keep running until it is either aborted or one of finish(), yield*() or wait*() is called!
Moreover, every time a task run with immediate
as handler (and that didn't set a target handler) calls wait
, then the task will continue running immediately when some thread calls signal(), and again keeps running!
If you don't want a call to run
and/or signal
to take too long, or it would not be thread-safe to not run the task from the main loop of a thread, then either pass a default engine, a thread pool queue, or (when the default handler is Handler::immediate) you've to make sure the task ‐ when (re)started ‐ quickly calls yield*()
or wait*()
(again), causing the task to be added to the highest priority queue of the thread pool.
Note that if during such engineless and queueless state yield() is called without passing a handler, then the task will be added to the highest priority queue of the thread pool.
Sinds normally ‐ for some instance of AIEngine ‐ it is the same thread that calls the AIEngine::mainloop member function in the main loop of that thread, there is a one-on-one relationship between a thread and an AIEngine object.
Once a task is added to an engine then every time the thread of that engine returns to its main loop, it processes one or more tasks in its queue until either — all tasks are finished, idle, moved to another handler or aborted — or, if a maximum duration was set, until more than max_duration milliseconds was spent in the mainloop
(this applies to new tasks, not a task whose multiplex_impl
is already called —only a frequent call to yield() is your friend there).
Note that each task object keeps track of three handlers:
AIStatefulTask::mTargetHandler // Last handler passed to target() or yield*().
AIStatefulTask::mState.current_handler // While idle Handler::idle
, otherwise the first non-idle handler from the top (of this list of three), or Handler::immediate
.AIStatefulTask::mDefaultHandler // The handler passed to run() (that is immediate when none was passed).
The first, mTargetHandler
, is the handler that was passed to the last call of member function AIStatefulTask::target (which is also called by the AIStatefulTask::yield*() member functions that take an engine or handler as parameter). It will be idle
when target
wasn't called yet, or when Handler::idle
is explicitly passed as handler to one of these member functions.
The second, current_handler
, is the handler that the task is added to ‐ for as long as the task needs to be run. It is Handler::idle when task didn't run at all yet or doesn't need to run anymore (e.g., when it is idle). As soon as this value is changed to a different value than the handler that the task is currently active in then that handler will not run that task anymore and remove it from its queue (if any); it is therefore the canonical handler that the task runs in. If a task goes idle, this value is set to Handler::idle; otherwise it is set to the last handler that that task did run in, which is the first non-idle handler from the top for the short list above.
The last, mDefaultHandler
, is the handler that is passed to run and never changes.
#include <AIEngine.h>
Public Member Functions | |
AIEngine (char const *name, float max_duration=0.0f) | |
void | add (AIStatefulTask *stateful_task) |
utils::FuzzyBool | mainloop () |
void | wake_up () |
Wake up a sleeping engine. | |
void | flush () |
char const * | name () const |
void | setMaxDuration (float max_duration) |
bool | hasMaxDuration () const |
|
inline |
Construct an AIEngine.
The argument name must be a string-literal (only the pointer to it is stored). If max_duration is less than or equal zero (the default) then no duration is set and the engine won't return from mainloop until all tasks in its queue either finished, are waiting (idle) or did yield to a different engine.
name | A human readable name for this engine. Mainly used for debug output. |
max_duration | The maximum duration per loop (in milliseconds) during which new tasks are (re)started. See setMaxDuration. |
void AIEngine::add | ( | AIStatefulTask * | stateful_task | ) |
Add stateful_task to this engine.
The task will remain assigned to the engine until it no longer active (tested after returning from multiplex_impl).
Normally you should not call this function directly. Instead, use AIStatefulTask::run.
stateful_task | The task to add. |
void AIEngine::flush | ( | ) |
Flush all tasks from this engine.
All queued tasks are removed from the engine and marked as killed. This can be used when terminating a program, just prior to destructing all remaining objects, to avoid that tasks do call backs and use objects that are being destructed.
|
inline |
Return true if a maximum duration was set.
Note, only engines with a set maximum duration can be used to sleep on by using AIStatefulTask::yield_frame or AIStatefulTask::yield_ms.
utils::FuzzyBool AIEngine::mainloop | ( | ) |
The main loop of the engine.
Run all tasks that were added to the engine until they are all finished and/or idle, or until mMaxDuration milliseconds have passed if a maximum duration was set.
Returns true if there are still tasks in the engine that require CPU and mainloop() must be called again (the next frame). This is the case when one or more tasks called yield or when the call exceeded max_duration and there is still one or more task left that wasn't executed.
|
inline |
Return a human readable name of this engine.
This is simply the string that was passed upon construction.
void AIEngine::setMaxDuration | ( | float | max_duration | ) |
Set mMaxDuration in milliseconds.
The maximum time the engine will spend in mainloop calling multiplex
on unfinished and non-idle tasks. Note that if the last call to multiplex
takes considerable time then it is possible that the time spend in mainloop
will go arbitrarily far beyond mMaxDuration
. It is the responsibility of the user to not run states (of task) that can take too long in engines that have an mMaxDuration
set.