Path: blob/main/crates/bevy_state/src/state/transitions.rs
6596 views
use core::{marker::PhantomData, mem};12use bevy_ecs::{3event::{BufferedEvent, EventReader, EventWriter},4schedule::{IntoScheduleConfigs, Schedule, ScheduleLabel, Schedules, SystemSet},5system::{Commands, In, ResMut},6world::World,7};89use super::{resources::State, states::States};1011/// The label of a [`Schedule`] that **only** runs whenever [`State<S>`] enters the provided state.12///13/// This schedule ignores identity transitions.14#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]15pub struct OnEnter<S: States>(pub S);1617/// The label of a [`Schedule`] that **only** runs whenever [`State<S>`] exits the provided state.18///19/// This schedule ignores identity transitions.20#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]21pub struct OnExit<S: States>(pub S);2223/// The label of a [`Schedule`] that **only** runs whenever [`State<S>`]24/// exits AND enters the provided `exited` and `entered` states.25///26/// Systems added to this schedule are always ran *after* [`OnExit`], and *before* [`OnEnter`].27///28/// This schedule will run on identity transitions.29#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]30pub struct OnTransition<S: States> {31/// The state being exited.32pub exited: S,33/// The state being entered.34pub entered: S,35}3637/// Runs [state transitions](States).38///39/// By default, it will be triggered once before [`PreStartup`] and then each frame after [`PreUpdate`], but40/// you can manually trigger it at arbitrary times by creating an exclusive41/// system to run the schedule.42///43/// ```rust44/// use bevy_state::prelude::*;45/// use bevy_ecs::prelude::*;46///47/// fn run_state_transitions(world: &mut World) {48/// let _ = world.try_run_schedule(StateTransition);49/// }50/// ```51///52/// This schedule is split up into four phases, as described in [`StateTransitionSteps`].53///54/// [`PreStartup`]: https://docs.rs/bevy/latest/bevy/prelude/struct.PreStartup.html55/// [`PreUpdate`]: https://docs.rs/bevy/latest/bevy/prelude/struct.PreUpdate.html56#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]57pub struct StateTransition;5859/// A [`BufferedEvent`] sent when any state transition of `S` happens.60/// This includes identity transitions, where `exited` and `entered` have the same value.61///62/// If you know exactly what state you want to respond to ahead of time, consider [`OnEnter`], [`OnTransition`], or [`OnExit`]63#[derive(Debug, Copy, Clone, PartialEq, Eq, BufferedEvent)]64pub struct StateTransitionEvent<S: States> {65/// The state being exited.66pub exited: Option<S>,67/// The state being entered.68pub entered: Option<S>,69}7071/// Applies state transitions and runs transitions schedules in order.72///73/// These system sets are run sequentially, in the order of the enum variants.74#[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)]75pub enum StateTransitionSystems {76/// States apply their transitions from [`NextState`](super::NextState)77/// and compute functions based on their parent states.78DependentTransitions,79/// Exit schedules are executed in leaf to root order80ExitSchedules,81/// Transition schedules are executed in arbitrary order.82TransitionSchedules,83/// Enter schedules are executed in root to leaf order.84EnterSchedules,85}8687/// Deprecated alias for [`StateTransitionSystems`].88#[deprecated(since = "0.17.0", note = "Renamed to `StateTransitionSystems`.")]89pub type StateTransitionSteps = StateTransitionSystems;9091#[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)]92/// System set that runs exit schedule(s) for state `S`.93pub struct ExitSchedules<S: States>(PhantomData<S>);9495impl<S: States> Default for ExitSchedules<S> {96fn default() -> Self {97Self(Default::default())98}99}100101#[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)]102/// System set that runs transition schedule(s) for state `S`.103pub struct TransitionSchedules<S: States>(PhantomData<S>);104105impl<S: States> Default for TransitionSchedules<S> {106fn default() -> Self {107Self(Default::default())108}109}110111#[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)]112/// System set that runs enter schedule(s) for state `S`.113pub struct EnterSchedules<S: States>(PhantomData<S>);114115impl<S: States> Default for EnterSchedules<S> {116fn default() -> Self {117Self(Default::default())118}119}120121/// System set that applies transitions for state `S`.122#[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)]123pub(crate) struct ApplyStateTransition<S: States>(PhantomData<S>);124125impl<S: States> Default for ApplyStateTransition<S> {126fn default() -> Self {127Self(Default::default())128}129}130131/// This function actually applies a state change, and registers the required132/// schedules for downstream computed states and transition schedules.133///134/// The `new_state` is an option to allow for removal - `None` will trigger the135/// removal of the `State<S>` resource from the [`World`].136pub(crate) fn internal_apply_state_transition<S: States>(137mut event: EventWriter<StateTransitionEvent<S>>,138mut commands: Commands,139current_state: Option<ResMut<State<S>>>,140new_state: Option<S>,141) {142match new_state {143Some(entered) => {144match current_state {145// If the [`State<S>`] resource exists, and the state is not the one we are146// entering - we need to set the new value, compute dependent states, send transition events147// and register transition schedules.148Some(mut state_resource) => {149let exited = match *state_resource == entered {150true => entered.clone(),151false => mem::replace(&mut state_resource.0, entered.clone()),152};153154// Transition events are sent even for same state transitions155// Although enter and exit schedules are not run by default.156event.write(StateTransitionEvent {157exited: Some(exited.clone()),158entered: Some(entered.clone()),159});160}161None => {162// If the [`State<S>`] resource does not exist, we create it, compute dependent states, send a transition event and register the `OnEnter` schedule.163commands.insert_resource(State(entered.clone()));164165event.write(StateTransitionEvent {166exited: None,167entered: Some(entered.clone()),168});169}170};171}172None => {173// We first remove the [`State<S>`] resource, and if one existed we compute dependent states, send a transition event and run the `OnExit` schedule.174if let Some(resource) = current_state {175commands.remove_resource::<State<S>>();176177event.write(StateTransitionEvent {178exited: Some(resource.get().clone()),179entered: None,180});181}182}183}184}185186/// Sets up the schedules and systems for handling state transitions187/// within a [`World`].188///189/// Runs automatically when using `App` to insert states, but needs to190/// be added manually in other situations.191pub fn setup_state_transitions_in_world(world: &mut World) {192let mut schedules = world.get_resource_or_init::<Schedules>();193if schedules.contains(StateTransition) {194return;195}196let mut schedule = Schedule::new(StateTransition);197schedule.configure_sets(198(199StateTransitionSystems::DependentTransitions,200StateTransitionSystems::ExitSchedules,201StateTransitionSystems::TransitionSchedules,202StateTransitionSystems::EnterSchedules,203)204.chain(),205);206schedules.insert(schedule);207}208209/// Returns the latest state transition event of type `S`, if any are available.210pub fn last_transition<S: States>(211mut reader: EventReader<StateTransitionEvent<S>>,212) -> Option<StateTransitionEvent<S>> {213reader.read().last().cloned()214}215216pub(crate) fn run_enter<S: States>(217transition: In<Option<StateTransitionEvent<S>>>,218world: &mut World,219) {220let Some(transition) = transition.0 else {221return;222};223if transition.entered == transition.exited {224return;225}226let Some(entered) = transition.entered else {227return;228};229230let _ = world.try_run_schedule(OnEnter(entered));231}232233pub(crate) fn run_exit<S: States>(234transition: In<Option<StateTransitionEvent<S>>>,235world: &mut World,236) {237let Some(transition) = transition.0 else {238return;239};240if transition.entered == transition.exited {241return;242}243let Some(exited) = transition.exited else {244return;245};246247let _ = world.try_run_schedule(OnExit(exited));248}249250pub(crate) fn run_transition<S: States>(251transition: In<Option<StateTransitionEvent<S>>>,252world: &mut World,253) {254let Some(transition) = transition.0 else {255return;256};257let Some(exited) = transition.exited else {258return;259};260let Some(entered) = transition.entered else {261return;262};263264let _ = world.try_run_schedule(OnTransition { exited, entered });265}266267268