dc4bc/fsm/fsm/fsm.go

511 lines
11 KiB
Go
Raw Permalink Normal View History

2020-07-28 07:52:05 -07:00
package fsm
import (
"errors"
2020-07-29 14:49:41 -07:00
"fmt"
"strings"
2020-07-28 07:52:05 -07:00
"sync"
)
//
// fsmInstance, err := fsm.New(scope)
// if err != nil {
// log.Println(err)
// return
// }
//
// fsmInstance.Do(event, args)
//
// Temporary global finish state for deprecating operations
const (
StateGlobalIdle = State("__idle")
StateGlobalDone = State("__done")
2020-08-11 09:46:18 -07:00
)
2020-08-09 05:02:50 -07:00
2020-08-11 09:46:18 -07:00
const (
2020-08-09 05:02:50 -07:00
EventRunDefault EventRunMode = iota
EventRunBefore
EventRunAfter
2020-07-28 07:52:05 -07:00
)
type State string
func (s *State) String() string {
return string(*s)
}
type Event string
func (e *Event) String() string {
return string(*e)
}
2020-08-13 08:22:06 -07:00
func (e Event) IsEmpty() bool {
2020-08-08 14:44:52 -07:00
return e.String() == ""
}
2020-08-09 05:02:50 -07:00
type EventRunMode uint8
// Response returns result for processing with clientMocks events
type Response struct {
2020-07-28 07:52:05 -07:00
// Returns machine execution result state
State State
2020-07-28 07:52:05 -07:00
// Must be cast, according to mapper event_name->response_type
Data interface{}
}
type FSM struct {
name string
initialState State
currentState State
2020-07-28 07:52:05 -07:00
// May be mapping must require pair source + event?
transitions map[trKey]*trEvent
2020-08-13 08:22:06 -07:00
autoTransitions map[trAutoKeyEvent]*trEvent
2020-08-09 05:02:50 -07:00
2020-07-28 07:52:05 -07:00
callbacks Callbacks
initialEvent Event
2020-07-28 07:52:05 -07:00
// Finish states, for switch machine or fin,
// These states cannot be linked as SrcState in this machine
finStates map[State]bool
2020-07-28 07:52:05 -07:00
// stateMu guards access to the currentState state.
stateMu sync.RWMutex
}
// Transition key source + dst
type trKey struct {
source State
event Event
2020-07-28 07:52:05 -07:00
}
// Transition lightweight event description
type trEvent struct {
2020-08-09 05:02:50 -07:00
event Event
dstState State
isInternal bool
isAuto bool
runMode EventRunMode
2020-07-28 07:52:05 -07:00
}
2020-08-13 08:22:06 -07:00
type trAutoKeyEvent struct {
state State
runMode EventRunMode
}
type EventDesc struct {
Name Event
2020-07-28 07:52:05 -07:00
SrcState []State
2020-07-28 07:52:05 -07:00
// Dst state changes after callback
DstState State
2020-07-28 07:52:05 -07:00
// Internal events, cannot be emitted from external call
IsInternal bool
2020-08-06 17:20:13 -07:00
2020-08-09 05:02:50 -07:00
// Event must run without manual call
IsAuto bool
AutoRunMode EventRunMode
2020-07-28 07:52:05 -07:00
}
2020-08-08 14:44:52 -07:00
type Callback func(event Event, args ...interface{}) (Event, interface{}, error)
2020-07-28 07:52:05 -07:00
type Callbacks map[Event]Callback
2020-07-28 07:52:05 -07:00
func MustNewFSM(machineName string, initialState State, events []EventDesc, callbacks Callbacks) *FSM {
2020-07-29 14:49:41 -07:00
machineName = strings.TrimSpace(machineName)
initialState = State(strings.TrimSpace(initialState.String()))
2020-07-28 07:52:05 -07:00
2020-07-29 14:49:41 -07:00
if machineName == "" {
panic("machine name cannot be empty")
2020-07-28 07:52:05 -07:00
}
2020-07-29 14:49:41 -07:00
if initialState == "" {
panic("initial state state cannot be empty")
2020-07-28 07:52:05 -07:00
}
// to remove
if len(events) == 0 {
panic("cannot init fsm with empty events")
}
f := &FSM{
2020-08-09 05:02:50 -07:00
name: machineName,
currentState: initialState,
initialState: initialState,
transitions: make(map[trKey]*trEvent),
2020-08-13 08:22:06 -07:00
autoTransitions: make(map[trAutoKeyEvent]*trEvent),
2020-08-09 05:02:50 -07:00
finStates: make(map[State]bool),
callbacks: make(map[Event]Callback),
2020-07-28 07:52:05 -07:00
}
allEvents := make(map[Event]bool)
2020-07-28 07:52:05 -07:00
// Required for find finStates
allSources := make(map[State]bool)
allStates := make(map[State]bool)
2020-07-28 07:52:05 -07:00
// Validate events
for _, event := range events {
event.Name = Event(strings.TrimSpace(event.Name.String()))
event.DstState = State(strings.TrimSpace(event.DstState.String()))
2020-07-28 07:52:05 -07:00
if event.Name == "" {
panic("cannot init empty event")
}
if event.DstState == "" {
panic("event dest cannot be empty, use StateGlobalDone for finish or external state")
}
if _, ok := allEvents[event.Name]; ok {
2020-07-29 14:49:41 -07:00
panic(fmt.Sprintf("duplicate event \"%s\"", event.Name))
2020-07-28 07:52:05 -07:00
}
allEvents[event.Name] = true
allStates[event.DstState] = true
2020-07-29 14:49:41 -07:00
trimmedSourcesCounter := 0
2020-07-28 07:52:05 -07:00
for _, sourceState := range event.SrcState {
sourceState := State(strings.TrimSpace(sourceState.String()))
2020-07-29 14:49:41 -07:00
if sourceState == "" {
continue
}
2020-07-28 07:52:05 -07:00
tKey := trKey{
sourceState,
event.Name,
}
if sourceState == StateGlobalDone {
panic("StateGlobalDone cannot set as source state")
}
if _, ok := f.transitions[tKey]; ok {
panic("duplicate dst for pair `source + event`")
}
2020-08-11 09:46:18 -07:00
if event.IsAuto && event.AutoRunMode == EventRunDefault {
event.AutoRunMode = EventRunAfter
}
2020-08-09 05:02:50 -07:00
trEvent := &trEvent{
2020-08-06 17:20:13 -07:00
tKey.event,
event.DstState,
event.IsInternal,
2020-08-09 05:02:50 -07:00
event.IsAuto,
event.AutoRunMode,
2020-08-06 17:20:13 -07:00
}
2020-07-28 07:52:05 -07:00
2020-08-09 05:02:50 -07:00
f.transitions[tKey] = trEvent
2020-07-28 07:52:05 -07:00
// For using provider, event must use with IsGlobal = true
2020-07-29 14:49:41 -07:00
if sourceState == initialState {
2020-08-09 05:02:50 -07:00
if f.initialEvent == "" {
f.initialEvent = event.Name
2020-07-28 07:52:05 -07:00
}
2020-08-09 05:02:50 -07:00
}
if event.IsAuto {
if event.AutoRunMode != EventRunBefore && event.AutoRunMode != EventRunAfter {
panic("{AutoRunMode} not set for auto event")
}
2020-08-13 08:22:06 -07:00
trAutoKey := trAutoKeyEvent{sourceState, event.AutoRunMode}
if _, ok := f.autoTransitions[trAutoKey]; ok {
2020-08-09 05:02:50 -07:00
panic(fmt.Sprintf(
"auto event \"%s\" already exists for state \"%s\"",
event.Name,
sourceState,
))
}
2020-08-13 08:22:06 -07:00
f.autoTransitions[trAutoKey] = trEvent
2020-07-28 07:52:05 -07:00
}
allSources[sourceState] = true
2020-07-29 14:49:41 -07:00
trimmedSourcesCounter++
}
if trimmedSourcesCounter == 0 {
panic("event must have minimum one source available state")
2020-07-28 07:52:05 -07:00
}
}
if len(allStates) < 2 {
panic("machine must contain at least two states")
}
// Validate callbacks
for event, callback := range callbacks {
if event == "" {
2020-07-29 14:49:41 -07:00
panic("callback machineName cannot be empty")
2020-07-28 07:52:05 -07:00
}
if _, ok := allEvents[event]; !ok {
2020-08-13 08:22:06 -07:00
panic(fmt.Sprintf("callback has unused event \"%s\"", event))
2020-07-28 07:52:05 -07:00
}
f.callbacks[event] = callback
}
for state := range allStates {
if state == StateGlobalIdle {
continue
}
// Exit states cannot be a source in this machine
if _, exists := allSources[state]; !exists || state == StateGlobalDone {
f.finStates[state] = true
}
}
if len(f.finStates) == 0 {
panic("cannot initialize machine without final states")
}
return f
}
2020-08-21 06:56:03 -07:00
// WithState returns FSM copy with custom setup state
2020-08-21 07:29:52 -07:00
func (f *FSM) MustCopyWithState(state State) *FSM {
var exists bool
2020-08-21 06:56:03 -07:00
f.stateMu.RLock()
defer f.stateMu.RUnlock()
if state != "" {
2020-08-21 07:29:52 -07:00
for _, s := range f.StatesList() {
if s == state {
exists = true
}
}
if !exists {
panic(fmt.Sprintf("cannot set state, not exists \"%s\" for \"%s\"", state, f.name))
}
2020-08-21 06:56:03 -07:00
f.currentState = state
}
2020-08-21 07:29:52 -07:00
return f
2020-08-21 06:56:03 -07:00
}
2020-08-06 17:20:13 -07:00
func (f *FSM) DoInternal(event Event, args ...interface{}) (resp *Response, err error) {
trEvent, ok := f.transitions[trKey{f.currentState, event}]
if !ok {
2020-09-29 07:31:31 -07:00
return nil, fmt.Errorf("cannot execute internal event \"%s\" for state \"%s\"",
event, f.currentState)
2020-08-06 17:20:13 -07:00
}
return f.do(trEvent, args...)
}
2020-07-28 07:52:05 -07:00
2020-08-06 17:20:13 -07:00
func (f *FSM) Do(event Event, args ...interface{}) (resp *Response, err error) {
2020-07-28 07:52:05 -07:00
trEvent, ok := f.transitions[trKey{f.currentState, event}]
if !ok {
2020-09-29 07:31:31 -07:00
return nil, fmt.Errorf("cannot execute event \"%s\" for state \"%s\"", event, f.currentState)
2020-07-28 07:52:05 -07:00
}
if trEvent.isInternal {
return nil, errors.New("event is internal")
}
2020-08-06 17:20:13 -07:00
return f.do(trEvent, args...)
}
2020-08-13 08:22:06 -07:00
// Check and execute auto event
func (f *FSM) processAutoEvent(mode EventRunMode, args ...interface{}) (exists bool, outEvent Event, response interface{}, err error) {
autoEvent, exists := f.autoTransitions[trAutoKeyEvent{f.State(), mode}]
if !exists {
return
}
if f.isCallbackExists(autoEvent.event) {
outEvent, response, err = f.execCallback(autoEvent.event, args...)
// Do not try change state on error
if err != nil {
return exists, "", response, err
}
}
if outEvent.IsEmpty() || autoEvent.event == outEvent {
err = f.SetState(autoEvent.event)
} else {
err = f.SetState(outEvent)
}
return
}
2020-08-06 17:20:13 -07:00
func (f *FSM) do(trEvent *trEvent, args ...interface{}) (resp *Response, err error) {
2020-08-08 14:44:52 -07:00
var outEvent Event
2020-08-06 17:20:13 -07:00
// f.eventMu.Lock()
// defer f.eventMu.Unlock()
2020-08-13 08:22:06 -07:00
resp = &Response{}
2020-08-09 05:02:50 -07:00
// Process auto event
2020-08-13 08:22:06 -07:00
isAutoEventExecuted, outEvent, data, err := f.processAutoEvent(EventRunBefore, args...)
if isAutoEventExecuted {
resp.State = f.State()
if data != nil {
resp.Data = data
2020-08-06 17:20:13 -07:00
}
2020-08-13 08:22:06 -07:00
if err != nil {
return resp, err
}
2020-07-28 07:52:05 -07:00
}
2020-08-13 08:22:06 -07:00
if f.isCallbackExists(trEvent.event) {
outEvent, resp.Data, err = f.execCallback(trEvent.event, args...)
2020-07-28 07:52:05 -07:00
// Do not try change state on error
if err != nil {
return resp, err
}
}
2020-08-09 05:02:50 -07:00
// Set state when callback executed
if outEvent.IsEmpty() || trEvent.event == outEvent {
err = f.SetState(trEvent.event)
2020-08-13 08:22:06 -07:00
if err != nil {
return resp, err
}
2020-08-09 05:02:50 -07:00
} else {
err = f.SetState(outEvent)
2020-08-13 08:22:06 -07:00
if err != nil {
return resp, err
}
2020-08-09 05:02:50 -07:00
}
2020-08-08 14:44:52 -07:00
2020-08-13 08:22:06 -07:00
resp.State = f.State()
2020-08-09 05:02:50 -07:00
// Process auto event
2020-09-29 08:05:40 -07:00
isAutoEventExecuted, _, data, err = f.processAutoEvent(EventRunAfter, args...)
2020-08-13 08:22:06 -07:00
if isAutoEventExecuted {
resp.State = f.State()
if data != nil {
resp.Data = data
2020-08-09 05:02:50 -07:00
}
2020-08-13 08:22:06 -07:00
if err != nil {
return resp, err
2020-08-09 05:02:50 -07:00
}
}
2020-08-06 17:20:13 -07:00
2020-08-13 16:35:52 -07:00
resp.State = f.State()
2020-07-28 07:52:05 -07:00
return
}
// State returns the currentState state of the FSM.
func (f *FSM) State() State {
2020-07-28 07:52:05 -07:00
f.stateMu.RLock()
defer f.stateMu.RUnlock()
return f.currentState
}
2020-08-06 17:20:13 -07:00
// SetState allows the user to move to the given state from currentState state.
2020-07-28 07:52:05 -07:00
// The call does not trigger any callbacks, if defined.
2020-08-06 17:20:13 -07:00
func (f *FSM) SetState(event Event) error {
2020-07-28 07:52:05 -07:00
f.stateMu.Lock()
defer f.stateMu.Unlock()
trEvent, ok := f.transitions[trKey{f.currentState, event}]
if !ok {
2020-09-29 08:05:40 -07:00
return fmt.Errorf("cannot set state by event \"%s\" for state \"%s\"", event, f.currentState)
2020-07-28 07:52:05 -07:00
}
f.currentState = trEvent.dstState
return nil
}
func (f *FSM) Name() string {
return f.name
}
func (f *FSM) InitialState() State {
2020-07-28 07:52:05 -07:00
return f.initialState
}
// Check entry event for available emitting as global entry event
func (f *FSM) GlobalInitialEvent() (event Event) {
2020-07-28 07:52:05 -07:00
if initialEvent, exists := f.transitions[trKey{StateGlobalIdle, f.initialEvent}]; exists {
if !initialEvent.isInternal {
event = f.initialEvent
}
}
return
}
func (f *FSM) EntryEvent() (event Event) {
2020-07-28 07:52:05 -07:00
if entryEvent, exists := f.transitions[trKey{f.initialState, f.initialEvent}]; exists {
if !entryEvent.isInternal {
event = f.initialEvent
}
}
return
}
func (f *FSM) EventsList() (events []Event) {
var eventsMap = map[Event]bool{}
2020-07-28 07:52:05 -07:00
if len(f.transitions) > 0 {
for trKey, trEvent := range f.transitions {
if !trEvent.isInternal {
2020-07-29 14:49:41 -07:00
eventsMap[trKey.event] = true
if _, exists := eventsMap[trKey.event]; !exists {
events = append(events, trKey.event)
}
2020-07-28 07:52:05 -07:00
}
}
}
2020-07-29 14:49:41 -07:00
if len(eventsMap) > 0 {
for event := range eventsMap {
events = append(events, event)
}
}
2020-07-28 07:52:05 -07:00
return
}
2020-08-21 07:29:52 -07:00
func (f *FSM) StatesList() (states []State) {
var allStates = map[State]bool{}
2020-07-28 07:52:05 -07:00
if len(f.transitions) > 0 {
2020-09-29 08:05:40 -07:00
for trKey := range f.transitions {
2020-07-28 07:52:05 -07:00
allStates[trKey.source] = true
}
}
if len(allStates) > 0 {
for state := range allStates {
states = append(states, state)
}
}
return
}
2020-08-13 08:22:06 -07:00
func (f *FSM) isCallbackExists(event Event) bool {
_, exists := f.callbacks[event]
return exists
}
func (f *FSM) execCallback(event Event, args ...interface{}) (Event, interface{}, error) {
2020-09-29 08:05:40 -07:00
callback := f.callbacks[event]
2020-08-13 08:22:06 -07:00
return callback(event, args...)
}
func (f *FSM) IsFinState(state State) bool {
2020-07-28 07:52:05 -07:00
_, exists := f.finStates[state]
return exists
}