Fluent syntax is a natural way to describe finite state machines. Defacing code with a hundred macro decorations and trait implementations is not.
These state machines are defined using a builder, built, then started when ready to use. You can create a full state machine in one expression!
fn create_turnstile_fsm() -> PassiveStateMachine<States, Turnstile, Events> {
StateMachineBuilder::create(Locked, Turnstile::default())
.on_enter(|| println!("turnstile is locked"))
.on_mut(Coin, |model| {
model.coins += 1;
model.print_revenue()
})
.goto(Unlocked)
.on(Push, || {
println!("turnstile won't budge, maybe try inserting a coin")
})
.in_state(Unlocked)
.on_enter(|| println!("turnstile clicks"))
.on(Push, || println!("enjoy your ride!"))
.on_mut(Push, |model| {
model.riders += 1;
model.print_ridership();
})
.goto(Locked)
.on(Coin, || {
println!("you already paid! Try pushing on the turnstile.")
})
.build_passive()
}After the machine is built, start it, and then begin firing events to make it do stuff.
fn main() {
let mut machine = create_turnstile_fsm();
machine.start();
loop {
let action = input!("add a coin with (c) and push with (p): ");
match action.trim().to_lowercase().parse() {
Ok('c') => {
machine.fire(Coin);
}
Ok('p') => {
machine.fire(Push);
}
_ => continue,
};
}
}- Fluent syntax for machine description
- No traits to implement -- machine can be defined and built in one line of code
- Define any number of actions for entry, exit, and event for every state. Actions are executed in order of definition.
- Built-in model manipulation
- Passive (blocking) or active (non-blocking) state machine
- No dependencies
These are general guidelines, but feel free to mix and match how you see fit.
To see an example of a passive state machine, check out examples/turnstile.rs.
In general, passive machines rely on external events from fire() handled by on() and on_mut() followed by a goto
to define transitions.
The model is updated when events are fired, and transitions are based on fired events. This model is usually not shared in other scopes; the user fires an event, then checks the model to see what changed.
To see an example of an active state machine, check out examples/stoplight.rs.
In general, active machines rely on the current state and the state of the model to define transitions, handled by the
function passed to create_active(). This model is usually shared between scopes; the model is updated externally, then
the state machine checks the model for what state to transition to next.
Author: Wes Kelly
If you want me to add a new feature, email me and we can work something out.
This crate was inspired by a C# library I use constantly called Appccelerate State Machine.
Contributions are welcome! Email me if you have any questions. Large features may need some careful consideration, so ask me before you spend a lot of time on them.
If you make something cool, please share it and I'll mention it here!
- Recursive events
- Error handling
- More state/event introspection to aid in logging and debugging
- Async interfaces
- FFI interface
- Better documentation
- Performance testing
Versions below 1.0.0 are considered to be fully experimental. Major things might change, including the license.
Major releases mean interface changes and functionality changes. Don't switch between major versions unless you know why you want to.
Minor releases keep the existing interface the same, but may have additional methods, traits, or features. The functionality may also be slightly different behind the scenes, but should be non-intrusive.
Patch releases are for fixing bugs, and none of the interface will change.