use bevy_ecs::{
message::{MessageReader, MessageWriter},
schedule::{IntoScheduleConfigs, Schedule},
system::{Commands, IntoSystem, Res, ResMut},
};
use variadics_please::all_tuples;
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,
EnterSchedules, ExitSchedules, NextState, State, StateTransitionEvent, StateTransitionSystems,
States, TransitionSchedules,
};
mod sealed {
pub trait StateSetSealed {}
}
pub trait StateSet: StateSetSealed {
const SET_DEPENDENCY_DEPTH: usize;
fn register_computed_state_systems_in_schedule<T: ComputedStates<SourceStates = Self>>(
schedule: &mut Schedule,
);
fn register_sub_state_systems_in_schedule<T: SubStates<SourceStates = Self>>(
schedule: &mut Schedule,
);
}
trait InnerStateSet: Sized {
type RawState: States;
const DEPENDENCY_DEPTH: usize;
fn convert_to_usable_state(wrapped: Option<&State<Self::RawState>>) -> Option<Self>;
}
impl<S: States> InnerStateSet for S {
type RawState = Self;
const DEPENDENCY_DEPTH: usize = S::DEPENDENCY_DEPTH;
fn convert_to_usable_state(wrapped: Option<&State<Self::RawState>>) -> Option<Self> {
wrapped.map(|v| v.0.clone())
}
}
impl<S: States> InnerStateSet for Option<S> {
type RawState = S;
const DEPENDENCY_DEPTH: usize = S::DEPENDENCY_DEPTH;
fn convert_to_usable_state(wrapped: Option<&State<Self::RawState>>) -> Option<Self> {
Some(wrapped.map(|v| v.0.clone()))
}
}
impl<S: InnerStateSet> StateSetSealed for S {}
impl<S: InnerStateSet> StateSet for S {
const SET_DEPENDENCY_DEPTH: usize = S::DEPENDENCY_DEPTH;
fn register_computed_state_systems_in_schedule<T: ComputedStates<SourceStates = Self>>(
schedule: &mut Schedule,
) {
let apply_state_transition =
|mut parent_changed: MessageReader<StateTransitionEvent<S::RawState>>,
event: MessageWriter<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
};
internal_apply_state_transition(
event,
commands,
current_state,
new_state,
T::ALLOW_SAME_STATE_TRANSITIONS,
);
};
schedule.configure_sets((
ApplyStateTransition::<T>::default()
.in_set(StateTransitionSystems::DependentTransitions)
.after(ApplyStateTransition::<S::RawState>::default()),
ExitSchedules::<T>::default()
.in_set(StateTransitionSystems::ExitSchedules)
.before(ExitSchedules::<S::RawState>::default()),
TransitionSchedules::<T>::default().in_set(StateTransitionSystems::TransitionSchedules),
EnterSchedules::<T>::default()
.in_set(StateTransitionSystems::EnterSchedules)
.after(EnterSchedules::<S::RawState>::default()),
));
schedule
.add_systems(apply_state_transition.in_set(ApplyStateTransition::<T>::default()))
.add_systems(
last_transition::<T>
.pipe(run_exit::<T>)
.in_set(ExitSchedules::<T>::default()),
)
.add_systems(
last_transition::<T>
.pipe(run_transition::<T>)
.in_set(TransitionSchedules::<T>::default()),
)
.add_systems(
last_transition::<T>
.pipe(run_enter::<T>)
.in_set(EnterSchedules::<T>::default()),
);
}
fn register_sub_state_systems_in_schedule<T: SubStates<SourceStates = Self>>(
schedule: &mut Schedule,
) {
let apply_state_transition =
|mut parent_changed: MessageReader<StateTransitionEvent<S::RawState>>,
event: MessageWriter<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 initial_state = if parent_changed {
if let Some(state_set) = S::convert_to_usable_state(state_set.as_deref()) {
T::should_exist(state_set)
} else {
None
}
} else {
current_state.clone()
};
let same_state_enforced = next_state
.as_ref()
.map(|(_, same_state_enforced)| same_state_enforced)
.cloned()
.unwrap_or_default();
let new_state = initial_state.map(|x| {
next_state
.map(|(next, _)| next)
.or(current_state)
.unwrap_or(x)
});
internal_apply_state_transition(
event,
commands,
current_state_res,
new_state,
same_state_enforced,
);
};
schedule.configure_sets((
ApplyStateTransition::<T>::default()
.in_set(StateTransitionSystems::DependentTransitions)
.after(ApplyStateTransition::<S::RawState>::default()),
ExitSchedules::<T>::default()
.in_set(StateTransitionSystems::ExitSchedules)
.before(ExitSchedules::<S::RawState>::default()),
TransitionSchedules::<T>::default().in_set(StateTransitionSystems::TransitionSchedules),
EnterSchedules::<T>::default()
.in_set(StateTransitionSystems::EnterSchedules)
.after(EnterSchedules::<S::RawState>::default()),
));
schedule
.add_systems(apply_state_transition.in_set(ApplyStateTransition::<T>::default()))
.add_systems(
last_transition::<T>
.pipe(run_exit::<T>)
.in_set(ExitSchedules::<T>::default()),
)
.add_systems(
last_transition::<T>
.pipe(run_transition::<T>)
.in_set(TransitionSchedules::<T>::default()),
)
.add_systems(
last_transition::<T>
.pipe(run_enter::<T>)
.in_set(EnterSchedules::<T>::default()),
);
}
}
macro_rules! impl_state_set_sealed_tuples {
($(#[$meta:meta])* $(($param: ident, $val: ident, $evt: ident)), *) => {
$(#[$meta])*
impl<$($param: InnerStateSet),*> StateSetSealed for ($($param,)*) {}
$(#[$meta])*
impl<$($param: InnerStateSet),*> StateSet for ($($param,)*) {
const SET_DEPENDENCY_DEPTH : usize = $($param::DEPENDENCY_DEPTH +)* 0;
fn register_computed_state_systems_in_schedule<T: ComputedStates<SourceStates = Self>>(
schedule: &mut Schedule,
) {
let apply_state_transition =
|($(mut $evt),*,): ($(MessageReader<StateTransitionEvent<$param::RawState>>),*,),
message: MessageWriter<StateTransitionEvent<T>>,
commands: Commands,
current_state: Option<ResMut<State<T>>>,
($($val),*,): ($(Option<Res<State<$param::RawState>>>),*,)| {
if ($($evt.is_empty())&&*) {
return;
}
$($evt.clear();)*
let new_state = if let ($(Some($val)),*,) = ($($param::convert_to_usable_state($val.as_deref())),*,) {
T::compute(($($val),*, ))
} else {
None
};
internal_apply_state_transition(message, commands, current_state, new_state, false);
};
schedule.configure_sets((
ApplyStateTransition::<T>::default()
.in_set(StateTransitionSystems::DependentTransitions)
$(.after(ApplyStateTransition::<$param::RawState>::default()))*,
ExitSchedules::<T>::default()
.in_set(StateTransitionSystems::ExitSchedules)
$(.before(ExitSchedules::<$param::RawState>::default()))*,
TransitionSchedules::<T>::default()
.in_set(StateTransitionSystems::TransitionSchedules),
EnterSchedules::<T>::default()
.in_set(StateTransitionSystems::EnterSchedules)
$(.after(EnterSchedules::<$param::RawState>::default()))*,
));
schedule
.add_systems(apply_state_transition.in_set(ApplyStateTransition::<T>::default()))
.add_systems(last_transition::<T>.pipe(run_exit::<T>).in_set(ExitSchedules::<T>::default()))
.add_systems(last_transition::<T>.pipe(run_transition::<T>).in_set(TransitionSchedules::<T>::default()))
.add_systems(last_transition::<T>.pipe(run_enter::<T>).in_set(EnterSchedules::<T>::default()));
}
fn register_sub_state_systems_in_schedule<T: SubStates<SourceStates = Self>>(
schedule: &mut Schedule,
) {
let apply_state_transition =
|($(mut $evt),*,): ($(MessageReader<StateTransitionEvent<$param::RawState>>),*,),
message: MessageWriter<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;
}
let current_state = current_state_res.as_ref().map(|s| s.get()).cloned();
let initial_state = if parent_changed {
if let ($(Some($val)),*,) = ($($param::convert_to_usable_state($val.as_deref())),*,) {
T::should_exist(($($val),*, ))
} else {
None
}
} else {
current_state.clone()
};
let same_state_enforced = next_state
.as_ref()
.map(|(_, same_state_enforced)| same_state_enforced)
.cloned()
.unwrap_or_default();
let new_state = initial_state.map(|x| {
next_state
.map(|(next, _)| next)
.or(current_state)
.unwrap_or(x)
});
internal_apply_state_transition(message, commands, current_state_res, new_state, same_state_enforced);
};
schedule.configure_sets((
ApplyStateTransition::<T>::default()
.in_set(StateTransitionSystems::DependentTransitions)
$(.after(ApplyStateTransition::<$param::RawState>::default()))*,
ExitSchedules::<T>::default()
.in_set(StateTransitionSystems::ExitSchedules)
$(.before(ExitSchedules::<$param::RawState>::default()))*,
TransitionSchedules::<T>::default()
.in_set(StateTransitionSystems::TransitionSchedules),
EnterSchedules::<T>::default()
.in_set(StateTransitionSystems::EnterSchedules)
$(.after(EnterSchedules::<$param::RawState>::default()))*,
));
schedule
.add_systems(apply_state_transition.in_set(ApplyStateTransition::<T>::default()))
.add_systems(last_transition::<T>.pipe(run_exit::<T>).in_set(ExitSchedules::<T>::default()))
.add_systems(last_transition::<T>.pipe(run_transition::<T>).in_set(TransitionSchedules::<T>::default()))
.add_systems(last_transition::<T>.pipe(run_enter::<T>).in_set(EnterSchedules::<T>::default()));
}
}
};
}
all_tuples!(
#[doc(fake_variadic)]
impl_state_set_sealed_tuples,
1,
15,
S,
s,
ereader
);