use crate::app::{App, Stage, game_scope};
use crate::ecs::world::World;
type StateHook = Box<dyn FnMut(&mut World) + Send + Sync>;
pub struct AppState<S: Copy + PartialEq + Send + Sync + 'static> {
current: S,
next: Option<S>,
entered: bool,
enter: Vec<(S, StateHook)>,
exit: Vec<(S, StateHook)>,
}
pub fn current_state<S: Copy + PartialEq + Send + Sync + 'static>(world: &World) -> S {
world
.ecs
.resource::<AppState<S>>()
.unwrap_or_else(|| {
panic!(
"no state of type {} was inserted, call insert_state in a plugin",
std::any::type_name::<S>()
)
})
.current
}
pub fn in_state<S: Copy + PartialEq + Send + Sync + 'static>(world: &World, state: S) -> bool {
current_state::<S>(world) == state
}
pub trait GateableSystem<Marker>: Send + 'static {
fn run(&mut self, world: &mut World);
}
impl<F> GateableSystem<crate::app::WorldSystemMarker> for F
where
F: FnMut(&mut World) + Send + 'static,
{
fn run(&mut self, world: &mut World) {
self(world);
}
}
impl<T, F> GateableSystem<crate::app::GameSystemMarker<T>> for F
where
T: Send + Sync + 'static,
F: FnMut(&mut T, &mut World) + Send + 'static,
{
fn run(&mut self, world: &mut World) {
game_scope(world, |state: &mut T, world| self(state, world));
}
}
pub fn while_in<S: Copy + PartialEq + Send + Sync + 'static, Marker>(
state: S,
mut system: impl GateableSystem<Marker>,
) -> impl FnMut(&mut World) + Send + 'static {
move |world: &mut World| {
if in_state(world, state) {
system.run(world);
}
}
}
pub fn next_state<S: Copy + PartialEq + Send + Sync + 'static>(world: &mut World, state: S) {
world
.ecs
.resource_mut::<AppState<S>>()
.unwrap_or_else(|| {
panic!(
"no state of type {} was inserted, call insert_state in a plugin",
std::any::type_name::<S>()
)
})
.next = Some(state);
}
fn apply_state_transitions<S: Copy + PartialEq + Send + Sync + 'static>(world: &mut World) {
loop {
let Some(state) = world.ecs.resource_mut::<AppState<S>>() else {
return;
};
let (previous, next) = if !state.entered {
state.entered = true;
(None, state.current)
} else {
let Some(next) = state.next.take() else {
return;
};
if next == state.current {
continue;
}
let previous = state.current;
state.current = next;
(Some(previous), next)
};
let mut exit = std::mem::take(&mut state.exit);
let mut enter = std::mem::take(&mut state.enter);
if let Some(previous) = previous {
for (target, hook) in &mut exit {
if *target == previous {
hook(world);
}
}
}
for (target, hook) in &mut enter {
if *target == next {
hook(world);
}
}
let Some(state) = world.ecs.resource_mut::<AppState<S>>() else {
return;
};
state.exit = exit;
state.enter = enter;
}
}
impl App {
pub fn insert_state<S: Copy + PartialEq + Send + Sync + 'static>(
&mut self,
initial: S,
) -> &mut Self {
if self.world.ecs.resource::<AppState<S>>().is_some() {
panic!(
"a state of type {} was already inserted",
std::any::type_name::<S>()
);
}
self.world.ecs.insert_resource(AppState {
current: initial,
next: None,
entered: false,
enter: Vec::new(),
exit: Vec::new(),
});
self.add_system(Stage::First, apply_state_transitions::<S>);
self
}
pub fn on_enter<S: Copy + PartialEq + Send + Sync + 'static>(
&mut self,
state: S,
system: impl FnMut(&mut World) + Send + Sync + 'static,
) -> &mut Self {
self.state_holder::<S>()
.enter
.push((state, Box::new(system)));
self
}
pub fn on_exit<S: Copy + PartialEq + Send + Sync + 'static>(
&mut self,
state: S,
system: impl FnMut(&mut World) + Send + Sync + 'static,
) -> &mut Self {
self.state_holder::<S>()
.exit
.push((state, Box::new(system)));
self
}
fn state_holder<S: Copy + PartialEq + Send + Sync + 'static>(&mut self) -> &mut AppState<S> {
self.world
.ecs
.resource_mut::<AppState<S>>()
.unwrap_or_else(|| {
panic!(
"no state of type {} was inserted, call insert_state first",
std::any::type_name::<S>()
)
})
}
}