Skip to main content

bevy_state/
app.rs

1use bevy_app::{App, MainScheduleOrder, Plugin, PreStartup, PreUpdate, SubApp};
2use bevy_ecs::{message::Messages, schedule::IntoScheduleConfigs, world::FromWorld};
3use bevy_utils::once;
4use log::warn;
5
6use crate::{
7    state::{
8        setup_state_transitions_in_world, ComputedStates, FreelyMutableState, NextState, State,
9        StateTransition, StateTransitionEvent, StateTransitionSystems, States, SubStates,
10    },
11    state_scoped::{despawn_entities_on_enter_state, despawn_entities_on_exit_state},
12};
13
14#[cfg(feature = "bevy_reflect")]
15use bevy_reflect::{FromReflect, GetTypeRegistration, Typed};
16
17/// State installation methods for [`App`] and [`SubApp`].
18pub trait AppExtStates {
19    /// Initializes a [`State`] with standard starting values.
20    ///
21    /// This method is idempotent: it has no effect when called again using the same generic type.
22    ///
23    /// Adds [`State<S>`] and [`NextState<S>`] resources, and enables use of the [`OnEnter`](crate::state::OnEnter),
24    /// [`OnTransition`](crate::state::OnTransition) and [`OnExit`](crate::state::OnExit) schedules.
25    /// These schedules are triggered before [`Update`](bevy_app::Update) and at startup.
26    ///
27    /// If you would like to control how other systems run based on the current state, you can
28    /// emulate this behavior using the [`in_state`](crate::condition::in_state) [`SystemCondition`](bevy_ecs::prelude::SystemCondition).
29    ///
30    /// Note that you can also apply state transitions at other points in the schedule
31    /// by triggering the [`StateTransition`](struct@StateTransition) schedule manually.
32    ///
33    /// The use of any states requires the presence of [`StatesPlugin`] (which is included in `DefaultPlugins`).
34    fn init_state<S: FreelyMutableState + FromWorld>(&mut self) -> &mut Self;
35
36    /// Inserts a specific [`State`] to the current [`App`] and overrides any [`State`] previously
37    /// added of the same type.
38    ///
39    /// Adds [`State<S>`] and [`NextState<S>`] resources, and enables use of the [`OnEnter`](crate::state::OnEnter),
40    /// [`OnTransition`](crate::state::OnTransition) and [`OnExit`](crate::state::OnExit) schedules.
41    /// These schedules are triggered before [`Update`](bevy_app::Update) and at startup.
42    ///
43    /// If you would like to control how other systems run based on the current state, you can
44    /// emulate this behavior using the [`in_state`](crate::condition::in_state) [`SystemCondition`](bevy_ecs::prelude::SystemCondition).
45    ///
46    /// Note that you can also apply state transitions at other points in the schedule
47    /// by triggering the [`StateTransition`](struct@StateTransition) schedule manually.
48    fn insert_state<S: FreelyMutableState>(&mut self, state: S) -> &mut Self;
49
50    /// Sets up a type implementing [`ComputedStates`].
51    ///
52    /// This method is idempotent: it has no effect when called again using the same generic type.
53    fn add_computed_state<S: ComputedStates>(&mut self) -> &mut Self;
54
55    /// Sets up a type implementing [`SubStates`].
56    ///
57    /// This method is idempotent: it has no effect when called again using the same generic type.
58    fn add_sub_state<S: SubStates>(&mut self) -> &mut Self;
59
60    #[cfg(feature = "bevy_reflect")]
61    /// Registers the state type `T` using [`App::register_type`],
62    /// and adds [`ReflectState`](crate::reflect::ReflectState) type data to `T` in the type registry.
63    ///
64    /// This enables reflection code to access the state. For detailed information, see the docs on [`crate::reflect::ReflectState`] .
65    fn register_type_state<S>(&mut self) -> &mut Self
66    where
67        S: States + FromReflect + GetTypeRegistration + Typed;
68
69    #[cfg(feature = "bevy_reflect")]
70    /// Registers the state type `T` using [`App::register_type`],
71    /// and adds [`crate::reflect::ReflectState`] and [`crate::reflect::ReflectFreelyMutableState`] type data to `T` in the type registry.
72    ///
73    /// This enables reflection code to access and modify the state.
74    /// For detailed information, see the docs on [`crate::reflect::ReflectState`] and [`crate::reflect::ReflectFreelyMutableState`].
75    fn register_type_mutable_state<S>(&mut self) -> &mut Self
76    where
77        S: FreelyMutableState + FromReflect + GetTypeRegistration + Typed;
78}
79
80/// Separate function to only warn once for all state installation methods.
81fn warn_if_no_states_plugin_installed(app: &SubApp) {
82    if !app.is_plugin_added::<StatesPlugin>() {
83        once!(warn!(
84            "States were added to the app, but `StatesPlugin` is not installed."
85        ));
86    }
87}
88
89impl AppExtStates for SubApp {
90    fn init_state<S: FreelyMutableState + FromWorld>(&mut self) -> &mut Self {
91        warn_if_no_states_plugin_installed(self);
92        if !self.world().contains_resource::<State<S>>() {
93            self.init_resource::<State<S>>()
94                .init_resource::<NextState<S>>()
95                .add_message::<StateTransitionEvent<S>>();
96            let schedule = self.get_schedule_mut(StateTransition).expect(
97                "The `StateTransition` schedule is missing. Did you forget to add StatesPlugin or DefaultPlugins before calling init_state?"
98            );
99            S::register_state(schedule);
100            let state = self.world().resource::<State<S>>().get().clone();
101            self.world_mut().write_message(StateTransitionEvent {
102                exited: None,
103                entered: Some(state),
104                // makes no difference: the state didn't exist before anyways
105                allow_same_state_transitions: true,
106            });
107            enable_state_scoped_entities::<S>(self);
108        } else {
109            let name = core::any::type_name::<S>();
110            warn!("State {name} is already initialized.");
111        }
112
113        self
114    }
115
116    fn insert_state<S: FreelyMutableState>(&mut self, state: S) -> &mut Self {
117        warn_if_no_states_plugin_installed(self);
118        if !self.world().contains_resource::<State<S>>() {
119            self.insert_resource::<State<S>>(State::new(state.clone()))
120                .init_resource::<NextState<S>>()
121                .add_message::<StateTransitionEvent<S>>();
122            let schedule = self.get_schedule_mut(StateTransition).expect(
123                "The `StateTransition` schedule is missing. Did you forget to add StatesPlugin or DefaultPlugins before calling insert_state?"
124            );
125            S::register_state(schedule);
126            self.world_mut().write_message(StateTransitionEvent {
127                exited: None,
128                entered: Some(state),
129                // makes no difference: the state didn't exist before anyways
130                allow_same_state_transitions: true,
131            });
132            enable_state_scoped_entities::<S>(self);
133        } else {
134            // Overwrite previous state and initial event
135            self.insert_resource::<State<S>>(State::new(state.clone()));
136            self.world_mut()
137                .resource_mut::<Messages<StateTransitionEvent<S>>>()
138                .clear();
139            self.world_mut().write_message(StateTransitionEvent {
140                exited: None,
141                entered: Some(state),
142                // Not configurable for the moment. This controls whether inserting a state with the same value as a pre-existing state should run state transitions.
143                // Leaving it at `true` makes state insertion idempotent. Neat!
144                allow_same_state_transitions: true,
145            });
146        }
147
148        self
149    }
150
151    fn add_computed_state<S: ComputedStates>(&mut self) -> &mut Self {
152        warn_if_no_states_plugin_installed(self);
153        if !self
154            .world()
155            .contains_resource::<Messages<StateTransitionEvent<S>>>()
156        {
157            self.add_message::<StateTransitionEvent<S>>();
158            let schedule = self.get_schedule_mut(StateTransition).expect(
159                "The `StateTransition` schedule is missing. Did you forget to add StatesPlugin or DefaultPlugins before calling add_computed_state?"
160            );
161            S::register_computed_state_systems(schedule);
162            let state = self
163                .world()
164                .get_resource::<State<S>>()
165                .map(|s| s.get().clone());
166            self.world_mut().write_message(StateTransitionEvent {
167                exited: None,
168                entered: state,
169                allow_same_state_transitions: S::ALLOW_SAME_STATE_TRANSITIONS,
170            });
171            enable_state_scoped_entities::<S>(self);
172        } else {
173            let name = core::any::type_name::<S>();
174            warn!("Computed state {name} is already initialized.");
175        }
176
177        self
178    }
179
180    fn add_sub_state<S: SubStates>(&mut self) -> &mut Self {
181        warn_if_no_states_plugin_installed(self);
182        if !self
183            .world()
184            .contains_resource::<Messages<StateTransitionEvent<S>>>()
185        {
186            self.init_resource::<NextState<S>>();
187            self.add_message::<StateTransitionEvent<S>>();
188            let schedule = self.get_schedule_mut(StateTransition).expect(
189                "The `StateTransition` schedule is missing. Did you forget to add StatesPlugin or DefaultPlugins before calling add_sub_state?"
190            );
191            S::register_sub_state_systems(schedule);
192            let state = self
193                .world()
194                .get_resource::<State<S>>()
195                .map(|s| s.get().clone());
196            self.world_mut().write_message(StateTransitionEvent {
197                exited: None,
198                entered: state,
199                // makes no difference: the state didn't exist before anyways
200                allow_same_state_transitions: true,
201            });
202            enable_state_scoped_entities::<S>(self);
203        } else {
204            let name = core::any::type_name::<S>();
205            warn!("Sub state {name} is already initialized.");
206        }
207
208        self
209    }
210
211    #[cfg(feature = "bevy_reflect")]
212    fn register_type_state<S>(&mut self) -> &mut Self
213    where
214        S: States + FromReflect + GetTypeRegistration + Typed,
215    {
216        self.register_type::<S>();
217        self.register_type::<State<S>>();
218        self.register_type_data::<S, crate::reflect::ReflectState>();
219        self
220    }
221
222    #[cfg(feature = "bevy_reflect")]
223    fn register_type_mutable_state<S>(&mut self) -> &mut Self
224    where
225        S: FreelyMutableState + FromReflect + GetTypeRegistration + Typed,
226    {
227        self.register_type::<S>();
228        self.register_type::<State<S>>();
229        self.register_type::<NextState<S>>();
230        self.register_type_data::<S, crate::reflect::ReflectState>();
231        self.register_type_data::<S, crate::reflect::ReflectFreelyMutableState>();
232        self
233    }
234}
235
236fn enable_state_scoped_entities<S: States>(app: &mut SubApp) {
237    if !app
238        .world()
239        .contains_resource::<Messages<StateTransitionEvent<S>>>()
240    {
241        let name = core::any::type_name::<S>();
242        warn!("State scoped entities are enabled for state `{name}`, but the state wasn't initialized in the app!");
243    }
244
245    // Note: We work with `StateTransition` in set
246    // `StateTransitionSystems::ExitSchedules` rather than `OnExit`, because
247    // `OnExit` only runs for one specific variant of the state.
248    app.add_systems(
249        StateTransition,
250        despawn_entities_on_exit_state::<S>.in_set(StateTransitionSystems::ExitSchedules),
251    )
252    // Note: We work with `StateTransition` in set
253    // `StateTransitionSystems::EnterSchedules` rather than `OnEnter`, because
254    // `OnEnter` only runs for one specific variant of the state.
255    .add_systems(
256        StateTransition,
257        despawn_entities_on_enter_state::<S>.in_set(StateTransitionSystems::EnterSchedules),
258    );
259}
260
261impl AppExtStates for App {
262    fn init_state<S: FreelyMutableState + FromWorld>(&mut self) -> &mut Self {
263        self.main_mut().init_state::<S>();
264        self
265    }
266
267    fn insert_state<S: FreelyMutableState>(&mut self, state: S) -> &mut Self {
268        self.main_mut().insert_state::<S>(state);
269        self
270    }
271
272    fn add_computed_state<S: ComputedStates>(&mut self) -> &mut Self {
273        self.main_mut().add_computed_state::<S>();
274        self
275    }
276
277    fn add_sub_state<S: SubStates>(&mut self) -> &mut Self {
278        self.main_mut().add_sub_state::<S>();
279        self
280    }
281
282    #[cfg(feature = "bevy_reflect")]
283    fn register_type_state<S>(&mut self) -> &mut Self
284    where
285        S: States + FromReflect + GetTypeRegistration + Typed,
286    {
287        self.main_mut().register_type_state::<S>();
288        self
289    }
290
291    #[cfg(feature = "bevy_reflect")]
292    fn register_type_mutable_state<S>(&mut self) -> &mut Self
293    where
294        S: FreelyMutableState + FromReflect + GetTypeRegistration + Typed,
295    {
296        self.main_mut().register_type_mutable_state::<S>();
297        self
298    }
299}
300
301/// Registers the [`StateTransition`] schedule in the [`MainScheduleOrder`] to enable state processing.
302#[derive(Default)]
303pub struct StatesPlugin;
304
305impl Plugin for StatesPlugin {
306    fn build(&self, app: &mut App) {
307        let mut schedule = app.world_mut().resource_mut::<MainScheduleOrder>();
308        schedule.insert_after(PreUpdate, StateTransition);
309        schedule.insert_startup_before(PreStartup, StateTransition);
310        setup_state_transitions_in_world(app.world_mut());
311    }
312}
313
314#[cfg(test)]
315mod tests {
316    use crate::{
317        app::StatesPlugin,
318        state::{State, StateTransition, StateTransitionEvent},
319    };
320    use bevy_app::App;
321    use bevy_ecs::message::Messages;
322    use bevy_state_macros::States;
323
324    use super::AppExtStates;
325
326    #[derive(States, Default, PartialEq, Eq, Hash, Debug, Clone)]
327    enum TestState {
328        #[default]
329        A,
330        B,
331        C,
332    }
333
334    #[test]
335    fn insert_state_can_overwrite_init_state() {
336        let mut app = App::new();
337        app.add_plugins(StatesPlugin);
338
339        app.init_state::<TestState>();
340        app.insert_state(TestState::B);
341
342        let world = app.world_mut();
343        world.run_schedule(StateTransition);
344
345        assert_eq!(world.resource::<State<TestState>>().0, TestState::B);
346        let events = world.resource::<Messages<StateTransitionEvent<TestState>>>();
347        assert_eq!(events.len(), 1);
348        let mut reader = events.get_cursor();
349        let last = reader.read(events).last().unwrap();
350        assert_eq!(last.exited, None);
351        assert_eq!(last.entered, Some(TestState::B));
352    }
353
354    #[test]
355    fn insert_state_can_overwrite_insert_state() {
356        let mut app = App::new();
357        app.add_plugins(StatesPlugin);
358
359        app.insert_state(TestState::B);
360        app.insert_state(TestState::C);
361
362        let world = app.world_mut();
363        world.run_schedule(StateTransition);
364
365        assert_eq!(world.resource::<State<TestState>>().0, TestState::C);
366        let events = world.resource::<Messages<StateTransitionEvent<TestState>>>();
367        assert_eq!(events.len(), 1);
368        let mut reader = events.get_cursor();
369        let last = reader.read(events).last().unwrap();
370        assert_eq!(last.exited, None);
371        assert_eq!(last.entered, Some(TestState::C));
372    }
373}