AIStatefulTask ‐ Asynchronous, Stateful Task Scheduler library.

Threads-like task objects evolving through user-defined states.

A tasks life cycle

Creation, initialization, running and destruction.

Because Task objects are called by (possibly) multiple threads, although one at a time, that are constantly going into functions and leaving them again at the very least all the way back to the their main loop; it makes no sense to even want to create a Task object on the stack: the life-time of a Task object is unrelated to any scope.

The correct way to create task objects therefore is by allocating them with new on the heap. There are two ways to do this:

  1. The quick and dirty way, where you just want to run a task a single time. The task will delete itself when finished.

    // Here 'task' is usually just some automatic variable.
    MyTask* task = new MyTask(my_args);
    task->my_initialization(my_params); // Assuming MyTask needs this.
    task->run(handler, callback_args); // This will cause 'task' to
    // be deleted when finished.
    // Do not use 'task' anymore here.

  2. If you want to control the life-time of the Task.
    // In general 'm_task' will be a member variable of an object.
    boost::intrusive_ptr<MyTask> m_task = statefultask::create<MyTask>(my_args);
    // Then, every time you want to run it:
    m_task->my_initialization(my_params);
    m_task->run(handler, callback_args);
    // The task object will be kept around until both,
    // 'm_task' is destructed and the task did finish.

In both cases handler is either an AIEngine* or a AIQueueHandle.

Note that this allows the task to be re-used: it can simply be re-initialized and run again after it finished; there is no need to allocate memory on the heap and construct a Task object every time you want to run it! Of course a task must be finished first before you can restart it.

In the above code snippets, my_args and my_params are entirely user defined; respectively the parameters of the constructor of MyTask and the initialization parameters to some user defined function MyTask::my_initialization.

callback_args are the arguments of one of the AIStatefulTask::run member functions. See the documentation of that member function for a description of those arguments.

Any thread may create a stateful task object, initialize it by calling its initializing member function and call one of the AIStatefulTask::run methods, which might or might not immediately start to execute the task.

The call to run causes a call to initialize_impl, which must call set_state at least once (only the last call is used). Upon return from initialize_impl, multiplex_impl will be called with that state.

multiplex_impl may never reentrant (cause itself to be called) and should end by callling either one of wait, yield, finish [or abort].

Leaving multiplex_impl without calling any of those might result in an immediate reentry, which could cause the task to enter an infinite loop without reaching the main loop again, unless the state is changed with set_state.

If multiplex_impl calls wait(condition), and there was only one call to signal(condition) since the last call to wait(condition), then multiplex_impl will not be called again until signal(condition) is called from outside.

If multiplex_impl calls finish then finish_impl will be called [if instead it calls abort then first abort_impl will be called, followed by a call to finish_impl]. Upon return from multiplex_impl, and if finish [or abort] was called, the call back passed to run will be called.

If the call back function does not call run, or when kill is called after run was called, then the task is deleted upon return from the call back, provided the user didn't keep a boost::intrusive_ptr<> reference around.