Crate simple_statemachine

Source
Expand description

§Simple Statemachine

This crate defines a single macro statemachine!(). As the single argument the macro takes the definition of a statemachine, described in a simple and easy to ready DSL.

§Simple examples

The simplest possible statemachine that can be defined is this:

statemachine!{
    Name StatemachineName
    InitialState TheOnlyState

    TheOnlyState{}
}

But, of course, this statemachine doesn’t do anything.

Let’s define a simple traffic light with two states: Walk and DontWalk:

statemachine!{
    Name TrafficLightStatemachine
    InitialState DontWalk

    DontWalk {
        TimerFired => Walk
    }
    Walk {
        TimerFired => DontWalk
    }
}

OK, now we have two states, and anytime a timer fires, the state will switch from DontWalk to Walk and vice versa. For safety reasons, the traffic light starts in the DontWalk state. Of course, the lights actually have to be switched for the traffic light to work. We have two possibilities for this simple example: actions or entry and exit handlers.

(back to top)

§Using action handlers

 statemachine!{
    Name TrafficLightStatemachine
    InitialState DontWalk

    DontWalk {
        TimerFired ==switch_to_walk=> Walk
    }
    Walk {
        TimerFired ==switch_to_dont_walk=> DontWalk
    }
 }

 struct LightSwitch{}
 impl TrafficLightStatemachineHandler for LightSwitch{
     fn switch_to_walk(&mut self) {
         println!("Switching \"Don't Walk\" off");
         println!("Switching \"Walk\" on");
     }

     fn switch_to_dont_walk(&mut self) {
         println!("Switching \"Walk\" off");
         println!("Switching \"Don't Walk\" on");
     }
 }

 fn main() {
     let lights=LightSwitch{};
     let sm=TrafficLightStatemachine::new(lights);

     sm.event(TrafficLightStatemachineEvent::TimerFired);
     assert_eq!(sm.get_state(),TrafficLightStatemachineState::Walk);

     sm.event(TrafficLightStatemachineEvent::TimerFired);
     assert_eq!(sm.get_state(),TrafficLightStatemachineState::DontWalk);

     sm.event(TrafficLightStatemachineEvent::TimerFired);
     assert_eq!(sm.get_state(),TrafficLightStatemachineState::Walk);

     sm.event(TrafficLightStatemachineEvent::TimerFired);
     assert_eq!(sm.get_state(),TrafficLightStatemachineState::DontWalk);
 }

(back to top)

§Using entry/exit handlers

 statemachine!{
    Name TrafficLightStatemachine
    InitialState DontWalk

    DontWalk {
        OnEntry switch_on_dont_walk
        OnExit switch_off_dont_walk
        TimerFired => Walk
    }
    Walk {
        OnEntry switch_on_walk
        OnExit switch_off_walk
        TimerFired => DontWalk
    }
 }

 struct LightSwitch{}
 impl TrafficLightStatemachineHandler for LightSwitch{
     fn switch_off_dont_walk(&mut self) {
         println!("Switching \"Don't Walk\" off");
     }
     fn switch_on_dont_walk(&mut self) {
         println!("Switching \"Don't Walk\" on");
     }
     fn switch_off_walk(&mut self) {
         println!("Switching \"Walk\" off");
     }
     fn switch_on_walk(&mut self) {
         println!("Switching \"Walk\" on");
     }
 }

(back to top)

§Using guards

Let’s extend the Using actions example a bit more. Assume a traffic light with a button for pedestrians to press to switch the car’s light to red and the pedestrian’s light to “walk”.

An event may occur in multiple transition lines in the definition of a state, given that all occurrences are guarded. An unguarded line may be added after all guarded lines as the default transition if all guards return false. Guards are evaluated top to bottom, until a guard function returns true.

statemachine!{
   Name TrafficLightStatemachine
   InitialState DontWalk

   DontWalk {
       ButtonPressed ==set_button_to_pressed=> DontWalk
       TimerFired[check_button_is_pressed] ==switch_to_walk=> Walk
       TimerFired => DontWalk
   }
   Walk {
       OnEntry set_button_to_not_pressed
       TimerFired ==switch_to_dont_walk=> DontWalk
   }
}

struct LightSwitch{
    button_pressed:bool
}
impl TrafficLightStatemachineHandler for LightSwitch{
    fn check_button_is_pressed(&self) -> bool {
        self.button_pressed
    }
    fn set_button_to_pressed(&mut self) {
        self.button_pressed=true;
    }
    fn set_button_to_not_pressed(&mut self) {
        self.button_pressed=false;
    }
    //...//
}

fn main() {
    let lights=LightSwitch{button_pressed:false};
    let sm=TrafficLightStatemachine::new(lights);
    let lights=sm.get_handler();

    // button is not pressed
    sm.event(TrafficLightStatemachineEvent::TimerFired);
    assert_eq!(sm.get_state(),TrafficLightStatemachineState::DontWalk);

    sm.event(TrafficLightStatemachineEvent::ButtonPressed);
    assert_eq!(lights.borrow().button_pressed,true);

    sm.event(TrafficLightStatemachineEvent::TimerFired);
    assert_eq!(sm.get_state(),TrafficLightStatemachineState::Walk);
    assert_eq!(lights.borrow().button_pressed,false);

    sm.event(TrafficLightStatemachineEvent::TimerFired);
    assert_eq!(sm.get_state(),TrafficLightStatemachineState::DontWalk);

    sm.event(TrafficLightStatemachineEvent::TimerFired);
    assert_eq!(sm.get_state(),TrafficLightStatemachineState::DontWalk);
}

(back to top)

§Events With Payload

Sometimes, you would like to include payload with events, e.g. a received data block in a Received event. The optional parameter EventPayload defines the payload type to use. All statemachine events will transport the same payload type. Generics and explicit lifetime parameters are not possible.

 type DataType=Vec<u8>;

 statemachine!{
    Name ReceiverStatemachine
    InitialState Idle
    EventPayload DataType

    Idle {
        Connect => Connected
    }
    Connected {
        ReceivedPacket ==output_data=> Connected
        Disconnect => Idle
    }
 }

 struct Receiver{}
 impl ReceiverStatemachineHandler for Receiver {
     fn output_data(&mut self, payload: &DataType) {
         assert_eq!(from_utf8(payload.as_slice()).unwrap(),"Hello, world!");
     }
 }

 fn main(){
    let rcv=Receiver{};
    let sm=ReceiverStatemachine::new(rcv);
    sm.event(ReceiverStatemachineEvent::Connect(Vec::new()));
    sm.event(ReceiverStatemachineEvent::ReceivedPacket(Vec::<u8>::from("Hello, world!")));
    sm.event(ReceiverStatemachineEvent::Disconnect(Vec::new()));
 }

(back to top)

§Handling Unexpected Events

The world is not perfect, and sometimes an event might be sent to the statemachine that is not expected. The default behavior is to ignore the unexpected event. If you would like the statemachine to react to unexpected events, a special handler can be defined by the optional parameter UnexpectedHandler:

 type DataType=Vec<u8>;

 statemachine!{
    Name ReceiverStatemachine
    InitialState Idle
    EventPayload DataType
    UnexpectedHandler unexpected_event_handler

    Idle {
        Connect => Connected
    }
    Connected {
        ReceivedPacket ==output_data=> Connected
        Disconnect => Idle
    }
 }

 struct Receiver{}
 impl ReceiverStatemachineHandler for Receiver {
     fn unexpected_event_handler(&mut self, state: ReceiverStatemachineState, event: &ReceiverStatemachineEvent) {
        println!("Received an unexpected event in state {:?}: {:?}",state,event);
     }
     //...//
 }

 fn main(){
    let rcv=Receiver{};
    let sm=ReceiverStatemachine::new(rcv);
    sm.event(ReceiverStatemachineEvent::ReceivedPacket(Vec::<u8>::from("This is unexpected!")));
    sm.event(ReceiverStatemachineEvent::Connect(Vec::new()));
    sm.event(ReceiverStatemachineEvent::ReceivedPacket(Vec::<u8>::from("Hello, world!")));
    sm.event(ReceiverStatemachineEvent::Disconnect(Vec::new()));
 }

(back to top)

§Sending Events from Handlers

For some statemachine constructs, it might be necessary to send events from inside the handler functions (enter, exit, action). For this special use case, the statemachine defines a function event_from_handler(). Use of event() from inside a handler function is undefined behavior.

To be able to access the statemachine from the handler struct, the handler must hold a reference to the statemachine. This leads to a somewhat more complicated handling.

statemachine!{
    Name ComplexMachine
    InitialState Init

    Init{
        DeferDecision ==decide_now=> IntermediateState
    }
    IntermediateState{
        First => FirstState
        Second => SecondState
    }
    FirstState{
        BackToStart => Init
    }
    SecondState{}
}

struct Handler{
    choose_second:bool,
    sm: Weak<RefCell<ComplexMachine<Self>>>
}
impl Handler{
    fn set_sm(&mut self,sm: Weak<RefCell<ComplexMachine<Self>>>){
        self.sm=sm;
    }
}
impl ComplexMachineHandler for Handler{
    fn decide_now(&mut self){
        if self.choose_second {
            Weak::upgrade(&self.sm).unwrap().borrow()
                .event_from_handler(ComplexMachineEvent::Second);
        } else {
            Weak::upgrade(&self.sm).unwrap().borrow()
                .event_from_handler(ComplexMachineEvent::First);
        }
    }
}
fn main(){
    let h=Handler{ choose_second:false, sm:Weak::new() };
    let sm=Rc::new(RefCell::new(ComplexMachine::new(h)));
    let h=sm.borrow().get_handler();
    h.borrow_mut().set_sm(Rc::downgrade(&sm));

    sm.borrow().event(ComplexMachineEvent::DeferDecision);
    assert_eq!(sm.borrow().get_state(),ComplexMachineState::FirstState);

    sm.borrow().event(ComplexMachineEvent::BackToStart);
    assert_eq!(sm.borrow().get_state(),ComplexMachineState::Init);

    h.borrow_mut().choose_second=true;
    sm.borrow().event(ComplexMachineEvent::DeferDecision);
    assert_eq!(sm.borrow().get_state(),ComplexMachineState::SecondState);
}

(back to top)

§Accessing the Handler

The statemachine takes ownership of the struct implementing the handler trait. To access the handler, the statemachine implements three functions: get_handler(), get_handler_ref(), and get_handler_mut():

statemachine!{
    Name SimpleMachine
    InitialState TheOnlyState

    TheOnlyState{}
}

struct Handler{
    access_me:bool
}
impl SimpleMachineHandler for Handler{
}
fn main(){
    let h=Handler{access_me:false};
    let sm=SimpleMachine::new(h);

    // get_handler_ref() returns a unmutable borrowed reference:
    assert_eq!(sm.get_handler_ref().access_me,false);

    // get_handler_mut() returns a mutable borrowed reference:
    sm.get_handler_mut().access_me=true;
    assert_eq!(sm.get_handler_ref().access_me,true);

    // get_handler() returns a Rc<RefCell<Handler>>
    let href=sm.get_handler();
    assert_eq!(href.borrow().access_me,true);

    href.borrow_mut().access_me=false;
    assert_eq!(href.borrow().access_me,false);
}

(back to top)

§Extended Options

For special needs, the signatures of the handler (entry, exit, action) and guard functions can be changed to take extended transition (state and event) information. These extended signatures are activated by an option list in square brackets after the opening brace of the statemachine definition.

The following options are available:

  • entry_handler_with_transition_info - adds transition info to entry handlers
  • exit_handler_with_transition_info - adds transition info to exit_handlers
  • action_handler_with_transition_info - adds transition info to action handlers
  • guard_with_transition_info - adds transition info to guard functions
 statemachine!{
     [
         entry_handler_with_transition_info,
         exit_handler_with_transition_info,
         action_handler_with_transition_info,
         guard_with_transition_info
     ]
     Name OptionMachine
     InitialState Init

     Init{
         OnExit init_on_exit
         OneEvent[guard] ==action=> SecondState
     }
     SecondState{
         OnEntry second_on_entry
     }
 }
 struct Handler{}
 impl OptionMachineHandler for Handler{
     fn second_on_entry(
            &mut self,
            old_state: OptionMachineState,
            event: &OptionMachineEvent,
            new_state: OptionMachineState
     ) { /*...*/ }

     fn init_on_exit(
            &mut self,
            old_state: OptionMachineState,
            event: &OptionMachineEvent,
            new_state: OptionMachineState
     ) { /*...*/ }

     fn guard(
            &self,
            state: OptionMachineState,
            event: &OptionMachineEvent
     ) -> bool { /*...*/ false }

     fn action(
            &mut self,
            old_state: OptionMachineState,
            event: &OptionMachineEvent,
            new_state: OptionMachineState
     ) { /*...*/ }
 }

(back to top)

§Interface Reference

§Statemachine DSL

This is an example of a statemachine using all available features:

statemachine!{
    [
        action_handler_with_transition_info,
        entry_handler_with_transition_info,
        exit_handler_with_transition_info,
        guard_with_transition_info
    ]
    Name                StatemachineName
    InitialState        StateName1
    EventPayload        OptionalEventPayloadType
    UnexpectedHandler   ueh_function_name_optional

    StateName1 {
        OnEntry on_entry_function_name1_optional
        OnExit  on_exit_function_name1_optional
        EventName1[guard_function_name_optional] == action_function_name1_optional => StateName2
        EventName2 == action_function_name2_optional => StateName3
        EventName3[guard_function_name_optional] == action_function_name_optional => StateName3
        EventName4[guard_function_name_optional] == action_function_name_optional => StateName3
    }

    StateName2 {
        OnEntry on_entry_function_name2_optional
        OnExit  on_exit_function_name2_optional

        EventName2[guard_function_name_optional] => StateName3
        EventName3[guard_function_name_optional] == action_function_name_optional => StateName3
        EventName4[guard_function_name_optional] == action_function_name_optional => StateName3
    }

    StateName3{}
}

(back to top)

§Created types and traits

The Name of the statemachine is used as a base name for

  • Event type,
  • State type, and
  • Trait name

A name MyMachine creates

enum MyMachineEvent{
//...
}
enum MyMachineState{
//...
}
trait MyMachineHandler{
//...
}

The enum values are exactly as given in the statemachine definition.

If EventPayload is defined, all event values are created with this payload type. An event payload of MyPayload gives e.g.

enum MyMachineEvent {
    FirstEvent(MyPayload),
    SecondEvent(MyPayload),
}

(back to top)

§Trait functions - entry, exit, and action handlers, and guards

The function names of handlers and guard functions are exactly as given in the statemachine definition. The signature depends on the options given, see also Extended Options.

§Action handler

Action handlers are called while transitioning from one state to the next, after the old state’s exit handler and before the new state’s entry handler.

§Without payload
trait MyMachineHandler {
    fn action_handler(&mut self);
}
§With payload
trait MyMachineHandler {
    fn action_handler(&mut self,
           payload: &MyPayload);
}
§Without payload, with transition info option
trait MyMachineHandler {
    fn action_handler(&mut self,
           old_state: MyMachineState,
           event: &MyMachineEvent,
           new_state: MyMachineState);
}
§With payload, with transition info option
trait MyMachineHandler {
    fn action_handler(&mut self,
           old_state: MyMachineState,
           event: &MyMachineEvent,
           new_state: MyMachineState,
           payload: &MyPayload);
}

(back to top)

§Entry and exit handlers

Entry handlers are called directly after a transition has happened. If an action is defined for the transition, the action is called before the entry handler.

Exit handlers are called directly before a transition happens. If an action is defined for the transition, the action is called after the exit handler. If a guard is defined for the event, the exit handler is called after the guard.

§Standard
trait MyMachineHandler {
    fn entry_exit_handler(&mut self);
}
§With transition info option
trait MyMachineHandler {
    fn entry_exit_handler(&mut self,
           old_state: MyMachineState,
           event: &MyMachineEvent,
           new_state: MyMachineState);
}

(back to top)

§Guards

Guards are run before any entry, exit, or action handlers, since they decide which transition will be executed. For details see Using guards.

Guards return a bool. If the return value is true, the guarded transition will be executed.

§Standard
trait MyMachineHandler {
    fn my_guard(&mut self) -> bool;
}
§With transition info option
trait MyMachineHandler {
    fn my_guard(&mut self,
           state: MyMachineState,
           event: &MyMachineEvent
       ) -> bool;
}

(back to top)

§Statemachine interface

The created statemachine struct defines several functions to interact with the statemachine.

§Statemachine definition

The statemachine handler is given as a generic parameter (e.g. statemachine Name MyStatemachine):

struct MyStatemachine<Handler>{
    /*...*/
}
impl<Handler> MyStatemachine<Handler>{
     /*...*/
}

§Functions

§new()
pub fn new(handler: Handler) -> Self

Creates a new statemachine instance.

  • handler - The handler providing handlers and guards

§get_handler()
pub fn get_handler(&self) -> Rc<RefCell<Handler>>

Returns a Rc reference counted RefCell to the owned Handler.


§get_handler_ref()
pub fn get_handler_ref(&self) -> Ref<Handler>

Returns a non-mutably borrowed reference to the owned Handler.


§get_handler_mut()
pub fn get_handler_mut(&self) -> RefMut<Handler>

Returns a mutably borrowed reference to the owned Handler.


§get_state()
pub fn get_state(&self) -> StatemachineState

Returns the current state of the statemachine.


§event()
pub fn event(&mut self, ev: &StatemachineEvent)

Sends an event to the statemachine, triggering state changes and executing actions.

  • ev - The event to send to the machine.

Calling event()from inside a handler function is undefined behavior.


§event_from_handler()
pub fn event_from_handler(&mut self, ev: &StatemachineEvent)

Sends an event to the statemachine from inside a handler function. This event will saved and executed after the current event is completely processed, and before control is returned to the caller of event(). Only one event can be sent from handlers while processing a given event. Additional calls to event_from_handler() will overwrite the saved event.

  • ev - The event to send to the machine.

Calling event_from_handler() from outside a handler function is undefined behavior.

Macros§

statemachine
The statemachine!() macro takes as parameter the description of a statemachine in a domain-specific language. For a detailed description, please refer to the module documentation.