Module statemachine

A finite state machine implementation in Lua.

State machines are defined as a class from a config table, and then instantiated with a context table. The class validates and copies the config once, and instances are cheap to create.

Example:

local StateMachine = require "statemachine"

-- Step 1: Create a class (validates config, copies states — done once)
local DoorLock = StateMachine({
    initial_state = "locked",
    states = {
        locked = {
            enter = function(self, ctx, from) end,   -- Note: from is nil when first started!
            leave = function(self, ctx, to) end,
            step  = function(self, ctx) end,   -- return seconds, or nil for no stepping
            transitions = {
                -- The callback is also a guard: return true to allow, or nil+err to block.
                unlocked = function(self, ctx, to)
                    if not ctx.has_key then
                        return nil, "key required to unlock"
                    end
                    return true
                end,
            },
        },
        unlocked = {
            enter = function(self, ctx, from) end,
            leave = function(self, ctx, to) end,
            step  = function(self, ctx) end,
            transitions = {
                locked = function(self, ctx, to) return true end,
            },
        },
    },
})

-- Step 2: Create instances (cheap — just stores ctx and enters initial state)
local door1 = DoorLock({ count = 0, has_key = true })
local door2 = DoorLock({ count = 0 })

-- Step 3: Transition (returns nil+err if guard blocks, raises on missing path)
local ok, err = door1:transition_to("unlocked")  -- ok = true
local ok, err = door2:transition_to("unlocked")  -- ok = nil, err = "key required to unlock"

Info:

  • Copyright: Copyright (c) 2026-2026 Thijs Schreijer
  • License: MIT, see LICENSE.md.
  • Author: Thijs Schreijer

Functions

SMClass ([ctx={}]) Create a new instance from this class.
SMInstance:get_context () Get the shared context table.
SMInstance:get_current_state () Get the current state name.
SMInstance:has_transition_to (state) Check if a transition path to the given state exists from the current state.
SMInstance:step () Invoke the current state's step callback and return its result.


Functions

SMClass ([ctx={}])
Create a new instance from this class.

Parameters:

  • ctx table the shared context table (default {})

Returns:

  1. SMInstance a new state machine instance
  2. any the return value of the initial state's enter callback, or true if it returned nothing (typically this is the number of seconds after which to call the step callback, but it can be used for any purpose).
SMInstance:get_context ()
Get the shared context table.

Returns:

    table the context table
SMInstance:get_current_state ()
Get the current state name.

Returns:

    string the current state name
SMInstance:has_transition_to (state)
Check if a transition path to the given state exists from the current state. This is a static check; it does not invoke the transition callback/guard. Use transition_to to perform the actual (guarded) transition.

Parameters:

  • state string the target state name

Returns:

    boolean true if a transition path exists
SMInstance:step ()
Invoke the current state's step callback and return its result. The step callback is intended for time-driven behaviour such as timeouts and retries. By convention it returns the number of seconds the caller should wait before calling step again, or nil when no further stepping is needed.

When step calls transition_to internally it should return the result, so that the new state's requested delay (from its enter callback) is propagated back to the caller.

Returns:

    number or nil seconds until the next call, or nil if not needed
generated by LDoc 1.5.0 Last updated 2026-04-02 13:24:07