-
Notifications
You must be signed in to change notification settings - Fork 0
/
statmach.go
209 lines (185 loc) · 6.71 KB
/
statmach.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
package statmach
import (
"errors"
"fmt"
)
type transitionRepresentation struct {
guardFunc func(params ...interface{}) bool
trigger string
destState *StateConfigure
}
func newTransitionRepresentation(destState *StateConfigure, trigger string, guardFunc func(params ...interface{}) bool) *transitionRepresentation {
return &transitionRepresentation{
destState: destState,
guardFunc: guardFunc,
trigger: trigger,
}
}
// StateConfigure is used to represent a state and its transitions
type StateConfigure struct {
name string
sm *StateMachine
transitionMap map[string]*transitionRepresentation
parentState *StateConfigure
substates map[string]*StateConfigure
onExitFunc func(trigger string, destState string)
onEntryMap map[string]func(params ...interface{})
}
func newStateConfigure(name string, sm *StateMachine) *StateConfigure {
return &StateConfigure{
name: name,
sm: sm,
transitionMap: make(map[string]*transitionRepresentation),
onEntryMap: make(map[string]func(params ...interface{})),
substates: make(map[string]*StateConfigure),
}
}
// Name returns the state name
func (c *StateConfigure) Name() string {
return c.name
}
func (c *StateConfigure) internalPermit(trigger string, destState string, guardFunc func(params ...interface{}) bool) error {
if c.name == destState {
return errors.New("Destination state cannot be the same as name of the state by Permit method.Try to use PermitReentry")
}
transRepresent, ok := c.transitionMap[trigger]
if ok {
return fmt.Errorf("a transition between from %v to %v via '%v' already exists", c.name, transRepresent.destState.name, trigger)
}
// append transition to map
transRepresent = newTransitionRepresentation(c.sm.Configure(destState), trigger, guardFunc)
c.transitionMap[trigger] = transRepresent
return nil
}
// Permit adds a transition to destState via trigger
func (c *StateConfigure) Permit(trigger string, destState string) error {
return c.internalPermit(trigger, destState, nil)
}
// PermitIf adds a conditional transition to destState via trigger.
// transition occurs if it is allowed by guardFunc
func (c *StateConfigure) PermitIf(trigger string, destState string, guardFunc func(params ...interface{}) bool) error {
if guardFunc == nil {
return errors.New("guardFunc cannot be nil")
}
return c.internalPermit(trigger, destState, guardFunc)
}
func (c *StateConfigure) internalPermitReentry(trigger string, guardFunc func(params ...interface{}) bool) error {
transRepresent, ok := c.transitionMap[trigger]
if ok {
return fmt.Errorf("a transition between from %v to %v via '%v' already exists", c.name, transRepresent.destState.name, trigger)
}
c.transitionMap[trigger] = newTransitionRepresentation(c, trigger, guardFunc)
return nil
}
// PermitReentry adds a transition to itself via trigger
func (c *StateConfigure) PermitReentry(trigger string) error {
return c.internalPermitReentry(trigger, nil)
}
// PermitReentryIf adds a conditional transition to itself via trigger.
// transition occurs if it is allowed by guardFunc
func (c *StateConfigure) PermitReentryIf(trigger string, guardFunc func(params ...interface{}) bool) error {
if guardFunc == nil {
return errors.New("guardFunc cannot be nil")
}
return c.internalPermitReentry(trigger, guardFunc)
}
// OnEntryFrom registers entry handler for the specified trigger when the current state changes to the state
func (c *StateConfigure) OnEntryFrom(trigger string, handlerFn func(params ...interface{})) error {
if handlerFn == nil {
return errors.New("onEntryFrom handler cannot be nil")
}
_, ok := c.onEntryMap[trigger]
if ok {
return errors.New("a function to handle entry transition for the trigger is already registered")
}
c.onEntryMap[trigger] = handlerFn
return nil
}
// OnExit registers exit handler for the specified trigger when the machine leaves the current state
func (c *StateConfigure) OnExit(fn func(trigger string, destState string)) error {
if c.onExitFunc != nil {
return errors.New("onExit can be handled by only just one function")
}
c.onExitFunc = fn
return nil
}
// SubstateOf registers the state as substate of parent state
func (c *StateConfigure) SubstateOf(parentStateName string) error {
if c.parentState != nil {
return errors.New("a state could have just only one parent")
}
if c.name == parentStateName {
return errors.New("a state cannot be substate of itself")
}
if _, ok := c.substates[parentStateName]; ok {
return errors.New("states cannot be substates of each other")
}
c.parentState = c.sm.Configure(parentStateName)
c.parentState.substates[c.name] = c
return nil
}
// StateMachine represents a state machine
type StateMachine struct {
stateMap map[string]*StateConfigure
currentState *StateConfigure
}
// New creates new state machine
func New(initialState string) *StateMachine {
sm := &StateMachine{
stateMap: make(map[string]*StateConfigure),
}
sm.currentState = sm.Configure(initialState)
return sm
}
// Configure adds a state into the machine. If the state already exists, returns the existing one
func (sm *StateMachine) Configure(stateName string) *StateConfigure {
sc, ok := sm.stateMap[stateName]
if ok {
return sc
}
sc = newStateConfigure(stateName, sm)
sm.stateMap[stateName] = sc
return sc
}
// CurrentState returns the current state of the machine
func (sm *StateMachine) CurrentState() *StateConfigure {
return sm.currentState
}
func (sm *StateMachine) lookUpTransition(trigger string, sourceState *StateConfigure) (transition *transitionRepresentation, srcState *StateConfigure, err error) {
currState := sourceState
transRepresent, ok := currState.transitionMap[trigger]
for {
if ok {
return transRepresent, currState, nil
}
if currState.parentState == nil {
break
}
currState = currState.parentState
transRepresent, ok = currState.transitionMap[trigger]
}
return nil, nil, errors.New("a valid transition not found")
}
// Fire triggers off a transition from the current state via trigger
// params will be passed to exit and entry handlers
func (sm *StateMachine) Fire(trigger string, params ...interface{}) (bool, error) {
transRepresent, _, errValidTransition := sm.lookUpTransition(trigger, sm.currentState)
if errValidTransition != nil {
return false, errValidTransition
}
allowTransition := true
if transRepresent.guardFunc != nil {
allowTransition = transRepresent.guardFunc(params...)
}
if allowTransition {
destState := transRepresent.destState
if sm.currentState.onExitFunc != nil {
sm.currentState.onExitFunc(trigger, transRepresent.destState.name)
}
sm.currentState = destState // update current state
if entryHandler, entryOk := destState.onEntryMap[trigger]; entryOk {
entryHandler(params...)
}
}
return allowTransition, nil
}