Path: blob/main/crates/bevy_state/src/state/computed_states.rs
9395 views
use core::{fmt::Debug, hash::Hash};12use bevy_ecs::schedule::Schedule;34use super::{state_set::StateSet, states::States};56/// A state whose value is automatically computed based on the values of other [`States`].7///8/// A **computed state** is a state that is deterministically derived from a set of `SourceStates`.9/// The [`StateSet`] is passed into the `compute` method whenever one of them changes, and the10/// result becomes the state's value.11///12/// ```13/// # use bevy_state::prelude::*;14/// # use bevy_ecs::prelude::*;15/// #16/// /// Computed States require some state to derive from17/// #[derive(States, Clone, PartialEq, Eq, Hash, Debug, Default)]18/// enum AppState {19/// #[default]20/// Menu,21/// InGame { paused: bool }22/// }23///24/// #[derive(Clone, PartialEq, Eq, Hash, Debug)]25/// struct InGame;26///27/// impl ComputedStates for InGame {28/// /// We set the source state to be the state, or a tuple of states,29/// /// we want to depend on. You can also wrap each state in an Option,30/// /// if you want the computed state to execute even if the state doesn't31/// /// currently exist in the world.32/// type SourceStates = AppState;33///34/// /// We then define the compute function, which takes in35/// /// your SourceStates36/// fn compute(sources: AppState) -> Option<Self> {37/// match sources {38/// /// When we are in game, we want to return the InGame state39/// AppState::InGame { .. } => Some(InGame),40/// /// Otherwise, we don't want the `State<InGame>` resource to exist,41/// /// so we return None.42/// _ => None43/// }44/// }45/// }46/// ```47///48/// you can then add it to an App, and from there you use the state as normal49///50/// ```51/// # use bevy_state::prelude::*;52/// # use bevy_ecs::prelude::*;53/// #54/// # struct App;55/// # impl App {56/// # fn new() -> Self { App }57/// # fn init_state<S>(&mut self) -> &mut Self {self}58/// # fn add_computed_state<S>(&mut self) -> &mut Self {self}59/// # }60/// # struct AppState;61/// # struct InGame;62/// #63/// App::new()64/// .init_state::<AppState>()65/// .add_computed_state::<InGame>();66/// ```67pub trait ComputedStates: 'static + Send + Sync + Clone + PartialEq + Eq + Hash + Debug {68/// The set of states from which the [`Self`] is derived.69///70/// This can either be a single type that implements [`States`], an Option of a type71/// that implements [`States`], or a tuple72/// containing multiple types that implement [`States`] or Optional versions of them.73///74/// For example, `(MapState, EnemyState)` is valid, as is `(MapState, Option<EnemyState>)`75type SourceStates: StateSet;7677/// Whether state transition schedules should be run when the state changes to the same value. Default is `true`.78const ALLOW_SAME_STATE_TRANSITIONS: bool = true;7980/// Computes the next value of [`State<Self>`](crate::state::State).81/// This function gets called whenever one of the [`SourceStates`](Self::SourceStates) changes.82///83/// If the result is [`None`], the [`State<Self>`](crate::state::State) resource will be removed from the world.84fn compute(sources: Self::SourceStates) -> Option<Self>;8586/// This function sets up systems that compute the state whenever one of the [`SourceStates`](Self::SourceStates)87/// change. It is called by `App::add_computed_state`, but can be called manually if `App` is not88/// used.89fn register_computed_state_systems(schedule: &mut Schedule) {90Self::SourceStates::register_computed_state_systems_in_schedule::<Self>(schedule);91}92}9394impl<S: ComputedStates> States for S {95const DEPENDENCY_DEPTH: usize = S::SourceStates::SET_DEPENDENCY_DEPTH + 1;96}9798#[cfg(test)]99mod tests {100use crate::{101app::{AppExtStates, StatesPlugin},102prelude::DespawnOnEnter,103state::{ComputedStates, StateTransition},104};105use bevy_app::App;106use bevy_ecs::component::Component;107use bevy_state_macros::States;108109#[derive(Component)]110struct TestComponent;111112#[derive(States, Default, PartialEq, Eq, Hash, Debug, Clone)]113struct TestState;114115#[derive(PartialEq, Eq, Hash, Debug, Clone)]116struct TestComputedState;117118impl ComputedStates for TestComputedState {119type SourceStates = TestState;120121fn compute(_: Self::SourceStates) -> Option<Self> {122Some(TestComputedState)123}124}125126#[test]127fn computed_states_are_state_scoped_by_default() {128let mut app = App::new();129app.add_plugins(StatesPlugin);130app.insert_state(TestState);131app.add_computed_state::<TestComputedState>();132133let world = app.world_mut();134135world.spawn((DespawnOnEnter(TestComputedState), TestComponent));136137assert!(world.query::<&TestComponent>().single(world).is_ok());138world.run_schedule(StateTransition);139assert_eq!(world.query::<&TestComponent>().iter(world).len(), 0);140}141}142143144