@defold-typescript/types 0.47.0 → 0.49.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/hsm/debug.d.ts CHANGED
@@ -14,8 +14,11 @@ export interface MachineInspector {
14
14
  }
15
15
  /**
16
16
  * Prints one line when it is called, then one line for each move `instance` makes, each starting
17
- * with `hsm` and the label. Call it once after `start`. A label another `inspect` call already
18
- * used gets an ordinal, such as `enemy#2`. In a release build it registers nothing and returns an
19
- * inspector whose `draw` does nothing, so the calls can stay in shipped code.
17
+ * with `hsm` and the label. A move's line names the state whose rule made it and that rule's key.
18
+ * An event no active state takes prints an `unhandled` line with its `type` and the active
19
+ * leaves, once per type until the next move. Call it once after `start`. A label another
20
+ * `inspect` call already used gets an ordinal, such as `enemy#2`. In a release build it registers
21
+ * nothing and returns an inspector whose `draw` does nothing, so the calls can stay in shipped
22
+ * code.
20
23
  */
21
24
  export declare function inspect<Ctx, E extends EventObject, P extends string>(instance: MachineInstance<Ctx, E, P>, label: string): MachineInspector;
package/hsm/index.d.ts CHANGED
@@ -16,18 +16,26 @@ export type MoveCause = "event" | "after" | "update" | "stop" | "reload" | "alwa
16
16
  /**
17
17
  * A listener given to `onMove`. It gets the leaf left, the leaf entered (`undefined` after
18
18
  * `stop()`), the cause, and the event, which is `undefined` unless the cause is `event`. It runs
19
- * after the move's `enter` hooks.
19
+ * after the move's `enter` hooks. The event is read-only: the listener cannot assign to its
20
+ * fields.
20
21
  * @noSelf
21
22
  */
22
- export type MoveListener<E extends EventObject, P extends string = string> = (from: P, to: P | undefined, cause: MoveCause, event: E | undefined) => void;
23
+ export type MoveListener<E extends EventObject, P extends string = string> = (from: P, to: P | undefined, cause: MoveCause, event: Readonly<E> | undefined) => void;
23
24
  /**
24
- * One running machine, returned by `start`. `P` is the union of the machine's state paths; the
25
- * `machine` a hook, a `run` or a `task` receives uses plain `string` paths instead.
25
+ * A listener given to `onUnhandled`. It gets the event no rule of an active state took. The
26
+ * event is read-only: the listener cannot assign to its fields.
26
27
  * @noSelf
27
28
  */
28
- export interface MachineInstance<Ctx, E extends EventObject, P extends string = string> {
29
- /** The context passed to `start`. */
30
- readonly ctx: Ctx;
29
+ export type UnhandledListener<E extends EventObject> = (event: Readonly<E>) => void;
30
+ /**
31
+ * What a `when` sees of the running machine: which states are active, and nothing that changes
32
+ * it. `P` is the union of the paths it takes. The `machine` a `when` receives takes the machine's
33
+ * state paths, and also any path below a state that holds only callbacks with unannotated
34
+ * parameters and `after` or `every` lists with one in them, or whose child states all do. `Tag`
35
+ * is the union of the machine's tag names.
36
+ * @noSelf
37
+ */
38
+ export interface MachineView<P extends string = string, Tag extends string = never> {
31
39
  /**
32
40
  * The full path of the deepest active state, or `undefined` once stopped. In a parallel state
33
41
  * it is the first region's leaf.
@@ -39,16 +47,45 @@ export interface MachineInstance<Ctx, E extends EventObject, P extends string =
39
47
  */
40
48
  readonly leaves: readonly P[];
41
49
  /** Whether the state at the full path `path` is active. Ancestors of the current state count. */
42
- readonly matches: (path: P) => boolean;
50
+ readonly matches: {
51
+ matches(this: void, path: P): boolean;
52
+ }["matches"];
53
+ /**
54
+ * Whether an active state carries `tag`. A parent's tag holds while any of its children is
55
+ * active, and a tag in any region of a parallel state counts. The answer is `false` for a name
56
+ * no state carries, and once stopped.
57
+ */
58
+ readonly hasTag: (tag: Tag) => boolean;
59
+ }
60
+ /**
61
+ * One running machine, returned by `start`. `P` is the union of the paths it takes: the
62
+ * machine's state paths on a started instance. The `machine` a hook, a `run` or a `task`
63
+ * receives takes those paths, and also any path below a state that holds only callbacks with
64
+ * unannotated parameters and `after` or `every` lists with one in them, or whose child states all
65
+ * do. `Tag` is the union of the machine's tag names.
66
+ * @noSelf
67
+ */
68
+ export interface MachineInstance<Ctx, E extends EventObject, P extends string = string, Tag extends string = never> extends MachineView<P, Tag> {
69
+ /** The context passed to `start`. */
70
+ readonly ctx: Ctx;
71
+ /**
72
+ * Whether a rule of an active state would take `event` if it were sent at that moment. The
73
+ * answer is for that one event, payload included: a rule's `when` reads it. A rule with no
74
+ * `to` counts. No hook, `run` or listener runs and nothing is queued, and once the machine is
75
+ * stopped the answer is `false`.
76
+ */
77
+ readonly can: (event: E) => boolean;
43
78
  /**
44
79
  * Handles `event`, or queues it when called during a step. An event no active state handles is
45
- * dropped, and once the machine is stopped the call does nothing.
80
+ * dropped: it runs the active states' `unhandled` hooks and is then reported to the
81
+ * `onUnhandled` listeners. Once the machine is stopped the call does nothing.
46
82
  */
47
83
  readonly send: (event: E) => void;
48
84
  /**
49
- * Advances the machine by `dt` seconds: runs the `update` hooks, then the `after` timers if no
50
- * hook moved it. A machine whose `update` is not called is frozen in time, and once the machine
51
- * is stopped the call does nothing.
85
+ * Advances the machine by `dt` seconds: runs the `update` hooks, then, in every region no hook
86
+ * moved, each state's `every` entries and after them its `after` timers. A machine with no
87
+ * parallel state is one region. A machine whose `update` is not called is frozen in time, and
88
+ * once the machine is stopped the call does nothing.
52
89
  */
53
90
  readonly update: (dt: number) => void;
54
91
  /**
@@ -63,12 +100,28 @@ export interface MachineInstance<Ctx, E extends EventObject, P extends string =
63
100
  * function removes that one.
64
101
  */
65
102
  readonly onMove: (listener: MoveListener<E, P>) => () => void;
103
+ /**
104
+ * Adds a listener called with each event the machine processes that no rule of an active state
105
+ * takes. It runs inside the step, after the active states' `unhandled` hooks and before the
106
+ * next queued event. A rule with no `to` takes its event. Each call adds a listener, and the
107
+ * returned function removes that one.
108
+ */
109
+ readonly onUnhandled: (listener: UnhandledListener<E>) => () => void;
66
110
  }
67
111
  /**
68
112
  * A function a rule's `run` calls during the move, after the `exit` hooks and before the `enter`
69
- * hooks. Its `event` is the variant the `on` key names.
113
+ * hooks. Its `event` is the variant the `on` key names, and is read-only: a `run` changes `ctx`
114
+ * and cannot assign to a field of the event. `Hook` is the union of the paths its `machine`
115
+ * takes: the machine's paths for a `run` written in the config, and any `string` when left out.
70
116
  */
71
- export type MoveAction<Ctx, E extends EventObject, V extends E = E> = (ctx: Ctx, event: V, machine: MachineInstance<Ctx, E>) => void;
117
+ export type MoveAction<Ctx, E extends EventObject, V extends E = E, Tag extends string = never, Hook extends string = string> = (ctx: Ctx, event: Readonly<V>, machine: MachineInstance<Ctx, E, Hook, Tag>) => void;
118
+ /**
119
+ * A function a `run` calls where there is no event: under `after`, `always` and `every`. `Hook`
120
+ * is the union of the paths its `machine` takes: the machine's paths for a `run` written in the
121
+ * config, and any `string` when left out.
122
+ */
123
+ export type RunAction<Ctx, E extends EventObject, Tag extends string = never, Hook extends string = string> = (ctx: Ctx, machine: MachineInstance<Ctx, E, Hook, Tag>) => void;
124
+ type RuleRun<Ctx, E extends EventObject, Tag extends string = never, Hook extends string = string> = [E] extends [never] ? never : RunAction<Ctx, E, Tag, Hook> | readonly RunAction<Ctx, E, Tag, Hook>[];
72
125
  interface RuleList<Rule> {
73
126
  readonly 0: Rule;
74
127
  readonly [index: number]: Rule;
@@ -79,7 +132,7 @@ interface RuleList<Rule> {
79
132
  * no `to` runs its `run` and nothing else: no state exits or enters.
80
133
  * @noSelf
81
134
  */
82
- export interface TransitionConfig<Ctx, E extends EventObject, V extends E = E, To extends string = string> {
135
+ export interface TransitionConfig<Ctx, E extends EventObject, V extends E = E, To extends string = string, Tag extends string = never, Hook extends string = string> {
83
136
  /**
84
137
  * The full path of the state to move to. A `to` naming the state the rule is on exits and
85
138
  * re-enters it, restarting its timers and `task`; a `to` below it keeps it active.
@@ -88,15 +141,18 @@ export interface TransitionConfig<Ctx, E extends EventObject, V extends E = E, T
88
141
  /**
89
142
  * The rule is taken only when this returns `true`. It must have no side effects: it is also
90
143
  * checked when the move does not happen, in guarded lists and on every `always` recheck.
144
+ * `ctx` and `event` are read-only here, one level deep: a write belongs in `run`.
145
+ * `machine` reads the active states; inside a parallel state it shows the regions that already
146
+ * took the event.
91
147
  */
92
- readonly when?: (ctx: Ctx, event: V) => boolean;
148
+ readonly when?: (ctx: Readonly<Ctx>, event: Readonly<V>, machine: MachineView<Hook, Tag>) => boolean;
93
149
  /**
94
150
  * Runs during the move. The event is accepted when its key matches and `when` passes; then
95
151
  * `exit` runs on each state left, deepest first; then `run`; then `enter` on each state
96
152
  * entered, outermost first. Shared ancestors neither exit nor enter. A rule with no `to` runs
97
153
  * only `run`.
98
154
  */
99
- readonly run?: MoveAction<Ctx, E, V> | readonly MoveAction<Ctx, E, V>[];
155
+ readonly run?: MoveAction<Ctx, E, V, Tag, Hook> | readonly MoveAction<Ctx, E, V, Tag, Hook>[];
100
156
  /** Renamed to `to`. */
101
157
  readonly target?: never;
102
158
  /** Renamed to `when`. */
@@ -110,20 +166,27 @@ export interface TransitionConfig<Ctx, E extends EventObject, V extends E = E, T
110
166
  * What an `on` key maps to: a target path, a `TransitionConfig`, or a non-empty list of them
111
167
  * tried in order. The first rule with no `when`, or whose `when` returns `true`, is taken.
112
168
  */
113
- export type TransitionSpec<Ctx, E extends EventObject, V extends E = E, To extends string = string> = To | TransitionConfig<Ctx, E, V, To> | RuleList<TransitionConfig<Ctx, E, V, To>>;
169
+ export type TransitionSpec<Ctx, E extends EventObject, V extends E = E, To extends string = string, Tag extends string = never, Hook extends string = string> = To | TransitionConfig<Ctx, E, V, To, Tag, Hook> | RuleList<TransitionConfig<Ctx, E, V, To, Tag, Hook>>;
114
170
  /**
115
- * The object form of an `always` rule: a `to` and an optional `when`. It gets no event and has
116
- * no `run`; side effects belong in the target's `enter`.
171
+ * The object form of an `always` rule: a `to`, an optional `when` and an optional `run`. It gets
172
+ * no event. `E` is the machine's event type; a rule typed without it has no `run`. `Hook` is
173
+ * the union of the paths the `machine` of its `when` and `run` takes: any `string` when left out.
117
174
  * @noSelf
118
175
  */
119
- export interface AlwaysConfig<Ctx, To extends string = string> {
176
+ export interface AlwaysConfig<Ctx, To extends string = string, E extends EventObject = never, Tag extends string = never, Hook extends string = string> {
120
177
  /**
121
178
  * The full path of the state to move to. An `always` rule without one throws when the machine
122
179
  * is defined.
123
180
  */
124
181
  readonly to: To;
125
- /** Checked on every recheck, so it must have no side effects. */
126
- readonly when?: (ctx: Ctx) => boolean;
182
+ /**
183
+ * Checked on every recheck, so it must have no side effects. `ctx` is read-only here, one
184
+ * level deep: a write belongs in `run`. `machine` reads the active states; inside a parallel
185
+ * state it shows the regions that already moved.
186
+ */
187
+ readonly when?: (ctx: Readonly<Ctx>, machine: MachineView<Hook, Tag>) => boolean;
188
+ /** Runs between the `exit` and `enter` hooks of the move, only when the rule is taken. */
189
+ readonly run?: RuleRun<Ctx, E, Tag, Hook>;
127
190
  /** Renamed to `to`. */
128
191
  readonly target?: never;
129
192
  /** Renamed to `when`. */
@@ -133,46 +196,149 @@ export interface AlwaysConfig<Ctx, To extends string = string> {
133
196
  * What `always` takes: a target path, an `AlwaysConfig`, or a non-empty list of them. The first
134
197
  * whose `when` is missing or passes is taken.
135
198
  */
136
- export type AlwaysSpec<Ctx, To extends string = string> = To | AlwaysConfig<Ctx, To> | RuleList<AlwaysConfig<Ctx, To>>;
199
+ export type AlwaysSpec<Ctx, To extends string = string, E extends EventObject = never, Tag extends string = never, Hook extends string = string> = To | AlwaysConfig<Ctx, To, E, Tag, Hook> | AlwaysConfig<Ctx, To, never, Tag, Hook> | RuleList<AlwaysConfig<Ctx, To, E, Tag, Hook> | AlwaysConfig<Ctx, To, never, Tag, Hook>>;
200
+ /**
201
+ * The object form of a rule on an `after` timer: a `to`, a `when` and a `run`, each optional. A
202
+ * rule with neither a `to` nor a `run` throws when the machine is defined. `E` is the machine's
203
+ * event type; a rule typed without it has no `run`. `Hook` is the union of the paths the
204
+ * `machine` of its `when` and `run` takes: any `string` when left out.
205
+ * @noSelf
206
+ */
207
+ export interface AfterRule<Ctx, To extends string = string, E extends EventObject = never, Tag extends string = never, Hook extends string = string> {
208
+ /**
209
+ * The full path of the state to move to when the timer fires. A rule without one runs its
210
+ * `run` and nothing else: no state exits or enters, and it is not a move.
211
+ */
212
+ readonly to?: To;
213
+ /**
214
+ * The timer fires only when this returns `true`. It is first checked once the delay has
215
+ * passed, then on every `update` call until it passes or the state is left, so it must have no
216
+ * side effects. `ctx` is read-only here, one level deep: a write belongs in `run`. `machine`
217
+ * reads the active states.
218
+ */
219
+ readonly when?: (ctx: Readonly<Ctx>, machine: MachineView<Hook, Tag>) => boolean;
220
+ /**
221
+ * Runs when the timer fires: between the `exit` and `enter` hooks of the move, or on its own
222
+ * for a rule with no `to`.
223
+ */
224
+ readonly run?: RuleRun<Ctx, E, Tag, Hook>;
225
+ }
226
+ /**
227
+ * One entry of the list form of `after`: a `delay` beside the fields of an `AfterRule`.
228
+ * @noSelf
229
+ */
230
+ export interface AfterConfig<Ctx, To extends string = string, E extends EventObject = never, Tag extends string = never, Hook extends string = string> extends AfterRule<Ctx, To, E, Tag, Hook> {
231
+ /**
232
+ * The delay in seconds of game time: a number, or a function of the context that returns one.
233
+ * A function is read once each time the state is entered, after its `enter` hook, and again on
234
+ * a hot reload, so the delay is fixed for that stay. The function only reads the context: it
235
+ * cannot assign to a field of `ctx`. A result that is not a non-negative number throws, naming
236
+ * the state.
237
+ */
238
+ readonly delay: number | ((ctx: Readonly<Ctx>) => number);
239
+ }
240
+ /**
241
+ * One entry of the list form of `every`: a `delay` and the `run` it repeats.
242
+ * @noSelf
243
+ */
244
+ export interface EveryConfig<Ctx, E extends EventObject, Tag extends string = never, Hook extends string = string> {
245
+ /**
246
+ * The seconds of game time between two runs: a number, or a function of the context that
247
+ * returns one. A function is read once each time the state is entered, after its `enter` hook,
248
+ * and again on a hot reload, so the delay is fixed for that stay. The function only reads the
249
+ * context: it cannot assign to a field of `ctx`. A result that is not a positive number
250
+ * throws, naming the state.
251
+ */
252
+ readonly delay: number | ((ctx: Readonly<Ctx>) => number);
253
+ /** Runs each time the delay passes: one function, or a list run in order. */
254
+ readonly run: RunAction<Ctx, E, Tag, Hook> | readonly RunAction<Ctx, E, Tag, Hook>[];
255
+ /** Not taken here: an `after` timer leaves the state. */
256
+ readonly to?: never;
257
+ /** Not taken here: a `run` tests a condition itself. */
258
+ readonly when?: never;
259
+ }
137
260
  /**
138
261
  * A state's rules by event `type`. Only the machine's event types are keys, and each rule's
139
262
  * `when` and `run` see the event variant its key names.
140
263
  */
141
- export type OnConfig<Ctx, E extends EventObject, To extends string = string> = {
264
+ export type OnConfig<Ctx, E extends EventObject, To extends string = string, Tag extends string = never, Hook extends string = string> = {
142
265
  readonly [K in E["type"]]?: TransitionSpec<Ctx, E, Extract<E, {
143
266
  type: K;
144
- }>, To>;
267
+ }>, To, Tag, Hook>;
145
268
  };
146
269
  /**
147
270
  * Runs on every way in (`enter`) or out (`exit`) of the state: a move, `start` and `stop`. A hot
148
271
  * reload enters the states it adds, but drops a removed state without its `exit`. It gets no
149
- * event; event data reaches `enter` through `ctx`, set in `run`.
272
+ * event; event data reaches `enter` through `ctx`, set in `run`. `Hook` is the union of the
273
+ * paths its `machine` takes: the machine's paths for a hook written in the config, and any
274
+ * `string` when left out.
150
275
  */
151
- export type StateHook<Ctx, E extends EventObject> = (ctx: Ctx, machine: MachineInstance<Ctx, E>) => void;
276
+ export type StateHook<Ctx, E extends EventObject, Tag extends string = never, Hook extends string = string> = (ctx: Ctx, machine: MachineInstance<Ctx, E, Hook, Tag>) => void;
152
277
  /**
153
278
  * Runs on every `update(dt)` while the state is active, deepest state first. It returns a full
154
279
  * path to move there, or `undefined` to pass. The first hook to return a path wins: the hooks
155
- * above it and the `after` timers do not run on that call, and the move has no `run`.
280
+ * above it and the `after` timers of its region do not run on that call, and the move has no
281
+ * `run`. Under a parallel state the other regions still run their hooks and timers, unless the
282
+ * move left the parallel state or entered it again. `Hook` is the union of the paths its
283
+ * `machine` takes: the machine's paths for a hook written in the config, and any `string` when
284
+ * left out.
285
+ */
286
+ export type UpdateHook<Ctx, E extends EventObject, To extends string = string, Tag extends string = never, Hook extends string = string> = (ctx: Ctx, dt: number, machine: MachineInstance<Ctx, E, Hook, Tag>) => To | undefined;
287
+ /**
288
+ * Runs when the machine drops an event while the state is active: for the events `onUnhandled`
289
+ * reports, and no others. Every active state that has the hook runs it, deepest first, in the
290
+ * order `update` hooks run. `event` is the machine's whole event union, and is read-only: the
291
+ * hook cannot assign to its fields. The hook moves nothing: a `send` from it queues, and a
292
+ * `stop()` ends the step once every hook and listener has run for the event. `Hook` is the
293
+ * union of the paths its `machine` takes: the machine's paths for a hook written in the config,
294
+ * and any `string` when left out.
156
295
  */
157
- export type UpdateHook<Ctx, E extends EventObject, To extends string = string> = (ctx: Ctx, dt: number, machine: MachineInstance<Ctx, E>) => To | undefined;
296
+ export type UnhandledHook<Ctx, E extends EventObject, Tag extends string = never, Hook extends string = string> = (ctx: Ctx, event: Readonly<E>, machine: MachineInstance<Ctx, E, Hook, Tag>) => void;
158
297
  /**
159
298
  * Starts work that lives as long as the state. `finish` sends one event and is ignored after the
160
- * first call or once the state is left. The returned cleanup runs on every way out.
299
+ * first call or once the state is left. The returned cleanup runs on every way out. A task that
300
+ * starts several callbacks counts them with `waitGroup`. `Hook` is the union of the paths its
301
+ * `machine` takes: the machine's paths for a task written in the config, and any `string` when
302
+ * left out.
303
+ * @noSelf
304
+ */
305
+ export type TaskStart<Ctx, E extends EventObject, Tag extends string = never, Hook extends string = string> = (ctx: Ctx, finish: (event: E) => void, machine: MachineInstance<Ctx, E, Hook, Tag>) => (() => void) | void;
306
+ /**
307
+ * Counts several pieces of engine work a state starts and waits for, returned by `waitGroup()`.
308
+ * It knows nothing about a machine: built inside a `task`, it is new for each entry of the
309
+ * state.
161
310
  * @noSelf
162
311
  */
163
- export type TaskStart<Ctx, E extends EventObject> = (ctx: Ctx, finish: (event: E) => void, machine: MachineInstance<Ctx, E>) => (() => void) | void;
312
+ export interface WaitGroup {
313
+ /**
314
+ * Records one pending piece of work and returns the function that reports it finished. That
315
+ * function counts once, and a later call does nothing. It ignores its arguments, so an engine
316
+ * function takes it as its callback, as in `http.request(url, "GET", group.add())`.
317
+ */
318
+ readonly add: () => () => void;
319
+ /**
320
+ * Calls `callback` once when no added work is pending: at once when none is pending at the
321
+ * call, and otherwise inside the report that finishes the last pending piece. Each call
322
+ * registers one callback, and several run in the order registered. A `wait` made before the
323
+ * first `add` runs at once, so it goes after them.
324
+ */
325
+ readonly wait: (callback: () => void) => void;
326
+ }
164
327
  /**
165
328
  * A machine definition, returned by the second `defineMachine` call. Its `start` creates the
166
329
  * instances that run.
167
330
  * @noSelf
168
331
  */
169
- export interface Machine<Ctx, E extends EventObject, P extends string = string> {
332
+ export interface Machine<Ctx, E extends EventObject, P extends string = string, Tag extends string = never> {
170
333
  /**
171
- * Creates an instance with `ctx` as its context and enters the root's `initial` chain. Each
172
- * call returns a separate instance, which has already handled the events its `enter` hooks
173
- * sent.
334
+ * Creates an instance with `ctx` as its context. Without `at` it enters the root's `initial`
335
+ * chain. With `at`, a list of full state paths such as a copy of `leaves`, it enters toward
336
+ * each path: a level or region no path names goes through `initial`, a path the machine lacks
337
+ * falls back to its closest parent, and of two paths that cannot be active together the first
338
+ * wins. Timer progress and `restoreDepth` memory are not part of the list. Each call returns a
339
+ * separate instance, which has already handled the events its `enter` hooks sent.
174
340
  */
175
- readonly start: (ctx: Ctx) => MachineInstance<Ctx, E, P>;
341
+ readonly start: (ctx: Ctx, at?: readonly P[]) => MachineInstance<Ctx, E, P, Tag>;
176
342
  }
177
343
  type PathDepth = [never, 0, 1, 2, 3];
178
344
  type NextCount = [never, 2, 3, 4, 5, 6];
@@ -180,10 +346,6 @@ type TreePathsBelow<T, D extends number> = {
180
346
  [K in keyof T & string]: K | `${K}/${D extends 1 ? keyof T[K] extends never ? never : string : TreePathsBelow<T[K], PathDepth[D]>}`;
181
347
  }[keyof T & string];
182
348
  type TreePath<T> = `/${TreePathsBelow<T, 4>}`;
183
- type SlotPathsBelow<T, D extends number> = unknown extends T ? string : {
184
- [K in keyof T & string]: K | `${K}/${D extends 1 ? string : SlotPathsBelow<T[K], PathDepth[D]>}`;
185
- }[keyof T & string];
186
- type SlotPath<T> = `/${SlotPathsBelow<T, 4>}`;
187
349
  type TreeOf<C> = C extends {
188
350
  readonly states: infer Children;
189
351
  } ? {
@@ -197,56 +359,89 @@ export type StatePath<C> = TreePath<TreeOf<C>>;
197
359
  type TreeDepth<T, D extends number> = unknown extends T ? number : [D] extends [never] ? number : 1 | ({
198
360
  [K in keyof T]: unknown extends T[K] ? never : TreeDepth<T[K], PathDepth[D]>;
199
361
  }[keyof T] extends infer N ? N extends number ? number extends N ? number : NextCount[N] : never : never);
200
- type AnyChild<Ctx, E extends EventObject, T, To extends string> = unknown extends T ? {
201
- readonly [name: string]: StateNode<Ctx, E, unknown, string, To>;
362
+ type AnyChild<Ctx, E extends EventObject, T, To extends string, Tag extends string = never, Hook extends string = string> = unknown extends T ? {
363
+ readonly [name: string]: StateNode<Ctx, E, unknown, string, To, Tag, Hook>;
202
364
  } : unknown;
203
365
  /** @noSelf */
204
- interface StateBase<Ctx, E extends EventObject, T, Self extends string, To extends string> {
366
+ interface StateBase<Ctx, E extends EventObject, T, Self extends string, To extends string, Tag extends string = never, Hook extends string = string> {
205
367
  /** Child states, by name. A name is non-empty and contains no `/`. */
206
368
  readonly states?: {
207
- readonly [K in keyof T]: StateNode<Ctx, E, T[K], `${Self}/${K & string}`, To>;
208
- } & NoInfer<AnyChild<Ctx, E, T, To>>;
369
+ readonly [K in keyof T]: StateNode<Ctx, E, T[K], `${Self}/${K & string}`, To, Tag, Hook>;
370
+ } & NoInfer<AnyChild<Ctx, E, T, To, Tag, Hook>>;
371
+ /**
372
+ * Names from the machine's third type argument that hold while the state is active, read with
373
+ * `hasTag`.
374
+ */
375
+ readonly tags?: readonly Tag[];
209
376
  /**
210
377
  * Rules by event `type`, each a target path, a rule object or a list of them. An event starts
211
378
  * at the deepest active state and moves up to the parent while no rule takes it.
212
379
  */
213
- readonly on?: NoInfer<OnConfig<Ctx, E, To>>;
380
+ readonly on?: NoInfer<OnConfig<Ctx, E, To, Tag, Hook>>;
214
381
  /**
215
- * Targets by delay in seconds of game time. The delay is counted by `update(dt)` while the
216
- * state is active, from zero on each entry. One `update` call fires at most one timer, and
217
- * none when an `update` hook moved the machine.
382
+ * Timers, in one of two forms: targets or `AfterRule` objects by delay in seconds of game
383
+ * time, such as `{ 2: "/idle" }`, or a list of `AfterConfig` entries, such as
384
+ * `[{ delay: (ctx) => ctx.patrolFor, to: "/rest" }]`, whose `delay` is a number or a function
385
+ * of the context. A delay function is read once each time the state is entered, after its
386
+ * `enter` hook, and again on a hot reload. The delay is counted by `update(dt)` while the state
387
+ * is active, from zero on each entry, and a timer fires at most once per stay. An `update`
388
+ * call tries the due timers in delay order: one whose `when` fails stays pending, one with no
389
+ * `to` runs its `run` and the call goes on, and the first that moves ends the timers of its
390
+ * region for that call. None is tried in a region an `update` hook moved on that call. Under a
391
+ * parallel state each region follows both rules on its own, and the parallel state's own
392
+ * timers are tried only when no region moved. A state's `every` entries run before its timers.
218
393
  */
219
394
  readonly after?: NoInfer<{
220
- readonly [seconds: number]: To;
221
- }>;
395
+ readonly [seconds: number]: To | AfterRule<Ctx, To, E, Tag, Hook> | AfterRule<Ctx, To, never, Tag, Hook>;
396
+ } | readonly (AfterConfig<Ctx, To, E, Tag, Hook> | AfterConfig<Ctx, To, never, Tag, Hook>)[]>;
222
397
  /**
223
- * Targets taken right after any move that leaves this state active; the first whose `when` is
224
- * missing or passes wins. It is checked after a move only: not after a rule with no `to`, and
225
- * not on an `update` call that moves nothing.
398
+ * Code that repeats while the state is active, in one of two forms: a `run` by delay in
399
+ * seconds of game time, such as `{ 0.5: (ctx) => burn(ctx) }`, or a list of `EveryConfig`
400
+ * entries, such as `[{ delay: (ctx) => ctx.fireEvery, run: fire }]`, whose `delay` is a number
401
+ * or a function of the context, read as an `after` delay is. A `run` is one function or a list
402
+ * run in order. An entry first runs one delay after the state is entered, then each time
403
+ * another delay has passed on the state's clock, which `update(dt)` advances while the state
404
+ * is active, from zero on each entry. It runs at most once per `update` call: a call that
405
+ * covers several delays runs it once, and its later runs stay on multiples of the delay. A run
406
+ * is not a move: no `update` hook is skipped, `always` is not rechecked and `onMove` is not
407
+ * called. A state's entries run where its `after` timers are tried and before them, a list in
408
+ * list order and the other form shortest delay first; on a call in which an `update` hook or a
409
+ * timer of a deeper state moved, they wait for the next call. A delay that is not a positive
410
+ * number throws, naming the state.
226
411
  */
227
- readonly always?: AlwaysSpec<Ctx, To>;
412
+ readonly every?: NoInfer<{
413
+ readonly [seconds: number]: RunAction<Ctx, E, Tag, Hook> | readonly RunAction<Ctx, E, Tag, Hook>[];
414
+ } | readonly EveryConfig<Ctx, E, Tag, Hook>[]>;
415
+ /**
416
+ * Rules taken right after any move that leaves this state active; the first whose `when` is
417
+ * missing or passes wins, and its `run` runs during the move. It is checked after a move only:
418
+ * not after a rule with no `to`, and not on an `update` call that moves nothing.
419
+ */
420
+ readonly always?: AlwaysSpec<Ctx, To, E, Tag, Hook>;
228
421
  /**
229
422
  * Runs each time the state is entered, outermost state first: on a move, at `start`, and when
230
423
  * a hot reload adds the state. It gets no event.
231
424
  */
232
- readonly enter?: StateHook<Ctx, E>;
425
+ readonly enter?: StateHook<Ctx, E, Tag, Hook>;
233
426
  /**
234
427
  * Runs each time the state is left, deepest state first, including on `stop()`. A state a hot
235
428
  * reload removes is dropped without it.
236
429
  */
237
- readonly exit?: StateHook<Ctx, E>;
430
+ readonly exit?: StateHook<Ctx, E, Tag, Hook>;
238
431
  /**
239
432
  * Runs on every `update(dt)` while the state is active. Return one of the machine's full paths
240
433
  * to move there, or `undefined` to stay.
241
434
  */
242
- readonly update?: NoInfer<UpdateHook<Ctx, E, To>>;
435
+ readonly update?: NoInfer<UpdateHook<Ctx, E, To, Tag, Hook>>;
243
436
  /** Started on each entry; see `TaskStart`. */
244
- readonly task?: TaskStart<Ctx, E>;
437
+ readonly task?: TaskStart<Ctx, E, Tag, Hook>;
438
+ /** Runs when the machine drops an event while the state is active; see `UnhandledHook`. */
439
+ readonly unhandled?: UnhandledHook<Ctx, E, Tag, Hook>;
245
440
  /** Renamed to `task`. */
246
441
  readonly invoke?: never;
247
442
  }
248
443
  /** @noSelf */
249
- interface CompoundState<Ctx, E extends EventObject, T, Self extends string, To extends string> extends StateBase<Ctx, E, T, Self, To> {
444
+ interface CompoundState<Ctx, E extends EventObject, T, Self extends string, To extends string, Tag extends string = never, Hook extends string = string> extends StateBase<Ctx, E, T, Self, To, Tag, Hook> {
250
445
  /**
251
446
  * `"parallel"` keeps every child active at once, each as a region with its own active state.
252
447
  * Not allowed on the root.
@@ -266,7 +461,7 @@ interface CompoundState<Ctx, E extends EventObject, T, Self extends string, To e
266
461
  readonly restoreDepth?: NoInfer<TreeDepth<T, 4> | "all">;
267
462
  }
268
463
  /** @noSelf */
269
- interface ParallelState<Ctx, E extends EventObject, T, Self extends string, To extends string> extends StateBase<Ctx, E, T, Self, To> {
464
+ interface ParallelState<Ctx, E extends EventObject, T, Self extends string, To extends string, Tag extends string = never, Hook extends string = string> extends StateBase<Ctx, E, T, Self, To, Tag, Hook> {
270
465
  /**
271
466
  * `"parallel"` keeps every child active at once, each as a region with its own active state.
272
467
  * Not allowed on the root.
@@ -275,45 +470,110 @@ interface ParallelState<Ctx, E extends EventObject, T, Self extends string, To e
275
470
  readonly initial?: never;
276
471
  readonly restoreDepth?: never;
277
472
  }
278
- type StateNode<Ctx, E extends EventObject, T, Self extends string, To extends string> = CompoundState<Ctx, E, T, Self, To> | ParallelState<Ctx, E, T, Self, To>;
279
- type RootState<Ctx, E extends EventObject, T> = CompoundState<Ctx, E, T, "", NoInfer<SlotPath<T>>> & {
473
+ type StateNode<Ctx, E extends EventObject, T, Self extends string, To extends string, Tag extends string = never, Hook extends string = string> = CompoundState<Ctx, E, T, Self, To, Tag, Hook> | ParallelState<Ctx, E, T, Self, To, Tag, Hook>;
474
+ type StateKey = keyof CompoundState<unknown, EventObject, unknown, string, string> | keyof ParallelState<unknown, EventObject, unknown, string, string>;
475
+ type NodeLevel<NT> = {
476
+ readonly [K in keyof NT]: NodeKeys<NT[K]> & InferOnly<NT[K]>;
477
+ };
478
+ type NodeKeys<N> = {
479
+ readonly [P in keyof N]: NodeSlot<P, N[P], keyof N>;
480
+ };
481
+ type NodeSlot<P, V, Keys> = P extends "states" ? NodeLevel<V> : P extends "initial" | "restoreDepth" ? "states" extends Keys ? unknown : never : P extends StateKey ? unknown : never;
482
+ type InferOnly<X> = X extends unknown ? unknown : X;
483
+ type StatesOf<N> = N extends {
484
+ readonly states: infer C;
485
+ } ? C : unknown;
486
+ type HasStates<N> = "states" extends keyof N ? true : false;
487
+ type NodePathsBelow<L, D extends number> = unknown extends L ? string : {
488
+ [K in keyof L & string]: K | (unknown extends L[K] ? `${K}/${string}` : HasStates<L[K]> extends true ? `${K}/${D extends 1 ? string : NodePathsBelow<StatesOf<L[K]>, PathDepth[D]>}` : never);
489
+ }[keyof L & string];
490
+ type NodePath<NT> = `/${NodePathsBelow<NT, 4>}`;
491
+ type RootState<Ctx, E extends EventObject, T, Tag extends string = never, NT = unknown, HT = unknown> = CompoundState<Ctx, E, T, "", NoInfer<NodePath<NT>>, Tag, NoInfer<NodePath<HT>>> & {
280
492
  readonly initial: string;
281
493
  readonly states: object;
494
+ } & {
495
+ readonly states: NodeLevel<NT>;
496
+ } & {
497
+ readonly states: NodeLevel<HT>;
282
498
  };
283
499
  /**
284
500
  * The config of one state. Every field is optional. A state with child `states` is a compound
285
501
  * state and needs an `initial`, unless its `type` is `"parallel"`; a state without children is a
286
- * leaf.
502
+ * leaf. `Tag` is the union of the machine's tag names. The `machine` a callback of a config
503
+ * typed this way receives takes any `string` path.
287
504
  */
288
- export type StateConfig<Ctx, E extends EventObject> = StateNode<Ctx, E, unknown, string, string>;
505
+ export type StateConfig<Ctx, E extends EventObject, Tag extends string = never> = StateNode<Ctx, E, unknown, string, string, Tag>;
289
506
  /**
290
507
  * The root config passed to `defineMachine`. It takes the fields of a state and requires
291
- * `initial` and `states`. The root has no name and cannot be parallel.
508
+ * `initial` and `states`. The root has no name and cannot be parallel. `Tag` is the union of the
509
+ * machine's tag names. The `machine` a callback of a config typed this way receives takes any
510
+ * `string` path.
292
511
  */
293
- export type MachineConfig<Ctx, E extends EventObject> = CompoundState<Ctx, E, unknown, string, string> & {
512
+ export type MachineConfig<Ctx, E extends EventObject, Tag extends string = never> = CompoundState<Ctx, E, unknown, string, string, Tag> & {
294
513
  readonly initial: string;
295
514
  readonly states: {
296
- readonly [name: string]: StateConfig<Ctx, E>;
515
+ readonly [name: string]: StateConfig<Ctx, E, Tag>;
297
516
  };
298
517
  };
518
+ type WithPrivate<Ctx, Priv> = [Priv] extends [never] ? Ctx : Ctx & Priv;
299
519
  /**
300
520
  * Defines a machine in two calls: `defineMachine<Ctx, E>()` fixes the context and event types and
301
521
  * takes an optional hot-reload key, and the second call takes the config. The config is checked
302
522
  * when that call runs, and a mistake throws, naming the state path. With a key, a later
303
523
  * definition under it puts its states into the machine already built, so every running instance
304
524
  * sees the edit; without one, each call builds a separate machine.
525
+ *
526
+ * A third type argument, as in `defineMachine<Ctx, E, Tag>()`, is a union of tag names: a state's
527
+ * `tags` lists some of them, and `hasTag` asks whether an active state carries one. A machine
528
+ * defined without it has no tags.
529
+ *
530
+ * The root config may hold `privateCtx`, for a machine that keeps fields of its own, such as a
531
+ * counter, a heat level or a list it builds up. Every hook, `when`, `run`, `task` and `update`
532
+ * then sees those fields beside `Ctx`, while `start` takes only `Ctx` and the instance it returns
533
+ * shows only `Ctx` in its `ctx`. A machine without fields of its own leaves `privateCtx` out.
534
+ */
535
+ export declare function defineMachine<Ctx, E extends EventObject, Tag extends string = never>(key?: string): <T, Priv extends object = never, NT = unknown, HT = unknown>(config: RootState<WithPrivate<Ctx, Priv>, E, T, Tag, NT, HT> & PrivateCtxConfig<Ctx, Priv>) => Machine<Ctx, E, TreePath<T>, Tag>;
536
+ /**
537
+ * Makes a group that counts the engine work a `task` started and tells when all of it has
538
+ * finished. Each `add()` returns the callback to pass to one engine call, and `wait` runs its
539
+ * callback once every one of them has reported.
540
+ *
541
+ * @example
542
+ * ```ts
543
+ * loading: {
544
+ * task: (ctx, finish) => {
545
+ * const group = waitGroup();
546
+ * http.request(ctx.url, "GET", group.add());
547
+ * go.animate(
548
+ * ctx.curtain,
549
+ * "tint.w",
550
+ * go.PLAYBACK_ONCE_FORWARD,
551
+ * 0,
552
+ * go.EASING_LINEAR,
553
+ * 0.5,
554
+ * 0,
555
+ * group.add(),
556
+ * );
557
+ * group.wait(() => finish({ type: "READY" }));
558
+ * },
559
+ * on: { READY: "/playing" },
560
+ * },
561
+ * ```
305
562
  */
306
- export declare function defineMachine<Ctx, E extends EventObject>(key?: string): <T>(config: RootState<Ctx, E, T>) => Machine<Ctx, E, TreePath<T>>;
563
+ export declare function waitGroup(): WaitGroup;
307
564
  /**
308
- * The key a `definePrivateMachine` config adds to the root, beside `initial` and `states`.
565
+ * The optional key a `defineMachine` root config takes beside `initial` and `states`.
309
566
  * @noSelf
310
567
  */
311
568
  export interface PrivateCtxConfig<Ctx, Priv> {
312
569
  /**
313
570
  * Builds the fields only the machine uses. It runs once per `start`, before the root's `enter`
314
571
  * hook, so every instance gets fresh fields of its own; a value written straight into the
315
- * config would be one array or table shared by every instance. It takes no arguments: to set
316
- * a private field from a field of `Ctx`, do it in the root's `enter` hook.
572
+ * config would be one array or table shared by every instance. It receives the object passed
573
+ * to `start`, typed `Ctx`, so a private field can start from a field of `Ctx`:
574
+ * `privateCtx: (ctx: TurretOptions) => ({ heat: ctx.startHeat })`. Give `ctx` its type; without
575
+ * one TypeScript can type a hook before it knows the private fields, and that hook reports the
576
+ * field as missing.
317
577
  *
318
578
  * The fields are added to the object passed to `start`, which stays the instance's `ctx`, so
319
579
  * restarting with `start(old.ctx)` keeps the public fields and builds the private ones fresh.
@@ -323,92 +583,84 @@ export interface PrivateCtxConfig<Ctx, Priv> {
323
583
  *
324
584
  * The fields are hidden from callers by type only. At run time `ctx` is one table, and hooks,
325
585
  * `hsm-view` and a debugger see every field.
586
+ *
587
+ * There are two ways to type the fields.
588
+ *
589
+ * @example
590
+ * **Inferred.** For a few fields, write them inline and let TypeScript infer their types. Add
591
+ * `as` where the inferred type is wrong: an empty array is `never[]` until it reads
592
+ * `[] as Hash[]`, and a field holding one of several strings is any `string` until it reads
593
+ * `"cool" as "cool" | "hot"`.
594
+ *
595
+ * ```ts
596
+ * type TurretEvent = { type: "SPOTTED" } | { type: "LOST" };
597
+ *
598
+ * export const turret = defineMachine<{ readonly fireRate: number }, TurretEvent>("turret")({
599
+ * privateCtx: () => ({ heat: 0, targets: [] as Hash[] }),
600
+ * initial: "/idle",
601
+ * states: {
602
+ * idle: { on: { SPOTTED: "/firing" } },
603
+ * firing: {
604
+ * enter: (ctx) => {
605
+ * ctx.heat += 1 / ctx.fireRate;
606
+ * },
607
+ * on: { LOST: "/idle" },
608
+ * },
609
+ * },
610
+ * });
611
+ *
612
+ * const gun = turret.start({ fireRate: 2 }); // gun.ctx shows fireRate only
613
+ * ```
614
+ *
615
+ * @example
616
+ * **A named type.** For more fields, or when a helper outside the config needs the whole
617
+ * context, declare the fields as an interface and make it the factory's return type. The
618
+ * fields need no `as`, a misspelled or unknown field is a compile error, and the whole context
619
+ * is `TurretOptions & TurretPrivate`. `satisfies TurretPrivate` does not do this: it checks the
620
+ * object but keeps its narrow types, so an empty array stays `never[]`.
621
+ *
622
+ * ```ts
623
+ * interface TurretOptions {
624
+ * readonly fireRate: number;
625
+ * }
626
+ *
627
+ * interface TurretPrivate {
628
+ * heat: number;
629
+ * mode: "cool" | "hot";
630
+ * }
631
+ *
632
+ * type TurretEvent = { type: "SPOTTED" } | { type: "LOST" };
633
+ *
634
+ * const overheated = (ctx: TurretOptions & TurretPrivate) => ctx.heat > ctx.fireRate;
635
+ *
636
+ * export const turret = defineMachine<TurretOptions, TurretEvent>("turret")({
637
+ * privateCtx: (): TurretPrivate => ({ heat: 0, mode: "cool" }),
638
+ * initial: "/idle",
639
+ * states: {
640
+ * idle: { on: { SPOTTED: "/firing" } },
641
+ * firing: {
642
+ * enter: (ctx) => {
643
+ * ctx.heat += 1 / ctx.fireRate;
644
+ * },
645
+ * always: { to: "/cooling", when: overheated },
646
+ * on: { LOST: "/idle" },
647
+ * },
648
+ * cooling: {
649
+ * enter: (ctx) => {
650
+ * ctx.mode = "hot";
651
+ * },
652
+ * exit: (ctx) => {
653
+ * ctx.heat = 0;
654
+ * ctx.mode = "cool";
655
+ * },
656
+ * after: { 2: "/idle" },
657
+ * },
658
+ * },
659
+ * });
660
+ * ```
326
661
  */
327
- readonly privateCtx: () => Priv & {
662
+ readonly privateCtx?: (ctx: Ctx) => Priv & {
328
663
  readonly [K in keyof Ctx & keyof Priv]: never;
329
664
  };
330
665
  }
331
- /**
332
- * Like `defineMachine`, for a machine that keeps fields of its own, such as a counter, a heat
333
- * level or a list it builds up. The config's `privateCtx` returns those fields. Every hook,
334
- * `when`, `run`, `task` and `update` sees them beside `Ctx`, while `start` takes only `Ctx` and
335
- * the instance it returns shows only `Ctx` in its `ctx`. A machine without fields of its own
336
- * uses `defineMachine`.
337
- *
338
- * There are two ways to type the fields.
339
- *
340
- * @example
341
- * **Inferred.** For a few fields, write them inline and let TypeScript infer their types. Add
342
- * `as` where the inferred type is wrong: an empty array is `never[]` until it reads
343
- * `[] as Hash[]`, and a field holding one of several strings is any `string` until it reads
344
- * `"cool" as "cool" | "hot"`.
345
- *
346
- * ```ts
347
- * type TurretEvent = { type: "SPOTTED" } | { type: "LOST" };
348
- *
349
- * export const turret = definePrivateMachine<{ readonly fireRate: number }, TurretEvent>("turret")({
350
- * privateCtx: () => ({ heat: 0, targets: [] as Hash[] }),
351
- * initial: "/idle",
352
- * states: {
353
- * idle: { on: { SPOTTED: "/firing" } },
354
- * firing: {
355
- * enter: (ctx) => {
356
- * ctx.heat += 1 / ctx.fireRate;
357
- * },
358
- * on: { LOST: "/idle" },
359
- * },
360
- * },
361
- * });
362
- *
363
- * const gun = turret.start({ fireRate: 2 }); // gun.ctx shows fireRate only
364
- * ```
365
- *
366
- * @example
367
- * **A named type.** For more fields, or when a helper outside the config needs the whole
368
- * context, declare the fields as an interface and make it the factory's return type. The fields
369
- * need no `as`, a misspelled or unknown field is a compile error, and the whole context is
370
- * `TurretOptions & TurretPrivate`. `satisfies TurretPrivate` does not do this: it checks the
371
- * object but keeps its narrow types, so an empty array stays `never[]`.
372
- *
373
- * ```ts
374
- * interface TurretOptions {
375
- * readonly fireRate: number;
376
- * }
377
- *
378
- * interface TurretPrivate {
379
- * heat: number;
380
- * mode: "cool" | "hot";
381
- * }
382
- *
383
- * type TurretEvent = { type: "SPOTTED" } | { type: "LOST" };
384
- *
385
- * const overheated = (ctx: TurretOptions & TurretPrivate) => ctx.heat > ctx.fireRate;
386
- *
387
- * export const turret = definePrivateMachine<TurretOptions, TurretEvent>("turret")({
388
- * privateCtx: (): TurretPrivate => ({ heat: 0, mode: "cool" }),
389
- * initial: "/idle",
390
- * states: {
391
- * idle: { on: { SPOTTED: "/firing" } },
392
- * firing: {
393
- * enter: (ctx) => {
394
- * ctx.heat += 1 / ctx.fireRate;
395
- * },
396
- * always: { to: "/cooling", when: overheated },
397
- * on: { LOST: "/idle" },
398
- * },
399
- * cooling: {
400
- * enter: (ctx) => {
401
- * ctx.mode = "hot";
402
- * },
403
- * exit: (ctx) => {
404
- * ctx.heat = 0;
405
- * ctx.mode = "cool";
406
- * },
407
- * after: { 2: "/idle" },
408
- * },
409
- * },
410
- * });
411
- * ```
412
- */
413
- export declare const definePrivateMachine: <Ctx, E extends EventObject>(key?: string) => <T, Priv extends object>(config: RootState<Ctx & Priv, E, T> & PrivateCtxConfig<Ctx, Priv>) => Machine<Ctx, E, TreePath<T>>;
414
666
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defold-typescript/types",
3
- "version": "0.47.0",
3
+ "version": "0.49.0",
4
4
  "description": "TypeScript types for the Defold engine's Lua APIs.",
5
5
  "license": "MIT",
6
6
  "repository": {