From 50bd3b05286770b3a34b4d257bce800dc848613d Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Fri, 10 Feb 2023 08:35:39 -0500 Subject: [PATCH 01/38] add a struct for making generic system combinators --- crates/bevy_ecs/src/system/combinator.rs | 147 +++++++++++++++++++++++ crates/bevy_ecs/src/system/mod.rs | 2 + 2 files changed, 149 insertions(+) create mode 100644 crates/bevy_ecs/src/system/combinator.rs diff --git a/crates/bevy_ecs/src/system/combinator.rs b/crates/bevy_ecs/src/system/combinator.rs new file mode 100644 index 0000000000000..d988d8ed0face --- /dev/null +++ b/crates/bevy_ecs/src/system/combinator.rs @@ -0,0 +1,147 @@ +use std::{borrow::Cow, marker::PhantomData}; + +use crate::{ + archetype::ArchetypeComponentId, component::ComponentId, prelude::World, query::Access, +}; + +use super::{ReadOnlySystem, System}; + +pub trait Combine { + type In; + type Out; + + fn combine( + input: Self::In, + world: &World, + a: impl FnOnce(A::In, &World) -> A::Out, + b: impl FnOnce(B::In, &World) -> B::Out, + ) -> Self::Out; + + fn combine_exclusive( + input: Self::In, + world: &mut World, + a: impl FnOnce(A::In, &mut World) -> A::Out, + b: impl FnOnce(B::In, &mut World) -> B::Out, + ) -> Self::Out; +} + +pub struct CombinatorSystem { + _marker: PhantomData Func>, + a: A, + b: B, + name: Cow<'static, str>, + component_access: Access, + archetype_component_access: Access, +} + +impl CombinatorSystem { + pub const fn new(a: A, b: B, name: Cow<'static, str>) -> Self { + Self { + _marker: PhantomData, + a, + b, + name, + component_access: Access::new(), + archetype_component_access: Access::new(), + } + } +} + +impl System for CombinatorSystem +where + Func: Combine + 'static, + A: System, + B: System, +{ + type In = Func::In; + type Out = Func::Out; + + fn name(&self) -> Cow<'static, str> { + self.name.clone() + } + + fn type_id(&self) -> std::any::TypeId { + std::any::TypeId::of::() + } + + fn component_access(&self) -> &crate::query::Access { + &self.component_access + } + + fn archetype_component_access( + &self, + ) -> &crate::query::Access { + &self.archetype_component_access + } + + fn is_send(&self) -> bool { + self.a.is_send() && self.b.is_send() + } + + fn is_exclusive(&self) -> bool { + self.a.is_exclusive() || self.b.is_exclusive() + } + + unsafe fn run_unsafe(&mut self, input: Self::In, world: &crate::prelude::World) -> Self::Out { + Func::combine( + input, + world, + |input, w| self.a.run_unsafe(input, w), + |input, w| self.b.run_unsafe(input, w), + ) + } + + fn run(&mut self, input: Self::In, world: &mut World) -> Self::Out { + Func::combine_exclusive( + input, + world, + |input, w| self.a.run(input, w), + |input, w| self.b.run(input, w), + ) + } + + fn apply_buffers(&mut self, world: &mut crate::prelude::World) { + self.a.apply_buffers(world); + self.b.apply_buffers(world); + } + + fn initialize(&mut self, world: &mut crate::prelude::World) { + self.a.initialize(world); + self.b.initialize(world); + self.component_access.extend(self.a.component_access()); + self.component_access.extend(self.b.component_access()); + } + + fn update_archetype_component_access(&mut self, world: &crate::prelude::World) { + self.a.update_archetype_component_access(world); + self.b.update_archetype_component_access(world); + + self.archetype_component_access + .extend(self.a.archetype_component_access()); + self.archetype_component_access + .extend(self.b.archetype_component_access()); + } + + fn check_change_tick(&mut self, change_tick: u32) { + self.a.check_change_tick(change_tick); + self.b.check_change_tick(change_tick); + } + + fn get_last_change_tick(&self) -> u32 { + self.a.get_last_change_tick() + } + + fn set_last_change_tick(&mut self, last_change_tick: u32) { + self.a.set_last_change_tick(last_change_tick); + self.b.set_last_change_tick(last_change_tick); + } +} + +/// SAFETY: Both systems are read-only, so the system combining them will only read from the world. +unsafe impl ReadOnlySystem for CombinatorSystem +where + Func: Combine + 'static, + A: ReadOnlySystem, + B: ReadOnlySystem, +{ +} diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index dab5dd654d825..7ef6780544727 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -98,6 +98,7 @@ //! - All tuples between 1 to 16 elements where each element implements [`SystemParam`] //! - [`()` (unit primitive type)](https://doc.rust-lang.org/stable/std/primitive.unit.html) +mod combinator; mod commands; mod exclusive_function_system; mod exclusive_system_param; @@ -108,6 +109,7 @@ mod system; mod system_param; mod system_piping; +pub use combinator::*; pub use commands::*; pub use exclusive_function_system::*; pub use exclusive_system_param::*; From 7d3c53ed095676768c01300da3b185b559a80608 Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Fri, 10 Feb 2023 08:36:14 -0500 Subject: [PATCH 02/38] implement `PipeSystem` using `CombinatorSystem` --- crates/bevy_ecs/src/system/system_piping.rs | 143 ++++---------------- 1 file changed, 29 insertions(+), 114 deletions(-) diff --git a/crates/bevy_ecs/src/system/system_piping.rs b/crates/bevy_ecs/src/system/system_piping.rs index 4787fccd469d0..ea91b7690f24a 100644 --- a/crates/bevy_ecs/src/system/system_piping.rs +++ b/crates/bevy_ecs/src/system/system_piping.rs @@ -1,13 +1,10 @@ use crate::{ - archetype::ArchetypeComponentId, - component::ComponentId, - query::Access, system::{IntoSystem, System}, world::World, }; -use std::{any::TypeId, borrow::Cow}; +use std::borrow::Cow; -use super::ReadOnlySystem; +use super::{CombinatorSystem, Combine}; /// A [`System`] created by piping the output of the first system into the input of the second. /// @@ -48,122 +45,40 @@ use super::ReadOnlySystem; /// result.ok().filter(|&n| n < 100) /// } /// ``` -pub struct PipeSystem { - system_a: SystemA, - system_b: SystemB, - name: Cow<'static, str>, - component_access: Access, - archetype_component_access: Access, -} - -impl PipeSystem { - /// Manual constructor for creating a [`PipeSystem`]. - /// This should only be used when [`IntoPipeSystem::pipe`] cannot be used, - /// such as in `const` contexts. - pub const fn new(system_a: SystemA, system_b: SystemB, name: Cow<'static, str>) -> Self { - Self { - system_a, - system_b, - name, - component_access: Access::new(), - archetype_component_access: Access::new(), - } - } -} - -impl> System for PipeSystem { - type In = SystemA::In; - type Out = SystemB::Out; - - fn name(&self) -> Cow<'static, str> { - self.name.clone() - } - - fn type_id(&self) -> TypeId { - TypeId::of::<(SystemA, SystemB)>() - } - - fn archetype_component_access(&self) -> &Access { - &self.archetype_component_access - } - - fn component_access(&self) -> &Access { - &self.component_access - } - - fn is_send(&self) -> bool { - self.system_a.is_send() && self.system_b.is_send() - } - - fn is_exclusive(&self) -> bool { - self.system_a.is_exclusive() || self.system_b.is_exclusive() - } - - unsafe fn run_unsafe(&mut self, input: Self::In, world: &World) -> Self::Out { - let out = self.system_a.run_unsafe(input, world); - self.system_b.run_unsafe(out, world) - } - - // needed to make exclusive systems work - fn run(&mut self, input: Self::In, world: &mut World) -> Self::Out { - let out = self.system_a.run(input, world); - self.system_b.run(out, world) - } - - fn apply_buffers(&mut self, world: &mut World) { - self.system_a.apply_buffers(world); - self.system_b.apply_buffers(world); - } +pub type PipeSystem = CombinatorSystem; - fn initialize(&mut self, world: &mut World) { - self.system_a.initialize(world); - self.system_b.initialize(world); - self.component_access - .extend(self.system_a.component_access()); - self.component_access - .extend(self.system_b.component_access()); - } - - fn update_archetype_component_access(&mut self, world: &World) { - self.system_a.update_archetype_component_access(world); - self.system_b.update_archetype_component_access(world); +#[doc(hidden)] +pub struct Pipe; - self.archetype_component_access - .extend(self.system_a.archetype_component_access()); - self.archetype_component_access - .extend(self.system_b.archetype_component_access()); - } - - fn check_change_tick(&mut self, change_tick: u32) { - self.system_a.check_change_tick(change_tick); - self.system_b.check_change_tick(change_tick); - } - - fn get_last_change_tick(&self) -> u32 { - self.system_a.get_last_change_tick() - } - - fn set_last_change_tick(&mut self, last_change_tick: u32) { - self.system_a.set_last_change_tick(last_change_tick); - self.system_b.set_last_change_tick(last_change_tick); +impl Combine for Pipe +where + A: System, + B: System, +{ + type In = A::In; + type Out = B::Out; + + fn combine( + input: Self::In, + world: &World, + a: impl FnOnce(::In, &World) -> ::Out, + b: impl FnOnce(::In, &World) -> ::Out, + ) -> Self::Out { + let payload = a(input, world); + b(payload, world) } - fn default_system_sets(&self) -> Vec> { - let mut system_sets = self.system_a.default_system_sets(); - system_sets.extend_from_slice(&self.system_b.default_system_sets()); - system_sets + fn combine_exclusive( + input: Self::In, + world: &mut World, + a: impl FnOnce(::In, &mut World) -> ::Out, + b: impl FnOnce(::In, &mut World) -> ::Out, + ) -> Self::Out { + let payload = a(input, world); + b(payload, world) } } -/// SAFETY: Both systems are read-only, so piping them together will only read from the world. -unsafe impl> ReadOnlySystem - for PipeSystem -where - SystemA: ReadOnlySystem, - SystemB: ReadOnlySystem, -{ -} - /// An extension trait providing the [`IntoPipeSystem::pipe`] method to pass input from one system into the next. /// /// The first system must have return type `T` From f2d5b08909b0179eb17ca6c90d098add6ccc22fa Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Fri, 10 Feb 2023 09:18:21 -0500 Subject: [PATCH 03/38] and `and_then`/`or_else` combinators --- crates/bevy_ecs/src/schedule/condition.rs | 89 ++++++++++++++++++++++- 1 file changed, 87 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/schedule/condition.rs b/crates/bevy_ecs/src/schedule/condition.rs index 763d88d8e0901..fbbf5cbf8cf00 100644 --- a/crates/bevy_ecs/src/schedule/condition.rs +++ b/crates/bevy_ecs/src/schedule/condition.rs @@ -1,4 +1,9 @@ -use crate::system::BoxedSystem; +use std::borrow::Cow; + +use crate::{ + system::{BoxedSystem, CombinatorSystem, Combine, IntoSystem, System}, + world::World, +}; pub type BoxedCondition = BoxedSystem<(), bool>; @@ -6,7 +11,21 @@ pub type BoxedCondition = BoxedSystem<(), bool>; /// /// Implemented for functions and closures that convert into [`System`](crate::system::System) /// with [read-only](crate::system::ReadOnlySystemParam) parameters. -pub trait Condition: sealed::Condition {} +pub trait Condition: sealed::Condition { + fn and_then>(self, and_then: C) -> AndThen { + let a = IntoSystem::into_system(self); + let b = IntoSystem::into_system(and_then); + let name = format!("{} && {}", a.name(), b.name()); + CombinatorSystem::new(a, b, Cow::Owned(name)) + } + + fn or_else>(self, or_else: C) -> OrElse { + let a = IntoSystem::into_system(self); + let b = IntoSystem::into_system(or_else); + let name = format!("{} || {}", a.name(), b.name()); + CombinatorSystem::new(a, b, Cow::Owned(name)) + } +} impl Condition for F where F: sealed::Condition {} @@ -142,3 +161,69 @@ pub mod common_conditions { condition.pipe(|In(val): In| !val) } } + +pub type AndThen = CombinatorSystem; + +pub type OrElse = CombinatorSystem; + +#[doc(hidden)] +pub struct AndThenMarker; + +impl Combine for AndThenMarker +where + In: Copy, + A: System, + B: System, +{ + type In = In; + type Out = bool; + + fn combine( + input: Self::In, + world: &World, + a: impl FnOnce(::In, &World) -> ::Out, + b: impl FnOnce(::In, &World) -> ::Out, + ) -> Self::Out { + a(input, world) && b(input, world) + } + + fn combine_exclusive( + input: Self::In, + world: &mut World, + a: impl FnOnce(::In, &mut World) -> ::Out, + b: impl FnOnce(::In, &mut World) -> ::Out, + ) -> Self::Out { + a(input, world) && b(input, world) + } +} + +#[doc(hidden)] +pub struct OrElseMarker; + +impl Combine for OrElseMarker +where + In: Copy, + A: System, + B: System, +{ + type In = In; + type Out = bool; + + fn combine( + input: Self::In, + world: &World, + a: impl FnOnce(::In, &World) -> ::Out, + b: impl FnOnce(::In, &World) -> ::Out, + ) -> Self::Out { + a(input, world) || b(input, world) + } + + fn combine_exclusive( + input: Self::In, + world: &mut World, + a: impl FnOnce(::In, &mut World) -> ::Out, + b: impl FnOnce(::In, &mut World) -> ::Out, + ) -> Self::Out { + a(input, world) || b(input, world) + } +} From 6fa420a890b477b0ca17bf3a809fa267dc37dca0 Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Fri, 10 Feb 2023 09:39:27 -0500 Subject: [PATCH 04/38] tweak a safety comment Co-authored-by: Alice Cecile --- crates/bevy_ecs/src/system/combinator.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/system/combinator.rs b/crates/bevy_ecs/src/system/combinator.rs index d988d8ed0face..bd7107c3bc7ca 100644 --- a/crates/bevy_ecs/src/system/combinator.rs +++ b/crates/bevy_ecs/src/system/combinator.rs @@ -137,7 +137,7 @@ where } } -/// SAFETY: Both systems are read-only, so the system combining them will only read from the world. +/// SAFETY: Both systems are read-only, so any system created by combining them will only read from the world. unsafe impl ReadOnlySystem for CombinatorSystem where Func: Combine + 'static, From 8e2579d1ba303bb8557af3bd20780e6589eaf7a7 Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Fri, 10 Feb 2023 10:13:06 -0500 Subject: [PATCH 05/38] add docs to `and_then` and `or_else` --- crates/bevy_ecs/src/schedule/condition.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/bevy_ecs/src/schedule/condition.rs b/crates/bevy_ecs/src/schedule/condition.rs index fbbf5cbf8cf00..facd1cecd6551 100644 --- a/crates/bevy_ecs/src/schedule/condition.rs +++ b/crates/bevy_ecs/src/schedule/condition.rs @@ -12,6 +12,11 @@ pub type BoxedCondition = BoxedSystem<(), bool>; /// Implemented for functions and closures that convert into [`System`](crate::system::System) /// with [read-only](crate::system::ReadOnlySystemParam) parameters. pub trait Condition: sealed::Condition { + /// Returns a new run condition that only returns `true` + /// if both this one and the passed `and_then` return `true`. + /// + /// The returned run condition is short-circuiting, meaning + /// `and_then` will only be invoked if `self` returns `true`. fn and_then>(self, and_then: C) -> AndThen { let a = IntoSystem::into_system(self); let b = IntoSystem::into_system(and_then); @@ -19,6 +24,11 @@ pub trait Condition: sealed::Condition { CombinatorSystem::new(a, b, Cow::Owned(name)) } + /// Returns a new run condition that returns `true` + /// if either this one or the passed `or_else` return `true`. + /// + /// The returned run condition is short-circuiting, meaning + /// `and_then` will only be invoked if `self` returns `false`. fn or_else>(self, or_else: C) -> OrElse { let a = IntoSystem::into_system(self); let b = IntoSystem::into_system(or_else); From d91d90637c7de44755b737082d4e34319e37b06d Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Fri, 10 Feb 2023 10:18:13 -0500 Subject: [PATCH 06/38] add docs to `CombinatorSystem` --- crates/bevy_ecs/src/system/combinator.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/bevy_ecs/src/system/combinator.rs b/crates/bevy_ecs/src/system/combinator.rs index bd7107c3bc7ca..c49896f0229db 100644 --- a/crates/bevy_ecs/src/system/combinator.rs +++ b/crates/bevy_ecs/src/system/combinator.rs @@ -25,6 +25,8 @@ pub trait Combine { ) -> Self::Out; } +/// A [`System`] defined by combining two other systems. +/// The behavior of this combinator is specified by implementing the [`Combine`] trait. pub struct CombinatorSystem { _marker: PhantomData Func>, a: A, From 78524ba58b7e0c40e3b04468a993a4356f770052 Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Fri, 10 Feb 2023 10:50:43 -0500 Subject: [PATCH 07/38] add a doctest to `Combine` --- crates/bevy_ecs/src/system/combinator.rs | 79 ++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/crates/bevy_ecs/src/system/combinator.rs b/crates/bevy_ecs/src/system/combinator.rs index c49896f0229db..60a0840685246 100644 --- a/crates/bevy_ecs/src/system/combinator.rs +++ b/crates/bevy_ecs/src/system/combinator.rs @@ -6,6 +6,85 @@ use crate::{ use super::{ReadOnlySystem, System}; +/// Customizes the behavior of a [`CombinatorSystem`]. +/// +/// # Examples +/// +/// ``` +/// use bevy_ecs::prelude::*; +/// use bevy_ecs::system::{CombinatorSystem, Combine}; +/// +/// // A system combinator that performs an exclusive-or (XOR) +/// // operation on the output of two systems. +/// pub struct XorSystem = CombinatorSystem; +/// +/// // This struct is used to customize the behavior of our combinator. +/// pub struct XorMarker; +/// +/// impl Combine for XorMarker +/// where A: System, +/// where B: System, +/// { +/// type In = (); +/// type Out = bool; +/// +/// fn combine( +/// _input: Self::In, +/// world: &World, +/// a: impl FnOnce(A::In, &World) -> A::Out, +/// b: impl FnOnce(B::In, &World) -> B::Out, +/// ) -> Self::Out { +/// a((), world) ^ b((), world) +/// } +/// +/// fn combine_exclusive( +/// _input: Self::In, +/// world: &mut World, +/// a: impl FnOnce(A::In, &mut World) -> A::Out, +/// b: impl FnOnce(B::In, &mut World) -> B::Out, +/// ) -> Self::Out { +/// a((), world) ^ b((), world) +/// } +/// } +/// +/// # #[derive(Resource) struct A(u32); +/// # #[derive(Resource) struct B(u32); +/// # #[derive(Resource, Default) struct RanFlag(bool); +/// # let mut world = World::new(); +/// # world.init_resource::(); +/// # +/// # let mut app = Schedule::new(); +/// app.add_system(my_system.run_if(Xor::new( +/// state_equals(A(1)), +/// state_equals(B(1)), +/// // The name of the combined system. +/// Cow::Borrowed("a ^ b"), +/// ))); +/// # fn my_system(mut flag: ResMut) { flag.0 = true; } +/// # +/// # world.insert_resource(A(0)); +/// # world.insert_resoruce(B(0)); +/// # schedule.run(&mut world); +/// # // Neither condition passes, so the system does not run. +/// # assert!(!world.resource::().0); +/// # +/// # world.insert_resource(A(1)); +/// # schedule.run(&mut world); +/// # // Only the first condition passes, so the system runs. +/// # assert!(world.resource::().0); +/// # world.resource_mut::().0 = false; +/// # +/// # world.insert_resource(B(1)); +/// # schedule.run(&mut world); +/// # // Both conditions pass, so the system does not run. +/// # assert!(!world.resource::().0); +/// # +/// # world.insert_resource(A(0)); +/// # schedule.run(&mut world); +/// # // Only the second condition passes, so the system runs. +/// # assert!(world.resource::().0); +/// # world.resource_mut::().0 = false; +/// ``` pub trait Combine { type In; type Out; From d353e062abf801217c75e0af149d7c34d872e170 Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Fri, 10 Feb 2023 11:25:50 -0500 Subject: [PATCH 08/38] update doctest --- crates/bevy_ecs/src/system/combinator.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/system/combinator.rs b/crates/bevy_ecs/src/system/combinator.rs index 60a0840685246..885688dd25297 100644 --- a/crates/bevy_ecs/src/system/combinator.rs +++ b/crates/bevy_ecs/src/system/combinator.rs @@ -16,7 +16,7 @@ use super::{ReadOnlySystem, System}; /// /// // A system combinator that performs an exclusive-or (XOR) /// // operation on the output of two systems. -/// pub struct XorSystem = CombinatorSystem; +/// pub type Xor = CombinatorSystem; /// /// // This struct is used to customize the behavior of our combinator. /// pub struct XorMarker; From 435c657d2fecb2910e515420ae203c9db477d2e1 Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Fri, 10 Feb 2023 17:53:25 -0500 Subject: [PATCH 09/38] fix some mistakes in the doctest --- crates/bevy_ecs/src/system/combinator.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/bevy_ecs/src/system/combinator.rs b/crates/bevy_ecs/src/system/combinator.rs index 885688dd25297..1da54c4bea43e 100644 --- a/crates/bevy_ecs/src/system/combinator.rs +++ b/crates/bevy_ecs/src/system/combinator.rs @@ -21,7 +21,7 @@ use super::{ReadOnlySystem, System}; /// // This struct is used to customize the behavior of our combinator. /// pub struct XorMarker; /// -/// impl Combine for XorMarker +/// impl Combine for XorMarker /// where A: System, /// where B: System, /// { @@ -47,9 +47,9 @@ use super::{ReadOnlySystem, System}; /// } /// } /// -/// # #[derive(Resource) struct A(u32); -/// # #[derive(Resource) struct B(u32); -/// # #[derive(Resource, Default) struct RanFlag(bool); +/// # #[derive(Resource)] struct A(u32); +/// # #[derive(Resource)] struct B(u32); +/// # #[derive(Resource, Default)] struct RanFlag(bool); /// # let mut world = World::new(); /// # world.init_resource::(); /// # From 40ab70a5cee906963e3562369eb3850dd9d58e99 Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Fri, 10 Feb 2023 18:17:46 -0500 Subject: [PATCH 10/38] typo --- crates/bevy_ecs/src/system/combinator.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/system/combinator.rs b/crates/bevy_ecs/src/system/combinator.rs index 1da54c4bea43e..7ce9d4bccbb96 100644 --- a/crates/bevy_ecs/src/system/combinator.rs +++ b/crates/bevy_ecs/src/system/combinator.rs @@ -63,7 +63,7 @@ use super::{ReadOnlySystem, System}; /// # fn my_system(mut flag: ResMut) { flag.0 = true; } /// # /// # world.insert_resource(A(0)); -/// # world.insert_resoruce(B(0)); +/// # world.insert_resource(B(0)); /// # schedule.run(&mut world); /// # // Neither condition passes, so the system does not run. /// # assert!(!world.resource::().0); From 2b76184204c80df47065c7cb5c5ee3ce04a2f182 Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Fri, 10 Feb 2023 18:46:53 -0500 Subject: [PATCH 11/38] use `IntoSystem` properly --- crates/bevy_ecs/src/system/combinator.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/system/combinator.rs b/crates/bevy_ecs/src/system/combinator.rs index 7ce9d4bccbb96..670d63b9ec2c5 100644 --- a/crates/bevy_ecs/src/system/combinator.rs +++ b/crates/bevy_ecs/src/system/combinator.rs @@ -55,8 +55,8 @@ use super::{ReadOnlySystem, System}; /// # /// # let mut app = Schedule::new(); /// app.add_system(my_system.run_if(Xor::new( -/// state_equals(A(1)), -/// state_equals(B(1)), +/// IntoSystem::into_system(state_equals(A(1))), +/// IntoSystem::into_system(state_equals(B(1))), /// // The name of the combined system. /// Cow::Borrowed("a ^ b"), /// ))); From 5a437c68156ca527e0d323646fb796f7e855f93a Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Fri, 10 Feb 2023 18:53:23 -0500 Subject: [PATCH 12/38] `resource` -> `state` --- crates/bevy_ecs/src/system/combinator.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/system/combinator.rs b/crates/bevy_ecs/src/system/combinator.rs index 670d63b9ec2c5..97ead2663bff4 100644 --- a/crates/bevy_ecs/src/system/combinator.rs +++ b/crates/bevy_ecs/src/system/combinator.rs @@ -55,8 +55,8 @@ use super::{ReadOnlySystem, System}; /// # /// # let mut app = Schedule::new(); /// app.add_system(my_system.run_if(Xor::new( -/// IntoSystem::into_system(state_equals(A(1))), -/// IntoSystem::into_system(state_equals(B(1))), +/// IntoSystem::into_system(resource_equals(A(1))), +/// IntoSystem::into_system(resource_equals(B(1))), /// // The name of the combined system. /// Cow::Borrowed("a ^ b"), /// ))); From 1fd4d1e16e547c533ccaf5e312259ab35f855532 Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Fri, 10 Feb 2023 18:55:37 -0500 Subject: [PATCH 13/38] reorder generics --- crates/bevy_ecs/src/system/combinator.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/system/combinator.rs b/crates/bevy_ecs/src/system/combinator.rs index 97ead2663bff4..93146cf2422d8 100644 --- a/crates/bevy_ecs/src/system/combinator.rs +++ b/crates/bevy_ecs/src/system/combinator.rs @@ -16,7 +16,7 @@ use super::{ReadOnlySystem, System}; /// /// // A system combinator that performs an exclusive-or (XOR) /// // operation on the output of two systems. -/// pub type Xor = CombinatorSystem; +/// pub type Xor = CombinatorSystem; /// /// // This struct is used to customize the behavior of our combinator. /// pub struct XorMarker; From d76bc895ed58c93dfcdf64b37327fb8bca57b71c Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Fri, 10 Feb 2023 18:56:16 -0500 Subject: [PATCH 14/38] derive eq --- crates/bevy_ecs/src/system/combinator.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/system/combinator.rs b/crates/bevy_ecs/src/system/combinator.rs index 93146cf2422d8..f726bcf909ed0 100644 --- a/crates/bevy_ecs/src/system/combinator.rs +++ b/crates/bevy_ecs/src/system/combinator.rs @@ -47,8 +47,8 @@ use super::{ReadOnlySystem, System}; /// } /// } /// -/// # #[derive(Resource)] struct A(u32); -/// # #[derive(Resource)] struct B(u32); +/// # #[derive(Resource, PartialEq, Eq)] struct A(u32); +/// # #[derive(Resource, PartialEq, Eq)] struct B(u32); /// # #[derive(Resource, Default)] struct RanFlag(bool); /// # let mut world = World::new(); /// # world.init_resource::(); From 9d212e80505c6cf2db2c8d0b2a017de805bc898c Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Fri, 10 Feb 2023 18:57:25 -0500 Subject: [PATCH 15/38] update renamed variables --- crates/bevy_ecs/src/system/combinator.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/bevy_ecs/src/system/combinator.rs b/crates/bevy_ecs/src/system/combinator.rs index f726bcf909ed0..12b6316593f0f 100644 --- a/crates/bevy_ecs/src/system/combinator.rs +++ b/crates/bevy_ecs/src/system/combinator.rs @@ -64,23 +64,23 @@ use super::{ReadOnlySystem, System}; /// # /// # world.insert_resource(A(0)); /// # world.insert_resource(B(0)); -/// # schedule.run(&mut world); +/// # app.run(&mut world); /// # // Neither condition passes, so the system does not run. /// # assert!(!world.resource::().0); /// # /// # world.insert_resource(A(1)); -/// # schedule.run(&mut world); +/// # app.run(&mut world); /// # // Only the first condition passes, so the system runs. /// # assert!(world.resource::().0); /// # world.resource_mut::().0 = false; /// # /// # world.insert_resource(B(1)); -/// # schedule.run(&mut world); +/// # app.run(&mut world); /// # // Both conditions pass, so the system does not run. /// # assert!(!world.resource::().0); /// # /// # world.insert_resource(A(0)); -/// # schedule.run(&mut world); +/// # app.run(&mut world); /// # // Only the second condition passes, so the system runs. /// # assert!(world.resource::().0); /// # world.resource_mut::().0 = false; From 892b1dae545558526b0deef5f838e95dddc7ce79 Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Fri, 10 Feb 2023 19:00:18 -0500 Subject: [PATCH 16/38] use fully qualiified `Cow` --- crates/bevy_ecs/src/system/combinator.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/system/combinator.rs b/crates/bevy_ecs/src/system/combinator.rs index 12b6316593f0f..66f36e5b6ccd5 100644 --- a/crates/bevy_ecs/src/system/combinator.rs +++ b/crates/bevy_ecs/src/system/combinator.rs @@ -58,7 +58,7 @@ use super::{ReadOnlySystem, System}; /// IntoSystem::into_system(resource_equals(A(1))), /// IntoSystem::into_system(resource_equals(B(1))), /// // The name of the combined system. -/// Cow::Borrowed("a ^ b"), +/// std::borrow::Cow::Borrowed("a ^ b"), /// ))); /// # fn my_system(mut flag: ResMut) { flag.0 = true; } /// # From c25c564bee8a50adebf79f63cec20905265603b0 Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Fri, 10 Feb 2023 19:01:12 -0500 Subject: [PATCH 17/38] fix a where clause --- crates/bevy_ecs/src/system/combinator.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/system/combinator.rs b/crates/bevy_ecs/src/system/combinator.rs index 66f36e5b6ccd5..231aa47df6ab9 100644 --- a/crates/bevy_ecs/src/system/combinator.rs +++ b/crates/bevy_ecs/src/system/combinator.rs @@ -22,8 +22,9 @@ use super::{ReadOnlySystem, System}; /// pub struct XorMarker; /// /// impl Combine for XorMarker -/// where A: System, -/// where B: System, +/// where +/// A: System, +/// B: System, /// { /// type In = (); /// type Out = bool; From b7753261eda08f65629ea1cd7ae550b090d2a556 Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Fri, 10 Feb 2023 19:06:23 -0500 Subject: [PATCH 18/38] simplify `Combine::combine` --- crates/bevy_ecs/src/schedule/condition.rs | 14 ++++++-------- crates/bevy_ecs/src/system/combinator.rs | 17 +++++++---------- crates/bevy_ecs/src/system/system_piping.rs | 9 ++++----- 3 files changed, 17 insertions(+), 23 deletions(-) diff --git a/crates/bevy_ecs/src/schedule/condition.rs b/crates/bevy_ecs/src/schedule/condition.rs index facd1cecd6551..69e570d70fed1 100644 --- a/crates/bevy_ecs/src/schedule/condition.rs +++ b/crates/bevy_ecs/src/schedule/condition.rs @@ -190,11 +190,10 @@ where fn combine( input: Self::In, - world: &World, - a: impl FnOnce(::In, &World) -> ::Out, - b: impl FnOnce(::In, &World) -> ::Out, + a: impl FnOnce(::In) -> ::Out, + b: impl FnOnce(::In) -> ::Out, ) -> Self::Out { - a(input, world) && b(input, world) + a(input) && b(input) } fn combine_exclusive( @@ -221,11 +220,10 @@ where fn combine( input: Self::In, - world: &World, - a: impl FnOnce(::In, &World) -> ::Out, - b: impl FnOnce(::In, &World) -> ::Out, + a: impl FnOnce(::In) -> ::Out, + b: impl FnOnce(::In) -> ::Out, ) -> Self::Out { - a(input, world) || b(input, world) + a(input) || b(input) } fn combine_exclusive( diff --git a/crates/bevy_ecs/src/system/combinator.rs b/crates/bevy_ecs/src/system/combinator.rs index 231aa47df6ab9..ed4172962936f 100644 --- a/crates/bevy_ecs/src/system/combinator.rs +++ b/crates/bevy_ecs/src/system/combinator.rs @@ -31,11 +31,10 @@ use super::{ReadOnlySystem, System}; /// /// fn combine( /// _input: Self::In, -/// world: &World, -/// a: impl FnOnce(A::In, &World) -> A::Out, -/// b: impl FnOnce(B::In, &World) -> B::Out, +/// a: impl FnOnce(A::In) -> A::Out, +/// b: impl FnOnce(B::In) -> B::Out, /// ) -> Self::Out { -/// a((), world) ^ b((), world) +/// a(()) ^ b(()) /// } /// /// fn combine_exclusive( @@ -92,9 +91,8 @@ pub trait Combine { fn combine( input: Self::In, - world: &World, - a: impl FnOnce(A::In, &World) -> A::Out, - b: impl FnOnce(B::In, &World) -> B::Out, + a: impl FnOnce(A::In) -> A::Out, + b: impl FnOnce(B::In) -> B::Out, ) -> Self::Out; fn combine_exclusive( @@ -167,9 +165,8 @@ where unsafe fn run_unsafe(&mut self, input: Self::In, world: &crate::prelude::World) -> Self::Out { Func::combine( input, - world, - |input, w| self.a.run_unsafe(input, w), - |input, w| self.b.run_unsafe(input, w), + |input| self.a.run_unsafe(input, world), + |input| self.b.run_unsafe(input, world), ) } diff --git a/crates/bevy_ecs/src/system/system_piping.rs b/crates/bevy_ecs/src/system/system_piping.rs index ea91b7690f24a..371671c3b438e 100644 --- a/crates/bevy_ecs/src/system/system_piping.rs +++ b/crates/bevy_ecs/src/system/system_piping.rs @@ -60,12 +60,11 @@ where fn combine( input: Self::In, - world: &World, - a: impl FnOnce(::In, &World) -> ::Out, - b: impl FnOnce(::In, &World) -> ::Out, + a: impl FnOnce(::In) -> ::Out, + b: impl FnOnce(::In) -> ::Out, ) -> Self::Out { - let payload = a(input, world); - b(payload, world) + let value = a(input); + b(value) } fn combine_exclusive( From f6e3fbc78220ef6fe37e0c730022a76f17ab58d1 Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Fri, 10 Feb 2023 19:21:42 -0500 Subject: [PATCH 19/38] remove `Combine::combine_exclusive` --- crates/bevy_ecs/src/schedule/condition.rs | 23 +-------------- crates/bevy_ecs/src/system/combinator.rs | 31 ++++++--------------- crates/bevy_ecs/src/system/system_piping.rs | 15 +--------- 3 files changed, 11 insertions(+), 58 deletions(-) diff --git a/crates/bevy_ecs/src/schedule/condition.rs b/crates/bevy_ecs/src/schedule/condition.rs index 69e570d70fed1..176a131f08356 100644 --- a/crates/bevy_ecs/src/schedule/condition.rs +++ b/crates/bevy_ecs/src/schedule/condition.rs @@ -1,9 +1,6 @@ use std::borrow::Cow; -use crate::{ - system::{BoxedSystem, CombinatorSystem, Combine, IntoSystem, System}, - world::World, -}; +use crate::system::{BoxedSystem, CombinatorSystem, Combine, IntoSystem, System}; pub type BoxedCondition = BoxedSystem<(), bool>; @@ -195,15 +192,6 @@ where ) -> Self::Out { a(input) && b(input) } - - fn combine_exclusive( - input: Self::In, - world: &mut World, - a: impl FnOnce(::In, &mut World) -> ::Out, - b: impl FnOnce(::In, &mut World) -> ::Out, - ) -> Self::Out { - a(input, world) && b(input, world) - } } #[doc(hidden)] @@ -225,13 +213,4 @@ where ) -> Self::Out { a(input) || b(input) } - - fn combine_exclusive( - input: Self::In, - world: &mut World, - a: impl FnOnce(::In, &mut World) -> ::Out, - b: impl FnOnce(::In, &mut World) -> ::Out, - ) -> Self::Out { - a(input, world) || b(input, world) - } } diff --git a/crates/bevy_ecs/src/system/combinator.rs b/crates/bevy_ecs/src/system/combinator.rs index ed4172962936f..005bb1dbb22d3 100644 --- a/crates/bevy_ecs/src/system/combinator.rs +++ b/crates/bevy_ecs/src/system/combinator.rs @@ -36,15 +36,6 @@ use super::{ReadOnlySystem, System}; /// ) -> Self::Out { /// a(()) ^ b(()) /// } -/// -/// fn combine_exclusive( -/// _input: Self::In, -/// world: &mut World, -/// a: impl FnOnce(A::In, &mut World) -> A::Out, -/// b: impl FnOnce(B::In, &mut World) -> B::Out, -/// ) -> Self::Out { -/// a((), world) ^ b((), world) -/// } /// } /// /// # #[derive(Resource, PartialEq, Eq)] struct A(u32); @@ -94,13 +85,6 @@ pub trait Combine { a: impl FnOnce(A::In) -> A::Out, b: impl FnOnce(B::In) -> B::Out, ) -> Self::Out; - - fn combine_exclusive( - input: Self::In, - world: &mut World, - a: impl FnOnce(A::In, &mut World) -> A::Out, - b: impl FnOnce(B::In, &mut World) -> B::Out, - ) -> Self::Out; } /// A [`System`] defined by combining two other systems. @@ -171,12 +155,15 @@ where } fn run(&mut self, input: Self::In, world: &mut World) -> Self::Out { - Func::combine_exclusive( - input, - world, - |input, w| self.a.run(input, w), - |input, w| self.b.run(input, w), - ) + let world = <*mut World>::from(world); + // SAFETY: Only one of `run_a` and `run_b` can be called at any given time. (TODO: make sure this is true) + // Since the mutable reference to `world` only exists within the scope of either closure, + // we can be sure that they will never alias one another. + let run_a = |input| self.a.run(input, unsafe { &mut *world }); + #[allow(clippy::undocumented_unsafe_blocks)] + let run_b = |input| self.b.run(input, unsafe { &mut *world }); + + Func::combine(input, run_a, run_b) } fn apply_buffers(&mut self, world: &mut crate::prelude::World) { diff --git a/crates/bevy_ecs/src/system/system_piping.rs b/crates/bevy_ecs/src/system/system_piping.rs index 371671c3b438e..b1551e64f3562 100644 --- a/crates/bevy_ecs/src/system/system_piping.rs +++ b/crates/bevy_ecs/src/system/system_piping.rs @@ -1,7 +1,4 @@ -use crate::{ - system::{IntoSystem, System}, - world::World, -}; +use crate::system::{IntoSystem, System}; use std::borrow::Cow; use super::{CombinatorSystem, Combine}; @@ -66,16 +63,6 @@ where let value = a(input); b(value) } - - fn combine_exclusive( - input: Self::In, - world: &mut World, - a: impl FnOnce(::In, &mut World) -> ::Out, - b: impl FnOnce(::In, &mut World) -> ::Out, - ) -> Self::Out { - let payload = a(input, world); - b(payload, world) - } } /// An extension trait providing the [`IntoPipeSystem::pipe`] method to pass input from one system into the next. From 6cd5eefe69f2aa166a29257d9b50843809f02588 Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Fri, 10 Feb 2023 20:37:11 -0500 Subject: [PATCH 20/38] add safety comments to `run_unsafe` --- crates/bevy_ecs/src/system/combinator.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/bevy_ecs/src/system/combinator.rs b/crates/bevy_ecs/src/system/combinator.rs index 005bb1dbb22d3..1a888d9a446cd 100644 --- a/crates/bevy_ecs/src/system/combinator.rs +++ b/crates/bevy_ecs/src/system/combinator.rs @@ -149,6 +149,9 @@ where unsafe fn run_unsafe(&mut self, input: Self::In, world: &crate::prelude::World) -> Self::Out { Func::combine( input, + // SAFETY: The world accesses for both underlying systems have been registered, + // so the caller will guarantee that no other systems will conflict with `a` or `b`. + // Only one of these closures can be called at once, so they will not conflict with each other. |input| self.a.run_unsafe(input, world), |input| self.b.run_unsafe(input, world), ) From 685c7124d81e295392204bcab340ab6708d8003d Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Fri, 10 Feb 2023 20:54:22 -0500 Subject: [PATCH 21/38] remove some unnecessary fully qualified types --- crates/bevy_ecs/src/system/combinator.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/crates/bevy_ecs/src/system/combinator.rs b/crates/bevy_ecs/src/system/combinator.rs index 1a888d9a446cd..69dfb97d5bd95 100644 --- a/crates/bevy_ecs/src/system/combinator.rs +++ b/crates/bevy_ecs/src/system/combinator.rs @@ -128,13 +128,11 @@ where std::any::TypeId::of::() } - fn component_access(&self) -> &crate::query::Access { + fn component_access(&self) -> &Access { &self.component_access } - fn archetype_component_access( - &self, - ) -> &crate::query::Access { + fn archetype_component_access(&self) -> &Access { &self.archetype_component_access } @@ -146,7 +144,7 @@ where self.a.is_exclusive() || self.b.is_exclusive() } - unsafe fn run_unsafe(&mut self, input: Self::In, world: &crate::prelude::World) -> Self::Out { + unsafe fn run_unsafe(&mut self, input: Self::In, world: &World) -> Self::Out { Func::combine( input, // SAFETY: The world accesses for both underlying systems have been registered, @@ -169,19 +167,19 @@ where Func::combine(input, run_a, run_b) } - fn apply_buffers(&mut self, world: &mut crate::prelude::World) { + fn apply_buffers(&mut self, world: &mut World) { self.a.apply_buffers(world); self.b.apply_buffers(world); } - fn initialize(&mut self, world: &mut crate::prelude::World) { + fn initialize(&mut self, world: &mut World) { self.a.initialize(world); self.b.initialize(world); self.component_access.extend(self.a.component_access()); self.component_access.extend(self.b.component_access()); } - fn update_archetype_component_access(&mut self, world: &crate::prelude::World) { + fn update_archetype_component_access(&mut self, world: &World) { self.a.update_archetype_component_access(world); self.b.update_archetype_component_access(world); From affe2d94043673657561a4b2c56d097827a5023f Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Fri, 10 Feb 2023 21:09:29 -0500 Subject: [PATCH 22/38] use `UnsafeCell` to retain lifetimes --- crates/bevy_ecs/src/system/combinator.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/crates/bevy_ecs/src/system/combinator.rs b/crates/bevy_ecs/src/system/combinator.rs index 69dfb97d5bd95..df966976c0dce 100644 --- a/crates/bevy_ecs/src/system/combinator.rs +++ b/crates/bevy_ecs/src/system/combinator.rs @@ -1,4 +1,6 @@ -use std::{borrow::Cow, marker::PhantomData}; +use std::{borrow::Cow, cell::UnsafeCell, marker::PhantomData}; + +use bevy_ptr::UnsafeCellDeref; use crate::{ archetype::ArchetypeComponentId, component::ComponentId, prelude::World, query::Access, @@ -155,14 +157,16 @@ where ) } - fn run(&mut self, input: Self::In, world: &mut World) -> Self::Out { - let world = <*mut World>::from(world); + fn run<'w>(&mut self, input: Self::In, world: &'w mut World) -> Self::Out { + // SAFETY: Converting `&mut T` -> `&UnsafeCell` + // is explicitly allowed in the docs for `UnsafeCell`. + let world: &'w UnsafeCell = unsafe { std::mem::transmute(world) }; // SAFETY: Only one of `run_a` and `run_b` can be called at any given time. (TODO: make sure this is true) // Since the mutable reference to `world` only exists within the scope of either closure, // we can be sure that they will never alias one another. - let run_a = |input| self.a.run(input, unsafe { &mut *world }); + let run_a = |input| self.a.run(input, unsafe { world.deref_mut() }); #[allow(clippy::undocumented_unsafe_blocks)] - let run_b = |input| self.b.run(input, unsafe { &mut *world }); + let run_b = |input| self.b.run(input, unsafe { world.deref_mut() }); Func::combine(input, run_a, run_b) } From c077ed13639be89ecf2c6518247c844e24cf180c Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Sat, 11 Feb 2023 08:43:54 -0500 Subject: [PATCH 23/38] improve safety comments for `CombinatorSystem` --- crates/bevy_ecs/src/system/combinator.rs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/crates/bevy_ecs/src/system/combinator.rs b/crates/bevy_ecs/src/system/combinator.rs index df966976c0dce..9af7ca0c9d36a 100644 --- a/crates/bevy_ecs/src/system/combinator.rs +++ b/crates/bevy_ecs/src/system/combinator.rs @@ -151,7 +151,8 @@ where input, // SAFETY: The world accesses for both underlying systems have been registered, // so the caller will guarantee that no other systems will conflict with `a` or `b`. - // Only one of these closures can be called at once, so they will not conflict with each other. + // Since these closures are `!Send + !Synd + !'static`, they can never be called + // in parallel, so their world accesses will not conflict with each other. |input| self.a.run_unsafe(input, world), |input| self.b.run_unsafe(input, world), ) @@ -161,14 +162,15 @@ where // SAFETY: Converting `&mut T` -> `&UnsafeCell` // is explicitly allowed in the docs for `UnsafeCell`. let world: &'w UnsafeCell = unsafe { std::mem::transmute(world) }; - // SAFETY: Only one of `run_a` and `run_b` can be called at any given time. (TODO: make sure this is true) - // Since the mutable reference to `world` only exists within the scope of either closure, - // we can be sure that they will never alias one another. - let run_a = |input| self.a.run(input, unsafe { world.deref_mut() }); - #[allow(clippy::undocumented_unsafe_blocks)] - let run_b = |input| self.b.run(input, unsafe { world.deref_mut() }); - - Func::combine(input, run_a, run_b) + Func::combine( + input, + // SAFETY: Since these closures are `!Send + !Synd + !'static`, they can never + // be called in parallel. Since mutable access to `world` only exists within + // the scope of either closure, we can be sure they will never alias one another. + |input| self.a.run(input, unsafe { world.deref_mut() }), + #[allow(clippy::undocumented_unsafe_blocks)] + |input| self.b.run(input, unsafe { world.deref_mut() }), + ) } fn apply_buffers(&mut self, world: &mut World) { From 6f2a8d053ffb41367d06719ccdb29a0ee9f7f618 Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Sun, 12 Feb 2023 10:42:39 -0500 Subject: [PATCH 24/38] add a note pointing to the docs on `Combine` --- crates/bevy_ecs/src/system/combinator.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/bevy_ecs/src/system/combinator.rs b/crates/bevy_ecs/src/system/combinator.rs index 9af7ca0c9d36a..5765bc454fa69 100644 --- a/crates/bevy_ecs/src/system/combinator.rs +++ b/crates/bevy_ecs/src/system/combinator.rs @@ -91,6 +91,7 @@ pub trait Combine { /// A [`System`] defined by combining two other systems. /// The behavior of this combinator is specified by implementing the [`Combine`] trait. +/// For a full usage example, see the docs for `Combine`. pub struct CombinatorSystem { _marker: PhantomData Func>, a: A, From 3dcfc0b70b3915df40fb906047052cee9e09d4a3 Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Sun, 12 Feb 2023 10:50:24 -0500 Subject: [PATCH 25/38] document members of `Combine` trait --- crates/bevy_ecs/src/system/combinator.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crates/bevy_ecs/src/system/combinator.rs b/crates/bevy_ecs/src/system/combinator.rs index 5765bc454fa69..d444a5a587234 100644 --- a/crates/bevy_ecs/src/system/combinator.rs +++ b/crates/bevy_ecs/src/system/combinator.rs @@ -79,9 +79,16 @@ use super::{ReadOnlySystem, System}; /// # world.resource_mut::().0 = false; /// ``` pub trait Combine { + /// The [input](System::In) type for a [`CombinatorSystem`]. type In; + + /// The [output](System::Out) type for a [`CombinatorSystem`]. type Out; + /// When used in a [`CombinatorSystem`], this function customizes how + /// the two composite systems are invoked and their outputs are combined. + /// + /// See the trait-level docs for [`Combine`] for an example implementation. fn combine( input: Self::In, a: impl FnOnce(A::In) -> A::Out, From 7a4c5a5bd0c588e91f800f2bd8341d21e477e519 Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Sun, 12 Feb 2023 10:52:39 -0500 Subject: [PATCH 26/38] use a correct parameter name --- crates/bevy_ecs/src/schedule/condition.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/schedule/condition.rs b/crates/bevy_ecs/src/schedule/condition.rs index 176a131f08356..8dff47764b53f 100644 --- a/crates/bevy_ecs/src/schedule/condition.rs +++ b/crates/bevy_ecs/src/schedule/condition.rs @@ -25,7 +25,7 @@ pub trait Condition: sealed::Condition { /// if either this one or the passed `or_else` return `true`. /// /// The returned run condition is short-circuiting, meaning - /// `and_then` will only be invoked if `self` returns `false`. + /// `or_else` will only be invoked if `self` returns `false`. fn or_else>(self, or_else: C) -> OrElse { let a = IntoSystem::into_system(self); let b = IntoSystem::into_system(or_else); From a09eb71ee44d44be0549493a5c81679ecea37fb6 Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Sun, 12 Feb 2023 11:22:28 -0500 Subject: [PATCH 27/38] add a doctest to `and_then` --- crates/bevy_ecs/src/schedule/condition.rs | 37 +++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/crates/bevy_ecs/src/schedule/condition.rs b/crates/bevy_ecs/src/schedule/condition.rs index 8dff47764b53f..da84e250c113b 100644 --- a/crates/bevy_ecs/src/schedule/condition.rs +++ b/crates/bevy_ecs/src/schedule/condition.rs @@ -14,6 +14,43 @@ pub trait Condition: sealed::Condition { /// /// The returned run condition is short-circuiting, meaning /// `and_then` will only be invoked if `self` returns `true`. + /// + /// # Examples + /// + /// ```should_panic + /// use bevy_ecs::prelude::*; + /// + /// #[derive(Resource, PartialEq)] + /// struct R(u32); + /// + /// # let mut app = Schedule::new(); + /// # let mut world = World::new(); + /// app.add_system( + /// // The `resource_equals` run condition will panic since we don't initialize `R`, + /// // just like if we used `Res` in a system. + /// my_system.run_if(resource_equals(R(0))), + /// ); + /// # app.run(&mut world); + /// ``` + /// + /// Use `.and_then()` to avoid checking the condition. + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # #[derive(Resource)] + /// # struct R(u32); + /// # let mut app = Schedule::new(); + /// # let mut world = World::new(); + /// app.add_system( + /// // `resource_equals` condition will only get run if the resource `R` exists. + /// my_system.run_if(resource_exists::().and_then(resource_equals(R(0)))), + /// ); + /// # app.run(&mut world); + /// ``` + /// + /// Note that in this case, it's better to just use the run condition [`resource_exists_and_equals`]. + /// + /// [`resource_exists_and_equals`]: common_conditions::resource_exists_and_equals fn and_then>(self, and_then: C) -> AndThen { let a = IntoSystem::into_system(self); let b = IntoSystem::into_system(and_then); From 0a26bbd5daa928d4704f4647b21398790503501e Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Sun, 12 Feb 2023 11:34:03 -0500 Subject: [PATCH 28/38] add a doctest to `or_else` --- crates/bevy_ecs/src/schedule/condition.rs | 35 +++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/crates/bevy_ecs/src/schedule/condition.rs b/crates/bevy_ecs/src/schedule/condition.rs index da84e250c113b..0bf5218f156bb 100644 --- a/crates/bevy_ecs/src/schedule/condition.rs +++ b/crates/bevy_ecs/src/schedule/condition.rs @@ -63,6 +63,41 @@ pub trait Condition: sealed::Condition { /// /// The returned run condition is short-circuiting, meaning /// `or_else` will only be invoked if `self` returns `false`. + /// + /// # Examples + /// + /// ``` + /// use bevy_ecs::prelude::*; + /// + /// #[derive(Resource, PartialEq)] + /// struct A(u32); + /// + /// #[derive(Resource, PartialEq)] + /// struct B(u32); + /// + /// # let mut app = Schedule::new(); + /// # let mut world = World::new(); + /// # #[derive(Resource)] struct C(bool); + /// # fn my_system(mut c: ResMut) { c.0 = true; } + /// app.add_system( + /// // Only run the system if either `A` or `B` exist. + /// my_system.run_if(state_exists::().or_else(state_exists::())), + /// ); + /// # + /// # world.insert_resource(C(false)); + /// # app.run(&mut world); + /// # assert!(!world.resource::().0); + /// # + /// # world.insert_resource(A(0)); + /// # app.run(&mut world); + /// # assert!(world.resource::().0); + /// # + /// # world.remove_resource::(); + /// # world.insert_resource(B(0)); + /// # world.insert_resource(C(false)); + /// # app.run(&mut world); + /// # assert!(world.resource::().0); + /// ``` fn or_else>(self, or_else: C) -> OrElse { let a = IntoSystem::into_system(self); let b = IntoSystem::into_system(or_else); From 514e4ca6a73e47188e243ab1bb00505f2db06da2 Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Sun, 12 Feb 2023 11:34:20 -0500 Subject: [PATCH 29/38] add a missing derive --- crates/bevy_ecs/src/schedule/condition.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/schedule/condition.rs b/crates/bevy_ecs/src/schedule/condition.rs index 0bf5218f156bb..fa7cf5708e543 100644 --- a/crates/bevy_ecs/src/schedule/condition.rs +++ b/crates/bevy_ecs/src/schedule/condition.rs @@ -37,7 +37,7 @@ pub trait Condition: sealed::Condition { /// /// ``` /// # use bevy_ecs::prelude::*; - /// # #[derive(Resource)] + /// # #[derive(Resource, PartialEq)] /// # struct R(u32); /// # let mut app = Schedule::new(); /// # let mut world = World::new(); From 5e948c7e05daf4cfbb73467d3f8f3689025bb692 Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Sun, 12 Feb 2023 11:41:05 -0500 Subject: [PATCH 30/38] `state` -> `resource` --- crates/bevy_ecs/src/schedule/condition.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/schedule/condition.rs b/crates/bevy_ecs/src/schedule/condition.rs index fa7cf5708e543..a3ddc97997b24 100644 --- a/crates/bevy_ecs/src/schedule/condition.rs +++ b/crates/bevy_ecs/src/schedule/condition.rs @@ -81,7 +81,7 @@ pub trait Condition: sealed::Condition { /// # fn my_system(mut c: ResMut) { c.0 = true; } /// app.add_system( /// // Only run the system if either `A` or `B` exist. - /// my_system.run_if(state_exists::().or_else(state_exists::())), + /// my_system.run_if(resource_exists::().or_else(resource_exists::())), /// ); /// # /// # world.insert_resource(C(false)); From ef2667147c405e24a64d84d3026aae167a14ec8c Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Sun, 12 Feb 2023 11:41:56 -0500 Subject: [PATCH 31/38] declare a forgotten function --- crates/bevy_ecs/src/schedule/condition.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/bevy_ecs/src/schedule/condition.rs b/crates/bevy_ecs/src/schedule/condition.rs index a3ddc97997b24..2bcda84b6f8bb 100644 --- a/crates/bevy_ecs/src/schedule/condition.rs +++ b/crates/bevy_ecs/src/schedule/condition.rs @@ -25,6 +25,7 @@ pub trait Condition: sealed::Condition { /// /// # let mut app = Schedule::new(); /// # let mut world = World::new(); + /// # fn my_system() {} /// app.add_system( /// // The `resource_equals` run condition will panic since we don't initialize `R`, /// // just like if we used `Res` in a system. @@ -41,6 +42,7 @@ pub trait Condition: sealed::Condition { /// # struct R(u32); /// # let mut app = Schedule::new(); /// # let mut world = World::new(); + /// # fn my_system() {} /// app.add_system( /// // `resource_equals` condition will only get run if the resource `R` exists. /// my_system.run_if(resource_exists::().and_then(resource_equals(R(0)))), From b1ddab8fdb8dbf0afb4497a27048eea77ef603b5 Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Sun, 12 Feb 2023 11:45:24 -0500 Subject: [PATCH 32/38] add docs to `AndThen`/`OrElse` typedefs --- crates/bevy_ecs/src/schedule/condition.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/bevy_ecs/src/schedule/condition.rs b/crates/bevy_ecs/src/schedule/condition.rs index 2bcda84b6f8bb..0452554f94d77 100644 --- a/crates/bevy_ecs/src/schedule/condition.rs +++ b/crates/bevy_ecs/src/schedule/condition.rs @@ -243,8 +243,10 @@ pub mod common_conditions { } } +/// Combines the outputs of two systems using the `&&` operator. pub type AndThen = CombinatorSystem; +/// Combines the outputs of two systems using the `||` operator. pub type OrElse = CombinatorSystem; #[doc(hidden)] From 96301c850038f49b76c2dcec930c553399cdce5f Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Sun, 12 Feb 2023 11:48:17 -0500 Subject: [PATCH 33/38] add `Condition<>` to the prelude --- crates/bevy_ecs/src/lib.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 77d6befc5d68d..d155d1887e2c7 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -36,9 +36,10 @@ pub mod prelude { query::{Added, AnyOf, ChangeTrackers, Changed, Or, QueryState, With, Without}, removal_detection::RemovedComponents, schedule::{ - apply_state_transition, apply_system_buffers, common_conditions::*, IntoSystemConfig, - IntoSystemConfigs, IntoSystemSet, IntoSystemSetConfig, IntoSystemSetConfigs, NextState, - OnEnter, OnExit, OnUpdate, Schedule, Schedules, State, States, SystemSet, + apply_state_transition, apply_system_buffers, common_conditions::*, Condition, + IntoSystemConfig, IntoSystemConfigs, IntoSystemSet, IntoSystemSetConfig, + IntoSystemSetConfigs, NextState, OnEnter, OnExit, OnUpdate, Schedule, Schedules, State, + States, SystemSet, }, system::{ adapter as system_adapter, From caafd21d684b290561c30407dc8b7d7b6a568aab Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Sun, 12 Feb 2023 12:41:59 -0500 Subject: [PATCH 34/38] Update crates/bevy_ecs/src/system/combinator.rs Co-authored-by: Alice Cecile --- crates/bevy_ecs/src/system/combinator.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/system/combinator.rs b/crates/bevy_ecs/src/system/combinator.rs index d444a5a587234..081435cbac6da 100644 --- a/crates/bevy_ecs/src/system/combinator.rs +++ b/crates/bevy_ecs/src/system/combinator.rs @@ -98,7 +98,7 @@ pub trait Combine { /// A [`System`] defined by combining two other systems. /// The behavior of this combinator is specified by implementing the [`Combine`] trait. -/// For a full usage example, see the docs for `Combine`. +/// For a full usage example, see the docs for [`Combine`]. pub struct CombinatorSystem { _marker: PhantomData Func>, a: A, From 1302b62b841f61872bbc433a2a68f5bde033e578 Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Sun, 12 Feb 2023 12:50:12 -0500 Subject: [PATCH 35/38] remove an extra word --- crates/bevy_ecs/src/schedule/condition.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/schedule/condition.rs b/crates/bevy_ecs/src/schedule/condition.rs index 0452554f94d77..2d4592098b8f6 100644 --- a/crates/bevy_ecs/src/schedule/condition.rs +++ b/crates/bevy_ecs/src/schedule/condition.rs @@ -44,7 +44,7 @@ pub trait Condition: sealed::Condition { /// # let mut world = World::new(); /// # fn my_system() {} /// app.add_system( - /// // `resource_equals` condition will only get run if the resource `R` exists. + /// // `resource_equals` will only get run if the resource `R` exists. /// my_system.run_if(resource_exists::().and_then(resource_equals(R(0)))), /// ); /// # app.run(&mut world); From 9c1ed0846c597740dd58691c597d9118b5d71eb2 Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Thu, 16 Feb 2023 10:42:43 -0500 Subject: [PATCH 36/38] combine default system sets --- crates/bevy_ecs/src/system/combinator.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/bevy_ecs/src/system/combinator.rs b/crates/bevy_ecs/src/system/combinator.rs index 081435cbac6da..7de7b9b053868 100644 --- a/crates/bevy_ecs/src/system/combinator.rs +++ b/crates/bevy_ecs/src/system/combinator.rs @@ -216,6 +216,12 @@ where self.a.set_last_change_tick(last_change_tick); self.b.set_last_change_tick(last_change_tick); } + + fn default_system_sets(&self) -> Vec> { + let mut default_sets = self.a.default_system_sets(); + default_sets.append(&mut self.b.default_system_sets()); + default_sets + } } /// SAFETY: Both systems are read-only, so any system created by combining them will only read from the world. From 5a14d657176da41df25485b964b052603db34d1a Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Sat, 18 Feb 2023 09:08:33 -0500 Subject: [PATCH 37/38] add a note about default short circuiting behavior --- examples/ecs/run_conditions.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/ecs/run_conditions.rs b/examples/ecs/run_conditions.rs index a0147d93ae1e9..adb7e43788f41 100644 --- a/examples/ecs/run_conditions.rs +++ b/examples/ecs/run_conditions.rs @@ -16,8 +16,10 @@ fn main() { // The common_conditions module has a few useful run conditions // for checking resources and states. These are included in the prelude. .run_if(resource_exists::()) - // This is our custom run condition. Both this and the - // above condition must be true for the system to run. + // This is a custom run condition, defined using a system that returns + // a `bool` and which has read-only `SystemParam`s. + // Both run conditions must return `true` in order for the system to run. + // Note that this second run condition will be evaluated even if the first returns `false`. .run_if(has_user_input), ) .add_system( From 35bfba1e85589233c213eb82ce57c2c78f513ffe Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Sat, 18 Feb 2023 09:16:48 -0500 Subject: [PATCH 38/38] add `and_then` to the example for run conditions --- examples/ecs/run_conditions.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/ecs/run_conditions.rs b/examples/ecs/run_conditions.rs index adb7e43788f41..4cd43b2149759 100644 --- a/examples/ecs/run_conditions.rs +++ b/examples/ecs/run_conditions.rs @@ -24,17 +24,17 @@ fn main() { ) .add_system( print_input_counter - // This is also a custom run condition but this time in the form of a closure. - // This is useful for small, simple run conditions you don't need to reuse. - // All the normal rules still apply: all parameters must be read only except for local parameters. - // In this case we will only run if the input counter resource exists and has changed but not just been added. - .run_if(|res: Option>| { - if let Some(counter) = res { - counter.is_changed() && !counter.is_added() - } else { - false - } - }), + // `.and_then()` is a run condition combinator that only evaluates the second condition + // if the first condition returns `true`. This behavior is known as "short-circuiting", + // and is how the `&&` operator works in Rust (as well as most C-family languages). + // In this case, the short-circuiting behavior prevents the second run condition from + // panicking if the `InputCounter` resource has not been initialized. + .run_if(resource_exists::().and_then( + // This is a custom run condition in the form of a closure. + // This is useful for small, simple run conditions you don't need to reuse. + // All the normal rules still apply: all parameters must be read only except for local parameters. + |counter: Res| counter.is_changed() && !counter.is_added(), + )), ) .add_system( print_time_message