Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_state/src/state/transitions.rs
9374 views
1
use core::{marker::PhantomData, mem};
2
3
use bevy_ecs::{
4
message::{Message, MessageReader, MessageWriter},
5
schedule::{IntoScheduleConfigs, Schedule, ScheduleLabel, Schedules, SystemSet},
6
system::{Commands, In, ResMut},
7
world::World,
8
};
9
10
use super::{
11
resources::{PreviousState, State},
12
states::States,
13
};
14
15
/// The label of a [`Schedule`] that **only** runs whenever [`State<S>`] enters the provided state.
16
///
17
/// This schedule ignores identity transitions.
18
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
19
pub struct OnEnter<S: States>(pub S);
20
21
/// The label of a [`Schedule`] that **only** runs whenever [`State<S>`] exits the provided state.
22
///
23
/// This schedule ignores identity transitions.
24
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
25
pub struct OnExit<S: States>(pub S);
26
27
/// The label of a [`Schedule`] that **only** runs whenever [`State<S>`]
28
/// exits AND enters the provided `exited` and `entered` states.
29
///
30
/// Systems added to this schedule are always ran *after* [`OnExit`], and *before* [`OnEnter`].
31
///
32
/// This schedule will run on identity transitions.
33
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
34
pub struct OnTransition<S: States> {
35
/// The state being exited.
36
pub exited: S,
37
/// The state being entered.
38
pub entered: S,
39
}
40
41
/// Runs [state transitions](States).
42
///
43
/// By default, it will be triggered once before [`PreStartup`] and then each frame after [`PreUpdate`], but
44
/// you can manually trigger it at arbitrary times by creating an exclusive
45
/// system to run the schedule.
46
///
47
/// ```rust
48
/// use bevy_state::prelude::*;
49
/// use bevy_ecs::prelude::*;
50
///
51
/// fn run_state_transitions(world: &mut World) {
52
/// let _ = world.try_run_schedule(StateTransition);
53
/// }
54
/// ```
55
///
56
/// This schedule is split up into four phases, as described in [`StateTransitionSystems`].
57
///
58
/// [`PreStartup`]: https://docs.rs/bevy/latest/bevy/prelude/struct.PreStartup.html
59
/// [`PreUpdate`]: https://docs.rs/bevy/latest/bevy/prelude/struct.PreUpdate.html
60
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
61
pub struct StateTransition;
62
63
/// A [`Message`] sent when any state transition of `S` happens.
64
/// This includes identity transitions, where `exited` and `entered` have the same value.
65
///
66
/// If you know exactly what state you want to respond to ahead of time, consider [`OnEnter`], [`OnTransition`], or [`OnExit`]
67
#[derive(Debug, Copy, Clone, PartialEq, Eq, Message)]
68
pub struct StateTransitionEvent<S: States> {
69
/// The state being exited.
70
pub exited: Option<S>,
71
/// The state being entered.
72
pub entered: Option<S>,
73
/// Allow running state transition events when `exited` and `entered` are the same
74
pub allow_same_state_transitions: bool,
75
}
76
77
/// Applies state transitions and runs transitions schedules in order.
78
///
79
/// These system sets are run sequentially, in the order of the enum variants.
80
#[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)]
81
pub enum StateTransitionSystems {
82
/// States apply their transitions from [`NextState`](super::NextState)
83
/// and compute functions based on their parent states.
84
DependentTransitions,
85
/// Exit schedules are executed in leaf to root order
86
ExitSchedules,
87
/// Transition schedules are executed in arbitrary order.
88
TransitionSchedules,
89
/// Enter schedules are executed in root to leaf order.
90
EnterSchedules,
91
}
92
93
#[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)]
94
/// System set that runs exit schedule(s) for state `S`.
95
pub struct ExitSchedules<S: States>(PhantomData<S>);
96
97
impl<S: States> Default for ExitSchedules<S> {
98
fn default() -> Self {
99
Self(Default::default())
100
}
101
}
102
103
#[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)]
104
/// System set that runs transition schedule(s) for state `S`.
105
pub struct TransitionSchedules<S: States>(PhantomData<S>);
106
107
impl<S: States> Default for TransitionSchedules<S> {
108
fn default() -> Self {
109
Self(Default::default())
110
}
111
}
112
113
#[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)]
114
/// System set that runs enter schedule(s) for state `S`.
115
pub struct EnterSchedules<S: States>(PhantomData<S>);
116
117
impl<S: States> Default for EnterSchedules<S> {
118
fn default() -> Self {
119
Self(Default::default())
120
}
121
}
122
123
/// System set that applies transitions for state `S`.
124
#[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)]
125
pub(crate) struct ApplyStateTransition<S: States>(PhantomData<S>);
126
127
impl<S: States> Default for ApplyStateTransition<S> {
128
fn default() -> Self {
129
Self(Default::default())
130
}
131
}
132
133
/// This function actually applies a state change, and registers the required
134
/// schedules for downstream computed states and transition schedules.
135
///
136
/// The `new_state` is an option to allow for removal - `None` will trigger the
137
/// removal of the `State<S>` resource from the [`World`].
138
pub(crate) fn internal_apply_state_transition<S: States>(
139
mut event: MessageWriter<StateTransitionEvent<S>>,
140
mut commands: Commands,
141
current_state: Option<ResMut<State<S>>>,
142
mut previous_state: Option<ResMut<PreviousState<S>>>,
143
new_state: Option<S>,
144
allow_same_state_transitions: bool,
145
) {
146
match new_state {
147
Some(entered) => {
148
match current_state {
149
// If the [`State<S>`] resource exists, and the state is not the one we are
150
// entering - we need to set the new value, compute dependent states, send transition events
151
// and register transition schedules.
152
Some(mut state_resource) => {
153
let exited = match *state_resource == entered {
154
true => entered.clone(),
155
false => mem::replace(&mut state_resource.0, entered.clone()),
156
};
157
158
// Transition events are sent even for same state transitions
159
// Although enter and exit schedules are not run by default.
160
event.write(StateTransitionEvent {
161
exited: Some(exited.clone()),
162
entered: Some(entered.clone()),
163
allow_same_state_transitions,
164
});
165
166
if let Some(ref mut previous_state) = previous_state {
167
previous_state.0 = exited;
168
} else {
169
commands.insert_resource(PreviousState(exited));
170
}
171
}
172
None => {
173
// If the [`State<S>`] resource does not exist, we create it, compute dependent states, send a transition event and register the `OnEnter` schedule.
174
commands.insert_resource(State(entered.clone()));
175
176
event.write(StateTransitionEvent {
177
exited: None,
178
entered: Some(entered.clone()),
179
allow_same_state_transitions,
180
});
181
182
// When [`State<S>`] is initialized, there can be stale data in
183
// [`PreviousState<S>`] from a prior transition to `None`, so we remove it.
184
if previous_state.is_some() {
185
commands.remove_resource::<PreviousState<S>>();
186
}
187
}
188
};
189
}
190
None => {
191
// We first remove the [`State<S>`] resource, and if one existed we compute dependent states, send a transition event and run the `OnExit` schedule.
192
if let Some(resource) = current_state {
193
let exited = resource.get().clone();
194
commands.remove_resource::<State<S>>();
195
196
event.write(StateTransitionEvent {
197
exited: Some(exited.clone()),
198
entered: None,
199
allow_same_state_transitions,
200
});
201
202
if let Some(ref mut previous_state) = previous_state {
203
previous_state.0 = exited;
204
} else {
205
commands.insert_resource(PreviousState(exited));
206
}
207
}
208
}
209
}
210
}
211
212
/// Sets up the schedules and systems for handling state transitions
213
/// within a [`World`].
214
///
215
/// Runs automatically when using `App` to insert states, but needs to
216
/// be added manually in other situations.
217
pub fn setup_state_transitions_in_world(world: &mut World) {
218
let mut schedules = world.get_resource_or_init::<Schedules>();
219
if schedules.contains(StateTransition) {
220
return;
221
}
222
let mut schedule = Schedule::new(StateTransition);
223
schedule.configure_sets(
224
(
225
StateTransitionSystems::DependentTransitions,
226
StateTransitionSystems::ExitSchedules,
227
StateTransitionSystems::TransitionSchedules,
228
StateTransitionSystems::EnterSchedules,
229
)
230
.chain(),
231
);
232
233
schedules.insert(schedule);
234
}
235
236
/// Returns the latest state transition event of type `S`, if any are available.
237
pub fn last_transition<S: States>(
238
mut reader: MessageReader<StateTransitionEvent<S>>,
239
) -> Option<StateTransitionEvent<S>> {
240
reader.read().last().cloned()
241
}
242
243
pub(crate) fn run_enter<S: States>(
244
transition: In<Option<StateTransitionEvent<S>>>,
245
world: &mut World,
246
) {
247
let Some(transition) = transition.0 else {
248
return;
249
};
250
if transition.entered == transition.exited && !transition.allow_same_state_transitions {
251
return;
252
}
253
let Some(entered) = transition.entered else {
254
return;
255
};
256
257
let _ = world.try_run_schedule(OnEnter(entered));
258
}
259
260
pub(crate) fn run_exit<S: States>(
261
transition: In<Option<StateTransitionEvent<S>>>,
262
world: &mut World,
263
) {
264
let Some(transition) = transition.0 else {
265
return;
266
};
267
if transition.entered == transition.exited && !transition.allow_same_state_transitions {
268
return;
269
}
270
let Some(exited) = transition.exited else {
271
return;
272
};
273
274
let _ = world.try_run_schedule(OnExit(exited));
275
}
276
277
pub(crate) fn run_transition<S: States>(
278
transition: In<Option<StateTransitionEvent<S>>>,
279
world: &mut World,
280
) {
281
let Some(transition) = transition.0 else {
282
return;
283
};
284
let Some(exited) = transition.exited else {
285
return;
286
};
287
let Some(entered) = transition.entered else {
288
return;
289
};
290
291
let _ = world.try_run_schedule(OnTransition { exited, entered });
292
}
293
294