@defold-typescript/transpiler 0.47.0 → 0.48.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/hsm/index.ts +216 -121
- package/package.json +2 -2
package/dist/hsm/index.ts
CHANGED
|
@@ -168,6 +168,22 @@ export type AlwaysSpec<Ctx, To extends string = string> =
|
|
|
168
168
|
| AlwaysConfig<Ctx, To>
|
|
169
169
|
| RuleList<AlwaysConfig<Ctx, To>>;
|
|
170
170
|
|
|
171
|
+
/**
|
|
172
|
+
* One entry of the list form of `after`: a `delay` and the `to` its timer leads to.
|
|
173
|
+
* @noSelf
|
|
174
|
+
*/
|
|
175
|
+
export interface AfterConfig<Ctx, To extends string = string> {
|
|
176
|
+
/**
|
|
177
|
+
* The delay in seconds of game time: a number, or a function of the context that returns one.
|
|
178
|
+
* A function is read once each time the state is entered, after its `enter` hook, and again on
|
|
179
|
+
* a hot reload, so the delay is fixed for that stay. A result that is not a non-negative number
|
|
180
|
+
* throws, naming the state.
|
|
181
|
+
*/
|
|
182
|
+
readonly delay: number | ((ctx: Ctx) => number);
|
|
183
|
+
/** The full path of the state to move to when the delay has passed. */
|
|
184
|
+
readonly to: To;
|
|
185
|
+
}
|
|
186
|
+
|
|
171
187
|
/**
|
|
172
188
|
* A state's rules by event `type`. Only the machine's event types are keys, and each rule's
|
|
173
189
|
* `when` and `run` see the event variant its key names.
|
|
@@ -304,11 +320,15 @@ interface StateBase<Ctx, E extends EventObject, T, Self extends string, To exten
|
|
|
304
320
|
*/
|
|
305
321
|
readonly on?: NoInfer<OnConfig<Ctx, E, To>>;
|
|
306
322
|
/**
|
|
307
|
-
*
|
|
308
|
-
*
|
|
309
|
-
*
|
|
323
|
+
* Timers, in one of two forms: targets by delay in seconds of game time, such as
|
|
324
|
+
* `{ 2: "/idle" }`, or a list of `AfterConfig` entries, such as
|
|
325
|
+
* `[{ delay: (ctx) => ctx.patrolFor, to: "/rest" }]`, whose `delay` is a number or a function
|
|
326
|
+
* of the context. A delay function is read once each time the state is entered, after its
|
|
327
|
+
* `enter` hook, and again on a hot reload. The delay is counted by `update(dt)` while the state
|
|
328
|
+
* is active, from zero on each entry. One `update` call fires at most one timer, and none when
|
|
329
|
+
* an `update` hook moved the machine.
|
|
310
330
|
*/
|
|
311
|
-
readonly after?: NoInfer<{ readonly [seconds: number]: To }>;
|
|
331
|
+
readonly after?: NoInfer<{ readonly [seconds: number]: To } | readonly AfterConfig<Ctx, To>[]>;
|
|
312
332
|
/**
|
|
313
333
|
* Targets taken right after any move that leaves this state active; the first whose `when` is
|
|
314
334
|
* missing or passes wins. It is checked after a move only: not after a rule with no `to`, and
|
|
@@ -415,6 +435,8 @@ interface Transition<Ctx, E extends EventObject> {
|
|
|
415
435
|
readonly run: readonly MoveAction<Ctx, E>[];
|
|
416
436
|
}
|
|
417
437
|
|
|
438
|
+
type Delay<Ctx> = number | ((ctx: Ctx) => number);
|
|
439
|
+
|
|
418
440
|
interface Compiled<Ctx, E extends EventObject> {
|
|
419
441
|
count: number;
|
|
420
442
|
readonly configs: StateConfig<Ctx, E>[];
|
|
@@ -431,6 +453,10 @@ interface Compiled<Ctx, E extends EventObject> {
|
|
|
431
453
|
readonly transitions: Transition<Ctx, E>[][];
|
|
432
454
|
readonly afterDelays: number[][];
|
|
433
455
|
readonly afterTargets: number[][];
|
|
456
|
+
// Set only for a state whose after list holds a delay function: its entries in list order,
|
|
457
|
+
// for each instance to sort once the function is read.
|
|
458
|
+
readonly afterListDelays: (Delay<Ctx>[] | undefined)[];
|
|
459
|
+
readonly afterListTargets: (number[] | undefined)[];
|
|
434
460
|
readonly always: Transition<Ctx, E>[][];
|
|
435
461
|
readonly pathIndex: { [path: string]: number };
|
|
436
462
|
}
|
|
@@ -763,6 +789,25 @@ function parseDelay(key: string | number): number {
|
|
|
763
789
|
return delay;
|
|
764
790
|
}
|
|
765
791
|
|
|
792
|
+
// NaN fails the comparison, so it is rejected with the negative numbers.
|
|
793
|
+
function checkDelay(path: string, delay: unknown, written: unknown): void {
|
|
794
|
+
if (typeof delay !== "number" || !(delay >= 0)) {
|
|
795
|
+
throw `hsm: state "${describePath(path)}" has an after delay "${written}" that is not a non-negative number`;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// Keeps the delays ascending; timers with one delay stay in the order they were added.
|
|
800
|
+
function insertTimer(delays: number[], targets: number[], delay: number, target: number): void {
|
|
801
|
+
let slot = delays.length;
|
|
802
|
+
while (slot > 0 && (delays[slot - 1] as number) > delay) {
|
|
803
|
+
delays[slot] = delays[slot - 1] as number;
|
|
804
|
+
targets[slot] = targets[slot - 1] as number;
|
|
805
|
+
slot--;
|
|
806
|
+
}
|
|
807
|
+
delays[slot] = delay;
|
|
808
|
+
targets[slot] = target;
|
|
809
|
+
}
|
|
810
|
+
|
|
766
811
|
function compileAfter<Ctx, E extends EventObject>(compiled: Compiled<Ctx, E>, index: number): void {
|
|
767
812
|
const delays: number[] = [];
|
|
768
813
|
const targets: number[] = [];
|
|
@@ -773,20 +818,39 @@ function compileAfter<Ctx, E extends EventObject>(compiled: Compiled<Ctx, E>, in
|
|
|
773
818
|
return;
|
|
774
819
|
}
|
|
775
820
|
const path = compiled.paths[index] as string;
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
821
|
+
// Read as an array type: the transpiler shifts an index to Lua's base 1 only on an array.
|
|
822
|
+
const list = after as readonly AfterConfig<Ctx>[];
|
|
823
|
+
if (typeof list[0] !== "object") {
|
|
824
|
+
const byDelay = after as { readonly [seconds: number]: string };
|
|
825
|
+
for (const key in byDelay) {
|
|
826
|
+
const delay = parseDelay(key);
|
|
827
|
+
checkDelay(path, delay, key);
|
|
828
|
+
const target = resolveTarget(compiled, index, byDelay[key as unknown as number] as string);
|
|
829
|
+
insertTimer(delays, targets, delay, target);
|
|
780
830
|
}
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
const listDelays: Delay<Ctx>[] = [];
|
|
834
|
+
const listTargets: number[] = [];
|
|
835
|
+
let perInstance = false;
|
|
836
|
+
for (let i = 0; i < list.length; i++) {
|
|
837
|
+
const entry = list[i] as AfterConfig<Ctx>;
|
|
838
|
+
const delay = entry.delay;
|
|
839
|
+
if (typeof delay === "function") {
|
|
840
|
+
perInstance = true;
|
|
841
|
+
} else {
|
|
842
|
+
checkDelay(path, delay, delay);
|
|
787
843
|
}
|
|
788
|
-
|
|
789
|
-
|
|
844
|
+
listDelays[i] = delay;
|
|
845
|
+
listTargets[i] = resolveTarget(compiled, index, entry.to);
|
|
846
|
+
}
|
|
847
|
+
if (perInstance) {
|
|
848
|
+
compiled.afterListDelays[index] = listDelays;
|
|
849
|
+
compiled.afterListTargets[index] = listTargets;
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
for (let i = 0; i < listDelays.length; i++) {
|
|
853
|
+
insertTimer(delays, targets, listDelays[i] as number, listTargets[i] as number);
|
|
790
854
|
}
|
|
791
855
|
}
|
|
792
856
|
|
|
@@ -807,6 +871,8 @@ function compile<Ctx, E extends EventObject>(config: MachineConfig<Ctx, E>): Com
|
|
|
807
871
|
transitions: [],
|
|
808
872
|
afterDelays: [],
|
|
809
873
|
afterTargets: [],
|
|
874
|
+
afterListDelays: [],
|
|
875
|
+
afterListTargets: [],
|
|
810
876
|
always: [],
|
|
811
877
|
pathIndex: {},
|
|
812
878
|
};
|
|
@@ -865,15 +931,16 @@ interface Registered<Ctx, E extends EventObject> {
|
|
|
865
931
|
|
|
866
932
|
const registered: { [key: string]: Registered<unknown, EventObject> } = {};
|
|
867
933
|
|
|
868
|
-
// Adds the fields the root's privateCtx returns to ctx
|
|
869
|
-
// has keeps its value: a hot reload adds new private fields and
|
|
934
|
+
// Adds the fields the root's privateCtx returns to ctx; the factory receives target. With
|
|
935
|
+
// onlyMissing, a field ctx already has keeps its value: a hot reload adds new private fields and
|
|
936
|
+
// leaves the old ones alone.
|
|
870
937
|
function fillPrivate(
|
|
871
938
|
target: { [name: string]: unknown },
|
|
872
939
|
root: unknown,
|
|
873
940
|
onlyMissing: boolean,
|
|
874
941
|
): void {
|
|
875
|
-
const factory = (root as
|
|
876
|
-
const fields = (factory?.() || {}) as { [name: string]: unknown };
|
|
942
|
+
const factory = (root as PrivateCtxConfig<unknown, object>).privateCtx;
|
|
943
|
+
const fields = (factory?.(target) || {}) as { [name: string]: unknown };
|
|
877
944
|
for (const name in fields) {
|
|
878
945
|
if (!onlyMissing || target[name] === undefined) {
|
|
879
946
|
target[name] = fields[name];
|
|
@@ -899,11 +966,16 @@ function createMachine<Ctx, E extends EventObject>(
|
|
|
899
966
|
let transitions = compiled.transitions;
|
|
900
967
|
let afterDelays = compiled.afterDelays;
|
|
901
968
|
let afterTargets = compiled.afterTargets;
|
|
969
|
+
let afterListDelays = compiled.afterListDelays;
|
|
970
|
+
let afterListTargets = compiled.afterListTargets;
|
|
902
971
|
let always = compiled.always;
|
|
903
972
|
const activeChild: number[] = [];
|
|
904
973
|
const isActive: boolean[] = [];
|
|
905
974
|
const elapsed: number[] = [];
|
|
906
975
|
const fired: number[] = [];
|
|
976
|
+
// The sorted timers of a state whose after list holds a delay function, read on each entry.
|
|
977
|
+
const ownDelays: (number[] | undefined)[] = [];
|
|
978
|
+
const ownTargets: (number[] | undefined)[] = [];
|
|
907
979
|
const entryId: number[] = [];
|
|
908
980
|
const cleanup: ((() => void) | undefined)[] = [];
|
|
909
981
|
// Keyed by path, not index, so a remembered child outlives a rebind's recompilation.
|
|
@@ -937,10 +1009,32 @@ function createMachine<Ctx, E extends EventObject>(
|
|
|
937
1009
|
isActive[state] = false;
|
|
938
1010
|
elapsed[state] = 0;
|
|
939
1011
|
fired[state] = 0;
|
|
1012
|
+
ownDelays[state] = undefined;
|
|
1013
|
+
ownTargets[state] = undefined;
|
|
940
1014
|
entryId[state] = 0;
|
|
941
1015
|
cleanup[state] = undefined;
|
|
942
1016
|
}
|
|
943
1017
|
|
|
1018
|
+
function readDelays(state: number): void {
|
|
1019
|
+
const list = afterListDelays[state];
|
|
1020
|
+
if (list === undefined) {
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
const listTargets = afterListTargets[state] as number[];
|
|
1024
|
+
const delays: number[] = [];
|
|
1025
|
+
const targets: number[] = [];
|
|
1026
|
+
for (let i = 0; i < list.length; i++) {
|
|
1027
|
+
let delay = list[i] as Delay<Ctx>;
|
|
1028
|
+
if (typeof delay === "function") {
|
|
1029
|
+
delay = delay(ctx);
|
|
1030
|
+
checkDelay(paths[state] as string, delay, delay);
|
|
1031
|
+
}
|
|
1032
|
+
insertTimer(delays, targets, delay, listTargets[i] as number);
|
|
1033
|
+
}
|
|
1034
|
+
ownDelays[state] = delays;
|
|
1035
|
+
ownTargets[state] = targets;
|
|
1036
|
+
}
|
|
1037
|
+
|
|
944
1038
|
function growSlots(): void {
|
|
945
1039
|
while (slotCount < compiled.count) {
|
|
946
1040
|
clearSlot(slotCount);
|
|
@@ -995,6 +1089,8 @@ function createMachine<Ctx, E extends EventObject>(
|
|
|
995
1089
|
transitions = compiled.transitions;
|
|
996
1090
|
afterDelays = compiled.afterDelays;
|
|
997
1091
|
afterTargets = compiled.afterTargets;
|
|
1092
|
+
afterListDelays = compiled.afterListDelays;
|
|
1093
|
+
afterListTargets = compiled.afterListTargets;
|
|
998
1094
|
always = compiled.always;
|
|
999
1095
|
fillPrivate(ctx as never, configs[ROOT], true);
|
|
1000
1096
|
growSlots();
|
|
@@ -1019,6 +1115,7 @@ function createMachine<Ctx, E extends EventObject>(
|
|
|
1019
1115
|
fired[state] = keptFired[i] as number;
|
|
1020
1116
|
entryId[state] = keptEntryId[i] as number;
|
|
1021
1117
|
cleanup[state] = keptCleanup[i];
|
|
1118
|
+
readDelays(state);
|
|
1022
1119
|
}
|
|
1023
1120
|
for (let i = dropped.length - 1; i >= 0; i--) {
|
|
1024
1121
|
const run = keptCleanup[dropped[i] as number];
|
|
@@ -1121,6 +1218,7 @@ function createMachine<Ctx, E extends EventObject>(
|
|
|
1121
1218
|
if (hook !== undefined) {
|
|
1122
1219
|
hook(ctx, instance);
|
|
1123
1220
|
}
|
|
1221
|
+
readDelays(state);
|
|
1124
1222
|
const task = config.task;
|
|
1125
1223
|
if (task !== undefined) {
|
|
1126
1224
|
// A path, not an index, so a finish held across a rebind still finds its entry.
|
|
@@ -1518,17 +1616,12 @@ function createMachine<Ctx, E extends EventObject>(
|
|
|
1518
1616
|
if (child !== NO_STATE && fireTimers(child)) {
|
|
1519
1617
|
return true;
|
|
1520
1618
|
}
|
|
1521
|
-
const delays = afterDelays[state] as number[];
|
|
1619
|
+
const delays = ownDelays[state] || (afterDelays[state] as number[]);
|
|
1522
1620
|
const next = fired[state] as number;
|
|
1523
1621
|
if (next < delays.length && (delays[next] as number) <= (elapsed[state] as number)) {
|
|
1524
1622
|
fired[state] = next + 1;
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
(afterTargets[state] as number[])[next] as number,
|
|
1528
|
-
undefined,
|
|
1529
|
-
undefined,
|
|
1530
|
-
"after",
|
|
1531
|
-
);
|
|
1623
|
+
const targets = ownTargets[state] || (afterTargets[state] as number[]);
|
|
1624
|
+
transition(state, targets[next] as number, undefined, undefined, "after");
|
|
1532
1625
|
return true;
|
|
1533
1626
|
}
|
|
1534
1627
|
return false;
|
|
@@ -1603,6 +1696,11 @@ function define<Ctx, E extends EventObject>(
|
|
|
1603
1696
|
return machine;
|
|
1604
1697
|
}
|
|
1605
1698
|
|
|
1699
|
+
// Ctx for a config without privateCtx, Ctx & Priv for one with it. The tuple test keeps the
|
|
1700
|
+
// context whole while Priv has no inference yet, so a generic helper in a slot, such as
|
|
1701
|
+
// `sequence`, still takes its ctx from the slot.
|
|
1702
|
+
type WithPrivate<Ctx, Priv> = [Priv] extends [never] ? Ctx : Ctx & Priv;
|
|
1703
|
+
|
|
1606
1704
|
// Ctx and E are given explicitly and the config is inferred, so the config takes a second call.
|
|
1607
1705
|
// A key makes a later definition under it (a hot-reloaded module re-running) rebind live instances.
|
|
1608
1706
|
/**
|
|
@@ -1611,23 +1709,33 @@ function define<Ctx, E extends EventObject>(
|
|
|
1611
1709
|
* when that call runs, and a mistake throws, naming the state path. With a key, a later
|
|
1612
1710
|
* definition under it puts its states into the machine already built, so every running instance
|
|
1613
1711
|
* sees the edit; without one, each call builds a separate machine.
|
|
1712
|
+
*
|
|
1713
|
+
* The root config may hold `privateCtx`, for a machine that keeps fields of its own, such as a
|
|
1714
|
+
* counter, a heat level or a list it builds up. Every hook, `when`, `run`, `task` and `update`
|
|
1715
|
+
* then sees those fields beside `Ctx`, while `start` takes only `Ctx` and the instance it returns
|
|
1716
|
+
* shows only `Ctx` in its `ctx`. A machine without fields of its own leaves `privateCtx` out.
|
|
1614
1717
|
*/
|
|
1615
1718
|
export function defineMachine<Ctx, E extends EventObject>(
|
|
1616
1719
|
key?: string,
|
|
1617
|
-
): <T
|
|
1720
|
+
): <T, Priv extends object = never>(
|
|
1721
|
+
config: RootState<WithPrivate<Ctx, Priv>, E, T> & PrivateCtxConfig<Ctx, Priv>,
|
|
1722
|
+
) => Machine<Ctx, E, TreePath<T>> {
|
|
1618
1723
|
return ((config: MachineConfig<Ctx, E>) => define(key, config)) as never;
|
|
1619
1724
|
}
|
|
1620
1725
|
|
|
1621
1726
|
/**
|
|
1622
|
-
* The key a `
|
|
1727
|
+
* The optional key a `defineMachine` root config takes beside `initial` and `states`.
|
|
1623
1728
|
* @noSelf
|
|
1624
1729
|
*/
|
|
1625
1730
|
export interface PrivateCtxConfig<Ctx, Priv> {
|
|
1626
1731
|
/**
|
|
1627
1732
|
* Builds the fields only the machine uses. It runs once per `start`, before the root's `enter`
|
|
1628
1733
|
* hook, so every instance gets fresh fields of its own; a value written straight into the
|
|
1629
|
-
* config would be one array or table shared by every instance. It
|
|
1630
|
-
* a private field from a field of `Ctx
|
|
1734
|
+
* config would be one array or table shared by every instance. It receives the object passed
|
|
1735
|
+
* to `start`, typed `Ctx`, so a private field can start from a field of `Ctx`:
|
|
1736
|
+
* `privateCtx: (ctx: TurretOptions) => ({ heat: ctx.startHeat })`. Give `ctx` its type; without
|
|
1737
|
+
* one TypeScript can type a hook before it knows the private fields, and that hook reports the
|
|
1738
|
+
* field as missing.
|
|
1631
1739
|
*
|
|
1632
1740
|
* The fields are added to the object passed to `start`, which stays the instance's `ctx`, so
|
|
1633
1741
|
* restarting with `start(old.ctx)` keeps the public fields and builds the private ones fresh.
|
|
@@ -1637,94 +1745,81 @@ export interface PrivateCtxConfig<Ctx, Priv> {
|
|
|
1637
1745
|
*
|
|
1638
1746
|
* The fields are hidden from callers by type only. At run time `ctx` is one table, and hooks,
|
|
1639
1747
|
* `hsm-view` and a debugger see every field.
|
|
1748
|
+
*
|
|
1749
|
+
* There are two ways to type the fields.
|
|
1750
|
+
*
|
|
1751
|
+
* @example
|
|
1752
|
+
* **Inferred.** For a few fields, write them inline and let TypeScript infer their types. Add
|
|
1753
|
+
* `as` where the inferred type is wrong: an empty array is `never[]` until it reads
|
|
1754
|
+
* `[] as Hash[]`, and a field holding one of several strings is any `string` until it reads
|
|
1755
|
+
* `"cool" as "cool" | "hot"`.
|
|
1756
|
+
*
|
|
1757
|
+
* ```ts
|
|
1758
|
+
* type TurretEvent = { type: "SPOTTED" } | { type: "LOST" };
|
|
1759
|
+
*
|
|
1760
|
+
* export const turret = defineMachine<{ readonly fireRate: number }, TurretEvent>("turret")({
|
|
1761
|
+
* privateCtx: () => ({ heat: 0, targets: [] as Hash[] }),
|
|
1762
|
+
* initial: "/idle",
|
|
1763
|
+
* states: {
|
|
1764
|
+
* idle: { on: { SPOTTED: "/firing" } },
|
|
1765
|
+
* firing: {
|
|
1766
|
+
* enter: (ctx) => {
|
|
1767
|
+
* ctx.heat += 1 / ctx.fireRate;
|
|
1768
|
+
* },
|
|
1769
|
+
* on: { LOST: "/idle" },
|
|
1770
|
+
* },
|
|
1771
|
+
* },
|
|
1772
|
+
* });
|
|
1773
|
+
*
|
|
1774
|
+
* const gun = turret.start({ fireRate: 2 }); // gun.ctx shows fireRate only
|
|
1775
|
+
* ```
|
|
1776
|
+
*
|
|
1777
|
+
* @example
|
|
1778
|
+
* **A named type.** For more fields, or when a helper outside the config needs the whole
|
|
1779
|
+
* context, declare the fields as an interface and make it the factory's return type. The
|
|
1780
|
+
* fields need no `as`, a misspelled or unknown field is a compile error, and the whole context
|
|
1781
|
+
* is `TurretOptions & TurretPrivate`. `satisfies TurretPrivate` does not do this: it checks the
|
|
1782
|
+
* object but keeps its narrow types, so an empty array stays `never[]`.
|
|
1783
|
+
*
|
|
1784
|
+
* ```ts
|
|
1785
|
+
* interface TurretOptions {
|
|
1786
|
+
* readonly fireRate: number;
|
|
1787
|
+
* }
|
|
1788
|
+
*
|
|
1789
|
+
* interface TurretPrivate {
|
|
1790
|
+
* heat: number;
|
|
1791
|
+
* mode: "cool" | "hot";
|
|
1792
|
+
* }
|
|
1793
|
+
*
|
|
1794
|
+
* type TurretEvent = { type: "SPOTTED" } | { type: "LOST" };
|
|
1795
|
+
*
|
|
1796
|
+
* const overheated = (ctx: TurretOptions & TurretPrivate) => ctx.heat > ctx.fireRate;
|
|
1797
|
+
*
|
|
1798
|
+
* export const turret = defineMachine<TurretOptions, TurretEvent>("turret")({
|
|
1799
|
+
* privateCtx: (): TurretPrivate => ({ heat: 0, mode: "cool" }),
|
|
1800
|
+
* initial: "/idle",
|
|
1801
|
+
* states: {
|
|
1802
|
+
* idle: { on: { SPOTTED: "/firing" } },
|
|
1803
|
+
* firing: {
|
|
1804
|
+
* enter: (ctx) => {
|
|
1805
|
+
* ctx.heat += 1 / ctx.fireRate;
|
|
1806
|
+
* },
|
|
1807
|
+
* always: { to: "/cooling", when: overheated },
|
|
1808
|
+
* on: { LOST: "/idle" },
|
|
1809
|
+
* },
|
|
1810
|
+
* cooling: {
|
|
1811
|
+
* enter: (ctx) => {
|
|
1812
|
+
* ctx.mode = "hot";
|
|
1813
|
+
* },
|
|
1814
|
+
* exit: (ctx) => {
|
|
1815
|
+
* ctx.heat = 0;
|
|
1816
|
+
* ctx.mode = "cool";
|
|
1817
|
+
* },
|
|
1818
|
+
* after: { 2: "/idle" },
|
|
1819
|
+
* },
|
|
1820
|
+
* },
|
|
1821
|
+
* });
|
|
1822
|
+
* ```
|
|
1640
1823
|
*/
|
|
1641
|
-
readonly privateCtx:
|
|
1824
|
+
readonly privateCtx?: (ctx: Ctx) => Priv & { readonly [K in keyof Ctx & keyof Priv]: never };
|
|
1642
1825
|
}
|
|
1643
|
-
|
|
1644
|
-
/**
|
|
1645
|
-
* Like `defineMachine`, for a machine that keeps fields of its own, such as a counter, a heat
|
|
1646
|
-
* level or a list it builds up. The config's `privateCtx` returns those fields. Every hook,
|
|
1647
|
-
* `when`, `run`, `task` and `update` sees them beside `Ctx`, while `start` takes only `Ctx` and
|
|
1648
|
-
* the instance it returns shows only `Ctx` in its `ctx`. A machine without fields of its own
|
|
1649
|
-
* uses `defineMachine`.
|
|
1650
|
-
*
|
|
1651
|
-
* There are two ways to type the fields.
|
|
1652
|
-
*
|
|
1653
|
-
* @example
|
|
1654
|
-
* **Inferred.** For a few fields, write them inline and let TypeScript infer their types. Add
|
|
1655
|
-
* `as` where the inferred type is wrong: an empty array is `never[]` until it reads
|
|
1656
|
-
* `[] as Hash[]`, and a field holding one of several strings is any `string` until it reads
|
|
1657
|
-
* `"cool" as "cool" | "hot"`.
|
|
1658
|
-
*
|
|
1659
|
-
* ```ts
|
|
1660
|
-
* type TurretEvent = { type: "SPOTTED" } | { type: "LOST" };
|
|
1661
|
-
*
|
|
1662
|
-
* export const turret = definePrivateMachine<{ readonly fireRate: number }, TurretEvent>("turret")({
|
|
1663
|
-
* privateCtx: () => ({ heat: 0, targets: [] as Hash[] }),
|
|
1664
|
-
* initial: "/idle",
|
|
1665
|
-
* states: {
|
|
1666
|
-
* idle: { on: { SPOTTED: "/firing" } },
|
|
1667
|
-
* firing: {
|
|
1668
|
-
* enter: (ctx) => {
|
|
1669
|
-
* ctx.heat += 1 / ctx.fireRate;
|
|
1670
|
-
* },
|
|
1671
|
-
* on: { LOST: "/idle" },
|
|
1672
|
-
* },
|
|
1673
|
-
* },
|
|
1674
|
-
* });
|
|
1675
|
-
*
|
|
1676
|
-
* const gun = turret.start({ fireRate: 2 }); // gun.ctx shows fireRate only
|
|
1677
|
-
* ```
|
|
1678
|
-
*
|
|
1679
|
-
* @example
|
|
1680
|
-
* **A named type.** For more fields, or when a helper outside the config needs the whole
|
|
1681
|
-
* context, declare the fields as an interface and make it the factory's return type. The fields
|
|
1682
|
-
* need no `as`, a misspelled or unknown field is a compile error, and the whole context is
|
|
1683
|
-
* `TurretOptions & TurretPrivate`. `satisfies TurretPrivate` does not do this: it checks the
|
|
1684
|
-
* object but keeps its narrow types, so an empty array stays `never[]`.
|
|
1685
|
-
*
|
|
1686
|
-
* ```ts
|
|
1687
|
-
* interface TurretOptions {
|
|
1688
|
-
* readonly fireRate: number;
|
|
1689
|
-
* }
|
|
1690
|
-
*
|
|
1691
|
-
* interface TurretPrivate {
|
|
1692
|
-
* heat: number;
|
|
1693
|
-
* mode: "cool" | "hot";
|
|
1694
|
-
* }
|
|
1695
|
-
*
|
|
1696
|
-
* type TurretEvent = { type: "SPOTTED" } | { type: "LOST" };
|
|
1697
|
-
*
|
|
1698
|
-
* const overheated = (ctx: TurretOptions & TurretPrivate) => ctx.heat > ctx.fireRate;
|
|
1699
|
-
*
|
|
1700
|
-
* export const turret = definePrivateMachine<TurretOptions, TurretEvent>("turret")({
|
|
1701
|
-
* privateCtx: (): TurretPrivate => ({ heat: 0, mode: "cool" }),
|
|
1702
|
-
* initial: "/idle",
|
|
1703
|
-
* states: {
|
|
1704
|
-
* idle: { on: { SPOTTED: "/firing" } },
|
|
1705
|
-
* firing: {
|
|
1706
|
-
* enter: (ctx) => {
|
|
1707
|
-
* ctx.heat += 1 / ctx.fireRate;
|
|
1708
|
-
* },
|
|
1709
|
-
* always: { to: "/cooling", when: overheated },
|
|
1710
|
-
* on: { LOST: "/idle" },
|
|
1711
|
-
* },
|
|
1712
|
-
* cooling: {
|
|
1713
|
-
* enter: (ctx) => {
|
|
1714
|
-
* ctx.mode = "hot";
|
|
1715
|
-
* },
|
|
1716
|
-
* exit: (ctx) => {
|
|
1717
|
-
* ctx.heat = 0;
|
|
1718
|
-
* ctx.mode = "cool";
|
|
1719
|
-
* },
|
|
1720
|
-
* after: { 2: "/idle" },
|
|
1721
|
-
* },
|
|
1722
|
-
* },
|
|
1723
|
-
* });
|
|
1724
|
-
* ```
|
|
1725
|
-
*/
|
|
1726
|
-
export const definePrivateMachine: <Ctx, E extends EventObject>(
|
|
1727
|
-
key?: string,
|
|
1728
|
-
) => <T, Priv extends object>(
|
|
1729
|
-
config: RootState<Ctx & Priv, E, T> & PrivateCtxConfig<Ctx, Priv>,
|
|
1730
|
-
) => Machine<Ctx, E, TreePath<T>> = defineMachine as never;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@defold-typescript/transpiler",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.48.0",
|
|
4
4
|
"description": "TypeScript-to-Lua build pipeline tuned for Defold's runtime.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"test": "bun test"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@defold-typescript/types": "0.
|
|
38
|
+
"@defold-typescript/types": "0.48.0",
|
|
39
39
|
"@typescript-to-lua/language-extensions": "1.19.0",
|
|
40
40
|
"typescript": "6.0.2",
|
|
41
41
|
"typescript-to-lua": "^1.36.0"
|