bevy/crates/bevy_state/src/condition.rs
newclarityex ecccd57417
Generic system config (#17962)
# Objective
Prevents duplicate implementation between IntoSystemConfigs and
IntoSystemSetConfigs using a generic, adds a NodeType trait for more
config flexibility (opening the door to implement
https://github.com/bevyengine/bevy/issues/14195?).

## Solution
Followed writeup by @ItsDoot:
https://hackmd.io/@doot/rJeefFHc1x

Removes IntoSystemConfigs and IntoSystemSetConfigs, instead using
IntoNodeConfigs with generics.

## Testing
Pending

---

## Showcase
N/A

## Migration Guide
SystemSetConfigs -> NodeConfigs<InternedSystemSet>
SystemConfigs -> NodeConfigs<ScheduleSystem>
IntoSystemSetConfigs -> IntoNodeConfigs<InternedSystemSet, M>
IntoSystemConfigs -> IntoNodeConfigs<ScheduleSystem, M>

---------

Co-authored-by: Christian Hughes <9044780+ItsDoot@users.noreply.github.com>
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2025-03-12 00:12:30 +00:00

199 lines
5.8 KiB
Rust

use crate::state::{State, States};
use bevy_ecs::{change_detection::DetectChanges, system::Res};
/// A [`Condition`](bevy_ecs::prelude::Condition)-satisfying system that returns `true`
/// if the state machine exists.
///
/// # Example
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # use bevy_state::prelude::*;
/// # use bevy_app::{App, Update};
/// # use bevy_state::app::StatesPlugin;
/// # #[derive(Resource, Default)]
/// # struct Counter(u8);
/// # let mut app = App::new();
/// # app
/// # .init_resource::<Counter>()
/// # .add_plugins(StatesPlugin);
/// #[derive(States, Clone, Copy, Default, Eq, PartialEq, Hash, Debug)]
/// enum GameState {
/// #[default]
/// Playing,
/// Paused,
/// }
///
/// app.add_systems(Update,
/// // `state_exists` will only return true if the
/// // given state exists
/// my_system.run_if(state_exists::<GameState>),
/// );
///
/// fn my_system(mut counter: ResMut<Counter>) {
/// counter.0 += 1;
/// }
///
/// // `GameState` does not yet exist so `my_system` won't run
/// app.update();
/// assert_eq!(app.world().resource::<Counter>().0, 0);
///
/// app.init_state::<GameState>();
///
/// // `GameState` now exists so `my_system` will run
/// app.update();
/// assert_eq!(app.world().resource::<Counter>().0, 1);
/// ```
pub fn state_exists<S: States>(current_state: Option<Res<State<S>>>) -> bool {
current_state.is_some()
}
/// Generates a [`Condition`](bevy_ecs::prelude::Condition)-satisfying closure that returns `true`
/// if the state machine is currently in `state`.
///
/// Will return `false` if the state does not exist or if not in `state`.
///
/// # Example
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # use bevy_state::prelude::*;
/// # use bevy_app::{App, Update};
/// # use bevy_state::app::StatesPlugin;
/// # #[derive(Resource, Default)]
/// # struct Counter(u8);
/// # let mut app = App::new();
/// # app
/// # .init_resource::<Counter>()
/// # .add_plugins(StatesPlugin);
/// #[derive(States, Clone, Copy, Default, Eq, PartialEq, Hash, Debug)]
/// enum GameState {
/// #[default]
/// Playing,
/// Paused,
/// }
///
/// app
/// .init_state::<GameState>()
/// .add_systems(Update, (
/// // `in_state` will only return true if the
/// // given state equals the given value
/// play_system.run_if(in_state(GameState::Playing)),
/// pause_system.run_if(in_state(GameState::Paused)),
/// ));
///
/// fn play_system(mut counter: ResMut<Counter>) {
/// counter.0 += 1;
/// }
///
/// fn pause_system(mut counter: ResMut<Counter>) {
/// counter.0 -= 1;
/// }
///
/// // We default to `GameState::Playing` so `play_system` runs
/// app.update();
/// assert_eq!(app.world().resource::<Counter>().0, 1);
///
/// app.insert_state(GameState::Paused);
///
/// // Now that we are in `GameState::Pause`, `pause_system` will run
/// app.update();
/// assert_eq!(app.world().resource::<Counter>().0, 0);
/// ```
pub fn in_state<S: States>(state: S) -> impl FnMut(Option<Res<State<S>>>) -> bool + Clone {
move |current_state: Option<Res<State<S>>>| match current_state {
Some(current_state) => *current_state == state,
None => false,
}
}
/// A [`Condition`](bevy_ecs::prelude::Condition)-satisfying system that returns `true`
/// if the state machine changed state.
///
/// To do things on transitions to/from specific states, use their respective OnEnter/OnExit
/// schedules. Use this run condition if you want to detect any change, regardless of the value.
///
/// Returns false if the state does not exist or the state has not changed.
///
/// # Example
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # use bevy_state::prelude::*;
/// # use bevy_state::app::StatesPlugin;
/// # use bevy_app::{App, Update};
/// # #[derive(Resource, Default)]
/// # struct Counter(u8);
/// # let mut app = App::new();
/// # app
/// # .init_resource::<Counter>()
/// # .add_plugins(StatesPlugin);
/// #[derive(States, Clone, Copy, Default, Eq, PartialEq, Hash, Debug)]
/// enum GameState {
/// #[default]
/// Playing,
/// Paused,
/// }
///
/// app
/// .init_state::<GameState>()
/// .add_systems(Update,
/// // `state_changed` will only return true if the
/// // given states value has just been updated or
/// // the state has just been added
/// my_system.run_if(state_changed::<GameState>),
/// );
///
/// fn my_system(mut counter: ResMut<Counter>) {
/// counter.0 += 1;
/// }
///
/// // `GameState` has just been added so `my_system` will run
/// app.update();
/// assert_eq!(app.world().resource::<Counter>().0, 1);
///
/// // `GameState` has not been updated so `my_system` will not run
/// app.update();
/// assert_eq!(app.world().resource::<Counter>().0, 1);
///
/// app.insert_state(GameState::Paused);
///
/// // Now that `GameState` has been updated `my_system` will run
/// app.update();
/// assert_eq!(app.world().resource::<Counter>().0, 2);
/// ```
pub fn state_changed<S: States>(current_state: Option<Res<State<S>>>) -> bool {
let Some(current_state) = current_state else {
return false;
};
current_state.is_changed()
}
#[cfg(test)]
mod tests {
use bevy_ecs::schedule::{Condition, IntoScheduleConfigs, Schedule};
use crate::prelude::*;
use bevy_state_macros::States;
#[derive(States, PartialEq, Eq, Debug, Default, Hash, Clone)]
enum TestState {
#[default]
A,
B,
}
fn test_system() {}
// Ensure distributive_run_if compiles with the common conditions.
#[test]
fn distributive_run_if_compiles() {
Schedule::default().add_systems(
(test_system, test_system)
.distributive_run_if(state_exists::<TestState>)
.distributive_run_if(in_state(TestState::A).or(in_state(TestState::B)))
.distributive_run_if(state_changed::<TestState>),
);
}
}