fsmy is a finite state machine rust library.
It is originally inspired by rust-fsm, with a few important differences, the main one being integrated support to contexts.
- On-transition context mutation - when leaving a state, entering one and along the transition edge
- Custom alphabets for input, output, state and context
- Parent states (regions)
- DSL via
fsmy-dsl - Mermaid.js diagrams
- Final states detection
- Virtual states
- Ability to rewind states and context (see
JournaledStateMachine)
- Function-based, non-static outputs
- Parallel states
- Other languages bindings
cargo add fsmyThe DSL is similar to the one in rust-fsm-dsl but also accepts
mutation closures to run on state transitions.
use fsmy::dsl::state_machine;
/// Context is optional, if not provided defaults to the type `()`
#[derive(Default, Debug)]
pub struct Context {
counter: u8,
}
impl Context {
pub fn counter(&self) -> u8 {
self.counter
}
}
state_machine! {
#[repr(C)]
#[derive(Debug)]
#[alphabet(context(super::Context))]
pub state_machine::Machine(Uninitialized)
// Transitions can define outputs using square brackets after target state
Uninitialized(Init) => Initialized[Running],
// States can transition to themselves and also define a mutation to be run when the transition happens
Initialized(Tick) => Initialized |context| { context.counter += 1; }
}
use state_machine::*;
let mut machine = Machine::default().start().unwrap();
let next = machine.consume(Input::Init);
assert!(next.is_ok());
assert_eq!(next.unwrap(), Some(&Output::Running));
assert_eq!(machine.context().counter(), 0);
let next = machine.consume(Input::Tick);
assert!(next.is_ok());
assert!(next.unwrap().is_none());
assert_eq!(machine.state(), &State::Initialized);
assert_eq!(machine.context().counter(), 1);There are three different points where a context can be mutated, and all of them happen on a state transition and in the following order.
- The
on_leavemutation callback defined when defining a state viadefine_state, taken from the source state on the transition edge - The mutation defined when using
add_transition_with_mutation(can also be done via DSL, see example above) - The
on_entermutation callback defined when defining a state viadefine_state, taken from the target state on the transition edge
In other words, when transitioning from state A to state B:
on_leavefrom state A is called- Mutation on the edge is called
on_enteron state B is called
Currently on_enter and on_leave are not supported by the DSL and can only be registered through the regular state machine rust api.
When defining a custom alphabet it is important that especially state implements some traits needed by the state machine implementations. You can use whatever type you want but if you use enums, which is very common, you can just
#[derive(Primitive)]
pub enum MyState {
Init,
Running,
Ended
}to automatically implement all of these traits for you.
events: enables events busdsl: enables DSL macros (see below)history: enables the journaled version of the state machine implementation