Partitions
The execution order of systems is actually undefined. Although the scheduler avoids executing systems requesting conflicting resources from running concurrently, it is undefined which system executes first. For example, consider the following code:
#![allow(unused)] fn main() { #[global(initial = Self::default())] struct Clock { ticks: u32, } #[system] fn update_clock(#[dynec(global)] clock: &mut Clock) { clock.ticks += 1; } #[system] fn render_status( #[dynec(global)] status: &mut StatusBar, #[dynec(global)] clock: &Clock, ) { status.set_text(format!("Current time: {}", clock.ticks)); } #[system] fn render_progress( #[dynec(global)] progress: &mut ProgressBar, #[dynec(global)] clock: &Clock, ) { progress.set_progress(clock.ticks); } }
It is actually possible that render_status
is executed before update_clock
but render_progress
is executed afterwards,
in which case the progress bar and the status bar have different values in the end.
To avoid this problem, we introduce partitions, which can ensure some systems are executed before some others.
A partition is any thread-safe value that implements Debug + Eq + Hash
.
Any two equivalent values (with the same type) are considered to be the same partition.
So for the example above, we can create a ClockUpdatedPartition
type:
#![allow(unused)] fn main() { #[derive(Debug, PartialEq, Eq, Hash)] struct ClockUpdatedPartition; #[system(before(ClockUpdatedPartition))] fn update_clock(#[dynec(global)] clock: &mut Clock) { clock.ticks += 1; } #[system(after(ClockUpdatedPartition))] fn render_status( #[dynec(global)] status: &mut StatusBar, #[dynec(global)] clock: &Clock, ) { status.set_text(format!("Current time: {}", clock.ticks)); } #[system(after(ClockUpdatedPartition))] fn render_progress( #[dynec(global)] progress: &mut ProgressBar, #[dynec(global)] clock: &Clock, ) { progress.set_progress(clock.ticks); } // Other systems not related to `ClockUpdatedPartition`. #[system] fn other_systems() {} }
Thus, update_clock
is already executed when ClockUpdatedPartition
is complete.
Then render_status
and render_progress
are only executed
after the partition is complete,
so they render the value of the updated clock.
This is illustrated by the following diagram:
---
displayMode: compact
---
gantt
dateFormat s
axisFormat %S
tickInterval 1s
section Worker 1
other_systems :0, 0.5s
render_status :1, 1s
section Worker 2
update_clock :0, 1s
render_progress :1, 0.5s
ClockUpdatedPartition :milestone, 1, 1
In this example, Worker 1 cannot start executing render_status
even though it is idle,
until all dependencies for ClockUpdatedPartition
have completed.
If the scheduler detected a cyclic dependency,
e.g. if update_clock
declares ClockUpdatedPartition
it panics with an error like this:
Scheduled systems have a cyclic dependency: thread-safe system #0 (main::update_clock) -> partition #0 (main::ClockUpdatedPartition) -> thread-safe system #0 (main::update_clock)