hsm

package module
v1.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 2, 2026 License: MIT Imports: 16 Imported by: 0

README

package hsm

import "github.com/stateforward/hsm.go"

Package hsm provides a powerful hierarchical state machine (HSM) implementation for Go.

Overview

It enables modeling complex state-driven systems with features like hierarchical states, entry/exit actions, guard conditions, and event-driven transitions. The implementation follows the Unified DSK (Domain Specific Kit) specification, ensuring consistency across platforms.

Features

  • Hierarchical States: Support for nested states and regions.
  • Event-Driven: Asynchronous event processing with context propagation.
  • Guards & Actions: Flexible functional definitions for transition guards and state actions.
  • Type Safe: Generics-based implementation for state context.

Usage

Define your state machine structure and behavior using the declarative builder pattern:

type MyHSM struct {
    hsm.HSM
    counter int
}

model := hsm.Define(
    "example",
    hsm.State("foo"),
    hsm.State("bar"),
    hsm.Transition(
        hsm.Trigger("moveToBar"),
        hsm.Source("foo"),
        hsm.Target("bar"),
    ),
    hsm.Initial("foo"),
)

// Start the state machine
sm := hsm.Start(context.Background(), &MyHSM{}, &model)
sm.Dispatch(hsm.Event{Name: "moveToBar"})
  • ErrNilHSM, ErrInvalidState, ErrMissingHSM
  • InitialEvent, ErrorEvent, AnyEvent, FinalEvent, InfiniteDuration
  • Keys
  • NullKind, ElementKind, NamespaceKind, VertexKind, ConstraintKind, BehaviorKind, ConcurrentKind, StateMachineKind, StateKind, RegionKind, TransitionKind, InternalKind, ExternalKind, LocalKind, SelfKind, EventKind, TimeEventKind, CompletionEventKind, ErrorEventKind, PseudostateKind, InitialKind, FinalStateKind, ChoiceKind, CustomKind
  • closedChannel
  • empty
  • func AfterDispatch(ctx context.Context, hsm Instance, event Event) <-chan struct{}
  • func AfterEntry(ctx context.Context, hsm Instance, state string) <-chan struct{}
  • func AfterExecuted(ctx context.Context, hsm Instance, state string) <-chan struct{}
  • func AfterExit(ctx context.Context, hsm Instance, state string) <-chan struct{}
  • func AfterProcess(ctx context.Context, hsm Instance, maybeEvent ...Event) <-chan struct{}
  • func DispatchAll(ctx context.Context, event Event) <-chan struct{} — DispatchAll sends an event to all state machine instances in the current context.
  • func DispatchTo(ctx context.Context, event Event, maybeIds ...string) <-chan struct{}
  • func Dispatch[T context.Context](ctx T, hsm Instance, event Event) <-chan struct{} — Dispatch sends an event to a specific state machine instance.
  • func ID(hsm Instance) string
  • func IsAncestor(current, target string) bool
  • func LCA(a, b string) string — LCA finds the Lowest Common Ancestor between two qualified state names in a hierarchical state machine.
  • func Match(value string, patterns ...string) bool — Match provides a simple interface, handling basic cases directly and delegating complex matching to the match function.
  • func Name(hsm Instance) string
  • func New[T Instance](sm T, model *Model, maybeConfig ...Config) T
  • func QualifiedName(hsm Instance) string
  • func Restart(ctx context.Context, hsm Instance, maybeData ...any) <-chan struct{}
  • func Start[T Instance](ctx context.Context, sm T, maybeData ...any) T
  • func Started[T Instance](ctx context.Context, sm T, model *Model, maybeConfig ...Config) T — Started creates and starts a new state machine instance with the given model and configuration.
  • func Stop(ctx context.Context, hsm Instance) <-chan struct{} — Stop gracefully stops a state machine instance.
  • func apply(model *Model, stack []Element, partials ...RedefinableElement)
  • func buildCaches(model *Model)
  • func cleanup[T Instance](ctx context.Context, sm *hsm[T], element Element)
  • func getFunctionName(fn any) string
  • func get[T Element](model *Model, name string) T
  • func parse(value, pattern string) bool — parse implements wildcard matching using a goto-based iterative approach.
  • func traceback(maybeError ...error) func(err error)
  • type Config — Config provides configuration options for state machine initialization.
  • type Element
  • type EventDetail
  • type Event
  • type Expression
  • type HSM
  • type Instance — Instance represents an active state machine instance that can process events and track state.
  • type Model — Model represents the complete state machine model definition.
  • type Operation
  • type RedefinableElement — RedefinableElement is a function type that modifies a Model by adding or updating elements.
  • type Snapshot
  • type active
  • type after
  • type behavior
  • type constraint
  • type ctx
  • type element
  • type hsm
  • type instance
  • type key
  • type mutex
  • type paths
  • type queue
  • type state
  • type timeouts
  • type transition
  • type vertex
Variables
NullKind, ElementKind, NamespaceKind, VertexKind, ConstraintKind, BehaviorKind, ConcurrentKind, StateMachineKind, StateKind, RegionKind, TransitionKind, InternalKind, ExternalKind, LocalKind, SelfKind, EventKind, TimeEventKind, CompletionEventKind, ErrorEventKind, PseudostateKind, InitialKind, FinalStateKind, ChoiceKind, CustomKind
var (
	NullKind            = kind.Make()
	ElementKind         = kind.Make()
	NamespaceKind       = kind.Make(ElementKind)
	VertexKind          = kind.Make(ElementKind)
	ConstraintKind      = kind.Make(ElementKind)
	BehaviorKind        = kind.Make(ElementKind)
	ConcurrentKind      = kind.Make(BehaviorKind)
	StateMachineKind    = kind.Make(ConcurrentKind, NamespaceKind)
	StateKind           = kind.Make(VertexKind, NamespaceKind)
	RegionKind          = kind.Make(ElementKind)
	TransitionKind      = kind.Make(ElementKind)
	InternalKind        = kind.Make(TransitionKind)
	ExternalKind        = kind.Make(TransitionKind)
	LocalKind           = kind.Make(TransitionKind)
	SelfKind            = kind.Make(TransitionKind)
	EventKind           = kind.Make(ElementKind)
	TimeEventKind       = kind.Make(EventKind)
	CompletionEventKind = kind.Make(EventKind)
	ErrorEventKind      = kind.Make(CompletionEventKind)
	PseudostateKind     = kind.Make(VertexKind)
	InitialKind         = kind.Make(PseudostateKind)
	FinalStateKind      = kind.Make(StateKind)
	ChoiceKind          = kind.Make(PseudostateKind)
	CustomKind          = kind.Make(ElementKind)
)
ErrNilHSM, ErrInvalidState, ErrMissingHSM
var (
	ErrNilHSM       = errors.New("hsm is nil")
	ErrInvalidState = errors.New("invalid state")
	ErrMissingHSM   = errors.New("missing hsm in context")
)
InitialEvent, ErrorEvent, AnyEvent, FinalEvent, InfiniteDuration
var (
	InitialEvent = Event{
		Name: "hsm_initial",
		Kind: CompletionEventKind,
	}
	ErrorEvent = Event{
		Name: "hsm_error",
		Kind: ErrorEventKind,
	}
	AnyEvent = Event{
		Name: "*",
		Kind: EventKind,
	}
	FinalEvent = Event{
		Name: "hsm_final",
		Kind: CompletionEventKind,
	}
	InfiniteDuration = time.Duration(-1)
)
Keys
var Keys = struct {
	Instances key[*atomic.Pointer[[]Instance]]
	Owner     key[Instance]
	HSM       key[HSM]
}{
	Instances: key[*atomic.Pointer[[]Instance]]{},
	Owner:     key[Instance]{},
	HSM:       key[HSM]{},
}
closedChannel
var closedChannel = func() chan struct{} {
	done := make(chan struct{})
	close(done)
	return done
}()
empty
var empty = Event{}
Functions
AfterDispatch
func AfterDispatch(ctx context.Context, hsm Instance, event Event) <-chan struct{}
AfterEntry
func AfterEntry(ctx context.Context, hsm Instance, state string) <-chan struct{}
AfterExecuted
func AfterExecuted(ctx context.Context, hsm Instance, state string) <-chan struct{}
AfterExit
func AfterExit(ctx context.Context, hsm Instance, state string) <-chan struct{}
AfterProcess
func AfterProcess(ctx context.Context, hsm Instance, maybeEvent ...Event) <-chan struct{}
Dispatch
func Dispatch[T context.Context](ctx T, hsm Instance, event Event) <-chan struct{}

Dispatch sends an event to a specific state machine instance. Returns a channel that closes when the event has been fully processed.

Example:

sm := hsm.Start(...)
done := sm.Dispatch(hsm.Event{Name: "start"})
<-done // Wait for event processing to complete
DispatchAll
func DispatchAll(ctx context.Context, event Event) <-chan struct{}

DispatchAll sends an event to all state machine instances in the current context. Returns a channel that closes when all instances have processed the event. DispatchAll sends an event to all state machine instances in the current context. Returns a channel that closes when all instances have processed the event.

Example:

sm1 := hsm.Start(...)
sm2 := hsm.Start(...)
done := hsm.DispatchAll(context.Background(), hsm.Event{Name: "globalEvent"})
<-done // Wait for all instances to process the event
DispatchTo
func DispatchTo(ctx context.Context, event Event, maybeIds ...string) <-chan struct{}
ID
func ID(hsm Instance) string
IsAncestor
func IsAncestor(current, target string) bool
LCA
func LCA(a, b string) string

LCA finds the Lowest Common Ancestor between two qualified state names in a hierarchical state machine. It takes two qualified names 'a' and 'b' as strings and returns their closest common ancestor.

For example:

  • LCA("/s/s1", "/s/s2") returns "/s"
  • LCA("/s/s1", "/s/s1/s11") returns "/s/s1"
  • LCA("/s/s1", "/s/s1") returns "/s/s1"
Match
func Match(value string, patterns ...string) bool

Match provides a simple interface, handling basic cases directly and delegating complex matching to the match function.

Name
func Name(hsm Instance) string
New
func New[T Instance](sm T, model *Model, maybeConfig ...Config) T
QualifiedName
func QualifiedName(hsm Instance) string
Restart
func Restart(ctx context.Context, hsm Instance, maybeData ...any) <-chan struct{}
Start
func Start[T Instance](ctx context.Context, sm T, maybeData ...any) T
Started
func Started[T Instance](ctx context.Context, sm T, model *Model, maybeConfig ...Config) T

Started creates and starts a new state machine instance with the given model and configuration. The state machine will begin executing from its initial state.

Example:

model := hsm.Define(...)
sm := hsm.Started(context.Background(), &MyHSM{}, &model, hsm.Config{
    Trace: func(ctx context.Context, step string, data ...any) (context.Context, func(...any)) {
        log.Printf("Step: %s, Data: %v", step, data)
        return ctx, func(...any) {}
    },
    Id: "my-hsm-1",
})
Stop
func Stop(ctx context.Context, hsm Instance) <-chan struct{}

Stop gracefully stops a state machine instance. It cancels any running activities and prevents further event processing.

Example:

sm := hsm.Start(...)
// ... use state machine ...
hsm.Stop(sm)
apply
func apply(model *Model, stack []Element, partials ...RedefinableElement)
buildCaches
func buildCaches(model *Model)
cleanup
func cleanup[T Instance](ctx context.Context, sm *hsm[T], element Element)
get
func get[T Element](model *Model, name string) T
getFunctionName
func getFunctionName(fn any) string
parse
func parse(value, pattern string) bool

parse implements wildcard matching using a goto-based iterative approach. It supports the '*' wildcard, which matches zero or more characters.

traceback
func traceback(maybeError ...error) func(err error)

type Config

type Config struct {
	// ID is a unique identifier for the state machine instance.
	ID string
	// ActivityTimeout is the timeout for the state activity to terminate.
	ActivityTimeout time.Duration
	// Name is the name of the state machine.
	Name string
	// Data to be passed during initialization
	Data any
}

Config provides configuration options for state machine initialization.

type Element

type Element interface {
	Id() string
	Kind() uint64
	Owner() string
	QualifiedName() string
	Name() string
}
Functions returning Element
find
func find(stack []Element, maybeKinds ...uint64) Element

type Event

type Event struct {
	Kind   uint64 `xml:"kind,attr" json:"kind"`
	Name   string `xml:"name,attr" json:"name"`
	ID     string `xml:"id,attr" json:"id"`
	Source string `xml:"source,attr,omitempty" json:"source,omitempty"`
	Target string `xml:"target,attr,omitempty" json:"target,omitempty"`
	Data   any    `xml:"data" json:"data"`
	Schema any    `xml:"schema" json:"schema"`
}
Methods
Event.WithData
func () WithData(data any) Event
Event.WithDataAndID
func () WithDataAndID(data any, id string) Event

type EventDetail

type EventDetail struct {
	Event  string
	Target string
	Guard  bool
	Schema any
}

type Expression

type Expression[T Instance] func(ctx context.Context, hsm T, event Event) bool

type HSM

type HSM struct {
	instance
}
Methods
HSM.bind
func () bind(instance Instance)

type Instance

type Instance interface {
	// State returns the current state's qualified name.
	State() string
	Context() context.Context
	// non exported
	channels() *after
	takeSnapshot() Snapshot
	wait() <-chan struct{}
	start(ctx context.Context, instance Instance, event *Event)
	dispatch(ctx context.Context, event Event) <-chan struct{}
	bind(instance Instance)
	stop(ctx context.Context) <-chan struct{}
	restart(ctx context.Context, maybeData ...any) <-chan struct{}
}

Instance represents an active state machine instance that can process events and track state. It provides methods for event dispatch and state management.

Functions returning Instance
FromContext
func FromContext(ctx context.Context) (Instance, bool)

FromContext retrieves a state machine instance from a context. Returns the instance and a boolean indicating whether it was found.

Example:

if sm, ok := hsm.FromContext(ctx); ok {
    log.Printf("Current state: %s", sm.State())
}
InstancesFromContext
func InstancesFromContext(ctx context.Context) ([]Instance, bool)

type Model

type Model struct {
	state
	members  map[string]Element
	events   map[string]*Event
	elements []RedefinableElement
	// TransitionMap provides fast lookup of transitions by state and event name
	TransitionMap map[string]map[string][]*transition // stateQualifiedName -> eventName -> transitions
	// DeferredMap provides fast lookup of deferred events by state
	DeferredMap map[string]map[string]struct{} // stateQualifiedName -> Set<deferredEventNames>
}

Model represents the complete state machine model definition. It contains the root state and maintains a namespace of all elements.

Functions returning Model
Define
func Define(name string, redefinableElements ...RedefinableElement) Model

Define creates a new state machine model with the given name and elements. The first argument can be either a string name or a RedefinableElement. Additional elements are added to the model in the order they are specified.

Example:

model := hsm.Define(
    "traffic_light",
    hsm.State("red"),
    hsm.State("yellow"),
    hsm.State("green"),
    hsm.Initial("red")
)
Methods
Model.Activities
func () Activities() []string
Model.Entry
func () Entry() []string
Model.Exit
func () Exit() []string
Model.Id
func () Id() string
Model.Kind
func () Kind() uint64
Model.Members
func () Members() map[string]Element
Model.Name
func () Name() string
Model.Owner
func () Owner() string
Model.QualifiedName
func () QualifiedName() string
Model.Transitions
func () Transitions() []string
Model.push
func () push(partial RedefinableElement)

type Operation

type Operation[T Instance] func(ctx context.Context, hsm T, event Event)

type RedefinableElement

type RedefinableElement = func(model *Model, stack []Element) Element

RedefinableElement is a function type that modifies a Model by adding or updating elements. It's used to build the state machine structure in a declarative way.

Functions returning RedefinableElement
Activity
func Activity[T Instance](funcs ...func(ctx context.Context, hsm T, event Event)) RedefinableElement

Activity defines a long-running action that is executed while in a state. The activity is started after the entry action and stopped before the exit action.

Example:

hsm.Activity(func(ctx context.Context, hsm *MyHSM, event Event) {
    for {
        select {
        case <-ctx.Done():
            return
        case <-time.After(time.Second):
            log.Println("Activity tick")
        }
    }
})
After
func After[T Instance](expr func(ctx context.Context, hsm T, event Event) time.Duration) RedefinableElement

After creates a time-based transition that occurs after a specified duration. The duration can be dynamically computed based on the state machine's context.

Example:

hsm.Transition(
    hsm.After(func(ctx context.Context, hsm *MyHSM, event Event) time.Duration {
        return time.Second * 30
    }),
    hsm.Source("active"),
    hsm.Target("timeout")
)
Choice
func Choice[T interface{ RedefinableElement | string }](elementOrName T, partialElements ...RedefinableElement) RedefinableElement

Choice creates a pseudo-state that enables dynamic branching based on guard conditions. The first transition with a satisfied guard condition is taken.

Example:

hsm.Choice(
    hsm.Transition(
        hsm.Target("approved"),
        hsm.Guard(func(ctx context.Context, hsm *MyHSM, event Event) bool {
            return hsm.score > 700
        })
    ),
    hsm.Transition(
        hsm.Target("rejected")
    )
)
Defer
func Defer[T interface {
	string | *Event | Event
}](events ...T) RedefinableElement

Defer schedules events to be processed after the current state is exited.

Example:

hsm.Defer(hsm.Event{Name: "event_name"})
Effect
func Effect[T Instance](funcs ...func(ctx context.Context, hsm T, event Event)) RedefinableElement

Effect defines an action to be executed during a transition. The effect function is called after exiting the source state and before entering the target state.

Example:

hsm.Effect(func(ctx context.Context, hsm *MyHSM, event Event) {
    log.Printf("Transitioning with event: %s", event.Name)
})
Entry
func Entry[T Instance](funcs ...func(ctx context.Context, hsm T, event Event)) RedefinableElement

Entry defines an action to be executed when entering a state. The entry action is executed before any internal activities are started.

Example:

hsm.Entry(func(ctx context.Context, hsm *MyHSM, event Event) {
    log.Printf("Entering state with event: %s", event.Name)
})
Every
func Every[T Instance](expr func(ctx context.Context, hsm T, event Event) time.Duration) RedefinableElement

Every schedules events to be processed on an interval.

Example:

hsm.Every(func(ctx context.Context, hsm T, event Event) time.Duration {
    return time.Second * 30
})
Exit
func Exit[T Instance](funcs ...func(ctx context.Context, hsm T, event Event)) RedefinableElement

Exit defines an action to be executed when exiting a state. The exit action is executed after any internal activities are stopped.

Example:

hsm.Exit(func(ctx context.Context, hsm *MyHSM, event Event) {
    log.Printf("Exiting state with event: %s", event.Name)
})
Final
func Final(name string) RedefinableElement

Final creates a final state that represents the completion of a composite state or the entire state machine. When a final state is entered, a completion event is generated.

Example:

hsm.State("process",
    hsm.State("working"),
    hsm.Final("done"),
    hsm.Transition(
        hsm.Source("working"),
        hsm.Target("done")
    )
)
Guard
func Guard[T Instance](fn func(ctx context.Context, hsm T, event Event) bool) RedefinableElement

Guard defines a condition that must be true for a transition to be taken. If multiple transitions are possible, the first one with a satisfied guard is chosen.

Example:

hsm.Guard(func(ctx context.Context, hsm *MyHSM, event Event) bool {
    return hsm.counter > 10
})
Initial
func Initial(partialElements ...RedefinableElement) RedefinableElement

Initial defines the initial state for a composite state or the entire state machine. When a composite state is entered, its initial state is automatically entered.

Example:

hsm.State("operational",
    hsm.State("idle"),
    hsm.State("running"),
    hsm.Initial("idle")
)
On
func On[T interface{ *Event | Event }](events ...T) RedefinableElement

On defines the events that can cause a transition. Multiple events can be specified for a single transition.

Example:

hsm.Transition(
    hsm.On("start", "resume"),
    hsm.Source("idle"),
    hsm.Target("running")
)
Source
func Source[T interface{ RedefinableElement | string }](nameOrPartialElement T) RedefinableElement

Source specifies the source state of a transition. It can be used within a Transition definition.

Example:

hsm.Transition(
    hsm.Source("idle"),
    hsm.Target("running")
)
State
func State(name string, partialElements ...RedefinableElement) RedefinableElement

State creates a new state element with the given name and optional child elements. States can have entry/exit actions, activities, and transitions.

Example:

hsm.State("active",
    hsm.Entry(func(ctx context.Context, hsm *MyHSM, event Event) {
        log.Println("Entering active state")
    }),
    hsm.Activity(func(ctx context.Context, hsm *MyHSM, event Event) {
        // Long-running activity
    }),
    hsm.Exit(func(ctx context.Context, hsm *MyHSM, event Event) {
        log.Println("Exiting active state")
    })
)
Target
func Target[T interface{ RedefinableElement | string }](nameOrPartialElement T) RedefinableElement

Target specifies the target state of a transition. It can be used within a Transition definition.

Example:

hsm.Transition(
    hsm.Source("idle"),
    hsm.Target("running")
)
Transition
func Transition[T interface{ RedefinableElement | string }](nameOrPartialElement T, partialElements ...RedefinableElement) RedefinableElement

Transition creates a new transition between states. Transitions can have triggers, guards, and effects.

Example:

hsm.Transition(
    hsm.Trigger("submit"),
    hsm.Source("draft"),
    hsm.Target("review"),
    hsm.Guard(func(ctx context.Context, hsm *MyHSM, event Event) bool {
        return hsm.IsValid()
    }),
    hsm.Effect(func(ctx context.Context, hsm *MyHSM, event Event) {
        log.Println("Transitioning from draft to review")
    })
)
When
func When[T Instance](expr func(ctx context.Context, hsm T, event Event) <-chan struct{}) RedefinableElement

type Snapshot

type Snapshot struct {
	ID            string
	QualifiedName string
	State         string
	QueueLen      int
	Events        []EventDetail
}
Functions returning Snapshot
TakeSnapshot
func TakeSnapshot(ctx context.Context, hsm Instance) Snapshot

type active

type active struct {
	ctx
	cancel  context.CancelFunc
	channel chan struct{}
}

type after

type after struct {
	entered    sync.Map
	exited     sync.Map
	dispatched sync.Map
	processed  sync.Map
	executed   sync.Map
}

type behavior

type behavior[T Instance] struct {
	element
	operation Operation[T]
}
Methods
behavior.Id
func () Id() string
behavior.Kind
func () Kind() uint64
behavior.Name
func () Name() string
behavior.Owner
func () Owner() string
behavior.QualifiedName
func () QualifiedName() string

type constraint

type constraint[T Instance] struct {
	element
	expression Expression[T]
}
Methods
constraint.Id
func () Id() string
constraint.Kind
func () Kind() uint64
constraint.Name
func () Name() string
constraint.Owner
func () Owner() string
constraint.QualifiedName
func () QualifiedName() string

type ctx

type ctx = context.Context

type element

type element struct {
	kind          uint64
	qualifiedName string
	id            string
}
Methods
element.Id
func () Id() string
element.Kind
func () Kind() uint64
element.Name
func () Name() string
element.Owner
func () Owner() string
element.QualifiedName
func () QualifiedName() string

type hsm

type hsm[T Instance] struct {
	behavior[T]
	state      atomic.Value
	context    context.Context
	cancel     context.CancelFunc
	model      *Model
	active     map[string]*active
	queue      queue
	instance   T
	timeouts   timeouts
	processing mutex
	after      after
}
Methods
hsm.Context
func () Context() context.Context
hsm.Id
func () Id() string
hsm.Kind
func () Kind() uint64
hsm.Name
func () Name() string
hsm.Owner
func () Owner() string
hsm.QualifiedName
func () QualifiedName() string
hsm.State
func () State() string
hsm.activate
func () activate(ctx context.Context, element Element) *active
hsm.bind
func () bind(instance Instance)
hsm.channels
func () channels() *after
hsm.dispatch
func () dispatch(ctx context.Context, event Event) <-chan struct{}
hsm.enter
func () enter(ctx context.Context, element Element, event *Event, defaultEntry bool) Element
hsm.evaluate
func () evaluate(ctx context.Context, guard *constraint[T], event *Event) bool
hsm.execute
func () execute(ctx context.Context, element *behavior[T], event *Event)
hsm.executeAll
func () executeAll(ctx context.Context, names []string, event *Event)
hsm.exit
func () exit(ctx context.Context, element Element, event *Event)
hsm.process
func () process(ctx context.Context)
hsm.restart
func () restart(ctx context.Context, maybeData ...any) <-chan struct{}
hsm.start
func () start(ctx context.Context, instance Instance, event *Event)
hsm.stop
func () stop(ctx context.Context) <-chan struct{}
hsm.takeSnapshot
func () takeSnapshot() Snapshot
hsm.terminate
func () terminate(ctx context.Context, element Element)
hsm.transition
func () transition(ctx context.Context, current Element, transition *transition, event *Event) Element
hsm.wait
func () wait() <-chan struct{}

type instance

type instance = Instance

type key

type key[T any] struct{}

type mutex

type mutex struct {
	internal sync.RWMutex
	signal   atomic.Value
}
Methods
mutex.tryLock
func () tryLock() bool
mutex.wLock
func () wLock()
mutex.wUnlock
func () wUnlock()
mutex.wait
func () wait() <-chan struct{}

type paths

type paths struct {
	enter []string
	exit  []string
}

type queue

type queue struct {
	mutex sync.RWMutex
	lifo  []Event // lifo
	fifo  []Event // fifo

}
Methods
queue.len
func () len() int
queue.pop
func () pop() (Event, bool)
queue.push
func () push(events ...Event)

type state

type state struct {
	vertex
	initial    string
	entry      []string
	exit       []string
	activities []string
	deferred   []string
}
Methods
state.Activities
func () Activities() []string
state.Entry
func () Entry() []string
state.Exit
func () Exit() []string
state.Id
func () Id() string
state.Kind
func () Kind() uint64
state.Name
func () Name() string
state.Owner
func () Owner() string
state.QualifiedName
func () QualifiedName() string
state.Transitions
func () Transitions() []string

type timeouts

type timeouts struct {
	activity time.Duration
}

type transition

type transition struct {
	element
	source string
	target string
	guard  string
	effect []string
	events []string
	paths  map[string]paths
}
Methods
transition.Effect
func () Effect() []string
transition.Events
func () Events() []string
transition.Guard
func () Guard() string
transition.Id
func () Id() string
transition.Kind
func () Kind() uint64
transition.Name
func () Name() string
transition.Owner
func () Owner() string
transition.QualifiedName
func () QualifiedName() string
transition.Source
func () Source() string
transition.Target
func () Target() string

type vertex

type vertex struct {
	element
	transitions []string
}
Methods
vertex.Id
func () Id() string
vertex.Kind
func () Kind() uint64
vertex.Name
func () Name() string
vertex.Owner
func () Owner() string
vertex.QualifiedName
func () QualifiedName() string
vertex.Transitions
func () Transitions() []string

Documentation

Overview

Package hsm provides a powerful hierarchical state machine (HSM) implementation for Go.

Overview

It enables modeling complex state-driven systems with features like hierarchical states, entry/exit actions, guard conditions, and event-driven transitions. The implementation follows the Unified DSK (Domain Specific Kit) specification, ensuring consistency across platforms.

Features

  • **Hierarchical States**: Support for nested states and regions.
  • **Event-Driven**: Asynchronous event processing with context propagation.
  • **Guards & Actions**: Flexible functional definitions for transition guards and state actions.
  • **Type Safe**: Generics-based implementation for state context.

Usage

Define your state machine structure and behavior using the declarative builder pattern:

type MyHSM struct {
    hsm.HSM
    counter int
}

model := hsm.Define(
    "example",
    hsm.State("foo"),
    hsm.State("bar"),
    hsm.Transition(
        hsm.Trigger("moveToBar"),
        hsm.Source("foo"),
        hsm.Target("bar"),
    ),
    hsm.Initial("foo"),
)

// Start the state machine
sm := hsm.Start(context.Background(), &MyHSM{}, &model)
sm.Dispatch(hsm.Event{Name: "moveToBar"})

Index

Constants

View Source
const Version = "v1.0.1"

Variables

View Source
var (
	NullKind            = kind.Make()
	ElementKind         = kind.Make()
	NamespaceKind       = kind.Make(ElementKind)
	VertexKind          = kind.Make(ElementKind)
	ConstraintKind      = kind.Make(ElementKind)
	BehaviorKind        = kind.Make(ElementKind)
	ConcurrentKind      = kind.Make(BehaviorKind)
	StateMachineKind    = kind.Make(ConcurrentKind, NamespaceKind)
	StateKind           = kind.Make(VertexKind, NamespaceKind)
	RegionKind          = kind.Make(ElementKind)
	TransitionKind      = kind.Make(ElementKind)
	InternalKind        = kind.Make(TransitionKind)
	ExternalKind        = kind.Make(TransitionKind)
	LocalKind           = kind.Make(TransitionKind)
	SelfKind            = kind.Make(TransitionKind)
	EventKind           = kind.Make(ElementKind)
	TimeEventKind       = kind.Make(EventKind)
	CompletionEventKind = kind.Make(EventKind)
	ErrorEventKind      = kind.Make(CompletionEventKind)
	PseudostateKind     = kind.Make(VertexKind)
	InitialKind         = kind.Make(PseudostateKind)
	FinalStateKind      = kind.Make(StateKind)
	ChoiceKind          = kind.Make(PseudostateKind)
	CustomKind          = kind.Make(ElementKind)
)
View Source
var (
	ErrNilHSM       = errors.New("hsm is nil")
	ErrInvalidState = errors.New("invalid state")
	ErrMissingHSM   = errors.New("missing hsm in context")
)
View Source
var (
	InitialEvent = Event{
		Name: "hsm_initial",
		Kind: CompletionEventKind,
	}
	ErrorEvent = Event{
		Name: "hsm_error",
		Kind: ErrorEventKind,
	}
	AnyEvent = Event{
		Name: "*",
		Kind: EventKind,
	}
	FinalEvent = Event{
		Name: "hsm_final",
		Kind: CompletionEventKind,
	}
	InfiniteDuration = time.Duration(-1)
)
View Source
var Keys = struct {
	Instances key[*atomic.Pointer[[]Instance]]
	Owner     key[Instance]
	HSM       key[HSM]
}{
	Instances: key[*atomic.Pointer[[]Instance]]{},
	Owner:     key[Instance]{},
	HSM:       key[HSM]{},
}

Functions

func AfterDispatch

func AfterDispatch(ctx context.Context, hsm Instance, event Event) <-chan struct{}

func AfterEntry

func AfterEntry(ctx context.Context, hsm Instance, state string) <-chan struct{}

func AfterExecuted

func AfterExecuted(ctx context.Context, hsm Instance, state string) <-chan struct{}

func AfterExit

func AfterExit(ctx context.Context, hsm Instance, state string) <-chan struct{}

func AfterProcess

func AfterProcess(ctx context.Context, hsm Instance, maybeEvent ...Event) <-chan struct{}

func Dispatch

func Dispatch[T context.Context](ctx T, hsm Instance, event Event) <-chan struct{}

Dispatch sends an event to a specific state machine instance. Returns a channel that closes when the event has been fully processed.

Example:

sm := hsm.Start(...)
done := sm.Dispatch(hsm.Event{Name: "start"})
<-done // Wait for event processing to complete

func DispatchAll

func DispatchAll(ctx context.Context, event Event) <-chan struct{}

DispatchAll sends an event to all state machine instances in the current context. Returns a channel that closes when all instances have processed the event. DispatchAll sends an event to all state machine instances in the current context. Returns a channel that closes when all instances have processed the event.

Example:

sm1 := hsm.Start(...)
sm2 := hsm.Start(...)
done := hsm.DispatchAll(context.Background(), hsm.Event{Name: "globalEvent"})
<-done // Wait for all instances to process the event

func DispatchTo

func DispatchTo(ctx context.Context, event Event, maybeIds ...string) <-chan struct{}

func ID

func ID(hsm Instance) string

func IsAncestor

func IsAncestor(current, target string) bool

func LCA

func LCA(a, b string) string

LCA finds the Lowest Common Ancestor between two qualified state names in a hierarchical state machine. It takes two qualified names 'a' and 'b' as strings and returns their closest common ancestor.

For example: - LCA("/s/s1", "/s/s2") returns "/s" - LCA("/s/s1", "/s/s1/s11") returns "/s/s1" - LCA("/s/s1", "/s/s1") returns "/s/s1"

func Match

func Match(value string, patterns ...string) bool

Match provides a simple interface, handling basic cases directly and delegating complex matching to the match function.

func Name

func Name(hsm Instance) string

func New

func New[T Instance](sm T, model *Model, maybeConfig ...Config) T

func QualifiedName

func QualifiedName(hsm Instance) string

func Restart

func Restart(ctx context.Context, hsm Instance, maybeData ...any) <-chan struct{}

func Start

func Start[T Instance](ctx context.Context, sm T, maybeData ...any) T

func Started

func Started[T Instance](ctx context.Context, sm T, model *Model, maybeConfig ...Config) T

Started creates and starts a new state machine instance with the given model and configuration. The state machine will begin executing from its initial state.

Example:

model := hsm.Define(...)
sm := hsm.Started(context.Background(), &MyHSM{}, &model, hsm.Config{
    Trace: func(ctx context.Context, step string, data ...any) (context.Context, func(...any)) {
        log.Printf("Step: %s, Data: %v", step, data)
        return ctx, func(...any) {}
    },
    Id: "my-hsm-1",
})

func Stop

func Stop(ctx context.Context, hsm Instance) <-chan struct{}

Stop gracefully stops a state machine instance. It cancels any running activities and prevents further event processing.

Example:

sm := hsm.Start(...)
// ... use state machine ...
hsm.Stop(sm)

Types

type Config

type Config struct {
	// ID is a unique identifier for the state machine instance.
	ID string
	// ActivityTimeout is the timeout for the state activity to terminate.
	ActivityTimeout time.Duration
	// Name is the name of the state machine.
	Name string
	// Data to be passed during initialization
	Data any
}

Config provides configuration options for state machine initialization.

type Element

type Element interface {
	Id() string
	Kind() uint64
	Owner() string
	QualifiedName() string
	Name() string
}

type Event

type Event struct {
	Kind   uint64 `xml:"kind,attr" json:"kind"`
	Name   string `xml:"name,attr" json:"name"`
	ID     string `xml:"id,attr" json:"id"`
	Source string `xml:"source,attr,omitempty" json:"source,omitempty"`
	Target string `xml:"target,attr,omitempty" json:"target,omitempty"`
	Data   any    `xml:"data" json:"data"`
	Schema any    `xml:"schema" json:"schema"`
}

func (Event) WithData

func (e Event) WithData(data any) Event

func (Event) WithDataAndID

func (e Event) WithDataAndID(data any, id string) Event

type EventDetail

type EventDetail struct {
	Event  string
	Target string
	Guard  bool
	Schema any
}

type Expression

type Expression[T Instance] func(ctx context.Context, hsm T, event Event) bool

type HSM

type HSM struct {
	// contains filtered or unexported fields
}

type Instance

type Instance interface {
	// State returns the current state's qualified name.
	State() string
	Context() context.Context
	// contains filtered or unexported methods
}

Instance represents an active state machine instance that can process events and track state. It provides methods for event dispatch and state management.

func FromContext

func FromContext(ctx context.Context) (Instance, bool)

FromContext retrieves a state machine instance from a context. Returns the instance and a boolean indicating whether it was found.

Example:

if sm, ok := hsm.FromContext(ctx); ok {
    log.Printf("Current state: %s", sm.State())
}

func InstancesFromContext

func InstancesFromContext(ctx context.Context) ([]Instance, bool)

type Model

type Model struct {

	// TransitionMap provides fast lookup of transitions by state and event name
	TransitionMap map[string]map[string][]*transition // stateQualifiedName -> eventName -> transitions
	// DeferredMap provides fast lookup of deferred events by state
	DeferredMap map[string]map[string]struct{} // stateQualifiedName -> Set<deferredEventNames>
	// contains filtered or unexported fields
}

Model represents the complete state machine model definition. It contains the root state and maintains a namespace of all elements.

func Define

func Define(name string, redefinableElements ...RedefinableElement) Model

Define creates a new state machine model with the given name and elements. The first argument can be either a string name or a RedefinableElement. Additional elements are added to the model in the order they are specified.

Example:

model := hsm.Define(
    "traffic_light",
    hsm.State("red"),
    hsm.State("yellow"),
    hsm.State("green"),
    hsm.Initial("red")
)

func (*Model) Activities

func (state *Model) Activities() []string

func (*Model) Entry

func (state *Model) Entry() []string

func (*Model) Exit

func (state *Model) Exit() []string

func (*Model) Members

func (model *Model) Members() map[string]Element

type Operation

type Operation[T Instance] func(ctx context.Context, hsm T, event Event)

type RedefinableElement

type RedefinableElement = func(model *Model, stack []Element) Element

RedefinableElement is a function type that modifies a Model by adding or updating elements. It's used to build the state machine structure in a declarative way.

func Activity

func Activity[T Instance](funcs ...func(ctx context.Context, hsm T, event Event)) RedefinableElement

Activity defines a long-running action that is executed while in a state. The activity is started after the entry action and stopped before the exit action.

Example:

hsm.Activity(func(ctx context.Context, hsm *MyHSM, event Event) {
    for {
        select {
        case <-ctx.Done():
            return
        case <-time.After(time.Second):
            log.Println("Activity tick")
        }
    }
})

func After

func After[T Instance](expr func(ctx context.Context, hsm T, event Event) time.Duration) RedefinableElement

After creates a time-based transition that occurs after a specified duration. The duration can be dynamically computed based on the state machine's context.

Example:

hsm.Transition(
    hsm.After(func(ctx context.Context, hsm *MyHSM, event Event) time.Duration {
        return time.Second * 30
    }),
    hsm.Source("active"),
    hsm.Target("timeout")
)

func Choice

func Choice[T interface{ RedefinableElement | string }](elementOrName T, partialElements ...RedefinableElement) RedefinableElement

Choice creates a pseudo-state that enables dynamic branching based on guard conditions. The first transition with a satisfied guard condition is taken.

Example:

hsm.Choice(
    hsm.Transition(
        hsm.Target("approved"),
        hsm.Guard(func(ctx context.Context, hsm *MyHSM, event Event) bool {
            return hsm.score > 700
        })
    ),
    hsm.Transition(
        hsm.Target("rejected")
    )
)

func Defer

func Defer[T interface {
	string | *Event | Event
}](events ...T) RedefinableElement

Defer schedules events to be processed after the current state is exited.

Example:

hsm.Defer(hsm.Event{Name: "event_name"})

func Effect

func Effect[T Instance](funcs ...func(ctx context.Context, hsm T, event Event)) RedefinableElement

Effect defines an action to be executed during a transition. The effect function is called after exiting the source state and before entering the target state.

Example:

hsm.Effect(func(ctx context.Context, hsm *MyHSM, event Event) {
    log.Printf("Transitioning with event: %s", event.Name)
})

func Entry

func Entry[T Instance](funcs ...func(ctx context.Context, hsm T, event Event)) RedefinableElement

Entry defines an action to be executed when entering a state. The entry action is executed before any internal activities are started.

Example:

hsm.Entry(func(ctx context.Context, hsm *MyHSM, event Event) {
    log.Printf("Entering state with event: %s", event.Name)
})

func Every

func Every[T Instance](expr func(ctx context.Context, hsm T, event Event) time.Duration) RedefinableElement

Every schedules events to be processed on an interval.

Example:

hsm.Every(func(ctx context.Context, hsm T, event Event) time.Duration {
    return time.Second * 30
})

func Exit

func Exit[T Instance](funcs ...func(ctx context.Context, hsm T, event Event)) RedefinableElement

Exit defines an action to be executed when exiting a state. The exit action is executed after any internal activities are stopped.

Example:

hsm.Exit(func(ctx context.Context, hsm *MyHSM, event Event) {
    log.Printf("Exiting state with event: %s", event.Name)
})

func Final

func Final(name string) RedefinableElement

Final creates a final state that represents the completion of a composite state or the entire state machine. When a final state is entered, a completion event is generated.

Example:

hsm.State("process",
    hsm.State("working"),
    hsm.Final("done"),
    hsm.Transition(
        hsm.Source("working"),
        hsm.Target("done")
    )
)

func Guard

func Guard[T Instance](fn func(ctx context.Context, hsm T, event Event) bool) RedefinableElement

Guard defines a condition that must be true for a transition to be taken. If multiple transitions are possible, the first one with a satisfied guard is chosen.

Example:

hsm.Guard(func(ctx context.Context, hsm *MyHSM, event Event) bool {
    return hsm.counter > 10
})

func Initial

func Initial(partialElements ...RedefinableElement) RedefinableElement

Initial defines the initial state for a composite state or the entire state machine. When a composite state is entered, its initial state is automatically entered.

Example:

hsm.State("operational",
    hsm.State("idle"),
    hsm.State("running"),
    hsm.Initial("idle")
)

func On

func On[T interface{ *Event | Event }](events ...T) RedefinableElement

On defines the events that can cause a transition. Multiple events can be specified for a single transition.

Example:

hsm.Transition(
    hsm.On("start", "resume"),
    hsm.Source("idle"),
    hsm.Target("running")
)

func Source

func Source[T interface{ RedefinableElement | string }](nameOrPartialElement T) RedefinableElement

Source specifies the source state of a transition. It can be used within a Transition definition.

Example:

hsm.Transition(
    hsm.Source("idle"),
    hsm.Target("running")
)

func State

func State(name string, partialElements ...RedefinableElement) RedefinableElement

State creates a new state element with the given name and optional child elements. States can have entry/exit actions, activities, and transitions.

Example:

hsm.State("active",
    hsm.Entry(func(ctx context.Context, hsm *MyHSM, event Event) {
        log.Println("Entering active state")
    }),
    hsm.Activity(func(ctx context.Context, hsm *MyHSM, event Event) {
        // Long-running activity
    }),
    hsm.Exit(func(ctx context.Context, hsm *MyHSM, event Event) {
        log.Println("Exiting active state")
    })
)

func Target

func Target[T interface{ RedefinableElement | string }](nameOrPartialElement T) RedefinableElement

Target specifies the target state of a transition. It can be used within a Transition definition.

Example:

hsm.Transition(
    hsm.Source("idle"),
    hsm.Target("running")
)

func Transition

func Transition[T interface{ RedefinableElement | string }](nameOrPartialElement T, partialElements ...RedefinableElement) RedefinableElement

Transition creates a new transition between states. Transitions can have triggers, guards, and effects.

Example:

hsm.Transition(
    hsm.Trigger("submit"),
    hsm.Source("draft"),
    hsm.Target("review"),
    hsm.Guard(func(ctx context.Context, hsm *MyHSM, event Event) bool {
        return hsm.IsValid()
    }),
    hsm.Effect(func(ctx context.Context, hsm *MyHSM, event Event) {
        log.Println("Transitioning from draft to review")
    })
)

func When

func When[T Instance](expr func(ctx context.Context, hsm T, event Event) <-chan struct{}) RedefinableElement

type Snapshot

type Snapshot struct {
	ID            string
	QualifiedName string
	State         string
	QueueLen      int
	Events        []EventDetail
}

func TakeSnapshot

func TakeSnapshot(ctx context.Context, hsm Instance) Snapshot

Directories

Path Synopsis
kind module
muid module

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL