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