cAASM is a single-header C99 library for FSM definition with powerful DSL and a callbacks system.
It's an experiment of bringing Ruby's AASM into C. For the complete documentation, go to their repository. Things like Guards callbacks and others are better described there.
caasm.h- Main library file. You must include it :)caasm_dsl.h- Pretty scoped macros for eliminating the boilerplate.
#define AASM_OPTIMIZE_STATES_LOOKUP: reduces state search complexity from O(N) to O(1).#define AASM_OPTIMIZE_EVENTS_LOOKUP: reduces event search complexity from O(N) to O(1).#define AASM_OPTIMIZE_TRANSITIONS_LOOKUP: reduces transition search complexity from O(N) to O(1), imposing the constraint: only one transition from each state-event pair.
Run them with gcc job.c -o j.exe && ./j.exe
job.c: Separate arrays for states, events, transitions. Begin with this example.job_optimized.c: The same example as previous, but with configuration for O(1) states, events and transitions lookups.job_inplace.c: In‑place FSM definition using compound literals. (Check this one to compare with the next example)job_dsl.c: Fancy macros eliminating all of the boilerplate.job_dsl_w_callbacks.c: Callbacks usage example.
The Ruby's AASM Job code:
class Job
include AASM
aasm do
state :sleeping, initial: true
state :running, :cleaning
event :run do
transitions from: :sleeping, to: :running
end
event :clean do
transitions from: :running, to: :cleaning
end
event :sleep do
transitions from: [:running, :cleaning], to: :sleeping
end
end
endThe C AASM Job code:
#include "caasm.h"
enum State {
STATE_SLEEPING,
STATE_RUNNING,
STATE_CLEANING,
};
enum Event {
EVENT_RUN,
EVENT_CLEAN,
EVENT_SLEEP,
};
#include "caasm_dsl.h"
static AASM_Runtime runtime = {
AASM_STATES(
INITIAL_STATE(SLEEPING),
STATE(RUNNING),
STATE(CLEANING),
),
AASM_EVENTS(
EVENT(RUN,
TRANSITIONS({FROM(SLEEPING), TO(RUNNING)})
),
EVENT(CLEAN,
TRANSITIONS({FROM(RUNNING), TO(CLEANING)})
),
EVENT(SLEEP,
TRANSITIONS({FROM(RUNNING, CLEANING), TO(SLEEPING)})
),
),
};
#include "caasm_dsl.h"
int main(void) {
char *err = NULL;
bool ok = aasm_init(&runtime, NULL, &err);
bool transition_occurred = aasm_fire_event(&runtime, EVENT_RUN); // true SLEEPING -> RUNNING
transition_occured = aasm_fire_event(&runtime, EVENT_CLEAN); // true RUNNING -> CLEANING
transition_occured = aasm_fire_event(&runtime, EVENT_RUN); // false
transition_occured = aasm_fire_event(&runtime, EVENT_SLEEP); // true CLEANING -> SLEEPING
}FSM with callbacks (All callbacks are optional)
class Job
include AASM
aasm do
state :sleeping, initial: true, before_enter: :do_something
state :running, before_enter: :state_running_before_enter
state :cleaning, before_enter: :state_cleaning_before_enter
after_all_transitions :log_status_change
before_all_events :before_all_events
after_all_events :after_all_events
event :run, before: :event_run_before, after: :notify_somebody do
transitions from: :sleeping, to: :running, after: :transition_after_run
end
event :clean do
transitions from: :running, to: :cleaning, after: :log_run_time
end
event :sleep, after: :event_sleep_after do
transitions from: [:running, :cleaning], to: :sleeping
end
end
end#include "caasm_dsl.h"
static AASM_Runtime runtime = {
AASM_STATES(
INITIAL_STATE(SLEEPING, BEFORE_ENTER(do_something)),
STATE(RUNNING, BEFORE_ENTER(state_running_before_enter)),
STATE(CLEANING, BEFORE_ENTER(state_cleaning_before_enter))
),
.after_all_transitions = log_status_change,
.before_all_events = before_all_events,
.after_all_events = after_all_events
AASM_EVENTS(
EVENT(RUN, BEFORE(event_run_before), AFTER(notify_somebody),
TRANSITIONS({FROM(SLEEPING), TO(RUNNING), AFTER(transition_after_run)})),
EVENT(CLEAN,
TRANSITIONS({FROM(RUNNING), TO(CLEANING), AFTER(log_run_time)})),
EVENT(SLEEP, AFTER(event_sleep_after),
TRANSITIONS({FROM(RUNNING, CLEANING), TO(SLEEPING)})),
),
};
#include "caasm_dsl.h"-
AASM_Runtime
- Fields:
- States (array of states)
- Events (array of events)
- Callbacks:
- before_all_events
- after_all_transitions
- after_all_events
- Fields:
-
AASM_State
- Fields:
- id (AASM_State_ID)
- is_initial (bool)
- Callbacks:
- before_enter
- enter
- after_enter
- before_exit
- exit
- after_exit
- Fields:
-
AASM_Event
- Fields:
- id (AASM_Event_ID)
- Transitions (array of transitions)
- Callbacks:
- before
- guards
- after
- Fields:
-
AASM_Transition
- Fields:
- from (array of AASM_State_ID)
- to (AASM_State_ID)
- Callbacks:
- guards
- after
- Fields:
The original library contains numerous callbacks. The AI claims that all of them can be useful.
| Callback / Step | Description | Notes |
|---|---|---|
before_all_events |
Global hook before any event fires; useful for logging, metrics, or setup common to all events. | Runs regardless of which event is triggered. |
event.before |
Event‑specific preparation logic before guards; useful for setting context or validation. | |
event.guards |
Conditions that must pass for the event to proceed; e.g., user permissions, system status. | If false → abort, no state change. |
transition.guards |
Transition‑specific conditions; e.g., business rules for a particular state change. | If false → abort. |
old_state.before_exit |
Preparation before leaving the old state; validation or setup before cleanup. | |
old_state.exit |
Actual exit action for the old state; resource cleanup, stopping timers, releasing locks. | |
after_all_transitions |
Runs after transition logic but before state update; logging transition details (from/to). | |
transition.after |
Transition‑specific logic after the transition; actions for this particular state change. | |
new_state.before_enter |
Preparation before entering the new state; validation or setup before initialization. | |
new_state.enter |
Actual enter action for the new state; resource acquisition, starting timers, acquiring locks. | |
| State Update (not a callback) | Actual state change occurs here. The current_state is updated from old to new. |
Critical point in the flow; happens between new_state.enter and old_state.after_exit. |
old_state.after_exit |
Cleanup after leaving the old state, runs after the state update. | |
new_state.after_enter |
Cleanup after entering the new state, runs after the state update. | |
event.after |
Event‑specific cleanup after the transition completes; notifications or side effects. | |
after_all_events |
Global cleanup hook after any event completes; final logging, metrics, or cleanup. |