// Copyright (c) 2013 - Max Persson // // 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_ - called before event named // // 2. before_event - called before all events // // 3. leave_ - called before leaving // // 4. leave_state - called before leaving all states // // 5. enter_ - called after entering // // 6. enter_state - called after entering all states // // 7. after_ - called after event named // // 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. - called after entering // // 2. - called after event named // // 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_ 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 }