diff --git a/src/system/schedule/stage.rs b/src/system/schedule/stage.rs index 737d9010..7304bbf5 100644 --- a/src/system/schedule/stage.rs +++ b/src/system/schedule/stage.rs @@ -24,12 +24,21 @@ use super::Stages; define_null!(); +/// A stage within a schedule. +/// +/// A single stage contains only tasks that can always be run in parallel. pub trait Stage<'a, R, FI, VI, P, I, Q>: Send where R: Registry, { + /// A list of booleans indicating whether each task within the stage has already been run. type HasRun: Send; + /// Run all of the tasks within this stage in parallel. + /// + /// After the tasks have been scheduled to run, tasks within the following stage will also + /// be attempted to be scheduled. Any tasks that are dynamically found to be able to run in + /// parallel with the current tasks will be executed as well. fn run<'b, N, NFI, NVI, NP, NI, NQ>( &mut self, world: SendableWorld, @@ -40,12 +49,24 @@ where where N: Stages<'b, R, NFI, NVI, NP, NI, NQ>; - fn run_add_ons( + /// Attempt to run as many tasks within this stage as possible as add-ons to the previous + /// stage. + /// + /// `borrowed_archetypes` contains a set of dynamic claims that are already borrowed by the + /// previous stage. This method respects those claims when evaluating whether new tasks can be + /// executed. + /// + /// # Safety + /// `borrowed_archetypes` must accurately represent the dynamic claims already made on the + /// component columns within `world`. + unsafe fn run_add_ons( &mut self, world: SendableWorld, borrowed_archetypes: HashMap, R::Claims, FnvBuildHasher>, ) -> Self::HasRun; + /// Creates a new default set of booleans to indicate that each task within the stage has not + /// been run. fn new_has_run() -> Self::HasRun; } @@ -71,11 +92,13 @@ where N::new_has_run() } else { // Run tasks from next stage that can be parallelized dynamically. - next_stage.run_add_ons(world, borrowed_archetypes) + // SAFETY: The safety contract of this method call is upheld by the safety contract of + // this method. + unsafe { next_stage.run_add_ons(world, borrowed_archetypes) } } } - fn run_add_ons( + unsafe fn run_add_ons( &mut self, _world: SendableWorld, _borrowed_archetypes: HashMap, R::Claims, FnvBuildHasher>, @@ -186,19 +209,31 @@ where } } - fn run_add_ons( + unsafe fn run_add_ons( &mut self, world: SendableWorld, mut borrowed_archetypes: HashMap, R::Claims, FnvBuildHasher>, ) -> Self::HasRun { if query_archetype_identifiers::(world, &mut borrowed_archetypes) { rayon::join( - || (true, self.1.run_add_ons(world, borrowed_archetypes)), + || { + ( + true, + // SAFETY: The safety contract of this method call is upheld by the safety + // contract of this method. + unsafe { self.1.run_add_ons(world, borrowed_archetypes) }, + ) + }, || self.0.run(world), ) .0 } else { - (false, self.1.run_add_ons(world, borrowed_archetypes)) + ( + false, + // SAFETY: The safety contract of this method call is upheld by the safety contract + // of this method. + unsafe { self.1.run_add_ons(world, borrowed_archetypes) }, + ) } } diff --git a/src/system/schedule/stages.rs b/src/system/schedule/stages.rs index ff19deb5..c8e9b551 100644 --- a/src/system/schedule/stages.rs +++ b/src/system/schedule/stages.rs @@ -13,20 +13,44 @@ use hashbrown::HashMap; define_null!(); +/// The stages within a schedule. pub trait Stages<'a, R, FI, VI, P, I, Q>: Send where R: Registry, { + /// A list of booleans indicating whether each task within the first stage has already been run. type HasRun: Send; + /// Run all of the stages, parallelizing as much work as possible. + /// + /// The parallelization strategy involves two parts: + /// + /// 1. Compile-time scheduling: at compile time, tasks are split into stages, where all tasks + /// in a stage can always be run in parallel with each other, no matter the `World`. + /// 2. Run-time optimization: at run-time, component claims on archetype tables within the + /// `World` are tracked when scheduling a single stage. Then, any tasks within the next stage + /// whose borrowed components do not interfere with the tasks in the current stage's dynamic + /// claims are run as well. fn run(&mut self, world: &mut World, has_run: Self::HasRun); - fn run_add_ons( + /// Attempt to run as many tasks within the first stage in the list as possible as add-ons to + /// the previous stage. + /// + /// `borrowed_archetypes` contains a set of dynamic claims that are already borrowed by the + /// previous stage. This method respects those claims when evaluating whether new tasks can be + /// executed. + /// + /// # Safety + /// `borrowed_archetypes` must accurately represent the dynamic claims already made on the + /// component columns within `world`. + unsafe fn run_add_ons( &mut self, world: SendableWorld, borrowed_archetypes: HashMap, R::Claims, FnvBuildHasher>, ) -> Self::HasRun; + /// Creates a new default set of booleans to indicate that each task within the first stage has + /// not been run. fn new_has_run() -> Self::HasRun; } @@ -38,7 +62,7 @@ where fn run(&mut self, _world: &mut World, _has_run: Self::HasRun) {} - fn run_add_ons( + unsafe fn run_add_ons( &mut self, _world: SendableWorld, _borrowed_archetypes: HashMap, R::Claims, FnvBuildHasher>, @@ -72,12 +96,14 @@ where self.1.run(world, next_has_run); } - fn run_add_ons( + unsafe fn run_add_ons( &mut self, world: SendableWorld, borrowed_archetypes: HashMap, R::Claims, FnvBuildHasher>, ) -> Self::HasRun { - self.0.run_add_ons(world, borrowed_archetypes) + // SAFETY: The safety contract of this method call is upheld by the safety contract of this + // method. + unsafe { self.0.run_add_ons(world, borrowed_archetypes) } } fn new_has_run() -> Self::HasRun {