Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_state/src/state_scoped_events.rs
6595 views
1
use alloc::vec::Vec;
2
use core::marker::PhantomData;
3
4
use bevy_app::{App, SubApp};
5
use bevy_ecs::{
6
event::{BufferedEvent, EventReader, Events},
7
resource::Resource,
8
system::Commands,
9
world::World,
10
};
11
use bevy_platform::collections::HashMap;
12
13
use crate::state::{OnEnter, OnExit, StateTransitionEvent, States};
14
15
fn clear_event_queue<E: BufferedEvent>(w: &mut World) {
16
if let Some(mut queue) = w.get_resource_mut::<Events<E>>() {
17
queue.clear();
18
}
19
}
20
21
#[derive(Copy, Clone)]
22
enum TransitionType {
23
OnExit,
24
OnEnter,
25
}
26
27
#[derive(Resource)]
28
struct StateScopedEvents<S: States> {
29
/// Keeps track of which events need to be reset when the state is exited.
30
on_exit: HashMap<S, Vec<fn(&mut World)>>,
31
/// Keeps track of which events need to be reset when the state is entered.
32
on_enter: HashMap<S, Vec<fn(&mut World)>>,
33
}
34
35
impl<S: States> StateScopedEvents<S> {
36
fn add_event<E: BufferedEvent>(&mut self, state: S, transition_type: TransitionType) {
37
let map = match transition_type {
38
TransitionType::OnExit => &mut self.on_exit,
39
TransitionType::OnEnter => &mut self.on_enter,
40
};
41
map.entry(state).or_default().push(clear_event_queue::<E>);
42
}
43
44
fn cleanup(&self, w: &mut World, state: S, transition_type: TransitionType) {
45
let map = match transition_type {
46
TransitionType::OnExit => &self.on_exit,
47
TransitionType::OnEnter => &self.on_enter,
48
};
49
let Some(fns) = map.get(&state) else {
50
return;
51
};
52
for callback in fns {
53
(*callback)(w);
54
}
55
}
56
}
57
58
impl<S: States> Default for StateScopedEvents<S> {
59
fn default() -> Self {
60
Self {
61
on_exit: HashMap::default(),
62
on_enter: HashMap::default(),
63
}
64
}
65
}
66
67
fn clear_events_on_exit<S: States>(
68
mut c: Commands,
69
mut transitions: EventReader<StateTransitionEvent<S>>,
70
) {
71
let Some(transition) = transitions.read().last() else {
72
return;
73
};
74
if transition.entered == transition.exited {
75
return;
76
}
77
let Some(exited) = transition.exited.clone() else {
78
return;
79
};
80
81
c.queue(move |w: &mut World| {
82
w.resource_scope::<StateScopedEvents<S>, ()>(|w, events| {
83
events.cleanup(w, exited, TransitionType::OnExit);
84
});
85
});
86
}
87
88
fn clear_events_on_enter<S: States>(
89
mut c: Commands,
90
mut transitions: EventReader<StateTransitionEvent<S>>,
91
) {
92
let Some(transition) = transitions.read().last() else {
93
return;
94
};
95
if transition.entered == transition.exited {
96
return;
97
}
98
let Some(entered) = transition.entered.clone() else {
99
return;
100
};
101
102
c.queue(move |w: &mut World| {
103
w.resource_scope::<StateScopedEvents<S>, ()>(|w, events| {
104
events.cleanup(w, entered, TransitionType::OnEnter);
105
});
106
});
107
}
108
109
fn clear_events_on_state_transition<E: BufferedEvent, S: States>(
110
app: &mut SubApp,
111
_p: PhantomData<E>,
112
state: S,
113
transition_type: TransitionType,
114
) {
115
if !app.world().contains_resource::<StateScopedEvents<S>>() {
116
app.init_resource::<StateScopedEvents<S>>();
117
}
118
app.world_mut()
119
.resource_mut::<StateScopedEvents<S>>()
120
.add_event::<E>(state.clone(), transition_type);
121
match transition_type {
122
TransitionType::OnExit => app.add_systems(OnExit(state), clear_events_on_exit::<S>),
123
TransitionType::OnEnter => app.add_systems(OnEnter(state), clear_events_on_enter::<S>),
124
};
125
}
126
127
/// Extension trait for [`App`] adding methods for registering state scoped events.
128
pub trait StateScopedEventsAppExt {
129
/// Clears an [`BufferedEvent`] when exiting the specified `state`.
130
///
131
/// Note that event cleanup is ambiguously ordered relative to
132
/// [`DespawnOnExit`](crate::prelude::DespawnOnExit) entity cleanup,
133
/// and the [`OnExit`] schedule for the target state.
134
/// All of these (state scoped entities and events cleanup, and `OnExit`)
135
/// occur within schedule [`StateTransition`](crate::prelude::StateTransition)
136
/// and system set `StateTransitionSystems::ExitSchedules`.
137
fn clear_events_on_exit<E: BufferedEvent>(&mut self, state: impl States) -> &mut Self;
138
139
/// Clears an [`BufferedEvent`] when entering the specified `state`.
140
///
141
/// Note that event cleanup is ambiguously ordered relative to
142
/// [`DespawnOnEnter`](crate::prelude::DespawnOnEnter) entity cleanup,
143
/// and the [`OnEnter`] schedule for the target state.
144
/// All of these (state scoped entities and events cleanup, and `OnEnter`)
145
/// occur within schedule [`StateTransition`](crate::prelude::StateTransition)
146
/// and system set `StateTransitionSystems::EnterSchedules`.
147
fn clear_events_on_enter<E: BufferedEvent>(&mut self, state: impl States) -> &mut Self;
148
}
149
150
impl StateScopedEventsAppExt for App {
151
fn clear_events_on_exit<E: BufferedEvent>(&mut self, state: impl States) -> &mut Self {
152
clear_events_on_state_transition(
153
self.main_mut(),
154
PhantomData::<E>,
155
state,
156
TransitionType::OnExit,
157
);
158
self
159
}
160
161
fn clear_events_on_enter<E: BufferedEvent>(&mut self, state: impl States) -> &mut Self {
162
clear_events_on_state_transition(
163
self.main_mut(),
164
PhantomData::<E>,
165
state,
166
TransitionType::OnEnter,
167
);
168
self
169
}
170
}
171
172
impl StateScopedEventsAppExt for SubApp {
173
fn clear_events_on_exit<E: BufferedEvent>(&mut self, state: impl States) -> &mut Self {
174
clear_events_on_state_transition(self, PhantomData::<E>, state, TransitionType::OnExit);
175
self
176
}
177
178
fn clear_events_on_enter<E: BufferedEvent>(&mut self, state: impl States) -> &mut Self {
179
clear_events_on_state_transition(self, PhantomData::<E>, state, TransitionType::OnEnter);
180
self
181
}
182
}
183
184
#[cfg(test)]
185
mod tests {
186
use super::*;
187
use crate::app::StatesPlugin;
188
use bevy_ecs::event::BufferedEvent;
189
use bevy_state::prelude::*;
190
191
#[derive(States, Default, Clone, Hash, Eq, PartialEq, Debug)]
192
enum TestState {
193
#[default]
194
A,
195
B,
196
}
197
198
#[derive(BufferedEvent, Debug)]
199
struct StandardEvent;
200
201
#[derive(BufferedEvent, Debug)]
202
struct StateScopedEvent;
203
204
#[test]
205
fn clear_event_on_exit_state() {
206
let mut app = App::new();
207
app.add_plugins(StatesPlugin);
208
app.init_state::<TestState>();
209
210
app.add_event::<StandardEvent>();
211
app.add_event::<StateScopedEvent>()
212
.clear_events_on_exit::<StateScopedEvent>(TestState::A);
213
214
app.world_mut().write_event(StandardEvent).unwrap();
215
app.world_mut().write_event(StateScopedEvent).unwrap();
216
assert!(!app.world().resource::<Events<StandardEvent>>().is_empty());
217
assert!(!app
218
.world()
219
.resource::<Events<StateScopedEvent>>()
220
.is_empty());
221
222
app.world_mut()
223
.resource_mut::<NextState<TestState>>()
224
.set(TestState::B);
225
app.update();
226
227
assert!(!app.world().resource::<Events<StandardEvent>>().is_empty());
228
assert!(app
229
.world()
230
.resource::<Events<StateScopedEvent>>()
231
.is_empty());
232
}
233
234
#[test]
235
fn clear_event_on_enter_state() {
236
let mut app = App::new();
237
app.add_plugins(StatesPlugin);
238
app.init_state::<TestState>();
239
240
app.add_event::<StandardEvent>();
241
app.add_event::<StateScopedEvent>()
242
.clear_events_on_enter::<StateScopedEvent>(TestState::B);
243
244
app.world_mut().write_event(StandardEvent).unwrap();
245
app.world_mut().write_event(StateScopedEvent).unwrap();
246
assert!(!app.world().resource::<Events<StandardEvent>>().is_empty());
247
assert!(!app
248
.world()
249
.resource::<Events<StateScopedEvent>>()
250
.is_empty());
251
252
app.world_mut()
253
.resource_mut::<NextState<TestState>>()
254
.set(TestState::B);
255
app.update();
256
257
assert!(!app.world().resource::<Events<StandardEvent>>().is_empty());
258
assert!(app
259
.world()
260
.resource::<Events<StateScopedEvent>>()
261
.is_empty());
262
}
263
}
264
265