diff --git a/crates/bevy_ecs/src/schedule/condition.rs b/crates/bevy_ecs/src/schedule/condition.rs index 95173181e0..4c29a5994b 100644 --- a/crates/bevy_ecs/src/schedule/condition.rs +++ b/crates/bevy_ecs/src/schedule/condition.rs @@ -194,6 +194,8 @@ mod sealed { /// A collection of [run conditions](Condition) that may be useful in any bevy app. pub mod common_conditions { + use bevy_utils::warn_once; + use super::NotSystem; use crate::{ change_detection::DetectChanges, @@ -701,9 +703,7 @@ pub mod common_conditions { /// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true` /// if the state machine is currently in `state`. /// - /// # Panics - /// - /// The condition will panic if the resource does not exist. + /// Will return `false` if the state does not exist or if not in `state`. /// /// # Example /// @@ -748,10 +748,26 @@ pub mod common_conditions { /// app.run(&mut world); /// assert_eq!(world.resource::().0, 0); /// ``` - pub fn in_state(state: S) -> impl FnMut(Res>) -> bool + Clone { - move |current_state: Res>| *current_state == state + pub fn in_state(state: S) -> impl FnMut(Option>>) -> bool + Clone { + move |current_state: Option>>| match current_state { + Some(current_state) => *current_state == state, + None => { + warn_once!("No state matching the type for {} exists - did you forget to `add_state` when initializing the app?", { + let debug_state = format!("{state:?}"); + let result = debug_state + .split("::") + .next() + .unwrap_or("Unknown State Type"); + result.to_string() + }); + + false + } + } } + /// Identical to [`in_state`] - use that instead. + /// /// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true` /// if the state machine exists and is currently in `state`. /// @@ -804,13 +820,11 @@ pub mod common_conditions { /// app.run(&mut world); /// assert_eq!(world.resource::().0, 0); /// ``` + #[deprecated(since = "0.13.0", note = "use `in_state` instead.")] pub fn state_exists_and_equals( state: S, ) -> impl FnMut(Option>>) -> bool + Clone { - move |current_state: Option>>| match current_state { - Some(current_state) => *current_state == state, - None => false, - } + in_state(state) } /// A [`Condition`](super::Condition)-satisfying system that returns `true` @@ -819,9 +833,7 @@ pub mod common_conditions { /// To do things on transitions to/from specific states, use their respective OnEnter/OnExit /// schedules. Use this run condition if you want to detect any change, regardless of the value. /// - /// # Panics - /// - /// The condition will panic if the resource does not exist. + /// Returns false if the state does not exist or the state has not changed. /// /// # Example /// @@ -866,7 +878,10 @@ pub mod common_conditions { /// app.run(&mut world); /// assert_eq!(world.resource::().0, 2); /// ``` - pub fn state_changed(current_state: Res>) -> bool { + pub fn state_changed(current_state: Option>>) -> bool { + let Some(current_state) = current_state else { + return false; + }; current_state.is_changed() } diff --git a/crates/bevy_ecs/src/schedule/state.rs b/crates/bevy_ecs/src/schedule/state.rs index dacb5605ec..6308c7731e 100644 --- a/crates/bevy_ecs/src/schedule/state.rs +++ b/crates/bevy_ecs/src/schedule/state.rs @@ -188,9 +188,10 @@ pub struct StateTransitionEvent { /// Run the enter schedule (if it exists) for the current state. pub fn run_enter_schedule(world: &mut World) { - world - .try_run_schedule(OnEnter(world.resource::>().0.clone())) - .ok(); + let Some(state) = world.get_resource::>() else { + return; + }; + world.try_run_schedule(OnEnter(state.0.clone())).ok(); } /// If a new state is queued in [`NextState`], this system: @@ -202,26 +203,34 @@ pub fn run_enter_schedule(world: &mut World) { pub fn apply_state_transition(world: &mut World) { // We want to take the `NextState` resource, // but only mark it as changed if it wasn't empty. - let mut next_state_resource = world.resource_mut::>(); + let Some(mut next_state_resource) = world.get_resource_mut::>() else { + return; + }; if let Some(entered) = next_state_resource.bypass_change_detection().0.take() { next_state_resource.set_changed(); - - let mut state_resource = world.resource_mut::>(); - if *state_resource != entered { - let exited = mem::replace(&mut state_resource.0, entered.clone()); - world.send_event(StateTransitionEvent { - before: exited.clone(), - after: entered.clone(), - }); - // Try to run the schedules if they exist. - world.try_run_schedule(OnExit(exited.clone())).ok(); - world - .try_run_schedule(OnTransition { - from: exited, - to: entered.clone(), - }) - .ok(); - world.try_run_schedule(OnEnter(entered)).ok(); - } + match world.get_resource_mut::>() { + Some(mut state_resource) => { + if *state_resource != entered { + let exited = mem::replace(&mut state_resource.0, entered.clone()); + world.send_event(StateTransitionEvent { + before: exited.clone(), + after: entered.clone(), + }); + // Try to run the schedules if they exist. + world.try_run_schedule(OnExit(exited.clone())).ok(); + world + .try_run_schedule(OnTransition { + from: exited, + to: entered.clone(), + }) + .ok(); + world.try_run_schedule(OnEnter(entered)).ok(); + } + } + None => { + world.insert_resource(State(entered.clone())); + world.try_run_schedule(OnEnter(entered)).ok(); + } + }; } }