diff --git a/crates/bevy_ecs/src/schedule/condition.rs b/crates/bevy_ecs/src/schedule/condition.rs index 4983804dab9c5..fca479625f049 100644 --- a/crates/bevy_ecs/src/schedule/condition.rs +++ b/crates/bevy_ecs/src/schedule/condition.rs @@ -1,10 +1,6 @@ use alloc::{borrow::Cow, boxed::Box, format}; -use core::ops::Not; -use crate::system::{ - Adapt, AdapterSystem, CombinatorSystem, Combine, IntoSystem, ReadOnlySystem, System, SystemIn, - SystemInput, -}; +use crate::system::{CombinatorSystem, Combine, ReadOnlySystem, System, SystemIn, SystemInput}; /// A type-erased run condition stored in a [`Box`]. pub type BoxedCondition = Box>; @@ -29,7 +25,8 @@ pub type BoxedCondition = Box>; /// `SystemCondition` trait has `Marker` type parameter, which has no special meaning, /// but exists to work around the limitation of Rust's trait system. /// -/// Type parameter in return type can be set to `<()>` by calling [`IntoSystem::into_system`], +/// Type parameter in return type can be set to `<()>` by calling +/// [`crate::system::IntoSystem::into_system`], /// but usually have to be specified when passing a condition to a function. /// /// ``` @@ -150,9 +147,13 @@ pub trait SystemCondition: /// 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>(self, and: C) -> And { - let a = IntoSystem::into_system(self); - let b = IntoSystem::into_system(and); + fn and(self, and: C) -> And + where + CIn: SystemInput, + C: SystemCondition, + { + let a = self.into_condition_system(); + let b = and.into_condition_system(); let name = format!("{} && {}", a.name(), b.name()); CombinatorSystem::new(a, b, Cow::Owned(name)) } @@ -202,9 +203,13 @@ pub trait SystemCondition: /// ), /// ); /// ``` - fn nand>(self, nand: C) -> Nand { - let a = IntoSystem::into_system(self); - let b = IntoSystem::into_system(nand); + fn nand(self, nand: C) -> Nand + where + CIn: SystemInput, + C: SystemCondition, + { + let a = self.into_condition_system(); + let b = nand.into_condition_system(); let name = format!("!({} && {})", a.name(), b.name()); CombinatorSystem::new(a, b, Cow::Owned(name)) } @@ -254,9 +259,13 @@ pub trait SystemCondition: /// ), /// ); /// ``` - fn nor>(self, nor: C) -> Nor { - let a = IntoSystem::into_system(self); - let b = IntoSystem::into_system(nor); + fn nor(self, nor: C) -> Nor + where + CIn: SystemInput, + C: SystemCondition, + { + let a = self.into_condition_system(); + let b = nor.into_condition_system(); let name = format!("!({} || {})", a.name(), b.name()); CombinatorSystem::new(a, b, Cow::Owned(name)) } @@ -301,9 +310,13 @@ pub trait SystemCondition: /// # app.run(&mut world); /// # assert!(world.resource::().0); /// ``` - fn or>(self, or: C) -> Or { - let a = IntoSystem::into_system(self); - let b = IntoSystem::into_system(or); + fn or(self, or: C) -> Or + where + CIn: SystemInput, + C: SystemCondition, + { + let a = self.into_condition_system(); + let b = or.into_condition_system(); let name = format!("{} || {}", a.name(), b.name()); CombinatorSystem::new(a, b, Cow::Owned(name)) } @@ -353,9 +366,13 @@ pub trait SystemCondition: /// ), /// ); /// ``` - fn xnor>(self, xnor: C) -> Xnor { - let a = IntoSystem::into_system(self); - let b = IntoSystem::into_system(xnor); + fn xnor(self, xnor: C) -> Xnor + where + CIn: SystemInput, + C: SystemCondition, + { + let a = self.into_condition_system(); + let b = xnor.into_condition_system(); let name = format!("!({} ^ {})", a.name(), b.name()); CombinatorSystem::new(a, b, Cow::Owned(name)) } @@ -395,75 +412,128 @@ pub trait SystemCondition: /// ); /// # app.run(&mut world); /// ``` - fn xor>(self, xor: C) -> Xor { - let a = IntoSystem::into_system(self); - let b = IntoSystem::into_system(xor); + fn xor(self, xor: C) -> Xor + where + CIn: SystemInput, + C: SystemCondition, + { + let a = self.into_condition_system(); + let b = xor.into_condition_system(); let name = format!("({} ^ {})", a.name(), b.name()); CombinatorSystem::new(a, b, Cow::Owned(name)) } } -impl SystemCondition for F where - F: sealed::SystemCondition +impl SystemCondition for F +where + In: SystemInput, + F: sealed::SystemCondition, { } mod sealed { use crate::{ error::BevyError, - system::{IntoSystem, ReadOnlySystem, SystemInput}, + system::{ + Adapt, AdapterSystem, IntoAdapterSystem, IntoSystem, ReadOnlySystem, System, SystemIn, + SystemInput, + }, }; pub trait SystemCondition: IntoSystem { - // This associated type is necessary to let the compiler - // know that `Self::System` is `ReadOnlySystem`. + /// This type informs the compiler that [`Self::System`](IntoSystem::System) is [`ReadOnlySystem`]. type ReadOnlySystem: ReadOnlySystem; - fn into_condition_system(self) -> impl ReadOnlySystem; + /// The type that `Self` converts into + type ConditionSystem: ReadOnlySystem; + + fn into_condition_system(self) -> Self::ConditionSystem; } - impl SystemCondition for F + impl SystemCondition for F where + In: SystemInput, F: IntoSystem, F::System: ReadOnlySystem, { type ReadOnlySystem = F::System; + type ConditionSystem = F::System; - fn into_condition_system(self) -> impl ReadOnlySystem { + fn into_condition_system(self) -> Self::ConditionSystem { IntoSystem::into_system(self) } } - impl SystemCondition> for F + /// Used with [`AdapterSystem`] to transform the `Result<(), BevyError>` returned by the system into a `bool`. + #[doc(hidden)] + #[derive(Clone, Copy)] + pub struct ResultUnitAdapter; + + impl>> Adapt for ResultUnitAdapter { + type In = S::In; + type Out = bool; + + fn adapt( + &mut self, + input: ::Inner<'_>, + run_system: impl FnOnce(SystemIn<'_, S>) -> S::Out, + ) -> Self::Out { + run_system(input).is_ok() + } + } + + impl SystemCondition> for F where + In: SystemInput, F: IntoSystem, Marker>, F::System: ReadOnlySystem, { type ReadOnlySystem = F::System; + type ConditionSystem = AdapterSystem; - fn into_condition_system(self) -> impl ReadOnlySystem { - IntoSystem::into_system(self.map(|result| result.is_ok())) + fn into_condition_system(self) -> Self::ConditionSystem { + IntoSystem::into_system(IntoAdapterSystem::new(ResultUnitAdapter, self)) } } - impl SystemCondition> for F + /// Used with [`AdapterSystem`] to transform the `Result` returned by the system into a `bool`. + #[doc(hidden)] + #[derive(Clone, Copy)] + pub struct ResultBoolAdapter; + + impl>> Adapt for ResultBoolAdapter { + type In = S::In; + type Out = bool; + + fn adapt( + &mut self, + input: ::Inner<'_>, + run_system: impl FnOnce(SystemIn<'_, S>) -> S::Out, + ) -> Self::Out { + matches!(run_system(input), Ok(true)) + } + } + + impl SystemCondition> for F where + In: SystemInput, F: IntoSystem, Marker>, F::System: ReadOnlySystem, { type ReadOnlySystem = F::System; + type ConditionSystem = AdapterSystem; - fn into_condition_system(self) -> impl ReadOnlySystem { - IntoSystem::into_system(self.map(|result| matches!(result, Ok(true)))) + fn into_condition_system(self) -> Self::ConditionSystem { + IntoSystem::into_system(IntoAdapterSystem::new(ResultBoolAdapter, self)) } } } /// A collection of [run conditions](SystemCondition) that may be useful in any bevy app. pub mod common_conditions { - use super::{NotSystem, SystemCondition}; + use super::SystemCondition; use crate::{ change_detection::DetectChanges, event::{BufferedEvent, EventReader}, @@ -471,9 +541,11 @@ pub mod common_conditions { prelude::{Component, Query, With}, query::QueryFilter, resource::Resource, - system::{In, IntoSystem, Local, Res, System, SystemInput}, + system::{ + Adapt, AdapterSystem, In, IntoAdapterSystem, IntoSystem, Local, Res, System, SystemIn, + SystemInput, + }, }; - use alloc::format; /// A [`SystemCondition`]-satisfying system that returns `true` /// on the first time the condition is run and false every time after. @@ -1007,7 +1079,25 @@ pub mod common_conditions { !query.is_empty() } - /// Generates a [`SystemCondition`] that inverses the result of passed one. + /// Used with [`AdapterSystem`] to inverse the `bool` returned by the system. + #[doc(hidden)] + #[derive(Clone, Copy)] + pub struct InverseBoolAdapter; + + impl> Adapt for InverseBoolAdapter { + type In = S::In; + type Out = bool; + + fn adapt( + &mut self, + input: ::Inner<'_>, + run_system: impl FnOnce(SystemIn<'_, S>) -> S::Out, + ) -> Self::Out { + !run_system(input) + } + } + + /// Generates a [`SystemCondition`] that inverses the result of the passed one. /// /// # Example /// @@ -1036,14 +1126,17 @@ pub mod common_conditions { /// app.run(&mut world); /// assert_eq!(world.resource::().0, 0); /// ``` - pub fn not(condition: T) -> NotSystem + pub fn not( + condition: C, + ) -> AdapterSystem where - TOut: core::ops::Not, - T: IntoSystem<(), TOut, Marker>, + In: SystemInput, + C: SystemCondition, { - let condition = IntoSystem::into_system(condition); - let name = format!("!{}", condition.name()); - NotSystem::new(super::NotMarker, condition, name.into()) + IntoSystem::into_system(IntoAdapterSystem::new( + InverseBoolAdapter, + condition.into_condition_system(), + )) } /// Generates a [`SystemCondition`] that returns true when the passed one changes. @@ -1084,16 +1177,18 @@ pub mod common_conditions { /// app.run(&mut world); /// assert_eq!(world.resource::().0, 2); /// ``` - pub fn condition_changed(condition: C) -> impl SystemCondition<(), CIn> + pub fn condition_changed(condition: C) -> impl SystemCondition<(), CIn> where CIn: SystemInput, - C: SystemCondition, + C: SystemCondition, { - IntoSystem::into_system(condition.pipe(|In(new): In, mut prev: Local| { - let changed = *prev != new; - *prev = new; - changed - })) + IntoSystem::into_system(condition.into_condition_system().pipe( + |In(new): In, mut prev: Local| { + let changed = *prev != new; + *prev = new; + changed + }, + )) } /// Generates a [`SystemCondition`] that returns true when the result of @@ -1140,15 +1235,15 @@ pub mod common_conditions { /// app.run(&mut world); /// assert_eq!(world.resource::().0, 2); /// ``` - pub fn condition_changed_to( + pub fn condition_changed_to( to: bool, condition: C, ) -> impl SystemCondition<(), CIn> where CIn: SystemInput, - C: SystemCondition, + C: SystemCondition, { - IntoSystem::into_system(condition.pipe( + IntoSystem::into_system(condition.into_condition_system().pipe( move |In(new): In, mut prev: Local| -> bool { let now_true = *prev != new && new == to; *prev = new; @@ -1158,29 +1253,6 @@ pub mod common_conditions { } } -/// Invokes [`Not`] with the output of another system. -/// -/// See [`common_conditions::not`] for examples. -pub type NotSystem = AdapterSystem; - -/// Used with [`AdapterSystem`] to negate the output of a system via the [`Not`] operator. -#[doc(hidden)] -#[derive(Clone, Copy)] -pub struct NotMarker; - -impl> Adapt for NotMarker { - type In = S::In; - type Out = ::Output; - - fn adapt( - &mut self, - input: ::Inner<'_>, - run_system: impl FnOnce(SystemIn<'_, S>) -> S::Out, - ) -> Self::Out { - !run_system(input) - } -} - /// Combines the outputs of two systems using the `&&` operator. pub type And = CombinatorSystem; @@ -1214,7 +1286,7 @@ where fn combine( input: ::Inner<'_>, a: impl FnOnce(SystemIn<'_, A>) -> A::Out, - b: impl FnOnce(SystemIn<'_, A>) -> B::Out, + b: impl FnOnce(SystemIn<'_, B>) -> B::Out, ) -> Self::Out { a(input) && b(input) } @@ -1328,20 +1400,27 @@ where #[cfg(test)] mod tests { use super::{common_conditions::*, SystemCondition}; - use crate::event::{BufferedEvent, Event}; - use crate::query::With; use crate::{ change_detection::ResMut, component::Component, + error::BevyError, + query::{Has, With}, schedule::{IntoScheduleConfigs, Schedule}, system::Local, + system::Query, world::World, }; - use bevy_ecs_macros::Resource; + use bevy_ecs_macros::{BufferedEvent, Event, Resource}; #[derive(Resource, Default)] struct Counter(usize); + #[derive(Component)] + struct Player; + + #[derive(Component)] + struct Wounded; + fn increment_counter(mut counter: ResMut) { counter.0 += 1; } @@ -1355,6 +1434,18 @@ mod tests { *has_ran } + fn multiply_counter(factor: usize) -> impl FnMut(ResMut) { + move |mut counter| counter.0 *= factor + } + + fn single_player(q_player: Query<(), With>) -> Result<(), BevyError> { + Ok(q_player.single()?) + } + + fn wounded_player(q_player: Query, With>) -> Result { + Ok(q_player.single()?) + } + #[test] fn run_condition() { let mut world = World::new(); @@ -1408,6 +1499,46 @@ mod tests { assert_eq!(world.resource::().0, 52); } + #[test] + fn mixed_run_conditions_combinators() { + let mut world = World::new(); + let mut counter = 1; + world.insert_resource(Counter(counter)); + let mut schedule = Schedule::default(); + + schedule.add_systems(( + multiply_counter(2).run_if(single_player.and(|| true)), + multiply_counter(3).run_if((|| true).nand(wounded_player)), + multiply_counter(5).run_if((|| false).or(wounded_player)), + multiply_counter(7).run_if(wounded_player.nor(single_player)), + multiply_counter(11).run_if(single_player.xor(wounded_player)), + multiply_counter(13).run_if(single_player.xnor(|| false)), + multiply_counter(17).run_if(condition_changed(wounded_player)), + multiply_counter(19).run_if(condition_changed_to(true, single_player)), + multiply_counter(23).run_if(not(single_player)), + multiply_counter(29).run_if(not(wounded_player)), + )); + + schedule.run(&mut world); + counter *= 3 * 7 * 13 * 23 * 29; + assert_eq!(world.resource::().0, counter); + + let player = world.spawn(Player).id(); + schedule.run(&mut world); + counter *= 2 * 3 * 11 * 19 * 29; + assert_eq!(world.resource::().0, counter); + + world.entity_mut(player).insert(Wounded); + schedule.run(&mut world); + counter *= 2 * 5 * 17; + assert_eq!(world.resource::().0, counter); + + world.spawn(Player); + schedule.run(&mut world); + counter *= 3 * 7 * 13 * 17 * 23 * 29; + assert_eq!(world.resource::().0, counter); + } + #[test] fn multiple_run_conditions() { let mut world = World::new(); diff --git a/crates/bevy_ecs/src/system/adapter_system.rs b/crates/bevy_ecs/src/system/adapter_system.rs index ff63a3e8ce996..8428ef9dac70e 100644 --- a/crates/bevy_ecs/src/system/adapter_system.rs +++ b/crates/bevy_ecs/src/system/adapter_system.rs @@ -16,7 +16,6 @@ use crate::{ /// use bevy_ecs::system::{Adapt, AdapterSystem}; /// /// // A system adapter that inverts the result of a system. -/// // NOTE: Instead of manually implementing this, you can just use `bevy_ecs::schedule::common_conditions::not`. /// pub type NotSystem = AdapterSystem; /// /// // This struct is used to customize the behavior of our adapter. diff --git a/release-content/migration-guides/remove_not_system.md b/release-content/migration-guides/remove_not_system.md new file mode 100644 index 0000000000000..035486b4c40ee --- /dev/null +++ b/release-content/migration-guides/remove_not_system.md @@ -0,0 +1,8 @@ +--- +title: Remove `NotSystem` +pull_requests: [19580] +--- + +Not used anywhere in the engine and very niche to users, `NotSystem` has been removed. + +If you were using it, consider redefining it yourself as in this [example](https://docs.rs/bevy/main/bevy/ecs/system/trait.Adapt.html#examples).