blTasks is a library to split code execution over multiple frames to avoid lagging your users and being barked by the Evil Watchdog.
The central element of this library are Tasks, which are user-supplied functions whose execution will be controlled by the library's scheduler. During the execution of these functions, they must signal the scheduler any time they're about to start a potentially time consuming block of code, so it can decide whether to continue during the current frame or wait until more time is available. Execution of the task is done impersonating whatever addon you specify (the one that created the task by default), so messages, CPU time and errors are attributed to the correct source.
Tasks can wait on each other, on external conditions, and be manually suspended and resumed. They can also form tree-like structures, with tasks and subtasks that need to work together to achieve whatever goal you choose. These task trees can be recursively stopped when they aren't needed anymore.
Tasks (and task trees) that fall out of scope are automatically stopped and collected by the garbage collector, preventing them from consuming resources when they're no longer accessible. This behavior can be disabled by abandoning them, which will keep them alive until they complete.
Tasks can, optionally, return results once they finish, which can be retrieved later.
Warning: This library doesn't provide threads. It just provides a concurrency control framework intended to be used to split execution over multiple frames, reduce that execution impact on players framerate and, therefore, avoid watchdog performance warnings and abortions. However, tasks are not threads.
blTasks documentation is also available ingame using Imhothar's API Browser!
This namespace provides functionality to spawn new tasks and get a handle to the currently executing task.
- blTasks.Task.Create: Used to spawn new tasks.
- blTasks.Task.Current: Returns a handle to the currently executing task (if any).
- Event.blTasks.Task.Finished: Fired when a task finishes.
This namespace allows creating and combining wait conditions.
- blTasks.Wait.And: Creates a wait condition that will be met while all its subconditions are met.
- blTasks.Wait.Or: Creates a wait condition that will be met while any of its subconditions is met.
- blTasks.Wait.Task: Creates a wait condition that will be met after the supplied task finishes.
- blTasks.Wait.Children: Creates a wait condition that will be met after all children of the task finish.
- blTasks.Wait.Frame: Creates a wait condition that will be met after the current frame finishes.
- blTasks.Wait.Timespan: Creates a wait condition that will be met after a number of seconds have elapsed.
- blTasks.Wait.Timestamp: Creates a wait condition that will be met after a timestamp is reached.
- blTasks.Wait.Interaction: Creates a wait condition that will be met while the interaction type is available.
- blTasks.Wait.Queue: Creates a wait condition that will be met while the supplied queue has enough available slots.
blTasks uses two object types:
- TaskHandle: Handle to control the execution of the task.
- WaitCondition: Token object used to make tasks know which conditions they must wait on.
Handle to control the execution of the task.
Task handle reference rules:
- If no strong reference to the handle is kept, the task will be stopped shortly after, unless it has been abandoned.
- Keeping a strong reference to a finished task handle could prevent the resources acquired by it from being freed, so don't hold handles longer than needed.
- Certain API methods or events could expose you task handles created by other addons. It'd be polite not to hold strong references to those.
- TaskHandle:Start: Starts running the task.
- TaskHandle:Stop: Finishes the task.
- TaskHandle:Wait: Prevents execution of the task until the given wait condition is met.
- TaskHandle:Suspend: Suspends execution of the task until explicitly resumed.
- TaskHandle:Resume: Resumes execution of a suspended task.
- TaskHandle:Abandon: Marks a task as abandoned and orphans it from its parent, if it had one.
- TaskHandle:Breath: Signals to the scheduler that the task is about to start a time consuming block of code.
- TaskHandle:BreathLong: Signals to the scheduler that the task is about to start a time consuming block of code, of longer duration than TaskHandle:Breath.
- TaskHandle:BreathShort: Signals to the scheduler that the task is about to start a time consuming block of code, of shorter duration than TaskHandle:Breath.
- TaskHandle:Finished: Checks if the task has finished.
- TaskHandle:Result: Gets the results of the task, or any error that ocurred during its execution.
Token object used to make tasks know which conditions they must wait on.
WaitConditions can be added (+) to create a new condition that will be met when any of the operands is met.
They can also be multiplied (*) to create a new condition that will be met when all the operands are met.
Note: Certain wait conditions could cause a deadlock (a circle of wait dependences between tasks). When a potential deadlock is detected, an error is thrown.
Tasks created with this library will be regularly run by a scheduler routine since they start until they terminate or are stopped.
The scheduler measures the time spent by tasks, ensuring they don't run for a long time, to prevent lagging the user or being warned or killed by the Evil Watchdog.
As Lua's multitasking model is collaborative, your task will have to tell the scheduler (using the Breath* methods) when it's about to execute a potentially time consuming block of code, so the scheduler can evaluate whether it's appropiate to run it immediately, or wait until enough time is available. While [currently] the scheduler won't penalize you for not yielding, it would defeat the purpose of using this library not doing it.
The scheduler also tries to ensure that all tasks are assigned some CPU time so they don't starve. Currently, the scheduler selects which task to run using a round-robin algorithm, though other scheduling algorithms could be added or replace the current one in the future, so don't rely on this implementation detail.
The library keeps a hierarchical list of tasks that defines a parent-child relationship between tasks.
Parent - children rules
- When a task is started, the library checks if it has been done from within another task and, if so, marks the newly started task as a child of the former. Note this relationship is established when the task is started, not when it's created.
- Tasks that aren't started from within another task are known as 'orphan', and therefore lack a parent, though they can have children of their own.
- Abandoning a task will also make it orphan.
- When a task is finished (either because it completes, throws an error or is automatically or manually stopped, all its children will be stopped.
There is also a wait condition that makes the parent task wait until all its children finish.
You can use these rules on your advantage to spawn children tasks that work in parallel, or terminate a whole tree of descendant tasks when the job they were doing isn't needed anymore, without needing to keep track of them yourself.
However, don't forget TaskHandle reference rules: If you don't keep at least one strong reference to the task handles, their tasks could be stopped before they have finished their job. Usually, storing them in a local variable on the parent task will suffice, as it won't fall out of scope until the parent task finishes.