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