-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathfsm.go
201 lines (179 loc) · 6.38 KB
/
fsm.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
package fsm
import (
"bytes"
"encoding/json"
"fmt"
)
/*
implementation of a concurrent safe finite state machine
In a replicated state machine, if a transaction is valid,
a set of inputs will cause the state of the system to
transition to the next state. A transaction is an atomic
operation on a database. This means the operations either
complete in full or never complete at all. The set of
transactions maintained in a replicated state machine is
known as a “transaction log.”
*/
type FiniteStateMachine struct {
namedStates map[string]StateEvents
namedTransitions map[string]StateTransition
currentState string
currentStateEvents StateEvents
}
func (machine *FiniteStateMachine) AddState(stateName string, stateEvent StateEvents) {
stateEvent.StateType = State
machine.namedStates[stateName] = stateEvent
}
func (machine *FiniteStateMachine) DeleteState(stateName string) {
delete(machine.namedStates, stateName)
}
// HasState returns if the FSM has a StateEvents added with given name
func (machine FiniteStateMachine) HasState(name string) bool {
_, hasKey := machine.namedStates[name]
return hasKey
}
// adds new transaction to the Finite state machine specifing the transaction name, origin and destination
func (machine *FiniteStateMachine) AddTransaction(transactioName string, fromState string, toState string) {
tx := StateTransition{TransactionName: transactioName, FromState: fromState, ToState: toState}
key := fromState + "-" + toState
machine.namedTransitions[key] = tx
}
// deletes an existing transaction from the Finite state machine specifing the transaction name
func (machine *FiniteStateMachine) DeleteTransaction(transactioName string) {
delete(machine.namedTransitions, transactioName)
}
func (machine *FiniteStateMachine) SetInitialState(stateName string) {
if machine.currentState == "" {
//an initial state setup has been requested
//fmt.Println("setting FSM initial state to", stateName)
machine.currentState = stateName
//trigger requested state onEnter event
state, ok := machine.namedStates[stateName]
if ok {
machine.currentStateEvents = state
if state.OnEnter != nil {
//fmt.Println("triggering ", stateName, "OnEnter() event")
state.OnEnter()
}
state.StateType = StartState
machine.namedStates[stateName] = state
}
}
}
func (machine *FiniteStateMachine) SetFinalState(stateName string) {
//an initial state setup has been requested
//fmt.Println("setting FSM final state to", stateName)
state, ok := machine.namedStates[stateName]
if ok {
state.StateType = EndState
machine.namedStates[stateName] = state
}
}
// changes the current state of the Finite state machine to requested state if allowed by transactions
func (machine *FiniteStateMachine) ChangeStateTo(stateName string) {
//a typical FSM state change
// check if we have a valid transaction from current state to requested state
_, valid := machine.HasValidTransaction(machine.currentState, stateName)
if valid {
//fmt.Println("changing state from", machine.currentState, "to", stateName, "using transaction", txData.TransactionName)
//trigger previous state exit event
if machine.currentStateEvents.OnExit != nil {
machine.currentStateEvents.OnExit()
}
//trigger requested state onEnter event
state, ok := machine.namedStates[stateName]
if ok {
machine.currentStateEvents = state
if state.OnEnter != nil {
//fmt.Println("triggering ", stateName, "OnEnter() event")
state.OnEnter()
}
}
machine.currentState = stateName
} else {
//fmt.Println("there is no a direct transaction from", machine.currentState, "to", stateName)
}
}
func (machine FiniteStateMachine) HasValidTransaction(from string, to string) (StateTransition, bool) {
//fmt.Println("checkinf if valid transaction exist from", from, "to", to)
key := from + "-" + to
tx, found := machine.namedTransitions[key]
return tx, found
}
// Visualize outputs a visualization of a FSM in Graphviz format.
func (machine FiniteStateMachine) DotGraph() string {
var buf bytes.Buffer
states := make(map[string]int)
buf.WriteString(`digraph fsm {
size ="4,4";
node [shape=circle,fontsize=12,fixedsize=true,width=0.8];
edge [fontsize=6];
rankdir=LR;
`)
// make sure the initial state is at top
for k, v := range machine.namedTransitions {
states[k]++
buf.WriteString(fmt.Sprintf(` "%s" -> "%s" [ label = "%s" ];`, v.FromState, v.ToState, v.TransactionName))
buf.WriteString("\n")
}
buf.WriteString("\n")
for k, v := range machine.namedStates {
buf.WriteString(fmt.Sprintf(` "%s" %s;`, k, v.dot()))
buf.WriteString("\n")
}
buf.WriteString(fmt.Sprintln("}"))
return buf.String()
}
// return machine content encoded as Json
func (machine FiniteStateMachine) Json() ([]byte, error) {
type internalMachine struct {
NamedStates map[string]StateEvents `json:"states"`
NamedTransactions map[string]StateTransition `json:"transitions"`
CurrentState string `json:"current"`
}
var internal internalMachine
internal = internalMachine{
NamedStates: machine.namedStates,
NamedTransactions: machine.namedTransitions,
CurrentState: machine.currentState,
}
return json.Marshal(internal)
}
//buils the machine from json input
func (machine *FiniteStateMachine) Load(raw []byte) error {
type internalMachine struct {
NamedStates map[string]StateEvents `json:"states"`
NamedTransitions map[string]StateTransition `json:"transitions"`
CurrentState string `json:"current"`
}
var internal internalMachine
internal = internalMachine{}
err := json.Unmarshal(raw, &internal)
if err != nil {
return err
} else {
machine.namedStates = internal.NamedStates
machine.namedTransitions = internal.NamedTransitions
machine.currentState = "start"
machine.currentStateEvents = StateEvents{}
return nil
}
}
// State returns current state of the FSM
func (machine *FiniteStateMachine) State() string {
return machine.currentState
}
// Create a new Finite state machine and returns it as struct
func New() FiniteStateMachine {
m := FiniteStateMachine{}
m.namedStates = make(map[string]StateEvents, 0)
m.namedTransitions = make(map[string]StateTransition, 0)
return m
}
// Create a new Finite state machine and returns it as pointer
func NewPtr() *FiniteStateMachine {
m := new(FiniteStateMachine)
m.namedStates = make(map[string]StateEvents, 0)
m.namedTransitions = make(map[string]StateTransition, 0)
return m
}