use core::{marker::PhantomData, mem}; use bevy_ecs::{ event::{BufferedEvent, Event, EventReader, EventWriter}, schedule::{IntoScheduleConfigs, Schedule, ScheduleLabel, Schedules, SystemSet}, system::{Commands, In, ResMut}, world::World, }; use super::{resources::State, states::States}; /// The label of a [`Schedule`] that **only** runs whenever [`State`] enters the provided state. /// /// This schedule ignores identity transitions. #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)] pub struct OnEnter(pub S); /// The label of a [`Schedule`] that **only** runs whenever [`State`] exits the provided state. /// /// This schedule ignores identity transitions. #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)] pub struct OnExit(pub S); /// The label of a [`Schedule`] that **only** runs whenever [`State`] /// exits AND enters the provided `exited` and `entered` states. /// /// Systems added to this schedule are always ran *after* [`OnExit`], and *before* [`OnEnter`]. /// /// This schedule will run on identity transitions. #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)] pub struct OnTransition { /// The state being exited. pub exited: S, /// The state being entered. pub entered: S, } /// Runs [state transitions](States). /// /// By default, it will be triggered once before [`PreStartup`] and then each frame after [`PreUpdate`], but /// you can manually trigger it at arbitrary times by creating an exclusive /// system to run the schedule. /// /// ```rust /// use bevy_state::prelude::*; /// use bevy_ecs::prelude::*; /// /// fn run_state_transitions(world: &mut World) { /// let _ = world.try_run_schedule(StateTransition); /// } /// ``` /// /// [`PreStartup`]: https://docs.rs/bevy/latest/bevy/prelude/struct.PreStartup.html /// [`PreUpdate`]: https://docs.rs/bevy/latest/bevy/prelude/struct.PreUpdate.html #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)] pub struct StateTransition; /// A [`BufferedEvent`] sent when any state transition of `S` happens. /// This includes identity transitions, where `exited` and `entered` have the same value. /// /// If you know exactly what state you want to respond to ahead of time, consider [`OnEnter`], [`OnTransition`], or [`OnExit`] #[derive(Debug, Copy, Clone, PartialEq, Eq, Event, BufferedEvent)] pub struct StateTransitionEvent { /// The state being exited. pub exited: Option, /// The state being entered. pub entered: Option, } /// Applies state transitions and runs transitions schedules in order. /// /// These system sets are run sequentially, in the order of the enum variants. #[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)] pub enum StateTransitionSystems { /// States apply their transitions from [`NextState`](super::NextState) /// and compute functions based on their parent states. DependentTransitions, /// Exit schedules are executed in leaf to root order ExitSchedules, /// Transition schedules are executed in arbitrary order. TransitionSchedules, /// Enter schedules are executed in root to leaf order. EnterSchedules, } /// Deprecated alias for [`StateTransitionSystems`]. #[deprecated(since = "0.17.0", note = "Renamed to `StateTransitionSystems`.")] pub type StateTransitionSteps = StateTransitionSystems; #[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)] /// System set that runs exit schedule(s) for state `S`. pub struct ExitSchedules(PhantomData); impl Default for ExitSchedules { fn default() -> Self { Self(Default::default()) } } #[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)] /// System set that runs transition schedule(s) for state `S`. pub struct TransitionSchedules(PhantomData); impl Default for TransitionSchedules { fn default() -> Self { Self(Default::default()) } } #[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)] /// System set that runs enter schedule(s) for state `S`. pub struct EnterSchedules(PhantomData); impl Default for EnterSchedules { fn default() -> Self { Self(Default::default()) } } /// System set that applies transitions for state `S`. #[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)] pub(crate) struct ApplyStateTransition(PhantomData); impl Default for ApplyStateTransition { fn default() -> Self { Self(Default::default()) } } /// This function actually applies a state change, and registers the required /// schedules for downstream computed states and transition schedules. /// /// The `new_state` is an option to allow for removal - `None` will trigger the /// removal of the `State` resource from the [`World`]. pub(crate) fn internal_apply_state_transition( mut event: EventWriter>, mut commands: Commands, current_state: Option>>, new_state: Option, ) { match new_state { Some(entered) => { match current_state { // If the [`State`] resource exists, and the state is not the one we are // entering - we need to set the new value, compute dependent states, send transition events // and register transition schedules. Some(mut state_resource) => { let exited = match *state_resource == entered { true => entered.clone(), false => mem::replace(&mut state_resource.0, entered.clone()), }; // Transition events are sent even for same state transitions // Although enter and exit schedules are not run by default. event.write(StateTransitionEvent { exited: Some(exited.clone()), entered: Some(entered.clone()), }); } None => { // If the [`State`] resource does not exist, we create it, compute dependent states, send a transition event and register the `OnEnter` schedule. commands.insert_resource(State(entered.clone())); event.write(StateTransitionEvent { exited: None, entered: Some(entered.clone()), }); } }; } None => { // We first remove the [`State`] resource, and if one existed we compute dependent states, send a transition event and run the `OnExit` schedule. if let Some(resource) = current_state { commands.remove_resource::>(); event.write(StateTransitionEvent { exited: Some(resource.get().clone()), entered: None, }); } } } } /// Sets up the schedules and systems for handling state transitions /// within a [`World`]. /// /// Runs automatically when using `App` to insert states, but needs to /// be added manually in other situations. pub fn setup_state_transitions_in_world(world: &mut World) { let mut schedules = world.get_resource_or_init::(); if schedules.contains(StateTransition) { return; } let mut schedule = Schedule::new(StateTransition); schedule.configure_sets( ( StateTransitionSystems::DependentTransitions, StateTransitionSystems::ExitSchedules, StateTransitionSystems::TransitionSchedules, StateTransitionSystems::EnterSchedules, ) .chain(), ); schedules.insert(schedule); } /// Returns the latest state transition event of type `S`, if any are available. pub fn last_transition( mut reader: EventReader>, ) -> Option> { reader.read().last().cloned() } pub(crate) fn run_enter( transition: In>>, world: &mut World, ) { let Some(transition) = transition.0 else { return; }; if transition.entered == transition.exited { return; } let Some(entered) = transition.entered else { return; }; let _ = world.try_run_schedule(OnEnter(entered)); } pub(crate) fn run_exit( transition: In>>, world: &mut World, ) { let Some(transition) = transition.0 else { return; }; if transition.entered == transition.exited { return; } let Some(exited) = transition.exited else { return; }; let _ = world.try_run_schedule(OnExit(exited)); } pub(crate) fn run_transition( transition: In>>, world: &mut World, ) { let Some(transition) = transition.0 else { return; }; let Some(exited) = transition.exited else { return; }; let Some(entered) = transition.entered else { return; }; let _ = world.try_run_schedule(OnTransition { exited, entered }); }