Path: blob/main/crates/bevy_state/src/state/computed_states.rs
6596 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/// Computes the next value of [`State<Self>`](crate::state::State).78/// This function gets called whenever one of the [`SourceStates`](Self::SourceStates) changes.79///80/// If the result is [`None`], the [`State<Self>`](crate::state::State) resource will be removed from the world.81fn compute(sources: Self::SourceStates) -> Option<Self>;8283/// This function sets up systems that compute the state whenever one of the [`SourceStates`](Self::SourceStates)84/// change. It is called by `App::add_computed_state`, but can be called manually if `App` is not85/// used.86fn register_computed_state_systems(schedule: &mut Schedule) {87Self::SourceStates::register_computed_state_systems_in_schedule::<Self>(schedule);88}89}9091impl<S: ComputedStates> States for S {92const DEPENDENCY_DEPTH: usize = S::SourceStates::SET_DEPENDENCY_DEPTH + 1;93}9495#[cfg(test)]96mod tests {97use crate::{98app::{AppExtStates, StatesPlugin},99prelude::DespawnOnEnter,100state::{ComputedStates, StateTransition},101};102use bevy_app::App;103use bevy_ecs::component::Component;104use bevy_state_macros::States;105106#[derive(Component)]107struct TestComponent;108109#[derive(States, Default, PartialEq, Eq, Hash, Debug, Clone)]110struct TestState;111112#[derive(PartialEq, Eq, Hash, Debug, Clone)]113struct TestComputedState;114115impl ComputedStates for TestComputedState {116type SourceStates = TestState;117118fn compute(_: Self::SourceStates) -> Option<Self> {119Some(TestComputedState)120}121}122123#[test]124fn computed_states_are_state_scoped_by_default() {125let mut app = App::new();126app.add_plugins(StatesPlugin);127app.insert_state(TestState);128app.add_computed_state::<TestComputedState>();129130let world = app.world_mut();131132world.spawn((DespawnOnEnter(TestComputedState), TestComponent));133134assert!(world.query::<&TestComponent>().single(world).is_ok());135world.run_schedule(StateTransition);136assert_eq!(world.query::<&TestComponent>().iter(world).len(), 0);137}138}139140141