Skip to content

Commit 1a1b22e

Browse files
authored
Restore overwrite capabilities of insert_state (#13848)
# Objective - Fixes #13844 - Warn user when initializing state multiple times ## Solution - `insert_state` will overwrite previously initialized state value, reset transition events and re-insert it's own transition event. - `init_state`, `add_sub_state`, `add_computed_state` are idempotent, so calling them multiple times will emit a warning. ## Testing - 2 tests confirming overwrite works. - Given the example from #13844 ```rs use bevy::prelude::*; fn main() { App::new() .add_plugins(DefaultPlugins) .insert_state(AppState::A) .insert_state(AppState::B) .add_systems(OnEnter(AppState::A), setup_a) .add_systems(OnEnter(AppState::B), setup_b) .add_systems(OnExit(AppState::A), cleanup_a) .add_systems(OnExit(AppState::B), cleanup_b) .run(); } #[derive(States, Debug, Clone, PartialEq, Eq, Hash)] enum AppState { A, B, } fn setup_a() { info!("setting up A"); } fn setup_b() { info!("setting up B"); } fn cleanup_a() { info!("cleaning up A"); } fn cleanup_b() { info!("cleaning up B"); } ``` We get the following result: ``` INFO states: setting up B ``` which matches our expectations.
1 parent 1395e36 commit 1a1b22e

File tree

1 file changed

+79
-0
lines changed

1 file changed

+79
-0
lines changed

crates/bevy_state/src/app.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use bevy_ecs::{
44
schedule::{IntoSystemConfigs, ScheduleLabel},
55
world::FromWorld,
66
};
7+
use bevy_utils::tracing::warn;
78

89
use crate::state::{
910
setup_state_transitions_in_world, ComputedStates, FreelyMutableState, NextState, State,
@@ -70,6 +71,9 @@ impl AppExtStates for SubApp {
7071
exited: None,
7172
entered: Some(state),
7273
});
74+
} else {
75+
let name = std::any::type_name::<S>();
76+
warn!("State {} is already initialized.", name);
7377
}
7478

7579
self
@@ -87,6 +91,16 @@ impl AppExtStates for SubApp {
8791
exited: None,
8892
entered: Some(state),
8993
});
94+
} else {
95+
// Overwrite previous state and initial event
96+
self.insert_resource::<State<S>>(State::new(state.clone()));
97+
self.world_mut()
98+
.resource_mut::<Events<StateTransitionEvent<S>>>()
99+
.clear();
100+
self.world_mut().send_event(StateTransitionEvent {
101+
exited: None,
102+
entered: Some(state),
103+
});
90104
}
91105

92106
self
@@ -109,6 +123,9 @@ impl AppExtStates for SubApp {
109123
exited: None,
110124
entered: state,
111125
});
126+
} else {
127+
let name = std::any::type_name::<S>();
128+
warn!("Computed state {} is already initialized.", name);
112129
}
113130

114131
self
@@ -132,6 +149,9 @@ impl AppExtStates for SubApp {
132149
exited: None,
133150
entered: state,
134151
});
152+
} else {
153+
let name = std::any::type_name::<S>();
154+
warn!("Sub state {} is already initialized.", name);
135155
}
136156

137157
self
@@ -192,3 +212,62 @@ impl Plugin for StatesPlugin {
192212
schedule.insert_after(PreUpdate, StateTransition);
193213
}
194214
}
215+
216+
#[cfg(test)]
217+
mod tests {
218+
use crate::{
219+
self as bevy_state,
220+
state::{State, StateTransition, StateTransitionEvent},
221+
};
222+
use bevy_app::App;
223+
use bevy_ecs::event::Events;
224+
use bevy_state_macros::States;
225+
226+
use super::AppExtStates;
227+
228+
#[derive(States, Default, PartialEq, Eq, Hash, Debug, Clone)]
229+
enum TestState {
230+
#[default]
231+
A,
232+
B,
233+
C,
234+
}
235+
236+
#[test]
237+
fn insert_state_can_overwrite_init_state() {
238+
let mut app = App::new();
239+
240+
app.init_state::<TestState>();
241+
app.insert_state(TestState::B);
242+
243+
let world = app.world_mut();
244+
world.run_schedule(StateTransition);
245+
246+
assert_eq!(world.resource::<State<TestState>>().0, TestState::B);
247+
let events = world.resource::<Events<StateTransitionEvent<TestState>>>();
248+
assert_eq!(events.len(), 1);
249+
let mut reader = events.get_reader();
250+
let last = reader.read(events).last().unwrap();
251+
assert_eq!(last.exited, None);
252+
assert_eq!(last.entered, Some(TestState::B));
253+
}
254+
255+
#[test]
256+
fn insert_state_can_overwrite_insert_state() {
257+
let mut app = App::new();
258+
259+
app.insert_state(TestState::B);
260+
app.insert_state(TestState::C);
261+
262+
let world = app.world_mut();
263+
world.run_schedule(StateTransition);
264+
265+
assert_eq!(world.resource::<State<TestState>>().0, TestState::C);
266+
let events = world.resource::<Events<StateTransitionEvent<TestState>>>();
267+
assert_eq!(events.len(), 1);
268+
let mut reader = events.get_reader();
269+
let last = reader.read(events).last().unwrap();
270+
assert_eq!(last.exited, None);
271+
assert_eq!(last.entered, Some(TestState::C));
272+
}
273+
}

0 commit comments

Comments
 (0)