-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
252 lines (223 loc) · 6.47 KB
/
index.js
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
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
/**
* @license synchronous-fsm https://github.com/flams/synchronous-fsm
*
* The MIT License (MIT)
*
* Copyright (c) 2014 Olivier Scherrer <pode.fr@gmail.com>
*/
"use strict";
var toArray = require("to-array"),
simpleLoop = require("simple-loop");
/**
* @class
* Creates a stateMachine
*
* @param initState {String} the initial state
* @param diagram {Object} the diagram that describes the state machine
* @example
*
* diagram = {
* "State1" : [
* [ message1, action, nextState], // Same as the state's add function
* [ message2, action2, nextState]
* ],
*
* "State2" :
* [ message3, action3, scope3, nextState]
* ... and so on ....
*
* }
*
* @return the stateMachine object
*/
module.exports = function StateMachineConstructor($initState, $diagram) {
/**
* The list of states
* @private
*/
var _states = {},
/**
* The current state
* @private
*/
_currentState = "";
/**
* Set the initialization state
* @param {String} name the name of the init state
* @returns {Boolean}
*/
this.init = function init(name) {
if (_states[name]) {
_currentState = name;
return true;
} else {
return false;
}
};
/**
* Add a new state
* @private
* @param {String} name the name of the state
* @returns {State} a new state
*/
this.add = function add(name) {
if (!_states[name]) {
var transition = _states[name] = new Transition();
return transition;
} else {
return _states[name];
}
};
/**
* Get an existing state
* @private
* @param {String} name the name of the state
* @returns {State} the state
*/
this.get = function get(name) {
return _states[name];
};
/**
* Get the current state
* @returns {String}
*/
this.getCurrent = function getCurrent() {
return _currentState;
};
/**
* Tell if the state machine has the given state
* @param {String} state the name of the state
* @returns {Boolean} true if it has the given state
*/
this.has = function has(state) {
return _states.hasOwnProperty(state);
};
/**
* Advances the state machine to a given state
* @param {String} state the name of the state to advance the state machine to
* @returns {Boolean} true if it has the given state
*/
this.advance = function advance(state) {
if (this.has(state)) {
_currentState = state;
return true;
} else {
return false;
}
};
/**
* Pass an event to the state machine
* @param {String} name the name of the event
* @returns {Boolean} true if the event exists in the current state
*/
this.event = function event(name) {
var nextState;
nextState = _states[_currentState].event.apply(_states[_currentState].event, toArray(arguments));
// False means that there's no such event
// But undefined means that the state doesn't change
if (nextState === false) {
return false;
} else {
// There could be no next state, so the current one remains
if (nextState) {
// Call the exit action if any
_states[_currentState].event("exit");
_currentState = nextState;
// Call the new state's entry action if any
_states[_currentState].event("entry");
}
return true;
}
};
/**
* Initializes the StateMachine with the given diagram
*/
if ($diagram) {
simpleLoop($diagram, function (transition, state) {
var myState = this.add(state);
transition.forEach(function (params){
myState.add.apply(null, params);
});
}, this);
}
/**
* Sets its initial state
*/
this.init($initState);
};
/**
* Each state has associated transitions
* @constructor
*/
function Transition() {
/**
* The list of transitions associated to a state
* @private
*/
var _transitions = {};
/**
* Add a new transition
* @private
* @param {String} event the event that will trigger the transition
* @param {Function} action the function that is executed
* @param {Object} scope [optional] the scope in which to execute the action
* @param {String} next [optional] the name of the state to transit to.
* @returns {Boolean} true if success, false if the transition already exists
*/
this.add = function add(event, action, scope, next) {
var arr = [];
if (_transitions[event]) {
return false;
}
if (typeof event == "string" &&
typeof action == "function") {
arr[0] = action;
if (typeof scope == "object") {
arr[1] = scope;
}
if (typeof scope == "string") {
arr[2] = scope;
}
if (typeof next == "string") {
arr[2] = next;
}
_transitions[event] = arr;
return true;
}
return false;
};
/**
* Check if a transition can be triggered with given event
* @private
* @param {String} event the name of the event
* @returns {Boolean} true if exists
*/
this.has = function has(event) {
return !!_transitions[event];
};
/**
* Get a transition from it's event
* @private
* @param {String} event the name of the event
* @return the transition
*/
this.get = function get(event) {
return _transitions[event] || false;
};
/**
* Execute the action associated to the given event
* @param {String} event the name of the event
* @param {params} params to pass to the action
* @private
* @returns false if error, the next state or undefined if success (that sounds weird)
*/
this.event = function event(newEvent) {
var _transition = _transitions[newEvent];
if (_transition) {
_transition[0].apply(_transition[1], toArray(arguments).slice(1));
return _transition[2];
} else {
return false;
}
};
}