Path: blob/main/crates/bevy_state/src/state/transitions.rs
9374 views
use core::{marker::PhantomData, mem};12use bevy_ecs::{3message::{Message, MessageReader, MessageWriter},4schedule::{IntoScheduleConfigs, Schedule, ScheduleLabel, Schedules, SystemSet},5system::{Commands, In, ResMut},6world::World,7};89use super::{10resources::{PreviousState, State},11states::States,12};1314/// The label of a [`Schedule`] that **only** runs whenever [`State<S>`] enters the provided state.15///16/// This schedule ignores identity transitions.17#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]18pub struct OnEnter<S: States>(pub S);1920/// The label of a [`Schedule`] that **only** runs whenever [`State<S>`] exits the provided state.21///22/// This schedule ignores identity transitions.23#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]24pub struct OnExit<S: States>(pub S);2526/// The label of a [`Schedule`] that **only** runs whenever [`State<S>`]27/// exits AND enters the provided `exited` and `entered` states.28///29/// Systems added to this schedule are always ran *after* [`OnExit`], and *before* [`OnEnter`].30///31/// This schedule will run on identity transitions.32#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]33pub struct OnTransition<S: States> {34/// The state being exited.35pub exited: S,36/// The state being entered.37pub entered: S,38}3940/// Runs [state transitions](States).41///42/// By default, it will be triggered once before [`PreStartup`] and then each frame after [`PreUpdate`], but43/// you can manually trigger it at arbitrary times by creating an exclusive44/// system to run the schedule.45///46/// ```rust47/// use bevy_state::prelude::*;48/// use bevy_ecs::prelude::*;49///50/// fn run_state_transitions(world: &mut World) {51/// let _ = world.try_run_schedule(StateTransition);52/// }53/// ```54///55/// This schedule is split up into four phases, as described in [`StateTransitionSystems`].56///57/// [`PreStartup`]: https://docs.rs/bevy/latest/bevy/prelude/struct.PreStartup.html58/// [`PreUpdate`]: https://docs.rs/bevy/latest/bevy/prelude/struct.PreUpdate.html59#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]60pub struct StateTransition;6162/// A [`Message`] sent when any state transition of `S` happens.63/// This includes identity transitions, where `exited` and `entered` have the same value.64///65/// If you know exactly what state you want to respond to ahead of time, consider [`OnEnter`], [`OnTransition`], or [`OnExit`]66#[derive(Debug, Copy, Clone, PartialEq, Eq, Message)]67pub struct StateTransitionEvent<S: States> {68/// The state being exited.69pub exited: Option<S>,70/// The state being entered.71pub entered: Option<S>,72/// Allow running state transition events when `exited` and `entered` are the same73pub allow_same_state_transitions: bool,74}7576/// Applies state transitions and runs transitions schedules in order.77///78/// These system sets are run sequentially, in the order of the enum variants.79#[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)]80pub enum StateTransitionSystems {81/// States apply their transitions from [`NextState`](super::NextState)82/// and compute functions based on their parent states.83DependentTransitions,84/// Exit schedules are executed in leaf to root order85ExitSchedules,86/// Transition schedules are executed in arbitrary order.87TransitionSchedules,88/// Enter schedules are executed in root to leaf order.89EnterSchedules,90}9192#[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)]93/// System set that runs exit schedule(s) for state `S`.94pub struct ExitSchedules<S: States>(PhantomData<S>);9596impl<S: States> Default for ExitSchedules<S> {97fn default() -> Self {98Self(Default::default())99}100}101102#[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)]103/// System set that runs transition schedule(s) for state `S`.104pub struct TransitionSchedules<S: States>(PhantomData<S>);105106impl<S: States> Default for TransitionSchedules<S> {107fn default() -> Self {108Self(Default::default())109}110}111112#[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)]113/// System set that runs enter schedule(s) for state `S`.114pub struct EnterSchedules<S: States>(PhantomData<S>);115116impl<S: States> Default for EnterSchedules<S> {117fn default() -> Self {118Self(Default::default())119}120}121122/// System set that applies transitions for state `S`.123#[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)]124pub(crate) struct ApplyStateTransition<S: States>(PhantomData<S>);125126impl<S: States> Default for ApplyStateTransition<S> {127fn default() -> Self {128Self(Default::default())129}130}131132/// This function actually applies a state change, and registers the required133/// schedules for downstream computed states and transition schedules.134///135/// The `new_state` is an option to allow for removal - `None` will trigger the136/// removal of the `State<S>` resource from the [`World`].137pub(crate) fn internal_apply_state_transition<S: States>(138mut event: MessageWriter<StateTransitionEvent<S>>,139mut commands: Commands,140current_state: Option<ResMut<State<S>>>,141mut previous_state: Option<ResMut<PreviousState<S>>>,142new_state: Option<S>,143allow_same_state_transitions: bool,144) {145match new_state {146Some(entered) => {147match current_state {148// If the [`State<S>`] resource exists, and the state is not the one we are149// entering - we need to set the new value, compute dependent states, send transition events150// and register transition schedules.151Some(mut state_resource) => {152let exited = match *state_resource == entered {153true => entered.clone(),154false => mem::replace(&mut state_resource.0, entered.clone()),155};156157// Transition events are sent even for same state transitions158// Although enter and exit schedules are not run by default.159event.write(StateTransitionEvent {160exited: Some(exited.clone()),161entered: Some(entered.clone()),162allow_same_state_transitions,163});164165if let Some(ref mut previous_state) = previous_state {166previous_state.0 = exited;167} else {168commands.insert_resource(PreviousState(exited));169}170}171None => {172// If the [`State<S>`] resource does not exist, we create it, compute dependent states, send a transition event and register the `OnEnter` schedule.173commands.insert_resource(State(entered.clone()));174175event.write(StateTransitionEvent {176exited: None,177entered: Some(entered.clone()),178allow_same_state_transitions,179});180181// When [`State<S>`] is initialized, there can be stale data in182// [`PreviousState<S>`] from a prior transition to `None`, so we remove it.183if previous_state.is_some() {184commands.remove_resource::<PreviousState<S>>();185}186}187};188}189None => {190// We first remove the [`State<S>`] resource, and if one existed we compute dependent states, send a transition event and run the `OnExit` schedule.191if let Some(resource) = current_state {192let exited = resource.get().clone();193commands.remove_resource::<State<S>>();194195event.write(StateTransitionEvent {196exited: Some(exited.clone()),197entered: None,198allow_same_state_transitions,199});200201if let Some(ref mut previous_state) = previous_state {202previous_state.0 = exited;203} else {204commands.insert_resource(PreviousState(exited));205}206}207}208}209}210211/// Sets up the schedules and systems for handling state transitions212/// within a [`World`].213///214/// Runs automatically when using `App` to insert states, but needs to215/// be added manually in other situations.216pub fn setup_state_transitions_in_world(world: &mut World) {217let mut schedules = world.get_resource_or_init::<Schedules>();218if schedules.contains(StateTransition) {219return;220}221let mut schedule = Schedule::new(StateTransition);222schedule.configure_sets(223(224StateTransitionSystems::DependentTransitions,225StateTransitionSystems::ExitSchedules,226StateTransitionSystems::TransitionSchedules,227StateTransitionSystems::EnterSchedules,228)229.chain(),230);231232schedules.insert(schedule);233}234235/// Returns the latest state transition event of type `S`, if any are available.236pub fn last_transition<S: States>(237mut reader: MessageReader<StateTransitionEvent<S>>,238) -> Option<StateTransitionEvent<S>> {239reader.read().last().cloned()240}241242pub(crate) fn run_enter<S: States>(243transition: In<Option<StateTransitionEvent<S>>>,244world: &mut World,245) {246let Some(transition) = transition.0 else {247return;248};249if transition.entered == transition.exited && !transition.allow_same_state_transitions {250return;251}252let Some(entered) = transition.entered else {253return;254};255256let _ = world.try_run_schedule(OnEnter(entered));257}258259pub(crate) fn run_exit<S: States>(260transition: In<Option<StateTransitionEvent<S>>>,261world: &mut World,262) {263let Some(transition) = transition.0 else {264return;265};266if transition.entered == transition.exited && !transition.allow_same_state_transitions {267return;268}269let Some(exited) = transition.exited else {270return;271};272273let _ = world.try_run_schedule(OnExit(exited));274}275276pub(crate) fn run_transition<S: States>(277transition: In<Option<StateTransitionEvent<S>>>,278world: &mut World,279) {280let Some(transition) = transition.0 else {281return;282};283let Some(exited) = transition.exited else {284return;285};286let Some(entered) = transition.entered else {287return;288};289290let _ = world.try_run_schedule(OnTransition { exited, entered });291}292293294