From 12f005a024c163333455bfd8112893c6a8075716 Mon Sep 17 00:00:00 2001 From: Alix Bott Date: Mon, 26 Aug 2024 20:32:44 +0200 Subject: [PATCH] Add `condition_changed` and `condition_became_true` to `common_conditions` (#14917) # Objective - I needed to run a system whenever a specific condition became true after being previously false. - Other users might also need to run a system when a condition changes, regardless of if it became true or false. ## Solution - This adds two systems to common_conditions: - `condition_changed` that changes whenever the inner condition changes - `condition_became_true` that returns true whenever the inner condition becomes true after previously being false ## Testing - I added a doctest for each function --------- Co-authored-by: Alice Cecile Co-authored-by: Jan Hohenheim --- crates/bevy_ecs/src/schedule/condition.rs | 133 +++++++++++++++++++--- 1 file changed, 118 insertions(+), 15 deletions(-) diff --git a/crates/bevy_ecs/src/schedule/condition.rs b/crates/bevy_ecs/src/schedule/condition.rs index fb030467a8..99b7bd1acf 100644 --- a/crates/bevy_ecs/src/schedule/condition.rs +++ b/crates/bevy_ecs/src/schedule/condition.rs @@ -490,16 +490,16 @@ mod sealed { /// A collection of [run conditions](Condition) that may be useful in any bevy app. pub mod common_conditions { - use super::NotSystem; + use super::{Condition, NotSystem}; use crate::{ change_detection::DetectChanges, event::{Event, EventReader}, prelude::{Component, Query, With}, removal_detection::RemovedComponents, - system::{IntoSystem, Local, Res, Resource, System}, + system::{In, IntoSystem, Local, Res, Resource, System}, }; - /// A [`Condition`](super::Condition)-satisfying system that returns `true` + /// A [`Condition`]-satisfying system that returns `true` /// on the first time the condition is run and false every time after. /// /// # Example @@ -537,7 +537,7 @@ pub mod common_conditions { } } - /// A [`Condition`](super::Condition)-satisfying system that returns `true` + /// A [`Condition`]-satisfying system that returns `true` /// if the resource exists. /// /// # Example @@ -572,7 +572,7 @@ pub mod common_conditions { res.is_some() } - /// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true` + /// Generates a [`Condition`]-satisfying closure that returns `true` /// if the resource is equal to `value`. /// /// # Panics @@ -612,7 +612,7 @@ pub mod common_conditions { move |res: Res| *res == value } - /// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true` + /// Generates a [`Condition`]-satisfying closure that returns `true` /// if the resource exists and is equal to `value`. /// /// The condition will return `false` if the resource does not exist. @@ -657,7 +657,7 @@ pub mod common_conditions { } } - /// A [`Condition`](super::Condition)-satisfying system that returns `true` + /// A [`Condition`]-satisfying system that returns `true` /// if the resource of the given type has been added since the condition was last checked. /// /// # Example @@ -698,7 +698,7 @@ pub mod common_conditions { } } - /// A [`Condition`](super::Condition)-satisfying system that returns `true` + /// A [`Condition`]-satisfying system that returns `true` /// if the resource of the given type has had its value changed since the condition /// was last checked. /// @@ -752,7 +752,7 @@ pub mod common_conditions { res.is_changed() } - /// A [`Condition`](super::Condition)-satisfying system that returns `true` + /// A [`Condition`]-satisfying system that returns `true` /// if the resource of the given type has had its value changed since the condition /// was last checked. /// @@ -812,7 +812,7 @@ pub mod common_conditions { } } - /// A [`Condition`](super::Condition)-satisfying system that returns `true` + /// A [`Condition`]-satisfying system that returns `true` /// if the resource of the given type has had its value changed since the condition /// was last checked. /// @@ -889,7 +889,7 @@ pub mod common_conditions { } } - /// A [`Condition`](super::Condition)-satisfying system that returns `true` + /// A [`Condition`]-satisfying system that returns `true` /// if the resource of the given type has been removed since the condition was last checked. /// /// # Example @@ -941,7 +941,7 @@ pub mod common_conditions { } } - /// A [`Condition`](super::Condition)-satisfying system that returns `true` + /// A [`Condition`]-satisfying system that returns `true` /// if there are any new events of the given type since it was last called. /// /// # Example @@ -985,7 +985,7 @@ pub mod common_conditions { reader.read().count() > 0 } - /// A [`Condition`](super::Condition)-satisfying system that returns `true` + /// A [`Condition`]-satisfying system that returns `true` /// if there are any entities with the given component type. /// /// # Example @@ -1022,7 +1022,7 @@ pub mod common_conditions { !query.is_empty() } - /// A [`Condition`](super::Condition)-satisfying system that returns `true` + /// A [`Condition`]-satisfying system that returns `true` /// if there are any entity with a component of the given type removed. pub fn any_component_removed(mut removals: RemovedComponents) -> bool { // `RemovedComponents` based on events and therefore events need to be consumed, @@ -1033,7 +1033,7 @@ pub mod common_conditions { removals.read().count() > 0 } - /// Generates a [`Condition`](super::Condition) that inverses the result of passed one. + /// Generates a [`Condition`] that inverses the result of passed one. /// /// # Example /// @@ -1071,6 +1071,109 @@ pub mod common_conditions { let name = format!("!{}", condition.name()); NotSystem::new(super::NotMarker, condition, name.into()) } + + /// Generates a [`Condition`] that returns true when the passed one changes. + /// + /// The first time this is called, the passed condition is assumed to have been previously false. + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # #[derive(Resource, Default)] + /// # struct Counter(u8); + /// # let mut app = Schedule::default(); + /// # let mut world = World::new(); + /// # world.init_resource::(); + /// app.add_systems( + /// my_system.run_if(condition_changed(resource_exists::)), + /// ); + /// + /// #[derive(Resource)] + /// struct MyResource; + /// + /// fn my_system(mut counter: ResMut) { + /// counter.0 += 1; + /// } + /// + /// // `MyResource` is initially there, the inner condition is true, the system runs once + /// world.insert_resource(MyResource); + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 1); + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 1); + /// + /// // We remove `MyResource`, the inner condition is now false, the system runs one more time. + /// world.remove_resource::(); + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 2); + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 2); + /// ``` + pub fn condition_changed>( + condition: C, + ) -> impl Condition<(), CIn> { + condition.pipe(|In(new): In, mut prev: Local| -> bool { + let changed = *prev != new; + *prev = new; + changed + }) + } + + /// Generates a [`Condition`] that returns true when the result of + /// the passed one went from false to true since the last time this was called. + /// + /// The first time this is called, the passed condition is assumed to have been previously false. + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # #[derive(Resource, Default)] + /// # struct Counter(u8); + /// # let mut app = Schedule::default(); + /// # let mut world = World::new(); + /// # world.init_resource::(); + /// app.add_systems( + /// my_system.run_if(condition_changed_to(true, resource_exists::)), + /// ); + /// + /// #[derive(Resource)] + /// struct MyResource; + /// + /// fn my_system(mut counter: ResMut) { + /// counter.0 += 1; + /// } + /// + /// // `MyResource` is initially there, the inner condition is true, the system runs once + /// world.insert_resource(MyResource); + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 1); + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 1); + /// + /// // We remove `MyResource`, the inner condition is now false, the system doesn't run. + /// world.remove_resource::(); + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 1); + /// + /// // We reinsert `MyResource` again, so the system will run one more time + /// world.insert_resource(MyResource); + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 2); + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 2); + /// ``` + pub fn condition_changed_to>( + to: bool, + condition: C, + ) -> impl Condition<(), CIn> { + condition.pipe(move |In(new): In, mut prev: Local| -> bool { + let now_true = *prev != new && new == to; + *prev = new; + now_true + }) + } } /// Invokes [`Not`] with the output of another system.