Vendor dependencies for 0.3.0 release

This commit is contained in:
2025-09-27 10:29:08 -05:00
parent 0c8d39d483
commit 82ab7f317b
26803 changed files with 16134934 additions and 0 deletions

375
vendor/bevy_state/src/app.rs vendored Normal file
View File

@@ -0,0 +1,375 @@
use bevy_app::{App, MainScheduleOrder, Plugin, PreStartup, PreUpdate, SubApp};
use bevy_ecs::{event::Events, schedule::IntoScheduleConfigs, world::FromWorld};
use bevy_utils::once;
use log::warn;
use crate::{
state::{
setup_state_transitions_in_world, ComputedStates, FreelyMutableState, NextState, State,
StateTransition, StateTransitionEvent, StateTransitionSteps, States, SubStates,
},
state_scoped::clear_state_scoped_entities,
};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::{FromReflect, GetTypeRegistration, Typed};
/// State installation methods for [`App`] and [`SubApp`].
pub trait AppExtStates {
/// Initializes a [`State`] with standard starting values.
///
/// This method is idempotent: it has no effect when called again using the same generic type.
///
/// Adds [`State<S>`] and [`NextState<S>`] resources, and enables use of the [`OnEnter`](crate::state::OnEnter),
/// [`OnTransition`](crate::state::OnTransition) and [`OnExit`](crate::state::OnExit) schedules.
/// These schedules are triggered before [`Update`](bevy_app::Update) and at startup.
///
/// If you would like to control how other systems run based on the current state, you can
/// emulate this behavior using the [`in_state`](crate::condition::in_state) [`Condition`](bevy_ecs::prelude::Condition).
///
/// Note that you can also apply state transitions at other points in the schedule
/// by triggering the [`StateTransition`](struct@StateTransition) schedule manually.
///
/// The use of any states requires the presence of [`StatesPlugin`] (which is included in `DefaultPlugins`).
fn init_state<S: FreelyMutableState + FromWorld>(&mut self) -> &mut Self;
/// Inserts a specific [`State`] to the current [`App`] and overrides any [`State`] previously
/// added of the same type.
///
/// Adds [`State<S>`] and [`NextState<S>`] resources, and enables use of the [`OnEnter`](crate::state::OnEnter),
/// [`OnTransition`](crate::state::OnTransition) and [`OnExit`](crate::state::OnExit) schedules.
/// These schedules are triggered before [`Update`](bevy_app::Update) and at startup.
///
/// If you would like to control how other systems run based on the current state, you can
/// emulate this behavior using the [`in_state`](crate::condition::in_state) [`Condition`](bevy_ecs::prelude::Condition).
///
/// Note that you can also apply state transitions at other points in the schedule
/// by triggering the [`StateTransition`](struct@StateTransition) schedule manually.
fn insert_state<S: FreelyMutableState>(&mut self, state: S) -> &mut Self;
/// Sets up a type implementing [`ComputedStates`].
///
/// This method is idempotent: it has no effect when called again using the same generic type.
fn add_computed_state<S: ComputedStates>(&mut self) -> &mut Self;
/// Sets up a type implementing [`SubStates`].
///
/// This method is idempotent: it has no effect when called again using the same generic type.
fn add_sub_state<S: SubStates>(&mut self) -> &mut Self;
/// Enable state-scoped entity clearing for state `S`.
///
/// If the [`States`] trait was derived with the `#[states(scoped_entities)]` attribute, it
/// will be called automatically.
///
/// For more information refer to [`StateScoped`](crate::state_scoped::StateScoped).
fn enable_state_scoped_entities<S: States>(&mut self) -> &mut Self;
#[cfg(feature = "bevy_reflect")]
/// Registers the state type `T` using [`App::register_type`],
/// and adds [`ReflectState`](crate::reflect::ReflectState) type data to `T` in the type registry.
///
/// This enables reflection code to access the state. For detailed information, see the docs on [`crate::reflect::ReflectState`] .
fn register_type_state<S>(&mut self) -> &mut Self
where
S: States + FromReflect + GetTypeRegistration + Typed;
#[cfg(feature = "bevy_reflect")]
/// Registers the state type `T` using [`App::register_type`],
/// and adds [`crate::reflect::ReflectState`] and [`crate::reflect::ReflectFreelyMutableState`] type data to `T` in the type registry.
///
/// This enables reflection code to access and modify the state.
/// For detailed information, see the docs on [`crate::reflect::ReflectState`] and [`crate::reflect::ReflectFreelyMutableState`].
fn register_type_mutable_state<S>(&mut self) -> &mut Self
where
S: FreelyMutableState + FromReflect + GetTypeRegistration + Typed;
}
/// Separate function to only warn once for all state installation methods.
fn warn_if_no_states_plugin_installed(app: &SubApp) {
if !app.is_plugin_added::<StatesPlugin>() {
once!(warn!(
"States were added to the app, but `StatesPlugin` is not installed."
));
}
}
impl AppExtStates for SubApp {
fn init_state<S: FreelyMutableState + FromWorld>(&mut self) -> &mut Self {
warn_if_no_states_plugin_installed(self);
if !self.world().contains_resource::<State<S>>() {
self.init_resource::<State<S>>()
.init_resource::<NextState<S>>()
.add_event::<StateTransitionEvent<S>>();
let schedule = self.get_schedule_mut(StateTransition).expect(
"The `StateTransition` schedule is missing. Did you forget to add StatesPlugin or DefaultPlugins before calling init_state?"
);
S::register_state(schedule);
let state = self.world().resource::<State<S>>().get().clone();
self.world_mut().send_event(StateTransitionEvent {
exited: None,
entered: Some(state),
});
if S::SCOPED_ENTITIES_ENABLED {
self.enable_state_scoped_entities::<S>();
}
} else {
let name = core::any::type_name::<S>();
warn!("State {} is already initialized.", name);
}
self
}
fn insert_state<S: FreelyMutableState>(&mut self, state: S) -> &mut Self {
warn_if_no_states_plugin_installed(self);
if !self.world().contains_resource::<State<S>>() {
self.insert_resource::<State<S>>(State::new(state.clone()))
.init_resource::<NextState<S>>()
.add_event::<StateTransitionEvent<S>>();
let schedule = self.get_schedule_mut(StateTransition).expect(
"The `StateTransition` schedule is missing. Did you forget to add StatesPlugin or DefaultPlugins before calling insert_state?"
);
S::register_state(schedule);
self.world_mut().send_event(StateTransitionEvent {
exited: None,
entered: Some(state),
});
if S::SCOPED_ENTITIES_ENABLED {
self.enable_state_scoped_entities::<S>();
}
} else {
// Overwrite previous state and initial event
self.insert_resource::<State<S>>(State::new(state.clone()));
self.world_mut()
.resource_mut::<Events<StateTransitionEvent<S>>>()
.clear();
self.world_mut().send_event(StateTransitionEvent {
exited: None,
entered: Some(state),
});
}
self
}
fn add_computed_state<S: ComputedStates>(&mut self) -> &mut Self {
warn_if_no_states_plugin_installed(self);
if !self
.world()
.contains_resource::<Events<StateTransitionEvent<S>>>()
{
self.add_event::<StateTransitionEvent<S>>();
let schedule = self.get_schedule_mut(StateTransition).expect(
"The `StateTransition` schedule is missing. Did you forget to add StatesPlugin or DefaultPlugins before calling add_computed_state?"
);
S::register_computed_state_systems(schedule);
let state = self
.world()
.get_resource::<State<S>>()
.map(|s| s.get().clone());
self.world_mut().send_event(StateTransitionEvent {
exited: None,
entered: state,
});
if S::SCOPED_ENTITIES_ENABLED {
self.enable_state_scoped_entities::<S>();
}
} else {
let name = core::any::type_name::<S>();
warn!("Computed state {} is already initialized.", name);
}
self
}
fn add_sub_state<S: SubStates>(&mut self) -> &mut Self {
warn_if_no_states_plugin_installed(self);
if !self
.world()
.contains_resource::<Events<StateTransitionEvent<S>>>()
{
self.init_resource::<NextState<S>>();
self.add_event::<StateTransitionEvent<S>>();
let schedule = self.get_schedule_mut(StateTransition).expect(
"The `StateTransition` schedule is missing. Did you forget to add StatesPlugin or DefaultPlugins before calling add_sub_state?"
);
S::register_sub_state_systems(schedule);
let state = self
.world()
.get_resource::<State<S>>()
.map(|s| s.get().clone());
self.world_mut().send_event(StateTransitionEvent {
exited: None,
entered: state,
});
if S::SCOPED_ENTITIES_ENABLED {
self.enable_state_scoped_entities::<S>();
}
} else {
let name = core::any::type_name::<S>();
warn!("Sub state {} is already initialized.", name);
}
self
}
fn enable_state_scoped_entities<S: States>(&mut self) -> &mut Self {
if !self
.world()
.contains_resource::<Events<StateTransitionEvent<S>>>()
{
let name = core::any::type_name::<S>();
warn!("State scoped entities are enabled for state `{}`, but the state isn't installed in the app!", name);
}
// We work with [`StateTransition`] in set [`StateTransitionSteps::ExitSchedules`] as opposed to [`OnExit`],
// because [`OnExit`] only runs for one specific variant of the state.
self.add_systems(
StateTransition,
clear_state_scoped_entities::<S>.in_set(StateTransitionSteps::ExitSchedules),
)
}
#[cfg(feature = "bevy_reflect")]
fn register_type_state<S>(&mut self) -> &mut Self
where
S: States + FromReflect + GetTypeRegistration + Typed,
{
self.register_type::<S>();
self.register_type::<State<S>>();
self.register_type_data::<S, crate::reflect::ReflectState>();
self
}
#[cfg(feature = "bevy_reflect")]
fn register_type_mutable_state<S>(&mut self) -> &mut Self
where
S: FreelyMutableState + FromReflect + GetTypeRegistration + Typed,
{
self.register_type::<S>();
self.register_type::<State<S>>();
self.register_type::<NextState<S>>();
self.register_type_data::<S, crate::reflect::ReflectState>();
self.register_type_data::<S, crate::reflect::ReflectFreelyMutableState>();
self
}
}
impl AppExtStates for App {
fn init_state<S: FreelyMutableState + FromWorld>(&mut self) -> &mut Self {
self.main_mut().init_state::<S>();
self
}
fn insert_state<S: FreelyMutableState>(&mut self, state: S) -> &mut Self {
self.main_mut().insert_state::<S>(state);
self
}
fn add_computed_state<S: ComputedStates>(&mut self) -> &mut Self {
self.main_mut().add_computed_state::<S>();
self
}
fn add_sub_state<S: SubStates>(&mut self) -> &mut Self {
self.main_mut().add_sub_state::<S>();
self
}
fn enable_state_scoped_entities<S: States>(&mut self) -> &mut Self {
self.main_mut().enable_state_scoped_entities::<S>();
self
}
#[cfg(feature = "bevy_reflect")]
fn register_type_state<S>(&mut self) -> &mut Self
where
S: States + FromReflect + GetTypeRegistration + Typed,
{
self.main_mut().register_type_state::<S>();
self
}
#[cfg(feature = "bevy_reflect")]
fn register_type_mutable_state<S>(&mut self) -> &mut Self
where
S: FreelyMutableState + FromReflect + GetTypeRegistration + Typed,
{
self.main_mut().register_type_mutable_state::<S>();
self
}
}
/// Registers the [`StateTransition`] schedule in the [`MainScheduleOrder`] to enable state processing.
#[derive(Default)]
pub struct StatesPlugin;
impl Plugin for StatesPlugin {
fn build(&self, app: &mut App) {
let mut schedule = app.world_mut().resource_mut::<MainScheduleOrder>();
schedule.insert_after(PreUpdate, StateTransition);
schedule.insert_startup_before(PreStartup, StateTransition);
setup_state_transitions_in_world(app.world_mut());
}
}
#[cfg(test)]
mod tests {
use crate::{
app::StatesPlugin,
state::{State, StateTransition, StateTransitionEvent},
};
use bevy_app::App;
use bevy_ecs::event::Events;
use bevy_state_macros::States;
use super::AppExtStates;
#[derive(States, Default, PartialEq, Eq, Hash, Debug, Clone)]
enum TestState {
#[default]
A,
B,
C,
}
#[test]
fn insert_state_can_overwrite_init_state() {
let mut app = App::new();
app.add_plugins(StatesPlugin);
app.init_state::<TestState>();
app.insert_state(TestState::B);
let world = app.world_mut();
world.run_schedule(StateTransition);
assert_eq!(world.resource::<State<TestState>>().0, TestState::B);
let events = world.resource::<Events<StateTransitionEvent<TestState>>>();
assert_eq!(events.len(), 1);
let mut reader = events.get_cursor();
let last = reader.read(events).last().unwrap();
assert_eq!(last.exited, None);
assert_eq!(last.entered, Some(TestState::B));
}
#[test]
fn insert_state_can_overwrite_insert_state() {
let mut app = App::new();
app.add_plugins(StatesPlugin);
app.insert_state(TestState::B);
app.insert_state(TestState::C);
let world = app.world_mut();
world.run_schedule(StateTransition);
assert_eq!(world.resource::<State<TestState>>().0, TestState::C);
let events = world.resource::<Events<StateTransitionEvent<TestState>>>();
assert_eq!(events.len(), 1);
let mut reader = events.get_cursor();
let last = reader.read(events).last().unwrap();
assert_eq!(last.exited, None);
assert_eq!(last.entered, Some(TestState::C));
}
}

30
vendor/bevy_state/src/commands.rs vendored Normal file
View File

@@ -0,0 +1,30 @@
use bevy_ecs::{system::Commands, world::World};
use log::debug;
use crate::state::{FreelyMutableState, NextState};
/// Extension trait for [`Commands`] adding `bevy_state` helpers.
pub trait CommandsStatesExt {
/// Sets the next state the app should move to.
///
/// Internally this schedules a command that updates the [`NextState<S>`](crate::prelude::NextState)
/// resource with `state`.
///
/// Note that commands introduce sync points to the ECS schedule, so modifying `NextState`
/// directly may be more efficient depending on your use-case.
fn set_state<S: FreelyMutableState>(&mut self, state: S);
}
impl CommandsStatesExt for Commands<'_, '_> {
fn set_state<S: FreelyMutableState>(&mut self, state: S) {
self.queue(move |w: &mut World| {
let mut next = w.resource_mut::<NextState<S>>();
if let NextState::Pending(prev) = &*next {
if *prev != state {
debug!("overwriting next state {:?} with {:?}", prev, state);
}
}
next.set(state);
});
}
}

198
vendor/bevy_state/src/condition.rs vendored Normal file
View File

@@ -0,0 +1,198 @@
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>),
);
}
}

94
vendor/bevy_state/src/lib.rs vendored Normal file
View File

@@ -0,0 +1,94 @@
#![no_std]
//! In Bevy, states are app-wide interdependent, finite state machines that are generally used to model the large scale structure of your program: whether a game is paused, if the player is in combat, if assets are loaded and so on.
//!
//! This module provides 3 distinct types of state, all of which implement the [`States`](state::States) trait:
//!
//! - Standard [`States`](state::States) can only be changed by manually setting the [`NextState<S>`](state::NextState) resource.
//! These states are the baseline on which the other state types are built, and can be used on
//! their own for many simple patterns. See the [states example](https://github.com/bevyengine/bevy/blob/latest/examples/state/states.rs)
//! for a simple use case.
//! - [`SubStates`](state::SubStates) are children of other states - they can be changed manually using [`NextState<S>`](state::NextState),
//! but are removed from the [`World`](bevy_ecs::prelude::World) if the source states aren't in the right state. See the [sub_states example](https://github.com/bevyengine/bevy/blob/latest/examples/state/sub_states.rs)
//! for a simple use case based on the derive macro, or read the trait docs for more complex scenarios.
//! - [`ComputedStates`](state::ComputedStates) are fully derived from other states - they provide a [`compute`](state::ComputedStates::compute) method
//! that takes in the source states and returns their derived value. They are particularly useful for situations
//! where a simplified view of the source states is necessary - such as having an `InAMenu` computed state, derived
//! from a source state that defines multiple distinct menus. See the [computed state example](https://github.com/bevyengine/bevy/blob/latest/examples/state/computed_states.rs)
//! to see usage samples for these states.
//!
//! Most of the utilities around state involve running systems during transitions between states, or
//! determining whether to run certain systems, though they can be used more directly as well. This
//! makes it easier to transition between menus, add loading screens, pause games, and more.
//!
//! Specifically, Bevy provides the following utilities:
//!
//! - 3 Transition Schedules - [`OnEnter<S>`](crate::state::OnEnter), [`OnExit<S>`](crate::state::OnExit) and [`OnTransition<S>`](crate::state::OnTransition) - which are used
//! to trigger systems specifically during matching transitions.
//! - A [`StateTransitionEvent<S>`](crate::state::StateTransitionEvent) that gets fired when a given state changes.
//! - The [`in_state<S>`](crate::condition::in_state) and [`state_changed<S>`](crate::condition::state_changed) run conditions - which are used
//! to determine whether a system should run based on the current state.
#![cfg_attr(
any(docsrs, docsrs_dep),
expect(
internal_features,
reason = "rustdoc_internals is needed for fake_variadic"
)
)]
#![cfg_attr(any(docsrs, docsrs_dep), feature(rustdoc_internals))]
#[cfg(feature = "std")]
extern crate std;
extern crate alloc;
// Required to make proc macros work in bevy itself.
extern crate self as bevy_state;
#[cfg(feature = "bevy_app")]
/// Provides [`App`](bevy_app::App) and [`SubApp`](bevy_app::SubApp) with state installation methods
pub mod app;
/// Provides extension methods for [`Commands`](bevy_ecs::prelude::Commands).
pub mod commands;
/// Provides definitions for the runtime conditions that interact with the state system
pub mod condition;
/// Provides definitions for the basic traits required by the state system
pub mod state;
/// Provides [`StateScoped`](crate::state_scoped::StateScoped) and
/// [`clear_state_scoped_entities`](crate::state_scoped::clear_state_scoped_entities) for managing lifetime of entities.
pub mod state_scoped;
#[cfg(feature = "bevy_app")]
/// Provides [`App`](bevy_app::App) and [`SubApp`](bevy_app::SubApp) with methods for registering
/// state-scoped events.
pub mod state_scoped_events;
#[cfg(feature = "bevy_reflect")]
/// Provides definitions for the basic traits required by the state system
pub mod reflect;
/// The state prelude.
///
/// This includes the most common types in this crate, re-exported for your convenience.
pub mod prelude {
#[cfg(feature = "bevy_app")]
#[doc(hidden)]
pub use crate::{app::AppExtStates, state_scoped_events::StateScopedEventsAppExt};
#[cfg(feature = "bevy_reflect")]
#[doc(hidden)]
pub use crate::reflect::{ReflectFreelyMutableState, ReflectState};
#[doc(hidden)]
pub use crate::{
commands::CommandsStatesExt,
condition::*,
state::{
last_transition, ComputedStates, EnterSchedules, ExitSchedules, NextState, OnEnter,
OnExit, OnTransition, State, StateSet, StateTransition, StateTransitionEvent, States,
SubStates, TransitionSchedules,
},
state_scoped::StateScoped,
};
}

165
vendor/bevy_state/src/reflect.rs vendored Normal file
View File

@@ -0,0 +1,165 @@
use crate::state::{FreelyMutableState, NextState, State, States};
use bevy_ecs::{reflect::from_reflect_with_fallback, world::World};
use bevy_reflect::{FromType, Reflect, TypePath, TypeRegistry};
/// A struct used to operate on the reflected [`States`] trait of a type.
///
/// A [`ReflectState`] for type `T` can be obtained via
/// [`bevy_reflect::TypeRegistration::data`].
#[derive(Clone)]
pub struct ReflectState(ReflectStateFns);
/// The raw function pointers needed to make up a [`ReflectState`].
#[derive(Clone)]
pub struct ReflectStateFns {
/// Function pointer implementing [`ReflectState::reflect()`].
pub reflect: fn(&World) -> Option<&dyn Reflect>,
}
impl ReflectStateFns {
/// Get the default set of [`ReflectStateFns`] for a specific component type using its
/// [`FromType`] implementation.
///
/// This is useful if you want to start with the default implementation before overriding some
/// of the functions to create a custom implementation.
pub fn new<T: States + Reflect>() -> Self {
<ReflectState as FromType<T>>::from_type().0
}
}
impl ReflectState {
/// Gets the value of this [`States`] type from the world as a reflected reference.
pub fn reflect<'a>(&self, world: &'a World) -> Option<&'a dyn Reflect> {
(self.0.reflect)(world)
}
}
impl<S: States + Reflect> FromType<S> for ReflectState {
fn from_type() -> Self {
ReflectState(ReflectStateFns {
reflect: |world| {
world
.get_resource::<State<S>>()
.map(|res| res.get() as &dyn Reflect)
},
})
}
}
/// A struct used to operate on the reflected [`FreelyMutableState`] trait of a type.
///
/// A [`ReflectFreelyMutableState`] for type `T` can be obtained via
/// [`bevy_reflect::TypeRegistration::data`].
#[derive(Clone)]
pub struct ReflectFreelyMutableState(ReflectFreelyMutableStateFns);
/// The raw function pointers needed to make up a [`ReflectFreelyMutableState`].
#[derive(Clone)]
pub struct ReflectFreelyMutableStateFns {
/// Function pointer implementing [`ReflectFreelyMutableState::set_next_state()`].
pub set_next_state: fn(&mut World, &dyn Reflect, &TypeRegistry),
}
impl ReflectFreelyMutableStateFns {
/// Get the default set of [`ReflectFreelyMutableStateFns`] for a specific component type using its
/// [`FromType`] implementation.
///
/// This is useful if you want to start with the default implementation before overriding some
/// of the functions to create a custom implementation.
pub fn new<T: FreelyMutableState + Reflect + TypePath>() -> Self {
<ReflectFreelyMutableState as FromType<T>>::from_type().0
}
}
impl ReflectFreelyMutableState {
/// Tentatively set a pending state transition to a reflected [`ReflectFreelyMutableState`].
pub fn set_next_state(&self, world: &mut World, state: &dyn Reflect, registry: &TypeRegistry) {
(self.0.set_next_state)(world, state, registry);
}
}
impl<S: FreelyMutableState + Reflect + TypePath> FromType<S> for ReflectFreelyMutableState {
fn from_type() -> Self {
ReflectFreelyMutableState(ReflectFreelyMutableStateFns {
set_next_state: |world, reflected_state, registry| {
let new_state: S = from_reflect_with_fallback(
reflected_state.as_partial_reflect(),
world,
registry,
);
if let Some(mut next_state) = world.get_resource_mut::<NextState<S>>() {
next_state.set(new_state);
}
},
})
}
}
#[cfg(test)]
mod tests {
use crate::{
app::{AppExtStates, StatesPlugin},
reflect::{ReflectFreelyMutableState, ReflectState},
state::State,
};
use bevy_app::App;
use bevy_ecs::prelude::AppTypeRegistry;
use bevy_reflect::Reflect;
use bevy_state_macros::States;
use core::any::TypeId;
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, States, Reflect)]
enum StateTest {
A,
B,
}
#[test]
fn test_reflect_state_operations() {
let mut app = App::new();
app.add_plugins(StatesPlugin)
.insert_state(StateTest::A)
.register_type_mutable_state::<StateTest>();
let type_registry = app.world_mut().resource::<AppTypeRegistry>().0.clone();
let type_registry = type_registry.read();
let (reflect_state, reflect_mutable_state) = (
type_registry
.get_type_data::<ReflectState>(TypeId::of::<StateTest>())
.unwrap()
.clone(),
type_registry
.get_type_data::<ReflectFreelyMutableState>(TypeId::of::<StateTest>())
.unwrap()
.clone(),
);
let current_value = reflect_state.reflect(app.world()).unwrap();
assert_eq!(
current_value.downcast_ref::<StateTest>().unwrap(),
&StateTest::A
);
reflect_mutable_state.set_next_state(app.world_mut(), &StateTest::B, &type_registry);
assert_ne!(
app.world().resource::<State<StateTest>>().get(),
&StateTest::B
);
app.update();
assert_eq!(
app.world().resource::<State<StateTest>>().get(),
&StateTest::B
);
let current_value = reflect_state.reflect(app.world()).unwrap();
assert_eq!(
current_value.downcast_ref::<StateTest>().unwrap(),
&StateTest::B
);
}
}

View File

@@ -0,0 +1,95 @@
use core::{fmt::Debug, hash::Hash};
use bevy_ecs::schedule::Schedule;
use super::{state_set::StateSet, states::States};
/// A state whose value is automatically computed based on the values of other [`States`].
///
/// A **computed state** is a state that is deterministically derived from a set of `SourceStates`.
/// The [`StateSet`] is passed into the `compute` method whenever one of them changes, and the
/// result becomes the state's value.
///
/// ```
/// # use bevy_state::prelude::*;
/// # use bevy_ecs::prelude::*;
///
/// /// Computed States require some state to derive from
/// #[derive(States, Clone, PartialEq, Eq, Hash, Debug, Default)]
/// enum AppState {
/// #[default]
/// Menu,
/// InGame { paused: bool }
/// }
///
///
/// #[derive(Clone, PartialEq, Eq, Hash, Debug)]
/// struct InGame;
///
/// impl ComputedStates for InGame {
/// /// We set the source state to be the state, or a tuple of states,
/// /// we want to depend on. You can also wrap each state in an Option,
/// /// if you want the computed state to execute even if the state doesn't
/// /// currently exist in the world.
/// type SourceStates = AppState;
///
/// /// We then define the compute function, which takes in
/// /// your SourceStates
/// fn compute(sources: AppState) -> Option<Self> {
/// match sources {
/// /// When we are in game, we want to return the InGame state
/// AppState::InGame { .. } => Some(InGame),
/// /// Otherwise, we don't want the `State<InGame>` resource to exist,
/// /// so we return None.
/// _ => None
/// }
/// }
/// }
/// ```
///
/// you can then add it to an App, and from there you use the state as normal
///
/// ```
/// # use bevy_state::prelude::*;
/// # use bevy_ecs::prelude::*;
///
/// # struct App;
/// # impl App {
/// # fn new() -> Self { App }
/// # fn init_state<S>(&mut self) -> &mut Self {self}
/// # fn add_computed_state<S>(&mut self) -> &mut Self {self}
/// # }
/// # struct AppState;
/// # struct InGame;
///
/// App::new()
/// .init_state::<AppState>()
/// .add_computed_state::<InGame>();
/// ```
pub trait ComputedStates: 'static + Send + Sync + Clone + PartialEq + Eq + Hash + Debug {
/// The set of states from which the [`Self`] is derived.
///
/// This can either be a single type that implements [`States`], an Option of a type
/// that implements [`States`], or a tuple
/// containing multiple types that implement [`States`] or Optional versions of them.
///
/// For example, `(MapState, EnemyState)` is valid, as is `(MapState, Option<EnemyState>)`
type SourceStates: StateSet;
/// Computes the next value of [`State<Self>`](crate::state::State).
/// This function gets called whenever one of the [`SourceStates`](Self::SourceStates) changes.
///
/// If the result is [`None`], the [`State<Self>`](crate::state::State) resource will be removed from the world.
fn compute(sources: Self::SourceStates) -> Option<Self>;
/// This function sets up systems that compute the state whenever one of the [`SourceStates`](Self::SourceStates)
/// change. It is called by `App::add_computed_state`, but can be called manually if `App` is not
/// used.
fn register_computed_state_systems(schedule: &mut Schedule) {
Self::SourceStates::register_computed_state_systems_in_schedule::<Self>(schedule);
}
}
impl<S: ComputedStates> States for S {
const DEPENDENCY_DEPTH: usize = S::SourceStates::SET_DEPENDENCY_DEPTH + 1;
}

View File

@@ -0,0 +1,62 @@
use bevy_ecs::{
event::EventWriter,
prelude::Schedule,
schedule::IntoScheduleConfigs,
system::{Commands, IntoSystem, ResMut},
};
use super::{states::States, take_next_state, transitions::*, NextState, State};
/// This trait allows a state to be mutated directly using the [`NextState<S>`](crate::state::NextState) resource.
///
/// While ordinary states are freely mutable (and implement this trait as part of their derive macro),
/// computed states are not: instead, they can *only* change when the states that drive them do.
#[diagnostic::on_unimplemented(note = "consider annotating `{Self}` with `#[derive(States)]`")]
pub trait FreelyMutableState: States {
/// This function registers all the necessary systems to apply state changes and run transition schedules
fn register_state(schedule: &mut Schedule) {
schedule.configure_sets((
ApplyStateTransition::<Self>::default()
.in_set(StateTransitionSteps::DependentTransitions),
ExitSchedules::<Self>::default().in_set(StateTransitionSteps::ExitSchedules),
TransitionSchedules::<Self>::default()
.in_set(StateTransitionSteps::TransitionSchedules),
EnterSchedules::<Self>::default().in_set(StateTransitionSteps::EnterSchedules),
));
schedule
.add_systems(
apply_state_transition::<Self>.in_set(ApplyStateTransition::<Self>::default()),
)
.add_systems(
last_transition::<Self>
.pipe(run_exit::<Self>)
.in_set(ExitSchedules::<Self>::default()),
)
.add_systems(
last_transition::<Self>
.pipe(run_transition::<Self>)
.in_set(TransitionSchedules::<Self>::default()),
)
.add_systems(
last_transition::<Self>
.pipe(run_enter::<Self>)
.in_set(EnterSchedules::<Self>::default()),
);
}
}
fn apply_state_transition<S: FreelyMutableState>(
event: EventWriter<StateTransitionEvent<S>>,
commands: Commands,
current_state: Option<ResMut<State<S>>>,
next_state: Option<ResMut<NextState<S>>>,
) {
let Some(next_state) = take_next_state(next_state) else {
return;
};
let Some(current_state) = current_state else {
return;
};
internal_apply_state_transition(event, commands, Some(current_state), Some(next_state));
}

727
vendor/bevy_state/src/state/mod.rs vendored Normal file
View File

@@ -0,0 +1,727 @@
mod computed_states;
mod freely_mutable_state;
mod resources;
mod state_set;
mod states;
mod sub_states;
mod transitions;
pub use bevy_state_macros::*;
pub use computed_states::*;
pub use freely_mutable_state::*;
pub use resources::*;
pub use state_set::*;
pub use states::*;
pub use sub_states::*;
pub use transitions::*;
#[cfg(test)]
mod tests {
use alloc::vec::Vec;
use bevy_ecs::{event::EventRegistry, prelude::*};
use bevy_state_macros::{States, SubStates};
use super::*;
#[derive(States, PartialEq, Eq, Debug, Default, Hash, Clone)]
enum SimpleState {
#[default]
A,
B(bool),
}
#[derive(PartialEq, Eq, Debug, Hash, Clone)]
enum TestComputedState {
BisTrue,
BisFalse,
}
impl ComputedStates for TestComputedState {
type SourceStates = Option<SimpleState>;
fn compute(sources: Option<SimpleState>) -> Option<Self> {
sources.and_then(|source| match source {
SimpleState::A => None,
SimpleState::B(value) => Some(if value { Self::BisTrue } else { Self::BisFalse }),
})
}
}
#[test]
fn computed_state_with_a_single_source_is_correctly_derived() {
let mut world = World::new();
EventRegistry::register_event::<StateTransitionEvent<SimpleState>>(&mut world);
EventRegistry::register_event::<StateTransitionEvent<TestComputedState>>(&mut world);
world.init_resource::<State<SimpleState>>();
let mut schedules = Schedules::new();
let mut apply_changes = Schedule::new(StateTransition);
TestComputedState::register_computed_state_systems(&mut apply_changes);
SimpleState::register_state(&mut apply_changes);
schedules.insert(apply_changes);
world.insert_resource(schedules);
setup_state_transitions_in_world(&mut world);
world.run_schedule(StateTransition);
assert_eq!(world.resource::<State<SimpleState>>().0, SimpleState::A);
assert!(!world.contains_resource::<State<TestComputedState>>());
world.insert_resource(NextState::Pending(SimpleState::B(true)));
world.run_schedule(StateTransition);
assert_eq!(
world.resource::<State<SimpleState>>().0,
SimpleState::B(true)
);
assert_eq!(
world.resource::<State<TestComputedState>>().0,
TestComputedState::BisTrue
);
world.insert_resource(NextState::Pending(SimpleState::B(false)));
world.run_schedule(StateTransition);
assert_eq!(
world.resource::<State<SimpleState>>().0,
SimpleState::B(false)
);
assert_eq!(
world.resource::<State<TestComputedState>>().0,
TestComputedState::BisFalse
);
world.insert_resource(NextState::Pending(SimpleState::A));
world.run_schedule(StateTransition);
assert_eq!(world.resource::<State<SimpleState>>().0, SimpleState::A);
assert!(!world.contains_resource::<State<TestComputedState>>());
}
#[derive(SubStates, PartialEq, Eq, Debug, Default, Hash, Clone)]
#[source(SimpleState = SimpleState::B(true))]
enum SubState {
#[default]
One,
Two,
}
#[test]
fn sub_state_exists_only_when_allowed_but_can_be_modified_freely() {
let mut world = World::new();
EventRegistry::register_event::<StateTransitionEvent<SimpleState>>(&mut world);
EventRegistry::register_event::<StateTransitionEvent<SubState>>(&mut world);
world.init_resource::<State<SimpleState>>();
let mut schedules = Schedules::new();
let mut apply_changes = Schedule::new(StateTransition);
SubState::register_sub_state_systems(&mut apply_changes);
SimpleState::register_state(&mut apply_changes);
schedules.insert(apply_changes);
world.insert_resource(schedules);
setup_state_transitions_in_world(&mut world);
world.run_schedule(StateTransition);
assert_eq!(world.resource::<State<SimpleState>>().0, SimpleState::A);
assert!(!world.contains_resource::<State<SubState>>());
world.insert_resource(NextState::Pending(SubState::Two));
world.run_schedule(StateTransition);
assert_eq!(world.resource::<State<SimpleState>>().0, SimpleState::A);
assert!(!world.contains_resource::<State<SubState>>());
world.insert_resource(NextState::Pending(SimpleState::B(true)));
world.run_schedule(StateTransition);
assert_eq!(
world.resource::<State<SimpleState>>().0,
SimpleState::B(true)
);
assert_eq!(world.resource::<State<SubState>>().0, SubState::One);
world.insert_resource(NextState::Pending(SubState::Two));
world.run_schedule(StateTransition);
assert_eq!(
world.resource::<State<SimpleState>>().0,
SimpleState::B(true)
);
assert_eq!(world.resource::<State<SubState>>().0, SubState::Two);
world.insert_resource(NextState::Pending(SimpleState::B(false)));
world.run_schedule(StateTransition);
assert_eq!(
world.resource::<State<SimpleState>>().0,
SimpleState::B(false)
);
assert!(!world.contains_resource::<State<SubState>>());
}
#[derive(SubStates, PartialEq, Eq, Debug, Default, Hash, Clone)]
#[source(TestComputedState = TestComputedState::BisTrue)]
enum SubStateOfComputed {
#[default]
One,
Two,
}
#[test]
fn substate_of_computed_states_works_appropriately() {
let mut world = World::new();
EventRegistry::register_event::<StateTransitionEvent<SimpleState>>(&mut world);
EventRegistry::register_event::<StateTransitionEvent<TestComputedState>>(&mut world);
EventRegistry::register_event::<StateTransitionEvent<SubStateOfComputed>>(&mut world);
world.init_resource::<State<SimpleState>>();
let mut schedules = Schedules::new();
let mut apply_changes = Schedule::new(StateTransition);
TestComputedState::register_computed_state_systems(&mut apply_changes);
SubStateOfComputed::register_sub_state_systems(&mut apply_changes);
SimpleState::register_state(&mut apply_changes);
schedules.insert(apply_changes);
world.insert_resource(schedules);
setup_state_transitions_in_world(&mut world);
world.run_schedule(StateTransition);
assert_eq!(world.resource::<State<SimpleState>>().0, SimpleState::A);
assert!(!world.contains_resource::<State<SubStateOfComputed>>());
world.insert_resource(NextState::Pending(SubStateOfComputed::Two));
world.run_schedule(StateTransition);
assert_eq!(world.resource::<State<SimpleState>>().0, SimpleState::A);
assert!(!world.contains_resource::<State<SubStateOfComputed>>());
world.insert_resource(NextState::Pending(SimpleState::B(true)));
world.run_schedule(StateTransition);
assert_eq!(
world.resource::<State<SimpleState>>().0,
SimpleState::B(true)
);
assert_eq!(
world.resource::<State<SubStateOfComputed>>().0,
SubStateOfComputed::One
);
world.insert_resource(NextState::Pending(SubStateOfComputed::Two));
world.run_schedule(StateTransition);
assert_eq!(
world.resource::<State<SimpleState>>().0,
SimpleState::B(true)
);
assert_eq!(
world.resource::<State<SubStateOfComputed>>().0,
SubStateOfComputed::Two
);
world.insert_resource(NextState::Pending(SimpleState::B(false)));
world.run_schedule(StateTransition);
assert_eq!(
world.resource::<State<SimpleState>>().0,
SimpleState::B(false)
);
assert!(!world.contains_resource::<State<SubStateOfComputed>>());
}
#[derive(States, PartialEq, Eq, Debug, Default, Hash, Clone)]
struct OtherState {
a_flexible_value: &'static str,
another_value: u8,
}
#[derive(PartialEq, Eq, Debug, Hash, Clone)]
enum ComplexComputedState {
InAAndStrIsBobOrJane,
InTrueBAndUsizeAbove8,
}
impl ComputedStates for ComplexComputedState {
type SourceStates = (Option<SimpleState>, Option<OtherState>);
fn compute(sources: (Option<SimpleState>, Option<OtherState>)) -> Option<Self> {
match sources {
(Some(simple), Some(complex)) => {
if simple == SimpleState::A
&& (complex.a_flexible_value == "bob" || complex.a_flexible_value == "jane")
{
Some(ComplexComputedState::InAAndStrIsBobOrJane)
} else if simple == SimpleState::B(true) && complex.another_value > 8 {
Some(ComplexComputedState::InTrueBAndUsizeAbove8)
} else {
None
}
}
_ => None,
}
}
}
#[test]
fn complex_computed_state_gets_derived_correctly() {
let mut world = World::new();
EventRegistry::register_event::<StateTransitionEvent<SimpleState>>(&mut world);
EventRegistry::register_event::<StateTransitionEvent<OtherState>>(&mut world);
EventRegistry::register_event::<StateTransitionEvent<ComplexComputedState>>(&mut world);
world.init_resource::<State<SimpleState>>();
world.init_resource::<State<OtherState>>();
let mut schedules = Schedules::new();
let mut apply_changes = Schedule::new(StateTransition);
ComplexComputedState::register_computed_state_systems(&mut apply_changes);
SimpleState::register_state(&mut apply_changes);
OtherState::register_state(&mut apply_changes);
schedules.insert(apply_changes);
world.insert_resource(schedules);
setup_state_transitions_in_world(&mut world);
world.run_schedule(StateTransition);
assert_eq!(world.resource::<State<SimpleState>>().0, SimpleState::A);
assert_eq!(
world.resource::<State<OtherState>>().0,
OtherState::default()
);
assert!(!world.contains_resource::<State<ComplexComputedState>>());
world.insert_resource(NextState::Pending(SimpleState::B(true)));
world.run_schedule(StateTransition);
assert!(!world.contains_resource::<State<ComplexComputedState>>());
world.insert_resource(NextState::Pending(OtherState {
a_flexible_value: "felix",
another_value: 13,
}));
world.run_schedule(StateTransition);
assert_eq!(
world.resource::<State<ComplexComputedState>>().0,
ComplexComputedState::InTrueBAndUsizeAbove8
);
world.insert_resource(NextState::Pending(SimpleState::A));
world.insert_resource(NextState::Pending(OtherState {
a_flexible_value: "jane",
another_value: 13,
}));
world.run_schedule(StateTransition);
assert_eq!(
world.resource::<State<ComplexComputedState>>().0,
ComplexComputedState::InAAndStrIsBobOrJane
);
world.insert_resource(NextState::Pending(SimpleState::B(false)));
world.insert_resource(NextState::Pending(OtherState {
a_flexible_value: "jane",
another_value: 13,
}));
world.run_schedule(StateTransition);
assert!(!world.contains_resource::<State<ComplexComputedState>>());
}
#[derive(Resource, Default)]
struct ComputedStateTransitionCounter {
enter: usize,
exit: usize,
}
#[derive(States, PartialEq, Eq, Debug, Default, Hash, Clone)]
enum SimpleState2 {
#[default]
A1,
B2,
}
#[derive(PartialEq, Eq, Debug, Hash, Clone)]
enum TestNewcomputedState {
A1,
B2,
B1,
}
impl ComputedStates for TestNewcomputedState {
type SourceStates = (Option<SimpleState>, Option<SimpleState2>);
fn compute((s1, s2): (Option<SimpleState>, Option<SimpleState2>)) -> Option<Self> {
match (s1, s2) {
(Some(SimpleState::A), Some(SimpleState2::A1)) => Some(TestNewcomputedState::A1),
(Some(SimpleState::B(true)), Some(SimpleState2::B2)) => {
Some(TestNewcomputedState::B2)
}
(Some(SimpleState::B(true)), _) => Some(TestNewcomputedState::B1),
_ => None,
}
}
}
#[test]
fn computed_state_transitions_are_produced_correctly() {
let mut world = World::new();
EventRegistry::register_event::<StateTransitionEvent<SimpleState>>(&mut world);
EventRegistry::register_event::<StateTransitionEvent<SimpleState2>>(&mut world);
EventRegistry::register_event::<StateTransitionEvent<TestNewcomputedState>>(&mut world);
world.init_resource::<State<SimpleState>>();
world.init_resource::<State<SimpleState2>>();
world.init_resource::<Schedules>();
setup_state_transitions_in_world(&mut world);
let mut schedules = world
.get_resource_mut::<Schedules>()
.expect("Schedules don't exist in world");
let apply_changes = schedules
.get_mut(StateTransition)
.expect("State Transition Schedule Doesn't Exist");
TestNewcomputedState::register_computed_state_systems(apply_changes);
SimpleState::register_state(apply_changes);
SimpleState2::register_state(apply_changes);
schedules.insert({
let mut schedule = Schedule::new(OnEnter(TestNewcomputedState::A1));
schedule.add_systems(|mut count: ResMut<ComputedStateTransitionCounter>| {
count.enter += 1;
});
schedule
});
schedules.insert({
let mut schedule = Schedule::new(OnExit(TestNewcomputedState::A1));
schedule.add_systems(|mut count: ResMut<ComputedStateTransitionCounter>| {
count.exit += 1;
});
schedule
});
schedules.insert({
let mut schedule = Schedule::new(OnEnter(TestNewcomputedState::B1));
schedule.add_systems(|mut count: ResMut<ComputedStateTransitionCounter>| {
count.enter += 1;
});
schedule
});
schedules.insert({
let mut schedule = Schedule::new(OnExit(TestNewcomputedState::B1));
schedule.add_systems(|mut count: ResMut<ComputedStateTransitionCounter>| {
count.exit += 1;
});
schedule
});
schedules.insert({
let mut schedule = Schedule::new(OnEnter(TestNewcomputedState::B2));
schedule.add_systems(|mut count: ResMut<ComputedStateTransitionCounter>| {
count.enter += 1;
});
schedule
});
schedules.insert({
let mut schedule = Schedule::new(OnExit(TestNewcomputedState::B2));
schedule.add_systems(|mut count: ResMut<ComputedStateTransitionCounter>| {
count.exit += 1;
});
schedule
});
world.init_resource::<ComputedStateTransitionCounter>();
setup_state_transitions_in_world(&mut world);
assert_eq!(world.resource::<State<SimpleState>>().0, SimpleState::A);
assert_eq!(world.resource::<State<SimpleState2>>().0, SimpleState2::A1);
assert!(!world.contains_resource::<State<TestNewcomputedState>>());
world.insert_resource(NextState::Pending(SimpleState::B(true)));
world.insert_resource(NextState::Pending(SimpleState2::B2));
world.run_schedule(StateTransition);
assert_eq!(
world.resource::<State<TestNewcomputedState>>().0,
TestNewcomputedState::B2
);
assert_eq!(world.resource::<ComputedStateTransitionCounter>().enter, 1);
assert_eq!(world.resource::<ComputedStateTransitionCounter>().exit, 0);
world.insert_resource(NextState::Pending(SimpleState2::A1));
world.insert_resource(NextState::Pending(SimpleState::A));
world.run_schedule(StateTransition);
assert_eq!(
world.resource::<State<TestNewcomputedState>>().0,
TestNewcomputedState::A1
);
assert_eq!(
world.resource::<ComputedStateTransitionCounter>().enter,
2,
"Should Only Enter Twice"
);
assert_eq!(
world.resource::<ComputedStateTransitionCounter>().exit,
1,
"Should Only Exit Once"
);
world.insert_resource(NextState::Pending(SimpleState::B(true)));
world.insert_resource(NextState::Pending(SimpleState2::B2));
world.run_schedule(StateTransition);
assert_eq!(
world.resource::<State<TestNewcomputedState>>().0,
TestNewcomputedState::B2
);
assert_eq!(
world.resource::<ComputedStateTransitionCounter>().enter,
3,
"Should Only Enter Three Times"
);
assert_eq!(
world.resource::<ComputedStateTransitionCounter>().exit,
2,
"Should Only Exit Twice"
);
world.insert_resource(NextState::Pending(SimpleState::A));
world.run_schedule(StateTransition);
assert!(!world.contains_resource::<State<TestNewcomputedState>>());
assert_eq!(
world.resource::<ComputedStateTransitionCounter>().enter,
3,
"Should Only Enter Three Times"
);
assert_eq!(
world.resource::<ComputedStateTransitionCounter>().exit,
3,
"Should Only Exit Twice"
);
}
#[derive(Resource, Default, PartialEq, Debug)]
struct TransitionCounter {
exit: u8,
transition: u8,
enter: u8,
}
#[test]
fn same_state_transition_should_emit_event_and_not_run_schedules() {
let mut world = World::new();
setup_state_transitions_in_world(&mut world);
EventRegistry::register_event::<StateTransitionEvent<SimpleState>>(&mut world);
world.init_resource::<State<SimpleState>>();
let mut schedules = world.resource_mut::<Schedules>();
let apply_changes = schedules.get_mut(StateTransition).unwrap();
SimpleState::register_state(apply_changes);
let mut on_exit = Schedule::new(OnExit(SimpleState::A));
on_exit.add_systems(|mut c: ResMut<TransitionCounter>| c.exit += 1);
schedules.insert(on_exit);
let mut on_transition = Schedule::new(OnTransition {
exited: SimpleState::A,
entered: SimpleState::A,
});
on_transition.add_systems(|mut c: ResMut<TransitionCounter>| c.transition += 1);
schedules.insert(on_transition);
let mut on_enter = Schedule::new(OnEnter(SimpleState::A));
on_enter.add_systems(|mut c: ResMut<TransitionCounter>| c.enter += 1);
schedules.insert(on_enter);
world.insert_resource(TransitionCounter::default());
world.run_schedule(StateTransition);
assert_eq!(world.resource::<State<SimpleState>>().0, SimpleState::A);
assert!(world
.resource::<Events<StateTransitionEvent<SimpleState>>>()
.is_empty());
world.insert_resource(TransitionCounter::default());
world.insert_resource(NextState::Pending(SimpleState::A));
world.run_schedule(StateTransition);
assert_eq!(world.resource::<State<SimpleState>>().0, SimpleState::A);
assert_eq!(
*world.resource::<TransitionCounter>(),
TransitionCounter {
exit: 0,
transition: 1, // Same state transitions are allowed
enter: 0
}
);
assert_eq!(
world
.resource::<Events<StateTransitionEvent<SimpleState>>>()
.len(),
1
);
}
#[test]
fn same_state_transition_should_propagate_to_sub_state() {
let mut world = World::new();
EventRegistry::register_event::<StateTransitionEvent<SimpleState>>(&mut world);
EventRegistry::register_event::<StateTransitionEvent<SubState>>(&mut world);
world.insert_resource(State(SimpleState::B(true)));
world.init_resource::<State<SubState>>();
let mut schedules = Schedules::new();
let mut apply_changes = Schedule::new(StateTransition);
SimpleState::register_state(&mut apply_changes);
SubState::register_sub_state_systems(&mut apply_changes);
schedules.insert(apply_changes);
world.insert_resource(schedules);
setup_state_transitions_in_world(&mut world);
world.insert_resource(NextState::Pending(SimpleState::B(true)));
world.run_schedule(StateTransition);
assert_eq!(
world
.resource::<Events<StateTransitionEvent<SimpleState>>>()
.len(),
1
);
assert_eq!(
world
.resource::<Events<StateTransitionEvent<SubState>>>()
.len(),
1
);
}
#[test]
fn same_state_transition_should_propagate_to_computed_state() {
let mut world = World::new();
EventRegistry::register_event::<StateTransitionEvent<SimpleState>>(&mut world);
EventRegistry::register_event::<StateTransitionEvent<TestComputedState>>(&mut world);
world.insert_resource(State(SimpleState::B(true)));
world.insert_resource(State(TestComputedState::BisTrue));
let mut schedules = Schedules::new();
let mut apply_changes = Schedule::new(StateTransition);
SimpleState::register_state(&mut apply_changes);
TestComputedState::register_computed_state_systems(&mut apply_changes);
schedules.insert(apply_changes);
world.insert_resource(schedules);
setup_state_transitions_in_world(&mut world);
world.insert_resource(NextState::Pending(SimpleState::B(true)));
world.run_schedule(StateTransition);
assert_eq!(
world
.resource::<Events<StateTransitionEvent<SimpleState>>>()
.len(),
1
);
assert_eq!(
world
.resource::<Events<StateTransitionEvent<TestComputedState>>>()
.len(),
1
);
}
#[derive(Resource, Default, Debug)]
struct TransitionTracker(Vec<&'static str>);
#[derive(PartialEq, Eq, Debug, Hash, Clone)]
enum TransitionTestingComputedState {
IsA,
IsBAndEven,
IsBAndOdd,
}
impl ComputedStates for TransitionTestingComputedState {
type SourceStates = (Option<SimpleState>, Option<SubState>);
fn compute(sources: (Option<SimpleState>, Option<SubState>)) -> Option<Self> {
match sources {
(Some(simple), sub) => {
if simple == SimpleState::A {
Some(Self::IsA)
} else if sub == Some(SubState::One) {
Some(Self::IsBAndOdd)
} else if sub == Some(SubState::Two) {
Some(Self::IsBAndEven)
} else {
None
}
}
_ => None,
}
}
}
#[test]
fn check_transition_orders() {
let mut world = World::new();
setup_state_transitions_in_world(&mut world);
EventRegistry::register_event::<StateTransitionEvent<SimpleState>>(&mut world);
EventRegistry::register_event::<StateTransitionEvent<SubState>>(&mut world);
EventRegistry::register_event::<StateTransitionEvent<TransitionTestingComputedState>>(
&mut world,
);
world.insert_resource(State(SimpleState::B(true)));
world.init_resource::<State<SubState>>();
world.insert_resource(State(TransitionTestingComputedState::IsA));
let mut schedules = world.remove_resource::<Schedules>().unwrap();
let apply_changes = schedules.get_mut(StateTransition).unwrap();
SimpleState::register_state(apply_changes);
SubState::register_sub_state_systems(apply_changes);
TransitionTestingComputedState::register_computed_state_systems(apply_changes);
world.init_resource::<TransitionTracker>();
fn register_transition(string: &'static str) -> impl Fn(ResMut<TransitionTracker>) {
move |mut transitions: ResMut<TransitionTracker>| transitions.0.push(string)
}
schedules.add_systems(
StateTransition,
register_transition("simple exit").in_set(ExitSchedules::<SimpleState>::default()),
);
schedules.add_systems(
StateTransition,
register_transition("simple transition")
.in_set(TransitionSchedules::<SimpleState>::default()),
);
schedules.add_systems(
StateTransition,
register_transition("simple enter").in_set(EnterSchedules::<SimpleState>::default()),
);
schedules.add_systems(
StateTransition,
register_transition("sub exit").in_set(ExitSchedules::<SubState>::default()),
);
schedules.add_systems(
StateTransition,
register_transition("sub transition")
.in_set(TransitionSchedules::<SubState>::default()),
);
schedules.add_systems(
StateTransition,
register_transition("sub enter").in_set(EnterSchedules::<SubState>::default()),
);
schedules.add_systems(
StateTransition,
register_transition("computed exit")
.in_set(ExitSchedules::<TransitionTestingComputedState>::default()),
);
schedules.add_systems(
StateTransition,
register_transition("computed transition")
.in_set(TransitionSchedules::<TransitionTestingComputedState>::default()),
);
schedules.add_systems(
StateTransition,
register_transition("computed enter")
.in_set(EnterSchedules::<TransitionTestingComputedState>::default()),
);
world.insert_resource(schedules);
world.run_schedule(StateTransition);
let transitions = &world.resource::<TransitionTracker>().0;
assert_eq!(transitions.len(), 9);
assert_eq!(transitions[0], "computed exit");
assert_eq!(transitions[1], "sub exit");
assert_eq!(transitions[2], "simple exit");
// Transition order is arbitrary and doesn't need testing.
assert_eq!(transitions[6], "simple enter");
assert_eq!(transitions[7], "sub enter");
assert_eq!(transitions[8], "computed enter");
}
}

156
vendor/bevy_state/src/state/resources.rs vendored Normal file
View File

@@ -0,0 +1,156 @@
use core::ops::Deref;
use bevy_ecs::{
change_detection::DetectChangesMut,
resource::Resource,
system::ResMut,
world::{FromWorld, World},
};
use super::{freely_mutable_state::FreelyMutableState, states::States};
#[cfg(feature = "bevy_reflect")]
use bevy_ecs::prelude::ReflectResource;
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::prelude::ReflectDefault;
/// A finite-state machine whose transitions have associated schedules
/// ([`OnEnter(state)`](crate::state::OnEnter) and [`OnExit(state)`](crate::state::OnExit)).
///
/// The current state value can be accessed through this resource. To *change* the state,
/// queue a transition in the [`NextState<S>`] resource, and it will be applied during the
/// [`StateTransition`](crate::state::StateTransition) schedule - which by default runs after `PreUpdate`.
///
/// You can also manually trigger the [`StateTransition`](crate::state::StateTransition) schedule to apply the changes
/// at an arbitrary time.
///
/// The starting state is defined via the [`Default`] implementation for `S`.
///
/// ```
/// use bevy_state::prelude::*;
/// use bevy_ecs::prelude::*;
/// use bevy_state_macros::States;
///
/// #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default, States)]
/// enum GameState {
/// #[default]
/// MainMenu,
/// SettingsMenu,
/// InGame,
/// }
///
/// fn game_logic(game_state: Res<State<GameState>>) {
/// match game_state.get() {
/// GameState::InGame => {
/// // Run game logic here...
/// },
/// _ => {},
/// }
/// }
/// ```
#[derive(Resource, Debug)]
#[cfg_attr(
feature = "bevy_reflect",
derive(bevy_reflect::Reflect),
reflect(Resource, Debug, PartialEq)
)]
pub struct State<S: States>(pub(crate) S);
impl<S: States> State<S> {
/// Creates a new state with a specific value.
///
/// To change the state use [`NextState<S>`] rather than using this to modify the `State<S>`.
pub fn new(state: S) -> Self {
Self(state)
}
/// Get the current state.
pub fn get(&self) -> &S {
&self.0
}
}
impl<S: States + FromWorld> FromWorld for State<S> {
fn from_world(world: &mut World) -> Self {
Self(S::from_world(world))
}
}
impl<S: States> PartialEq<S> for State<S> {
fn eq(&self, other: &S) -> bool {
self.get() == other
}
}
impl<S: States> Deref for State<S> {
type Target = S;
fn deref(&self) -> &Self::Target {
self.get()
}
}
/// The next state of [`State<S>`].
///
/// This can be fetched as a resource and used to queue state transitions.
/// To queue a transition, call [`NextState::set`] or mutate the value to [`NextState::Pending`] directly.
///
/// Note that these transitions can be overridden by other systems:
/// only the actual value of this resource during the [`StateTransition`](crate::state::StateTransition) schedule matters.
///
/// ```
/// use bevy_state::prelude::*;
/// use bevy_ecs::prelude::*;
///
/// #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default, States)]
/// enum GameState {
/// #[default]
/// MainMenu,
/// SettingsMenu,
/// InGame,
/// }
///
/// fn start_game(mut next_game_state: ResMut<NextState<GameState>>) {
/// next_game_state.set(GameState::InGame);
/// }
/// ```
#[derive(Resource, Debug, Default, Clone)]
#[cfg_attr(
feature = "bevy_reflect",
derive(bevy_reflect::Reflect),
reflect(Resource, Default, Debug)
)]
pub enum NextState<S: FreelyMutableState> {
/// No state transition is pending
#[default]
Unchanged,
/// There is a pending transition for state `S`
Pending(S),
}
impl<S: FreelyMutableState> NextState<S> {
/// Tentatively set a pending state transition to `Some(state)`.
pub fn set(&mut self, state: S) {
*self = Self::Pending(state);
}
/// Remove any pending changes to [`State<S>`]
pub fn reset(&mut self) {
*self = Self::Unchanged;
}
}
pub(crate) fn take_next_state<S: FreelyMutableState>(
next_state: Option<ResMut<NextState<S>>>,
) -> Option<S> {
let mut next_state = next_state?;
match core::mem::take(next_state.bypass_change_detection()) {
NextState::Pending(x) => {
next_state.set_changed();
Some(x)
}
NextState::Unchanged => None,
}
}

351
vendor/bevy_state/src/state/state_set.rs vendored Normal file
View File

@@ -0,0 +1,351 @@
use bevy_ecs::{
event::{EventReader, EventWriter},
schedule::{IntoScheduleConfigs, Schedule},
system::{Commands, IntoSystem, Res, ResMut},
};
use variadics_please::all_tuples;
use self::sealed::StateSetSealed;
use super::{
computed_states::ComputedStates, internal_apply_state_transition, last_transition, run_enter,
run_exit, run_transition, sub_states::SubStates, take_next_state, ApplyStateTransition,
EnterSchedules, ExitSchedules, NextState, State, StateTransitionEvent, StateTransitionSteps,
States, TransitionSchedules,
};
mod sealed {
/// Sealed trait used to prevent external implementations of [`StateSet`](super::StateSet).
pub trait StateSetSealed {}
}
/// A [`States`] type or tuple of types which implement [`States`].
///
/// This trait is used allow implementors of [`States`], as well
/// as tuples containing exclusively implementors of [`States`], to
/// be used as [`ComputedStates::SourceStates`].
///
/// It is sealed, and auto implemented for all [`States`] types and
/// tuples containing them.
pub trait StateSet: StateSetSealed {
/// The total [`DEPENDENCY_DEPTH`](`States::DEPENDENCY_DEPTH`) of all
/// the states that are part of this [`StateSet`], added together.
///
/// Used to de-duplicate computed state executions and prevent cyclic
/// computed states.
const SET_DEPENDENCY_DEPTH: usize;
/// Sets up the systems needed to compute `T` whenever any `State` in this
/// `StateSet` is changed.
fn register_computed_state_systems_in_schedule<T: ComputedStates<SourceStates = Self>>(
schedule: &mut Schedule,
);
/// Sets up the systems needed to compute whether `T` exists whenever any `State` in this
/// `StateSet` is changed.
fn register_sub_state_systems_in_schedule<T: SubStates<SourceStates = Self>>(
schedule: &mut Schedule,
);
}
/// The `InnerStateSet` trait is used to isolate [`ComputedStates`] & [`SubStates`] from
/// needing to wrap all state dependencies in an [`Option<S>`].
///
/// Some [`ComputedStates`]'s might need to exist in different states based on the existence
/// of other states. So we needed the ability to use[`Option<S>`] when appropriate.
///
/// The isolation works because it is implemented for both S & [`Option<S>`], and has the `RawState` associated type
/// that allows it to know what the resource in the world should be. We can then essentially "unwrap" it in our
/// `StateSet` implementation - and the behavior of that unwrapping will depend on the arguments expected by the
/// the [`ComputedStates`] & [`SubStates]`.
trait InnerStateSet: Sized {
type RawState: States;
const DEPENDENCY_DEPTH: usize;
fn convert_to_usable_state(wrapped: Option<&State<Self::RawState>>) -> Option<Self>;
}
impl<S: States> InnerStateSet for S {
type RawState = Self;
const DEPENDENCY_DEPTH: usize = S::DEPENDENCY_DEPTH;
fn convert_to_usable_state(wrapped: Option<&State<Self::RawState>>) -> Option<Self> {
wrapped.map(|v| v.0.clone())
}
}
impl<S: States> InnerStateSet for Option<S> {
type RawState = S;
const DEPENDENCY_DEPTH: usize = S::DEPENDENCY_DEPTH;
fn convert_to_usable_state(wrapped: Option<&State<Self::RawState>>) -> Option<Self> {
Some(wrapped.map(|v| v.0.clone()))
}
}
impl<S: InnerStateSet> StateSetSealed for S {}
impl<S: InnerStateSet> StateSet for S {
const SET_DEPENDENCY_DEPTH: usize = S::DEPENDENCY_DEPTH;
fn register_computed_state_systems_in_schedule<T: ComputedStates<SourceStates = Self>>(
schedule: &mut Schedule,
) {
let apply_state_transition =
|mut parent_changed: EventReader<StateTransitionEvent<S::RawState>>,
event: EventWriter<StateTransitionEvent<T>>,
commands: Commands,
current_state: Option<ResMut<State<T>>>,
state_set: Option<Res<State<S::RawState>>>| {
if parent_changed.is_empty() {
return;
}
parent_changed.clear();
let new_state =
if let Some(state_set) = S::convert_to_usable_state(state_set.as_deref()) {
T::compute(state_set)
} else {
None
};
internal_apply_state_transition(event, commands, current_state, new_state);
};
schedule.configure_sets((
ApplyStateTransition::<T>::default()
.in_set(StateTransitionSteps::DependentTransitions)
.after(ApplyStateTransition::<S::RawState>::default()),
ExitSchedules::<T>::default()
.in_set(StateTransitionSteps::ExitSchedules)
.before(ExitSchedules::<S::RawState>::default()),
TransitionSchedules::<T>::default().in_set(StateTransitionSteps::TransitionSchedules),
EnterSchedules::<T>::default()
.in_set(StateTransitionSteps::EnterSchedules)
.after(EnterSchedules::<S::RawState>::default()),
));
schedule
.add_systems(apply_state_transition.in_set(ApplyStateTransition::<T>::default()))
.add_systems(
last_transition::<T>
.pipe(run_exit::<T>)
.in_set(ExitSchedules::<T>::default()),
)
.add_systems(
last_transition::<T>
.pipe(run_transition::<T>)
.in_set(TransitionSchedules::<T>::default()),
)
.add_systems(
last_transition::<T>
.pipe(run_enter::<T>)
.in_set(EnterSchedules::<T>::default()),
);
}
fn register_sub_state_systems_in_schedule<T: SubStates<SourceStates = Self>>(
schedule: &mut Schedule,
) {
// | parent changed | next state | already exists | should exist | what happens |
// | -------------- | ---------- | -------------- | ------------ | -------------------------------- |
// | false | false | false | - | - |
// | false | false | true | - | - |
// | false | true | false | false | - |
// | true | false | false | false | - |
// | true | true | false | false | - |
// | true | false | true | false | Some(current) -> None |
// | true | true | true | false | Some(current) -> None |
// | true | false | false | true | None -> Some(default) |
// | true | true | false | true | None -> Some(next) |
// | true | true | true | true | Some(current) -> Some(next) |
// | false | true | true | true | Some(current) -> Some(next) |
// | true | false | true | true | Some(current) -> Some(current) |
let apply_state_transition =
|mut parent_changed: EventReader<StateTransitionEvent<S::RawState>>,
event: EventWriter<StateTransitionEvent<T>>,
commands: Commands,
current_state_res: Option<ResMut<State<T>>>,
next_state_res: Option<ResMut<NextState<T>>>,
state_set: Option<Res<State<S::RawState>>>| {
let parent_changed = parent_changed.read().last().is_some();
let next_state = take_next_state(next_state_res);
if !parent_changed && next_state.is_none() {
return;
}
let current_state = current_state_res.as_ref().map(|s| s.get()).cloned();
let initial_state = if parent_changed {
if let Some(state_set) = S::convert_to_usable_state(state_set.as_deref()) {
T::should_exist(state_set)
} else {
None
}
} else {
current_state.clone()
};
let new_state = initial_state.map(|x| next_state.or(current_state).unwrap_or(x));
internal_apply_state_transition(event, commands, current_state_res, new_state);
};
schedule.configure_sets((
ApplyStateTransition::<T>::default()
.in_set(StateTransitionSteps::DependentTransitions)
.after(ApplyStateTransition::<S::RawState>::default()),
ExitSchedules::<T>::default()
.in_set(StateTransitionSteps::ExitSchedules)
.before(ExitSchedules::<S::RawState>::default()),
TransitionSchedules::<T>::default().in_set(StateTransitionSteps::TransitionSchedules),
EnterSchedules::<T>::default()
.in_set(StateTransitionSteps::EnterSchedules)
.after(EnterSchedules::<S::RawState>::default()),
));
schedule
.add_systems(apply_state_transition.in_set(ApplyStateTransition::<T>::default()))
.add_systems(
last_transition::<T>
.pipe(run_exit::<T>)
.in_set(ExitSchedules::<T>::default()),
)
.add_systems(
last_transition::<T>
.pipe(run_transition::<T>)
.in_set(TransitionSchedules::<T>::default()),
)
.add_systems(
last_transition::<T>
.pipe(run_enter::<T>)
.in_set(EnterSchedules::<T>::default()),
);
}
}
macro_rules! impl_state_set_sealed_tuples {
($(#[$meta:meta])* $(($param: ident, $val: ident, $evt: ident)), *) => {
$(#[$meta])*
impl<$($param: InnerStateSet),*> StateSetSealed for ($($param,)*) {}
$(#[$meta])*
impl<$($param: InnerStateSet),*> StateSet for ($($param,)*) {
const SET_DEPENDENCY_DEPTH : usize = $($param::DEPENDENCY_DEPTH +)* 0;
fn register_computed_state_systems_in_schedule<T: ComputedStates<SourceStates = Self>>(
schedule: &mut Schedule,
) {
let apply_state_transition =
|($(mut $evt),*,): ($(EventReader<StateTransitionEvent<$param::RawState>>),*,),
event: EventWriter<StateTransitionEvent<T>>,
commands: Commands,
current_state: Option<ResMut<State<T>>>,
($($val),*,): ($(Option<Res<State<$param::RawState>>>),*,)| {
if ($($evt.is_empty())&&*) {
return;
}
$($evt.clear();)*
let new_state = if let ($(Some($val)),*,) = ($($param::convert_to_usable_state($val.as_deref())),*,) {
T::compute(($($val),*, ))
} else {
None
};
internal_apply_state_transition(event, commands, current_state, new_state);
};
schedule.configure_sets((
ApplyStateTransition::<T>::default()
.in_set(StateTransitionSteps::DependentTransitions)
$(.after(ApplyStateTransition::<$param::RawState>::default()))*,
ExitSchedules::<T>::default()
.in_set(StateTransitionSteps::ExitSchedules)
$(.before(ExitSchedules::<$param::RawState>::default()))*,
TransitionSchedules::<T>::default()
.in_set(StateTransitionSteps::TransitionSchedules),
EnterSchedules::<T>::default()
.in_set(StateTransitionSteps::EnterSchedules)
$(.after(EnterSchedules::<$param::RawState>::default()))*,
));
schedule
.add_systems(apply_state_transition.in_set(ApplyStateTransition::<T>::default()))
.add_systems(last_transition::<T>.pipe(run_exit::<T>).in_set(ExitSchedules::<T>::default()))
.add_systems(last_transition::<T>.pipe(run_transition::<T>).in_set(TransitionSchedules::<T>::default()))
.add_systems(last_transition::<T>.pipe(run_enter::<T>).in_set(EnterSchedules::<T>::default()));
}
fn register_sub_state_systems_in_schedule<T: SubStates<SourceStates = Self>>(
schedule: &mut Schedule,
) {
let apply_state_transition =
|($(mut $evt),*,): ($(EventReader<StateTransitionEvent<$param::RawState>>),*,),
event: EventWriter<StateTransitionEvent<T>>,
commands: Commands,
current_state_res: Option<ResMut<State<T>>>,
next_state_res: Option<ResMut<NextState<T>>>,
($($val),*,): ($(Option<Res<State<$param::RawState>>>),*,)| {
let parent_changed = ($($evt.read().last().is_some())&&*);
let next_state = take_next_state(next_state_res);
if !parent_changed && next_state.is_none() {
return;
}
let current_state = current_state_res.as_ref().map(|s| s.get()).cloned();
let initial_state = if parent_changed {
if let ($(Some($val)),*,) = ($($param::convert_to_usable_state($val.as_deref())),*,) {
T::should_exist(($($val),*, ))
} else {
None
}
} else {
current_state.clone()
};
let new_state = initial_state.map(|x| next_state.or(current_state).unwrap_or(x));
internal_apply_state_transition(event, commands, current_state_res, new_state);
};
schedule.configure_sets((
ApplyStateTransition::<T>::default()
.in_set(StateTransitionSteps::DependentTransitions)
$(.after(ApplyStateTransition::<$param::RawState>::default()))*,
ExitSchedules::<T>::default()
.in_set(StateTransitionSteps::ExitSchedules)
$(.before(ExitSchedules::<$param::RawState>::default()))*,
TransitionSchedules::<T>::default()
.in_set(StateTransitionSteps::TransitionSchedules),
EnterSchedules::<T>::default()
.in_set(StateTransitionSteps::EnterSchedules)
$(.after(EnterSchedules::<$param::RawState>::default()))*,
));
schedule
.add_systems(apply_state_transition.in_set(ApplyStateTransition::<T>::default()))
.add_systems(last_transition::<T>.pipe(run_exit::<T>).in_set(ExitSchedules::<T>::default()))
.add_systems(last_transition::<T>.pipe(run_transition::<T>).in_set(TransitionSchedules::<T>::default()))
.add_systems(last_transition::<T>.pipe(run_enter::<T>).in_set(EnterSchedules::<T>::default()));
}
}
};
}
all_tuples!(
#[doc(fake_variadic)]
impl_state_set_sealed_tuples,
1,
15,
S,
s,
ereader
);

71
vendor/bevy_state/src/state/states.rs vendored Normal file
View File

@@ -0,0 +1,71 @@
use core::fmt::Debug;
use core::hash::Hash;
/// Types that can define world-wide states in a finite-state machine.
///
/// The [`Default`] trait defines the starting state.
/// Multiple states can be defined for the same world,
/// allowing you to classify the state of the world across orthogonal dimensions.
/// You can access the current state of type `T` with the [`State<T>`](crate::state::State) resource,
/// and the queued state with the [`NextState<T>`](crate::state::NextState) resource.
///
/// State transitions typically occur in the [`OnEnter<T::Variant>`](crate::state::OnEnter) and [`OnExit<T::Variant>`](crate::state::OnExit) schedules,
/// which can be run by triggering the [`StateTransition`](crate::state::StateTransition) schedule.
///
/// Types used as [`ComputedStates`](crate::state::ComputedStates) do not need to and should not derive [`States`].
/// [`ComputedStates`](crate::state::ComputedStates) should not be manually mutated: functionality provided
/// by the [`States`] derive and the associated [`FreelyMutableState`](crate::state::FreelyMutableState) trait.
///
/// # Example
///
/// ```
/// use bevy_state::prelude::*;
/// use bevy_ecs::prelude::IntoScheduleConfigs;
/// use bevy_ecs::system::{ResMut, ScheduleSystem};
///
///
/// #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default, States)]
/// enum GameState {
/// #[default]
/// MainMenu,
/// SettingsMenu,
/// InGame,
/// }
///
/// fn handle_escape_pressed(mut next_state: ResMut<NextState<GameState>>) {
/// # let escape_pressed = true;
/// if escape_pressed {
/// next_state.set(GameState::SettingsMenu);
/// }
/// }
///
/// fn open_settings_menu() {
/// // Show the settings menu...
/// }
///
/// # struct AppMock;
/// # impl AppMock {
/// # fn add_systems<S, M>(&mut self, schedule: S, systems: impl IntoScheduleConfigs<ScheduleSystem, M>) {}
/// # }
/// # struct Update;
/// # let mut app = AppMock;
///
/// app.add_systems(Update, handle_escape_pressed.run_if(in_state(GameState::MainMenu)));
/// app.add_systems(OnEnter(GameState::SettingsMenu), open_settings_menu);
/// ```
#[diagnostic::on_unimplemented(
message = "`{Self}` can not be used as a state",
label = "invalid state",
note = "consider annotating `{Self}` with `#[derive(States)]`"
)]
pub trait States: 'static + Send + Sync + Clone + PartialEq + Eq + Hash + Debug {
/// How many other states this state depends on.
/// Used to help order transitions and de-duplicate [`ComputedStates`](crate::state::ComputedStates), as well as prevent cyclical
/// `ComputedState` dependencies.
const DEPENDENCY_DEPTH: usize = 1;
/// Should [`StateScoped`](crate::state_scoped::StateScoped) be enabled for this state? If set to `true`,
/// the `StateScoped` component will be used to remove entities when changing state.
const SCOPED_ENTITIES_ENABLED: bool = false;
}

View File

@@ -0,0 +1,175 @@
use bevy_ecs::schedule::Schedule;
use super::{freely_mutable_state::FreelyMutableState, state_set::StateSet, states::States};
pub use bevy_state_macros::SubStates;
/// A sub-state is a state that exists only when the source state meet certain conditions,
/// but unlike [`ComputedStates`](crate::state::ComputedStates) - while they exist they can be manually modified.
///
/// The default approach to creating [`SubStates`] is using the derive macro, and defining a single source state
/// and value to determine it's existence.
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # use bevy_state::prelude::*;
///
/// #[derive(States, Clone, PartialEq, Eq, Hash, Debug, Default)]
/// enum AppState {
/// #[default]
/// Menu,
/// InGame
/// }
///
///
/// #[derive(SubStates, Clone, PartialEq, Eq, Hash, Debug, Default)]
/// #[source(AppState = AppState::InGame)]
/// enum GamePhase {
/// #[default]
/// Setup,
/// Battle,
/// Conclusion
/// }
/// ```
///
/// you can then add it to an App, and from there you use the state as normal:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # use bevy_state::prelude::*;
///
/// # struct App;
/// # impl App {
/// # fn new() -> Self { App }
/// # fn init_state<S>(&mut self) -> &mut Self {self}
/// # fn add_sub_state<S>(&mut self) -> &mut Self {self}
/// # }
/// # struct AppState;
/// # struct GamePhase;
///
/// App::new()
/// .init_state::<AppState>()
/// .add_sub_state::<GamePhase>();
/// ```
///
/// In more complex situations, the recommendation is to use an intermediary computed state, like so:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # use bevy_state::prelude::*;
///
/// /// Computed States require some state to derive from
/// #[derive(States, Clone, PartialEq, Eq, Hash, Debug, Default)]
/// enum AppState {
/// #[default]
/// Menu,
/// InGame { paused: bool }
/// }
///
/// #[derive(Clone, PartialEq, Eq, Hash, Debug)]
/// struct InGame;
///
/// impl ComputedStates for InGame {
/// /// We set the source state to be the state, or set of states,
/// /// we want to depend on. Any of the states can be wrapped in an Option.
/// type SourceStates = Option<AppState>;
///
/// /// We then define the compute function, which takes in the AppState
/// fn compute(sources: Option<AppState>) -> Option<Self> {
/// match sources {
/// /// When we are in game, we want to return the InGame state
/// Some(AppState::InGame { .. }) => Some(InGame),
/// /// Otherwise, we don't want the `State<InGame>` resource to exist,
/// /// so we return None.
/// _ => None
/// }
/// }
/// }
///
/// #[derive(SubStates, Clone, PartialEq, Eq, Hash, Debug, Default)]
/// #[source(InGame = InGame)]
/// enum GamePhase {
/// #[default]
/// Setup,
/// Battle,
/// Conclusion
/// }
/// ```
///
/// However, you can also manually implement them. If you do so, you'll also need to manually implement the `States` & `FreelyMutableState` traits.
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # use bevy_state::prelude::*;
/// # use bevy_state::state::{FreelyMutableState, NextState};
///
/// /// Computed States require some state to derive from
/// #[derive(States, Clone, PartialEq, Eq, Hash, Debug, Default)]
/// enum AppState {
/// #[default]
/// Menu,
/// InGame { paused: bool }
/// }
///
/// #[derive(Clone, PartialEq, Eq, Hash, Debug)]
/// enum GamePhase {
/// Setup,
/// Battle,
/// Conclusion
/// }
///
/// impl SubStates for GamePhase {
/// /// We set the source state to be the state, or set of states,
/// /// we want to depend on. Any of the states can be wrapped in an Option.
/// type SourceStates = Option<AppState>;
///
/// /// We then define the compute function, which takes in the [`Self::SourceStates`]
/// fn should_exist(sources: Option<AppState>) -> Option<Self> {
/// match sources {
/// /// When we are in game, we want a GamePhase state to exist.
/// /// We can set the initial value here or overwrite it through [`NextState`].
/// Some(AppState::InGame { .. }) => Some(Self::Setup),
/// /// If we don't want the `State<GamePhase>` resource to exist we return [`None`].
/// _ => None
/// }
/// }
/// }
///
/// impl States for GamePhase {
/// const DEPENDENCY_DEPTH : usize = <GamePhase as SubStates>::SourceStates::SET_DEPENDENCY_DEPTH + 1;
/// }
///
/// impl FreelyMutableState for GamePhase {}
/// ```
#[diagnostic::on_unimplemented(
message = "`{Self}` can not be used as a sub-state",
label = "invalid sub-state",
note = "consider annotating `{Self}` with `#[derive(SubStates)]`"
)]
pub trait SubStates: States + FreelyMutableState {
/// The set of states from which the [`Self`] is derived.
///
/// This can either be a single type that implements [`States`], or a tuple
/// containing multiple types that implement [`States`], or any combination of
/// types implementing [`States`] and Options of types implementing [`States`].
type SourceStates: StateSet;
/// This function gets called whenever one of the [`SourceStates`](Self::SourceStates) changes.
/// The result is used to determine the existence of [`State<Self>`](crate::state::State).
///
/// If the result is [`None`], the [`State<Self>`](crate::state::State) resource will be removed from the world,
/// otherwise if the [`State<Self>`](crate::state::State) resource doesn't exist
/// it will be created from the returned [`Some`] as the initial state.
///
/// Value within [`Some`] is ignored if the state already exists in the world
/// and only symbolizes that the state should still exist.
///
/// Initial value can also be overwritten by [`NextState`](crate::state::NextState).
fn should_exist(sources: Self::SourceStates) -> Option<Self>;
/// This function sets up systems that compute the state whenever one of the [`SourceStates`](Self::SourceStates)
/// change. It is called by `App::add_computed_state`, but can be called manually if `App` is not
/// used.
fn register_sub_state_systems(schedule: &mut Schedule) {
Self::SourceStates::register_sub_state_systems_in_schedule::<Self>(schedule);
}
}

View File

@@ -0,0 +1,260 @@
use core::{marker::PhantomData, mem};
use bevy_ecs::{
event::{Event, EventReader, EventWriter},
schedule::{IntoScheduleConfigs, Schedule, ScheduleLabel, Schedules, SystemSet},
system::{Commands, In, ResMut},
world::World,
};
use super::{resources::State, states::States};
/// The label of a [`Schedule`] that **only** runs whenever [`State<S>`] enters the provided state.
///
/// This schedule ignores identity transitions.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
pub struct OnEnter<S: States>(pub S);
/// The label of a [`Schedule`] that **only** runs whenever [`State<S>`] exits the provided state.
///
/// This schedule ignores identity transitions.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
pub struct OnExit<S: States>(pub S);
/// The label of a [`Schedule`] that **only** runs whenever [`State<S>`]
/// exits AND enters the provided `exited` and `entered` states.
///
/// Systems added to this schedule are always ran *after* [`OnExit`], and *before* [`OnEnter`].
///
/// This schedule will run on identity transitions.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
pub struct OnTransition<S: States> {
/// The state being exited.
pub exited: S,
/// The state being entered.
pub entered: S,
}
/// Runs [state transitions](States).
///
/// By default, it will be triggered once before [`PreStartup`] and then each frame after [`PreUpdate`], but
/// you can manually trigger it at arbitrary times by creating an exclusive
/// system to run the schedule.
///
/// ```rust
/// use bevy_state::prelude::*;
/// use bevy_ecs::prelude::*;
///
/// fn run_state_transitions(world: &mut World) {
/// let _ = world.try_run_schedule(StateTransition);
/// }
/// ```
///
/// [`PreStartup`]: https://docs.rs/bevy/latest/bevy/prelude/struct.PreStartup.html
/// [`PreUpdate`]: https://docs.rs/bevy/latest/bevy/prelude/struct.PreUpdate.html
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
pub struct StateTransition;
/// Event sent when any state transition of `S` happens.
/// This includes identity transitions, where `exited` and `entered` have the same value.
///
/// If you know exactly what state you want to respond to ahead of time, consider [`OnEnter`], [`OnTransition`], or [`OnExit`]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Event)]
pub struct StateTransitionEvent<S: States> {
/// The state being exited.
pub exited: Option<S>,
/// The state being entered.
pub entered: Option<S>,
}
/// Applies state transitions and runs transitions schedules in order.
///
/// These system sets are run sequentially, in the order of the enum variants.
#[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)]
pub enum StateTransitionSteps {
/// States apply their transitions from [`NextState`](super::NextState)
/// and compute functions based on their parent states.
DependentTransitions,
/// Exit schedules are executed in leaf to root order
ExitSchedules,
/// Transition schedules are executed in arbitrary order.
TransitionSchedules,
/// Enter schedules are executed in root to leaf order.
EnterSchedules,
}
#[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)]
/// System set that runs exit schedule(s) for state `S`.
pub struct ExitSchedules<S: States>(PhantomData<S>);
impl<S: States> Default for ExitSchedules<S> {
fn default() -> Self {
Self(Default::default())
}
}
#[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)]
/// System set that runs transition schedule(s) for state `S`.
pub struct TransitionSchedules<S: States>(PhantomData<S>);
impl<S: States> Default for TransitionSchedules<S> {
fn default() -> Self {
Self(Default::default())
}
}
#[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)]
/// System set that runs enter schedule(s) for state `S`.
pub struct EnterSchedules<S: States>(PhantomData<S>);
impl<S: States> Default for EnterSchedules<S> {
fn default() -> Self {
Self(Default::default())
}
}
/// System set that applies transitions for state `S`.
#[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)]
pub(crate) struct ApplyStateTransition<S: States>(PhantomData<S>);
impl<S: States> Default for ApplyStateTransition<S> {
fn default() -> Self {
Self(Default::default())
}
}
/// This function actually applies a state change, and registers the required
/// schedules for downstream computed states and transition schedules.
///
/// The `new_state` is an option to allow for removal - `None` will trigger the
/// removal of the `State<S>` resource from the [`World`].
pub(crate) fn internal_apply_state_transition<S: States>(
mut event: EventWriter<StateTransitionEvent<S>>,
mut commands: Commands,
current_state: Option<ResMut<State<S>>>,
new_state: Option<S>,
) {
match new_state {
Some(entered) => {
match current_state {
// If the [`State<S>`] resource exists, and the state is not the one we are
// entering - we need to set the new value, compute dependent states, send transition events
// and register transition schedules.
Some(mut state_resource) => {
let exited = match *state_resource == entered {
true => entered.clone(),
false => mem::replace(&mut state_resource.0, entered.clone()),
};
// Transition events are sent even for same state transitions
// Although enter and exit schedules are not run by default.
event.write(StateTransitionEvent {
exited: Some(exited.clone()),
entered: Some(entered.clone()),
});
}
None => {
// If the [`State<S>`] resource does not exist, we create it, compute dependent states, send a transition event and register the `OnEnter` schedule.
commands.insert_resource(State(entered.clone()));
event.write(StateTransitionEvent {
exited: None,
entered: Some(entered.clone()),
});
}
};
}
None => {
// We first remove the [`State<S>`] resource, and if one existed we compute dependent states, send a transition event and run the `OnExit` schedule.
if let Some(resource) = current_state {
commands.remove_resource::<State<S>>();
event.write(StateTransitionEvent {
exited: Some(resource.get().clone()),
entered: None,
});
}
}
}
}
/// Sets up the schedules and systems for handling state transitions
/// within a [`World`].
///
/// Runs automatically when using `App` to insert states, but needs to
/// be added manually in other situations.
pub fn setup_state_transitions_in_world(world: &mut World) {
let mut schedules = world.get_resource_or_init::<Schedules>();
if schedules.contains(StateTransition) {
return;
}
let mut schedule = Schedule::new(StateTransition);
schedule.configure_sets(
(
StateTransitionSteps::DependentTransitions,
StateTransitionSteps::ExitSchedules,
StateTransitionSteps::TransitionSchedules,
StateTransitionSteps::EnterSchedules,
)
.chain(),
);
schedules.insert(schedule);
}
/// Returns the latest state transition event of type `S`, if any are available.
pub fn last_transition<S: States>(
mut reader: EventReader<StateTransitionEvent<S>>,
) -> Option<StateTransitionEvent<S>> {
reader.read().last().cloned()
}
pub(crate) fn run_enter<S: States>(
transition: In<Option<StateTransitionEvent<S>>>,
world: &mut World,
) {
let Some(transition) = transition.0 else {
return;
};
if transition.entered == transition.exited {
return;
}
let Some(entered) = transition.entered else {
return;
};
let _ = world.try_run_schedule(OnEnter(entered));
}
pub(crate) fn run_exit<S: States>(
transition: In<Option<StateTransitionEvent<S>>>,
world: &mut World,
) {
let Some(transition) = transition.0 else {
return;
};
if transition.entered == transition.exited {
return;
}
let Some(exited) = transition.exited else {
return;
};
let _ = world.try_run_schedule(OnExit(exited));
}
pub(crate) fn run_transition<S: States>(
transition: In<Option<StateTransitionEvent<S>>>,
world: &mut World,
) {
let Some(transition) = transition.0 else {
return;
};
let Some(exited) = transition.exited else {
return;
};
let Some(entered) = transition.entered else {
return;
};
let _ = world.try_run_schedule(OnTransition { exited, entered });
}

93
vendor/bevy_state/src/state_scoped.rs vendored Normal file
View File

@@ -0,0 +1,93 @@
#[cfg(feature = "bevy_reflect")]
use bevy_ecs::reflect::ReflectComponent;
use bevy_ecs::{
component::Component,
entity::Entity,
event::EventReader,
system::{Commands, Query},
};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::prelude::*;
use crate::state::{StateTransitionEvent, States};
/// Entities marked with this component will be removed
/// when the world's state of the matching type no longer matches the supplied value.
///
/// To enable this feature remember to add the attribute `#[states(scoped_entities)]` when deriving [`States`].
/// It's also possible to enable it when adding the state to an app with [`enable_state_scoped_entities`](crate::app::AppExtStates::enable_state_scoped_entities).
///
/// ```
/// use bevy_state::prelude::*;
/// use bevy_ecs::prelude::*;
/// use bevy_ecs::system::ScheduleSystem;
///
/// #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default, States)]
/// #[states(scoped_entities)]
/// enum GameState {
/// #[default]
/// MainMenu,
/// SettingsMenu,
/// InGame,
/// }
///
/// # #[derive(Component)]
/// # struct Player;
///
/// fn spawn_player(mut commands: Commands) {
/// commands.spawn((
/// StateScoped(GameState::InGame),
/// Player
/// ));
/// }
///
/// # struct AppMock;
/// # impl AppMock {
/// # fn init_state<S>(&mut self) {}
/// # fn enable_state_scoped_entities<S>(&mut self) {}
/// # fn add_systems<S, M>(&mut self, schedule: S, systems: impl IntoScheduleConfigs<ScheduleSystem, M>) {}
/// # }
/// # struct Update;
/// # let mut app = AppMock;
///
/// app.init_state::<GameState>();
/// app.add_systems(OnEnter(GameState::InGame), spawn_player);
/// ```
#[derive(Component, Clone)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Component, Clone))]
pub struct StateScoped<S: States>(pub S);
impl<S> Default for StateScoped<S>
where
S: States + Default,
{
fn default() -> Self {
Self(S::default())
}
}
/// Removes entities marked with [`StateScoped<S>`]
/// when their state no longer matches the world state.
pub fn clear_state_scoped_entities<S: States>(
mut commands: Commands,
mut transitions: EventReader<StateTransitionEvent<S>>,
query: Query<(Entity, &StateScoped<S>)>,
) {
// We use the latest event, because state machine internals generate at most 1
// transition event (per type) each frame. No event means no change happened
// and we skip iterating all entities.
let Some(transition) = transitions.read().last() else {
return;
};
if transition.entered == transition.exited {
return;
}
let Some(exited) = &transition.exited else {
return;
};
for (entity, binding) in &query {
if binding.0 == *exited {
commands.entity(entity).despawn();
}
}
}

View File

@@ -0,0 +1,111 @@
use alloc::vec::Vec;
use core::marker::PhantomData;
use bevy_app::{App, SubApp};
use bevy_ecs::{
event::{Event, EventReader, Events},
resource::Resource,
system::Commands,
world::World,
};
use bevy_platform::collections::HashMap;
use crate::state::{FreelyMutableState, OnExit, StateTransitionEvent};
fn clear_event_queue<E: Event>(w: &mut World) {
if let Some(mut queue) = w.get_resource_mut::<Events<E>>() {
queue.clear();
}
}
#[derive(Resource)]
struct StateScopedEvents<S: FreelyMutableState> {
cleanup_fns: HashMap<S, Vec<fn(&mut World)>>,
}
impl<S: FreelyMutableState> StateScopedEvents<S> {
fn add_event<E: Event>(&mut self, state: S) {
self.cleanup_fns
.entry(state)
.or_default()
.push(clear_event_queue::<E>);
}
fn cleanup(&self, w: &mut World, state: S) {
let Some(fns) = self.cleanup_fns.get(&state) else {
return;
};
for callback in fns {
(*callback)(w);
}
}
}
impl<S: FreelyMutableState> Default for StateScopedEvents<S> {
fn default() -> Self {
Self {
cleanup_fns: HashMap::default(),
}
}
}
fn cleanup_state_scoped_event<S: FreelyMutableState>(
mut c: Commands,
mut transitions: EventReader<StateTransitionEvent<S>>,
) {
let Some(transition) = transitions.read().last() else {
return;
};
if transition.entered == transition.exited {
return;
}
let Some(exited) = transition.exited.clone() else {
return;
};
c.queue(move |w: &mut World| {
w.resource_scope::<StateScopedEvents<S>, ()>(|w, events| {
events.cleanup(w, exited);
});
});
}
fn add_state_scoped_event_impl<E: Event, S: FreelyMutableState>(
app: &mut SubApp,
_p: PhantomData<E>,
state: S,
) {
if !app.world().contains_resource::<StateScopedEvents<S>>() {
app.init_resource::<StateScopedEvents<S>>();
}
app.add_event::<E>();
app.world_mut()
.resource_mut::<StateScopedEvents<S>>()
.add_event::<E>(state.clone());
app.add_systems(OnExit(state), cleanup_state_scoped_event::<S>);
}
/// Extension trait for [`App`] adding methods for registering state scoped events.
pub trait StateScopedEventsAppExt {
/// Adds an [`Event`] that is automatically cleaned up when leaving the specified `state`.
///
/// Note that event cleanup is ordered ambiguously relative to [`StateScoped`](crate::prelude::StateScoped) entity
/// cleanup and the [`OnExit`] schedule for the target state. All of these (state scoped
/// entities and events cleanup, and `OnExit`) occur within schedule [`StateTransition`](crate::prelude::StateTransition)
/// and system set `StateTransitionSteps::ExitSchedules`.
fn add_state_scoped_event<E: Event>(&mut self, state: impl FreelyMutableState) -> &mut Self;
}
impl StateScopedEventsAppExt for App {
fn add_state_scoped_event<E: Event>(&mut self, state: impl FreelyMutableState) -> &mut Self {
add_state_scoped_event_impl(self.main_mut(), PhantomData::<E>, state);
self
}
}
impl StateScopedEventsAppExt for SubApp {
fn add_state_scoped_event<E: Event>(&mut self, state: impl FreelyMutableState) -> &mut Self {
add_state_scoped_event_impl(self, PhantomData::<E>, state);
self
}
}