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
17pub trait AppExtStates {
19 fn init_state<S: FreelyMutableState + FromWorld>(&mut self) -> &mut Self;
35
36 fn insert_state<S: FreelyMutableState>(&mut self, state: S) -> &mut Self;
49
50 fn add_computed_state<S: ComputedStates>(&mut self) -> &mut Self;
54
55 fn add_sub_state<S: SubStates>(&mut self) -> &mut Self;
59
60 #[cfg(feature = "bevy_reflect")]
61 fn register_type_state<S>(&mut self) -> &mut Self
66 where
67 S: States + FromReflect + GetTypeRegistration + Typed;
68
69 #[cfg(feature = "bevy_reflect")]
70 fn register_type_mutable_state<S>(&mut self) -> &mut Self
76 where
77 S: FreelyMutableState + FromReflect + GetTypeRegistration + Typed;
78}
79
80fn 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 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 allow_same_state_transitions: true,
131 });
132 enable_state_scoped_entities::<S>(self);
133 } else {
134 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 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 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 app.add_systems(
249 StateTransition,
250 despawn_entities_on_exit_state::<S>.in_set(StateTransitionSystems::ExitSchedules),
251 )
252 .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#[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}