Skip to content

statemachine

stable

A finite state machine implementation with handle-based instances, supporting states, transitions, event triggering, and history tracking.

use plugin statemachine::{StateMachine, add_state, set_state, …}
19 functions Utilities
/ filter jk navigate Esc clear
Functions (19)
  1. StateMachine Creates a new state machine instance starting in initial_state.
  2. add_state Registers a new state with the given name.
  3. set_state Forcefully sets the current state to name, bypassing all transition rules.
  4. current_state Returns the name of the currently active state.
  5. add_transition Registers a transition: when the machine is in state from and trigger is called with event, it moves to to.
  6. trigger Fires event on the machine.
  7. available_transitions Returns a table of {to, event} entries for every transition that is valid from the current state.
  8. available_events Returns a sorted, deduplicated list of event names that can be triggered from the current state.
  9. can_trigger Returns true if firing event from the current state would produce a valid transition.
  10. has_state Returns true if a state with the given name has been registered.
  11. state_count Returns the number of registered states, including the initial state.
  12. transition_count Returns the total number of registered transitions.
  13. history Returns an ordered list of every state the machine has visited since creation or the last reset, including the initial state.
  14. 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…
  15. get_states Returns a table of all registered state names.
  16. get_transitions Returns a table of all registered transitions, each as a {from, to, event} entry.
  17. remove_state Removes a state and every transition that references it (as either source or target).
  18. remove_transition Removes the specific transition matching all three parameters, leaving states untouched.
  19. 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())
enespt-br