@typeonce/ritmo 0.6.0 → 0.8.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/README.md +2 -0
- package/dist/Brand.d.ts +9 -4
- package/dist/Brand.d.ts.map +1 -1
- package/dist/Brand.js +9 -4
- package/dist/Brand.js.map +1 -1
- package/dist/Clock.d.ts +186 -0
- package/dist/Clock.d.ts.map +1 -0
- package/dist/Clock.js +111 -0
- package/dist/Clock.js.map +1 -0
- package/dist/Command.d.ts +250 -41
- package/dist/Command.d.ts.map +1 -1
- package/dist/Command.js +59 -23
- package/dist/Command.js.map +1 -1
- package/dist/Condition.d.ts +12 -1
- package/dist/Condition.d.ts.map +1 -1
- package/dist/Condition.js.map +1 -1
- package/dist/Debug.d.ts +163 -15
- package/dist/Debug.d.ts.map +1 -1
- package/dist/Debug.js.map +1 -1
- package/dist/Decode.d.ts +1 -1
- package/dist/Decode.js +1 -1
- package/dist/Descriptor.d.ts +67 -24
- package/dist/Descriptor.d.ts.map +1 -1
- package/dist/Descriptor.js +32 -15
- package/dist/Descriptor.js.map +1 -1
- package/dist/Entity.d.ts +22 -5
- package/dist/Entity.d.ts.map +1 -1
- package/dist/Entity.js +2 -2
- package/dist/Entity.js.map +1 -1
- package/dist/EntityScope.d.ts +26 -1
- package/dist/EntityScope.d.ts.map +1 -1
- package/dist/EntityScope.js +14 -2
- package/dist/EntityScope.js.map +1 -1
- package/dist/HostClock.d.ts +49 -0
- package/dist/HostClock.d.ts.map +1 -0
- package/dist/HostClock.js +46 -0
- package/dist/HostClock.js.map +1 -0
- package/dist/Inspector.d.ts +7 -1
- package/dist/Inspector.d.ts.map +1 -1
- package/dist/Inspector.js.map +1 -1
- package/dist/Machine.d.ts +67 -36
- package/dist/Machine.d.ts.map +1 -1
- package/dist/Machine.js +10 -3
- package/dist/Machine.js.map +1 -1
- package/dist/Query.d.ts +74 -26
- package/dist/Query.d.ts.map +1 -1
- package/dist/Query.js +32 -28
- package/dist/Query.js.map +1 -1
- package/dist/Runtime.d.ts +39 -16
- package/dist/Runtime.d.ts.map +1 -1
- package/dist/Runtime.js +556 -87
- package/dist/Runtime.js.map +1 -1
- package/dist/Schedule.d.ts +217 -10
- package/dist/Schedule.d.ts.map +1 -1
- package/dist/Schedule.js +5 -0
- package/dist/Schedule.js.map +1 -1
- package/dist/Schema.d.ts +196 -44
- package/dist/Schema.d.ts.map +1 -1
- package/dist/Schema.js +29 -11
- package/dist/Schema.js.map +1 -1
- package/dist/Snapshot.d.ts +14 -0
- package/dist/Snapshot.d.ts.map +1 -1
- package/dist/Snapshot.js.map +1 -1
- package/dist/System.d.ts +212 -58
- package/dist/System.d.ts.map +1 -1
- package/dist/System.js +84 -6
- package/dist/System.js.map +1 -1
- package/dist/index.d.ts +8 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -7
- package/dist/index.js.map +1 -1
- package/dist/internal/cells.d.ts +3 -3
- package/dist/internal/cells.d.ts.map +1 -1
- package/dist/internal/cells.js +9 -11
- package/dist/internal/cells.js.map +1 -1
- package/dist/internal/debug.d.ts +13 -0
- package/dist/internal/debug.d.ts.map +1 -1
- package/dist/internal/debug.js +192 -7
- package/dist/internal/debug.js.map +1 -1
- package/dist/internal/equal.d.ts +8 -0
- package/dist/internal/equal.d.ts.map +1 -0
- package/dist/internal/equal.js +38 -0
- package/dist/internal/equal.js.map +1 -0
- package/dist/internal/fragments.d.ts.map +1 -1
- package/dist/internal/fragments.js +11 -1
- package/dist/internal/fragments.js.map +1 -1
- package/dist/internal/game.d.ts.map +1 -1
- package/dist/internal/game.js +41 -6
- package/dist/internal/game.js.map +1 -1
- package/dist/internal/order.d.ts +10 -0
- package/dist/internal/order.d.ts.map +1 -0
- package/dist/internal/order.js +122 -0
- package/dist/internal/order.js.map +1 -0
- package/dist/internal/queries.d.ts.map +1 -1
- package/dist/internal/queries.js +32 -10
- package/dist/internal/queries.js.map +1 -1
- package/dist/internal/requirements.d.ts +26 -0
- package/dist/internal/requirements.d.ts.map +1 -0
- package/dist/internal/requirements.js +46 -0
- package/dist/internal/requirements.js.map +1 -0
- package/package.json +1 -1
- package/src/Brand.ts +9 -4
- package/src/Clock.ts +311 -0
- package/src/Command.ts +407 -89
- package/src/Condition.ts +17 -1
- package/src/Debug.ts +172 -14
- package/src/Decode.ts +1 -1
- package/src/Descriptor.ts +92 -37
- package/src/Entity.ts +24 -5
- package/src/EntityScope.ts +27 -2
- package/src/HostClock.ts +64 -0
- package/src/Inspector.ts +7 -2
- package/src/Machine.ts +81 -37
- package/src/Query.ts +110 -38
- package/src/Runtime.ts +607 -111
- package/src/Schedule.ts +380 -13
- package/src/Schema.ts +249 -63
- package/src/Snapshot.ts +4 -0
- package/src/System.ts +248 -93
- package/src/index.ts +8 -7
- package/src/internal/cells.ts +10 -12
- package/src/internal/debug.ts +208 -12
- package/src/internal/equal.ts +31 -0
- package/src/internal/fragments.ts +12 -2
- package/src/internal/game.ts +61 -7
- package/src/internal/order.ts +144 -0
- package/src/internal/queries.ts +37 -13
- package/src/internal/requirements.ts +71 -0
- package/dist/Definition.d.ts +0 -99
- package/dist/Definition.d.ts.map +0 -1
- package/dist/Definition.js +0 -68
- package/dist/Definition.js.map +0 -1
- package/dist/Feature.d.ts +0 -106
- package/dist/Feature.d.ts.map +0 -1
- package/dist/Feature.js +0 -87
- package/dist/Feature.js.map +0 -1
- package/dist/Fx.d.ts +0 -128
- package/dist/Fx.d.ts.map +0 -1
- package/dist/Fx.js +0 -112
- package/dist/Fx.js.map +0 -1
- package/dist/internal/definition.d.ts +0 -17
- package/dist/internal/definition.d.ts.map +0 -1
- package/dist/internal/definition.js +0 -25
- package/dist/internal/definition.js.map +0 -1
- package/src/Definition.ts +0 -106
- package/src/Feature.ts +0 -241
- package/src/Fx.ts +0 -176
- package/src/internal/definition.ts +0 -56
package/src/System.ts
CHANGED
|
@@ -57,7 +57,6 @@
|
|
|
57
57
|
*/
|
|
58
58
|
import type { Descriptor } from "./Descriptor.ts"
|
|
59
59
|
import type * as Entity from "./Entity.ts"
|
|
60
|
-
import type { Fx } from "./Fx.ts"
|
|
61
60
|
import * as Machine from "./Machine.ts"
|
|
62
61
|
import type * as Relation from "./Relation.ts"
|
|
63
62
|
import type { Query, QueryMatch } from "./Query.ts"
|
|
@@ -65,7 +64,7 @@ import type { ConstructedWriteCell, ReadCell, ReadonlyValue, WriteCell } from ".
|
|
|
65
64
|
import * as Requirement from "./Requirement.ts"
|
|
66
65
|
import type * as Result from "./Result.ts"
|
|
67
66
|
import type { Schema } from "./Schema.ts"
|
|
68
|
-
import type { CommandsApi } from "./Command.ts"
|
|
67
|
+
import type { CommandAccess, CommandsApi, NotDeclared } from "./Command.ts"
|
|
69
68
|
|
|
70
69
|
/**
|
|
71
70
|
* System declarations, typed execution context, and runtime requirements.
|
|
@@ -194,6 +193,50 @@ export const writeResource = <D extends Descriptor<"resource", string, any>>(
|
|
|
194
193
|
descriptor
|
|
195
194
|
})
|
|
196
195
|
|
|
196
|
+
/**
|
|
197
|
+
* Declares read access to a resource plus whether it changed since this
|
|
198
|
+
* system's previous completed run.
|
|
199
|
+
*/
|
|
200
|
+
export type ResourceChanged<D extends Descriptor<"resource", string, any>> = {
|
|
201
|
+
readonly mode: "changed"
|
|
202
|
+
readonly descriptor: D
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Creates a resource read that also reports `changed()`: whether a committed
|
|
207
|
+
* write left a different value since this system's previous completed run.
|
|
208
|
+
*
|
|
209
|
+
* Use it for work worth doing only when a value moved: rewriting a HUD label,
|
|
210
|
+
* rebuilding a cache from settings. Use `readResource` when the system needs
|
|
211
|
+
* the value every run anyway.
|
|
212
|
+
*
|
|
213
|
+
* A write that leaves an equal value is not a change: primitives compare
|
|
214
|
+
* with `Object.is`, plain objects and arrays by their entries, other objects
|
|
215
|
+
* by reference. This differs from `Game.Query.changed`, where every write
|
|
216
|
+
* through a component write cell counts. The first run reports `true`; a
|
|
217
|
+
* failed run (rolled back) is not a change, and a failed reader sees the same
|
|
218
|
+
* change again next run. Its own writes are not reported to the writer.
|
|
219
|
+
*
|
|
220
|
+
* Conditions (`Game.Condition.check`) cannot declare it: they own no cursor.
|
|
221
|
+
*
|
|
222
|
+
* @example
|
|
223
|
+
* ```ts
|
|
224
|
+
* const SyncHud = Game.System("SyncHud", {
|
|
225
|
+
* resources: { score: Game.System.changedResource(Score) },
|
|
226
|
+
* services: { hud: Game.System.service(Hud) }
|
|
227
|
+
* }, ({ resources, services }) => {
|
|
228
|
+
* if (!resources.score.changed()) return
|
|
229
|
+
* services.hud.score.textContent = `${resources.score.get()}`
|
|
230
|
+
* })
|
|
231
|
+
* ```
|
|
232
|
+
*/
|
|
233
|
+
export const changedResource = <D extends Descriptor<"resource", string, any>>(
|
|
234
|
+
descriptor: D
|
|
235
|
+
): ResourceChanged<D> => ({
|
|
236
|
+
mode: "changed",
|
|
237
|
+
descriptor
|
|
238
|
+
})
|
|
239
|
+
|
|
197
240
|
/**
|
|
198
241
|
* Declares read-only access to an event stream.
|
|
199
242
|
*/
|
|
@@ -246,7 +289,7 @@ export const readEvent = <D extends Descriptor<"event", string, any>>(
|
|
|
246
289
|
*
|
|
247
290
|
* If the payload needs to name an entity for later work, emit a durable
|
|
248
291
|
* `Game.Entity.handle(...)` (optionally with an intent component) and let the later
|
|
249
|
-
* reader re-resolve it through `
|
|
292
|
+
* reader re-resolve it through `queries.x.getHandle(...)` (through a declared query).
|
|
250
293
|
*
|
|
251
294
|
* @example
|
|
252
295
|
* ```ts
|
|
@@ -266,6 +309,58 @@ export const writeEvent = <D extends Descriptor<"event", string, any>>(
|
|
|
266
309
|
descriptor
|
|
267
310
|
})
|
|
268
311
|
|
|
312
|
+
/** The marker `thisTick(...)` adds to a read. */
|
|
313
|
+
export interface ThisTick {
|
|
314
|
+
readonly thisTick: true
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Requires that every system writing this event or resource runs earlier in
|
|
319
|
+
* the same `runtime.tick(...)` call than the reading system: the reader then
|
|
320
|
+
* sees this tick's data, never last tick's. The library never reorders
|
|
321
|
+
* anything; it only checks.
|
|
322
|
+
*
|
|
323
|
+
* `runtime.tick(...)` checks it at compile time over everything passed to that
|
|
324
|
+
* call, in order (gated groups count by position), and rejects a writer that
|
|
325
|
+
* runs after the reader and a call whose order is not statically known. A
|
|
326
|
+
* read with no writer in the call compiles (a reader reused in several
|
|
327
|
+
* ticks); the `event-never-written` lint reports data nobody writes.
|
|
328
|
+
* `tryTick` checks the same at runtime.
|
|
329
|
+
*
|
|
330
|
+
* Use it on reads whose correctness depends on running after their writers
|
|
331
|
+
* (damage applied the tick it is dealt). A plain read is right when last
|
|
332
|
+
* tick's data is acceptable.
|
|
333
|
+
*
|
|
334
|
+
* On a query (`queries: { enemies: Game.System.thisTick(Enemies) }`), the
|
|
335
|
+
* writers are systems whose declared commands can change what it matches:
|
|
336
|
+
* `spawns` covering every component it requires, or `inserts` / `removes` of
|
|
337
|
+
* any component it names (selection slots, optional ones included, `with`,
|
|
338
|
+
* `without`, filters). Each must run earlier with an `applyDeferred()` (or
|
|
339
|
+
* `applyStateTransitions(...)`) between it and the reader, since commands
|
|
340
|
+
* apply only there. `despawns` never count: a despawned entity stays visible
|
|
341
|
+
* to queries until the next `applyDeferred()`. Nested schedules are
|
|
342
|
+
* flattened, gated groups count by position, and a transition bundle counts
|
|
343
|
+
* at its `applyStateTransitions(...)` step (which applies queued commands
|
|
344
|
+
* first), as if every schedule in it runs, markers inside included.
|
|
345
|
+
*
|
|
346
|
+
* @example
|
|
347
|
+
* ```ts
|
|
348
|
+
* const ApplyDamage = Game.System("ApplyDamage", {
|
|
349
|
+
* events: { damage: Game.System.thisTick(Game.System.readEvent(Damage)) }
|
|
350
|
+
* }, ({ events }) => { ... })
|
|
351
|
+
*
|
|
352
|
+
* runtime.tick(Game.Schedule(DealDamage, ApplyDamage)) // compiles
|
|
353
|
+
* runtime.tick(Game.Schedule(ApplyDamage, DealDamage)) // "ApplyDamage needs Damage from this tick, but its writer DealDamage runs after it"
|
|
354
|
+
*
|
|
355
|
+
* const AimAtNewest = Game.System("AimAtNewest", { queries: { enemies: Game.System.thisTick(Enemies) } }, ...)
|
|
356
|
+
* runtime.tick(Game.Schedule(SpawnWave, Game.Schedule.applyDeferred(), AimAtNewest)) // compiles
|
|
357
|
+
* runtime.tick(Game.Schedule(SpawnWave, AimAtNewest)) // "AimAtNewest needs this tick's commands for queries.enemies, but SpawnWave queues them with no applyDeferred() in between"
|
|
358
|
+
* ```
|
|
359
|
+
*/
|
|
360
|
+
export const thisTick = <
|
|
361
|
+
Read extends EventRead<Descriptor<"event", string, any>> | ResourceRead<Descriptor<"resource", string, any>> | ResourceChanged<Descriptor<"resource", string, any>> | Query.Any<any>
|
|
362
|
+
>(read: Read): Read & ThisTick => ({ ...read, thisTick: true })
|
|
363
|
+
|
|
269
364
|
/**
|
|
270
365
|
* Declares read access to the current committed value of a finite-state machine.
|
|
271
366
|
*
|
|
@@ -307,7 +402,7 @@ export const transition = <M extends Machine.StateMachine.Any>(
|
|
|
307
402
|
* normal events: each run sees those published since its previous run.
|
|
308
403
|
*
|
|
309
404
|
* This is one of the clearest signs that the modeled value should be a machine
|
|
310
|
-
* rather than a
|
|
405
|
+
* rather than a resource or a `Descriptor.EntityState` component.
|
|
311
406
|
*/
|
|
312
407
|
export const readTransitionEvent = <M extends Machine.StateMachine.Any>(
|
|
313
408
|
stateMachine: M
|
|
@@ -316,7 +411,10 @@ export const readTransitionEvent = <M extends Machine.StateMachine.Any>(
|
|
|
316
411
|
/**
|
|
317
412
|
* Internal union for all supported resource access declarations.
|
|
318
413
|
*/
|
|
319
|
-
type ResourceAccess =
|
|
414
|
+
type ResourceAccess =
|
|
415
|
+
| ResourceRead<Descriptor<"resource", string, any>>
|
|
416
|
+
| ResourceChanged<Descriptor<"resource", string, any>>
|
|
417
|
+
| ResourceWrite<Descriptor<"resource", string, any>>
|
|
320
418
|
/**
|
|
321
419
|
* Internal union for all supported event access declarations.
|
|
322
420
|
*/
|
|
@@ -327,6 +425,15 @@ type EventAccess = EventRead<Descriptor<"event", string, any>> | EventWrite<Desc
|
|
|
327
425
|
*/
|
|
328
426
|
export interface ResourceReadView<T> extends ReadCell<T> {}
|
|
329
427
|
|
|
428
|
+
/**
|
|
429
|
+
* A read-only view over a resource value that also reports whether it
|
|
430
|
+
* changed since this system's previous completed run (`changedResource`).
|
|
431
|
+
*/
|
|
432
|
+
export interface ResourceChangedView<T> extends ReadCell<T> {
|
|
433
|
+
/** Whether a committed write left a different value since this system's previous completed run. `true` on the first run. */
|
|
434
|
+
changed(): boolean
|
|
435
|
+
}
|
|
436
|
+
|
|
330
437
|
/**
|
|
331
438
|
* A mutable view over a resource value.
|
|
332
439
|
*/
|
|
@@ -361,7 +468,8 @@ export interface EventWriteView<T> {
|
|
|
361
468
|
* A read-only view over one committed finite-state-machine value.
|
|
362
469
|
*/
|
|
363
470
|
export interface MachineReadView<M extends Machine.StateMachine.Any = Machine.StateMachine.Any> {
|
|
364
|
-
|
|
471
|
+
/** The committed value; `undefined` only for a sub-state whose parent is in another state. */
|
|
472
|
+
get(): Machine.CurrentValue<M>
|
|
365
473
|
is(value: Machine.StateMachine.Value<M>): boolean
|
|
366
474
|
}
|
|
367
475
|
|
|
@@ -370,20 +478,31 @@ export interface MachineReadView<M extends Machine.StateMachine.Any = Machine.St
|
|
|
370
478
|
*/
|
|
371
479
|
export interface NextMachineWriteView<M extends Machine.StateMachine.Any = Machine.StateMachine.Any> {
|
|
372
480
|
getPending(): Machine.StateMachine.Value<M> | undefined
|
|
373
|
-
|
|
374
|
-
|
|
481
|
+
/**
|
|
482
|
+
* Queues `value`, committed at the next `applyStateTransitions(...)`. For a
|
|
483
|
+
* sub-state it returns a `Result` that fails with `SubStateInactive` (and
|
|
484
|
+
* queues nothing) while the parent is in another state.
|
|
485
|
+
*/
|
|
486
|
+
set(value: Machine.StateMachine.Value<M>): Machine.SetResult<M>
|
|
487
|
+
/** Like `set`, but committing the current value again is skipped (no exit or enter schedules). */
|
|
488
|
+
setIfChanged(value: Machine.StateMachine.Value<M>): Machine.SetResult<M>
|
|
375
489
|
reset(): void
|
|
376
490
|
}
|
|
377
491
|
|
|
378
492
|
/**
|
|
379
493
|
* A read-only view over the last applied transition payload for one machine.
|
|
494
|
+
*
|
|
495
|
+
* Only meaningful inside transition schedules, so it is only given to
|
|
496
|
+
* systems that declare `transitions: { x: Game.System.transition(M) }`.
|
|
380
497
|
*/
|
|
381
498
|
export interface TransitionReadView<M extends Machine.StateMachine.Any = Machine.StateMachine.Any> {
|
|
382
499
|
get(): Machine.TransitionSnapshot<M>
|
|
383
500
|
}
|
|
384
501
|
|
|
385
502
|
/**
|
|
386
|
-
* A read-only event stream of committed machine transitions
|
|
503
|
+
* A read-only event stream of committed machine transitions, published by
|
|
504
|
+
* `applyStateTransitions(...)`. Like events, each reader sees the
|
|
505
|
+
* transitions published since its own previous run.
|
|
387
506
|
*/
|
|
388
507
|
export interface TransitionEventReadView<M extends Machine.StateMachine.Any = Machine.StateMachine.Any> {
|
|
389
508
|
all(): ReadonlyArray<Machine.TransitionSnapshot<M>>
|
|
@@ -430,8 +549,9 @@ export interface RelationFailureRead<R extends Relation.Relation.Any> {
|
|
|
430
549
|
* ticked less often than the one that removes (rendering after several fixed
|
|
431
550
|
* updates) still sees every removal. A system skipped by its run conditions
|
|
432
551
|
* keeps its position, like `added`/`changed`, and sees the removals when it
|
|
433
|
-
* runs again. Each log is capped at `Runtime.streamCapacity` entries;
|
|
434
|
-
*
|
|
552
|
+
* runs again. Each log is capped at `Runtime.streamCapacity` entries; a
|
|
553
|
+
* reader that missed dropped ones sees `lagged() === true` (and a `missed`
|
|
554
|
+
* read in the debug trace). Systems usually pair this
|
|
435
555
|
* with host cleanup such as removing renderer-owned nodes. {@link readDespawned}
|
|
436
556
|
* complements this for whole-entity teardown.
|
|
437
557
|
*
|
|
@@ -493,6 +613,14 @@ export const readDespawned = (): DespawnedRead => ({
|
|
|
493
613
|
*/
|
|
494
614
|
export interface RemovedReadView<S extends Schema.Any, Root = unknown> {
|
|
495
615
|
all(): ReadonlyArray<import("./Entity.ts").EntityId<S, Root>>
|
|
616
|
+
/**
|
|
617
|
+
* Whether records published since this system's previous run were dropped
|
|
618
|
+
* before it read them. Records wait for every reading system, so this only
|
|
619
|
+
* happens when a reader has not run for so long that the log exceeded its
|
|
620
|
+
* capacity (see `Runtime.streamCapacity`). Check it where a missed record
|
|
621
|
+
* would leak host state (a renderer node never destroyed).
|
|
622
|
+
*/
|
|
623
|
+
lagged(): boolean
|
|
496
624
|
}
|
|
497
625
|
|
|
498
626
|
/**
|
|
@@ -500,6 +628,14 @@ export interface RemovedReadView<S extends Schema.Any, Root = unknown> {
|
|
|
500
628
|
*/
|
|
501
629
|
export interface DespawnedReadView<S extends Schema.Any, Root = unknown> {
|
|
502
630
|
all(): ReadonlyArray<import("./Entity.ts").EntityId<S, Root>>
|
|
631
|
+
/**
|
|
632
|
+
* Whether records published since this system's previous run were dropped
|
|
633
|
+
* before it read them. Records wait for every reading system, so this only
|
|
634
|
+
* happens when a reader has not run for so long that the log exceeded its
|
|
635
|
+
* capacity (see `Runtime.streamCapacity`). Check it where a missed record
|
|
636
|
+
* would leak host state (a renderer node never destroyed).
|
|
637
|
+
*/
|
|
638
|
+
lagged(): boolean
|
|
503
639
|
}
|
|
504
640
|
|
|
505
641
|
/**
|
|
@@ -531,8 +667,46 @@ export interface QueryHandle<S extends Schema.Any, Q extends Query.Any> {
|
|
|
531
667
|
each(): ReadonlyArray<QueryMatch<S, Q>>
|
|
532
668
|
/**
|
|
533
669
|
* Retrieves the match for one specific entity id when it satisfies the query.
|
|
670
|
+
*
|
|
671
|
+
* Every read and write of an entity goes through a query the system
|
|
672
|
+
* declares, so its access is visible in the spec: there is no lookup that
|
|
673
|
+
* takes a query at call time.
|
|
534
674
|
*/
|
|
535
675
|
get(entityId: import("./Entity.ts").EntityId<S, Query.Root<Q>>): Result.Result<QueryMatch<S, Q>, Query.LookupError>
|
|
676
|
+
/**
|
|
677
|
+
* Resolves a stored durable handle back into this query's access.
|
|
678
|
+
*
|
|
679
|
+
* Use this for handles carried in events, resources, or components. A handle
|
|
680
|
+
* is storage-safe, not proof of liveness, so stale or mismatched handles stay
|
|
681
|
+
* explicit typed failures. A handle with an intent component needs a query
|
|
682
|
+
* that proves it (selects or requires it).
|
|
683
|
+
*/
|
|
684
|
+
getHandle<H extends Entity.Handle<Query.Root<Q>, any>>(
|
|
685
|
+
handle: [Entity.Handle.Intent<H>] extends [undefined]
|
|
686
|
+
? H
|
|
687
|
+
: Query.ProvesComponent<Q, Entity.Handle.Intent<H> & Descriptor<"component", string, any>> extends true
|
|
688
|
+
? H
|
|
689
|
+
: "this query does not prove the handle's intent component: select it, or add it to with"
|
|
690
|
+
): Result.Result<QueryMatch<S, Q>, Query.LookupError>
|
|
691
|
+
/**
|
|
692
|
+
* The direct children of one hierarchy parent that match this query, in the
|
|
693
|
+
* stored child order. Children that do not match are skipped; a missing
|
|
694
|
+
* parent is a `MissingEntity` failure.
|
|
695
|
+
*/
|
|
696
|
+
children<R extends Extract<Schema.Relations<S>[keyof Schema.Relations<S>], Relation.Relation.Hierarchy>>(
|
|
697
|
+
entityId: Entity.EntityId<S, Query.Root<Q>>,
|
|
698
|
+
relation: R
|
|
699
|
+
): Result.Result<ReadonlyArray<QueryMatch<S, Q>>, Relation.Relation.MissingEntityError>
|
|
700
|
+
/**
|
|
701
|
+
* The hierarchy descendants of one entity that match this query, in
|
|
702
|
+
* `options.order` (depth-first by default). Descendants that do not match
|
|
703
|
+
* are skipped. `lookup.descendants` returns every descendant's id instead.
|
|
704
|
+
*/
|
|
705
|
+
descendants<R extends Extract<Schema.Relations<S>[keyof Schema.Relations<S>], Relation.Relation.Hierarchy>>(
|
|
706
|
+
entityId: Entity.EntityId<S, Query.Root<Q>>,
|
|
707
|
+
relation: R,
|
|
708
|
+
options?: { readonly order?: "breadth" | "depth" }
|
|
709
|
+
): Result.Result<ReadonlyArray<QueryMatch<S, Q>>, Relation.Relation.MissingEntityError>
|
|
536
710
|
/**
|
|
537
711
|
* Returns a single match when exactly one entity satisfies the query.
|
|
538
712
|
*/
|
|
@@ -558,40 +732,19 @@ export interface QueryHandle<S extends Schema.Any, Q extends Query.Any> {
|
|
|
558
732
|
}
|
|
559
733
|
|
|
560
734
|
/**
|
|
561
|
-
*
|
|
735
|
+
* Relation lookups exposed to systems: entity ids along relation edges.
|
|
562
736
|
*
|
|
563
|
-
*
|
|
564
|
-
*
|
|
737
|
+
* Lookups return ids only. To read or write the entities they lead to, pass
|
|
738
|
+
* the ids to a declared query (`queries.x.get(id)`), or use the query
|
|
739
|
+
* handle's `children` / `descendants`, so every access stays in the spec.
|
|
565
740
|
*
|
|
566
741
|
* All lookup methods are total and explicit:
|
|
567
742
|
*
|
|
568
743
|
* - no hidden exceptions
|
|
569
|
-
* -
|
|
744
|
+
* - missing entities are normal typed failures
|
|
570
745
|
* - hierarchy traversal is available only for hierarchy relations
|
|
571
746
|
*/
|
|
572
747
|
export interface LookupApi<S extends Schema.Any, Root = unknown> {
|
|
573
|
-
get<Q extends Query.Any<Root>>(
|
|
574
|
-
entityId: Entity.EntityId<S, Root>,
|
|
575
|
-
query: Q
|
|
576
|
-
): Result.Result<QueryMatch<S, Q>, Query.LookupError>
|
|
577
|
-
/**
|
|
578
|
-
* Resolves a stored durable handle back into current-world query access.
|
|
579
|
-
*
|
|
580
|
-
* Use this for handles carried in events, resources, or components. A handle is
|
|
581
|
-
* storage-safe, not proof of liveness, so stale or mismatched handles remain
|
|
582
|
-
* explicit typed failures.
|
|
583
|
-
*/
|
|
584
|
-
getHandle<
|
|
585
|
-
H extends Entity.Handle<Root, any>,
|
|
586
|
-
Q extends Query.Any<Root>
|
|
587
|
-
>(
|
|
588
|
-
handle: [Entity.Handle.Intent<H>] extends [undefined]
|
|
589
|
-
? H
|
|
590
|
-
: Query.ProvesComponent<Q, Entity.Handle.Intent<H> & Descriptor<"component", string, any>> extends true
|
|
591
|
-
? H
|
|
592
|
-
: never,
|
|
593
|
-
query: Q
|
|
594
|
-
): Result.Result<QueryMatch<S, Q>, Query.LookupError>
|
|
595
748
|
related<R extends Extract<Schema.Relations<S>[keyof Schema.Relations<S>], Relation.Relation.Any>>(
|
|
596
749
|
entityId: Entity.EntityId<S, Root>,
|
|
597
750
|
relation: R
|
|
@@ -600,21 +753,6 @@ export interface LookupApi<S extends Schema.Any, Root = unknown> {
|
|
|
600
753
|
entityId: Entity.EntityId<S, Root>,
|
|
601
754
|
relation: R
|
|
602
755
|
): Result.Result<ReadonlyArray<Entity.EntityId<S, Root>>, Relation.Relation.MissingEntityError>
|
|
603
|
-
/**
|
|
604
|
-
* Reads the direct children of one hierarchy parent as typed query matches.
|
|
605
|
-
*
|
|
606
|
-
* This preserves the stored child order for that hierarchy relation and skips
|
|
607
|
-
* entities that do not satisfy the query. Missing parents remain explicit
|
|
608
|
-
* `MissingEntity` failures.
|
|
609
|
-
*/
|
|
610
|
-
childMatches<
|
|
611
|
-
R extends Extract<Schema.Relations<S>[keyof Schema.Relations<S>], Relation.Relation.Hierarchy>,
|
|
612
|
-
Q extends Query.Any<Root>
|
|
613
|
-
>(
|
|
614
|
-
entityId: Entity.EntityId<S, Root>,
|
|
615
|
-
relation: R,
|
|
616
|
-
query: Q
|
|
617
|
-
): Result.Result<ReadonlyArray<QueryMatch<S, Q>>, Relation.Relation.MissingEntityError>
|
|
618
756
|
parent<R extends Extract<Schema.Relations<S>[keyof Schema.Relations<S>], Relation.Relation.Hierarchy>>(
|
|
619
757
|
entityId: Entity.EntityId<S, Root>,
|
|
620
758
|
relation: R
|
|
@@ -630,24 +768,6 @@ export interface LookupApi<S extends Schema.Any, Root = unknown> {
|
|
|
630
768
|
readonly order?: "breadth" | "depth"
|
|
631
769
|
}
|
|
632
770
|
): Result.Result<ReadonlyArray<Entity.EntityId<S, Root>>, Relation.Relation.MissingEntityError>
|
|
633
|
-
/**
|
|
634
|
-
* Traverses hierarchy descendants and resolves only the entities that match
|
|
635
|
-
* the given query.
|
|
636
|
-
*
|
|
637
|
-
* Traversal order stays explicit through `options.order`, and non-matching
|
|
638
|
-
* descendants are skipped without turning traversal into a failure.
|
|
639
|
-
*/
|
|
640
|
-
descendantMatches<
|
|
641
|
-
R extends Extract<Schema.Relations<S>[keyof Schema.Relations<S>], Relation.Relation.Hierarchy>,
|
|
642
|
-
Q extends Query.Any<Root>
|
|
643
|
-
>(
|
|
644
|
-
entityId: Entity.EntityId<S, Root>,
|
|
645
|
-
relation: R,
|
|
646
|
-
query: Q,
|
|
647
|
-
options?: {
|
|
648
|
-
readonly order?: "breadth" | "depth"
|
|
649
|
-
}
|
|
650
|
-
): Result.Result<ReadonlyArray<QueryMatch<S, Q>>, Relation.Relation.MissingEntityError>
|
|
651
771
|
root<R extends Extract<Schema.Relations<S>[keyof Schema.Relations<S>], Relation.Relation.Hierarchy>>(
|
|
652
772
|
entityId: Entity.EntityId<S, Root>,
|
|
653
773
|
relation: R
|
|
@@ -686,6 +806,25 @@ export interface SystemAccessInput {
|
|
|
686
806
|
readonly removed?: Record<string, RemovedRead<Descriptor<"component", string, any>>>
|
|
687
807
|
readonly despawned?: Record<string, DespawnedRead>
|
|
688
808
|
readonly relationFailures?: Record<string, RelationFailureRead<Relation.Relation.Any>>
|
|
809
|
+
/**
|
|
810
|
+
* The commands the system may queue: `Game.System.commands(...)` with
|
|
811
|
+
* `spawns`, `inserts`, `removes`, `despawns`, `despawnsScope`, and
|
|
812
|
+
* `relates` permissions. Without it the system has no `commands`.
|
|
813
|
+
*/
|
|
814
|
+
readonly commands?: CommandAccess.Any
|
|
815
|
+
/**
|
|
816
|
+
* Run conditions that skip the system: while one fails, the system does not
|
|
817
|
+
* run and the events, transition events, and relation failures published
|
|
818
|
+
* meanwhile are dropped for it. Use it for a mode change after which those
|
|
819
|
+
* messages are stale (menu, game over); use `pauseWhen` instead for a freeze
|
|
820
|
+
* whose messages stay valid. `Schedule.when(...)` adds conditions to a whole
|
|
821
|
+
* group.
|
|
822
|
+
*
|
|
823
|
+
* Four gating forms, one rule: `when` drops the events published while
|
|
824
|
+
* gated (the system resumes on fresh messages), `pauseWhen` keeps them (the
|
|
825
|
+
* system resumes and reads the backlog). Put them on one system's spec, or
|
|
826
|
+
* on a group with `Schedule.when(...)` / `Schedule.pauseWhen(...)`.
|
|
827
|
+
*/
|
|
689
828
|
readonly when?: ReadonlyArray<Machine.Condition>
|
|
690
829
|
/**
|
|
691
830
|
* Run conditions that pause the system instead of skipping it: while one
|
|
@@ -763,6 +902,7 @@ export type SystemSpec<
|
|
|
763
902
|
readonly pauseWhen: AccessField<Access, "pauseWhen", readonly []>
|
|
764
903
|
readonly transitions: AccessField<Access, "transitions", {}>
|
|
765
904
|
readonly local: LocalFactory<Access>
|
|
905
|
+
readonly commands: "commands" extends keyof Access ? NonNullable<Access["commands"]> : undefined
|
|
766
906
|
readonly schema: S
|
|
767
907
|
readonly __schemaRoot: Root
|
|
768
908
|
}
|
|
@@ -775,6 +915,7 @@ export type AnySystemSpec = SystemSpec<any, any, any>
|
|
|
775
915
|
type ResourceContext<Spec extends AnySystemSpec> = {
|
|
776
916
|
readonly [K in keyof Spec["resources"]]:
|
|
777
917
|
Spec["resources"][K] extends ResourceRead<infer D> ? ResourceReadView<Descriptor.Value<D>>
|
|
918
|
+
: Spec["resources"][K] extends ResourceChanged<infer D> ? ResourceChangedView<Descriptor.Value<D>>
|
|
778
919
|
: Spec["resources"][K] extends ResourceWrite<infer D> ? ResourceWriteView<D>
|
|
779
920
|
: never
|
|
780
921
|
}
|
|
@@ -857,6 +998,11 @@ type QueryContext<Spec extends AnySystemSpec> = {
|
|
|
857
998
|
type LocalValue<Factory> = Factory extends () => infer Local ? Local : undefined
|
|
858
999
|
type LocalContext<Spec extends AnySystemSpec> = LocalValue<Spec["local"]>
|
|
859
1000
|
|
|
1001
|
+
type CommandsContext<Spec extends AnySystemSpec> =
|
|
1002
|
+
Spec["commands"] extends CommandAccess<infer Spawns, infer Inserts, infer Removes, infer Despawns, infer Scopes, infer Relates>
|
|
1003
|
+
? CommandsApi<Spec["schema"], Spec["__schemaRoot"], Spawns, Inserts, Removes, Despawns, Scopes, Relates>
|
|
1004
|
+
: NotDeclared
|
|
1005
|
+
|
|
860
1006
|
/**
|
|
861
1007
|
* The implementation context derived from a system spec.
|
|
862
1008
|
*
|
|
@@ -876,16 +1022,12 @@ export interface SystemContext<Spec extends AnySystemSpec> {
|
|
|
876
1022
|
readonly relationFailures: RelationFailureContext<Spec>
|
|
877
1023
|
readonly transitions: TransitionContext<Spec>
|
|
878
1024
|
readonly services: ServiceContext<Spec>
|
|
879
|
-
|
|
1025
|
+
/** The commands the spec declares (`commands: Game.System.commands(...)`). */
|
|
1026
|
+
readonly commands: CommandsContext<Spec>
|
|
880
1027
|
/** The system's own scratch state (see `local` in the spec); `undefined` when it declares none. */
|
|
881
1028
|
readonly local: LocalContext<Spec>
|
|
882
1029
|
}
|
|
883
1030
|
|
|
884
|
-
/**
|
|
885
|
-
* The service environment required by a system.
|
|
886
|
-
*/
|
|
887
|
-
export type SystemDependencies<Spec extends AnySystemSpec> = ServiceContext<Spec>
|
|
888
|
-
|
|
889
1031
|
type DescriptorFromAccess<Access> =
|
|
890
1032
|
Access extends { readonly descriptor: infer D extends Requirement.Requirement } ? D : never
|
|
891
1033
|
|
|
@@ -949,7 +1091,7 @@ export interface SystemFailure<
|
|
|
949
1091
|
|
|
950
1092
|
/** Extracts the normalized failure carried by one system definition. */
|
|
951
1093
|
export type FailureOf<Value> =
|
|
952
|
-
Value extends SystemDefinition<any,
|
|
1094
|
+
Value extends SystemDefinition<any, infer Error, any, infer Name, any>
|
|
953
1095
|
? [Error] extends [never] ? never : SystemFailure<Name, Error>
|
|
954
1096
|
: never
|
|
955
1097
|
|
|
@@ -958,7 +1100,6 @@ export type FailureOf<Value> =
|
|
|
958
1100
|
*/
|
|
959
1101
|
export interface SystemDefinition<
|
|
960
1102
|
Spec extends AnySystemSpec,
|
|
961
|
-
out A = void,
|
|
962
1103
|
out E = never,
|
|
963
1104
|
out Root = unknown,
|
|
964
1105
|
out Name extends string = string,
|
|
@@ -991,32 +1132,46 @@ export interface SystemDefinition<
|
|
|
991
1132
|
/**
|
|
992
1133
|
* The executable implementation of the system.
|
|
993
1134
|
*/
|
|
994
|
-
readonly run: SystemRun<Spec,
|
|
1135
|
+
readonly run: SystemRun<Spec, E>
|
|
995
1136
|
/**
|
|
996
1137
|
* The system this one is a gated copy of, made by `Schedule.when(...)`:
|
|
997
1138
|
* the same system with extra run conditions. The runtime keys per-system
|
|
998
1139
|
* state (change detection, event cursors) by the original, so a system and
|
|
999
1140
|
* its gated copies are one reader.
|
|
1000
1141
|
*/
|
|
1001
|
-
readonly base?: SystemDefinition<any, any, any, any, any
|
|
1142
|
+
readonly base?: SystemDefinition<any, any, any, any, any> | undefined
|
|
1002
1143
|
}
|
|
1003
1144
|
|
|
1004
1145
|
/**
|
|
1005
1146
|
* A system implementation.
|
|
1006
1147
|
*
|
|
1007
|
-
* Return nothing for a system that cannot fail. Return
|
|
1008
|
-
*
|
|
1009
|
-
*
|
|
1148
|
+
* Return nothing for a system that cannot fail. Return
|
|
1149
|
+
* `Result.failure(error)` for an expected failure: the system's ECS writes
|
|
1150
|
+
* are rolled back, its commands and events are dropped, and the tick stops
|
|
1151
|
+
* with a typed `SystemFailure` whose `error` is inferred from every
|
|
1152
|
+
* `Result.failure(...)` the body returns. A returned success
|
|
1153
|
+
* (`Result.success(...)`) behaves like returning nothing. Exceptions are
|
|
1154
|
+
* defects, not failures: they roll back the same way and are rethrown.
|
|
1155
|
+
*
|
|
1156
|
+
* @example
|
|
1157
|
+
* ```ts
|
|
1158
|
+
* const Spend = Game.System("Spend", {
|
|
1159
|
+
* resources: { gold: Game.System.writeResource(Gold) }
|
|
1160
|
+
* }, ({ resources }) => {
|
|
1161
|
+
* if (resources.gold.get() < 10) return Result.failure("NotEnoughGold" as const)
|
|
1162
|
+
* resources.gold.update((gold) => gold - 10)
|
|
1163
|
+
* })
|
|
1164
|
+
* ```
|
|
1010
1165
|
*/
|
|
1011
|
-
export type SystemRun<Spec extends AnySystemSpec,
|
|
1012
|
-
(context: SystemContext<Spec>) =>
|
|
1166
|
+
export type SystemRun<Spec extends AnySystemSpec, E = never> =
|
|
1167
|
+
(context: SystemContext<Spec>) => Result.Result<unknown, E> | void
|
|
1013
1168
|
|
|
1014
1169
|
/**
|
|
1015
1170
|
* Defines a system from an explicit spec and a typed implementation.
|
|
1016
1171
|
*
|
|
1017
1172
|
* This is the public entrypoint for authoring systems. The implementation only
|
|
1018
|
-
* receives the capabilities declared in the spec
|
|
1019
|
-
*
|
|
1173
|
+
* receives the capabilities declared in the spec; return
|
|
1174
|
+
* `Result.failure(error)` for an expected failure (see {@link SystemRun}).
|
|
1020
1175
|
*
|
|
1021
1176
|
* Use the string-name overload in normal code. The name is turned into an
|
|
1022
1177
|
* internal ordering token automatically, so the system can participate in
|
|
@@ -1075,7 +1230,6 @@ const collectSystemRequirements = (
|
|
|
1075
1230
|
|
|
1076
1231
|
export function System<
|
|
1077
1232
|
const Access extends SystemAccessInput & { readonly schema: Schema.Any },
|
|
1078
|
-
A = void,
|
|
1079
1233
|
E = never,
|
|
1080
1234
|
Root = unknown,
|
|
1081
1235
|
const Name extends string = string
|
|
@@ -1084,14 +1238,14 @@ export function System<
|
|
|
1084
1238
|
spec: Access & {
|
|
1085
1239
|
readonly [Key in Exclude<keyof Access, keyof SystemAccessInput | "schema">]: never
|
|
1086
1240
|
},
|
|
1087
|
-
run: SystemRun<SystemSpec<Access["schema"], Access, Root>,
|
|
1088
|
-
): SystemDefinition<SystemSpec<Access["schema"], Access, Root>,
|
|
1241
|
+
run: SystemRun<SystemSpec<Access["schema"], Access, Root>, E>
|
|
1242
|
+
): SystemDefinition<SystemSpec<Access["schema"], Access, Root>, E, Root, Name, SystemAccessNeeds<Access>>
|
|
1089
1243
|
|
|
1090
1244
|
export function System(
|
|
1091
1245
|
name: string,
|
|
1092
1246
|
spec: { readonly schema: Schema.Any } & SystemAccessInput,
|
|
1093
|
-
run: (context: any) =>
|
|
1094
|
-
): SystemDefinition<any, any, any, any, any
|
|
1247
|
+
run: (context: any) => Result.Result<any, any> | void
|
|
1248
|
+
): SystemDefinition<any, any, any, any, any> {
|
|
1095
1249
|
type Spec = SystemSpec<Schema.Any, SystemAccessInput, unknown>
|
|
1096
1250
|
|
|
1097
1251
|
const normalizedSpec = {
|
|
@@ -1110,6 +1264,7 @@ export function System(
|
|
|
1110
1264
|
pauseWhen: spec.pauseWhen ?? [],
|
|
1111
1265
|
transitions: spec.transitions ?? {},
|
|
1112
1266
|
local: spec.local,
|
|
1267
|
+
commands: spec.commands,
|
|
1113
1268
|
__schemaRoot: undefined
|
|
1114
1269
|
} as Spec
|
|
1115
1270
|
|
|
@@ -1123,5 +1278,5 @@ export function System(
|
|
|
1123
1278
|
},
|
|
1124
1279
|
spec: normalizedSpec,
|
|
1125
1280
|
run
|
|
1126
|
-
} as SystemDefinition<any, any, any, any, any
|
|
1281
|
+
} as SystemDefinition<any, any, any, any, any>
|
|
1127
1282
|
}
|
package/src/index.ts
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* small number of stable namespaces instead of many deep internal paths.
|
|
6
6
|
*/
|
|
7
7
|
/**
|
|
8
|
-
* Branding helpers for validated
|
|
8
|
+
* Branding helpers for packages that author validated value types (like
|
|
9
|
+
* `@typeonce/ritmo-math`). Game code validates through descriptors instead.
|
|
9
10
|
*/
|
|
10
11
|
export * as Brand from "./Brand.ts"
|
|
11
12
|
/**
|
|
@@ -17,13 +18,13 @@ export * as Decode from "./Decode.ts"
|
|
|
17
18
|
*/
|
|
18
19
|
export * as Debug from "./Debug.ts"
|
|
19
20
|
/**
|
|
20
|
-
*
|
|
21
|
+
* Simulation time: the fixed step, the clock resource, durations in whole steps.
|
|
21
22
|
*/
|
|
22
|
-
export * as
|
|
23
|
+
export * as Clock from "./Clock.ts"
|
|
23
24
|
/**
|
|
24
|
-
*
|
|
25
|
+
* Descriptor authoring helpers.
|
|
25
26
|
*/
|
|
26
|
-
export * as
|
|
27
|
+
export * as Descriptor from "./Descriptor.ts"
|
|
27
28
|
/**
|
|
28
29
|
* Entity identity and proof helpers.
|
|
29
30
|
*/
|
|
@@ -33,9 +34,9 @@ export * as Entity from "./Entity.ts"
|
|
|
33
34
|
*/
|
|
34
35
|
export * as EntityScope from "./EntityScope.ts"
|
|
35
36
|
/**
|
|
36
|
-
*
|
|
37
|
+
* The host's wall clock as an injected contract (`standard`, `test()`).
|
|
37
38
|
*/
|
|
38
|
-
export * as
|
|
39
|
+
export * as HostClock from "./HostClock.ts"
|
|
39
40
|
/**
|
|
40
41
|
* Read-only typed world projections.
|
|
41
42
|
*/
|