123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432 |
- // 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
- }
- // 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)
- }
- // 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
- }
|