dashboard/pkg/myfsm/fsm.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
}