Skip to content

Commit 63eb151

Browse files
lee-orrhymm
andauthored
Optional state (#11417)
# Objective Adjust bevy internals to utilize `Option<Res<State<S>>>` instead of `Res<State<S>>`, to allow for adding/removing states at runtime and avoid unexpected panics. As requested here: #10088 (comment) --- ## Changelog - Changed the use of `world.resource`/`world.resource_mut` to `world.get_resource`/`world.get_resource_mut` in the `run_enter_schedule` and `apply_state_transition` systems and handled the `None` option. - `in_state` now returns a ` FnMut(Option<Res<State<S>>>) -> bool + Clone`, returning `false` if the resource doesn't exist. - `state_exists_and_equals` was marked as deprecated, and now just runs and returns `in_state`, since their bevhaviour is now identical - `state_changed` now takes an `Option<Res<State<S>>>` and returns `false` if it does not exist. I would like to remove `state_exists_and_equals` fully, but wanted to ensure that is acceptable before doing so. --------- Co-authored-by: Mike <[email protected]>
1 parent eff96e2 commit 63eb151

File tree

2 files changed

+59
-35
lines changed

2 files changed

+59
-35
lines changed

crates/bevy_ecs/src/schedule/condition.rs

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,8 @@ mod sealed {
194194

195195
/// A collection of [run conditions](Condition) that may be useful in any bevy app.
196196
pub mod common_conditions {
197+
use bevy_utils::warn_once;
198+
197199
use super::NotSystem;
198200
use crate::{
199201
change_detection::DetectChanges,
@@ -701,9 +703,7 @@ pub mod common_conditions {
701703
/// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true`
702704
/// if the state machine is currently in `state`.
703705
///
704-
/// # Panics
705-
///
706-
/// The condition will panic if the resource does not exist.
706+
/// Will return `false` if the state does not exist or if not in `state`.
707707
///
708708
/// # Example
709709
///
@@ -748,10 +748,26 @@ pub mod common_conditions {
748748
/// app.run(&mut world);
749749
/// assert_eq!(world.resource::<Counter>().0, 0);
750750
/// ```
751-
pub fn in_state<S: States>(state: S) -> impl FnMut(Res<State<S>>) -> bool + Clone {
752-
move |current_state: Res<State<S>>| *current_state == state
751+
pub fn in_state<S: States>(state: S) -> impl FnMut(Option<Res<State<S>>>) -> bool + Clone {
752+
move |current_state: Option<Res<State<S>>>| match current_state {
753+
Some(current_state) => *current_state == state,
754+
None => {
755+
warn_once!("No state matching the type for {} exists - did you forget to `add_state` when initializing the app?", {
756+
let debug_state = format!("{state:?}");
757+
let result = debug_state
758+
.split("::")
759+
.next()
760+
.unwrap_or("Unknown State Type");
761+
result.to_string()
762+
});
763+
764+
false
765+
}
766+
}
753767
}
754768

769+
/// Identical to [`in_state`] - use that instead.
770+
///
755771
/// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true`
756772
/// if the state machine exists and is currently in `state`.
757773
///
@@ -804,13 +820,11 @@ pub mod common_conditions {
804820
/// app.run(&mut world);
805821
/// assert_eq!(world.resource::<Counter>().0, 0);
806822
/// ```
823+
#[deprecated(since = "0.13.0", note = "use `in_state` instead.")]
807824
pub fn state_exists_and_equals<S: States>(
808825
state: S,
809826
) -> impl FnMut(Option<Res<State<S>>>) -> bool + Clone {
810-
move |current_state: Option<Res<State<S>>>| match current_state {
811-
Some(current_state) => *current_state == state,
812-
None => false,
813-
}
827+
in_state(state)
814828
}
815829

816830
/// A [`Condition`](super::Condition)-satisfying system that returns `true`
@@ -819,9 +833,7 @@ pub mod common_conditions {
819833
/// To do things on transitions to/from specific states, use their respective OnEnter/OnExit
820834
/// schedules. Use this run condition if you want to detect any change, regardless of the value.
821835
///
822-
/// # Panics
823-
///
824-
/// The condition will panic if the resource does not exist.
836+
/// Returns false if the state does not exist or the state has not changed.
825837
///
826838
/// # Example
827839
///
@@ -866,7 +878,10 @@ pub mod common_conditions {
866878
/// app.run(&mut world);
867879
/// assert_eq!(world.resource::<Counter>().0, 2);
868880
/// ```
869-
pub fn state_changed<S: States>(current_state: Res<State<S>>) -> bool {
881+
pub fn state_changed<S: States>(current_state: Option<Res<State<S>>>) -> bool {
882+
let Some(current_state) = current_state else {
883+
return false;
884+
};
870885
current_state.is_changed()
871886
}
872887

crates/bevy_ecs/src/schedule/state.rs

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -188,9 +188,10 @@ pub struct StateTransitionEvent<S: States> {
188188

189189
/// Run the enter schedule (if it exists) for the current state.
190190
pub fn run_enter_schedule<S: States>(world: &mut World) {
191-
world
192-
.try_run_schedule(OnEnter(world.resource::<State<S>>().0.clone()))
193-
.ok();
191+
let Some(state) = world.get_resource::<State<S>>() else {
192+
return;
193+
};
194+
world.try_run_schedule(OnEnter(state.0.clone())).ok();
194195
}
195196

196197
/// If a new state is queued in [`NextState<S>`], this system:
@@ -202,26 +203,34 @@ pub fn run_enter_schedule<S: States>(world: &mut World) {
202203
pub fn apply_state_transition<S: States>(world: &mut World) {
203204
// We want to take the `NextState` resource,
204205
// but only mark it as changed if it wasn't empty.
205-
let mut next_state_resource = world.resource_mut::<NextState<S>>();
206+
let Some(mut next_state_resource) = world.get_resource_mut::<NextState<S>>() else {
207+
return;
208+
};
206209
if let Some(entered) = next_state_resource.bypass_change_detection().0.take() {
207210
next_state_resource.set_changed();
208-
209-
let mut state_resource = world.resource_mut::<State<S>>();
210-
if *state_resource != entered {
211-
let exited = mem::replace(&mut state_resource.0, entered.clone());
212-
world.send_event(StateTransitionEvent {
213-
before: exited.clone(),
214-
after: entered.clone(),
215-
});
216-
// Try to run the schedules if they exist.
217-
world.try_run_schedule(OnExit(exited.clone())).ok();
218-
world
219-
.try_run_schedule(OnTransition {
220-
from: exited,
221-
to: entered.clone(),
222-
})
223-
.ok();
224-
world.try_run_schedule(OnEnter(entered)).ok();
225-
}
211+
match world.get_resource_mut::<State<S>>() {
212+
Some(mut state_resource) => {
213+
if *state_resource != entered {
214+
let exited = mem::replace(&mut state_resource.0, entered.clone());
215+
world.send_event(StateTransitionEvent {
216+
before: exited.clone(),
217+
after: entered.clone(),
218+
});
219+
// Try to run the schedules if they exist.
220+
world.try_run_schedule(OnExit(exited.clone())).ok();
221+
world
222+
.try_run_schedule(OnTransition {
223+
from: exited,
224+
to: entered.clone(),
225+
})
226+
.ok();
227+
world.try_run_schedule(OnEnter(entered)).ok();
228+
}
229+
}
230+
None => {
231+
world.insert_resource(State(entered.clone()));
232+
world.try_run_schedule(OnEnter(entered)).ok();
233+
}
234+
};
226235
}
227236
}

0 commit comments

Comments
 (0)