Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_state/src/state/computed_states.rs
9395 views
1
use core::{fmt::Debug, hash::Hash};
2
3
use bevy_ecs::schedule::Schedule;
4
5
use super::{state_set::StateSet, states::States};
6
7
/// A state whose value is automatically computed based on the values of other [`States`].
8
///
9
/// A **computed state** is a state that is deterministically derived from a set of `SourceStates`.
10
/// The [`StateSet`] is passed into the `compute` method whenever one of them changes, and the
11
/// result becomes the state's value.
12
///
13
/// ```
14
/// # use bevy_state::prelude::*;
15
/// # use bevy_ecs::prelude::*;
16
/// #
17
/// /// Computed States require some state to derive from
18
/// #[derive(States, Clone, PartialEq, Eq, Hash, Debug, Default)]
19
/// enum AppState {
20
/// #[default]
21
/// Menu,
22
/// InGame { paused: bool }
23
/// }
24
///
25
/// #[derive(Clone, PartialEq, Eq, Hash, Debug)]
26
/// struct InGame;
27
///
28
/// impl ComputedStates for InGame {
29
/// /// We set the source state to be the state, or a tuple of states,
30
/// /// we want to depend on. You can also wrap each state in an Option,
31
/// /// if you want the computed state to execute even if the state doesn't
32
/// /// currently exist in the world.
33
/// type SourceStates = AppState;
34
///
35
/// /// We then define the compute function, which takes in
36
/// /// your SourceStates
37
/// fn compute(sources: AppState) -> Option<Self> {
38
/// match sources {
39
/// /// When we are in game, we want to return the InGame state
40
/// AppState::InGame { .. } => Some(InGame),
41
/// /// Otherwise, we don't want the `State<InGame>` resource to exist,
42
/// /// so we return None.
43
/// _ => None
44
/// }
45
/// }
46
/// }
47
/// ```
48
///
49
/// you can then add it to an App, and from there you use the state as normal
50
///
51
/// ```
52
/// # use bevy_state::prelude::*;
53
/// # use bevy_ecs::prelude::*;
54
/// #
55
/// # struct App;
56
/// # impl App {
57
/// # fn new() -> Self { App }
58
/// # fn init_state<S>(&mut self) -> &mut Self {self}
59
/// # fn add_computed_state<S>(&mut self) -> &mut Self {self}
60
/// # }
61
/// # struct AppState;
62
/// # struct InGame;
63
/// #
64
/// App::new()
65
/// .init_state::<AppState>()
66
/// .add_computed_state::<InGame>();
67
/// ```
68
pub trait ComputedStates: 'static + Send + Sync + Clone + PartialEq + Eq + Hash + Debug {
69
/// The set of states from which the [`Self`] is derived.
70
///
71
/// This can either be a single type that implements [`States`], an Option of a type
72
/// that implements [`States`], or a tuple
73
/// containing multiple types that implement [`States`] or Optional versions of them.
74
///
75
/// For example, `(MapState, EnemyState)` is valid, as is `(MapState, Option<EnemyState>)`
76
type SourceStates: StateSet;
77
78
/// Whether state transition schedules should be run when the state changes to the same value. Default is `true`.
79
const ALLOW_SAME_STATE_TRANSITIONS: bool = true;
80
81
/// Computes the next value of [`State<Self>`](crate::state::State).
82
/// This function gets called whenever one of the [`SourceStates`](Self::SourceStates) changes.
83
///
84
/// If the result is [`None`], the [`State<Self>`](crate::state::State) resource will be removed from the world.
85
fn compute(sources: Self::SourceStates) -> Option<Self>;
86
87
/// This function sets up systems that compute the state whenever one of the [`SourceStates`](Self::SourceStates)
88
/// change. It is called by `App::add_computed_state`, but can be called manually if `App` is not
89
/// used.
90
fn register_computed_state_systems(schedule: &mut Schedule) {
91
Self::SourceStates::register_computed_state_systems_in_schedule::<Self>(schedule);
92
}
93
}
94
95
impl<S: ComputedStates> States for S {
96
const DEPENDENCY_DEPTH: usize = S::SourceStates::SET_DEPENDENCY_DEPTH + 1;
97
}
98
99
#[cfg(test)]
100
mod tests {
101
use crate::{
102
app::{AppExtStates, StatesPlugin},
103
prelude::DespawnOnEnter,
104
state::{ComputedStates, StateTransition},
105
};
106
use bevy_app::App;
107
use bevy_ecs::component::Component;
108
use bevy_state_macros::States;
109
110
#[derive(Component)]
111
struct TestComponent;
112
113
#[derive(States, Default, PartialEq, Eq, Hash, Debug, Clone)]
114
struct TestState;
115
116
#[derive(PartialEq, Eq, Hash, Debug, Clone)]
117
struct TestComputedState;
118
119
impl ComputedStates for TestComputedState {
120
type SourceStates = TestState;
121
122
fn compute(_: Self::SourceStates) -> Option<Self> {
123
Some(TestComputedState)
124
}
125
}
126
127
#[test]
128
fn computed_states_are_state_scoped_by_default() {
129
let mut app = App::new();
130
app.add_plugins(StatesPlugin);
131
app.insert_state(TestState);
132
app.add_computed_state::<TestComputedState>();
133
134
let world = app.world_mut();
135
136
world.spawn((DespawnOnEnter(TestComputedState), TestComponent));
137
138
assert!(world.query::<&TestComponent>().single(world).is_ok());
139
world.run_schedule(StateTransition);
140
assert_eq!(world.query::<&TestComponent>().iter(world).len(), 0);
141
}
142
}
143
144