@typeonce/ritmo 0.6.0 → 0.7.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 +157 -33
- package/dist/Command.d.ts.map +1 -1
- package/dist/Command.js +49 -15
- package/dist/Command.js.map +1 -1
- package/dist/Debug.d.ts +104 -12
- 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 +31 -21
- package/dist/Descriptor.d.ts.map +1 -1
- package/dist/Descriptor.js +21 -12
- package/dist/Descriptor.js.map +1 -1
- package/dist/Entity.d.ts +14 -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 +6 -0
- package/dist/EntityScope.d.ts.map +1 -1
- package/dist/EntityScope.js +6 -0
- 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 +12 -3
- package/dist/Machine.d.ts.map +1 -1
- package/dist/Machine.js +4 -1
- package/dist/Machine.js.map +1 -1
- package/dist/Query.d.ts +28 -24
- package/dist/Query.d.ts.map +1 -1
- package/dist/Query.js +0 -24
- package/dist/Query.js.map +1 -1
- package/dist/Runtime.d.ts +36 -16
- package/dist/Runtime.d.ts.map +1 -1
- package/dist/Runtime.js +230 -67
- 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 +61 -25
- package/dist/Schema.d.ts.map +1 -1
- package/dist/Schema.js +0 -8
- package/dist/Schema.js.map +1 -1
- package/dist/System.d.ts +149 -52
- package/dist/System.d.ts.map +1 -1
- package/dist/System.js +52 -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 +6 -0
- package/dist/internal/debug.d.ts.map +1 -1
- package/dist/internal/debug.js +95 -1
- package/dist/internal/debug.js.map +1 -1
- package/dist/internal/game.d.ts.map +1 -1
- package/dist/internal/game.js +9 -2
- 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 +120 -0
- package/dist/internal/order.js.map +1 -0
- package/dist/internal/queries.js +2 -2
- package/dist/internal/queries.js.map +1 -1
- package/package.json +1 -1
- package/src/Brand.ts +9 -4
- package/src/Clock.ts +311 -0
- package/src/Command.ts +271 -73
- package/src/Debug.ts +111 -10
- package/src/Decode.ts +1 -1
- package/src/Descriptor.ts +37 -27
- package/src/Entity.ts +15 -5
- package/src/EntityScope.ts +6 -0
- package/src/HostClock.ts +64 -0
- package/src/Inspector.ts +7 -2
- package/src/Machine.ts +13 -3
- package/src/Query.ts +41 -33
- package/src/Runtime.ts +273 -91
- package/src/Schedule.ts +380 -13
- package/src/Schema.ts +70 -41
- package/src/System.ts +174 -88
- package/src/index.ts +8 -7
- package/src/internal/cells.ts +10 -12
- package/src/internal/debug.ts +104 -4
- package/src/internal/game.ts +11 -3
- package/src/internal/order.ts +140 -0
- package/src/internal/queries.ts +3 -3
- 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/Command.ts
CHANGED
|
@@ -125,7 +125,7 @@ export type SchemaEntry<S extends Schema.Any> = EntryOf<SchemaComponentDescripto
|
|
|
125
125
|
|
|
126
126
|
/**
|
|
127
127
|
* One entry accepted by `spawn(...)` and `insert(...)`: a plain descriptor/value
|
|
128
|
-
* pair, or a `Result` of one (from `entryRaw(...)`
|
|
128
|
+
* pair, or a `Result` of one (from `entryRaw(...)`).
|
|
129
129
|
*/
|
|
130
130
|
export type SpawnEntry<S extends Schema.Any> = SchemaEntry<S> | Result.Result<SchemaEntry<S>, any>
|
|
131
131
|
|
|
@@ -142,12 +142,21 @@ type EntryValue<T> =
|
|
|
142
142
|
export type FoldEntries<
|
|
143
143
|
Entries extends ReadonlyArray<SpawnEntry<any>>,
|
|
144
144
|
P extends Entity.ComponentProof = {}
|
|
145
|
-
> =
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
145
|
+
> = number extends Entries["length"]
|
|
146
|
+
// A widened array (a spread of entries built at runtime): each entry may or may not be there.
|
|
147
|
+
? P & MaybeEntries<EntryValue<Entries[number]>>
|
|
148
|
+
: Entries extends readonly [
|
|
149
|
+
infer Head extends SpawnEntry<any>,
|
|
150
|
+
...infer Tail extends Array<SpawnEntry<any>>
|
|
151
|
+
]
|
|
152
|
+
? FoldEntries<Tail, Draft.InsertEntry<P, EntryValue<Head>>>
|
|
153
|
+
: P
|
|
154
|
+
|
|
155
|
+
/** The proof of entries that may each be present: every component they can hold, optional. */
|
|
156
|
+
type MaybeEntries<Entry> = {
|
|
157
|
+
readonly [E in Entry as E extends readonly [infer D extends Descriptor<"component", string, any>, unknown] ? Descriptor.Name<D> : never]?:
|
|
158
|
+
E extends readonly [infer D extends Descriptor<"component", string, any>, unknown] ? Descriptor.Value<D> : never
|
|
159
|
+
}
|
|
151
160
|
|
|
152
161
|
/**
|
|
153
162
|
* Per-entry failures, aligned with the entries: `null` for entries that
|
|
@@ -262,20 +271,15 @@ export const entry = <D extends Descriptor<"component", string, any>>(
|
|
|
262
271
|
): Entry<D> => [descriptor, value]
|
|
263
272
|
|
|
264
273
|
/**
|
|
265
|
-
*
|
|
266
|
-
*
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
: Result.failure(result.error)
|
|
275
|
-
|
|
276
|
-
/**
|
|
277
|
-
* Validates raw input through a constructed descriptor and returns an entry
|
|
278
|
-
* result.
|
|
274
|
+
* Validates raw input through a constructed descriptor's own validator and
|
|
275
|
+
* returns an entry result for `spawn(...)` / `insert(...)`.
|
|
276
|
+
*
|
|
277
|
+
* This is the one way to put unvalidated data on an entity: the descriptor
|
|
278
|
+
* owns its validator (`Descriptor.ConstructedComponent(...)`, built with
|
|
279
|
+
* `Decode` or `Descriptor.fromStandardSchema`). Values that are already
|
|
280
|
+
* valid use a plain `[Descriptor, value]` entry instead. To validate authored
|
|
281
|
+
* constants once at module load, call the constructor (`Vector2.result(...)`)
|
|
282
|
+
* and keep the carried value.
|
|
279
283
|
*
|
|
280
284
|
* @example
|
|
281
285
|
* ```ts
|
|
@@ -290,15 +294,22 @@ export const entryResult = <D extends Descriptor<"component", string, any>, Erro
|
|
|
290
294
|
export const entryRaw = <D extends DescriptorModule.ConstructedDescriptor<"component", string, any, any, any>>(
|
|
291
295
|
descriptor: D,
|
|
292
296
|
raw: Descriptor.Raw<D>
|
|
293
|
-
): Result.Result<Entry<D>, Descriptor.ConstructionError<D>> =>
|
|
294
|
-
|
|
297
|
+
): Result.Result<Entry<D>, Descriptor.ConstructionError<D>> => {
|
|
298
|
+
const validated = DescriptorModule.constructorOf(descriptor).result(raw) as Result.Result<Descriptor.Value<D>, Descriptor.ConstructionError<D>>
|
|
299
|
+
return validated.ok
|
|
300
|
+
? Result.success([descriptor, validated.value] as Entry<D>)
|
|
301
|
+
: Result.failure(validated.error)
|
|
302
|
+
}
|
|
295
303
|
|
|
296
304
|
/**
|
|
297
|
-
* Adds entries to a draft. Plain entries return the
|
|
298
|
-
* a `Result`, the draft is returned as a `Result`
|
|
299
|
-
* entry's failure (or `null`).
|
|
305
|
+
* Adds entries to a draft, a value not yet spawned. Plain entries return the
|
|
306
|
+
* new draft; if any entry is a `Result`, the draft is returned as a `Result`
|
|
307
|
+
* whose error lists every entry's failure (or `null`).
|
|
308
|
+
*
|
|
309
|
+
* Use this while building what a spawn will hold. Use `commands.insert` to
|
|
310
|
+
* add or replace components on an entity that already exists.
|
|
300
311
|
*/
|
|
301
|
-
export const
|
|
312
|
+
export const extend = <
|
|
302
313
|
S extends Schema.Any,
|
|
303
314
|
P extends Entity.ComponentProof,
|
|
304
315
|
Root,
|
|
@@ -399,78 +410,267 @@ export const relate = <
|
|
|
399
410
|
}
|
|
400
411
|
])
|
|
401
412
|
|
|
413
|
+
/** One permission of `Game.System.commands(...)`: a kind of command, and what it may touch. */
|
|
414
|
+
export interface Permission<out Kind extends PermissionKind, out Value> {
|
|
415
|
+
readonly permission: Kind
|
|
416
|
+
readonly values: ReadonlyArray<Value>
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
export type PermissionKind = "spawns" | "inserts" | "removes" | "despawns" | "despawnsScope" | "relates"
|
|
420
|
+
|
|
421
|
+
type ComponentDescriptor = Descriptor<"component", string, any>
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Allows `spawn` / `spawnIn` of drafts whose components are all among `components`.
|
|
425
|
+
* Use it for entities the system creates; use `extend` on a draft to build one.
|
|
426
|
+
*/
|
|
427
|
+
export const spawns = <const Components extends ReadonlyArray<ComponentDescriptor>>(...components: Components): Permission<"spawns", Components[number]> =>
|
|
428
|
+
({ permission: "spawns", values: components })
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Allows `insert(entity, [X, value])` with `X` among `components`: add or
|
|
432
|
+
* replace at the next `applyDeferred()`. To change a value now, on an entity
|
|
433
|
+
* you matched, write through a query (`Game.Query.write`) instead.
|
|
434
|
+
*/
|
|
435
|
+
export const inserts = <const Components extends ReadonlyArray<ComponentDescriptor>>(...components: Components): Permission<"inserts", Components[number]> =>
|
|
436
|
+
({ permission: "inserts", values: components })
|
|
437
|
+
|
|
438
|
+
/** Allows `remove(entity, X)` with `X` among `components`. */
|
|
439
|
+
export const removes = <const Components extends ReadonlyArray<ComponentDescriptor>>(...components: Components): Permission<"removes", Components[number]> =>
|
|
440
|
+
({ permission: "removes", values: components })
|
|
441
|
+
|
|
402
442
|
/**
|
|
403
|
-
*
|
|
443
|
+
* Allows `despawn(entity)` where `entity` comes from a query match proving one
|
|
444
|
+
* of `components` (`match.entity`), never a raw id: the system can only
|
|
445
|
+
* despawn what it has proven is, for example, an `Enemy`.
|
|
446
|
+
*/
|
|
447
|
+
export const despawns = <const Components extends ReadonlyArray<ComponentDescriptor>>(...components: Components): Permission<"despawns", Components[number]> =>
|
|
448
|
+
({ permission: "despawns", values: components })
|
|
449
|
+
|
|
450
|
+
/** Allows `despawnScope(scope)` for this scope. */
|
|
451
|
+
export const despawnsScope = <const Scope extends EntityScope.EntityScope.Any>(scope: Scope): Permission<"despawnsScope", Scope> =>
|
|
452
|
+
({ permission: "despawnsScope", values: [scope] })
|
|
453
|
+
|
|
454
|
+
/** Allows `relate`, `unrelate`, and `reorderChildren` for these relations. */
|
|
455
|
+
export const relates = <const Relations extends ReadonlyArray<Relation.Relation.Any>>(...relations: Relations): Permission<"relates", Relations[number]> =>
|
|
456
|
+
({ permission: "relates", values: relations })
|
|
457
|
+
|
|
458
|
+
type PermissionValue<Permissions, Kind extends PermissionKind> =
|
|
459
|
+
Permissions extends Permission<Kind, infer Value> ? Value : never
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* The commands a system may queue, declared in its spec as
|
|
463
|
+
* `commands: Game.System.commands(...)`. Without it, the system has no
|
|
464
|
+
* `commands`.
|
|
465
|
+
*/
|
|
466
|
+
export interface CommandAccess<
|
|
467
|
+
out Spawns = never,
|
|
468
|
+
out Inserts = never,
|
|
469
|
+
out Removes = never,
|
|
470
|
+
out Despawns = never,
|
|
471
|
+
out Scopes = never,
|
|
472
|
+
out Relates = never
|
|
473
|
+
> {
|
|
474
|
+
readonly kind: "commands"
|
|
475
|
+
readonly permissions: ReadonlyArray<Permission<PermissionKind, unknown>>
|
|
476
|
+
readonly __permissions?: (_: never) => readonly [Spawns, Inserts, Removes, Despawns, Scopes, Relates]
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
export namespace CommandAccess {
|
|
480
|
+
export type Any = CommandAccess<any, any, any, any, any, any>
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/** Declares the commands a system may queue; see the permission builders. */
|
|
484
|
+
export const commands = <const Permissions extends ReadonlyArray<Permission<PermissionKind, unknown>>>(
|
|
485
|
+
...permissions: Permissions
|
|
486
|
+
): CommandAccess<
|
|
487
|
+
PermissionValue<Permissions[number], "spawns">,
|
|
488
|
+
PermissionValue<Permissions[number], "inserts">,
|
|
489
|
+
PermissionValue<Permissions[number], "removes">,
|
|
490
|
+
PermissionValue<Permissions[number], "despawns">,
|
|
491
|
+
PermissionValue<Permissions[number], "despawnsScope">,
|
|
492
|
+
PermissionValue<Permissions[number], "relates">
|
|
493
|
+
> => ({ kind: "commands", permissions })
|
|
494
|
+
|
|
495
|
+
/** The `commands` of a system that declares none. */
|
|
496
|
+
export type NotDeclared =
|
|
497
|
+
"commands are not declared for this system: add commands: Game.System.commands(...) to its spec, with the permissions it needs (spawns, inserts, removes, despawns, despawnsScope, relates)"
|
|
498
|
+
|
|
499
|
+
/** A method whose permission is not declared: any call fails with an argument error naming the fix. */
|
|
500
|
+
type MethodNotDeclared<Method extends string, Builder extends string> =
|
|
501
|
+
(...fix: ReadonlyArray<`${Method} is not declared in this system's commands: add Game.System.${Builder}(...) to Game.System.commands(...)`>) => never
|
|
502
|
+
|
|
503
|
+
type Fix<Message extends string> = { readonly __fixCommands__: Message }
|
|
504
|
+
|
|
505
|
+
type Names<D> = D extends { readonly name: infer Name extends string } ? Name : never
|
|
506
|
+
|
|
507
|
+
/** Every key of every member of a union of proofs (a draft with a conditional entry is a union). */
|
|
508
|
+
type AllKeys<P> = P extends unknown ? keyof P & string : never
|
|
509
|
+
|
|
510
|
+
type DraftAllowed<P, Spawns> =
|
|
511
|
+
[Exclude<AllKeys<P>, Names<Spawns>>] extends [never]
|
|
512
|
+
? unknown
|
|
513
|
+
: Fix<`${Exclude<AllKeys<P>, Names<Spawns>>} is not declared in this system's commands: add it to Game.System.spawns(...)`>
|
|
514
|
+
|
|
515
|
+
type EntriesAllowed<Entries extends ReadonlyArray<readonly [ComponentDescriptor, unknown]>, Inserts> =
|
|
516
|
+
[Exclude<Entries[number][0], Inserts>] extends [never]
|
|
517
|
+
? unknown
|
|
518
|
+
: Fix<`${Names<Exclude<Entries[number][0], Inserts>>} is not declared in this system's commands: add it to Game.System.inserts(...)`>
|
|
519
|
+
|
|
520
|
+
type RemoveAllowed<D, Removes> =
|
|
521
|
+
[D] extends [Removes] ? unknown : Fix<`${Names<D>} is not declared in this system's commands: add it to Game.System.removes(...)`>
|
|
522
|
+
|
|
523
|
+
type DespawnAllowed<E, Despawns> =
|
|
524
|
+
E extends { readonly kind: "EntityRef" | "EntityMut"; readonly __proves?: (_: never) => infer Proves }
|
|
525
|
+
? [Extract<Proves, Despawns>] extends [never]
|
|
526
|
+
? Fix<`despawn needs an entity from a query proving ${Names<Despawns>} (declared in despawns)`>
|
|
527
|
+
: unknown
|
|
528
|
+
: E extends { readonly [spawnedTypeId]: (_: never) => infer Spawned }
|
|
529
|
+
? [Extract<Spawned, Names<Despawns>>] extends [never]
|
|
530
|
+
? Fix<`despawn needs an entity spawned with ${Names<Despawns>} (declared in despawns)`>
|
|
531
|
+
: unknown
|
|
532
|
+
: Fix<`despawn needs an entity from a query proving ${Names<Despawns>} (declared in despawns), not a raw id: pass match.entity`>
|
|
533
|
+
|
|
534
|
+
declare const spawnedTypeId: unique symbol
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* The id `spawn` / `spawnIn` return: an `EntityId` that also records the
|
|
538
|
+
* components its draft holds in every case, so the system that queued it can
|
|
539
|
+
* `despawn` it before the next `applyDeferred()` (no query can match it yet).
|
|
540
|
+
* Stored as a plain `EntityId`, it is a raw id again.
|
|
541
|
+
*/
|
|
542
|
+
export type SpawnedId<S extends Schema.Any, Root, Components extends string> =
|
|
543
|
+
Entity.EntityId<S, Root> & { readonly [spawnedTypeId]: (_: never) => Components }
|
|
544
|
+
|
|
545
|
+
/** The keys every member of a union of proofs requires (what a draft holds in every case). */
|
|
546
|
+
type RequiredKeys<P> = P extends unknown ? { [K in keyof P]-?: {} extends Pick<P, K> ? never : K }[keyof P] & string : never
|
|
547
|
+
type MissingSomewhere<P, All> = P extends unknown ? Exclude<All, RequiredKeys<P>> : never
|
|
548
|
+
type HeldNames<P> = Exclude<AllKeys<P>, MissingSomewhere<P, AllKeys<P>>>
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* The command API a system gets for its declared permissions. Commands are
|
|
552
|
+
* the structural mutation entrypoint: spawns, inserts, removals, despawns,
|
|
553
|
+
* and relation edits. They are queued in order and applied only at the next
|
|
554
|
+
* `applyDeferred()` (or `applyStateTransitions(...)`) schedule step.
|
|
404
555
|
*
|
|
405
|
-
*
|
|
406
|
-
*
|
|
407
|
-
*
|
|
556
|
+
* Each method exists only with its permission, and only for what it names:
|
|
557
|
+
* `spawn` needs `Game.System.spawns(...)` listing every component of the
|
|
558
|
+
* draft, `insert` needs `inserts`, `remove` needs `removes`, `despawn` needs
|
|
559
|
+
* `despawns` and an entity from a query proving one of them, `despawnScope`
|
|
560
|
+
* needs `despawnsScope`, and relation edits need `relates`.
|
|
408
561
|
*
|
|
409
562
|
* Resource and event writes are not commands: they go through the
|
|
410
563
|
* `writeResource(...)` / `writeEvent(...)` access a system declares.
|
|
411
564
|
*/
|
|
412
|
-
export interface CommandsApi<
|
|
565
|
+
export interface CommandsApi<
|
|
566
|
+
S extends Schema.Any,
|
|
567
|
+
Root = unknown,
|
|
568
|
+
Spawns = never,
|
|
569
|
+
Inserts = never,
|
|
570
|
+
Removes = never,
|
|
571
|
+
Despawns = never,
|
|
572
|
+
Scopes = never,
|
|
573
|
+
Relates = never
|
|
574
|
+
> {
|
|
413
575
|
/**
|
|
414
576
|
* Queues a staged entity for spawning and returns its stable runtime id.
|
|
415
577
|
*/
|
|
416
|
-
readonly spawn:
|
|
578
|
+
readonly spawn: [Spawns] extends [never]
|
|
579
|
+
? MethodNotDeclared<"spawn", "spawns">
|
|
580
|
+
: <P extends Entity.ComponentProof>(draft: Entity.EntityDraft<S, P, Root> & DraftAllowed<P, Spawns>) => SpawnedId<S, Root, HeldNames<P>>
|
|
417
581
|
/**
|
|
418
|
-
* Queues an entity spawn owned by one lifetime scope
|
|
419
|
-
*
|
|
420
|
-
* Scoped entities can later be removed together with `despawnScope(...)`.
|
|
582
|
+
* Queues an entity spawn owned by one lifetime scope (removed together
|
|
583
|
+
* with `despawnScope(...)`).
|
|
421
584
|
*/
|
|
422
|
-
readonly spawnIn:
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
585
|
+
readonly spawnIn: [Spawns] extends [never]
|
|
586
|
+
? MethodNotDeclared<"spawnIn", "spawns">
|
|
587
|
+
: <P extends Entity.ComponentProof>(
|
|
588
|
+
scope: EntityScope.EntityScope<string, Root>,
|
|
589
|
+
draft: Entity.EntityDraft<S, P, Root> & DraftAllowed<P, Spawns>
|
|
590
|
+
) => SpawnedId<S, Root, HeldNames<P>>
|
|
426
591
|
/**
|
|
427
|
-
* Queues component inserts (or replacements) on an existing entity
|
|
592
|
+
* Queues component inserts (or replacements) on an existing entity, applied
|
|
593
|
+
* at the next `applyDeferred()`. To change a value now, on an entity you
|
|
594
|
+
* matched, write through a query (`Game.Query.write`) instead.
|
|
428
595
|
*
|
|
429
596
|
* Entries must already be valid; validate raw input with
|
|
430
597
|
* `Game.Command.entryRaw(...)` first.
|
|
431
598
|
*/
|
|
432
|
-
readonly insert:
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
599
|
+
readonly insert: [Inserts] extends [never]
|
|
600
|
+
? MethodNotDeclared<"insert", "inserts">
|
|
601
|
+
: <const Entries extends ReadonlyArray<SchemaEntry<S>>>(
|
|
602
|
+
entity: Entity.EntityId<S, Root>,
|
|
603
|
+
...entries: Entries & EntriesAllowed<Entries, Inserts>
|
|
604
|
+
) => Entity.EntityId<S, Root>
|
|
436
605
|
/**
|
|
437
|
-
* Queues an entity removal.
|
|
606
|
+
* Queues an entity removal. A despawned entity stays visible to queries
|
|
607
|
+
* until the next `applyDeferred()`, and despawns never count as writers for
|
|
608
|
+
* `Game.System.thisTick` query reads.
|
|
609
|
+
*
|
|
610
|
+
* Pass `match.entity` from a query proving one of the `despawns`
|
|
611
|
+
* components, or the id `spawn` returned in this run (it holds its draft's
|
|
612
|
+
* components); never a raw id.
|
|
438
613
|
*/
|
|
439
|
-
readonly despawn:
|
|
614
|
+
readonly despawn: [Despawns] extends [never]
|
|
615
|
+
? MethodNotDeclared<"despawn", "despawns">
|
|
616
|
+
: <const E>(entity: E & DespawnAllowed<E, Despawns>) => void
|
|
440
617
|
/**
|
|
441
618
|
* Queues removal of every entity owned by one lifetime scope.
|
|
442
619
|
*/
|
|
443
|
-
readonly despawnScope:
|
|
620
|
+
readonly despawnScope: [Scopes] extends [never]
|
|
621
|
+
? MethodNotDeclared<"despawnScope", "despawnsScope">
|
|
622
|
+
: (scope: Scopes) => void
|
|
444
623
|
/**
|
|
445
624
|
* Queues a component removal on an existing entity.
|
|
446
625
|
*/
|
|
447
|
-
readonly remove:
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
626
|
+
readonly remove: [Removes] extends [never]
|
|
627
|
+
? MethodNotDeclared<"remove", "removes">
|
|
628
|
+
: <D extends Extract<Schema.Components<S>[keyof Schema.Components<S>], ComponentDescriptor>>(
|
|
629
|
+
entity: Entity.EntityId<S, Root>,
|
|
630
|
+
descriptor: D & RemoveAllowed<D, Removes>
|
|
631
|
+
) => Entity.EntityId<S, Root>
|
|
451
632
|
/**
|
|
452
633
|
* Queues one live relation mutation for deferred application.
|
|
453
634
|
*/
|
|
454
|
-
readonly relate:
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
635
|
+
readonly relate: [Relates] extends [never]
|
|
636
|
+
? MethodNotDeclared<"relate", "relates">
|
|
637
|
+
: <R extends Extract<Relates, Relation.Relation.Any>>(
|
|
638
|
+
entity: Entity.EntityId<S, Root>,
|
|
639
|
+
relation: R,
|
|
640
|
+
target: Entity.EntityId<S, Root>
|
|
641
|
+
) => void
|
|
459
642
|
/**
|
|
460
643
|
* Queues removal of one outgoing relation when present.
|
|
461
644
|
*/
|
|
462
|
-
readonly unrelate:
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
645
|
+
readonly unrelate: [Relates] extends [never]
|
|
646
|
+
? MethodNotDeclared<"unrelate", "relates">
|
|
647
|
+
: <R extends Extract<Relates, Relation.Relation.Any>>(
|
|
648
|
+
entity: Entity.EntityId<S, Root>,
|
|
649
|
+
relation: R
|
|
650
|
+
) => void
|
|
466
651
|
/**
|
|
467
652
|
* Queues a hierarchy-only reorder of one parent's current children.
|
|
468
653
|
*/
|
|
469
|
-
readonly reorderChildren:
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
654
|
+
readonly reorderChildren: [Relates] extends [never]
|
|
655
|
+
? MethodNotDeclared<"reorderChildren", "relates">
|
|
656
|
+
: <R extends Extract<Relates, Relation.Relation.Hierarchy>>(
|
|
657
|
+
entity: Entity.EntityId<S, Root>,
|
|
658
|
+
relation: R,
|
|
659
|
+
children: ReadonlyArray<Entity.EntityId<S, Root>>
|
|
660
|
+
) => void
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/** The command queue the runtime gives each system: every command, unchecked, and `flush`. */
|
|
664
|
+
export interface CommandQueue<S extends Schema.Any, Root = unknown> {
|
|
665
|
+
readonly spawn: (draft: Entity.EntityDraft<S, any, Root>) => Entity.EntityId<S, Root>
|
|
666
|
+
readonly spawnIn: (scope: EntityScope.EntityScope<string, Root>, draft: Entity.EntityDraft<S, any, Root>) => Entity.EntityId<S, Root>
|
|
667
|
+
readonly insert: (entity: Entity.EntityId<S, Root>, ...entries: ReadonlyArray<SchemaEntry<S>>) => Entity.EntityId<S, Root>
|
|
668
|
+
readonly despawn: (entity: Entity.EntityId<S, Root> | { readonly id: Entity.EntityId<S, Root> }) => void
|
|
669
|
+
readonly despawnScope: (scope: EntityScope.EntityScope<string, Root>) => void
|
|
670
|
+
readonly remove: (entity: Entity.EntityId<S, Root>, descriptor: ComponentDescriptor) => Entity.EntityId<S, Root>
|
|
671
|
+
readonly relate: (entity: Entity.EntityId<S, Root>, relation: Relation.Relation.Any, target: Entity.EntityId<S, Root>) => void
|
|
672
|
+
readonly unrelate: (entity: Entity.EntityId<S, Root>, relation: Relation.Relation.Any) => void
|
|
673
|
+
readonly reorderChildren: (entity: Entity.EntityId<S, Root>, relation: Relation.Relation.Hierarchy, children: ReadonlyArray<Entity.EntityId<S, Root>>) => void
|
|
474
674
|
/**
|
|
475
675
|
* Drains the queued commands in insertion order.
|
|
476
676
|
*/
|
|
@@ -485,7 +685,7 @@ export interface CommandsApi<S extends Schema.Any, Root = unknown> {
|
|
|
485
685
|
*/
|
|
486
686
|
export const makeCommands = <S extends Schema.Any, Root = unknown>(
|
|
487
687
|
allocateId: () => Entity.EntityId<S, Root>
|
|
488
|
-
):
|
|
688
|
+
): CommandQueue<S, Root> => {
|
|
489
689
|
/**
|
|
490
690
|
* The per-system command buffer.
|
|
491
691
|
*
|
|
@@ -495,7 +695,7 @@ export const makeCommands = <S extends Schema.Any, Root = unknown>(
|
|
|
495
695
|
const queue: Array<DeferredCommand<S>> = []
|
|
496
696
|
|
|
497
697
|
return {
|
|
498
|
-
spawn
|
|
698
|
+
spawn(draft) {
|
|
499
699
|
const id = allocateId()
|
|
500
700
|
queue.push({
|
|
501
701
|
tag: "spawn",
|
|
@@ -508,10 +708,7 @@ export const makeCommands = <S extends Schema.Any, Root = unknown>(
|
|
|
508
708
|
})
|
|
509
709
|
return id
|
|
510
710
|
},
|
|
511
|
-
spawnIn
|
|
512
|
-
scope: EntityScope.EntityScope<string, Root>,
|
|
513
|
-
draft: Entity.EntityDraft<S, P, Root>
|
|
514
|
-
): Entity.EntityId<S, Root> {
|
|
711
|
+
spawnIn(scope, draft) {
|
|
515
712
|
const id = allocateId()
|
|
516
713
|
queue.push({
|
|
517
714
|
tag: "spawnIn",
|
|
@@ -537,10 +734,11 @@ export const makeCommands = <S extends Schema.Any, Root = unknown>(
|
|
|
537
734
|
return entity
|
|
538
735
|
},
|
|
539
736
|
despawn(entity) {
|
|
737
|
+
const id = typeof entity === "object" ? entity.id : entity
|
|
540
738
|
queue.push({
|
|
541
739
|
tag: "despawn",
|
|
542
740
|
apply(world) {
|
|
543
|
-
world.destroyEntity(
|
|
741
|
+
world.destroyEntity(id)
|
|
544
742
|
}
|
|
545
743
|
})
|
|
546
744
|
},
|
package/src/Debug.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Opt-in runtime introspection for tools, tests, and coding agents.
|
|
3
3
|
*
|
|
4
|
-
* A runtime made with `debug:
|
|
4
|
+
* A runtime made with `debug: { hostClock }` carries a `debug` handle. Runtimes made
|
|
5
5
|
* without it have no handle at all, so production code cannot reach into
|
|
6
6
|
* internals by accident and pays nothing for them.
|
|
7
7
|
*
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
*
|
|
36
36
|
* @example
|
|
37
37
|
* ```ts
|
|
38
|
-
* const runtime = Game.Runtime.make({ services, resources, debug:
|
|
38
|
+
* const runtime = Game.Runtime.make({ services, resources, debug: { hostClock: HostClock.standard } })
|
|
39
39
|
* runtime.debug.nameSchedules({ setup: setupSchedule, update: updateSchedule })
|
|
40
40
|
*
|
|
41
41
|
* const stop = runtime.debug.observe((event) => {
|
|
@@ -46,15 +46,16 @@
|
|
|
46
46
|
* stop()
|
|
47
47
|
* ```
|
|
48
48
|
*/
|
|
49
|
+
import type { HostClock } from "./HostClock.ts"
|
|
49
50
|
import type * as Machine from "./Machine.ts"
|
|
50
|
-
import type { ExecutableScheduleDefinition } from "./Schedule.ts"
|
|
51
|
+
import type { AnyScheduleOrder, ExecutableScheduleDefinition } from "./Schedule.ts"
|
|
51
52
|
import type { Schema } from "./Schema.ts"
|
|
52
53
|
|
|
53
54
|
/** Version of the description, dump, and trace formats. */
|
|
54
55
|
export const formatVersion = 1 as const
|
|
55
56
|
|
|
56
57
|
/**
|
|
57
|
-
* The debug handle of a runtime made with `debug:
|
|
58
|
+
* The debug handle of a runtime made with `debug: { hostClock }`.
|
|
58
59
|
*/
|
|
59
60
|
export interface Handle<S extends Schema.Any = Schema.Any, Root = unknown> {
|
|
60
61
|
/**
|
|
@@ -64,8 +65,15 @@ export interface Handle<S extends Schema.Any = Schema.Any, Root = unknown> {
|
|
|
64
65
|
* `onEnter(Game/Flow=Playing)`. Naming again replaces earlier names.
|
|
65
66
|
*/
|
|
66
67
|
readonly nameSchedules: (
|
|
67
|
-
schedules: Readonly<Record<string, ExecutableScheduleDefinition<S, any, Root, any, any>>>
|
|
68
|
+
schedules: Readonly<Record<string, ExecutableScheduleDefinition<S, any, Root, any, any, AnyScheduleOrder>>>
|
|
68
69
|
) => void
|
|
70
|
+
/**
|
|
71
|
+
* Declares the events the host publishes with `runtime.send`, so
|
|
72
|
+
* `describe()` counts them as written (`event-never-written`) before the
|
|
73
|
+
* first send happens. Events actually sent count as well. Declaring again
|
|
74
|
+
* replaces the list.
|
|
75
|
+
*/
|
|
76
|
+
readonly declareHostEvents: (events: ReadonlyArray<Schema.EventDescriptor<S>>) => void
|
|
69
77
|
/** The static description of the schema and the named schedules. */
|
|
70
78
|
readonly describe: () => Description
|
|
71
79
|
/** The current world as plain data. */
|
|
@@ -75,6 +83,21 @@ export interface Handle<S extends Schema.Any = Schema.Any, Root = unknown> {
|
|
|
75
83
|
* active only while at least one listener is registered.
|
|
76
84
|
*/
|
|
77
85
|
readonly observe: (listener: (event: TraceEvent) => void) => () => void
|
|
86
|
+
/**
|
|
87
|
+
* Delivers what the host does to the runtime, until the returned function
|
|
88
|
+
* is called: each tick's start and end (with the schedule values it runs)
|
|
89
|
+
* and each `runtime.send`. Unlike `observe`, it turns no tracing on, so a
|
|
90
|
+
* tool can listen for a whole play session (replay recorders do).
|
|
91
|
+
*/
|
|
92
|
+
readonly observeHost: (listener: (event: HostEvent<S, Root>) => void) => () => void
|
|
93
|
+
/**
|
|
94
|
+
* Reports the last failed tick result if nobody read its `ok`, as a
|
|
95
|
+
* `failure.ignored` event (to `observe` and `observeHost` listeners), with
|
|
96
|
+
* `when: "session-end"`. Call it when a session ends; the start of the
|
|
97
|
+
* next tick checks the same with `when: "next-tick"`. Emits nothing to a
|
|
98
|
+
* console.
|
|
99
|
+
*/
|
|
100
|
+
readonly checkIgnoredFailures: () => void
|
|
78
101
|
/** Retention and reader state of every stream that has readers or entries. */
|
|
79
102
|
readonly streams: () => ReadonlyArray<StreamStatus>
|
|
80
103
|
/** Live entity count and, per component, how many entities have it. Cheap enough to sample every frame. */
|
|
@@ -153,7 +176,15 @@ export interface SystemDescription {
|
|
|
153
176
|
readonly removed: ReadonlyArray<string>
|
|
154
177
|
readonly despawned: boolean
|
|
155
178
|
readonly relationFailures: ReadonlyArray<string>
|
|
179
|
+
/** Events and resources read with `Game.System.thisTick`, and query slots as `queries.<slot>`. */
|
|
180
|
+
readonly thisTick: ReadonlyArray<string>
|
|
156
181
|
readonly services: ReadonlyArray<string>
|
|
182
|
+
/**
|
|
183
|
+
* The declared command permissions (`Game.System.commands(...)`), by
|
|
184
|
+
* kind: component names for spawns, inserts, removes and despawns, scope
|
|
185
|
+
* names for despawnsScope, relation names for relates.
|
|
186
|
+
*/
|
|
187
|
+
readonly commands: CommandPermissions
|
|
157
188
|
/** Run conditions, rendered, for example `stateChanged(Game/Flow)`. */
|
|
158
189
|
readonly when: ReadonlyArray<string>
|
|
159
190
|
/** Pause conditions, rendered: while one fails the system waits and holds its stream entries. */
|
|
@@ -162,6 +193,16 @@ export interface SystemDescription {
|
|
|
162
193
|
readonly local: boolean
|
|
163
194
|
}
|
|
164
195
|
|
|
196
|
+
/** Declared command permissions of one system, by kind. Empty lists when it declares none. */
|
|
197
|
+
export interface CommandPermissions {
|
|
198
|
+
readonly spawns: ReadonlyArray<string>
|
|
199
|
+
readonly inserts: ReadonlyArray<string>
|
|
200
|
+
readonly removes: ReadonlyArray<string>
|
|
201
|
+
readonly despawns: ReadonlyArray<string>
|
|
202
|
+
readonly despawnsScope: ReadonlyArray<string>
|
|
203
|
+
readonly relates: ReadonlyArray<string>
|
|
204
|
+
}
|
|
205
|
+
|
|
165
206
|
export type StepDescription =
|
|
166
207
|
| { readonly kind: "system"; readonly system: string }
|
|
167
208
|
| { readonly kind: "applyDeferred" }
|
|
@@ -175,17 +216,28 @@ export interface ScheduleDescription {
|
|
|
175
216
|
/**
|
|
176
217
|
* A finding about the described schedules. `warning` is almost certainly a
|
|
177
218
|
* bug; `info` is worth a look but can be intentional.
|
|
219
|
+
*
|
|
220
|
+
* Most lints read declarations only. Two also use what the runtime has seen
|
|
221
|
+
* so far: events sent with `runtime.send(...)` count as written, and
|
|
222
|
+
* `relation-failure-never-read` fires for a relation that applied commands
|
|
223
|
+
* have tried to relate or reorder (which can fail) while no described system
|
|
224
|
+
* reads its failures, so describe after the game has run for a while.
|
|
178
225
|
*/
|
|
179
226
|
export interface Lint {
|
|
180
227
|
readonly severity: "warning" | "info"
|
|
181
228
|
readonly code:
|
|
182
229
|
| "event-never-read"
|
|
230
|
+
| "relation-failure-never-read"
|
|
183
231
|
| "event-never-written"
|
|
184
232
|
| "next-state-never-applied"
|
|
185
233
|
| "component-never-read"
|
|
186
234
|
| "read-before-write"
|
|
235
|
+
| "this-tick-order"
|
|
236
|
+
| "command-permission-unused"
|
|
187
237
|
readonly message: string
|
|
188
238
|
readonly subject: string
|
|
239
|
+
/** What to change to resolve it (or, for `info`, when it is fine as it is). */
|
|
240
|
+
readonly fix: string
|
|
189
241
|
}
|
|
190
242
|
|
|
191
243
|
/** Readers and writers of one component, resource, or event. */
|
|
@@ -207,7 +259,8 @@ export interface Description {
|
|
|
207
259
|
readonly systems: ReadonlyArray<SystemDescription>
|
|
208
260
|
/**
|
|
209
261
|
* Readers and writers across the named schedules. Component writers are
|
|
210
|
-
* systems with a write query slot
|
|
262
|
+
* systems with a write query slot or a declared `spawns`, `inserts`, or
|
|
263
|
+
* `removes` permission for it.
|
|
211
264
|
*/
|
|
212
265
|
readonly access: {
|
|
213
266
|
readonly components: ReadonlyArray<AccessIndexEntry>
|
|
@@ -450,6 +503,45 @@ export interface RestoreEvent {
|
|
|
450
503
|
readonly ok: boolean
|
|
451
504
|
}
|
|
452
505
|
|
|
506
|
+
/** What `observeHost` delivers. */
|
|
507
|
+
export type HostEvent<S extends Schema.Any = Schema.Any, Root = unknown> =
|
|
508
|
+
| {
|
|
509
|
+
readonly type: "tick.start"
|
|
510
|
+
/** The schedule values passed to this `tick(...)` (or `tryTick`) call, in order. */
|
|
511
|
+
readonly schedules: ReadonlyArray<ExecutableScheduleDefinition<S, any, Root, any, any, AnyScheduleOrder>>
|
|
512
|
+
readonly frame: number
|
|
513
|
+
}
|
|
514
|
+
| {
|
|
515
|
+
readonly type: "tick.end"
|
|
516
|
+
readonly schedules: ReadonlyArray<ExecutableScheduleDefinition<S, any, Root, any, any, AnyScheduleOrder>>
|
|
517
|
+
readonly frame: number
|
|
518
|
+
/** `failed`: a system returned `Result.failure`; `defect`: one threw (rethrown after this event). */
|
|
519
|
+
readonly outcome: "ok" | "failed" | "defect"
|
|
520
|
+
readonly error?: unknown
|
|
521
|
+
}
|
|
522
|
+
| { readonly type: "send"; readonly event: string; readonly value: unknown }
|
|
523
|
+
| FailureIgnoredEvent
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* A failed `tick` / `tryTick` result whose `ok` nobody read: the failure
|
|
527
|
+
* went unnoticed, and the systems after the failing one did not run that
|
|
528
|
+
* tick. Found at the start of the next tick (`when: "next-tick"`) or by
|
|
529
|
+
* `checkIgnoredFailures()` (`when: "session-end"`). Debug runtimes only.
|
|
530
|
+
*/
|
|
531
|
+
export interface FailureIgnoredEvent {
|
|
532
|
+
readonly type: "failure.ignored"
|
|
533
|
+
/** The frame of the failed tick. */
|
|
534
|
+
readonly frame: number
|
|
535
|
+
/** The schedule holding the failing system (the first one for a tick that ran nothing). */
|
|
536
|
+
readonly schedule: string
|
|
537
|
+
/** The failing system; `undefined` when `tryTick` ran nothing (missing requirements, order). */
|
|
538
|
+
readonly system: string | undefined
|
|
539
|
+
readonly error: unknown
|
|
540
|
+
/** The systems of the tick that did not run because of the failure, in order. */
|
|
541
|
+
readonly skipped: ReadonlyArray<string>
|
|
542
|
+
readonly when: "next-tick" | "session-end"
|
|
543
|
+
}
|
|
544
|
+
|
|
453
545
|
/** Everything `observe` delivers. */
|
|
454
546
|
export type TraceEvent =
|
|
455
547
|
| FrameEvent
|
|
@@ -461,14 +553,21 @@ export type TraceEvent =
|
|
|
461
553
|
| DeferredEvent
|
|
462
554
|
| TransitionEvent
|
|
463
555
|
| RestoreEvent
|
|
556
|
+
| FailureIgnoredEvent
|
|
464
557
|
|
|
465
558
|
/**
|
|
466
|
-
* The `debug` runtime option: `
|
|
559
|
+
* The `debug` runtime option: `{ hostClock }` attaches a handle, timing
|
|
560
|
+
* traces with that clock. A value that may be `undefined`
|
|
561
|
+
* (`import.meta.env.DEV ? { hostClock } : undefined`) attaches it when
|
|
562
|
+
* present and types `debug` as optional, so code checks it before use.
|
|
467
563
|
*/
|
|
468
|
-
export
|
|
564
|
+
export interface Option {
|
|
565
|
+
/** Times trace events (`ms`); see `HostClock`. */
|
|
566
|
+
readonly hostClock: HostClock
|
|
567
|
+
}
|
|
469
568
|
|
|
470
569
|
/**
|
|
471
|
-
* The member a runtime made with `debug:
|
|
570
|
+
* The member a runtime made with `debug: { hostClock }` carries.
|
|
472
571
|
*/
|
|
473
572
|
export interface Member<S extends Schema.Any, Root> {
|
|
474
573
|
readonly debug: Handle<S, Root>
|
|
@@ -476,4 +575,6 @@ export interface Member<S extends Schema.Any, Root> {
|
|
|
476
575
|
|
|
477
576
|
/** Resolves the extra runtime member for a `debug` option type. */
|
|
478
577
|
export type MemberFor<Enabled, S extends Schema.Any, Root> =
|
|
479
|
-
[Enabled] extends [
|
|
578
|
+
[Enabled] extends [true] ? Member<S, Root>
|
|
579
|
+
: [Enabled] extends [false | undefined] ? {}
|
|
580
|
+
: Partial<Member<S, Root>>
|