statemachine
stableA finite state machine implementation with handle-based instances, supporting states, transitions, event triggering, and history tracking.
use plugin statemachine::{StateMachine, add_state, set_state, …} Functions (19)
- StateMachine Creates a new state machine instance starting in initial_state.
- add_state Registers a new state with the given name.
- set_state Forcefully sets the current state to name, bypassing all transition rules.
- current_state Returns the name of the currently active state.
- add_transition Registers a transition: when the machine is in state from and trigger is called with event, it moves to to.
- trigger Fires event on the machine.
- available_transitions Returns a table of {to, event} entries for every transition that is valid from the current state.
- available_events Returns a sorted, deduplicated list of event names that can be triggered from the current state.
- can_trigger Returns true if firing event from the current state would produce a valid transition.
- has_state Returns true if a state with the given name has been registered.
- state_count Returns the number of registered states, including the initial state.
- transition_count Returns the total number of registered transitions.
- history Returns an ordered list of every state the machine has visited since creation or the last reset, including the initial state.
- is_terminal Returns true if the current state has no outgoing transitions, meaning the machine has reached a final/sink state from which no event can…
- get_states Returns a table of all registered state names.
- get_transitions Returns a table of all registered transitions, each as a {from, to, event} entry.
- remove_state Removes a state and every transition that references it (as either source or target).
- remove_transition Removes the specific transition matching all three parameters, leaving states untouched.
- reset Returns the machine to its initial state and clears the history (re-seeding it with just the initial state), while keeping all registered…
Overview
statemachine models a classic finite state machine (FSM): a set of named
states, a set of from -> to transitions each labelled with an event, and a
single "current" state that moves as events fire. Each machine is a stateful
handle created with StateMachine(initial_state); you call methods on that
handle and the receiver is automatically bound as self, so a method like
trigger("start") operates on the machine you called it on. Use it whenever you
have a process with well-defined modes — a UI screen flow, a network connection,
a door, an order lifecycle — and you want illegal transitions to be impossible
rather than merely discouraged.
The mental model is: declare every state up front with add_state, wire the
legal moves with add_transition, then drive the machine at runtime with
trigger (guarded) or set_state (forced). Inspect where you are with
current_state, ask what is possible with can_trigger / available_events,
and audit where you have been with history. The initial state doubles as the
reset target.
Common patterns
Build a machine, declare states and transitions, then drive it with events:
use plugin statemachine::{StateMachine}
let door = StateMachine("closed")
door.add_state("open")
door.add_state("locked")
door.add_transition("closed", "open", "open")
door.add_transition("open", "closed", "close")
door.add_transition("closed", "locked", "lock")
door.add_transition("locked", "closed", "unlock")
door.trigger("open")
print("door is now {door.current_state()}")
door.trigger("close")
door.trigger("lock")
print("door is now {door.current_state()}")
Guard an event before firing it so you never attempt an illegal move:
use plugin statemachine::{StateMachine}
let sm = StateMachine("idle")
sm.add_state("active")
sm.add_transition("idle", "active", "start")
if sm.can_trigger("submit") {
sm.trigger("submit")
} else {
print("'submit' is not valid from {sm.current_state()}")
}
Run a machine to completion and then audit the path it took:
use plugin statemachine::{StateMachine}
let order = StateMachine("placed")
order.add_state("shipped")
order.add_state("delivered")
order.add_transition("placed", "shipped", "ship")
order.add_transition("shipped", "delivered", "deliver")
order.trigger("ship")
order.trigger("deliver")
if order.is_terminal() {
print("order finished in state {order.current_state()}")
}
for i, state in order.history() {
print("{i}: {state}")
}
Creates a new state machine instance starting in initial_state.
Creates a new state machine instance starting in initial_state. The initial
state is automatically registered and becomes both the current state and the
reset target. The returned value is a handle on which you call all other
methods.
use plugin statemachine::{StateMachine}
let door = StateMachine("closed")
door.add_state("open")
door.add_state("locked")
door.add_transition("closed", "open", "open")
door.add_transition("open", "closed", "close")
door.add_transition("closed", "locked", "lock")
door.add_transition("locked", "closed", "unlock")
print(door.current_state())
Registers a new state with the given name.
Registers a new state with the given name. Adding a name that already exists simply overwrites its (empty) entry, so it is safe to call repeatedly.
use plugin statemachine::{StateMachine}
let sm = StateMachine("idle")
sm.add_state("running")
sm.add_state("paused")
print("states: {sm.state_count()}")
Forcefully sets the current state to name, bypassing all transition rules.
Forcefully sets the current state to name, bypassing all transition rules.
Errors if the state does not exist. The new state is appended to history, so a
forced jump still shows up in the audit trail.
use plugin statemachine::{StateMachine}
let sm = StateMachine("idle")
sm.add_state("error")
sm.set_state("error")
print(sm.current_state())
Useful for recovering a machine into a known state regardless of where it currently sits:
use plugin statemachine::{StateMachine}
let sm = StateMachine("idle")
sm.add_state("paused")
sm.add_state("idle")
sm.set_state("paused")
sm.set_state("idle")
print("recovered to {sm.current_state()}")
Returns the name of the currently active state.
Returns the name of the currently active state.
use plugin statemachine::{StateMachine}
let sm = StateMachine("booting")
print(sm.current_state())
Registers a transition: when the machine is in state from and trigger is called with event, it moves to to.
Registers a transition: when the machine is in state from and trigger is
called with event, it moves to to. The target state must be registered for
the transition to actually fire at runtime.
use plugin statemachine::{StateMachine}
let sm = StateMachine("off")
sm.add_state("on")
sm.add_transition("off", "on", "power")
sm.add_transition("on", "off", "power")
The same event name can drive different moves from different states — here
power toggles in both directions:
use plugin statemachine::{StateMachine}
let sm = StateMachine("off")
sm.add_state("on")
sm.add_transition("off", "on", "power")
sm.add_transition("on", "off", "power")
sm.trigger("power")
print(sm.current_state())
sm.trigger("power")
print(sm.current_state())
Fires event on the machine.
Fires event on the machine. If the current state has a matching transition to
a registered target, the machine moves to that state and returns true.
Returns false if no valid transition exists, leaving the current state
unchanged.
use plugin statemachine::{StateMachine}
let sm = StateMachine("idle")
sm.add_state("active")
sm.add_transition("idle", "active", "start")
let moved = sm.trigger("start")
print("transitioned: {moved}")
print(sm.current_state())
Because trigger returns a bool, you can branch on whether the move actually
happened:
use plugin statemachine::{StateMachine}
let sm = StateMachine("locked")
sm.add_state("open")
sm.add_transition("locked", "open", "unlock")
if !sm.trigger("push") {
print("still {sm.current_state()} — push had no effect")
}
sm.trigger("unlock")
print(sm.current_state())
Returns a table of {to, event} entries for every transition that is valid from the current state.
Returns a table of {to, event} entries for every transition that is valid from
the current state.
use plugin statemachine::{StateMachine}
let sm = StateMachine("menu")
sm.add_state("game")
sm.add_state("options")
sm.add_transition("menu", "game", "play")
sm.add_transition("menu", "options", "settings")
for _, t in sm.available_transitions() {
print("{t["event"]} -> {t["to"]}")
}
Returns a sorted, deduplicated list of event names that can be triggered from the current state.
Returns a sorted, deduplicated list of event names that can be triggered from the current state. Handy for rendering a menu of legal actions.
use plugin statemachine::{StateMachine}
let sm = StateMachine("menu")
sm.add_state("game")
sm.add_transition("menu", "game", "play")
for _, event in sm.available_events() {
print("can trigger: {event}")
}
Returns true if firing event from the current state would produce a valid transition.
Returns true if firing event from the current state would produce a valid
transition. Use it to guard a trigger call without changing state.
use plugin statemachine::{StateMachine}
let sm = StateMachine("draft")
sm.add_state("submitted")
sm.add_transition("draft", "submitted", "submit")
if sm.can_trigger("submit") {
sm.trigger("submit")
}
print(sm.current_state())
Returns true if a state with the given name has been registered.
Returns true if a state with the given name has been registered.
use plugin statemachine::{StateMachine}
let sm = StateMachine("idle")
if !sm.has_state("error") {
sm.add_state("error")
}
print(sm.has_state("error"))
Returns the number of registered states, including the initial state.
Returns the number of registered states, including the initial state.
use plugin statemachine::{StateMachine}
let sm = StateMachine("idle")
sm.add_state("running")
print("states: {sm.state_count()}")
Returns the total number of registered transitions.
Returns the total number of registered transitions.
use plugin statemachine::{StateMachine}
let sm = StateMachine("a")
sm.add_state("b")
sm.add_transition("a", "b", "go")
print("transitions: {sm.transition_count()}")
Returns an ordered list of every state the machine has visited since creation or the last reset, including the initial state.
Returns an ordered list of every state the machine has visited since creation or
the last reset, including the initial state. Every successful trigger and
set_state appends an entry.
use plugin statemachine::{StateMachine}
let sm = StateMachine("a")
sm.add_state("b")
sm.add_transition("a", "b", "go")
sm.trigger("go")
for i, state in sm.history() {
print("{i}: {state}")
}
Returns true if the current state has no outgoing transitions, meaning the machine has reached a final/sink state from which no event can…
Returns true if the current state has no outgoing transitions, meaning the
machine has reached a final/sink state from which no event can move it.
use plugin statemachine::{StateMachine}
let sm = StateMachine("running")
sm.add_state("done")
sm.add_transition("running", "done", "finish")
sm.trigger("finish")
if sm.is_terminal() {
print("process complete")
}
Returns a table of all registered state names.
Returns a table of all registered state names. Iteration order is unspecified because states are stored in a map.
use plugin statemachine::{StateMachine}
let sm = StateMachine("idle")
sm.add_state("running")
for _, name in sm.get_states() {
print(name)
}
Returns a table of all registered transitions, each as a {from, to, event} entry.
Returns a table of all registered transitions, each as a {from, to, event}
entry.
use plugin statemachine::{StateMachine}
let sm = StateMachine("idle")
sm.add_state("active")
sm.add_transition("idle", "active", "start")
for _, t in sm.get_transitions() {
print("{t["from"]} --[{t["event"]}]--> {t["to"]}")
}
Removes a state and every transition that references it (as either source or target).
Removes a state and every transition that references it (as either source or
target). Cannot remove the current state or the initial state — both raise an
error. Returns true if a state was actually removed.
use plugin statemachine::{StateMachine}
let sm = StateMachine("idle")
sm.add_state("temp")
let removed = sm.remove_state("temp")
print("removed: {removed}")
Removes the specific transition matching all three parameters, leaving states untouched.
Removes the specific transition matching all three parameters, leaving states
untouched. Returns true if a matching transition was found and removed.
use plugin statemachine::{StateMachine}
let sm = StateMachine("idle")
sm.add_state("active")
sm.add_transition("idle", "active", "start")
let gone = sm.remove_transition("idle", "active", "start")
print("removed: {gone}")
Returns the machine to its initial state and clears the history (re-seeding it with just the initial state), while keeping all registered…
Returns the machine to its initial state and clears the history (re-seeding it with just the initial state), while keeping all registered states and transitions intact.
use plugin statemachine::{StateMachine}
let sm = StateMachine("idle")
sm.add_state("active")
sm.add_transition("idle", "active", "start")
sm.trigger("start")
sm.reset()
print(sm.current_state())