use serde::{Deserialize, Serialize};
pub trait GameState: Send {
fn name(&self) -> &str;
fn on_enter(&mut self, _world: &mut crate::World) {}
fn on_exit(&mut self, _world: &mut crate::World) {}
fn update(&mut self, _world: &mut crate::World) {}
}
pub struct StateMachine {
states: Vec<Box<dyn GameState>>,
current: usize,
pending_transition: Option<usize>,
}
impl StateMachine {
pub fn new(initial: Box<dyn GameState>) -> Self {
Self {
states: vec![initial],
current: 0,
pending_transition: None,
}
}
pub fn add_state(&mut self, state: Box<dyn GameState>) -> usize {
let idx = self.states.len();
self.states.push(state);
idx
}
pub fn transition_to(&mut self, index: usize) {
if index < self.states.len() {
self.pending_transition = Some(index);
}
}
pub fn apply_transition(&mut self, world: &mut crate::World) {
if let Some(next) = self.pending_transition.take() {
self.states[self.current].on_exit(world);
self.current = next;
self.states[self.current].on_enter(world);
}
}
pub fn update(&mut self, world: &mut crate::World) {
self.states[self.current].update(world);
}
#[must_use]
pub fn current_name(&self) -> &str {
self.states[self.current].name()
}
#[must_use]
#[inline]
pub fn current_index(&self) -> usize {
self.current
}
#[must_use]
#[inline]
pub fn state_count(&self) -> usize {
self.states.len()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NamedState(pub String);
impl GameState for NamedState {
fn name(&self) -> &str {
&self.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn state_machine_basic() {
let mut sm = StateMachine::new(Box::new(NamedState("menu".into())));
let playing = sm.add_state(Box::new(NamedState("playing".into())));
let paused = sm.add_state(Box::new(NamedState("paused".into())));
assert_eq!(sm.current_name(), "menu");
assert_eq!(sm.state_count(), 3);
let mut world = crate::World::new();
sm.transition_to(playing);
sm.apply_transition(&mut world);
assert_eq!(sm.current_name(), "playing");
sm.transition_to(paused);
sm.apply_transition(&mut world);
assert_eq!(sm.current_name(), "paused");
}
#[test]
fn state_machine_no_transition() {
let mut sm = StateMachine::new(Box::new(NamedState("idle".into())));
let mut world = crate::World::new();
sm.apply_transition(&mut world); assert_eq!(sm.current_name(), "idle");
}
#[test]
fn state_machine_invalid_index() {
let mut sm = StateMachine::new(Box::new(NamedState("menu".into())));
sm.transition_to(999); let mut world = crate::World::new();
sm.apply_transition(&mut world);
assert_eq!(sm.current_name(), "menu"); }
#[test]
fn state_machine_update() {
let mut sm = StateMachine::new(Box::new(NamedState("game".into())));
let mut world = crate::World::new();
sm.update(&mut world); }
}