A lightweight, type-safe statechart implementation in Go using generics. Supports hierarchical states, parallel states, history states, guards, and actions in 272 lines of code.
- Generic Type Safety: Use any comparable type for states and events, any type for context
- Hierarchical States: Nested states with behavioral inheritance
- Parallel States: Multiple concurrent state regions
- History States: Remember last active child state
- Guards and Actions: Conditional transitions with side effects
- Thread Safe: Concurrent access protection with RWMutex
- Compile-time Definitions: Define state structure at compile time
- Runtime Flexibility: Dynamic event processing and state transitions
- State IDs (S comparable): Any comparable type (string, int, custom types)
- Event Types (E comparable): Any comparable type for events
- Context Data (C any): Any type for user-defined context/extended state
- Type Safety: Compile-time checking prevents invalid state/event combinations
- Performance: No reflection or type assertions needed
- Reusability: Same library works with different type combinations
- Zero Allocation: Struct-based generics avoid interface boxing
The statechart runtime is the execution engine that orchestrates state machine behavior:
- State Management: Tracks current active state and maintains hierarchical relationships
- Event Processing: Receives events and finds applicable transitions based on current state
- Transition Execution: Coordinates exit actions, transition actions, and entry actions
- Context Preservation: Maintains user-defined state data across transitions
- Hierarchy Navigation: Supports behavioral inheritance where child states inherit parent transitions
- Concurrency Control: Thread-safe operations for multi-goroutine environments
The runtime fits in ~270 LOC by focusing on essential statechart semantics:
- Compile-time Structure: States and transitions defined at compile time
- Runtime Execution: Dynamic event processing and state changes
- Memory Efficient: Direct struct allocation, no object pools needed for this size
- Type Safe: Generics eliminate runtime type checking
package main
import (
    "context"
    "github.com/user/statechart"
)
// Define your types
type MyState string
type MyEvent string
type MyContext struct {
    Counter int
}
func main() {
    // Create runtime with initial context
    ctx := statechart.Context[MyContext](MyContext{Counter: 0})
    runtime := statechart.NewRuntime[MyState, MyEvent, MyContext](ctx)
    
    // Add states
    runtime.AddState(
        statechart.StateID[MyState]("idle"), 
        statechart.SimpleState, 
        statechart.StateID[MyState](""),
    )
    
    // Add transitions with guards and actions
    runtime.AddTransition(
        statechart.StateID[MyState]("idle"),
        statechart.StateID[MyState]("active"),
        statechart.EventType[MyEvent]("start"),
        func(ctx context.Context, from statechart.StateID[MyState], event statechart.EventType[MyEvent], context *statechart.Context[MyContext]) bool {
            return (*context).Counter < 10 // Guard condition
        },
        func(ctx context.Context, from, to statechart.StateID[MyState], event statechart.EventType[MyEvent], context *statechart.Context[MyContext]) error {
            (*context).Counter++
            return nil
        },
    )
    
    // Start and use
    runtime.SetInitialState(statechart.StateID[MyState]("idle"))
    runtime.Start(context.Background())
    runtime.SendEvent(context.Background(), statechart.EventType[MyEvent]("start"))
}- Runtime[S, E, C]: Main statechart runtime engine
- State[S, E, C]: Individual state with hierarchy and actions
- Transition[S, E, C]: State transition with guard and action
- Guard[S, E, C]: Function type for transition conditions
- Action[S, E, C]: Function type for side effects
- SimpleState: Basic state with no children
- CompositeState: Hierarchical state with child states
- ParallelState: Concurrent state regions
- HistoryState: Remembers last active child state
- AddState(): Define states with hierarchy
- AddTransition(): Define transitions with guards/actions
- SendEvent(): Process events
- IsInState(): Check current state (including ancestors)
- GetContext(): Access user context data
See example/main.go for a complete door controller example demonstrating:
- Hierarchical states (Closed -> Locked/Unlocked)
- Guards (can't open when locked)
- Actions (logging, context updates)
- State introspection
- Context management
go get github.com/user/statechartMIT License - see LICENSE file for details.