Skip to content

stateforward/hsm

Repository files navigation

HSM (Hierarchical State Machine)

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.

Unified API Surface

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.

Core Concepts

All implementations follow a unified architecture:

  • Instance: The state-holding object (extends/implements HSM base).
  • 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) and activity (asynchronous).
  • Events: Typed events that trigger transitions.

Compliance & Design

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.

Quick Start by Language

Go

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)
}

JavaScript / TypeScript

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);

Python

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)

Rust

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(())
}

Zig

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
}

C++

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);
}

Building & Testing

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

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published