use core::{marker::PhantomData, mem};
use bevy_ecs::{
message::{Message, MessageReader, MessageWriter},
schedule::{IntoScheduleConfigs, Schedule, ScheduleLabel, Schedules, SystemSet},
system::{Commands, In, ResMut},
world::World,
};
use super::{resources::State, states::States};
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
pub struct OnEnter<S: States>(pub S);
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
pub struct OnExit<S: States>(pub S);
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
pub struct OnTransition<S: States> {
pub exited: S,
pub entered: S,
}
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
pub struct StateTransition;
#[derive(Debug, Copy, Clone, PartialEq, Eq, Message)]
pub struct StateTransitionEvent<S: States> {
pub exited: Option<S>,
pub entered: Option<S>,
pub allow_same_state_transitions: bool,
}
#[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)]
pub enum StateTransitionSystems {
DependentTransitions,
ExitSchedules,
TransitionSchedules,
EnterSchedules,
}
#[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)]
pub struct ExitSchedules<S: States>(PhantomData<S>);
impl<S: States> Default for ExitSchedules<S> {
fn default() -> Self {
Self(Default::default())
}
}
#[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)]
pub struct TransitionSchedules<S: States>(PhantomData<S>);
impl<S: States> Default for TransitionSchedules<S> {
fn default() -> Self {
Self(Default::default())
}
}
#[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)]
pub struct EnterSchedules<S: States>(PhantomData<S>);
impl<S: States> Default for EnterSchedules<S> {
fn default() -> Self {
Self(Default::default())
}
}
#[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)]
pub(crate) struct ApplyStateTransition<S: States>(PhantomData<S>);
impl<S: States> Default for ApplyStateTransition<S> {
fn default() -> Self {
Self(Default::default())
}
}
pub(crate) fn internal_apply_state_transition<S: States>(
mut event: MessageWriter<StateTransitionEvent<S>>,
mut commands: Commands,
current_state: Option<ResMut<State<S>>>,
new_state: Option<S>,
allow_same_state_transitions: bool,
) {
match new_state {
Some(entered) => {
match current_state {
Some(mut state_resource) => {
let exited = match *state_resource == entered {
true => entered.clone(),
false => mem::replace(&mut state_resource.0, entered.clone()),
};
event.write(StateTransitionEvent {
exited: Some(exited.clone()),
entered: Some(entered.clone()),
allow_same_state_transitions,
});
}
None => {
commands.insert_resource(State(entered.clone()));
event.write(StateTransitionEvent {
exited: None,
entered: Some(entered.clone()),
allow_same_state_transitions,
});
}
};
}
None => {
if let Some(resource) = current_state {
commands.remove_resource::<State<S>>();
event.write(StateTransitionEvent {
exited: Some(resource.get().clone()),
entered: None,
allow_same_state_transitions,
});
}
}
}
}
pub fn setup_state_transitions_in_world(world: &mut World) {
let mut schedules = world.get_resource_or_init::<Schedules>();
if schedules.contains(StateTransition) {
return;
}
let mut schedule = Schedule::new(StateTransition);
schedule.configure_sets(
(
StateTransitionSystems::DependentTransitions,
StateTransitionSystems::ExitSchedules,
StateTransitionSystems::TransitionSchedules,
StateTransitionSystems::EnterSchedules,
)
.chain(),
);
schedules.insert(schedule);
}
pub fn last_transition<S: States>(
mut reader: MessageReader<StateTransitionEvent<S>>,
) -> Option<StateTransitionEvent<S>> {
reader.read().last().cloned()
}
pub(crate) fn run_enter<S: States>(
transition: In<Option<StateTransitionEvent<S>>>,
world: &mut World,
) {
let Some(transition) = transition.0 else {
return;
};
if transition.entered == transition.exited && !transition.allow_same_state_transitions {
return;
}
let Some(entered) = transition.entered else {
return;
};
let _ = world.try_run_schedule(OnEnter(entered));
}
pub(crate) fn run_exit<S: States>(
transition: In<Option<StateTransitionEvent<S>>>,
world: &mut World,
) {
let Some(transition) = transition.0 else {
return;
};
if transition.entered == transition.exited && !transition.allow_same_state_transitions {
return;
}
let Some(exited) = transition.exited else {
return;
};
let _ = world.try_run_schedule(OnExit(exited));
}
pub(crate) fn run_transition<S: States>(
transition: In<Option<StateTransitionEvent<S>>>,
world: &mut World,
) {
let Some(transition) = transition.0 else {
return;
};
let Some(exited) = transition.exited else {
return;
};
let Some(entered) = transition.entered else {
return;
};
let _ = world.try_run_schedule(OnTransition { exited, entered });
}