diff --git a/crates/bevy_state/src/lib.rs b/crates/bevy_state/src/lib.rs index 38382fab4fd70..a64a34a04e6e3 100644 --- a/crates/bevy_state/src/lib.rs +++ b/crates/bevy_state/src/lib.rs @@ -47,8 +47,9 @@ pub mod prelude { pub use crate::condition::*; #[doc(hidden)] pub use crate::state::{ - last_transition, ComputedStates, NextState, OnEnter, OnExit, OnTransition, State, StateSet, - StateTransition, StateTransitionEvent, StateTransitionSteps, States, SubStates, + last_transition, ComputedStates, EnterSchedules, ExitSchedules, NextState, OnEnter, OnExit, + OnTransition, State, StateSet, StateTransition, StateTransitionEvent, States, SubStates, + TransitionSchedules, }; #[doc(hidden)] pub use crate::state_scoped::StateScoped; diff --git a/crates/bevy_state/src/state/freely_mutable_state.rs b/crates/bevy_state/src/state/freely_mutable_state.rs index 0df74779267c4..f2b6dd0c21171 100644 --- a/crates/bevy_state/src/state/freely_mutable_state.rs +++ b/crates/bevy_state/src/state/freely_mutable_state.rs @@ -17,27 +17,33 @@ use super::{take_next_state, transitions::*}; pub trait FreelyMutableState: States { /// This function registers all the necessary systems to apply state changes and run transition schedules fn register_state(schedule: &mut Schedule) { + schedule.configure_sets(( + ApplyStateTransition::::default() + .in_set(StateTransitionSteps::DependentTransitions), + ExitSchedules::::default().in_set(StateTransitionSteps::ExitSchedules), + TransitionSchedules::::default() + .in_set(StateTransitionSteps::TransitionSchedules), + EnterSchedules::::default().in_set(StateTransitionSteps::EnterSchedules), + )); + schedule .add_systems( - apply_state_transition::.in_set(ApplyStateTransition::::apply()), - ) - .add_systems( - last_transition:: - .pipe(run_enter::) - .in_set(StateTransitionSteps::EnterSchedules), + apply_state_transition::.in_set(ApplyStateTransition::::default()), ) .add_systems( last_transition:: .pipe(run_exit::) - .in_set(StateTransitionSteps::ExitSchedules), + .in_set(ExitSchedules::::default()), ) .add_systems( last_transition:: .pipe(run_transition::) - .in_set(StateTransitionSteps::TransitionSchedules), + .in_set(TransitionSchedules::::default()), ) - .configure_sets( - ApplyStateTransition::::apply().in_set(StateTransitionSteps::RootTransitions), + .add_systems( + last_transition:: + .pipe(run_enter::) + .in_set(EnterSchedules::::default()), ); } } diff --git a/crates/bevy_state/src/state/mod.rs b/crates/bevy_state/src/state/mod.rs index 9e5df2d0b6f62..5220b9f9152ad 100644 --- a/crates/bevy_state/src/state/mod.rs +++ b/crates/bevy_state/src/state/mod.rs @@ -508,14 +508,13 @@ mod tests { #[test] fn same_state_transition_should_emit_event_and_not_run_schedules() { let mut world = World::new(); + setup_state_transitions_in_world(&mut world, None); EventRegistry::register_event::>(&mut world); world.init_resource::>(); - let mut schedules = Schedules::new(); - let mut apply_changes = Schedule::new(StateTransition); - SimpleState::register_state(&mut apply_changes); - schedules.insert(apply_changes); + let mut schedules = world.resource_mut::(); + let apply_changes = schedules.get_mut(StateTransition).unwrap(); + SimpleState::register_state(apply_changes); - world.insert_resource(TransitionCounter::default()); let mut on_exit = Schedule::new(OnExit(SimpleState::A)); on_exit.add_systems(|mut c: ResMut| c.exit += 1); schedules.insert(on_exit); @@ -528,9 +527,7 @@ mod tests { let mut on_enter = Schedule::new(OnEnter(SimpleState::A)); on_enter.add_systems(|mut c: ResMut| c.enter += 1); schedules.insert(on_enter); - - world.insert_resource(schedules); - setup_state_transitions_in_world(&mut world, None); + world.insert_resource(TransitionCounter::default()); world.run_schedule(StateTransition); assert_eq!(world.resource::>().0, SimpleState::A); @@ -546,7 +543,7 @@ mod tests { *world.resource::(), TransitionCounter { exit: 0, - transition: 0, + transition: 1, // Same state transitions are allowed enter: 0 } ); @@ -619,4 +616,118 @@ mod tests { 1 ); } + + #[derive(Resource, Default, Debug)] + struct TransitionTracker(Vec<&'static str>); + + #[derive(PartialEq, Eq, Debug, Hash, Clone)] + enum TransitionTestingComputedState { + IsA, + IsBAndEven, + IsBAndOdd, + } + + impl ComputedStates for TransitionTestingComputedState { + type SourceStates = (Option, Option); + + fn compute(sources: (Option, Option)) -> Option { + match sources { + (Some(simple), sub) => { + if simple == SimpleState::A { + Some(Self::IsA) + } else if sub == Some(SubState::One) { + Some(Self::IsBAndOdd) + } else if sub == Some(SubState::Two) { + Some(Self::IsBAndEven) + } else { + None + } + } + _ => None, + } + } + } + + #[test] + fn check_transition_orders() { + let mut world = World::new(); + setup_state_transitions_in_world(&mut world, None); + EventRegistry::register_event::>(&mut world); + EventRegistry::register_event::>(&mut world); + EventRegistry::register_event::>( + &mut world, + ); + world.insert_resource(State(SimpleState::B(true))); + world.init_resource::>(); + world.insert_resource(State(TransitionTestingComputedState::IsA)); + let mut schedules = world.remove_resource::().unwrap(); + let apply_changes = schedules.get_mut(StateTransition).unwrap(); + SimpleState::register_state(apply_changes); + SubState::register_sub_state_systems(apply_changes); + TransitionTestingComputedState::register_computed_state_systems(apply_changes); + + world.init_resource::(); + fn register_transition(string: &'static str) -> impl Fn(ResMut) { + move |mut transitions: ResMut| transitions.0.push(string) + } + + schedules.add_systems( + StateTransition, + register_transition("simple exit").in_set(ExitSchedules::::default()), + ); + schedules.add_systems( + StateTransition, + register_transition("simple transition") + .in_set(TransitionSchedules::::default()), + ); + schedules.add_systems( + StateTransition, + register_transition("simple enter").in_set(EnterSchedules::::default()), + ); + + schedules.add_systems( + StateTransition, + register_transition("sub exit").in_set(ExitSchedules::::default()), + ); + schedules.add_systems( + StateTransition, + register_transition("sub transition") + .in_set(TransitionSchedules::::default()), + ); + schedules.add_systems( + StateTransition, + register_transition("sub enter").in_set(EnterSchedules::::default()), + ); + + schedules.add_systems( + StateTransition, + register_transition("computed exit") + .in_set(ExitSchedules::::default()), + ); + schedules.add_systems( + StateTransition, + register_transition("computed transition") + .in_set(TransitionSchedules::::default()), + ); + schedules.add_systems( + StateTransition, + register_transition("computed enter") + .in_set(EnterSchedules::::default()), + ); + + world.insert_resource(schedules); + + world.run_schedule(StateTransition); + + let transitions = &world.resource::().0; + + assert_eq!(transitions.len(), 9); + assert_eq!(transitions[0], "computed exit"); + assert_eq!(transitions[1], "sub exit"); + assert_eq!(transitions[2], "simple exit"); + // Transition order is arbitrary and doesn't need testing. + assert_eq!(transitions[6], "simple enter"); + assert_eq!(transitions[7], "sub enter"); + assert_eq!(transitions[8], "computed enter"); + } } diff --git a/crates/bevy_state/src/state/state_set.rs b/crates/bevy_state/src/state/state_set.rs index e6480d51d3152..6b83b2d17cb50 100644 --- a/crates/bevy_state/src/state/state_set.rs +++ b/crates/bevy_state/src/state/state_set.rs @@ -10,7 +10,8 @@ use self::sealed::StateSetSealed; use super::{ computed_states::ComputedStates, internal_apply_state_transition, last_transition, run_enter, run_exit, run_transition, sub_states::SubStates, take_next_state, ApplyStateTransition, - NextState, State, StateTransitionEvent, StateTransitionSteps, States, + EnterSchedules, ExitSchedules, NextState, State, StateTransitionEvent, StateTransitionSteps, + States, TransitionSchedules, }; mod sealed { @@ -114,27 +115,35 @@ impl StateSet for S { internal_apply_state_transition(event, commands, current_state, new_state); }; + schedule.configure_sets(( + ApplyStateTransition::::default() + .in_set(StateTransitionSteps::DependentTransitions) + .after(ApplyStateTransition::::default()), + ExitSchedules::::default() + .in_set(StateTransitionSteps::ExitSchedules) + .before(ExitSchedules::::default()), + TransitionSchedules::::default().in_set(StateTransitionSteps::TransitionSchedules), + EnterSchedules::::default() + .in_set(StateTransitionSteps::EnterSchedules) + .after(EnterSchedules::::default()), + )); + schedule - .add_systems(apply_state_transition.in_set(ApplyStateTransition::::apply())) - .add_systems( - last_transition:: - .pipe(run_enter::) - .in_set(StateTransitionSteps::EnterSchedules), - ) + .add_systems(apply_state_transition.in_set(ApplyStateTransition::::default())) .add_systems( last_transition:: .pipe(run_exit::) - .in_set(StateTransitionSteps::ExitSchedules), + .in_set(ExitSchedules::::default()), ) .add_systems( last_transition:: .pipe(run_transition::) - .in_set(StateTransitionSteps::TransitionSchedules), + .in_set(TransitionSchedules::::default()), ) - .configure_sets( - ApplyStateTransition::::apply() - .in_set(StateTransitionSteps::DependentTransitions) - .after(ApplyStateTransition::::apply()), + .add_systems( + last_transition:: + .pipe(run_enter::) + .in_set(EnterSchedules::::default()), ); } @@ -186,27 +195,35 @@ impl StateSet for S { internal_apply_state_transition(event, commands, current_state_res, new_state); }; + schedule.configure_sets(( + ApplyStateTransition::::default() + .in_set(StateTransitionSteps::DependentTransitions) + .after(ApplyStateTransition::::default()), + ExitSchedules::::default() + .in_set(StateTransitionSteps::ExitSchedules) + .before(ExitSchedules::::default()), + TransitionSchedules::::default().in_set(StateTransitionSteps::TransitionSchedules), + EnterSchedules::::default() + .in_set(StateTransitionSteps::EnterSchedules) + .after(EnterSchedules::::default()), + )); + schedule - .add_systems(apply_state_transition.in_set(ApplyStateTransition::::apply())) - .add_systems( - last_transition:: - .pipe(run_enter::) - .in_set(StateTransitionSteps::EnterSchedules), - ) + .add_systems(apply_state_transition.in_set(ApplyStateTransition::::default())) .add_systems( last_transition:: .pipe(run_exit::) - .in_set(StateTransitionSteps::ExitSchedules), + .in_set(ExitSchedules::::default()), ) .add_systems( last_transition:: .pipe(run_transition::) - .in_set(StateTransitionSteps::TransitionSchedules), + .in_set(TransitionSchedules::::default()), ) - .configure_sets( - ApplyStateTransition::::apply() - .in_set(StateTransitionSteps::DependentTransitions) - .after(ApplyStateTransition::::apply()), + .add_systems( + last_transition:: + .pipe(run_enter::) + .in_set(EnterSchedules::::default()), ); } } @@ -243,16 +260,25 @@ macro_rules! impl_state_set_sealed_tuples { internal_apply_state_transition(event, commands, current_state, new_state); }; - schedule - .add_systems(apply_state_transition.in_set(ApplyStateTransition::::apply())) - .add_systems(last_transition::.pipe(run_enter::).in_set(StateTransitionSteps::EnterSchedules)) - .add_systems(last_transition::.pipe(run_exit::).in_set(StateTransitionSteps::ExitSchedules)) - .add_systems(last_transition::.pipe(run_transition::).in_set(StateTransitionSteps::TransitionSchedules)) - .configure_sets( - ApplyStateTransition::::apply() + schedule.configure_sets(( + ApplyStateTransition::::default() .in_set(StateTransitionSteps::DependentTransitions) - $(.after(ApplyStateTransition::<$param::RawState>::apply()))* - ); + $(.after(ApplyStateTransition::<$param::RawState>::default()))*, + ExitSchedules::::default() + .in_set(StateTransitionSteps::ExitSchedules) + $(.before(ExitSchedules::<$param::RawState>::default()))*, + TransitionSchedules::::default() + .in_set(StateTransitionSteps::TransitionSchedules), + EnterSchedules::::default() + .in_set(StateTransitionSteps::EnterSchedules) + $(.after(EnterSchedules::<$param::RawState>::default()))*, + )); + + schedule + .add_systems(apply_state_transition.in_set(ApplyStateTransition::::default())) + .add_systems(last_transition::.pipe(run_exit::).in_set(ExitSchedules::::default())) + .add_systems(last_transition::.pipe(run_transition::).in_set(TransitionSchedules::::default())) + .add_systems(last_transition::.pipe(run_enter::).in_set(EnterSchedules::::default())); } fn register_sub_state_systems_in_schedule>( @@ -288,16 +314,25 @@ macro_rules! impl_state_set_sealed_tuples { internal_apply_state_transition(event, commands, current_state_res, new_state); }; - schedule - .add_systems(apply_state_transition.in_set(ApplyStateTransition::::apply())) - .add_systems(last_transition::.pipe(run_enter::).in_set(StateTransitionSteps::EnterSchedules)) - .add_systems(last_transition::.pipe(run_exit::).in_set(StateTransitionSteps::ExitSchedules)) - .add_systems(last_transition::.pipe(run_transition::).in_set(StateTransitionSteps::TransitionSchedules)) - .configure_sets( - ApplyStateTransition::::apply() + schedule.configure_sets(( + ApplyStateTransition::::default() .in_set(StateTransitionSteps::DependentTransitions) - $(.after(ApplyStateTransition::<$param::RawState>::apply()))* - ); + $(.after(ApplyStateTransition::<$param::RawState>::default()))*, + ExitSchedules::::default() + .in_set(StateTransitionSteps::ExitSchedules) + $(.before(ExitSchedules::<$param::RawState>::default()))*, + TransitionSchedules::::default() + .in_set(StateTransitionSteps::TransitionSchedules), + EnterSchedules::::default() + .in_set(StateTransitionSteps::EnterSchedules) + $(.after(EnterSchedules::<$param::RawState>::default()))*, + )); + + schedule + .add_systems(apply_state_transition.in_set(ApplyStateTransition::::default())) + .add_systems(last_transition::.pipe(run_exit::).in_set(ExitSchedules::::default())) + .add_systems(last_transition::.pipe(run_transition::).in_set(TransitionSchedules::::default())) + .add_systems(last_transition::.pipe(run_enter::).in_set(EnterSchedules::::default())); } } }; diff --git a/crates/bevy_state/src/state/sub_states.rs b/crates/bevy_state/src/state/sub_states.rs index 207ef74e240ef..c978780ba0534 100644 --- a/crates/bevy_state/src/state/sub_states.rs +++ b/crates/bevy_state/src/state/sub_states.rs @@ -150,7 +150,7 @@ pub trait SubStates: States + FreelyMutableState { /// /// This can either be a single type that implements [`States`], or a tuple /// containing multiple types that implement [`States`], or any combination of - /// types implementing [`States`] and Options of types implementing [`States`] + /// types implementing [`States`] and Options of types implementing [`States`]. type SourceStates: StateSet; /// This function gets called whenever one of the [`SourceStates`](Self::SourceStates) changes. diff --git a/crates/bevy_state/src/state/transitions.rs b/crates/bevy_state/src/state/transitions.rs index fa5348ec6af7c..747e09d9dd874 100644 --- a/crates/bevy_state/src/state/transitions.rs +++ b/crates/bevy_state/src/state/transitions.rs @@ -53,30 +53,58 @@ pub struct StateTransitionEvent { pub entered: Option, } -/// Applies manual state transitions using [`NextState`]. +/// 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 StateTransitionSteps { - /// Parentless states apply their [`NextState`]. - RootTransitions, - /// States with parents apply their computation and [`NextState`]. +pub(crate) enum StateTransitionSteps { + /// States apply their transitions from [`NextState`] and compute functions based on their parent states. DependentTransitions, - /// Exit schedules are executed. + /// Exit schedules are executed in leaf to root order ExitSchedules, - /// Transition schedules are executed. + /// Transition schedules are executed in arbitrary order. TransitionSchedules, - /// Enter schedules are executed. + /// Enter schedules are executed in root to leaf order. EnterSchedules, } -/// Defines a system set to aid with dependent state ordering #[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)] -pub struct ApplyStateTransition(PhantomData); +/// System set that runs exit schedule(s) for state `S`. +pub struct ExitSchedules(PhantomData); -impl ApplyStateTransition { - pub(crate) fn apply() -> Self { - Self(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()) } } @@ -151,7 +179,6 @@ pub fn setup_state_transitions_in_world( let mut schedule = Schedule::new(StateTransition); schedule.configure_sets( ( - StateTransitionSteps::RootTransitions, StateTransitionSteps::DependentTransitions, StateTransitionSteps::ExitSchedules, StateTransitionSteps::TransitionSchedules, diff --git a/examples/state/custom_transitions.rs b/examples/state/custom_transitions.rs index 8ca43b80e665f..5504736a0fedd 100644 --- a/examples/state/custom_transitions.rs +++ b/examples/state/custom_transitions.rs @@ -13,10 +13,7 @@ use std::marker::PhantomData; -use bevy::{ - dev_tools::states::*, ecs::schedule::ScheduleLabel, prelude::*, - state::state::StateTransitionSteps, -}; +use bevy::{dev_tools::states::*, ecs::schedule::ScheduleLabel, prelude::*}; use custom_transitions::*; @@ -72,16 +69,16 @@ mod custom_transitions { // State transitions are handled in three ordered steps, exposed as system sets. // We can add our systems to them, which will run the corresponding schedules when they're evaluated. // These are: - // - [`StateTransitionSteps::ExitSchedules`] - // - [`StateTransitionSteps::TransitionSchedules`] - // - [`StateTransitionSteps::EnterSchedules`] - .in_set(StateTransitionSteps::EnterSchedules), + // - [`ExitSchedules`] - Ran from leaf-states to root-states, + // - [`TransitionSchedules`] - Ran in arbitrary order, + // - [`EnterSchedules`] - Ran from root-states to leaf-states. + .in_set(EnterSchedules::::default()), ) .add_systems( StateTransition, last_transition:: .pipe(run_reexit::) - .in_set(StateTransitionSteps::ExitSchedules), + .in_set(ExitSchedules::::default()), ); } }