use super::timing::Duration;
pub enum Trans {
None,
Pop,
Push(Box<State>),
Switch(Box<State>),
Quit,
}
pub trait State {
fn on_start(&mut self) {}
fn on_stop(&mut self) {}
fn on_pause(&mut self) {}
fn on_resume(&mut self) {}
fn handle_events(&mut self, _events: &[i32]) {}
fn fixed_update(&mut self, _delta: Duration) -> Trans { Trans::None }
fn update(&mut self, _delta: Duration) -> Trans { Trans::Pop }
}
pub struct StateMachine {
running: bool,
state_stack: Vec<Box<State>>,
}
impl StateMachine {
pub fn new<T: 'static>(initial_state: T) -> StateMachine
where T: State
{
StateMachine {
running: false,
state_stack: vec![Box::new(initial_state)],
}
}
pub fn is_running(&self) -> bool {
self.running
}
pub fn start(&mut self) {
if !self.running {
self.state_stack.last_mut().unwrap().on_start();
self.running = true;
}
}
pub fn handle_events(&mut self, events: &[i32]) {
if self.running {
if let Some(state) = self.state_stack.last_mut() {
state.handle_events(events);
}
}
}
pub fn fixed_update(&mut self, delta_time: Duration) {
if self.running {
let mut trans = Trans::None;
if let Some(state) = self.state_stack.last_mut() {
trans = state.fixed_update(delta_time);
}
self.transition(trans);
}
}
pub fn update(&mut self, delta_time: Duration) {
if self.running {
let mut trans = Trans::None;
if let Some(state) = self.state_stack.last_mut() {
trans = state.update(delta_time);
}
self.transition(trans);
}
}
fn transition(&mut self, request: Trans) {
if self.running {
match request {
Trans::None => (),
Trans::Pop => self.pop(),
Trans::Push(state) => self.push(state),
Trans::Switch(state) => self.switch(state),
Trans::Quit => self.stop(),
}
}
}
fn switch(&mut self, state: Box<State>) {
if self.running {
if let Some(mut state) = self.state_stack.pop() {
state.on_stop();
}
self.state_stack.push(state);
self.state_stack.last_mut().unwrap().on_start();
}
}
fn push(&mut self, state: Box<State>) {
if self.running {
if let Some(state) = self.state_stack.last_mut() {
state.on_pause();
}
self.state_stack.push(state);
self.state_stack.last_mut().unwrap().on_start();
}
}
fn pop(&mut self) {
if self.running {
if let Some(mut state) = self.state_stack.pop() {
state.on_stop();
}
if let Some(state) = self.state_stack.last_mut() {
state.on_resume();
} else {
self.running = false;
}
}
}
fn stop(&mut self) {
if self.running {
while let Some(mut state) = self.state_stack.pop() {
state.on_stop();
}
self.running = false;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use timing::Duration;
struct State1(u8);
struct State2;
impl State for State1 {
fn update(&mut self, _delta: Duration) -> Trans {
if self.0 > 0 {
self.0 -= 1;
Trans::None
} else {
Trans::Switch(Box::new(State2))
}
}
}
impl State for State2 {
fn update(&mut self, _delta: Duration) -> Trans {
Trans::Pop
}
}
#[test]
fn switch_pop() {
let mut sm = StateMachine::new(State1(7));
sm.start();
for _ in 0..8 {
sm.update(Duration::seconds(0));
assert!(sm.is_running());
}
sm.update(Duration::seconds(0));
assert!(!sm.is_running());
}
}