mirror of
https://github.com/bevyengine/bevy
synced 2024-11-21 20:23:28 +00:00
Combine transition systems of Substates
(#13626)
# Objective Prerequisite to #13579. Combine separate `Substates` transition systems to centralize transition logic and exert more control over it. ## Solution Originally the transition happened in 2 stages: - `apply_state_transition` in `ManualTransitions` handled `NextState`, - closure system in `DependentTransitions` handled parent-related changes, insertion and deletion of the substate. Now: - Both transitions are processed in a single closure system during `DependentTransitions`. - Since `Substates` no longer use `ManualTransitions`, it's been renamed to `RootTransitions`. Only root states use it. - When `Substates` state comes into existence, it will try to initialize from `NextState` and fallback to `should_exist` result. - Remove `apply_state_transition` from public API. Consequentially, removed the possibility of multiple `StateTransitionEvent`s when both transition systems fire in a single frame. ## Changelog - Renamed `ManualTransitions` to `RootTransitions`. - `Substates` will initialize their value with `NextState` if available and fallback to `should_exist` result. ## Migration Guide - `apply_state_transition` is no longer publicly available, run the `StateTransition` schedule instead. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
This commit is contained in:
parent
09fb6b4820
commit
07aa9e5641
7 changed files with 161 additions and 146 deletions
|
@ -118,11 +118,7 @@ pub fn derive_substates(input: TokenStream) -> TokenStream {
|
|||
type SourceStates = #source_state_type;
|
||||
|
||||
fn should_exist(sources: #source_state_type) -> Option<Self> {
|
||||
if matches!(sources, #source_state_value) {
|
||||
Some(Self::default())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
matches!(sources, #source_state_value).then_some(Self::default())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ pub mod prelude {
|
|||
pub use crate::condition::*;
|
||||
#[doc(hidden)]
|
||||
pub use crate::state::{
|
||||
apply_state_transition, ComputedStates, NextState, OnEnter, OnExit, OnTransition, State,
|
||||
StateSet, StateTransition, StateTransitionEvent, States, SubStates,
|
||||
ComputedStates, NextState, OnEnter, OnExit, OnTransition, State, StateSet, StateTransition,
|
||||
StateTransitionEvent, States, SubStates,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
use bevy_ecs::prelude::Schedule;
|
||||
use bevy_ecs::schedule::{IntoSystemConfigs, IntoSystemSetConfigs};
|
||||
use bevy_ecs::system::IntoSystem;
|
||||
use bevy_ecs::{
|
||||
event::EventWriter,
|
||||
prelude::Schedule,
|
||||
system::{Commands, ResMut},
|
||||
};
|
||||
|
||||
use super::states::States;
|
||||
use super::transitions::*;
|
||||
use super::{states::States, NextState, State};
|
||||
use super::{take_next_state, transitions::*};
|
||||
|
||||
/// This trait allows a state to be mutated directly using the [`NextState<S>`](crate::state::NextState) resource.
|
||||
///
|
||||
|
@ -32,8 +36,24 @@ pub trait FreelyMutableState: States {
|
|||
.in_set(StateTransitionSteps::TransitionSchedules),
|
||||
)
|
||||
.configure_sets(
|
||||
ApplyStateTransition::<Self>::apply()
|
||||
.in_set(StateTransitionSteps::ManualTransitions),
|
||||
ApplyStateTransition::<Self>::apply().in_set(StateTransitionSteps::RootTransitions),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_state_transition<S: FreelyMutableState>(
|
||||
event: EventWriter<StateTransitionEvent<S>>,
|
||||
commands: Commands,
|
||||
current_state: Option<ResMut<State<S>>>,
|
||||
next_state: Option<ResMut<NextState<S>>>,
|
||||
) {
|
||||
let Some(next_state) = take_next_state(next_state) else {
|
||||
return;
|
||||
};
|
||||
let Some(current_state) = current_state else {
|
||||
return;
|
||||
};
|
||||
if next_state != *current_state.get() {
|
||||
internal_apply_state_transition(event, commands, Some(current_state), Some(next_state));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use bevy_ecs::{
|
||||
system::Resource,
|
||||
change_detection::DetectChangesMut,
|
||||
system::{ResMut, Resource},
|
||||
world::{FromWorld, World},
|
||||
};
|
||||
|
||||
|
@ -107,7 +108,7 @@ impl<S: States> Deref for State<S> {
|
|||
/// next_game_state.set(GameState::InGame);
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Resource, Debug, Default)]
|
||||
#[derive(Resource, Debug, Default, Clone)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(bevy_reflect::Reflect),
|
||||
|
@ -132,3 +133,17 @@ impl<S: FreelyMutableState> NextState<S> {
|
|||
*self = Self::Unchanged;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn take_next_state<S: FreelyMutableState>(
|
||||
next_state: Option<ResMut<NextState<S>>>,
|
||||
) -> Option<S> {
|
||||
let mut next_state = next_state?;
|
||||
|
||||
match std::mem::take(next_state.bypass_change_detection()) {
|
||||
NextState::Pending(x) => {
|
||||
next_state.set_changed();
|
||||
Some(x)
|
||||
}
|
||||
NextState::Unchanged => None,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,9 +8,9 @@ use bevy_utils::all_tuples;
|
|||
use self::sealed::StateSetSealed;
|
||||
|
||||
use super::{
|
||||
apply_state_transition, computed_states::ComputedStates, internal_apply_state_transition,
|
||||
last_transition, run_enter, run_exit, run_transition, sub_states::SubStates,
|
||||
ApplyStateTransition, State, StateTransitionEvent, StateTransitionSteps, States,
|
||||
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,
|
||||
};
|
||||
|
||||
mod sealed {
|
||||
|
@ -93,28 +93,29 @@ impl<S: InnerStateSet> StateSet for S {
|
|||
fn register_computed_state_systems_in_schedule<T: ComputedStates<SourceStates = Self>>(
|
||||
schedule: &mut Schedule,
|
||||
) {
|
||||
let system = |mut parent_changed: EventReader<StateTransitionEvent<S::RawState>>,
|
||||
event: EventWriter<StateTransitionEvent<T>>,
|
||||
commands: Commands,
|
||||
current_state: Option<ResMut<State<T>>>,
|
||||
state_set: Option<Res<State<S::RawState>>>| {
|
||||
if parent_changed.is_empty() {
|
||||
return;
|
||||
}
|
||||
parent_changed.clear();
|
||||
let apply_state_transition =
|
||||
|mut parent_changed: EventReader<StateTransitionEvent<S::RawState>>,
|
||||
event: EventWriter<StateTransitionEvent<T>>,
|
||||
commands: Commands,
|
||||
current_state: Option<ResMut<State<T>>>,
|
||||
state_set: Option<Res<State<S::RawState>>>| {
|
||||
if parent_changed.is_empty() {
|
||||
return;
|
||||
}
|
||||
parent_changed.clear();
|
||||
|
||||
let new_state =
|
||||
if let Some(state_set) = S::convert_to_usable_state(state_set.as_deref()) {
|
||||
T::compute(state_set)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let new_state =
|
||||
if let Some(state_set) = S::convert_to_usable_state(state_set.as_deref()) {
|
||||
T::compute(state_set)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
internal_apply_state_transition(event, commands, current_state, new_state);
|
||||
};
|
||||
internal_apply_state_transition(event, commands, current_state, new_state);
|
||||
};
|
||||
|
||||
schedule
|
||||
.add_systems(system.in_set(ApplyStateTransition::<T>::apply()))
|
||||
.add_systems(apply_state_transition.in_set(ApplyStateTransition::<T>::apply()))
|
||||
.add_systems(
|
||||
last_transition::<T>
|
||||
.pipe(run_enter::<T>)
|
||||
|
@ -140,45 +141,55 @@ impl<S: InnerStateSet> StateSet for S {
|
|||
fn register_sub_state_systems_in_schedule<T: SubStates<SourceStates = Self>>(
|
||||
schedule: &mut Schedule,
|
||||
) {
|
||||
let system = |mut parent_changed: EventReader<StateTransitionEvent<S::RawState>>,
|
||||
event: EventWriter<StateTransitionEvent<T>>,
|
||||
commands: Commands,
|
||||
current_state: Option<ResMut<State<T>>>,
|
||||
state_set: Option<Res<State<S::RawState>>>| {
|
||||
if parent_changed.is_empty() {
|
||||
return;
|
||||
}
|
||||
parent_changed.clear();
|
||||
// | parent changed | next state | already exists | should exist | what happens |
|
||||
// | -------------- | ---------- | -------------- | ------------ | -------------------------------- |
|
||||
// | false | false | false | - | - |
|
||||
// | false | false | true | - | - |
|
||||
// | false | true | false | false | - |
|
||||
// | true | false | false | false | - |
|
||||
// | true | true | false | false | - |
|
||||
// | true | false | true | false | Some(current) -> None |
|
||||
// | true | true | true | false | Some(current) -> None |
|
||||
// | true | false | false | true | None -> Some(default) |
|
||||
// | true | true | false | true | None -> Some(next) |
|
||||
// | true | true | true | true | Some(current) -> Some(next) |
|
||||
// | false | true | true | true | Some(current) -> Some(next) |
|
||||
// | true | false | true | true | Some(current) -> Some(current) |
|
||||
|
||||
let new_state =
|
||||
if let Some(state_set) = S::convert_to_usable_state(state_set.as_deref()) {
|
||||
T::should_exist(state_set)
|
||||
} else {
|
||||
None
|
||||
let apply_state_transition =
|
||||
|mut parent_changed: EventReader<StateTransitionEvent<S::RawState>>,
|
||||
event: EventWriter<StateTransitionEvent<T>>,
|
||||
commands: Commands,
|
||||
current_state_res: Option<ResMut<State<T>>>,
|
||||
next_state_res: Option<ResMut<NextState<T>>>,
|
||||
state_set: Option<Res<State<S::RawState>>>| {
|
||||
let parent_changed = parent_changed.read().last().is_some();
|
||||
let next_state = take_next_state(next_state_res);
|
||||
|
||||
if !parent_changed && next_state.is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
let current_state = current_state_res.as_ref().map(|s| s.get()).cloned();
|
||||
|
||||
let should_exist = match parent_changed {
|
||||
true => {
|
||||
if let Some(state_set) = S::convert_to_usable_state(state_set.as_deref()) {
|
||||
T::should_exist(state_set)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
false => current_state.clone(),
|
||||
};
|
||||
|
||||
match new_state {
|
||||
Some(value) => {
|
||||
if current_state.is_none() {
|
||||
internal_apply_state_transition(
|
||||
event,
|
||||
commands,
|
||||
current_state,
|
||||
Some(value),
|
||||
);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
internal_apply_state_transition(event, commands, current_state, None);
|
||||
}
|
||||
let new_state = should_exist
|
||||
.map(|initial_state| next_state.or(current_state).unwrap_or(initial_state));
|
||||
internal_apply_state_transition(event, commands, current_state_res, new_state);
|
||||
};
|
||||
};
|
||||
|
||||
schedule
|
||||
.add_systems(system.in_set(ApplyStateTransition::<T>::apply()))
|
||||
.add_systems(
|
||||
apply_state_transition::<T>.in_set(StateTransitionSteps::ManualTransitions),
|
||||
)
|
||||
.add_systems(apply_state_transition.in_set(ApplyStateTransition::<T>::apply()))
|
||||
.add_systems(
|
||||
last_transition::<T>
|
||||
.pipe(run_enter::<T>)
|
||||
|
@ -214,7 +225,7 @@ macro_rules! impl_state_set_sealed_tuples {
|
|||
fn register_computed_state_systems_in_schedule<T: ComputedStates<SourceStates = Self>>(
|
||||
schedule: &mut Schedule,
|
||||
) {
|
||||
let system = |($(mut $evt),*,): ($(EventReader<StateTransitionEvent<$param::RawState>>),*,), event: EventWriter<StateTransitionEvent<T>>, commands: Commands, current_state: Option<ResMut<State<T>>>, ($($val),*,): ($(Option<Res<State<$param::RawState>>>),*,)| {
|
||||
let apply_state_transition = |($(mut $evt),*,): ($(EventReader<StateTransitionEvent<$param::RawState>>),*,), event: EventWriter<StateTransitionEvent<T>>, commands: Commands, current_state: Option<ResMut<State<T>>>, ($($val),*,): ($(Option<Res<State<$param::RawState>>>),*,)| {
|
||||
if ($($evt.is_empty())&&*) {
|
||||
return;
|
||||
}
|
||||
|
@ -230,7 +241,7 @@ macro_rules! impl_state_set_sealed_tuples {
|
|||
};
|
||||
|
||||
schedule
|
||||
.add_systems(system.in_set(ApplyStateTransition::<T>::apply()))
|
||||
.add_systems(apply_state_transition.in_set(ApplyStateTransition::<T>::apply()))
|
||||
.add_systems(last_transition::<T>.pipe(run_enter::<T>).in_set(StateTransitionSteps::EnterSchedules))
|
||||
.add_systems(last_transition::<T>.pipe(run_exit::<T>).in_set(StateTransitionSteps::ExitSchedules))
|
||||
.add_systems(last_transition::<T>.pipe(run_transition::<T>).in_set(StateTransitionSteps::TransitionSchedules))
|
||||
|
@ -244,32 +255,49 @@ macro_rules! impl_state_set_sealed_tuples {
|
|||
fn register_sub_state_systems_in_schedule<T: SubStates<SourceStates = Self>>(
|
||||
schedule: &mut Schedule,
|
||||
) {
|
||||
let system = |($(mut $evt),*,): ($(EventReader<StateTransitionEvent<$param::RawState>>),*,), event: EventWriter<StateTransitionEvent<T>>, commands: Commands, current_state: Option<ResMut<State<T>>>, ($($val),*,): ($(Option<Res<State<$param::RawState>>>),*,)| {
|
||||
if ($($evt.is_empty())&&*) {
|
||||
let apply_state_transition = |($(mut $evt),*,): ($(EventReader<StateTransitionEvent<$param::RawState>>),*,), event: EventWriter<StateTransitionEvent<T>>, commands: Commands, current_state_res: Option<ResMut<State<T>>>, next_state_res: Option<ResMut<NextState<T>>>, ($($val),*,): ($(Option<Res<State<$param::RawState>>>),*,)| {
|
||||
let parent_changed = ($($evt.read().last().is_some())&&*);
|
||||
let next_state = take_next_state(next_state_res);
|
||||
|
||||
if !parent_changed && next_state.is_none() {
|
||||
return;
|
||||
}
|
||||
$($evt.clear();)*
|
||||
|
||||
let new_state = if let ($(Some($val)),*,) = ($($param::convert_to_usable_state($val.as_deref())),*,) {
|
||||
T::should_exist(($($val),*, ))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
match new_state {
|
||||
Some(value) => {
|
||||
if current_state.is_none() {
|
||||
internal_apply_state_transition(event, commands, current_state, Some(value));
|
||||
let current_state = current_state_res.as_ref().map(|s| s.get()).cloned();
|
||||
|
||||
let should_exist = match parent_changed {
|
||||
true => {
|
||||
if let ($(Some($val)),*,) = ($($param::convert_to_usable_state($val.as_deref())),*,) {
|
||||
T::should_exist(($($val),*, ))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
false => current_state.clone(),
|
||||
};
|
||||
|
||||
match should_exist {
|
||||
Some(initial_state) => {
|
||||
let new_state = match (current_state, next_state) {
|
||||
(_, Some(next_state)) => next_state,
|
||||
(Some(current_state), None) => current_state,
|
||||
(None, None) => initial_state,
|
||||
};
|
||||
internal_apply_state_transition(
|
||||
event,
|
||||
commands,
|
||||
current_state_res,
|
||||
Some(new_state),
|
||||
);
|
||||
}
|
||||
None => {
|
||||
internal_apply_state_transition(event, commands, current_state, None);
|
||||
},
|
||||
internal_apply_state_transition(event, commands, current_state_res, None);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
schedule
|
||||
.add_systems(system.in_set(ApplyStateTransition::<T>::apply()))
|
||||
.add_systems(apply_state_transition::<T>.in_set(StateTransitionSteps::ManualTransitions))
|
||||
.add_systems(apply_state_transition.in_set(ApplyStateTransition::<T>::apply()))
|
||||
.add_systems(last_transition::<T>.pipe(run_enter::<T>).in_set(StateTransitionSteps::EnterSchedules))
|
||||
.add_systems(last_transition::<T>.pipe(run_exit::<T>).in_set(StateTransitionSteps::ExitSchedules))
|
||||
.add_systems(last_transition::<T>.pipe(run_transition::<T>).in_set(StateTransitionSteps::TransitionSchedules))
|
||||
|
|
|
@ -96,13 +96,11 @@ pub use bevy_state_macros::SubStates;
|
|||
/// ```
|
||||
///
|
||||
/// However, you can also manually implement them. If you do so, you'll also need to manually implement the `States` & `FreelyMutableState` traits.
|
||||
/// Unlike the derive, this does not require an implementation of [`Default`], since you are providing the `exists` function
|
||||
/// directly.
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # use bevy_state::prelude::*;
|
||||
/// # use bevy_state::state::FreelyMutableState;
|
||||
/// # use bevy_state::state::{FreelyMutableState, NextState};
|
||||
///
|
||||
/// /// Computed States require some state to derive from
|
||||
/// #[derive(States, Clone, PartialEq, Eq, Hash, Debug, Default)]
|
||||
|
@ -127,11 +125,10 @@ pub use bevy_state_macros::SubStates;
|
|||
/// /// We then define the compute function, which takes in the [`Self::SourceStates`]
|
||||
/// fn should_exist(sources: Option<AppState>) -> Option<Self> {
|
||||
/// match sources {
|
||||
/// /// When we are in game, so we want a GamePhase state to exist, and the default is
|
||||
/// /// GamePhase::Setup
|
||||
/// Some(AppState::InGame { .. }) => Some(GamePhase::Setup),
|
||||
/// /// Otherwise, we don't want the `State<GamePhase>` resource to exist,
|
||||
/// /// so we return None.
|
||||
/// /// When we are in game, we want a GamePhase state to exist.
|
||||
/// /// We can set the initial value here or overwrite it through [`NextState`].
|
||||
/// Some(AppState::InGame { .. }) => Some(Self::Setup),
|
||||
/// /// If we don't want the `State<GamePhase>` resource to exist we return [`None`].
|
||||
/// _ => None
|
||||
/// }
|
||||
/// }
|
||||
|
@ -154,8 +151,14 @@ pub trait SubStates: States + FreelyMutableState {
|
|||
/// This function gets called whenever one of the [`SourceStates`](Self::SourceStates) changes.
|
||||
/// The result is used to determine the existence of [`State<Self>`](crate::state::State).
|
||||
///
|
||||
/// If the result is [`None`], the [`State<Self>`](crate::state::State) resource will be removed from the world, otherwise
|
||||
/// if the [`State<Self>`](crate::state::State) resource doesn't exist - it will be created with the [`Some`] value.
|
||||
/// If the result is [`None`], the [`State<Self>`](crate::state::State) resource will be removed from the world,
|
||||
/// otherwise if the [`State<Self>`](crate::state::State) resource doesn't exist
|
||||
/// it will be created from the returned [`Some`] as the initial state.
|
||||
///
|
||||
/// Value within [`Some`] is ignored if the state already exists in the world
|
||||
/// and only symbolises that the state should still exist.
|
||||
///
|
||||
/// Initial value can also be overwritten by [`NextState`](crate::state::NextState).
|
||||
fn should_exist(sources: Self::SourceStates) -> Option<Self>;
|
||||
|
||||
/// This function sets up systems that compute the state whenever one of the [`SourceStates`](Self::SourceStates)
|
||||
|
|
|
@ -9,11 +9,7 @@ use bevy_ecs::{
|
|||
world::World,
|
||||
};
|
||||
|
||||
use super::{
|
||||
freely_mutable_state::FreelyMutableState,
|
||||
resources::{NextState, State},
|
||||
states::States,
|
||||
};
|
||||
use super::{resources::State, states::States};
|
||||
|
||||
/// The label of a [`Schedule`] that runs whenever [`State<S>`]
|
||||
/// enters this state.
|
||||
|
@ -57,7 +53,7 @@ pub struct StateTransitionEvent<S: States> {
|
|||
/// These system sets are run sequentially, in the order of the enum variants.
|
||||
#[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) enum StateTransitionSteps {
|
||||
ManualTransitions,
|
||||
RootTransitions,
|
||||
DependentTransitions,
|
||||
ExitSchedules,
|
||||
TransitionSchedules,
|
||||
|
@ -142,7 +138,7 @@ pub fn setup_state_transitions_in_world(
|
|||
let mut schedule = Schedule::new(StateTransition);
|
||||
schedule.configure_sets(
|
||||
(
|
||||
StateTransitionSteps::ManualTransitions,
|
||||
StateTransitionSteps::RootTransitions,
|
||||
StateTransitionSteps::DependentTransitions,
|
||||
StateTransitionSteps::ExitSchedules,
|
||||
StateTransitionSteps::TransitionSchedules,
|
||||
|
@ -159,49 +155,6 @@ pub fn setup_state_transitions_in_world(
|
|||
}
|
||||
}
|
||||
|
||||
/// If a new state is queued in [`NextState<S>`], this system
|
||||
/// takes the new state value from [`NextState<S>`] and updates [`State<S>`], as well as
|
||||
/// sending the relevant [`StateTransitionEvent`].
|
||||
///
|
||||
/// If the [`State<S>`] resource does not exist, it does nothing. Removing or adding states
|
||||
/// should be done at App creation or at your own risk.
|
||||
///
|
||||
/// For [`SubStates`](crate::state::SubStates) - it only applies the state if the `SubState` currently exists. Otherwise, it is wiped.
|
||||
/// When a `SubState` is re-created, it will use the result of it's `should_exist` method.
|
||||
pub fn apply_state_transition<S: FreelyMutableState>(
|
||||
event: EventWriter<StateTransitionEvent<S>>,
|
||||
commands: Commands,
|
||||
current_state: Option<ResMut<State<S>>>,
|
||||
next_state: Option<ResMut<NextState<S>>>,
|
||||
) {
|
||||
// We want to check if the State and NextState resources exist
|
||||
let Some(mut next_state_resource) = next_state else {
|
||||
return;
|
||||
};
|
||||
|
||||
match next_state_resource.as_ref() {
|
||||
NextState::Pending(new_state) => {
|
||||
if let Some(current_state) = current_state {
|
||||
if new_state != current_state.get() {
|
||||
let new_state = new_state.clone();
|
||||
internal_apply_state_transition(
|
||||
event,
|
||||
commands,
|
||||
Some(current_state),
|
||||
Some(new_state),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
NextState::Unchanged => {
|
||||
// This is the default value, so we don't need to re-insert the resource
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
*next_state_resource.as_mut() = NextState::<S>::Unchanged;
|
||||
}
|
||||
|
||||
/// Returns the latest state transition event of type `S`, if any are available.
|
||||
pub fn last_transition<S: States>(
|
||||
mut reader: EventReader<StateTransitionEvent<S>>,
|
||||
|
|
Loading…
Reference in a new issue