[−][src]Crate specs_task
Fork-join multitasking for SPECS ECS
Here we expound on the technical details of this module's implementation. For basic usage, see the tests.
In this model, every task is some entity. The entity is allowed to have exactly one component
that implements TaskComponent
(it may have other components that don't implement
TaskComponent
). The task will be run to completion by the corresponding TaskRunnerSystem
.
Every task entity is also a node in a (hopefully acyclic) directed graph. An edge t2 --> t1
means that t2
cannot start until t1
has completed.
In order for tasks to become unblocked, the TaskManagerSystem
must run, whence it will
traverse the graph, starting at the "final entities", and check for entities that have
completed, potentially unblocking their parents. In order for a task to be run, it must be the
descendent of a final entity. Entities become final by calling TaskManager::finalize
.
Edges can either come from SingleEdge
or MultiEdge
components, but you should not use these
types directly. You might wonder why we need both. It's a fair question, because adding the
SingleEdge
concept does not actually make the model capable of representing any semantically
new graphs. The reason is efficiency.
If you want to implement a fork join like this (note: time is going left to right but the directed edges are going right to left):
r#" ----- t1.1 <--- ----- t2.1 <---
/ \ / \
t0 <------ t1.2 <----<------ t2.2 <---- t3
\ / \ /
----- t1.3 <--- ----- t2.3 <--- "#;
You would actually do this by calling TaskManager::make_fork
to create two "fork" entities
F1
and F2
that don't have TaskComponent
s, but they can have both a SingleEdge
and a
MultiEdge
. Note that the children on the MultiEdge
are called "prongs" of the fork.
r#" single single single
t0 <-------- F1 <-------------- F2 <-------- t3
| |
t1.1 <---| t2.1 <--|
t1.2 <---| multi t2.2 <--| multi
t1.3 <---| t2.3 <--| "#;
The semantics would be such that this graph is equivalent to the one above. Before any of the
tasks connected to F2
by the MultiEdge
could run, the tasks connected by the SingleEdge
({ t0, t1.1, t1.2, t1.3 }
) would have to be complete. t3
could only run once all of the
descendents of F2
had completed.
The advantages of this scheme are:
- a traversal of the graph starting from
t3
does not visit the same node twice - it is a bit easier to create fork-join graphs with larger numbers of concurrent tasks
- there are fewer edges for the most common use cases
Here's another example with "nested forks" to test your understanding:
r#" With fork entities:
t0 <-------------- FA <----- t2
|
tx <---|
t1 <--- FB <---|
|
ty <-----|
tz <-----|
As time orderings:
t0 < { t1, tx, ty, tz } < t2
t1 < { ty, tz }
Induced graph:
t0 <------- tx <------- t2
^ |
| /------ ty <----|
| v |
----- t1 <---- tz <----- "#;
Every user of this module should use it via the TaskManager
. It will enforce certain
invariants about the kinds of entities that can be constructed. For example, any entity with a
MultiEdge
component is considered a "fork entity", and it is not allowed to have a
TaskComponent
or a TaskProgress
. Therefore, if you want a task to have multiple children, it
must do so via a fork entity.
These systems must be dispatched for tasks to make progress:
TaskManagerSystem
TaskRunnerSystem
for everyT: TaskRunner
used
Potential bugs this module won't detect:
- leaked orphan entities
- graph cycles
- finalizing an entity that has children
- users manually tampering with the
TaskProgress
,SingleEdge
,MultiEdge
, orFinalTag
components; these should only be used inside this module
Structs
AlreadyJoined | This error means that you tried to |
TaskManager | The main object for users of this module. Used for creating and connecting tasks. |
TaskManagerSystem | Traverses all descendents of all finalized entities and unblocks them if possible. |
TaskRunnerSystem | The counterpart to an implementation |
Enums
OnCompletion | What to do to a final task and its descendents when it they complete. |
UnexpectedEntity | This error means the entity provided to one of the APIs did not have the expected components. |
Traits
TaskComponent | An ephemeral component that needs access to |