161 lines
3.6 KiB
Go
161 lines
3.6 KiB
Go
package fsm
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"time"
|
|
)
|
|
|
|
type EventName string
|
|
|
|
const (
|
|
EventEntry EventName = "EventEntry"
|
|
EventExit EventName = "EventExit"
|
|
|
|
EventOK EventName = "EventOK"
|
|
EventNoProc EventName = "EventNoProc"
|
|
)
|
|
|
|
type Callback func(fsm *FSM, event EventName, arg interface{}) EventName
|
|
|
|
type Callbacks map[string]Callback
|
|
|
|
type FSMState struct {
|
|
name string
|
|
parent string
|
|
initState string
|
|
processor Callback
|
|
}
|
|
|
|
type FSM struct {
|
|
name string
|
|
currentState *FSMState
|
|
eventNames *[]string
|
|
fsmStats map[string]*FSMState
|
|
}
|
|
|
|
type Event struct {
|
|
FSM *FSM
|
|
Event string
|
|
Src string
|
|
Dst string
|
|
Err error
|
|
Args []interface{}
|
|
canceled bool
|
|
async bool
|
|
cancelFunc func()
|
|
}
|
|
|
|
func NewFSMState(name string, parent, initState string, processor Callback) *FSMState {
|
|
return &FSMState{
|
|
name: name,
|
|
parent: parent,
|
|
initState: initState,
|
|
processor: processor,
|
|
}
|
|
}
|
|
|
|
func NewFSM(name string, eventNames *[]string, fsmStats ...*FSMState) (*FSM, error) {
|
|
if len(fsmStats) == 0 {
|
|
return nil, fmt.Errorf("initial state cannot be nil")
|
|
}
|
|
fsm := &FSM{
|
|
name: name,
|
|
currentState: fsmStats[0],
|
|
eventNames: eventNames,
|
|
fsmStats: make(map[string]*FSMState),
|
|
}
|
|
|
|
for index := range fsmStats {
|
|
fsm.fsmStats[fsmStats[index].name] = fsmStats[index]
|
|
}
|
|
|
|
// Execute ENTRY event for initial state and its sub-states
|
|
for tempState := fsm.currentState; tempState != nil; tempState = fsm.fsmStats[tempState.initState] {
|
|
fsm.currentState = tempState
|
|
if err := fsm.ExecuteEvent(EventEntry, nil); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return fsm, nil
|
|
}
|
|
|
|
func (fsm *FSM) ExecuteEvent(event EventName, arg interface{}) error {
|
|
if fsm == nil {
|
|
return fmt.Errorf("FSM is nil")
|
|
}
|
|
|
|
log.Printf("%d %-23s STATE:%-23s EVENT:%s\n", time.Now().UnixMilli(), fsm.name, fsm.currentState.name, event)
|
|
|
|
tmpState := fsm.currentState
|
|
if tmpState == nil {
|
|
return fmt.Errorf("current state is nil")
|
|
}
|
|
|
|
result := tmpState.processor(fsm, event, arg)
|
|
for result == EventNoProc && fsm.fsmStats[tmpState.parent] != nil {
|
|
tmpState = fsm.fsmStats[tmpState.parent]
|
|
result = tmpState.processor(fsm, event, arg)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (fsm *FSM) ChangeState(newState string, arg interface{}) error {
|
|
if newState == "" {
|
|
return fmt.Errorf("new state cannot be nil")
|
|
}
|
|
|
|
curState := fsm.currentState
|
|
|
|
for tempState := curState; tempState != nil; tempState = fsm.fsmStats[tempState.parent] {
|
|
fsm.currentState = tempState
|
|
if err := fsm.ExecuteEvent(EventExit, nil); err != nil {
|
|
return err
|
|
}
|
|
|
|
if newState == tempState.name {
|
|
if err := fsm.ExecuteEvent(EventEntry, arg); err != nil {
|
|
return err
|
|
}
|
|
break
|
|
}
|
|
|
|
if found, ok := fsm.findState(fsm.fsmStats[newState], tempState); ok {
|
|
for end := len(found) - 1; end >= 0; end-- {
|
|
fsm.currentState = found[end]
|
|
if err := fsm.ExecuteEvent(EventEntry, arg); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
// Enter sub-states of the new state
|
|
for tempState := fsm.fsmStats[newState].initState; fsm.fsmStats[tempState] != nil; tempState = fsm.fsmStats[tempState].initState {
|
|
fsm.currentState = fsm.fsmStats[tempState]
|
|
if err := fsm.ExecuteEvent(EventEntry, arg); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (fsm *FSM) findState(targetState, findState *FSMState) ([]*FSMState, bool) {
|
|
var fsmStateList []*FSMState
|
|
|
|
for tempState := targetState; tempState != nil; tempState = fsm.fsmStats[tempState.parent] {
|
|
|
|
fsmStateList = append(fsmStateList, tempState)
|
|
|
|
if findState.parent == tempState.parent {
|
|
return fsmStateList, true
|
|
}
|
|
}
|
|
|
|
return fsmStateList, false
|
|
}
|