@statewalker/fsm 0.16.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +59 -0
- package/dist/index.d.ts +109 -0
- package/dist/index.js +337 -259
- package/package.json +24 -21
- package/src/FsmBaseClass.ts +52 -0
- package/src/FsmProcess.ts +184 -0
- package/src/FsmState.ts +87 -0
- package/src/FsmStateConfig.ts +13 -0
- package/src/FsmStateDescriptor.ts +47 -0
- package/src/context/handlers.ts +30 -0
- package/src/context/index.ts +3 -0
- package/src/context/printer.ts +48 -0
- package/src/context/tracer.ts +19 -0
- package/src/index.ts +5 -0
- package/src/utils/bindMethods.ts +7 -0
- package/dist/index-umd.js +0 -310
- package/dist/index-umd.min.js +0 -2
- package/dist/index.min.js +0 -2
- package/index.js +0 -1
- package/src/KEYS.js +0 -7
- package/src/buildStateDescriptor.js +0 -34
- package/src/checkProcessDescriptor.js +0 -54
- package/src/getAllStateKeys.js +0 -27
- package/src/getAllTransitions.js +0 -33
- package/src/getStateDescriptor.js +0 -8
- package/src/getTargetStateKey.js +0 -18
- package/src/index.js +0 -10
- package/src/newAsyncProcess.js +0 -47
- package/src/newProcessInstance.js +0 -44
- package/src/newSyncProcess.js +0 -27
package/dist/index.js
CHANGED
|
@@ -1,295 +1,373 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
1
|
+
// src/utils/bindMethods.ts
|
|
2
|
+
function bindMethods(obj, ...methods) {
|
|
3
|
+
const o = obj;
|
|
4
|
+
methods.forEach((methodName) => {
|
|
5
|
+
o[methodName] = o[methodName].bind(o);
|
|
6
|
+
});
|
|
7
|
+
return obj;
|
|
8
|
+
}
|
|
3
9
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
10
|
+
// src/FsmBaseClass.ts
|
|
11
|
+
var FsmBaseClass = class {
|
|
12
|
+
handlers = {};
|
|
13
|
+
data = {};
|
|
14
|
+
constructor() {
|
|
15
|
+
bindMethods(this, "setData", "getData");
|
|
16
|
+
}
|
|
17
|
+
setData(key, value) {
|
|
18
|
+
this.data[key] = value;
|
|
19
|
+
return this;
|
|
20
|
+
}
|
|
21
|
+
getData(key) {
|
|
22
|
+
return this.data[key];
|
|
23
|
+
}
|
|
24
|
+
// ----------------------------------------------
|
|
25
|
+
// internal methods
|
|
26
|
+
_addHandler(type, handler, direct = true) {
|
|
27
|
+
const list = this.handlers[type] = this.handlers[type] || [];
|
|
28
|
+
direct ? list.push(handler) : list.unshift(handler);
|
|
29
|
+
return () => this._removeHandler(type, handler);
|
|
30
|
+
}
|
|
31
|
+
_removeHandler(type, handler) {
|
|
32
|
+
let list = this.handlers[type];
|
|
33
|
+
if (!list)
|
|
34
|
+
return;
|
|
35
|
+
list = list.filter((h) => h !== handler);
|
|
36
|
+
if (list.length > 0) {
|
|
37
|
+
this.handlers[type] = list;
|
|
18
38
|
} else {
|
|
19
|
-
|
|
20
|
-
stateKey = from;
|
|
21
|
-
eventKey = event;
|
|
22
|
-
targetStateKey = to;
|
|
39
|
+
delete this.handlers[type];
|
|
23
40
|
}
|
|
24
|
-
const index = result.transitions[stateKey] = result.transitions[stateKey] || {};
|
|
25
|
-
index[eventKey] = targetStateKey;
|
|
26
41
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
42
|
+
_runHandlerSync(type, ...args) {
|
|
43
|
+
const list = this.handlers[type] || [];
|
|
44
|
+
return list.map((handler) => handler(...args));
|
|
45
|
+
}
|
|
46
|
+
async _runHandler(type, ...args) {
|
|
47
|
+
const promises = this._runHandlerSync(type, ...args);
|
|
48
|
+
for (const promise of promises) {
|
|
49
|
+
try {
|
|
50
|
+
await promise;
|
|
51
|
+
} catch (error) {
|
|
52
|
+
await this._handleError(error);
|
|
53
|
+
}
|
|
34
54
|
}
|
|
35
55
|
}
|
|
36
|
-
|
|
37
|
-
|
|
56
|
+
async _handleError(error) {
|
|
57
|
+
console.error(error);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
38
60
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
61
|
+
// src/FsmState.ts
|
|
62
|
+
var FsmState = class extends FsmBaseClass {
|
|
63
|
+
process;
|
|
64
|
+
key;
|
|
65
|
+
parent;
|
|
66
|
+
descriptor;
|
|
67
|
+
constructor(process, parent, key, descriptor) {
|
|
68
|
+
super();
|
|
69
|
+
this.process = process;
|
|
70
|
+
this.key = key;
|
|
71
|
+
this.parent = parent;
|
|
72
|
+
this.descriptor = descriptor;
|
|
73
|
+
bindMethods(
|
|
74
|
+
this,
|
|
75
|
+
"onEnter",
|
|
76
|
+
"onExit",
|
|
77
|
+
"dump",
|
|
78
|
+
"restore",
|
|
79
|
+
"useData",
|
|
80
|
+
"onStateError"
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
onEnter(handler) {
|
|
84
|
+
return this._addHandler("onEnter", handler, true);
|
|
85
|
+
}
|
|
86
|
+
onExit(handler) {
|
|
87
|
+
return this._addHandler("onExit", handler, false);
|
|
88
|
+
}
|
|
89
|
+
onStateError(handler) {
|
|
90
|
+
return this._addHandler("onStateError", handler);
|
|
91
|
+
}
|
|
92
|
+
dump(handler) {
|
|
93
|
+
return this._addHandler("dump", handler, true);
|
|
94
|
+
}
|
|
95
|
+
restore(handler) {
|
|
96
|
+
return this._addHandler("restore", handler, true);
|
|
97
|
+
}
|
|
98
|
+
getData(key, recursive = true) {
|
|
99
|
+
return this.data[key] ?? (recursive ? this.parent?.getData(key, recursive) : void 0);
|
|
100
|
+
}
|
|
101
|
+
useData(key) {
|
|
102
|
+
return [
|
|
103
|
+
(recursive = true) => this.getData(key, recursive),
|
|
104
|
+
(value) => this.setData(key, value)
|
|
105
|
+
];
|
|
106
|
+
}
|
|
107
|
+
async _handleError(error) {
|
|
108
|
+
await this._runHandler("onStateError", this, error);
|
|
109
|
+
await this.process._handleStateError(this, error);
|
|
110
|
+
}
|
|
45
111
|
};
|
|
46
112
|
|
|
47
|
-
|
|
48
|
-
|
|
113
|
+
// src/FsmStateConfig.ts
|
|
114
|
+
var STATE_ANY = "*";
|
|
115
|
+
var STATE_INITIAL = "";
|
|
116
|
+
var STATE_FINAL = "";
|
|
117
|
+
var EVENT_ANY = "*";
|
|
118
|
+
var EVENT_EMPTY = "";
|
|
49
119
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
120
|
+
// src/FsmStateDescriptor.ts
|
|
121
|
+
var FsmStateDescriptor = class _FsmStateDescriptor {
|
|
122
|
+
transitions = {};
|
|
123
|
+
states = {};
|
|
124
|
+
static build(config) {
|
|
125
|
+
const descriptor = new _FsmStateDescriptor();
|
|
126
|
+
for (const [from, event, to] of config.transitions) {
|
|
127
|
+
const index = descriptor.transitions[from] = descriptor.transitions[from] || {};
|
|
128
|
+
index[event] = to;
|
|
58
129
|
}
|
|
59
|
-
|
|
60
|
-
|
|
130
|
+
if (config.states) {
|
|
131
|
+
for (const substateConfig of config.states) {
|
|
132
|
+
descriptor.states[substateConfig.key] = this.build(substateConfig);
|
|
133
|
+
}
|
|
61
134
|
}
|
|
62
|
-
return
|
|
135
|
+
return descriptor;
|
|
63
136
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
137
|
+
getTargetStateKey(stateKey, eventKey) {
|
|
138
|
+
const pairs = [
|
|
139
|
+
[stateKey, eventKey],
|
|
140
|
+
[STATE_ANY, eventKey],
|
|
141
|
+
[stateKey, EVENT_ANY],
|
|
142
|
+
[STATE_ANY, EVENT_ANY]
|
|
143
|
+
];
|
|
144
|
+
let targetKey;
|
|
145
|
+
for (let i = 0, len = pairs.length; targetKey === void 0 && i < len; i++) {
|
|
146
|
+
const [stateKey2, eventKey2] = pairs[i];
|
|
147
|
+
const stateTransitions = this.transitions[stateKey2];
|
|
148
|
+
if (!stateTransitions)
|
|
149
|
+
continue;
|
|
150
|
+
targetKey = stateTransitions[eventKey2];
|
|
73
151
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const deadendStates = [];
|
|
152
|
+
return targetKey !== void 0 ? targetKey : STATE_FINAL;
|
|
153
|
+
}
|
|
154
|
+
};
|
|
78
155
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
156
|
+
// src/FsmProcess.ts
|
|
157
|
+
var STATUS_NONE = 0;
|
|
158
|
+
var STATUS_FIRST = 1;
|
|
159
|
+
var STATUS_NEXT = 2;
|
|
160
|
+
var STATUS_LEAF = 4;
|
|
161
|
+
var STATUS_LAST = 8;
|
|
162
|
+
var STATUS_FINISHED = 16;
|
|
163
|
+
var STATUS_ENTER = STATUS_FIRST | STATUS_NEXT;
|
|
164
|
+
var STATUS_EXIT = STATUS_LEAF | STATUS_LAST;
|
|
165
|
+
var FsmProcess = class extends FsmBaseClass {
|
|
166
|
+
state;
|
|
167
|
+
event;
|
|
168
|
+
status = 0;
|
|
169
|
+
config;
|
|
170
|
+
rootDescriptor;
|
|
171
|
+
constructor(config) {
|
|
172
|
+
super();
|
|
173
|
+
this.rootDescriptor = FsmStateDescriptor.build(config);
|
|
174
|
+
this.config = config;
|
|
175
|
+
bindMethods(this, "dispatch", "dump", "restore");
|
|
176
|
+
}
|
|
177
|
+
async dispatch(event, mask = STATUS_LEAF) {
|
|
178
|
+
this.event = event;
|
|
179
|
+
while (true) {
|
|
180
|
+
if (this.status & STATUS_EXIT) {
|
|
181
|
+
await this.state?._runHandler("onExit", this.state);
|
|
86
182
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if (targetKey !== KEYS.STATE_FINAL && !(targetKey in sourceStates)) {
|
|
92
|
-
deadendStates.push(targetKey);
|
|
183
|
+
if (!this._update())
|
|
184
|
+
return false;
|
|
185
|
+
if (this.status & STATUS_ENTER) {
|
|
186
|
+
await this.state?._runHandler("onEnter", this.state);
|
|
93
187
|
}
|
|
188
|
+
if (this.status & mask)
|
|
189
|
+
return true;
|
|
94
190
|
}
|
|
95
|
-
|
|
96
|
-
return { unreachableStates, deadendStates }
|
|
97
191
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
192
|
+
async dump(...args) {
|
|
193
|
+
const dumpState = async (state) => {
|
|
194
|
+
const stateDump = {
|
|
195
|
+
key: state.key,
|
|
196
|
+
data: {}
|
|
197
|
+
};
|
|
198
|
+
await state._runHandler("dump", state, stateDump.data, ...args);
|
|
199
|
+
return stateDump;
|
|
200
|
+
};
|
|
201
|
+
const dumpStates = async (state, stack = []) => {
|
|
202
|
+
if (!state)
|
|
203
|
+
return stack;
|
|
204
|
+
state.parent && await dumpStates(state.parent, stack);
|
|
205
|
+
stack.push(await dumpState(state));
|
|
206
|
+
return stack;
|
|
207
|
+
};
|
|
208
|
+
const dump = {
|
|
209
|
+
status: this.status,
|
|
210
|
+
event: this.event,
|
|
211
|
+
stack: await dumpStates(this.state)
|
|
212
|
+
};
|
|
213
|
+
return dump;
|
|
105
214
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const add = (key) => index[key] = (index[key] || 0) + 1;
|
|
120
|
-
visit(descriptor);
|
|
121
|
-
return Object.keys(index).sort();
|
|
122
|
-
function visit(descriptor) {
|
|
123
|
-
add(descriptor.key);
|
|
124
|
-
for (const [from, destinations] of Object.entries(descriptor.transitions)) {
|
|
125
|
-
add(from);
|
|
126
|
-
for (const targetKey of Object.values(destinations)) {
|
|
127
|
-
add(targetKey);
|
|
128
|
-
}
|
|
215
|
+
async restore(dump, ...args) {
|
|
216
|
+
this.status = dump.status || 0;
|
|
217
|
+
this.event = dump.event;
|
|
218
|
+
this.state = void 0;
|
|
219
|
+
for (let i = 0; i < dump.stack.length; i++) {
|
|
220
|
+
const stateDump = dump.stack[i];
|
|
221
|
+
this.state = this.state ? this._newSubstate(this.state, stateDump.key) : this._newState(void 0, stateDump.key, this.rootDescriptor);
|
|
222
|
+
await this.state._runHandler(
|
|
223
|
+
"restore",
|
|
224
|
+
this.state,
|
|
225
|
+
stateDump.data,
|
|
226
|
+
...args
|
|
227
|
+
);
|
|
129
228
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
229
|
+
return this;
|
|
230
|
+
}
|
|
231
|
+
onStateCreate(handler) {
|
|
232
|
+
return this._addHandler("onStateCreate", handler, true);
|
|
233
|
+
}
|
|
234
|
+
onStateError(handler) {
|
|
235
|
+
return this._addHandler("onStateError", handler);
|
|
236
|
+
}
|
|
237
|
+
async _handleStateError(state, error) {
|
|
238
|
+
await this._runHandler("onStateError", state, error);
|
|
239
|
+
this._handleError(error);
|
|
240
|
+
}
|
|
241
|
+
_newState(parent, key, descriptor) {
|
|
242
|
+
const state = new FsmState(this, parent, key, descriptor);
|
|
243
|
+
this._runHandlerSync("onStateCreate", state);
|
|
244
|
+
return state;
|
|
245
|
+
}
|
|
246
|
+
_getSubstate(parent, prevStateKey) {
|
|
247
|
+
if (!parent)
|
|
248
|
+
return;
|
|
249
|
+
const toState = parent.descriptor?.getTargetStateKey(
|
|
250
|
+
prevStateKey || STATE_INITIAL,
|
|
251
|
+
this.event || EVENT_EMPTY
|
|
252
|
+
) || STATE_FINAL;
|
|
253
|
+
if (!toState)
|
|
254
|
+
return;
|
|
255
|
+
return this._newSubstate(parent, toState);
|
|
256
|
+
}
|
|
257
|
+
_newSubstate(parent, toState) {
|
|
258
|
+
let descriptor;
|
|
259
|
+
for (let state = parent; !descriptor && state; state = state.parent) {
|
|
260
|
+
descriptor = state.descriptor?.states[toState];
|
|
133
261
|
}
|
|
262
|
+
return this._newState(parent, toState, descriptor);
|
|
134
263
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
[KEYS.STATE_ANY, KEYS.EVENT_ANY]
|
|
143
|
-
];
|
|
144
|
-
let targetKey;
|
|
145
|
-
for (let i = 0, len = pairs.length; (targetKey === undefined) && i < len; i++) {
|
|
146
|
-
const [stateKey, eventKey] = pairs[i];
|
|
147
|
-
const stateTransitions = descriptor.transitions[stateKey];
|
|
148
|
-
if (!stateTransitions) continue;
|
|
149
|
-
targetKey = stateTransitions[eventKey];
|
|
150
|
-
}
|
|
151
|
-
return (targetKey !== undefined) ? targetKey : KEYS.STATE_FINAL;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function newProcessInstance({
|
|
155
|
-
config,
|
|
156
|
-
descriptor,
|
|
157
|
-
newState = (obj) => obj,
|
|
158
|
-
newProcess = (descriptor) => ({
|
|
159
|
-
descriptor,
|
|
160
|
-
status: 0,
|
|
161
|
-
stack: [],
|
|
162
|
-
event: undefined,
|
|
163
|
-
current: undefined
|
|
164
|
-
})
|
|
165
|
-
}) {
|
|
166
|
-
descriptor = descriptor || buildStateDescriptor(config);
|
|
167
|
-
const process = newProcess(descriptor);
|
|
168
|
-
const loadNextState = () => {
|
|
169
|
-
let key, descriptor;
|
|
170
|
-
if (!process.status) {
|
|
171
|
-
descriptor = process.descriptor;
|
|
172
|
-
key = descriptor.key;
|
|
264
|
+
_update() {
|
|
265
|
+
if (this.status & STATUS_FINISHED)
|
|
266
|
+
return false;
|
|
267
|
+
const nextState = this.status !== STATUS_NONE ? this.status & STATUS_ENTER ? this._getSubstate(this.state, STATE_INITIAL) : this._getSubstate(this.state?.parent, this.state?.key) : this._newState(void 0, this.config.key, this.rootDescriptor);
|
|
268
|
+
if (nextState !== void 0) {
|
|
269
|
+
this.state = nextState;
|
|
270
|
+
this.status = this.status & STATUS_EXIT ? STATUS_NEXT : STATUS_FIRST;
|
|
173
271
|
} else {
|
|
174
|
-
if (
|
|
175
|
-
|
|
272
|
+
if (this.status & STATUS_EXIT) {
|
|
273
|
+
this.state = this.state?.parent;
|
|
274
|
+
this.status = STATUS_LAST;
|
|
176
275
|
} else {
|
|
177
|
-
|
|
178
|
-
const parentDescriptor = process.stack[process.stack.length - 1].descriptor;
|
|
179
|
-
const stateKey = process.status & MODE.ENTER
|
|
180
|
-
? KEYS.STATE_INITIAL
|
|
181
|
-
: process.current ? process.current.key : KEYS.STATE_FINAL;
|
|
182
|
-
key = getTargetStateKey(parentDescriptor, stateKey, eventKey);
|
|
183
|
-
if (key) {
|
|
184
|
-
descriptor = getStateDescriptor(process, key);
|
|
185
|
-
}
|
|
276
|
+
this.status = STATUS_LEAF;
|
|
186
277
|
}
|
|
278
|
+
if (!this.state)
|
|
279
|
+
this.status = STATUS_FINISHED;
|
|
187
280
|
}
|
|
188
|
-
return
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
}
|
|
281
|
+
return !(this.status & STATUS_FINISHED);
|
|
282
|
+
}
|
|
283
|
+
};
|
|
192
284
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
}
|
|
201
|
-
const [process, loadNextState] = newProcessInstance({
|
|
202
|
-
config,
|
|
203
|
-
descriptor,
|
|
204
|
-
newProcess,
|
|
205
|
-
newState
|
|
206
|
-
});
|
|
207
|
-
const shift = newAsyncTreeWalker(before, after, process);
|
|
208
|
-
process.finished = false;
|
|
209
|
-
process.next = (event) => {
|
|
210
|
-
process.nextEvent = event;
|
|
211
|
-
return process.running = process.running || Promise
|
|
212
|
-
.resolve()
|
|
213
|
-
.then(async () => {
|
|
214
|
-
try {
|
|
215
|
-
process.event = process.nextEvent;
|
|
216
|
-
process.nextEvent = undefined;
|
|
217
|
-
while (!process.finished && process.event) {
|
|
218
|
-
process.finished = !await shift(loadNextState);
|
|
219
|
-
if ((process.status & MODE.EXIT) && process.nextEvent) {
|
|
220
|
-
// Consume the next event if it exists.
|
|
221
|
-
process.event = process.nextEvent;
|
|
222
|
-
process.nextEvent = undefined;
|
|
223
|
-
} else if (process.status & MODE.LEAF) {
|
|
224
|
-
// Returns control when the process is in a "leaf" state (a state without children)
|
|
225
|
-
break;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
} catch (error) {
|
|
229
|
-
handleError(error);
|
|
230
|
-
}
|
|
231
|
-
})
|
|
232
|
-
.finally(() => process.running = undefined)
|
|
233
|
-
.then(() => !process.finished);
|
|
234
|
-
};
|
|
235
|
-
return process;
|
|
285
|
+
// src/context/handlers.ts
|
|
286
|
+
var KEY_HANDLERS = "handlers";
|
|
287
|
+
function callStateHandlers(state) {
|
|
288
|
+
const key = state.key;
|
|
289
|
+
for (let parent = state.parent; !!parent; parent = parent?.parent) {
|
|
290
|
+
const handlers = parent.getData(KEY_HANDLERS)?.[key];
|
|
291
|
+
handlers?.forEach((handler) => handler(state));
|
|
292
|
+
}
|
|
236
293
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
* @returns {Array<Object>} - an array of all transitions;
|
|
244
|
-
* each entry in this returned array contains the following keys:
|
|
245
|
-
* 1) parentStateKey - key of the state where the transition can be activated
|
|
246
|
-
* 2) sourceStateKey - the initial transition state
|
|
247
|
-
* 3) eventKey - key of the event activating transition
|
|
248
|
-
* 4) targetStateKey - the resulting state for this transition
|
|
249
|
-
*
|
|
250
|
-
*/
|
|
251
|
-
function getAllTransitions(process) {
|
|
252
|
-
const list = [];
|
|
253
|
-
let stateKey = process.current ? process.current.key : KEYS.STATE_INITIAL;
|
|
254
|
-
for (let i = process.stack.length - 1; i >= 0; i--) {
|
|
255
|
-
const parentState = process.stack[i];
|
|
256
|
-
const parentStateKey = parentState.key;
|
|
257
|
-
const descriptor = parentState.descriptor;
|
|
258
|
-
for (const sKey of [stateKey, KEYS.STATE_ANY]) {
|
|
259
|
-
const stateTransitions = descriptor.transitions[sKey];
|
|
260
|
-
if (!stateTransitions) continue;
|
|
261
|
-
for (const[eventKey, targetKey] of Object.entries(stateTransitions)) {
|
|
262
|
-
list.push([parentStateKey, sKey, eventKey, targetKey]);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
stateKey = parentStateKey;
|
|
294
|
+
function addSubstateHandlers(state, handlers) {
|
|
295
|
+
const oldIndex = state.getData(KEY_HANDLERS);
|
|
296
|
+
const index = oldIndex ? { ...oldIndex } : {};
|
|
297
|
+
for (const [key, handler] of Object.entries(handlers)) {
|
|
298
|
+
const list = index[key] = index[key] || [];
|
|
299
|
+
list.push(handler);
|
|
266
300
|
}
|
|
267
|
-
|
|
301
|
+
state.setData(KEY_HANDLERS, index);
|
|
268
302
|
}
|
|
269
303
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
config,
|
|
279
|
-
descriptor,
|
|
280
|
-
newProcess,
|
|
281
|
-
newState,
|
|
282
|
-
});
|
|
283
|
-
const shift = newTreeWalker(before, after, process);
|
|
284
|
-
process.dispatch = process.next = (event) => {
|
|
285
|
-
if (typeof event === 'string') event = { key : event };
|
|
286
|
-
process.event = event;
|
|
287
|
-
while (process.event) {
|
|
288
|
-
if (!shift(loadNextState)) return false;
|
|
289
|
-
if (process.status & MODE.LEAF) return true;
|
|
304
|
+
// src/context/printer.ts
|
|
305
|
+
var KEY_PRINTER = "printer";
|
|
306
|
+
function preparePrinter(process, { prefix = "", print = console.log, lineNumbers = false }) {
|
|
307
|
+
let lineCounter = 0;
|
|
308
|
+
const shift = () => {
|
|
309
|
+
let prefix2 = "";
|
|
310
|
+
for (let s = process.state?.parent; !!s; s = s.parent) {
|
|
311
|
+
prefix2 += " ";
|
|
290
312
|
}
|
|
313
|
+
return prefix2;
|
|
291
314
|
};
|
|
292
|
-
|
|
315
|
+
const getPrefix = lineNumbers ? () => `[${++lineCounter}]${shift()}` : shift;
|
|
316
|
+
const printer = (...args) => print(prefix, getPrefix(), ...args);
|
|
317
|
+
return printer;
|
|
318
|
+
}
|
|
319
|
+
function setPrinter(state, config = {}) {
|
|
320
|
+
const printer = preparePrinter(state.process, config);
|
|
321
|
+
state.setData(KEY_PRINTER, printer);
|
|
322
|
+
}
|
|
323
|
+
function setProcessPrinter(process, config = {}) {
|
|
324
|
+
const printer = preparePrinter(process, config);
|
|
325
|
+
process.setData(KEY_PRINTER, printer);
|
|
326
|
+
}
|
|
327
|
+
function getPrinter(state) {
|
|
328
|
+
return state.getData(KEY_PRINTER, true) || state.process.getData(KEY_PRINTER) || console.log;
|
|
293
329
|
}
|
|
294
330
|
|
|
295
|
-
|
|
331
|
+
// src/context/tracer.ts
|
|
332
|
+
function setProcessTracer(process, print) {
|
|
333
|
+
return process.onStateCreate((state) => {
|
|
334
|
+
setStateTracer(state, print);
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
function setStateTracer(state, print) {
|
|
338
|
+
const printLine = print || getPrinter(state);
|
|
339
|
+
state.onEnter(() => {
|
|
340
|
+
printLine(`<${state?.key} event="${state.process.event}">`);
|
|
341
|
+
});
|
|
342
|
+
state.onExit(() => {
|
|
343
|
+
printLine(`</${state.key}> <!-- event="${state.process.event}" -->`);
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
export {
|
|
347
|
+
EVENT_ANY,
|
|
348
|
+
EVENT_EMPTY,
|
|
349
|
+
FsmProcess,
|
|
350
|
+
FsmState,
|
|
351
|
+
FsmStateDescriptor,
|
|
352
|
+
KEY_HANDLERS,
|
|
353
|
+
KEY_PRINTER,
|
|
354
|
+
STATE_ANY,
|
|
355
|
+
STATE_FINAL,
|
|
356
|
+
STATE_INITIAL,
|
|
357
|
+
STATUS_ENTER,
|
|
358
|
+
STATUS_EXIT,
|
|
359
|
+
STATUS_FINISHED,
|
|
360
|
+
STATUS_FIRST,
|
|
361
|
+
STATUS_LAST,
|
|
362
|
+
STATUS_LEAF,
|
|
363
|
+
STATUS_NEXT,
|
|
364
|
+
STATUS_NONE,
|
|
365
|
+
addSubstateHandlers,
|
|
366
|
+
callStateHandlers,
|
|
367
|
+
getPrinter,
|
|
368
|
+
preparePrinter,
|
|
369
|
+
setPrinter,
|
|
370
|
+
setProcessPrinter,
|
|
371
|
+
setProcessTracer,
|
|
372
|
+
setStateTracer
|
|
373
|
+
};
|