pub trait StateMachine {
type State: Clone + PartialEq + std::fmt::Debug;
type Event: Clone + std::fmt::Debug;
fn state(&self) -> &Self::State;
fn transition(&mut self, event: &Self::Event) -> &Self::State;
fn can_transition(&self, event: &Self::Event) -> bool;
}
type TransitionFn<S, E> = Box<dyn Fn(&S, &E) -> Option<S>>;
pub struct BasicFSM<S, E> {
current: S,
history: Vec<(S, E, S)>, transition_fn: TransitionFn<S, E>,
max_history: usize,
}
impl<S: Clone + PartialEq + std::fmt::Debug, E: Clone + std::fmt::Debug> BasicFSM<S, E> {
pub fn new(initial: S, transition_fn: impl Fn(&S, &E) -> Option<S> + 'static) -> Self {
Self {
current: initial,
history: Vec::new(),
transition_fn: Box::new(transition_fn),
max_history: 100,
}
}
pub fn with_max_history(mut self, max: usize) -> Self {
self.max_history = max;
self
}
pub fn history(&self) -> &[(S, E, S)] {
&self.history
}
pub fn checkpoint(&self) -> S {
self.current.clone()
}
pub fn restore(&mut self, state: S) {
self.current = state;
}
}
impl<S: Clone + PartialEq + std::fmt::Debug, E: Clone + std::fmt::Debug> StateMachine
for BasicFSM<S, E>
{
type State = S;
type Event = E;
fn state(&self) -> &S {
&self.current
}
fn transition(&mut self, event: &E) -> &S {
if let Some(next) = (self.transition_fn)(&self.current, event) {
let from = self.current.clone();
self.current = next;
let to = self.current.clone();
self.history.push((from, event.clone(), to));
if self.history.len() > self.max_history {
self.history.remove(0);
}
}
&self.current
}
fn can_transition(&self, event: &E) -> bool {
(self.transition_fn)(&self.current, event).is_some()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, Clone, PartialEq)]
enum Light {
Red,
Yellow,
Green,
}
#[derive(Debug, Clone)]
enum LightEvent {
Timer,
Emergency,
}
fn traffic_light() -> BasicFSM<Light, LightEvent> {
BasicFSM::new(Light::Red, |state, event| {
#[allow(unreachable_patterns)]
match (state, event) {
(Light::Red, LightEvent::Timer) => Some(Light::Green),
(Light::Green, LightEvent::Timer) => Some(Light::Yellow),
(Light::Yellow, LightEvent::Timer) => Some(Light::Red),
(_, LightEvent::Emergency) => Some(Light::Red),
_ => None,
}
})
}
#[test]
fn test_initial_state() {
let fsm = traffic_light();
assert_eq!(fsm.state(), &Light::Red);
}
#[test]
fn test_valid_transition() {
let mut fsm = traffic_light();
fsm.transition(&LightEvent::Timer);
assert_eq!(fsm.state(), &Light::Green);
}
#[test]
fn test_full_cycle() {
let mut fsm = traffic_light();
fsm.transition(&LightEvent::Timer); fsm.transition(&LightEvent::Timer); fsm.transition(&LightEvent::Timer); assert_eq!(fsm.state(), &Light::Red);
}
#[test]
fn test_emergency_from_any_state() {
let mut fsm = traffic_light();
fsm.transition(&LightEvent::Timer); fsm.transition(&LightEvent::Emergency); assert_eq!(fsm.state(), &Light::Red);
}
#[test]
fn test_can_transition() {
let fsm = traffic_light();
assert!(fsm.can_transition(&LightEvent::Timer));
}
#[test]
fn test_history_tracking() {
let mut fsm = traffic_light();
fsm.transition(&LightEvent::Timer);
fsm.transition(&LightEvent::Timer);
assert_eq!(fsm.history().len(), 2);
}
#[test]
fn test_checkpoint_restore() {
let mut fsm = traffic_light();
fsm.transition(&LightEvent::Timer); let cp = fsm.checkpoint();
fsm.transition(&LightEvent::Timer); assert_eq!(fsm.state(), &Light::Yellow);
fsm.restore(cp);
assert_eq!(fsm.state(), &Light::Green);
}
#[test]
fn test_history_max_limit() {
let mut fsm = traffic_light().with_max_history(2);
fsm.transition(&LightEvent::Timer);
fsm.transition(&LightEvent::Timer);
fsm.transition(&LightEvent::Timer);
assert_eq!(fsm.history().len(), 2); }
}