1
\$\begingroup\$

I'm beginner in rust. I would ask code review of my finite state machine. Main feature :

  • static (event, state and transition)
  • support pushdown

It is the first step, second step is to use macro for generate section.

In my example i have 3 events (e1, e2, e3), 4 states (red, green, blue , rainbow). There are 4 type of transitions :

  • Move to other state with or without save origin state
  • None to stay on same State
  • Pop to restore the last save state
  • Invalid to say invalid event on the state

static code :

use std::marker::PhantomData;

/**
 * Static
 */
#[derive(Debug, Copy, Clone)]
enum Transition<S> {
    None,
    Invalid,
    Move(S, bool),
    Pop,
}

trait StateAction<E, S> {
    fn push(&mut self, event: E) -> Transition<S>;
}

trait StateManager<E, S> {
    fn get_state(&mut self, state: S) -> &mut dyn StateAction<E, S>;
}

struct StateMachine<M, E, S>
where
    M: StateManager<E, S>,
    S: Copy,
    E: Copy,
{
    manager: M,
    current: S,
    pop_state: Vec<S>,
    phantom: PhantomData<E>,
}

impl<M, E, S> StateMachine<M, E, S>
where
    M: StateManager<E, S>,
    S: Copy,
    E: Copy,
{
    fn new(manager: M, state: S) -> Self {
        Self {
            manager: manager,
            current: state,
            pop_state: Vec::new(),
            phantom: PhantomData,
        }
    }

    fn push(&mut self, event: E) -> Transition<S> {
        let current_state  = self.manager.get_state(self.current);
        let mut transition = current_state.push(event);
        match transition {
            Transition::Move(new_state, push) => {
                if push {
                    self.pop_state.push(self.current);
                }
                self.current = new_state;
            }
            Transition::Pop => {
                match self.pop_state.pop() {
                    Some(new_state) => {
                        self.current = new_state;
                        transition   = Transition::Move(new_state, false);    
                    },
                    None => panic!("invalid pop"),
                }
            }
            _ => {}
        }
        transition
    }
}

generated code :

#[derive(Debug, Copy, Clone)]
enum Event {
    E1,
    E2,
    E3,
}

#[derive(Debug, Copy, Clone)]
enum State {
    Green,
    Red,
    Blue,
    Rainbow,
}

struct GreenState;

impl GreenState {
    fn new() -> Self {
        Self {}
    }
}

impl StateAction<Event, State> for GreenState {
    fn push(&mut self, event: Event) -> Transition<State> {
        match event {
            Event::E1 => Transition::Move(State::Red, false),
            Event::E2 => Transition::Move(State::Blue, false),
            Event::E3 => Transition::Move(State::Rainbow, true),
        }
    }
}

struct RedState;

impl RedState {
    fn new() -> Self {
        Self {}
    }
}

impl StateAction<Event, State> for RedState {
    fn push(&mut self, event: Event) -> Transition<State> {
        match event {
            Event::E1 => Transition::Move(State::Rainbow, true),
            Event::E2 => Transition::Move(State::Blue, false),
            Event::E3 => Transition::Move(State::Green, false),
        }
    }
}

struct BlueState;

impl BlueState {
    fn new() -> Self {
        Self {}
    }
}

impl StateAction<Event, State> for BlueState {
    fn push(&mut self, event: Event) -> Transition<State> {
        match event {
            Event::E1 => Transition::Move(State::Red, false),
            Event::E2 => Transition::Move(State::Rainbow, true),
            Event::E3 => Transition::Move(State::Green, false),
        }
    }
}

struct RainbowState;

impl RainbowState {
    fn new() -> Self {
        Self {}
    }
}

impl StateAction<Event, State> for RainbowState {
    fn push(&mut self, event: Event) -> Transition<State> {
        match event {
            Event::E1 => Transition::Pop,
            Event::E2 => Transition::None,
            Event::E3 => Transition::Invalid,
        }
    }
}

struct StateManagerImpl {
    states: (GreenState, RedState, BlueState, RainbowState),
}

impl StateManagerImpl {
    fn new() -> Self {
        Self {
            states: (
                GreenState::new(),
                RedState::new(),
                BlueState::new(),
                RainbowState::new(),
            ),
        }
    }
}

impl StateManager<Event, State> for StateManagerImpl {
    fn get_state(&mut self, state: State) -> &mut dyn StateAction<Event, State> {
        match state {
            State::Green => &mut self.states.0,
            State::Red => &mut self.states.1,
            State::Blue => &mut self.states.2,
            State::Rainbow => &mut self.states.3,
        }
    }
}

Example :

fn main() {
    let manager = StateManagerImpl::new();
    let mut sm = StateMachine::new(manager, State::Green);
    {
        // Green -> Red
        let n = sm.push(Event::E1);
        println!("after e1 {:?}", n);
    }
    {
        // Red   -> Blue
        let n = sm.push(Event::E2);
        println!("after e2 {:?}", n);
    }
    {
        // Blue  -> Rainbow
        let n = sm.push(Event::E2);
        println!("after e2 {:?}", n);
    }
    {
        // Rainbow (stay)
        let n = sm.push(Event::E2);
        println!("after e2 {:?}", n);
    }
    {
        // Rainbow (error)
        let n = sm.push(Event::E3);
        println!("after e3 {:?}", n);
    }
    {
        // Rainbow/Pop -> Blue
        let n = sm.push(Event::E1);
        println!("after e3 {:?}", n);
    }
}

the output:

after e1 Move(Red, false)
after e2 Move(Blue, false)
after e2 Move(Rainbow, true)
after e2 None
after e3 Invalid
after e3 Move(Blue, false)

what do you think ?

Thanks Etienne

\$\endgroup\$
  • 1
    \$\begingroup\$ Welcome to Code Review. Can you tell usorr about the purpose of your goal? I'm familiar with FSMs, but what prompted you to write this one? \$\endgroup\$ – Mast May 9 '20 at 16:33
  • \$\begingroup\$ My goal is the world domination but in the first time i want to learn rust and made a little game... but i'm not sure to understand your question \$\endgroup\$ – Vaillant Etienne May 9 '20 at 16:57
  • 2
    \$\begingroup\$ What I'm trying to ask for, considering code rarely stands on itself, is what prompted you to write this. This helps in understanding why you made certain decisions and what kind of advice you're actually looking for. A beginner requires different answers than an expert, we try to facilitate. \$\endgroup\$ – Mast May 9 '20 at 20:24
  • \$\begingroup\$ I have lot of experience in programming (c++) but 1) i'm beginner in rust (so i need feedback about my code about perf/useless copy/ rust writing style 2) it is first time i write a FSM, so i would want feedback about my api ... \$\endgroup\$ – Vaillant Etienne May 10 '20 at 8:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.