FSM is a flexible, thread-safe implementation of finite state machines in Go. It provides a clean API for modeling complex state transitions with support for context-aware operations.
- Flexible State Definition: Create states with unique names and IDs
- Conditional Transitions: Define transitions with custom conditions using Go functions
- Start and End States: Set specific start and end states for your state machine
- Validation: Validate your state machine configuration before use
- Context-Aware: All operations respect context cancellation
- Thread-Safe: All operations are thread-safe and can be used in concurrent environments
- Minimal Dependencies: Only depends on a single external package for slice operations
go get github.com/twlvprscs/state/fsmpackage main
import (
"context"
"fmt"
"github.com/twlvprscs/state/fsm"
)
func main() {
// Create states
s1 := fsm.NewState("STATE1")
s2 := fsm.NewState("STATE2")
s3 := fsm.NewState("STATE3")
// Define transitions
t1 := s1.When("value == 'a'", func(ctx context.Context, v interface{}) (bool, error) {
if s, ok := v.(string); ok {
return s == "a", nil
}
return false, nil
}).Then(s2)
t2 := s2.When("value == 'b'", func(ctx context.Context, v interface{}) (bool, error) {
if s, ok := v.(string); ok {
return s == "b", nil
}
return false, nil
}).Then(s3)
// Create machine with transitions
machine := fsm.NewMachine(fsm.WithTransitions(t1, t2))
// Set end states
if err := machine.SetEndStates("STATE3"); err != nil {
panic(err)
}
// Use the machine
ctx := context.Background()
// Transition to STATE2
changed, err := machine.Update(ctx, "a")
if err != nil {
panic(err)
}
fmt.Printf("State changed: %v, Current state: %s\n", changed, machine.Current().Name())
// Transition to STATE3
changed, err = machine.Update(ctx, "b")
if err != nil {
panic(err)
}
fmt.Printf("State changed: %v, Current state: %s\n", changed, machine.Current().Name())
// Check if we're in an end state
if machine.IsEndState() {
fmt.Println("Reached end state:", machine.Current().Name())
}
}The interface for state objects in the finite state machine.
type State interface {
Identifier
Name() string
When(string, TriggerFunc) Transition
}The interface for transitions between states.
type Transition interface {
Identifier
Description() string
From() State
To() State
Then(State) Transition
Go(context.Context, interface{}) (bool, error)
}Function type for transition conditions.
type TriggerFunc func(context.Context, interface{}) (bool, error)func NewState(name string, options ...StateOption) StateCreates a new state with the specified name and options.
func NewMachine(opts ...Option) *machineCreates a new finite state machine with the specified options.
// Adds transitions to the machine
func WithTransitions(transitions ...Transition) Option// Sets the start state by name
func (m *machine) SetStart(name string) error
// Resets the machine to its start state
func (m *machine) Reset() error
// Sets the end states by name
func (m *machine) SetEndStates(names ...string) error
// Checks if the current state is an end state
func (m *machine) IsEndState() bool
// Adds a transition to the machine
func (m *machine) AddTransition(t Transition)
// Validates the machine configuration
func (m *machine) Validate() error
// Returns the current state
func (m *machine) Current() State
// Updates the machine state based on the provided value
func (m *machine) Update(ctx context.Context, value interface{}) (bool, error)// Returns the state name
func (s machineState) Name() string
// Creates a new transition from this state with the specified condition
func (s machineState) When(desc string, f TriggerFunc) Transition
// Returns the state ID
func (s machineState) Id() uint64// Returns the transition ID
func (e *edge) Id() uint64
// Returns the transition description
func (e *edge) Description() string
// Returns the source state
func (e *edge) From() State
// Returns the destination state
func (e *edge) To() State
// Sets the destination state
func (e *edge) Then(s State) Transition
// Evaluates the transition condition
func (e *edge) Go(ctx context.Context, v interface{}) (bool, error)The FSM package uses a few key design patterns:
-
Interface-Based Design: The core types (State, Transition) are defined as interfaces, allowing for flexible implementations.
-
Builder Pattern: The
When().Then()pattern for creating transitions provides a fluent, readable API. -
Thread Safety: The implementation uses a combination of
sync.RWMutexandatomic.Valueto ensure thread safety. -
Context Awareness: All operations that might take time accept a context parameter, allowing for cancellation and timeouts.
-
Unique IDs: Each state and transition has a unique ID generated using atomic operations, ensuring uniqueness even in concurrent scenarios.
package main
import (
"context"
"fmt"
"github.com/twlvprscs/state/fsm"
)
func main() {
// Create states
idle := fsm.NewState("IDLE")
connecting := fsm.NewState("CONNECTING")
connected := fsm.NewState("CONNECTED")
authenticating := fsm.NewState("AUTHENTICATING")
authenticated := fsm.NewState("AUTHENTICATED")
error := fsm.NewState("ERROR")
// Define transitions
transitions := []fsm.Transition{
// From IDLE
idle.When("connect requested", func(ctx context.Context, v interface{}) (bool, error) {
cmd, ok := v.(string)
return ok && cmd == "connect", nil
}).Then(connecting),
// From CONNECTING
connecting.When("connection established", func(ctx context.Context, v interface{}) (bool, error) {
evt, ok := v.(string)
return ok && evt == "connected", nil
}).Then(connected),
connecting.When("connection failed", func(ctx context.Context, v interface{}) (bool, error) {
evt, ok := v.(string)
return ok && evt == "error", nil
}).Then(error),
// From CONNECTED
connected.When("auth requested", func(ctx context.Context, v interface{}) (bool, error) {
cmd, ok := v.(string)
return ok && cmd == "authenticate", nil
}).Then(authenticating),
// From AUTHENTICATING
authenticating.When("auth succeeded", func(ctx context.Context, v interface{}) (bool, error) {
evt, ok := v.(string)
return ok && evt == "auth_success", nil
}).Then(authenticated),
authenticating.When("auth failed", func(ctx context.Context, v interface{}) (bool, error) {
evt, ok := v.(string)
return ok && evt == "auth_failure", nil
}).Then(error),
// From ERROR
error.When("retry", func(ctx context.Context, v interface{}) (bool, error) {
cmd, ok := v.(string)
return ok && cmd == "retry", nil
}).Then(idle),
// From any state
authenticated.When("disconnect", func(ctx context.Context, v interface{}) (bool, error) {
cmd, ok := v.(string)
return ok && cmd == "disconnect", nil
}).Then(idle),
connected.When("disconnect", func(ctx context.Context, v interface{}) (bool, error) {
cmd, ok := v.(string)
return ok && cmd == "disconnect", nil
}).Then(idle),
}
// Create machine
machine := fsm.NewMachine(fsm.WithTransitions(transitions...))
// Set end states
machine.SetEndStates("AUTHENTICATED", "ERROR")
// Use the machine
ctx := context.Background()
// Simulate a successful flow
events := []string{"connect", "connected", "authenticate", "auth_success"}
for _, evt := range events {
changed, _ := machine.Update(ctx, evt)
fmt.Printf("Event: %s, Changed: %v, Current: %s\n",
evt, changed, machine.Current().Name())
}
// Check if we're in an end state
if machine.IsEndState() {
fmt.Println("Reached end state:", machine.Current().Name())
}
}The FSM package includes a Graph() method (currently a placeholder) that will eventually allow you to visualize your state machine. In the meantime, you can use the PlantUML format shown in the test file to visualize your state machines.
Example PlantUML:
@startuml
S1: IDLE
S2: CONNECTING
S3: CONNECTED
S4: AUTHENTICATING
S5: AUTHENTICATED
S6: ERROR
[*] --> S1
S1 --> S2 : connect
S2 --> S3 : connected
S2 --> S6 : error
S3 --> S4 : authenticate
S4 --> S5 : auth_success
S4 --> S6 : auth_failure
S6 --> S1 : retry
S5 --> S1 : disconnect
S3 --> S1 : disconnect
@enduml
The FSM package is ideal for scenarios where you need to model complex state transitions:
- Workflow Management: Model business processes with multiple steps and conditions
- Protocol Implementations: Implement network protocols with well-defined state transitions
- UI State Management: Manage the state of complex user interfaces
- Game Development: Model game states and transitions
- Parser Implementations: Build parsers for complex grammars
- IoT Device Control: Model the behavior of IoT devices
- Transaction Processing: Model the lifecycle of transactions
- Document Processing: Track the state of documents in a workflow