A high-performance, multi-language Hierarchical State Machine (HSM) library implementing UML 2.5 specification patterns (excluding Regions). Designed for consistency, safety, and performance across C++, Go, JavaScript/TypeScript, Python, Rust, and Zig.
The library exposes a consistent Domain Specific Kit (DSK) across all supported languages. The primitives (define, state, initial, target, transition, on, entry, exit) are identical whether you are writing C++, Go, Python, Rust, TypeScript, or Zig.
This "Learn Once, Write Anywhere" approach means:
- Cognitive Load is Low: Developers can switch between backend and frontend code without context switching the state machine logic.
- Portability is High: State machine definitions can be easily translated or even code-generated across languages.
- Review is Easier: The structure looks the same regardless of the implementation language.
All implementations follow a unified architecture:
- Instance: The state-holding object (extends/implements
HSMbase). - Model: A declarative, hierarchical definition of states and transitions.
- Context: Controls the execution lifecycle (cancellation, timeouts).
- Behaviors: Static/Pure functions for
entry,exit,effect(synchronous) andactivity(asynchronous). - Events: Typed events that trigger transitions.
This library aims for strict UML 2.5 compliance for hierarchical state machines, with one intentional deviation:
- No Orthogonal Regions: The complexity of parallel states (Regions) is avoided. Concurrent behavior should instead be achieved using Activities (async tasks) or composition via Submachines. This simplifies the runtime and reduces the surface area for bugs while maintaining expressiveness.
Uses hsm.Define with struct methods.
package main
import (
"context"
"github.com/stateforward/hsm"
)
type MyInstance struct {
hsm.HSM
count int
}
func (m *MyInstance) onEntry(ctx context.Context, _ *MyInstance, event hsm.Event) {
m.count++
}
func (m *MyInstance) DefineModel() hsm.Model {
return hsm.Define("Machine",
hsm.Initial(hsm.Target("idle")),
hsm.State("idle",
hsm.Entry(m.onEntry),
hsm.Transition(
hsm.On("next"),
hsm.Target("../active"),
),
),
hsm.State("active"),
)
}
func main() {
inst := &MyInstance{}
model := inst.DefineModel()
hsm.Start(context.Background(), inst, &model)
}Uses static model definition and static methods. Must use import * as hsm.
import * as hsm from './hsm.js';
class MyInstance extends hsm.Instance {
constructor() {
super();
this.count = 0;
}
static onEntry(ctx, inst, event) {
inst.count++;
}
static model = hsm.define("Machine",
hsm.initial(hsm.target("idle")),
hsm.state("idle",
hsm.entry(MyInstance.onEntry),
hsm.transition(
hsm.on("next"),
hsm.target("../active")
)
),
hsm.state("active")
);
}
const ctx = new hsm.Context();
hsm.start(ctx, new MyInstance(), MyInstance.model);Uses class-based definition with async behaviors.
import hsm
import asyncio
class MyInstance(hsm.Instance):
def __init__(self):
super().__init__()
self.count = 0
@staticmethod
async def on_entry(ctx, self, event):
self.count += 1
model = hsm.define("Machine",
hsm.initial(hsm.target("idle")),
hsm.state("idle",
hsm.entry(on_entry),
hsm.transition(
hsm.on("next"),
hsm.target("../active")
)
),
hsm.state("active")
)
async def main():
ctx = hsm.Context()
inst = MyInstance()
await hsm.start(ctx, inst, MyInstance.model)Uses macros define!, state!, transition! for compile-time optimization.
use rust::*;
struct MyInstance {
count: i32,
}
impl Instance for MyInstance {
// ... impl details
}
#[tokio::main]
async fn main() -> Result<()> {
let model = define!("Machine",
initial!(target!("idle")),
state!("idle",
entry!(|_ctx, inst: &mut MyInstance, _evt| {
inst.count += 1;
}),
transition!(
on!("next"),
target!("../active")
)
),
state!("active")
);
let ctx = Context::new();
start(&ctx, MyInstance { count: 0 }, model)?;
Ok(())
}Uses comptime definition for zero-overhead state machines.
const std = @import("std");
const hsm = @import("hsm");
const MyInstance = struct {
base: hsm.Instance,
count: i32,
// init/deinit ...
};
fn onEntry(ctx: *hsm.Context, inst: *hsm.Instance, event: hsm.Event) void {
const self: *MyInstance = @ptrCast(@alignCast(inst));
self.count += 1;
}
pub fn main() !void {
const model = comptime hsm.define("Machine", .{
hsm.initial(hsm.target("idle")),
hsm.state("idle", .{
hsm.entry(onEntry),
hsm.transition(.{
hsm.on("next"),
hsm.target("../active")
})
}),
hsm.state("active", .{})
});
// ... start logic
}Uses cthsm for compile-time state machine generation.
#include "cthsm/cthsm.hpp"
constexpr auto model = cthsm::define("Machine",
cthsm::state("idle",
cthsm::transition(
cthsm::on("next"),
cthsm::target("active")
)
),
cthsm::state("active")
);
int main() {
cthsm::compile<model> machine;
cthsm::Instance inst;
machine.start(inst);
}Each language submodule contains its own build and test instructions.
- Go:
cd go && go test ./... - JavaScript:
cd javascript && npm install && npm test - Python:
cd python && uv sync && pytest - Rust:
cd rust && cargo test - Zig:
cd zig && zig build test - C++:
cd cpp && mkdir build && cd build && cmake .. && make