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
- Events With Payload
- Handling Unexpected Events
- Sending Events from Handlers
- Accessing the Handler
- Extended Options
- Interface Reference
§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.
§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);
}
§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");
}
}
§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);
}
§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()));
}
§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()));
}
§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);
}
§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);
}
§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
) { /*...*/ }
}
§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{}
}
§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),
}
§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);
}
§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);
}
§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;
}
§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.