github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/util/fsm/fsm.go (about) 1 // Copyright 2017 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 // See doc.go for a description. 12 13 package fsm 14 15 import "context" 16 17 // State is a node in a Machine's transition graph. 18 type State interface { 19 State() 20 } 21 22 // ExtendedState is extra state in a Machine that does not contribute to state 23 // transition decisions, but that can be affected by a state transition. The 24 // interface is provided as part of the Args passed to Action after being 25 // given to a Machine during construction. 26 type ExtendedState interface{} 27 28 // Event is something that happens to a Machine which may or may not trigger a 29 // state transition. 30 type Event interface { 31 Event() 32 } 33 34 // EventPayload is extra payload on an Event that does not contribute to state 35 // transition decisions, but that can be affected by a state transition. The 36 // interface is provided as part of the Args passed to Action after being 37 // given to a Machine during a call to ApplyWithPayload. 38 type EventPayload interface{} 39 40 // Args is a structure containing the arguments passed to Transition.Action. 41 type Args struct { 42 Ctx context.Context 43 44 Prev State 45 Extended ExtendedState 46 47 Event Event 48 Payload EventPayload 49 } 50 51 // Transition is a Machine's response to an Event applied to a State. It may 52 // transition the machine to a new State and it may also perform an action on 53 // the Machine's ExtendedState. 54 type Transition struct { 55 Next State 56 Action func(Args) error 57 // Description, if set, is reflected in the DOT diagram. 58 Description string 59 } 60 61 // TransitionNotFoundError is returned from Machine.Apply when the Event cannot 62 // be applied to the current State. 63 type TransitionNotFoundError struct { 64 State State 65 Event Event 66 } 67 68 func (e *TransitionNotFoundError) Error() string { 69 return "event " + eventName(e.Event) + " inappropriate in current state " + stateName(e.State) 70 } 71 72 // Transitions is a set of expanded state transitions generated from a Pattern, 73 // forming a State graph with Events acting as the directed edges between 74 // different States. 75 // 76 // A Transitions graph is immutable and is only useful when used to direct a 77 // Machine. Because of this, multiple Machines can be instantiated using the 78 // same Transitions graph. 79 type Transitions struct { 80 expanded Pattern 81 } 82 83 // GetExpanded returns the expanded map of transitions. 84 func (t Transitions) GetExpanded() Pattern { 85 return t.expanded 86 } 87 88 // Compile creates a set of state Transitions from a Pattern. This is relatively 89 // expensive so it's expected that Compile is called once for each transition 90 // graph and assigned to a static variable. This variable can then be given to 91 // MakeMachine, which is cheap. 92 func Compile(p Pattern) Transitions { 93 return Transitions{expanded: expandPattern(p)} 94 } 95 96 func (t Transitions) apply(a Args) (State, error) { 97 sm, ok := t.expanded[a.Prev] 98 if !ok { 99 return a.Prev, &TransitionNotFoundError{State: a.Prev, Event: a.Event} 100 } 101 tr, ok := sm[a.Event] 102 if !ok { 103 return a.Prev, &TransitionNotFoundError{State: a.Prev, Event: a.Event} 104 } 105 if tr.Action != nil { 106 if err := tr.Action(a); err != nil { 107 return a.Prev, err 108 } 109 } 110 return tr.Next, nil 111 } 112 113 // Machine encapsulates a State with a set of State transitions. It reacts to 114 // Events, adjusting its internal State according to its Transition graph and 115 // perforing actions on its ExtendedState accordingly. 116 type Machine struct { 117 t Transitions 118 cur State 119 es ExtendedState 120 } 121 122 // MakeMachine creates a new Machine. 123 func MakeMachine(t Transitions, start State, es ExtendedState) Machine { 124 return Machine{t: t, cur: start, es: es} 125 } 126 127 // Apply applies the Event to the state Machine. 128 func (m *Machine) Apply(ctx context.Context, e Event) error { 129 return m.ApplyWithPayload(ctx, e, nil) 130 } 131 132 // ApplyWithPayload applies the Event to the state Machine, passing along the 133 // EventPayload to the state transition's Action function. 134 func (m *Machine) ApplyWithPayload(ctx context.Context, e Event, b EventPayload) (err error) { 135 m.cur, err = m.t.apply(Args{ 136 Ctx: ctx, 137 Prev: m.cur, 138 Extended: m.es, 139 Event: e, 140 Payload: b, 141 }) 142 return err 143 } 144 145 // CurState returns the current state. 146 func (m *Machine) CurState() State { 147 return m.cur 148 }