| // Copyright (c) 2013 - Max Persson <max@looplab.se> |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| // Package fsm implements a finite state machine. |
| // |
| // It is heavily based on two FSM implementations: |
| // |
| // Javascript Finite State Machine |
| // https://github.com/jakesgordon/javascript-state-machine |
| // |
| // Fysom for Python |
| // https://github.com/oxplot/fysom (forked at https://github.com/mriehl/fysom) |
| // |
| package fsm |
| |
| import ( |
| "strings" |
| "sync" |
| ) |
| |
| // transitioner is an interface for the FSM's transition function. |
| type transitioner interface { |
| transition(*FSM) error |
| } |
| |
| // FSM is the state machine that holds the current state. |
| // |
| // It has to be created with NewFSM to function properly. |
| type FSM struct { |
| // current is the state that the FSM is currently in. |
| current string |
| |
| // transitions maps events and source states to destination states. |
| transitions map[eKey]string |
| |
| // callbacks maps events and targers to callback functions. |
| callbacks map[cKey]Callback |
| |
| // transition is the internal transition functions used either directly |
| // or when Transition is called in an asynchronous state transition. |
| transition func() |
| // transitionerObj calls the FSM's transition() function. |
| transitionerObj transitioner |
| |
| // stateMu guards access to the current state. |
| stateMu sync.RWMutex |
| // eventMu guards access to Event() and Transition(). |
| eventMu sync.Mutex |
| } |
| |
| // EventDesc represents an event when initializing the FSM. |
| // |
| // The event can have one or more source states that is valid for performing |
| // the transition. If the FSM is in one of the source states it will end up in |
| // the specified destination state, calling all defined callbacks as it goes. |
| type EventDesc struct { |
| // Name is the event name used when calling for a transition. |
| Name string |
| |
| // Src is a slice of source states that the FSM must be in to perform a |
| // state transition. |
| Src []string |
| |
| // Dst is the destination state that the FSM will be in if the transition |
| // succeds. |
| Dst string |
| } |
| |
| // Callback is a function type that callbacks should use. Event is the current |
| // event info as the callback happens. |
| type Callback func(*Event) |
| |
| // Events is a shorthand for defining the transition map in NewFSM. |
| type Events []EventDesc |
| |
| // Callbacks is a shorthand for defining the callbacks in NewFSM.a |
| type Callbacks map[string]Callback |
| |
| // NewFSM constructs a FSM from events and callbacks. |
| // |
| // The events and transitions are specified as a slice of Event structs |
| // specified as Events. Each Event is mapped to one or more internal |
| // transitions from Event.Src to Event.Dst. |
| // |
| // Callbacks are added as a map specified as Callbacks where the key is parsed |
| // as the callback event as follows, and called in the same order: |
| // |
| // 1. before_<EVENT> - called before event named <EVENT> |
| // |
| // 2. before_event - called before all events |
| // |
| // 3. leave_<OLD_STATE> - called before leaving <OLD_STATE> |
| // |
| // 4. leave_state - called before leaving all states |
| // |
| // 5. enter_<NEW_STATE> - called after entering <NEW_STATE> |
| // |
| // 6. enter_state - called after entering all states |
| // |
| // 7. after_<EVENT> - called after event named <EVENT> |
| // |
| // 8. after_event - called after all events |
| // |
| // There are also two short form versions for the most commonly used callbacks. |
| // They are simply the name of the event or state: |
| // |
| // 1. <NEW_STATE> - called after entering <NEW_STATE> |
| // |
| // 2. <EVENT> - called after event named <EVENT> |
| // |
| // If both a shorthand version and a full version is specified it is undefined |
| // which version of the callback will end up in the internal map. This is due |
| // to the psuedo random nature of Go maps. No checking for multiple keys is |
| // currently performed. |
| func NewFSM(initial string, events []EventDesc, callbacks map[string]Callback) *FSM { |
| f := &FSM{ |
| transitionerObj: &transitionerStruct{}, |
| current: initial, |
| transitions: make(map[eKey]string), |
| callbacks: make(map[cKey]Callback), |
| } |
| |
| // Build transition map and store sets of all events and states. |
| allEvents := make(map[string]bool) |
| allStates := make(map[string]bool) |
| for _, e := range events { |
| for _, src := range e.Src { |
| f.transitions[eKey{e.Name, src}] = e.Dst |
| allStates[src] = true |
| allStates[e.Dst] = true |
| } |
| allEvents[e.Name] = true |
| } |
| |
| // Map all callbacks to events/states. |
| for name, fn := range callbacks { |
| var target string |
| var callbackType int |
| |
| switch { |
| case strings.HasPrefix(name, "before_"): |
| target = strings.TrimPrefix(name, "before_") |
| if target == "event" { |
| target = "" |
| callbackType = callbackBeforeEvent |
| } else if _, ok := allEvents[target]; ok { |
| callbackType = callbackBeforeEvent |
| } |
| case strings.HasPrefix(name, "leave_"): |
| target = strings.TrimPrefix(name, "leave_") |
| if target == "state" { |
| target = "" |
| callbackType = callbackLeaveState |
| } else if _, ok := allStates[target]; ok { |
| callbackType = callbackLeaveState |
| } |
| case strings.HasPrefix(name, "enter_"): |
| target = strings.TrimPrefix(name, "enter_") |
| if target == "state" { |
| target = "" |
| callbackType = callbackEnterState |
| } else if _, ok := allStates[target]; ok { |
| callbackType = callbackEnterState |
| } |
| case strings.HasPrefix(name, "after_"): |
| target = strings.TrimPrefix(name, "after_") |
| if target == "event" { |
| target = "" |
| callbackType = callbackAfterEvent |
| } else if _, ok := allEvents[target]; ok { |
| callbackType = callbackAfterEvent |
| } |
| default: |
| target = name |
| if _, ok := allStates[target]; ok { |
| callbackType = callbackEnterState |
| } else if _, ok := allEvents[target]; ok { |
| callbackType = callbackAfterEvent |
| } |
| } |
| |
| if callbackType != callbackNone { |
| f.callbacks[cKey{target, callbackType}] = fn |
| } |
| } |
| |
| return f |
| } |
| |
| // Current returns the current state of the FSM. |
| func (f *FSM) Current() string { |
| f.stateMu.RLock() |
| defer f.stateMu.RUnlock() |
| return f.current |
| } |
| |
| // Is returns true if state is the current state. |
| func (f *FSM) Is(state string) bool { |
| f.stateMu.RLock() |
| defer f.stateMu.RUnlock() |
| return state == f.current |
| } |
| |
| // SetState allows the user to move to the given state from current state. |
| // The call does not trigger any callbacks, if defined. |
| func (f *FSM) SetState(state string) { |
| f.stateMu.Lock() |
| defer f.stateMu.Unlock() |
| f.current = state |
| return |
| } |
| |
| // Can returns true if event can occur in the current state. |
| func (f *FSM) Can(event string) bool { |
| f.stateMu.RLock() |
| defer f.stateMu.RUnlock() |
| _, ok := f.transitions[eKey{event, f.current}] |
| return ok && (f.transition == nil) |
| } |
| |
| // AvailableTransitions returns a list of transitions avilable in the |
| // current state. |
| func (f *FSM) AvailableTransitions() []string { |
| f.stateMu.RLock() |
| defer f.stateMu.RUnlock() |
| var transitions []string |
| for key := range f.transitions { |
| if key.src == f.current { |
| transitions = append(transitions, key.event) |
| } |
| } |
| return transitions |
| } |
| |
| // Cannot returns true if event can not occure in the current state. |
| // It is a convenience method to help code read nicely. |
| func (f *FSM) Cannot(event string) bool { |
| return !f.Can(event) |
| } |
| |
| // Event initiates a state transition with the named event. |
| // |
| // The call takes a variable number of arguments that will be passed to the |
| // callback, if defined. |
| // |
| // It will return nil if the state change is ok or one of these errors: |
| // |
| // - event X inappropriate because previous transition did not complete |
| // |
| // - event X inappropriate in current state Y |
| // |
| // - event X does not exist |
| // |
| // - internal error on state transition |
| // |
| // The last error should never occur in this situation and is a sign of an |
| // internal bug. |
| func (f *FSM) Event(event string, args ...interface{}) error { |
| f.eventMu.Lock() |
| defer f.eventMu.Unlock() |
| |
| f.stateMu.RLock() |
| defer f.stateMu.RUnlock() |
| |
| if f.transition != nil { |
| return InTransitionError{event} |
| } |
| |
| dst, ok := f.transitions[eKey{event, f.current}] |
| if !ok { |
| for ekey := range f.transitions { |
| if ekey.event == event { |
| return InvalidEventError{event, f.current} |
| } |
| } |
| return UnknownEventError{event} |
| } |
| |
| e := &Event{f, event, f.current, dst, nil, args, false, false} |
| |
| err := f.beforeEventCallbacks(e) |
| if err != nil { |
| return err |
| } |
| |
| if f.current == dst { |
| f.afterEventCallbacks(e) |
| return NoTransitionError{e.Err} |
| } |
| |
| // Setup the transition, call it later. |
| f.transition = func() { |
| f.stateMu.Lock() |
| f.current = dst |
| f.stateMu.Unlock() |
| |
| f.enterStateCallbacks(e) |
| f.afterEventCallbacks(e) |
| } |
| |
| if err = f.leaveStateCallbacks(e); err != nil { |
| if _, ok := err.(CanceledError); ok { |
| f.transition = nil |
| } |
| return err |
| } |
| |
| // Perform the rest of the transition, if not asynchronous. |
| f.stateMu.RUnlock() |
| err = f.doTransition() |
| f.stateMu.RLock() |
| if err != nil { |
| return InternalError{} |
| } |
| |
| return e.Err |
| } |
| |
| // Transition wraps transitioner.transition. |
| func (f *FSM) Transition() error { |
| f.eventMu.Lock() |
| defer f.eventMu.Unlock() |
| return f.doTransition() |
| } |
| |
| // doTransition wraps transitioner.transition. |
| func (f *FSM) doTransition() error { |
| return f.transitionerObj.transition(f) |
| } |
| |
| // transitionerStruct is the default implementation of the transitioner |
| // interface. Other implementations can be swapped in for testing. |
| type transitionerStruct struct{} |
| |
| // Transition completes an asynchrounous state change. |
| // |
| // The callback for leave_<STATE> must prviously have called Async on its |
| // event to have initiated an asynchronous state transition. |
| func (t transitionerStruct) transition(f *FSM) error { |
| if f.transition == nil { |
| return NotInTransitionError{} |
| } |
| f.transition() |
| f.transition = nil |
| return nil |
| } |
| |
| // beforeEventCallbacks calls the before_ callbacks, first the named then the |
| // general version. |
| func (f *FSM) beforeEventCallbacks(e *Event) error { |
| if fn, ok := f.callbacks[cKey{e.Event, callbackBeforeEvent}]; ok { |
| fn(e) |
| if e.canceled { |
| return CanceledError{e.Err} |
| } |
| } |
| if fn, ok := f.callbacks[cKey{"", callbackBeforeEvent}]; ok { |
| fn(e) |
| if e.canceled { |
| return CanceledError{e.Err} |
| } |
| } |
| return nil |
| } |
| |
| // leaveStateCallbacks calls the leave_ callbacks, first the named then the |
| // general version. |
| func (f *FSM) leaveStateCallbacks(e *Event) error { |
| if fn, ok := f.callbacks[cKey{f.current, callbackLeaveState}]; ok { |
| fn(e) |
| if e.canceled { |
| return CanceledError{e.Err} |
| } else if e.async { |
| return AsyncError{e.Err} |
| } |
| } |
| if fn, ok := f.callbacks[cKey{"", callbackLeaveState}]; ok { |
| fn(e) |
| if e.canceled { |
| return CanceledError{e.Err} |
| } else if e.async { |
| return AsyncError{e.Err} |
| } |
| } |
| return nil |
| } |
| |
| // enterStateCallbacks calls the enter_ callbacks, first the named then the |
| // general version. |
| func (f *FSM) enterStateCallbacks(e *Event) { |
| if fn, ok := f.callbacks[cKey{f.current, callbackEnterState}]; ok { |
| fn(e) |
| } |
| if fn, ok := f.callbacks[cKey{"", callbackEnterState}]; ok { |
| fn(e) |
| } |
| } |
| |
| // afterEventCallbacks calls the after_ callbacks, first the named then the |
| // general version. |
| func (f *FSM) afterEventCallbacks(e *Event) { |
| if fn, ok := f.callbacks[cKey{e.Event, callbackAfterEvent}]; ok { |
| fn(e) |
| } |
| if fn, ok := f.callbacks[cKey{"", callbackAfterEvent}]; ok { |
| fn(e) |
| } |
| } |
| |
| const ( |
| callbackNone int = iota |
| callbackBeforeEvent |
| callbackLeaveState |
| callbackEnterState |
| callbackAfterEvent |
| ) |
| |
| // cKey is a struct key used for keeping the callbacks mapped to a target. |
| type cKey struct { |
| // target is either the name of a state or an event depending on which |
| // callback type the key refers to. It can also be "" for a non-targeted |
| // callback like before_event. |
| target string |
| |
| // callbackType is the situation when the callback will be run. |
| callbackType int |
| } |
| |
| // eKey is a struct key used for storing the transition map. |
| type eKey struct { |
| // event is the name of the event that the keys refers to. |
| event string |
| |
| // src is the source from where the event can transition. |
| src string |
| } |