use bevy_ecs::schedule::{Stage, StateData};
use bevy_ecs::system::{lifetimeless::SRes, SystemState};
use bevy_ecs::world::World;
use bevy_utils::HashMap;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CurrentState<T>(pub T);
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NextState<T>(pub T);
pub struct StateTransitionStage<T: StateData> {
enter_stages: HashMap<T, Box<dyn Stage>>,
exit_stages: HashMap<T, Box<dyn Stage>>,
init_state: T,
next_access: Option<SystemState<Option<SRes<NextState<T>>>>>,
}
impl<T: StateData> StateTransitionStage<T> {
pub fn new(init_state: T) -> Self {
Self {
enter_stages: Default::default(),
exit_stages: Default::default(),
init_state,
next_access: None,
}
}
pub fn set_enter_stage<S: Stage>(&mut self, state: T, stage: S) {
self.enter_stages.insert(state, Box::new(stage));
}
pub fn set_exit_stage<S: Stage>(&mut self, state: T, stage: S) {
self.exit_stages.insert(state, Box::new(stage));
}
pub fn with_enter_stage<S: Stage>(mut self, state: T, stage: S) -> Self {
self.set_enter_stage(state, stage);
self
}
pub fn with_exit_stage<S: Stage>(mut self, state: T, stage: S) -> Self {
self.set_exit_stage(state, stage);
self
}
}
impl<T: StateData> Stage for StateTransitionStage<T> {
fn run(&mut self, world: &mut World) {
if let Some(mut next_access) = self.next_access.take() {
loop {
let current = world.get_resource_or_insert_with(|| CurrentState(self.init_state.clone())).0.clone();
self.init_state = current.clone();
if let Some((next, changed)) = next_access.get(world).map(|x| (x.0.clone(), x.is_changed())) {
if changed {
if let Some(stage) = self.exit_stages.get_mut(¤t) {
stage.run(world);
}
world.insert_resource(CurrentState(next.clone()));
if let Some(stage) = self.enter_stages.get_mut(&next) {
stage.run(world);
}
} else {
break;
}
} else {
world.insert_resource(NextState(current.clone()));
break;
}
}
self.next_access = Some(next_access);
} else {
world.insert_resource(CurrentState(self.init_state.clone()));
world.insert_resource(NextState(self.init_state.clone()));
self.next_access = Some(SystemState::new(world));
if let Some(stage) = self.enter_stages.get_mut(&self.init_state) {
stage.run(world);
}
}
}
}