Skip to content

Commit

Permalink
Add condition_changed and condition_became_true to `common_condit…
Browse files Browse the repository at this point in the history
…ions` (#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 <alice.i.cecile@gmail.com>
Co-authored-by: Jan Hohenheim <jan@hohenheim.ch>
  • Loading branch information
3 people authored Aug 26, 2024
1 parent 23979b8 commit 12f005a
Showing 1 changed file with 118 additions and 15 deletions.
133 changes: 118 additions & 15 deletions crates/bevy_ecs/src/schedule/condition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -612,7 +612,7 @@ pub mod common_conditions {
move |res: Res<T>| *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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
///
Expand Down Expand Up @@ -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.
///
Expand Down Expand Up @@ -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.
///
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<T: Component>(mut removals: RemovedComponents<T>) -> bool {
// `RemovedComponents` based on events and therefore events need to be consumed,
Expand All @@ -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
///
Expand Down Expand Up @@ -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::<Counter>();
/// app.add_systems(
/// my_system.run_if(condition_changed(resource_exists::<MyResource>)),
/// );
///
/// #[derive(Resource)]
/// struct MyResource;
///
/// fn my_system(mut counter: ResMut<Counter>) {
/// 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::<Counter>().0, 1);
/// app.run(&mut world);
/// assert_eq!(world.resource::<Counter>().0, 1);
///
/// // We remove `MyResource`, the inner condition is now false, the system runs one more time.
/// world.remove_resource::<MyResource>();
/// app.run(&mut world);
/// assert_eq!(world.resource::<Counter>().0, 2);
/// app.run(&mut world);
/// assert_eq!(world.resource::<Counter>().0, 2);
/// ```
pub fn condition_changed<Marker, CIn, C: Condition<Marker, CIn>>(
condition: C,
) -> impl Condition<(), CIn> {
condition.pipe(|In(new): In<bool>, mut prev: Local<bool>| -> 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::<Counter>();
/// app.add_systems(
/// my_system.run_if(condition_changed_to(true, resource_exists::<MyResource>)),
/// );
///
/// #[derive(Resource)]
/// struct MyResource;
///
/// fn my_system(mut counter: ResMut<Counter>) {
/// 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::<Counter>().0, 1);
/// app.run(&mut world);
/// assert_eq!(world.resource::<Counter>().0, 1);
///
/// // We remove `MyResource`, the inner condition is now false, the system doesn't run.
/// world.remove_resource::<MyResource>();
/// app.run(&mut world);
/// assert_eq!(world.resource::<Counter>().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::<Counter>().0, 2);
/// app.run(&mut world);
/// assert_eq!(world.resource::<Counter>().0, 2);
/// ```
pub fn condition_changed_to<Marker, CIn, C: Condition<Marker, CIn>>(
to: bool,
condition: C,
) -> impl Condition<(), CIn> {
condition.pipe(move |In(new): In<bool>, mut prev: Local<bool>| -> bool {
let now_true = *prev != new && new == to;
*prev = new;
now_true
})
}
}

/// Invokes [`Not`] with the output of another system.
Expand Down

0 comments on commit 12f005a

Please sign in to comment.