diff --git a/pkg/myfsm/fsm.go b/pkg/myfsm/fsm.go new file mode 100644 index 0000000..6d980ac --- /dev/null +++ b/pkg/myfsm/fsm.go @@ -0,0 +1,160 @@ +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 +} diff --git a/pkg/myfsm/fsm_test.go b/pkg/myfsm/fsm_test.go new file mode 100644 index 0000000..4ca568e --- /dev/null +++ b/pkg/myfsm/fsm_test.go @@ -0,0 +1,87 @@ +package fsm + +import ( + "log" + "testing" + "time" +) + +type myFsm struct { + fsm *FSM +} + +func state1(fsm *FSM, event EventName, arg interface{}) EventName { + switch event { + case EventEntry: + log.Println("state1", event) + case EventExit: + log.Println("state1", event) + default: + log.Println("state1", event) + return EventNoProc + } + + return EventOK +} + +func state2(fsm *FSM, event EventName, arg interface{}) EventName { + switch event { + case EventEntry: + log.Println("state2", event) + case EventExit: + log.Println("state2", event) + default: + log.Println("state2", event) + return EventNoProc + } + + return EventOK +} + +func state3(fsm *FSM, event EventName, arg interface{}) EventName { + switch event { + case EventEntry: + log.Println("state3", event) + case EventExit: + log.Println("state3", event) + case "test event": + log.Println("state3", event) + default: + log.Println("state3", event) + return EventNoProc + } + + return EventOK +} + +func newMyFsm() *myFsm { + state1 := NewFSMState("State1", "", "State2", state1) + state2 := NewFSMState("State2", "State1", "", state2) + state3 := NewFSMState("State3", "State1", "", state3) + + fsm, err := NewFSM("MyFSM", &[]string{string(EventEntry), string(EventExit)}, state2, state1, state3) + if err != nil { + log.Fatal(err) + } + + return &myFsm{fsm: fsm} +} + +func Test_fsm(t *testing.T) { + myfsm := newMyFsm() + + if err := myfsm.fsm.ChangeState("State3", nil); err != nil { + log.Fatal(err) + } + + myfsm.fsm.ExecuteEvent("test event", nil) + + // Start a timer (example) + // timerHandle := &ReactorTimerHandle{} + // if err := fsm.StartReactorTimer(timerHandle, 0, 1000, EventEntry, nil); err != nil { + // log.Fatal(err) + // } + + // Keep the program running to observeinterior + time.Sleep(2 * time.Second) +}