Statemachine
A table-driven finite state machine with guards, entry and exit callbacks, and transition actions.
Usage#
Define the states and transitions, then initialize a StateMachine handle. The machine borrows both tables and the optional context pointer; keep them alive while the machine is in use. It performs no allocation and needs no destroy call.
enum { ST_IDLE, ST_RUNNING };
enum { EV_START, EV_STOP };
const SmState states[] = {
{ ST_IDLE, NULL, NULL },
{ ST_RUNNING, NULL, NULL },
};
const SmTransition transitions[] = {
{ ST_IDLE, EV_START, ST_RUNNING, NULL, NULL },
{ ST_RUNNING, EV_STOP, ST_IDLE, NULL, NULL },
};
StateMachine sm;
if (sm_init(&sm, states, 2, ST_IDLE, transitions, 2, NULL) == 0) {
sm_handle_event(&sm, EV_START);
/* sm_current_state(&sm) == ST_RUNNING */
}
Add callbacks to the tables when states need entry or exit work, or when a transition needs a guard or action. Callbacks receive the context pointer and the event passed to sm_handle_event. They must not call sm_handle_event on the same machine.
Structs#
SmState#
Describes a single state and its optional entry/exit callbacks.
typedef struct {
int id;
void (*entry)(void *ctx, int event);
void (*exit)(void *ctx, int event);
} SmState;
Members:
id: Integer identifying this state. Typically an enum valueentry: Optional callback invoked when the machine enters this state. Receives the user context and the event that caused the transitionexit: Optional callback invoked when the machine leaves this state. Receives the user context and the event that caused the transition
SmTransition#
One row of the transition table. Defines what happens when a specific event occurs in a specific state.
typedef struct {
int source;
int event;
int destination;
int (*guard)(void *ctx, int event);
void (*action)(void *ctx, int event);
} SmTransition;
Members:
source: The state this transition applies toevent: The event that triggers this transitiondestination: The state to move toguard: Optional function that must return nonzero for the transition to proceed. IfNULL, the transition is unconditionalaction: Optional function called during the transition, after the source state's exit callback and before the destination state's entry callback
StateMachine#
The state machine instance.
typedef struct {
const SmState *state_table;
size_t state_table_count;
const SmTransition *transition_table;
size_t transition_table_count;
int current_state;
void *ctx;
} StateMachine;
Members:
state_table: Pointer to an array ofSmStatedefinitionsstate_table_count: Number of entries instate_tabletransition_table: Pointer to an array ofSmTransitiondefinitionstransition_table_count: Number of entries intransition_tablecurrent_state: The ID of the current statectx: User-provided context pointer passed to all callbacks
Functions#
sm_init#
Initializes the state machine with the given state and transition tables. The initial_state must match the id of a state in the states array. States are looked up by ID, not by array index; IDs need not be contiguous. State IDs must be unique and cannot be SM_ANY_STATE. Every transition source must be a state id or SM_ANY_STATE. Every destination must name a real state; SM_ANY_STATE is not a valid destination. Validation runs before any write. A failed sm_init leaves sm untouched. The initial state's entry callback is not invoked. Returns 0 on success, -1 on error (NULL machine or table pointers, zero counts, duplicate or reserved state IDs, or unknown state IDs).
int sm_init(
StateMachine *sm,
const SmState *states,
size_t state_count,
int initial_state,
const SmTransition *transitions,
size_t transition_count,
void *ctx
);sm_handle_event#
Processes an event. Scans the transition table for the first entry matching the current state and event whose guard (if any) returns nonzero. SM_ANY_STATE matches any current state. SM_ANY_EVENT matches any event. Put specific rows before wildcards; the first match wins. When a match is found, the machine runs:
- Source state's
exitcallback - Transition's
actioncallback - State change to
destination - Destination state's
entrycallback
Self-transitions also run exit, action, and entry callbacks. The machine must have been initialized successfully before handling an event.
Returns 0 if a transition was taken, 1 if no matching transition was found (state unchanged), -1 if sm is NULL.
int sm_handle_event(StateMachine *sm, int event);
/* Usage: typical super-loop */
while (1) {
int event = poll_events();
sm_handle_event(&sm, event);
}Macros#
SM_ANY_STATE / SM_ANY_EVENT#
Wildcard source and event. Destination cannot be SM_ANY_STATE. Values are INT_MIN; do not use that as a real state or event id.
#define SM_ANY_STATE INT_MIN
#define SM_ANY_EVENT INT_MIN
/* Usage: one FAULT row instead of one per state */
{ SM_ANY_STATE, EV_FAULT, ST_ERROR, NULL, log_fault },sm_current_state#
Returns the current state ID.
#define sm_current_state(sm) ((sm)->current_state)