@statewalker/fsm 0.16.0 → 0.20.2
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 +111 -0
- package/dist/index.js +329 -259
- package/package.json +26 -24
- 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 +20 -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,365 @@
|
|
|
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) return;
|
|
34
|
+
list = list.filter((h) => h !== handler);
|
|
35
|
+
if (list.length > 0) {
|
|
36
|
+
this.handlers[type] = list;
|
|
18
37
|
} else {
|
|
19
|
-
|
|
20
|
-
stateKey = from;
|
|
21
|
-
eventKey = event;
|
|
22
|
-
targetStateKey = to;
|
|
38
|
+
delete this.handlers[type];
|
|
23
39
|
}
|
|
24
|
-
const index = result.transitions[stateKey] = result.transitions[stateKey] || {};
|
|
25
|
-
index[eventKey] = targetStateKey;
|
|
26
40
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
41
|
+
_runHandlerSync(type, ...args) {
|
|
42
|
+
const list = this.handlers[type] || [];
|
|
43
|
+
return list.map((handler) => handler(...args));
|
|
44
|
+
}
|
|
45
|
+
async _runHandler(type, ...args) {
|
|
46
|
+
const promises = this._runHandlerSync(type, ...args);
|
|
47
|
+
for (const promise of promises) {
|
|
48
|
+
try {
|
|
49
|
+
await promise;
|
|
50
|
+
} catch (error) {
|
|
51
|
+
await this._handleError(error);
|
|
52
|
+
}
|
|
34
53
|
}
|
|
35
54
|
}
|
|
36
|
-
|
|
37
|
-
|
|
55
|
+
async _handleError(error) {
|
|
56
|
+
console.error(error);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
38
59
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
60
|
+
// src/FsmState.ts
|
|
61
|
+
var FsmState = class extends FsmBaseClass {
|
|
62
|
+
process;
|
|
63
|
+
key;
|
|
64
|
+
parent;
|
|
65
|
+
descriptor;
|
|
66
|
+
constructor(process, parent, key, descriptor) {
|
|
67
|
+
super();
|
|
68
|
+
this.process = process;
|
|
69
|
+
this.key = key;
|
|
70
|
+
this.parent = parent;
|
|
71
|
+
this.descriptor = descriptor;
|
|
72
|
+
bindMethods(
|
|
73
|
+
this,
|
|
74
|
+
"onEnter",
|
|
75
|
+
"onExit",
|
|
76
|
+
"dump",
|
|
77
|
+
"restore",
|
|
78
|
+
"useData",
|
|
79
|
+
"onStateError"
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
onEnter(handler) {
|
|
83
|
+
return this._addHandler("onEnter", handler, true);
|
|
84
|
+
}
|
|
85
|
+
onExit(handler) {
|
|
86
|
+
return this._addHandler("onExit", handler, false);
|
|
87
|
+
}
|
|
88
|
+
onStateError(handler) {
|
|
89
|
+
return this._addHandler("onStateError", handler);
|
|
90
|
+
}
|
|
91
|
+
dump(handler) {
|
|
92
|
+
return this._addHandler("dump", handler, true);
|
|
93
|
+
}
|
|
94
|
+
restore(handler) {
|
|
95
|
+
return this._addHandler("restore", handler, true);
|
|
96
|
+
}
|
|
97
|
+
getData(key, recursive = true) {
|
|
98
|
+
return this.data[key] ?? (recursive ? this.parent?.getData(key, recursive) : void 0);
|
|
99
|
+
}
|
|
100
|
+
useData(key) {
|
|
101
|
+
return [
|
|
102
|
+
(recursive = true) => this.getData(key, recursive),
|
|
103
|
+
(value) => this.setData(key, value)
|
|
104
|
+
];
|
|
105
|
+
}
|
|
106
|
+
async _handleError(error) {
|
|
107
|
+
await this._runHandler("onStateError", this, error);
|
|
108
|
+
await this.process._handleStateError(this, error);
|
|
109
|
+
}
|
|
45
110
|
};
|
|
46
111
|
|
|
47
|
-
|
|
48
|
-
|
|
112
|
+
// src/FsmStateConfig.ts
|
|
113
|
+
var STATE_ANY = "*";
|
|
114
|
+
var STATE_INITIAL = "";
|
|
115
|
+
var STATE_FINAL = "";
|
|
116
|
+
var EVENT_ANY = "*";
|
|
117
|
+
var EVENT_EMPTY = "";
|
|
49
118
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
119
|
+
// src/FsmStateDescriptor.ts
|
|
120
|
+
var FsmStateDescriptor = class _FsmStateDescriptor {
|
|
121
|
+
transitions = {};
|
|
122
|
+
states = {};
|
|
123
|
+
static build(config) {
|
|
124
|
+
const descriptor = new _FsmStateDescriptor();
|
|
125
|
+
for (const [from, event, to] of config.transitions || []) {
|
|
126
|
+
const index = descriptor.transitions[from] = descriptor.transitions[from] || {};
|
|
127
|
+
index[event] = to;
|
|
58
128
|
}
|
|
59
|
-
|
|
60
|
-
|
|
129
|
+
if (config.states) {
|
|
130
|
+
for (const substateConfig of config.states) {
|
|
131
|
+
descriptor.states[substateConfig.key] = this.build(substateConfig);
|
|
132
|
+
}
|
|
61
133
|
}
|
|
62
|
-
return
|
|
134
|
+
return descriptor;
|
|
63
135
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
136
|
+
getTargetStateKey(stateKey, eventKey) {
|
|
137
|
+
const pairs = [
|
|
138
|
+
[stateKey, eventKey],
|
|
139
|
+
[STATE_ANY, eventKey],
|
|
140
|
+
[stateKey, EVENT_ANY],
|
|
141
|
+
[STATE_ANY, EVENT_ANY]
|
|
142
|
+
];
|
|
143
|
+
let targetKey;
|
|
144
|
+
for (let i = 0, len = pairs.length; targetKey === void 0 && i < len; i++) {
|
|
145
|
+
const [stateKey2, eventKey2] = pairs[i];
|
|
146
|
+
const stateTransitions = this.transitions[stateKey2];
|
|
147
|
+
if (!stateTransitions) continue;
|
|
148
|
+
targetKey = stateTransitions[eventKey2];
|
|
73
149
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const deadendStates = [];
|
|
150
|
+
return targetKey !== void 0 ? targetKey : STATE_FINAL;
|
|
151
|
+
}
|
|
152
|
+
};
|
|
78
153
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
154
|
+
// src/FsmProcess.ts
|
|
155
|
+
var STATUS_NONE = 0;
|
|
156
|
+
var STATUS_FIRST = 1;
|
|
157
|
+
var STATUS_NEXT = 2;
|
|
158
|
+
var STATUS_LEAF = 4;
|
|
159
|
+
var STATUS_LAST = 8;
|
|
160
|
+
var STATUS_FINISHED = 16;
|
|
161
|
+
var STATUS_ENTER = STATUS_FIRST | STATUS_NEXT;
|
|
162
|
+
var STATUS_EXIT = STATUS_LEAF | STATUS_LAST;
|
|
163
|
+
var FsmProcess = class extends FsmBaseClass {
|
|
164
|
+
state;
|
|
165
|
+
event;
|
|
166
|
+
status = 0;
|
|
167
|
+
config;
|
|
168
|
+
rootDescriptor;
|
|
169
|
+
constructor(config) {
|
|
170
|
+
super();
|
|
171
|
+
this.rootDescriptor = FsmStateDescriptor.build(config);
|
|
172
|
+
this.config = config;
|
|
173
|
+
bindMethods(this, "dispatch", "dump", "restore");
|
|
174
|
+
}
|
|
175
|
+
async dispatch(event, mask = STATUS_LEAF) {
|
|
176
|
+
this.event = event;
|
|
177
|
+
while (true) {
|
|
178
|
+
if (this.status & STATUS_EXIT) {
|
|
179
|
+
await this.state?._runHandler("onExit", this.state);
|
|
86
180
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
for (const targetKey of Object.keys(targetStates)) {
|
|
91
|
-
if (targetKey !== KEYS.STATE_FINAL && !(targetKey in sourceStates)) {
|
|
92
|
-
deadendStates.push(targetKey);
|
|
181
|
+
if (!this._update()) return false;
|
|
182
|
+
if (this.status & STATUS_ENTER) {
|
|
183
|
+
await this.state?._runHandler("onEnter", this.state);
|
|
93
184
|
}
|
|
185
|
+
if (this.status & mask) return true;
|
|
94
186
|
}
|
|
95
|
-
|
|
96
|
-
return { unreachableStates, deadendStates }
|
|
97
187
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
188
|
+
async dump(...args) {
|
|
189
|
+
const dumpState = async (state) => {
|
|
190
|
+
const stateDump = {
|
|
191
|
+
key: state.key,
|
|
192
|
+
data: {}
|
|
193
|
+
};
|
|
194
|
+
await state._runHandler("dump", state, stateDump.data, ...args);
|
|
195
|
+
return stateDump;
|
|
196
|
+
};
|
|
197
|
+
const dumpStates = async (state, stack = []) => {
|
|
198
|
+
if (!state) return stack;
|
|
199
|
+
state.parent && await dumpStates(state.parent, stack);
|
|
200
|
+
stack.push(await dumpState(state));
|
|
201
|
+
return stack;
|
|
202
|
+
};
|
|
203
|
+
const dump = {
|
|
204
|
+
status: this.status,
|
|
205
|
+
event: this.event,
|
|
206
|
+
stack: await dumpStates(this.state)
|
|
207
|
+
};
|
|
208
|
+
return dump;
|
|
105
209
|
}
|
|
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
|
-
}
|
|
210
|
+
async restore(dump, ...args) {
|
|
211
|
+
this.status = dump.status || 0;
|
|
212
|
+
this.event = dump.event;
|
|
213
|
+
this.state = void 0;
|
|
214
|
+
for (let i = 0; i < dump.stack.length; i++) {
|
|
215
|
+
const stateDump = dump.stack[i];
|
|
216
|
+
this.state = this.state ? this._newSubstate(this.state, stateDump.key) : this._newState(void 0, stateDump.key, this.rootDescriptor);
|
|
217
|
+
await this.state._runHandler(
|
|
218
|
+
"restore",
|
|
219
|
+
this.state,
|
|
220
|
+
stateDump.data,
|
|
221
|
+
...args
|
|
222
|
+
);
|
|
129
223
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
224
|
+
return this;
|
|
225
|
+
}
|
|
226
|
+
onStateCreate(handler) {
|
|
227
|
+
return this._addHandler("onStateCreate", handler, true);
|
|
228
|
+
}
|
|
229
|
+
onStateError(handler) {
|
|
230
|
+
return this._addHandler("onStateError", handler);
|
|
231
|
+
}
|
|
232
|
+
async _handleStateError(state, error) {
|
|
233
|
+
await this._runHandler("onStateError", state, error);
|
|
234
|
+
this._handleError(error);
|
|
235
|
+
}
|
|
236
|
+
_newState(parent, key, descriptor) {
|
|
237
|
+
const state = new FsmState(this, parent, key, descriptor);
|
|
238
|
+
this._runHandlerSync("onStateCreate", state);
|
|
239
|
+
return state;
|
|
240
|
+
}
|
|
241
|
+
_getSubstate(parent, prevStateKey) {
|
|
242
|
+
if (!parent) return;
|
|
243
|
+
const toState = parent.descriptor?.getTargetStateKey(
|
|
244
|
+
prevStateKey || STATE_INITIAL,
|
|
245
|
+
this.event || EVENT_EMPTY
|
|
246
|
+
) || STATE_FINAL;
|
|
247
|
+
if (!toState) return;
|
|
248
|
+
return this._newSubstate(parent, toState);
|
|
249
|
+
}
|
|
250
|
+
_newSubstate(parent, toState) {
|
|
251
|
+
let descriptor;
|
|
252
|
+
for (let state = parent; !descriptor && state; state = state.parent) {
|
|
253
|
+
descriptor = state.descriptor?.states[toState];
|
|
133
254
|
}
|
|
255
|
+
return this._newState(parent, toState, descriptor);
|
|
134
256
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
[stateKey, KEYS.EVENT_ANY],
|
|
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;
|
|
257
|
+
_update() {
|
|
258
|
+
if (this.status & STATUS_FINISHED) return false;
|
|
259
|
+
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);
|
|
260
|
+
if (nextState !== void 0) {
|
|
261
|
+
this.state = nextState;
|
|
262
|
+
this.status = this.status & STATUS_EXIT ? STATUS_NEXT : STATUS_FIRST;
|
|
173
263
|
} else {
|
|
174
|
-
if (
|
|
175
|
-
|
|
264
|
+
if (this.status & STATUS_EXIT) {
|
|
265
|
+
this.state = this.state?.parent;
|
|
266
|
+
this.status = STATUS_LAST;
|
|
176
267
|
} 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
|
-
}
|
|
268
|
+
this.status = STATUS_LEAF;
|
|
186
269
|
}
|
|
270
|
+
if (!this.state) this.status = STATUS_FINISHED;
|
|
187
271
|
}
|
|
188
|
-
return
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
}
|
|
272
|
+
return !(this.status & STATUS_FINISHED);
|
|
273
|
+
}
|
|
274
|
+
};
|
|
192
275
|
|
|
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;
|
|
276
|
+
// src/context/handlers.ts
|
|
277
|
+
var KEY_HANDLERS = "handlers";
|
|
278
|
+
function callStateHandlers(state) {
|
|
279
|
+
const key = state.key;
|
|
280
|
+
for (let parent = state.parent; !!parent; parent = parent?.parent) {
|
|
281
|
+
const handlers = parent.getData(KEY_HANDLERS)?.[key];
|
|
282
|
+
handlers?.forEach((handler) => handler(state));
|
|
283
|
+
}
|
|
236
284
|
}
|
|
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;
|
|
285
|
+
function addSubstateHandlers(state, handlers) {
|
|
286
|
+
const oldIndex = state.getData(KEY_HANDLERS);
|
|
287
|
+
const index = oldIndex ? { ...oldIndex } : {};
|
|
288
|
+
for (const [key, handler] of Object.entries(handlers)) {
|
|
289
|
+
const list = index[key] = index[key] || [];
|
|
290
|
+
list.push(handler);
|
|
266
291
|
}
|
|
267
|
-
|
|
292
|
+
state.setData(KEY_HANDLERS, index);
|
|
268
293
|
}
|
|
269
294
|
|
|
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;
|
|
295
|
+
// src/context/printer.ts
|
|
296
|
+
var KEY_PRINTER = "printer";
|
|
297
|
+
function preparePrinter(process, { prefix = "", print = console.log, lineNumbers = false }) {
|
|
298
|
+
let lineCounter = 0;
|
|
299
|
+
const shift = () => {
|
|
300
|
+
let prefix2 = "";
|
|
301
|
+
for (let s = process.state?.parent; !!s; s = s.parent) {
|
|
302
|
+
prefix2 += " ";
|
|
290
303
|
}
|
|
304
|
+
return prefix2;
|
|
291
305
|
};
|
|
292
|
-
|
|
306
|
+
const getPrefix = lineNumbers ? () => `[${++lineCounter}]${shift()}` : shift;
|
|
307
|
+
const printer = (...args) => print(prefix, getPrefix(), ...args);
|
|
308
|
+
return printer;
|
|
309
|
+
}
|
|
310
|
+
function setPrinter(state, config = {}) {
|
|
311
|
+
const printer = preparePrinter(state.process, config);
|
|
312
|
+
state.setData(KEY_PRINTER, printer);
|
|
313
|
+
}
|
|
314
|
+
function setProcessPrinter(process, config = {}) {
|
|
315
|
+
const printer = preparePrinter(process, config);
|
|
316
|
+
process.setData(KEY_PRINTER, printer);
|
|
317
|
+
}
|
|
318
|
+
function getPrinter(state) {
|
|
319
|
+
return state.getData(KEY_PRINTER, true) || state.process.getData(KEY_PRINTER) || console.log;
|
|
293
320
|
}
|
|
294
321
|
|
|
295
|
-
|
|
322
|
+
// src/context/tracer.ts
|
|
323
|
+
function setProcessTracer(process, print) {
|
|
324
|
+
return process.onStateCreate((state) => {
|
|
325
|
+
setStateTracer(state, print);
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
function setStateTracer(state, print) {
|
|
329
|
+
state.onEnter(() => {
|
|
330
|
+
const printLine = print || getPrinter(state);
|
|
331
|
+
printLine(`<${state?.key} event="${state.process.event}">`);
|
|
332
|
+
});
|
|
333
|
+
state.onExit(() => {
|
|
334
|
+
const printLine = print || getPrinter(state);
|
|
335
|
+
printLine(`</${state.key}> <!-- event="${state.process.event}" -->`);
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
export {
|
|
339
|
+
EVENT_ANY,
|
|
340
|
+
EVENT_EMPTY,
|
|
341
|
+
FsmProcess,
|
|
342
|
+
FsmState,
|
|
343
|
+
FsmStateDescriptor,
|
|
344
|
+
KEY_HANDLERS,
|
|
345
|
+
KEY_PRINTER,
|
|
346
|
+
STATE_ANY,
|
|
347
|
+
STATE_FINAL,
|
|
348
|
+
STATE_INITIAL,
|
|
349
|
+
STATUS_ENTER,
|
|
350
|
+
STATUS_EXIT,
|
|
351
|
+
STATUS_FINISHED,
|
|
352
|
+
STATUS_FIRST,
|
|
353
|
+
STATUS_LAST,
|
|
354
|
+
STATUS_LEAF,
|
|
355
|
+
STATUS_NEXT,
|
|
356
|
+
STATUS_NONE,
|
|
357
|
+
addSubstateHandlers,
|
|
358
|
+
callStateHandlers,
|
|
359
|
+
getPrinter,
|
|
360
|
+
preparePrinter,
|
|
361
|
+
setPrinter,
|
|
362
|
+
setProcessPrinter,
|
|
363
|
+
setProcessTracer,
|
|
364
|
+
setStateTracer
|
|
365
|
+
};
|