statechart

package module
v0.6.1 Latest Latest
Warning

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

Go to latest
Published: May 9, 2023 License: MIT Imports: 4 Imported by: 0

README

Overview

The go-statechart is a Go library for a strong type state machine.

Supported features

  • An easy-to-use library that flows UML statechart methodology
  • Hierarchical
  • Entry, exit and transition actions
  • Guards using custom handlers
  • Type-safety
  • No reflection
  • Support for asynchronous state machine
  • Support for blocking thread-safe state machine

Todo

  • Event deferral
  • Shallow/deep history
  • Orthogonal

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func AddCustomStateReaction

func AddCustomStateReaction[E any, C any, PE EventCst[E]](from StateSetupProxy[C], reaction Reaction[E, PE])

Add a custom reaction `E` is the event type `C` is the user context (deducted) `PE` is a pointer to E (deducted) `from` is the proxy of the current state `reaction` is the custom reaction function

func AddDefer

func AddDefer[E any, C any, PE EventCst[E]](state StateSetupProxy[C])

Add a defer event reaction `E` is the event type `C` is the user context (deducted) `PE` is a pointer to E (deducted) `state` is the proxy of the current state

func AddDiscard

func AddDiscard[E any, C any, PE EventCst[E]](state StateSetupProxy[C])

Add a discard event reaction `E` is the event type `C` is the user context (deducted) `PE` is a pointer to E (deducted) `state` is the proxy of the current state

func AddInStateReaction

func AddInStateReaction[E any, C any, PE EventCst[E]](state StateSetupProxy[C], action Action[E, PE])

Add an in-state reaction `E` is the event type `C` is the user context (deducted) `PE` is a pointer to E (deducted) `state` is the proxy of the current state `action` is the action function

func AddSimpleStateTransition

func AddSimpleStateTransition[E any, S any, C any, PE EventCst[E], PS StateCst[S, C]](from StateSetupProxy[C], action Action[E, PE])

Add a simple state transition `E` is the event type `S` is the actual user state that we are going to `C` is the user context (deducted) `PE` is a pointer to E (deducted) `PS` is a pointer to `S` (deducted) `from` is the proxy of the current state `action` is the action associated with the transition (optional)

func GeneticStateSelector

func GeneticStateSelector[S any, C any, PS StateCst[S, C]](state State[C]) bool

Returns true if the state is of type `*S` This is used to find a state by Type `S` is the actual user state `C` is the user context (deducted) `PS` is pointer to S (deducted)

func GetAncestor

func GetAncestor[S any, C any, PS StateCst[S, C]](state StateProxy[C]) *S

func SetStartingState

func SetStartingState[S any, C any, PS StateCst[S, C]](state StateSetupProxy[C])

Set a starting state using a State Type as a key `S` is the actual user state `C` is the user context (deducted) `PS` is a pointer to `S` (deducted) `proxy` is the proxy to the internal state in the machine

Types

type Action

type Action[E any, PE EventCst[E]] func(event PE)

Action Type function that takes the a pointer to Event as argument `E` the event type `PE` is deducted (Pointer to E) `event` the event that triggered this action

type AsyncStateMachine

type AsyncStateMachine[C any] struct {
	// contains filtered or unexported fields
}
Example

This Example Creates an Async State Machine. Please refer to [ExampleStateMachine1] for full example, [ExampleStateMachine1], [ExampleStateMachine2]

[ExampleStateMachine]: https://pkg.go.dev/github.com/hhassoubi/go-statechart#example-StateMachine [ExampleStateMachine1]: /github.com/hhassoubi/go-statechart#example-StateMachine [ExampleStateMachine2]: go-statechart#example-StateMachine

// Refer to [https://github.com/hhassoubi/go-statechart/blob/master/async_state_machine.go]
// Create State Machine
context := MyContext{}
sm := statechart.MakeAsyncStateMachine(&context)
idleState := sm.AddState(&Idle{})
activeState := sm.AddState(&Active{})
sm.AddSubState(&Stopped{}, activeState)
sm.AddSubState(&Running{}, activeState)
sm.Initialize(idleState)

// example event dispatch
sm.DispatchEvent(&ActivateEv{})   // go for Idle to Active to Stopped
sm.DispatchEvent(&StartStopEv{})  // go from Stopped to Running
sm.DispatchEvent(&ResetEv{})      // go from Running to Active to Stopped
sm.DispatchEvent(&DeactivateEv{}) // go from Stopped to Idle
sm.Close()                        // this will wait
Output:

Reset Counter
Start Counter
Stop Counter
Reset Counter

func MakeAsyncStateMachine

func MakeAsyncStateMachine[C any](userContext_ *C) AsyncStateMachine[C]

Creates an async state machine with a user context

func (*AsyncStateMachine[C]) AddState

func (sm *AsyncStateMachine[C]) AddState(state State[C]) StateId

Adds a new State to the State Machine `state` the new state object to add returns the new stateId

func (*AsyncStateMachine[C]) AddSubState

func (sm *AsyncStateMachine[C]) AddSubState(state State[C], parentId StateId) StateId

Adds a Sub-State to the State Machine `state` the new state object to add `parentId` the parent (super state) ID

func (*AsyncStateMachine[C]) Close

func (sm *AsyncStateMachine[C]) Close()

Closes the async channel

func (*AsyncStateMachine[C]) DispatchEvent

func (sm *AsyncStateMachine[C]) DispatchEvent(event Event)

Dispatches an events to the state machine `event` The Event to dispatch No error will occur if the Event is unknown to the state machine

func (*AsyncStateMachine[C]) GenerateUml added in v0.6.0

func (sm *AsyncStateMachine[C]) GenerateUml(w io.Writer, umlSyntax UmlSyntax, diagramType UmlDiagramType)

Generates the UML diagram for the state machine, using `w` the io stream writer `umlSyntax` the generator syntax only only support PlantUML syntax for now [https://plantuml.com/state-diagram] `diagramType` the type of diagram to use for the generation

func (*AsyncStateMachine[C]) Initialize

func (sm *AsyncStateMachine[C]) Initialize(initStateId StateId)

Initializes the state machine `initStateId` the initial starting state

func (*AsyncStateMachine[C]) SetDebugLogger

func (sm *AsyncStateMachine[C]) SetDebugLogger(logger func(msg string, keysAndValues ...interface{}))

Sets the Debug Trace Logger for the state machine

type BaseAction

type BaseAction func(event Event)

the abstract Action `event` the event that triggered this action

func ToBaseAction

func ToBaseAction[E any, PE EventCst[E]](action Action[E, PE]) BaseAction

A function that convert an Action to a BaseAction. the new generated function will perform a safe down cast `action` the concrete action returns the abstract action

type EntryAction

type EntryAction func()

the state enter action

type Event

type Event interface {
	// contains filtered or unexported methods
}

The Event interface This is need needed to avoid casting interface{} to concrete Event

type EventCst

type EventCst[E any] interface {
	*E
	Event
}

Event Type Parameter Constraint. This is a trick to force event to be passed by pointer `E` is the concrete event

type EventDefault

type EventDefault struct {
}

type EventReaction

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

This is used to link an event to a custom reaction

func MakeEventReaction

func MakeEventReaction[T any, PT EventCst[T]](reaction Reaction[T, PT], doc ...UmlDocReaction) EventReaction

makes an EventReaction from a custom reaction

type ExitAction

type ExitAction func()

the state exit action

type Reaction

type Reaction[T any, PT EventCst[T]] func(PT) ReactionResult

Custom reaction function type.

type ReactionResult

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

encapsulate the result for a reaction

func Transit

func Transit[S any, C any, PS StateCst[S, C]](from StateProxy[C]) ReactionResult

func TransitWithAction

func TransitWithAction[S any, C any, E any, PS StateCst[S, C], PE EventCst[E]](from StateProxy[C], action Action[E, PE]) ReactionResult

type ResultType

type ResultType int16
const (
	// Forwards the event to the parent state
	FORWARD ResultType = iota
	// Discards the event
	DISCARD
	// Transit to a new state
	TRANSIT
	// defer the event to be posted after state change
	DEFER
)

type State

type State[C any] interface {
	Setup(proxy StateSetupProxy[C]) (EntryAction, ExitAction)
	// contains filtered or unexported methods
}

The State[C] Interface where `C` is the user context

type StateCst

type StateCst[S any, C any] interface {
	*S
	State[C]
}

State Type Parameter Constraint. `S` is the actual user state `C` is the user context

type StateDefault

type StateDefault[C any] struct {
	// contains filtered or unexported fields
}

Default implementation of State[C], This could be used to simplify the API in the concrete state

func (*StateDefault[C]) GetAncestor

func (s *StateDefault[C]) GetAncestor(state StateId) State[C]

func (*StateDefault[C]) GetContext

func (s *StateDefault[C]) GetContext() *C

func (*StateDefault[C]) Init

func (s *StateDefault[C]) Init(proxy StateProxy[C])

Initializing the state.

func (*StateDefault[C]) Proxy

func (s *StateDefault[C]) Proxy() StateProxy[C]

type StateId

type StateId int

The State identifier generated by the state machine when calling AddState

var INVALID_STATE_ID StateId = -1

func FindStateId

func FindStateId[S any, C any, PS StateCst[S, C]](proxy StateProxy[C]) StateId

Finds the state id of the state that matches the Concrete State Type `S` is the actual user state `C` is the user context (deducted) `PS` is a pointer to `S` (deducted) `proxy` is the proxy to the internal state in the machine

type StateMachine

type StateMachine[C any] struct {
	// contains filtered or unexported fields
}
Example
// MIT License: https://github.com/hhassoubi/go-statechart/blob/master/LICENSE
// Copyright (c) 2023 Hicham Hassoubi

package main

import (
	"fmt"

	"github.com/hhassoubi/go-statechart"
)

// // Events
type StartStopEv struct {
	statechart.EventDefault
}
type ActivateEv struct {
	statechart.EventDefault
}
type DeactivateEv struct {
	statechart.EventDefault
}
type ResetEv struct {
	statechart.EventDefault
}
type MyContext struct {
}

func (*MyContext) StartCounter() {
	// Start Counter Code
	fmt.Println("Start Counter")
}
func (*MyContext) StopCounter() {
	// Start Counter Code
	fmt.Println("Stop Counter")
}
func (*MyContext) ResetCounter() {
	// Start Counter Code
	fmt.Println("Reset Counter")
}

// /// Idle State
type Idle struct {
	statechart.StateDefault[MyContext]
}

func (self *Idle) Setup(proxy statechart.StateSetupProxy[MyContext]) (statechart.EntryAction, statechart.ExitAction) {
	self.Init(proxy)
	// Add transition to Active state on ActivateEv event (	nil is for no action to take)
	statechart.AddSimpleStateTransition[ActivateEv, Active](proxy, nil)
	// return nil, nill because no action is needed for Enter and Exit
	return nil, nil
}

// /// Active State
type Active struct {
	statechart.StateDefault[MyContext]
}

func (self *Active) Setup(proxy statechart.StateSetupProxy[MyContext]) (statechart.EntryAction, statechart.ExitAction) {
	self.Init(proxy)
	// Add transition to Idle state (	nil is for no action to take)
	statechart.AddSimpleStateTransition[DeactivateEv, Idle](proxy, nil)
	// Add transition to Active state (	nil is for no action to take)
	statechart.AddSimpleStateTransition[ResetEv, Active](proxy, nil)
	// Add starting State to Transition to activated (optional). only needed for super states
	// signature: AddStartingState[State](self)
	statechart.SetStartingState[Stopped](proxy)
	return self.Enter, nil
}
func (self *Active) Enter() {
	// Reset Counter Code
	self.GetContext().ResetCounter()
}

// /// Stopped State
type Stopped struct {
	statechart.StateDefault[MyContext]
}

func (self *Stopped) Setup(proxy statechart.StateSetupProxy[MyContext]) (statechart.EntryAction, statechart.ExitAction) {
	self.Init(proxy)
	statechart.AddSimpleStateTransition[StartStopEv, Running](proxy, nil)
	return nil, nil
}

// /// Running State
type Running struct {
	statechart.StateDefault[MyContext]
}

func (self *Running) Setup(proxy statechart.StateSetupProxy[MyContext]) (statechart.EntryAction, statechart.ExitAction) {
	self.Init(proxy)
	statechart.AddSimpleStateTransition[StartStopEv, Stopped](proxy, nil)
	return self.Enter, self.Exit
}
func (self *Running) Enter() {
	self.GetContext().StartCounter()
}
func (self *Running) Exit() {
	// Stop Counter Code
	self.GetContext().StopCounter()
}

func main() {
	// Create State Machine
	context := MyContext{}
	sm := statechart.MakeStateMachine(&context)
	idleState := sm.AddState(&Idle{})
	activeState := sm.AddState(&Active{})
	sm.AddSubState(&Stopped{}, activeState)
	sm.AddSubState(&Running{}, activeState)
	sm.Initialize(idleState)

	// example event dispatch
	sm.DispatchEvent(&ActivateEv{})   // go for Idle to Active to Stopped
	sm.DispatchEvent(&StartStopEv{})  // go from Stopped to Running
	sm.DispatchEvent(&ResetEv{})      // go from Running to Active to Stopped
	sm.DispatchEvent(&DeactivateEv{}) // go from Stopped to Idle
}
Output:

Reset Counter
Start Counter
Stop Counter
Reset Counter

func MakeStateMachine

func MakeStateMachine[C any](userContext_ *C) StateMachine[C]

Creates a state machine with a user context

func (*StateMachine[C]) AddState

func (sm *StateMachine[C]) AddState(state State[C]) StateId

Adds a new State to the State Machine `state` the new state object to add returns the new stateId

func (*StateMachine[C]) AddSubState

func (sm *StateMachine[C]) AddSubState(state State[C], parentId StateId) StateId

Adds a Sub-State to the State Machine `state` the new state object to add `parentId` the parent (super state) ID

func (*StateMachine[C]) DispatchEvent

func (sm *StateMachine[C]) DispatchEvent(event Event)

Dispatches an events to the state machine `event` The Event to dispatch No error will occur if the Event is unknown to the state machine

func (*StateMachine[C]) GenerateUml added in v0.6.0

func (sm *StateMachine[C]) GenerateUml(w io.Writer, umlSyntax UmlSyntax, diagramType UmlDiagramType)

Generates the UML diagram for the state machine, using `w` the io stream writer `umlSyntax` the generator syntax only only support PlantUML syntax for now [https://plantuml.com/state-diagram] `diagramType` the type of diagram to use for the generation

func (*StateMachine[C]) Initialize

func (sm *StateMachine[C]) Initialize(initStateId StateId)

Initializes the state machine `initStateId` the initial starting state

func (*StateMachine[C]) SetDebugLogger

func (sm *StateMachine[C]) SetDebugLogger(logger func(msg string, keysAndValues ...interface{}))

Sets the Debug Trace Logger for the state machine

type StateProxy

type StateProxy[C any] interface {
	// Returns the name of the State
	Name() string
	// Returns an ancestor of the current state
	GetAncestor(state StateId) State[C]
	// Returns the user context
	GetContext() *C
	// Find the StateId of a state in the state machine
	// It is ok to cache the stateId, to improve performance
	// `selector` a function that is used to test if a state is a match
	FindStateId(selector func(state State[C]) bool) StateId
	// Create a transition result (only needed for custom reactions)
	Transit(state StateId, action BaseAction) ReactionResult
	// Create a forward result (only needed for custom reactions)
	Forward() ReactionResult
	// Create a discard result (only needed for custom reactions)
	Discard() ReactionResult
	// Create a defer result (only needed for custom reactions)
	Defer() ReactionResult
	// Post an event to the event queue that will be processed after the current reaction
	PostEvent(event Event)
}

StateProxy is a proxy for a state machine and state container. This is the main mechanisms to access the state machine

type StateSetupProxy

type StateSetupProxy[C any] interface {
	StateProxy[C]
	// Set the name of the state ( the default name is from reflect.TypeOf().Name() )
	SetName(string)
	// Add a state reaction
	AddReaction(reaction EventReaction)
	// Set a starting state (used for supper state)
	SetStartingState(state StateId)
}

`StateSetupProxy` is a `StateProxy` that can add reactions and set the starting state.

type UmlDiagramType added in v0.6.0

type UmlDiagramType int16
const (
	HIERARCHY_WITH_TRANSITION UmlDiagramType = iota
	HIERARCHY_ONLY
	FLAT_WITH_TRANSITION
)

type UmlDocReaction added in v0.6.0

type UmlDocReaction struct {
	ReactionResult ResultType
	TargetState    StateId
	ActionText     string
	GuardText      string
}

type UmlSyntax added in v0.6.0

type UmlSyntax int16
const (
	PLANT_UML UmlSyntax = iota
)

Jump to

Keyboard shortcuts

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