diff --git a/crates/bevy_ecs/src/schedule/run_criteria.rs b/crates/bevy_ecs/src/schedule/run_criteria.rs index 7e3da8a5f3b3f..bad1db9240668 100644 --- a/crates/bevy_ecs/src/schedule/run_criteria.rs +++ b/crates/bevy_ecs/src/schedule/run_criteria.rs @@ -6,6 +6,8 @@ use crate::{ system::{BoxedSystem, IntoSystem, System, SystemId}, world::World, }; +use bevy_ecs_macros::all_tuples; +use bevy_utils::HashMap; use std::borrow::Cow; /// Determines whether a system should be executed or not, and how many times it should be ran each @@ -90,17 +92,10 @@ impl BoxedRunCriteria { } } -pub(crate) enum RunCriteriaInner { - Single(BoxedSystem<(), ShouldRun>), - Piped { - input: usize, - system: BoxedSystem, - }, -} - pub(crate) struct RunCriteriaContainer { pub should_run: ShouldRun, - pub inner: RunCriteriaInner, + inner: Box, + pub parents: Vec, pub label: Option, pub before: Vec, pub after: Vec, @@ -111,10 +106,8 @@ impl RunCriteriaContainer { pub fn from_descriptor(descriptor: RunCriteriaDescriptor) -> Self { Self { should_run: ShouldRun::Yes, - inner: match descriptor.system { - RunCriteriaSystem::Single(system) => RunCriteriaInner::Single(system), - RunCriteriaSystem::Piped(system) => RunCriteriaInner::Piped { input: 0, system }, - }, + inner: descriptor.system, + parents: vec![], label: descriptor.label, before: descriptor.before, after: descriptor.after, @@ -122,48 +115,145 @@ impl RunCriteriaContainer { } } - pub fn name(&self) -> Cow<'static, str> { - match &self.inner { - RunCriteriaInner::Single(system) => system.name(), - RunCriteriaInner::Piped { system, .. } => system.name(), - } + pub fn update_parent_indices(&mut self, labels: &HashMap) { + self.parents = self + .after + .iter() + .take(self.inner.parents_len()) + .map(|label| { + *labels.get(label).unwrap_or_else(|| { + panic!( + "Couldn't find run criteria labelled {:?} to pipe from.", + label + ) + }) + }) + .collect(); } pub fn initialize(&mut self, world: &mut World) { - match &mut self.inner { - RunCriteriaInner::Single(system) => system.initialize(world), - RunCriteriaInner::Piped { system, .. } => system.initialize(world), - } + self.inner.initialize(world) } - pub fn update_archetypes(&mut self, world: &World) { + pub fn run(&mut self, world: &mut World, run_criteria: &[RunCriteriaContainer]) { let archetypes = world.archetypes(); let new_generation = archetypes.generation(); let old_generation = std::mem::replace(&mut self.archetype_generation, new_generation); let archetype_index_range = old_generation.value()..new_generation.value(); for archetype in archetypes.archetypes[archetype_index_range].iter() { - match &mut self.inner { - RunCriteriaInner::Single(system) => { - system.new_archetype(archetype); - } + self.inner.new_archetype(archetype); + } + self.archetype_generation = new_generation; + self.should_run = self + .inner + .evaluate_criteria(world, &self.parents, run_criteria) + } +} - RunCriteriaInner::Piped { system, .. } => { - system.new_archetype(archetype); - } +struct RunCriteriaInner(BoxedSystem); + +trait RunCriteriaTrait: Send + Sync { + fn evaluate_criteria( + &mut self, + world: &mut World, + parents: &[usize], + run_criteria: &[RunCriteriaContainer], + ) -> ShouldRun; + + fn parents_len(&self) -> usize; + + fn name(&self) -> Cow<'static, str>; + + fn new_archetype(&mut self, archetype: &Archetype); + + fn initialize(&mut self, world: &mut World); +} + +impl RunCriteriaTrait for RunCriteriaInner { + fn evaluate_criteria( + &mut self, + world: &mut World, + parents: &[usize], + run_criteria: &[RunCriteriaContainer], + ) -> ShouldRun { + self.0.run(run_criteria[parents[0]].should_run, world) + } + + fn parents_len(&self) -> usize { + 1 + } + + fn name(&self) -> Cow<'static, str> { + self.0.name() + } + + fn new_archetype(&mut self, archetype: &Archetype) { + self.0.new_archetype(archetype) + } + + fn initialize(&mut self, world: &mut World) { + self.0.initialize(world) + } +} + +macro_rules! into_should_run { + ($input: ident) => { + ShouldRun + }; +} + +macro_rules! count { + () => {0usize}; + ($head:tt $($tail:tt)*) => {1usize + count!($($tail)*)}; +} + +macro_rules! into_iter_next { + ($iterator: ident, $input: ident) => { + $iterator.next().unwrap() + }; +} + +macro_rules! impl_criteria_running { + ($($input: ident), *) => { + impl RunCriteriaTrait for RunCriteriaInner<($(into_should_run!($input),) *)> { + #[allow(unused_variables, unused_mut)] + fn evaluate_criteria( + &mut self, + world: &mut World, + parents: &[usize], + run_criteria: &[RunCriteriaContainer], + ) -> ShouldRun { + let mut parent_iter = parents.iter().cloned(); + let input = ($(run_criteria[into_iter_next!(parent_iter, $input)].should_run,) *); + self.0.run(input, world) + } + + fn parents_len(&self) -> usize { + count!($($input)*) + } + + fn name(&self) -> Cow<'static, str> { + self.0.name() + } + + fn new_archetype(&mut self, archetype: &Archetype) { + self.0.new_archetype(archetype) + } + + fn initialize(&mut self, world: &mut World) { + self.0.initialize(world) } } - self.archetype_generation = new_generation; } } +all_tuples!(impl_criteria_running, 0, 16, I); + impl GraphNode for RunCriteriaContainer { type Label = BoxedRunCriteriaLabel; fn name(&self) -> Cow<'static, str> { - match &self.inner { - RunCriteriaInner::Single(system) => system.name(), - RunCriteriaInner::Piped { system, .. } => system.name(), - } + self.inner.name() } fn labels(&self) -> &[BoxedRunCriteriaLabel] { @@ -195,18 +285,13 @@ pub(crate) enum DuplicateLabelStrategy { } pub struct RunCriteriaDescriptor { - pub(crate) system: RunCriteriaSystem, + system: Box, pub(crate) label: Option, pub(crate) duplicate_label_strategy: DuplicateLabelStrategy, pub(crate) before: Vec, pub(crate) after: Vec, } -pub(crate) enum RunCriteriaSystem { - Single(BoxedSystem<(), ShouldRun>), - Piped(BoxedSystem), -} - pub trait IntoRunCriteria { fn into(self) -> RunCriteriaDescriptorOrLabel; } @@ -302,7 +387,7 @@ impl RunCriteriaDescriptorCoercion<()> for RunCriteriaDescriptor { fn new_run_criteria_descriptor(system: BoxedSystem<(), ShouldRun>) -> RunCriteriaDescriptor { RunCriteriaDescriptor { - system: RunCriteriaSystem::Single(system), + system: Box::new(RunCriteriaInner(system)), label: None, duplicate_label_strategy: DuplicateLabelStrategy::Panic, before: vec![], @@ -354,48 +439,149 @@ pub struct RunCriteria { } impl RunCriteria { - /// Constructs a new run criteria that will retrieve the result of the criteria `label` - /// and pipe it as input to `system`. - pub fn pipe( - label: impl RunCriteriaLabel, - system: impl System, + /// Constructs a new run criteria that will retrieve the result(s) of the criteria `labels` + /// and pipe that as input to `system`. + /// + /// `labels` can be a single run criteria label, or a tuple of them. + /// + /// # Example + /// + /// ``` + /// use bevy_ecs::{prelude::*, schedule::ShouldRun}; + /// # fn system_a() {} + /// # fn system_b() {} + /// # fn system_c() {} + /// # fn system_d() {} + /// # fn some_simple_criteria() -> ShouldRun { ShouldRun::Yes } + /// # fn another_simple_criteria() -> ShouldRun { ShouldRun::Yes } + /// + /// #[derive(RunCriteriaLabel, Debug, Clone, PartialEq, Eq, Hash)] + /// enum MyCriteriaLabel { + /// Alpha, + /// Beta, + /// } + /// use MyCriteriaLabel::*; + /// + /// SystemStage::parallel() + /// .with_system_run_criteria(some_simple_criteria.label(Alpha)) + /// .with_system(system_a.with_run_criteria(another_simple_criteria.label(Beta))) + /// .with_system(system_b.with_run_criteria(RunCriteria::pipe( + /// Alpha, + /// |In(piped): In| if piped == ShouldRun::No { + /// ShouldRun::Yes + /// } else { + /// ShouldRun::No + /// } + /// ))) + /// .with_system(system_c.with_run_criteria(RunCriteria::pipe( + /// (Alpha, Beta), + /// |piped: In<(ShouldRun, ShouldRun)>| match piped { + /// In((ShouldRun::Yes, ShouldRun::Yes)) => ShouldRun::Yes, + /// _ => ShouldRun::No, + /// }, + /// ))) + /// // Alternative, short-hand syntax. + /// .with_system(system_d.with_run_criteria((Alpha, Beta).pipe( + /// |piped: In<(ShouldRun, ShouldRun)>| match piped { + /// In((ShouldRun::No, ShouldRun::No)) => ShouldRun::Yes, + /// _ => ShouldRun::No, + /// }, + /// ))); + /// ``` + pub fn pipe( + labels: impl RunCriteriaPiping, + system: impl IntoSystem, ) -> RunCriteriaDescriptor { - label.pipe(system) + labels.pipe(system) } } -pub trait RunCriteriaPiping { +pub trait RunCriteriaPiping { /// See [`RunCriteria::pipe()`]. - fn pipe(self, system: impl System) -> RunCriteriaDescriptor; + fn pipe(self, system: impl IntoSystem) -> RunCriteriaDescriptor; } -impl RunCriteriaPiping for BoxedRunCriteriaLabel { - fn pipe(self, system: impl System) -> RunCriteriaDescriptor { +impl RunCriteriaPiping for L +where + L: RunCriteriaLabel, +{ + fn pipe( + self, + system: impl IntoSystem, + ) -> RunCriteriaDescriptor { RunCriteriaDescriptor { - system: RunCriteriaSystem::Piped(Box::new(system)), + system: Box::new(RunCriteriaInner(Box::new(system.system()))), label: None, duplicate_label_strategy: DuplicateLabelStrategy::Panic, before: vec![], - after: vec![self], + after: vec![Box::new(self)], } } } -impl RunCriteriaPiping for L -where - L: RunCriteriaLabel, -{ - fn pipe(self, system: impl System) -> RunCriteriaDescriptor { +impl RunCriteriaPiping for BoxedRunCriteriaLabel { + fn pipe( + self, + system: impl IntoSystem, + ) -> RunCriteriaDescriptor { RunCriteriaDescriptor { - system: RunCriteriaSystem::Piped(Box::new(system)), + system: Box::new(RunCriteriaInner(Box::new(system.system()))), label: None, duplicate_label_strategy: DuplicateLabelStrategy::Panic, before: vec![], - after: vec![Box::new(self)], + after: vec![self], } } } +macro_rules! into_boxed_label { + ($label: ident) => { + BoxedRunCriteriaLabel + }; +} + +macro_rules! impl_criteria_piping { + ($($label: ident), *) => { + impl<$($label: RunCriteriaLabel), *> RunCriteriaPiping<($(into_should_run!($label),) *)> + for ($($label,) *) { + #[allow(non_snake_case)] + fn pipe( + self, + system: impl IntoSystem<($(into_should_run!($label),) *), ShouldRun, Param>, + ) -> RunCriteriaDescriptor { + let ($($label,) *) = self; + RunCriteriaDescriptor { + system: Box::new(RunCriteriaInner(Box::new(system.system()))), + label: None, + duplicate_label_strategy: DuplicateLabelStrategy::Panic, + before: vec![], + after: vec![$(Box::new($label),) *], + } + } + } + + impl RunCriteriaPiping<($(into_should_run!($label),) *)> + for ($(into_boxed_label!($label),) *) { + #[allow(non_snake_case)] + fn pipe( + self, + system: impl IntoSystem<($(into_should_run!($label),) *), ShouldRun, Param>, + ) -> RunCriteriaDescriptor { + let ($($label,) *) = self; + RunCriteriaDescriptor { + system: Box::new(RunCriteriaInner(Box::new(system.system()))), + label: None, + duplicate_label_strategy: DuplicateLabelStrategy::Panic, + before: vec![], + after: vec![$($label,) *], + } + } + } + }; +} + +all_tuples!(impl_criteria_piping, 1, 16, L); + pub struct RunOnce { ran: bool, system_id: SystemId, diff --git a/crates/bevy_ecs/src/schedule/stage.rs b/crates/bevy_ecs/src/schedule/stage.rs index 8eed39d3aa8e7..e6a5b24d39a83 100644 --- a/crates/bevy_ecs/src/schedule/stage.rs +++ b/crates/bevy_ecs/src/schedule/stage.rs @@ -5,8 +5,8 @@ use crate::{ BoxedRunCriteria, BoxedRunCriteriaLabel, BoxedSystemLabel, DuplicateLabelStrategy, ExclusiveSystemContainer, GraphNode, InsertionPoint, ParallelExecutor, ParallelSystemContainer, ParallelSystemExecutor, RunCriteriaContainer, - RunCriteriaDescriptor, RunCriteriaDescriptorOrLabel, RunCriteriaInner, ShouldRun, - SingleThreadedExecutor, SystemContainer, SystemDescriptor, SystemSet, + RunCriteriaDescriptor, RunCriteriaDescriptorOrLabel, ShouldRun, SingleThreadedExecutor, + SystemContainer, SystemDescriptor, SystemSet, }, system::System, world::{World, WorldId}, @@ -590,15 +590,7 @@ impl SystemStage { }) .collect(); for criteria in self.run_criteria.iter_mut() { - if let RunCriteriaInner::Piped { input: parent, .. } = &mut criteria.inner { - let label = &criteria.after[0]; - *parent = *labels.get(label).unwrap_or_else(|| { - panic!( - "Couldn't find run criteria labelled {:?} to pipe from.", - label - ) - }); - } + criteria.update_parent_indices(&labels); } fn update_run_criteria_indices( @@ -774,16 +766,8 @@ impl Stage for SystemStage { // Evaluate system run criteria. for index in 0..self.run_criteria.len() { let (run_criteria, tail) = self.run_criteria.split_at_mut(index); - let mut criteria = &mut tail[0]; - criteria.update_archetypes(world); - match &mut criteria.inner { - RunCriteriaInner::Single(system) => criteria.should_run = system.run((), world), - RunCriteriaInner::Piped { - input: parent, - system, - .. - } => criteria.should_run = system.run(run_criteria[*parent].should_run, world), - } + let criteria = &mut tail[0]; + criteria.run(world, run_criteria); } let mut run_system_loop = true; @@ -849,24 +833,11 @@ impl Stage for SystemStage { for index in 0..run_criteria.len() { let (run_criteria, tail) = run_criteria.split_at_mut(index); let criteria = &mut tail[0]; - criteria.update_archetypes(world); match criteria.should_run { ShouldRun::No => (), ShouldRun::Yes => criteria.should_run = ShouldRun::No, ShouldRun::YesAndCheckAgain | ShouldRun::NoAndCheckAgain => { - match &mut criteria.inner { - RunCriteriaInner::Single(system) => { - criteria.should_run = system.run((), world) - } - RunCriteriaInner::Piped { - input: parent, - system, - .. - } => { - criteria.should_run = - system.run(run_criteria[*parent].should_run, world) - } - } + criteria.run(world, run_criteria); match criteria.should_run { ShouldRun::Yes => { run_system_loop = true; @@ -897,7 +868,7 @@ mod tests { RunCriteria, RunCriteriaDescriptorCoercion, RunCriteriaPiping, ShouldRun, SingleThreadedExecutor, Stage, SystemSet, SystemStage, }, - system::{In, IntoExclusiveSystem, IntoSystem, Local, Query, ResMut}, + system::{ConfigurableSystem, In, IntoExclusiveSystem, IntoSystem, Local, Query, ResMut}, world::World, }; @@ -1486,7 +1457,7 @@ mod tests { .system() .label("3") .after("2") - .with_run_criteria("every other time".pipe(eot_piped.system()).label("piped")), + .with_run_criteria("every other time".pipe(eot_piped).label("piped")), ) .with_system( make_parallel!(4) @@ -1507,6 +1478,63 @@ mod tests { ); assert_eq!(stage.run_criteria.len(), 3); + // Multipiping. + world.get_resource_mut::>().unwrap().clear(); + let mut stage = SystemStage::parallel() + .with_system(make_parallel!(0).label("0")) + .with_system( + make_parallel!(1) + .after("0") + .label("1") + .with_run_criteria(every_other_time.label("every other time")), + ) + .with_system( + make_parallel!(2).after("1").label("2").with_run_criteria( + (|mut iteration: Local| { + *iteration += 1; + if *iteration == 3 { + *iteration = 0; + ShouldRun::Yes + } else { + ShouldRun::No + } + }) + .config(|config| config.0 = Some(2)) + .label("every third time"), + ), + ) + .with_system(make_parallel!(3).after("2").label("3").with_run_criteria( + ("every other time", "every third time").pipe( + |input: In<(ShouldRun, ShouldRun)>| match input { + In((ShouldRun::Yes, ShouldRun::Yes)) => ShouldRun::Yes, + _ => ShouldRun::No, + }, + ), + )) + .with_system( + make_parallel!(4) + .after("3") + .with_run_criteria(RunCriteria::pipe( + ("every other time", "every third time"), + |input: In<(ShouldRun, ShouldRun)>| match input { + In((ShouldRun::Yes, ShouldRun::Yes)) => ShouldRun::Yes, + _ => ShouldRun::No, + }, + )), + ); + for _ in 0..4 { + stage.run(&mut world); + } + stage.set_executor(Box::new(SingleThreadedExecutor::default())); + for _ in 0..4 { + stage.run(&mut world); + } + assert_eq!( + *world.get_resource::>().unwrap(), + vec![0, 1, 2, 3, 4, 0, 0, 1, 0, 2, 0, 1, 0, 0, 1, 2, 3, 4, 0,] + ); + assert_eq!(stage.run_criteria.len(), 4); + // Discarding extra criteria with matching labels. world.get_resource_mut::>().unwrap().clear(); let mut stage = SystemStage::parallel()