use bevy_ecs::schedule::{Stage, StateData, IntoSystemDescriptor, SystemSet, SystemStage};
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>>,
default: T,
}
impl<T: StateData> StateTransitionStage<T> {
pub fn new(default: T) -> Self {
Self {
enter_stages: Default::default(),
exit_stages: Default::default(),
default,
}
}
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
}
pub fn add_enter_system<Params>(&mut self, state: T, system: impl IntoSystemDescriptor<Params>) {
if !self.enter_stages.contains_key(&state) {
self.set_enter_stage(state.clone(), SystemStage::parallel());
}
let stage = self.enter_stages.get_mut(&state)
.expect("No enter stage for state.")
.downcast_mut::<SystemStage>()
.expect("State enter stage is not a SystemStage");
stage.add_system(system);
}
pub fn add_exit_system<Params>(&mut self, state: T, system: impl IntoSystemDescriptor<Params>) {
if !self.exit_stages.contains_key(&state) {
self.set_exit_stage(state.clone(), SystemStage::parallel());
}
let stage = self.exit_stages.get_mut(&state)
.expect("No exit stage for state.")
.downcast_mut::<SystemStage>()
.expect("State exit stage is not a SystemStage");
stage.add_system(system);
}
pub fn add_enter_system_set(&mut self, state: T, system_set: SystemSet) {
if !self.enter_stages.contains_key(&state) {
self.set_enter_stage(state.clone(), SystemStage::parallel());
}
let stage = self.enter_stages.get_mut(&state)
.expect("No enter stage for state.")
.downcast_mut::<SystemStage>()
.expect("State enter stage is not a SystemStage");
stage.add_system_set(system_set);
}
pub fn add_exit_system_set(&mut self, state: T, system_set: SystemSet) {
if !self.exit_stages.contains_key(&state) {
self.set_exit_stage(state.clone(), SystemStage::parallel());
}
let stage = self.exit_stages.get_mut(&state)
.expect("No exit stage for state.")
.downcast_mut::<SystemStage>()
.expect("State exit stage is not a SystemStage");
stage.add_system_set(system_set);
}
pub fn with_enter_system<Params>(mut self, state: T, system: impl IntoSystemDescriptor<Params>) -> Self {
self.add_enter_system(state, system);
self
}
pub fn with_exit_system<Params>(mut self, state: T, system: impl IntoSystemDescriptor<Params>) -> Self {
self.add_exit_system(state, system);
self
}
pub fn with_enter_system_set(mut self, state: T, system_set: SystemSet) -> Self {
self.add_enter_system_set(state, system_set);
self
}
pub fn with_exit_system_set(mut self, state: T, system_set: SystemSet) -> Self {
self.add_exit_system_set(state, system_set);
self
}
}
impl<T: StateData> Stage for StateTransitionStage<T> {
fn run(&mut self, world: &mut World) {
loop {
let current = if let Some(res) = world.get_resource::<CurrentState<T>>() {
res.0.clone()
} else {
world.insert_resource(CurrentState(self.default.clone()));
if let Some(stage) = self.enter_stages.get_mut(&self.default) {
stage.run(world);
}
world
.get_resource_or_insert_with(|| CurrentState(self.default.clone()))
.0
.clone()
};
let next = world.remove_resource::<NextState<T>>();
if let Some(NextState(next)) = next {
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;
}
}
}
}
#[cfg(feature = "app")]
pub mod app {
use std::any::TypeId;
use bevy_ecs::schedule::{StageLabel, Stage, StateData, IntoSystemDescriptor, SystemSet};
use bevy_app::{App, CoreStage};
use super::StateTransitionStage;
#[derive(Debug, Clone, PartialEq, Eq, Hash, StageLabel)]
pub struct StateTransitionStageLabel(TypeId, String);
impl StateTransitionStageLabel {
pub fn from_type<T: StateData>() -> Self {
use std::any::type_name;
StateTransitionStageLabel(TypeId::of::<T>(), type_name::<T>().to_owned())
}
}
pub trait AppLooplessStateExt {
fn add_loopless_state<T: StateData>(&mut self, init: T) -> &mut App;
fn add_loopless_state_after_stage<T: StateData>(&mut self, stage: impl StageLabel, init: T) -> &mut App;
fn add_loopless_state_before_stage<T: StateData>(&mut self, stage: impl StageLabel, init: T) -> &mut App;
fn add_enter_system<T: StateData, Params>(&mut self, state: T, system: impl IntoSystemDescriptor<Params>) -> &mut App;
fn add_exit_system<T: StateData, Params>(&mut self, state: T, system: impl IntoSystemDescriptor<Params>) -> &mut App;
fn add_enter_system_set<T: StateData>(&mut self, state: T, system_set: SystemSet) -> &mut App;
fn add_exit_system_set<T: StateData>(&mut self, state: T, system_set: SystemSet) -> &mut App;
fn set_enter_stage<T: StateData>(&mut self, state: T, system: impl Stage) -> &mut App;
fn set_exit_stage<T: StateData>(&mut self, state: T, system: impl Stage) -> &mut App;
}
impl AppLooplessStateExt for App {
fn add_loopless_state<T: StateData>(&mut self, init: T) -> &mut App {
self.add_loopless_state_before_stage(CoreStage::Update, init)
}
fn add_loopless_state_after_stage<T: StateData>(&mut self, stage: impl StageLabel, init: T) -> &mut App {
self.add_stage_after(
stage,
StateTransitionStageLabel::from_type::<T>(),
StateTransitionStage::new(init)
)
}
fn add_loopless_state_before_stage<T: StateData>(&mut self, stage: impl StageLabel, init: T) -> &mut App {
self.add_stage_before(
stage,
StateTransitionStageLabel::from_type::<T>(),
StateTransitionStage::new(init)
)
}
fn add_enter_system<T: StateData, Params>(&mut self, state: T, system: impl IntoSystemDescriptor<Params>) -> &mut App {
let stage = self.schedule.get_stage_mut::<StateTransitionStage<T>>(&StateTransitionStageLabel::from_type::<T>())
.expect("State Transiton Stage not found (assuming auto-added label)");
stage.add_enter_system(state, system);
self
}
fn add_exit_system<T: StateData, Params>(&mut self, state: T, system: impl IntoSystemDescriptor<Params>) -> &mut App {
let stage = self.schedule.get_stage_mut::<StateTransitionStage<T>>(&StateTransitionStageLabel::from_type::<T>())
.expect("State Transiton Stage not found (assuming auto-added label)");
stage.add_exit_system(state, system);
self
}
fn add_enter_system_set<T: StateData>(&mut self, state: T, system_set: SystemSet) -> &mut App {
let stage = self.schedule.get_stage_mut::<StateTransitionStage<T>>(&StateTransitionStageLabel::from_type::<T>())
.expect("State Transiton Stage not found (assuming auto-added label)");
stage.add_enter_system_set(state, system_set);
self
}
fn add_exit_system_set<T: StateData>(&mut self, state: T, system_set: SystemSet) -> &mut App {
let stage = self.schedule.get_stage_mut::<StateTransitionStage<T>>(&StateTransitionStageLabel::from_type::<T>())
.expect("State Transiton Stage not found (assuming auto-added label)");
stage.add_exit_system_set(state, system_set);
self
}
fn set_enter_stage<T: StateData>(&mut self, state: T, enter_stage: impl Stage) -> &mut App {
let stage = self.schedule.get_stage_mut::<StateTransitionStage<T>>(&StateTransitionStageLabel::from_type::<T>())
.expect("State Transiton Stage not found (assuming auto-added label)");
stage.set_enter_stage(state, enter_stage);
self
}
fn set_exit_stage<T: StateData>(&mut self, state: T, exit_stage: impl Stage) -> &mut App {
let stage = self.schedule.get_stage_mut::<StateTransitionStage<T>>(&StateTransitionStageLabel::from_type::<T>())
.expect("State Transiton Stage not found (assuming auto-added label)");
stage.set_exit_stage(state, exit_stage);
self
}
}
}