@defold-typescript/transpiler 0.41.0 → 0.43.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/dist/emitted-requires.d.ts +1 -1
- package/dist/hsm/defold.ts +33 -0
- package/dist/hsm/index.ts +696 -0
- package/dist/hsm-builtin.d.ts +25 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +305 -129
- package/dist/project-resources.d.ts +2 -1
- package/dist/transpile.d.ts +10 -0
- package/package.json +3 -3
- package/src/emitted-requires.ts +6 -4
- package/src/hsm-builtin.ts +195 -0
- package/src/index.ts +15 -2
- package/src/project-resources.ts +20 -7
- package/src/session.ts +2 -0
- package/src/transpile.ts +108 -26
|
@@ -0,0 +1,696 @@
|
|
|
1
|
+
export interface EventObject {
|
|
2
|
+
readonly type: string;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
/** @noSelf */
|
|
6
|
+
export interface MachineInstance<Ctx, E extends EventObject, P extends string = string> {
|
|
7
|
+
readonly ctx: Ctx;
|
|
8
|
+
readonly path: P | undefined;
|
|
9
|
+
readonly matches: (path: P) => boolean;
|
|
10
|
+
readonly send: (event: E) => void;
|
|
11
|
+
readonly update: (dt: number) => void;
|
|
12
|
+
readonly stop: () => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type TransitionAction<Ctx, E extends EventObject, V extends E = E> = (
|
|
16
|
+
ctx: Ctx,
|
|
17
|
+
event: V,
|
|
18
|
+
m: MachineInstance<Ctx, E>,
|
|
19
|
+
) => void;
|
|
20
|
+
|
|
21
|
+
/** @noSelf */
|
|
22
|
+
export interface TransitionConfig<Ctx, E extends EventObject, V extends E = E> {
|
|
23
|
+
readonly target?: string;
|
|
24
|
+
readonly guard?: (ctx: Ctx, event: V) => boolean;
|
|
25
|
+
readonly actions?: TransitionAction<Ctx, E, V> | readonly TransitionAction<Ctx, E, V>[];
|
|
26
|
+
readonly reenter?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type TransitionSpec<Ctx, E extends EventObject, V extends E = E> =
|
|
30
|
+
| string
|
|
31
|
+
| TransitionConfig<Ctx, E, V>
|
|
32
|
+
// Lua cannot tell [] from {}, so an empty list would compile to a targetless transition.
|
|
33
|
+
| readonly [TransitionConfig<Ctx, E, V>, ...TransitionConfig<Ctx, E, V>[]];
|
|
34
|
+
|
|
35
|
+
export type OnConfig<Ctx, E extends EventObject> = {
|
|
36
|
+
readonly [K in E["type"]]?: TransitionSpec<Ctx, E, Extract<E, { type: K }>>;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type StateHook<Ctx, E extends EventObject> = (ctx: Ctx, m: MachineInstance<Ctx, E>) => void;
|
|
40
|
+
|
|
41
|
+
export type UpdateHook<Ctx, E extends EventObject> = (
|
|
42
|
+
ctx: Ctx,
|
|
43
|
+
dt: number,
|
|
44
|
+
m: MachineInstance<Ctx, E>,
|
|
45
|
+
) => string | undefined;
|
|
46
|
+
|
|
47
|
+
/** @noSelf */
|
|
48
|
+
export type InvokeStart<Ctx, E extends EventObject> = (
|
|
49
|
+
ctx: Ctx,
|
|
50
|
+
settle: (event: E) => void,
|
|
51
|
+
m: MachineInstance<Ctx, E>,
|
|
52
|
+
) => void;
|
|
53
|
+
|
|
54
|
+
/** @noSelf */
|
|
55
|
+
export interface StateConfig<Ctx, E extends EventObject> {
|
|
56
|
+
readonly initial?: string;
|
|
57
|
+
readonly states?: { readonly [name: string]: StateConfig<Ctx, E> };
|
|
58
|
+
readonly on?: OnConfig<Ctx, E>;
|
|
59
|
+
readonly after?: { readonly [seconds: number]: string };
|
|
60
|
+
readonly enter?: StateHook<Ctx, E>;
|
|
61
|
+
readonly exit?: StateHook<Ctx, E>;
|
|
62
|
+
readonly update?: UpdateHook<Ctx, E>;
|
|
63
|
+
readonly invoke?: InvokeStart<Ctx, E>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface MachineConfig<Ctx, E extends EventObject> extends StateConfig<Ctx, E> {
|
|
67
|
+
readonly initial: string;
|
|
68
|
+
readonly states: { readonly [name: string]: StateConfig<Ctx, E> };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** @noSelf */
|
|
72
|
+
export interface Machine<Ctx, E extends EventObject, P extends string = string> {
|
|
73
|
+
readonly start: (ctx: Ctx) => MachineInstance<Ctx, E, P>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
type PathDepth = [never, 0, 1, 2, 3];
|
|
77
|
+
|
|
78
|
+
type PathsBelow<S, D extends number> = S extends { readonly states: infer Children }
|
|
79
|
+
? {
|
|
80
|
+
[K in keyof Children & string]:
|
|
81
|
+
| K
|
|
82
|
+
| `${K}/${D extends 1
|
|
83
|
+
? Children[K] extends { readonly states: object }
|
|
84
|
+
? string
|
|
85
|
+
: never
|
|
86
|
+
: PathsBelow<Children[K], PathDepth[D]>}`;
|
|
87
|
+
}[keyof Children & string]
|
|
88
|
+
: never;
|
|
89
|
+
|
|
90
|
+
export type StatePath<C> = `/${PathsBelow<C, 4>}`;
|
|
91
|
+
|
|
92
|
+
interface TransitionCheck<T> {
|
|
93
|
+
readonly target?: T;
|
|
94
|
+
readonly guard?: unknown;
|
|
95
|
+
readonly actions?: unknown;
|
|
96
|
+
readonly reenter?: unknown;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
type SpecCheck<T> = T | TransitionCheck<T> | readonly TransitionCheck<T>[];
|
|
100
|
+
|
|
101
|
+
// Hooks are listed as unknown so a hooks-only leaf still shares a property with this
|
|
102
|
+
// all-optional type; without them TypeScript rejects it as a weak-type mismatch.
|
|
103
|
+
type PathCheck<S, Self extends string, All extends string, Ev extends string> = {
|
|
104
|
+
readonly initial?: S extends { readonly states: infer Children }
|
|
105
|
+
? `${Self}/${keyof Children & string}`
|
|
106
|
+
: never;
|
|
107
|
+
readonly states?: S extends { readonly states: infer Children }
|
|
108
|
+
? {
|
|
109
|
+
readonly [K in keyof Children]: PathCheck<Children[K], `${Self}/${K & string}`, All, Ev>;
|
|
110
|
+
}
|
|
111
|
+
: unknown;
|
|
112
|
+
readonly on?: S extends { readonly on: infer On }
|
|
113
|
+
? { readonly [K in keyof On]: K extends Ev ? SpecCheck<All> : never }
|
|
114
|
+
: unknown;
|
|
115
|
+
readonly after?: S extends { readonly after: infer After }
|
|
116
|
+
? { readonly [K in keyof After]: All }
|
|
117
|
+
: unknown;
|
|
118
|
+
readonly enter?: unknown;
|
|
119
|
+
readonly exit?: unknown;
|
|
120
|
+
readonly update?: unknown;
|
|
121
|
+
readonly invoke?: unknown;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export interface MachineConfigError {
|
|
125
|
+
readonly "hsm: an initial or target names an unknown state path, or an on key an unknown event": never;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export type DefinedMachine<Ctx, E extends EventObject, C> =
|
|
129
|
+
C extends PathCheck<C, "", StatePath<C>, E["type"]>
|
|
130
|
+
? Machine<Ctx, E, StatePath<C>>
|
|
131
|
+
: MachineConfigError;
|
|
132
|
+
|
|
133
|
+
type Guard<Ctx, E extends EventObject> = (ctx: Ctx, event: E) => boolean;
|
|
134
|
+
|
|
135
|
+
interface Transition<Ctx, E extends EventObject> {
|
|
136
|
+
readonly target: number;
|
|
137
|
+
readonly guard: Guard<Ctx, E> | undefined;
|
|
138
|
+
readonly actions: readonly TransitionAction<Ctx, E>[];
|
|
139
|
+
readonly reenter: boolean;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
interface Compiled<Ctx, E extends EventObject> {
|
|
143
|
+
count: number;
|
|
144
|
+
maxDepth: number;
|
|
145
|
+
readonly configs: StateConfig<Ctx, E>[];
|
|
146
|
+
readonly paths: string[];
|
|
147
|
+
readonly parent: number[];
|
|
148
|
+
readonly depth: number[];
|
|
149
|
+
readonly initialChild: number[];
|
|
150
|
+
readonly onIndex: { [type: string]: number }[];
|
|
151
|
+
readonly transitions: Transition<Ctx, E>[][];
|
|
152
|
+
readonly afterDelays: number[][];
|
|
153
|
+
readonly afterTargets: number[][];
|
|
154
|
+
readonly pathIndex: { [path: string]: number };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const ROOT = 0;
|
|
158
|
+
const NO_STATE = -1;
|
|
159
|
+
|
|
160
|
+
function describePath(path: string): string {
|
|
161
|
+
return path === "" ? "(root)" : path;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function lookupPath<Ctx, E extends EventObject>(
|
|
165
|
+
compiled: Compiled<Ctx, E>,
|
|
166
|
+
path: string,
|
|
167
|
+
): number | undefined {
|
|
168
|
+
const index = compiled.pathIndex[path];
|
|
169
|
+
return typeof index === "number" ? index : undefined;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function resolveTarget<Ctx, E extends EventObject>(
|
|
173
|
+
compiled: Compiled<Ctx, E>,
|
|
174
|
+
source: number,
|
|
175
|
+
target: string,
|
|
176
|
+
): number {
|
|
177
|
+
const sourcePath = describePath(compiled.paths[source] as string);
|
|
178
|
+
if (target.charAt(0) !== "/") {
|
|
179
|
+
throw `hsm: state "${sourcePath}" targets "${target}", which is not a full path starting with "/"`;
|
|
180
|
+
}
|
|
181
|
+
const index = lookupPath(compiled, target);
|
|
182
|
+
if (index === undefined) {
|
|
183
|
+
throw `hsm: state "${sourcePath}" targets unknown state "${target}"`;
|
|
184
|
+
}
|
|
185
|
+
return index;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function registerState<Ctx, E extends EventObject>(
|
|
189
|
+
compiled: Compiled<Ctx, E>,
|
|
190
|
+
config: StateConfig<Ctx, E>,
|
|
191
|
+
parentIndex: number,
|
|
192
|
+
path: string,
|
|
193
|
+
): void {
|
|
194
|
+
const index = compiled.count;
|
|
195
|
+
compiled.count = index + 1;
|
|
196
|
+
const depth = parentIndex === NO_STATE ? 0 : (compiled.depth[parentIndex] as number) + 1;
|
|
197
|
+
if (depth > compiled.maxDepth) {
|
|
198
|
+
compiled.maxDepth = depth;
|
|
199
|
+
}
|
|
200
|
+
compiled.configs[index] = config;
|
|
201
|
+
compiled.paths[index] = path;
|
|
202
|
+
compiled.parent[index] = parentIndex;
|
|
203
|
+
compiled.depth[index] = depth;
|
|
204
|
+
compiled.initialChild[index] = NO_STATE;
|
|
205
|
+
compiled.pathIndex[path] = index;
|
|
206
|
+
const children = config.states;
|
|
207
|
+
if (children === undefined) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
for (const name in children) {
|
|
211
|
+
if (name === "" || name.indexOf("/") !== -1) {
|
|
212
|
+
throw `hsm: state "${describePath(path)}" has a child named "${name}"; state names must be non-empty and contain no "/"`;
|
|
213
|
+
}
|
|
214
|
+
registerState(compiled, children[name] as StateConfig<Ctx, E>, index, `${path}/${name}`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function hasChildren<Ctx, E extends EventObject>(config: StateConfig<Ctx, E>): boolean {
|
|
219
|
+
const children = config.states;
|
|
220
|
+
if (children === undefined) {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
for (const _name in children) {
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function compileInitial<Ctx, E extends EventObject>(
|
|
230
|
+
compiled: Compiled<Ctx, E>,
|
|
231
|
+
index: number,
|
|
232
|
+
): void {
|
|
233
|
+
const config = compiled.configs[index] as StateConfig<Ctx, E>;
|
|
234
|
+
const path = compiled.paths[index] as string;
|
|
235
|
+
const initial = config.initial;
|
|
236
|
+
if (!hasChildren(config)) {
|
|
237
|
+
if (initial !== undefined) {
|
|
238
|
+
throw `hsm: state "${describePath(path)}" has initial "${initial}" but no child states`;
|
|
239
|
+
}
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
if (initial === undefined) {
|
|
243
|
+
throw `hsm: compound state "${describePath(path)}" has no initial`;
|
|
244
|
+
}
|
|
245
|
+
const child = lookupPath(compiled, initial);
|
|
246
|
+
if (child === undefined || compiled.parent[child] !== index) {
|
|
247
|
+
throw `hsm: state "${describePath(path)}" has initial "${initial}", which is not one of its children`;
|
|
248
|
+
}
|
|
249
|
+
compiled.initialChild[index] = child;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function compileTransition<Ctx, E extends EventObject>(
|
|
253
|
+
compiled: Compiled<Ctx, E>,
|
|
254
|
+
source: number,
|
|
255
|
+
spec: string | TransitionConfig<Ctx, E>,
|
|
256
|
+
): Transition<Ctx, E> {
|
|
257
|
+
if (typeof spec === "string") {
|
|
258
|
+
return {
|
|
259
|
+
target: resolveTarget(compiled, source, spec),
|
|
260
|
+
guard: undefined,
|
|
261
|
+
actions: [],
|
|
262
|
+
reenter: false,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
const actions: TransitionAction<Ctx, E>[] = [];
|
|
266
|
+
const declared = spec.actions;
|
|
267
|
+
if (typeof declared === "function") {
|
|
268
|
+
actions.push(declared);
|
|
269
|
+
} else if (declared !== undefined) {
|
|
270
|
+
for (let i = 0; i < declared.length; i++) {
|
|
271
|
+
actions.push(declared[i] as TransitionAction<Ctx, E>);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return {
|
|
275
|
+
target: spec.target === undefined ? NO_STATE : resolveTarget(compiled, source, spec.target),
|
|
276
|
+
guard: spec.guard,
|
|
277
|
+
actions,
|
|
278
|
+
reenter: spec.reenter === true,
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function compileOn<Ctx, E extends EventObject>(compiled: Compiled<Ctx, E>, index: number): void {
|
|
283
|
+
const table: { [type: string]: number } = {};
|
|
284
|
+
compiled.onIndex[index] = table;
|
|
285
|
+
const on = (compiled.configs[index] as StateConfig<Ctx, E>).on as
|
|
286
|
+
| { readonly [type: string]: TransitionSpec<Ctx, E> | undefined }
|
|
287
|
+
| undefined;
|
|
288
|
+
if (on === undefined) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
for (const type in on) {
|
|
292
|
+
const spec = on[type];
|
|
293
|
+
if (spec === undefined) {
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
const list: Transition<Ctx, E>[] = [];
|
|
297
|
+
if (
|
|
298
|
+
typeof spec === "string" ||
|
|
299
|
+
(spec as readonly TransitionConfig<Ctx, E>[])[0] === undefined
|
|
300
|
+
) {
|
|
301
|
+
list.push(compileTransition(compiled, index, spec as string | TransitionConfig<Ctx, E>));
|
|
302
|
+
} else {
|
|
303
|
+
const candidates = spec as readonly TransitionConfig<Ctx, E>[];
|
|
304
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
305
|
+
list.push(compileTransition(compiled, index, candidates[i] as TransitionConfig<Ctx, E>));
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
table[type] = compiled.transitions.length;
|
|
309
|
+
compiled.transitions.push(list);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const NOT_A_DELAY = -1;
|
|
314
|
+
|
|
315
|
+
// Number-literal keys arrive as numbers in Lua and as canonical number strings in JavaScript.
|
|
316
|
+
// A string key counts only when it reads back unchanged, which rejects "" (0 in JavaScript).
|
|
317
|
+
function parseDelay(key: string | number): number {
|
|
318
|
+
if (typeof key === "number") {
|
|
319
|
+
return key;
|
|
320
|
+
}
|
|
321
|
+
let delay = NOT_A_DELAY;
|
|
322
|
+
try {
|
|
323
|
+
delay = (key as unknown as number) * 1;
|
|
324
|
+
} catch {
|
|
325
|
+
// Lua raises on arithmetic with a non-numeric string; delay keeps NOT_A_DELAY.
|
|
326
|
+
}
|
|
327
|
+
if (`${delay}` !== key) {
|
|
328
|
+
return NOT_A_DELAY;
|
|
329
|
+
}
|
|
330
|
+
return delay;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function compileAfter<Ctx, E extends EventObject>(compiled: Compiled<Ctx, E>, index: number): void {
|
|
334
|
+
const delays: number[] = [];
|
|
335
|
+
const targets: number[] = [];
|
|
336
|
+
compiled.afterDelays[index] = delays;
|
|
337
|
+
compiled.afterTargets[index] = targets;
|
|
338
|
+
const after = (compiled.configs[index] as StateConfig<Ctx, E>).after;
|
|
339
|
+
if (after === undefined) {
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
const path = compiled.paths[index] as string;
|
|
343
|
+
for (const key in after) {
|
|
344
|
+
const delay = parseDelay(key);
|
|
345
|
+
if (!(delay >= 0)) {
|
|
346
|
+
throw `hsm: state "${describePath(path)}" has an after delay "${key}" that is not a non-negative number`;
|
|
347
|
+
}
|
|
348
|
+
const target = resolveTarget(compiled, index, after[key as unknown as number] as string);
|
|
349
|
+
let slot = delays.length;
|
|
350
|
+
while (slot > 0 && (delays[slot - 1] as number) > delay) {
|
|
351
|
+
delays[slot] = delays[slot - 1] as number;
|
|
352
|
+
targets[slot] = targets[slot - 1] as number;
|
|
353
|
+
slot--;
|
|
354
|
+
}
|
|
355
|
+
delays[slot] = delay;
|
|
356
|
+
targets[slot] = target;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function compile<Ctx, E extends EventObject>(config: MachineConfig<Ctx, E>): Compiled<Ctx, E> {
|
|
361
|
+
const compiled: Compiled<Ctx, E> = {
|
|
362
|
+
count: 0,
|
|
363
|
+
maxDepth: 0,
|
|
364
|
+
configs: [],
|
|
365
|
+
paths: [],
|
|
366
|
+
parent: [],
|
|
367
|
+
depth: [],
|
|
368
|
+
initialChild: [],
|
|
369
|
+
onIndex: [],
|
|
370
|
+
transitions: [],
|
|
371
|
+
afterDelays: [],
|
|
372
|
+
afterTargets: [],
|
|
373
|
+
pathIndex: {},
|
|
374
|
+
};
|
|
375
|
+
registerState(compiled, config, NO_STATE, "");
|
|
376
|
+
for (let index = 0; index < compiled.count; index++) {
|
|
377
|
+
compileInitial(compiled, index);
|
|
378
|
+
compileOn(compiled, index);
|
|
379
|
+
compileAfter(compiled, index);
|
|
380
|
+
}
|
|
381
|
+
return compiled;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function isAncestorOrSelf<Ctx, E extends EventObject>(
|
|
385
|
+
compiled: Compiled<Ctx, E>,
|
|
386
|
+
ancestor: number,
|
|
387
|
+
state: number,
|
|
388
|
+
): boolean {
|
|
389
|
+
const ancestorDepth = compiled.depth[ancestor] as number;
|
|
390
|
+
let current = state;
|
|
391
|
+
while ((compiled.depth[current] as number) > ancestorDepth) {
|
|
392
|
+
current = compiled.parent[current] as number;
|
|
393
|
+
}
|
|
394
|
+
return current === ancestor;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function transitionDomain<Ctx, E extends EventObject>(
|
|
398
|
+
compiled: Compiled<Ctx, E>,
|
|
399
|
+
source: number,
|
|
400
|
+
target: number,
|
|
401
|
+
reenter: boolean,
|
|
402
|
+
): number {
|
|
403
|
+
if (source === ROOT || (!reenter && isAncestorOrSelf(compiled, source, target))) {
|
|
404
|
+
return source;
|
|
405
|
+
}
|
|
406
|
+
let domain = compiled.parent[source] as number;
|
|
407
|
+
while (domain !== ROOT && (domain === target || !isAncestorOrSelf(compiled, domain, target))) {
|
|
408
|
+
domain = compiled.parent[domain] as number;
|
|
409
|
+
}
|
|
410
|
+
return domain;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function createMachine<Ctx, E extends EventObject>(config: MachineConfig<Ctx, E>): Machine<Ctx, E> {
|
|
414
|
+
const compiled = compile(config);
|
|
415
|
+
const { paths, parent, depth, initialChild, configs, onIndex, transitions } = compiled;
|
|
416
|
+
const { afterDelays, afterTargets } = compiled;
|
|
417
|
+
|
|
418
|
+
function start(ctx: Ctx): MachineInstance<Ctx, E> {
|
|
419
|
+
const slots = compiled.maxDepth + 1;
|
|
420
|
+
const active: number[] = [];
|
|
421
|
+
const elapsed: number[] = [];
|
|
422
|
+
const fired: number[] = [];
|
|
423
|
+
const scratch: number[] = [];
|
|
424
|
+
const entryId: number[] = [];
|
|
425
|
+
for (let i = 0; i < slots; i++) {
|
|
426
|
+
active[i] = NO_STATE;
|
|
427
|
+
elapsed[i] = 0;
|
|
428
|
+
fired[i] = 0;
|
|
429
|
+
scratch[i] = NO_STATE;
|
|
430
|
+
entryId[i] = 0;
|
|
431
|
+
}
|
|
432
|
+
let entryCount = 0;
|
|
433
|
+
const queue: (E | undefined)[] = [];
|
|
434
|
+
let queueHead = 0;
|
|
435
|
+
let queueTail = 0;
|
|
436
|
+
let leafDepth = NO_STATE;
|
|
437
|
+
let running = true;
|
|
438
|
+
let busy = false;
|
|
439
|
+
let stopRequested = false;
|
|
440
|
+
|
|
441
|
+
const instance = {
|
|
442
|
+
ctx,
|
|
443
|
+
path: undefined as string | undefined,
|
|
444
|
+
matches,
|
|
445
|
+
send,
|
|
446
|
+
update,
|
|
447
|
+
stop,
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
function enterState(state: number): void {
|
|
451
|
+
const level = depth[state] as number;
|
|
452
|
+
active[level] = state;
|
|
453
|
+
elapsed[level] = 0;
|
|
454
|
+
fired[level] = 0;
|
|
455
|
+
leafDepth = level;
|
|
456
|
+
entryCount++;
|
|
457
|
+
const id = entryCount;
|
|
458
|
+
entryId[level] = id;
|
|
459
|
+
const config = configs[state] as StateConfig<Ctx, E>;
|
|
460
|
+
const hook = config.enter;
|
|
461
|
+
if (hook !== undefined) {
|
|
462
|
+
hook(ctx, instance);
|
|
463
|
+
}
|
|
464
|
+
const invoke = config.invoke;
|
|
465
|
+
if (invoke !== undefined) {
|
|
466
|
+
let settled = false;
|
|
467
|
+
invoke(
|
|
468
|
+
ctx,
|
|
469
|
+
(event: E) => {
|
|
470
|
+
if (settled || !running || stopRequested || entryId[level] !== id) {
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
settled = true;
|
|
474
|
+
send(event);
|
|
475
|
+
},
|
|
476
|
+
instance,
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function exitTo(level: number): void {
|
|
482
|
+
while (leafDepth > level) {
|
|
483
|
+
const state = active[leafDepth] as number;
|
|
484
|
+
active[leafDepth] = NO_STATE;
|
|
485
|
+
entryId[leafDepth] = 0;
|
|
486
|
+
leafDepth--;
|
|
487
|
+
const hook = (configs[state] as StateConfig<Ctx, E>).exit;
|
|
488
|
+
if (hook !== undefined) {
|
|
489
|
+
hook(ctx, instance);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function enterDown(from: number, target: number): void {
|
|
495
|
+
let count = 0;
|
|
496
|
+
let state = target;
|
|
497
|
+
while (state !== from) {
|
|
498
|
+
scratch[count] = state;
|
|
499
|
+
count++;
|
|
500
|
+
state = parent[state] as number;
|
|
501
|
+
}
|
|
502
|
+
while (count > 0) {
|
|
503
|
+
count--;
|
|
504
|
+
enterState(scratch[count] as number);
|
|
505
|
+
scratch[count] = NO_STATE;
|
|
506
|
+
}
|
|
507
|
+
state = target;
|
|
508
|
+
while (initialChild[state] !== NO_STATE) {
|
|
509
|
+
state = initialChild[state] as number;
|
|
510
|
+
enterState(state);
|
|
511
|
+
}
|
|
512
|
+
instance.path = paths[active[leafDepth] as number] as string;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function transition(
|
|
516
|
+
source: number,
|
|
517
|
+
target: number,
|
|
518
|
+
reenter: boolean,
|
|
519
|
+
actions: readonly TransitionAction<Ctx, E>[] | undefined,
|
|
520
|
+
event: E | undefined,
|
|
521
|
+
): void {
|
|
522
|
+
if (target === NO_STATE) {
|
|
523
|
+
runActions(actions, event);
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
const domain = transitionDomain(compiled, source, target, reenter);
|
|
527
|
+
exitTo(depth[domain] as number);
|
|
528
|
+
runActions(actions, event);
|
|
529
|
+
enterDown(domain, target);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function runActions(
|
|
533
|
+
actions: readonly TransitionAction<Ctx, E>[] | undefined,
|
|
534
|
+
event: E | undefined,
|
|
535
|
+
): void {
|
|
536
|
+
if (actions === undefined || event === undefined) {
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
for (let i = 0; i < actions.length; i++) {
|
|
540
|
+
(actions[i] as TransitionAction<Ctx, E>)(ctx, event, instance);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function processEvent(event: E): void {
|
|
545
|
+
for (let level = leafDepth; level >= 0; level--) {
|
|
546
|
+
const state = active[level] as number;
|
|
547
|
+
const listIndex = (onIndex[state] as { [type: string]: number })[event.type];
|
|
548
|
+
if (typeof listIndex !== "number") {
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
551
|
+
const list = transitions[listIndex] as Transition<Ctx, E>[];
|
|
552
|
+
for (let i = 0; i < list.length; i++) {
|
|
553
|
+
const candidate = list[i] as Transition<Ctx, E>;
|
|
554
|
+
if (candidate.guard === undefined || candidate.guard(ctx, event)) {
|
|
555
|
+
transition(state, candidate.target, candidate.reenter, candidate.actions, event);
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function clearQueue(): void {
|
|
563
|
+
while (queueHead < queueTail) {
|
|
564
|
+
queue[queueHead] = undefined;
|
|
565
|
+
queueHead++;
|
|
566
|
+
}
|
|
567
|
+
queueHead = 0;
|
|
568
|
+
queueTail = 0;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function stopNow(): void {
|
|
572
|
+
running = false;
|
|
573
|
+
busy = true;
|
|
574
|
+
clearQueue();
|
|
575
|
+
exitTo(NO_STATE);
|
|
576
|
+
instance.path = undefined;
|
|
577
|
+
busy = false;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function endStep(): void {
|
|
581
|
+
while (!stopRequested && queueHead < queueTail) {
|
|
582
|
+
const event = queue[queueHead] as E;
|
|
583
|
+
queue[queueHead] = undefined;
|
|
584
|
+
queueHead++;
|
|
585
|
+
processEvent(event);
|
|
586
|
+
}
|
|
587
|
+
clearQueue();
|
|
588
|
+
busy = false;
|
|
589
|
+
if (stopRequested) {
|
|
590
|
+
stopNow();
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
function send(event: E): void {
|
|
595
|
+
if (!running || stopRequested) {
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
queue[queueTail] = event;
|
|
599
|
+
queueTail++;
|
|
600
|
+
if (busy) {
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
busy = true;
|
|
604
|
+
endStep();
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
function fireUpdateHooks(dt: number): boolean {
|
|
608
|
+
for (let level = leafDepth; level >= 0; level--) {
|
|
609
|
+
const state = active[level] as number;
|
|
610
|
+
const hook = (configs[state] as StateConfig<Ctx, E>).update;
|
|
611
|
+
if (hook === undefined) {
|
|
612
|
+
continue;
|
|
613
|
+
}
|
|
614
|
+
const target = hook(ctx, dt, instance);
|
|
615
|
+
if (stopRequested) {
|
|
616
|
+
return true;
|
|
617
|
+
}
|
|
618
|
+
if (typeof target === "string") {
|
|
619
|
+
transition(state, resolveTarget(compiled, state, target), false, undefined, undefined);
|
|
620
|
+
return true;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
return false;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function fireTimers(dt: number): void {
|
|
627
|
+
for (let level = 0; level <= leafDepth; level++) {
|
|
628
|
+
elapsed[level] = (elapsed[level] as number) + dt;
|
|
629
|
+
}
|
|
630
|
+
for (let level = leafDepth; level >= 0; level--) {
|
|
631
|
+
const state = active[level] as number;
|
|
632
|
+
const delays = afterDelays[state] as number[];
|
|
633
|
+
const next = fired[level] as number;
|
|
634
|
+
if (next < delays.length && (delays[next] as number) <= (elapsed[level] as number)) {
|
|
635
|
+
fired[level] = next + 1;
|
|
636
|
+
transition(
|
|
637
|
+
state,
|
|
638
|
+
(afterTargets[state] as number[])[next] as number,
|
|
639
|
+
false,
|
|
640
|
+
undefined,
|
|
641
|
+
undefined,
|
|
642
|
+
);
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function update(dt: number): void {
|
|
649
|
+
if (!running || busy || stopRequested) {
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
busy = true;
|
|
653
|
+
if (!fireUpdateHooks(dt)) {
|
|
654
|
+
fireTimers(dt);
|
|
655
|
+
}
|
|
656
|
+
endStep();
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
function stop(): void {
|
|
660
|
+
if (!running) {
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
if (busy) {
|
|
664
|
+
stopRequested = true;
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
stopNow();
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
function matches(path: string): boolean {
|
|
671
|
+
for (let level = 1; level <= leafDepth; level++) {
|
|
672
|
+
if (paths[active[level] as number] === path) {
|
|
673
|
+
return true;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
return false;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
busy = true;
|
|
680
|
+
enterState(ROOT);
|
|
681
|
+
enterDown(ROOT, ROOT);
|
|
682
|
+
endStep();
|
|
683
|
+
return instance;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
return { start };
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// Ctx and E are given explicitly and the config is inferred, so the config takes a second call.
|
|
690
|
+
export function defineMachine<Ctx, E extends EventObject>(): <
|
|
691
|
+
const C extends MachineConfig<Ctx, E>,
|
|
692
|
+
>(
|
|
693
|
+
config: C,
|
|
694
|
+
) => DefinedMachine<Ctx, E, C> {
|
|
695
|
+
return createMachine as never;
|
|
696
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Plugin } from "typescript-to-lua";
|
|
2
|
+
export declare const HSM_MODULE_SPECIFIER = "@defold-typescript/types/hsm";
|
|
3
|
+
export declare const HSM_REQUIRE_ROOT = "defold_typescript_hsm";
|
|
4
|
+
export interface HsmModule {
|
|
5
|
+
/** The source file's basename without `.ts`. */
|
|
6
|
+
readonly name: string;
|
|
7
|
+
/** What a user imports. */
|
|
8
|
+
readonly specifier: string;
|
|
9
|
+
/** What the import lowers to, and the module path the CLI writes it under. */
|
|
10
|
+
readonly requireName: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function hsmRequireName(name: string): string;
|
|
13
|
+
export declare function hsmSourceFiles(dir: string): string[];
|
|
14
|
+
export declare function resolveHsmSourceDir(packageRoot?: string): string | null;
|
|
15
|
+
export declare function requireHsmSourceDir(): string;
|
|
16
|
+
export declare function hsmModules(): readonly HsmModule[];
|
|
17
|
+
export declare function isHsmRequireName(requirePath: string): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* The hsm modules a program needs: every one its chunks require, plus every one
|
|
20
|
+
* those require in turn. Read from emitted Lua rather than the import AST, so an
|
|
21
|
+
* import TypeScript elides (`import type`, an unused binding) selects nothing.
|
|
22
|
+
* `compile` runs only when some chunk requires a module.
|
|
23
|
+
*/
|
|
24
|
+
export declare function hsmClosure(chunks: Iterable<string>, compile: () => Readonly<Record<string, string>>): Record<string, string> | undefined;
|
|
25
|
+
export declare const hsmLoweringPlugin: Plugin;
|