@typeonce/ritmo 0.7.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/dist/Command.d.ts +102 -17
- package/dist/Command.d.ts.map +1 -1
- package/dist/Command.js +10 -8
- 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 +63 -7
- package/dist/Debug.d.ts.map +1 -1
- package/dist/Descriptor.d.ts +36 -3
- package/dist/Descriptor.d.ts.map +1 -1
- package/dist/Descriptor.js +11 -3
- package/dist/Descriptor.js.map +1 -1
- package/dist/Entity.d.ts +12 -4
- package/dist/Entity.d.ts.map +1 -1
- package/dist/Entity.js.map +1 -1
- package/dist/EntityScope.d.ts +20 -1
- package/dist/EntityScope.d.ts.map +1 -1
- package/dist/EntityScope.js +8 -2
- package/dist/EntityScope.js.map +1 -1
- package/dist/Machine.d.ts +55 -33
- package/dist/Machine.d.ts.map +1 -1
- package/dist/Machine.js +6 -2
- package/dist/Machine.js.map +1 -1
- package/dist/Query.d.ts +48 -4
- package/dist/Query.d.ts.map +1 -1
- package/dist/Query.js +32 -4
- package/dist/Query.js.map +1 -1
- package/dist/Runtime.d.ts +3 -0
- package/dist/Runtime.d.ts.map +1 -1
- package/dist/Runtime.js +330 -24
- package/dist/Runtime.js.map +1 -1
- package/dist/Schema.d.ts +141 -25
- package/dist/Schema.d.ts.map +1 -1
- package/dist/Schema.js +29 -3
- 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 +64 -7
- package/dist/System.d.ts.map +1 -1
- package/dist/System.js +32 -0
- package/dist/System.js.map +1 -1
- package/dist/internal/debug.d.ts +7 -0
- package/dist/internal/debug.d.ts.map +1 -1
- package/dist/internal/debug.js +97 -6
- 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 +33 -5
- package/dist/internal/game.js.map +1 -1
- package/dist/internal/order.d.ts.map +1 -1
- package/dist/internal/order.js +4 -2
- package/dist/internal/order.js.map +1 -1
- package/dist/internal/queries.d.ts.map +1 -1
- package/dist/internal/queries.js +30 -8
- 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/Command.ts +149 -29
- package/src/Condition.ts +17 -1
- package/src/Debug.ts +64 -7
- package/src/Descriptor.ts +55 -10
- package/src/Entity.ts +13 -4
- package/src/EntityScope.ts +21 -2
- package/src/Machine.ts +68 -34
- package/src/Query.ts +71 -7
- package/src/Runtime.ts +336 -22
- package/src/Schema.ts +185 -28
- package/src/Snapshot.ts +4 -0
- package/src/System.ts +75 -6
- package/src/internal/debug.ts +104 -8
- package/src/internal/equal.ts +31 -0
- package/src/internal/fragments.ts +12 -2
- package/src/internal/game.ts +51 -5
- package/src/internal/order.ts +8 -4
- package/src/internal/queries.ts +34 -10
- package/src/internal/requirements.ts +71 -0
package/src/Schema.ts
CHANGED
|
@@ -75,20 +75,80 @@ export type Registry = Record<string, Descriptor.Any>
|
|
|
75
75
|
|
|
76
76
|
/**
|
|
77
77
|
* A closed schema: the components, resources, events, and relations a world
|
|
78
|
-
* may contain.
|
|
78
|
+
* may contain, and the component requirements (`Schema.requires`) every
|
|
79
|
+
* entity of it keeps.
|
|
79
80
|
*/
|
|
80
81
|
export interface SchemaDefinition<
|
|
81
82
|
out Components extends Registry = {},
|
|
82
83
|
out Resources extends Registry = {},
|
|
83
84
|
out Events extends Registry = {},
|
|
84
|
-
out Relations extends Record<string, Relation.Relation.Any> = {}
|
|
85
|
+
out Relations extends Record<string, Relation.Relation.Any> = {},
|
|
86
|
+
out Requires extends RequirementPair = never
|
|
85
87
|
> {
|
|
86
88
|
readonly components: Components
|
|
87
89
|
readonly resources: Resources
|
|
88
90
|
readonly events: Events
|
|
89
91
|
readonly relations: Relations
|
|
92
|
+
/** Component requirements declared with `Schema.requires(...)`. */
|
|
93
|
+
readonly requires: ReadonlyArray<ComponentRequirement<Requires>>
|
|
90
94
|
}
|
|
91
95
|
|
|
96
|
+
/**
|
|
97
|
+
* One requirement carried in a schema type, by component name:
|
|
98
|
+
* `[requirer, required]`. One pair per required component, so lookups by
|
|
99
|
+
* either end stay exact.
|
|
100
|
+
*/
|
|
101
|
+
export type RequirementPair = readonly [string, string]
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* One `Schema.requires(...)` declaration: every entity holding `component`
|
|
105
|
+
* also holds each of `required`.
|
|
106
|
+
*/
|
|
107
|
+
export interface ComponentRequirement<out Pairs extends RequirementPair = RequirementPair> {
|
|
108
|
+
readonly kind: "requires"
|
|
109
|
+
readonly component: Descriptor<"component", string, any>
|
|
110
|
+
readonly required: ReadonlyArray<Descriptor<"component", string, any>>
|
|
111
|
+
readonly __pairs?: (_: never) => Pairs
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
type AnyComponentDescriptor = Descriptor<"component", string, any>
|
|
115
|
+
|
|
116
|
+
/** One `[from, to]` pair per required name. */
|
|
117
|
+
type RequirementPairs<From extends string, To extends string> = To extends string ? readonly [From, To] : never
|
|
118
|
+
|
|
119
|
+
type PairsOf<R> = R extends ComponentRequirement<infer Pairs> ? Pairs : never
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Declares that every entity holding `component` also holds each of
|
|
123
|
+
* `required`. List it in a fragment's `requires`.
|
|
124
|
+
*
|
|
125
|
+
* The compiler then rejects every command that would break it:
|
|
126
|
+
* `commands.spawn` / `spawnIn` of a draft missing a required entry,
|
|
127
|
+
* `commands.insert` of `component` on an entity not proven to hold the
|
|
128
|
+
* rest, and `commands.remove` of a required component while a requirer may
|
|
129
|
+
* stay. `Game.System.spawns(component)` also allows spawning what it
|
|
130
|
+
* requires. Nothing is ever filled in with a default: the draft must carry
|
|
131
|
+
* every value.
|
|
132
|
+
*
|
|
133
|
+
* Requirements chain (`A` requires `B`, `B` requires `C`) and may name
|
|
134
|
+
* components of other fragments of the same `Schema.bind`.
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* ```ts
|
|
138
|
+
* const Gameplay = Schema.fragment({
|
|
139
|
+
* components: { Player, Position, Velocity },
|
|
140
|
+
* requires: [Schema.requires(Player, Position, Velocity)]
|
|
141
|
+
* })
|
|
142
|
+
* ```
|
|
143
|
+
*/
|
|
144
|
+
export const requires = <
|
|
145
|
+
D extends AnyComponentDescriptor,
|
|
146
|
+
const Required extends readonly [AnyComponentDescriptor, ...Array<AnyComponentDescriptor>]
|
|
147
|
+
>(
|
|
148
|
+
component: D,
|
|
149
|
+
...required: Required
|
|
150
|
+
): ComponentRequirement<RequirementPairs<D["name"], Required[number]["name"]>> => ({ kind: "requires", component, required })
|
|
151
|
+
|
|
92
152
|
const schemaRootTypeId = "~ritmo/SchemaRoot" as const
|
|
93
153
|
|
|
94
154
|
export type RootToken<Name extends string = string> = {
|
|
@@ -112,7 +172,8 @@ export type MergeSchemaDefinitions<
|
|
|
112
172
|
Schema.Components<A> & Schema.Components<B>,
|
|
113
173
|
Schema.Resources<A> & Schema.Resources<B>,
|
|
114
174
|
Schema.Events<A> & Schema.Events<B>,
|
|
115
|
-
Schema.Relations<A> & Schema.Relations<B
|
|
175
|
+
Schema.Relations<A> & Schema.Relations<B>,
|
|
176
|
+
Schema.Requires<A> | Schema.Requires<B>
|
|
116
177
|
>
|
|
117
178
|
|
|
118
179
|
/**
|
|
@@ -186,6 +247,24 @@ export type ValidateFragments<Fragments extends ReadonlyArray<Schema.Any>> =
|
|
|
186
247
|
readonly __schemaConflicts__: FragmentConflicts<Fragments>
|
|
187
248
|
}
|
|
188
249
|
|
|
250
|
+
type UnregisteredRequirements<S extends Schema.Any> =
|
|
251
|
+
Exclude<Schema.Requires<S>[0] | Schema.Requires<S>[1], Schema.ComponentDescriptor<S>["name"]>
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Rejects requirements naming a component the bound schema does not
|
|
255
|
+
* register. Checked at `bind`, so a fragment may require a component another
|
|
256
|
+
* fragment registers. Erased schemas (`Schema.Any`) are checked by `bind` at
|
|
257
|
+
* runtime instead.
|
|
258
|
+
*/
|
|
259
|
+
export type ValidateRequires<S extends Schema.Any> =
|
|
260
|
+
[UnregisteredRequirements<S>] extends [never]
|
|
261
|
+
? unknown
|
|
262
|
+
: string extends Schema.Requires<S>[1]
|
|
263
|
+
? unknown
|
|
264
|
+
: {
|
|
265
|
+
readonly __schemaRequires__: `${UnregisteredRequirements<S>} is named in Schema.requires(...) but no fragment registers it: add it to a fragment's components`
|
|
266
|
+
}
|
|
267
|
+
|
|
189
268
|
/**
|
|
190
269
|
* Creates one explicit root token for schema-bound long-lived references.
|
|
191
270
|
*
|
|
@@ -224,8 +303,9 @@ export const empty = (): SchemaDefinition => emptySchema()
|
|
|
224
303
|
* @example
|
|
225
304
|
* ```ts
|
|
226
305
|
* const Combat = Schema.fragment({
|
|
227
|
-
* components: { Health, Damage },
|
|
228
|
-
* events: { Hit }
|
|
306
|
+
* components: { Health, Damage, Enemy },
|
|
307
|
+
* events: { Hit },
|
|
308
|
+
* requires: [Schema.requires(Enemy, Health)]
|
|
229
309
|
* })
|
|
230
310
|
* ```
|
|
231
311
|
*/
|
|
@@ -233,19 +313,22 @@ export const fragment = <
|
|
|
233
313
|
const Components extends Registry = {},
|
|
234
314
|
const Resources extends Registry = {},
|
|
235
315
|
const Events extends Registry = {},
|
|
236
|
-
const Relations extends Record<string, Relation.Relation.Any> = {}
|
|
316
|
+
const Relations extends Record<string, Relation.Relation.Any> = {},
|
|
317
|
+
const Requires extends ReadonlyArray<ComponentRequirement> = readonly []
|
|
237
318
|
>(definition: {
|
|
238
319
|
readonly components?: Components
|
|
239
320
|
readonly resources?: Resources
|
|
240
321
|
readonly events?: Events
|
|
241
322
|
readonly relations?: Relations
|
|
242
|
-
|
|
323
|
+
readonly requires?: Requires
|
|
324
|
+
} & ValidateFragments<[SchemaDefinition<Components, Resources, Events, Relations>]>): SchemaDefinition<Components, Resources, Events, Relations, PairsOf<Requires[number]>> =>
|
|
243
325
|
mergeSchemas(emptySchema(), {
|
|
244
326
|
components: definition.components ?? {},
|
|
245
327
|
resources: definition.resources ?? {},
|
|
246
328
|
events: definition.events ?? {},
|
|
247
|
-
relations: definition.relations ?? {}
|
|
248
|
-
|
|
329
|
+
relations: definition.relations ?? {},
|
|
330
|
+
requires: definition.requires ?? []
|
|
331
|
+
}) as SchemaDefinition<Components, Resources, Events, Relations, PairsOf<Requires[number]>>
|
|
249
332
|
|
|
250
333
|
/**
|
|
251
334
|
* Composes one or more fragments and returns one bound `Game` surface.
|
|
@@ -262,18 +345,18 @@ export const fragment = <
|
|
|
262
345
|
*/
|
|
263
346
|
export function bind<
|
|
264
347
|
S extends Schema.Any
|
|
265
|
-
>(schema: S & ValidateFragments<[S]>): Schema.Game<S, S>
|
|
348
|
+
>(schema: S & ValidateFragments<[S]> & ValidateRequires<S>): Schema.Game<S, S>
|
|
266
349
|
export function bind<
|
|
267
350
|
S extends Schema.Any,
|
|
268
351
|
const Name extends string
|
|
269
|
-
>(schema: S & ValidateFragments<[S]>, root: RootToken<Name>): Schema.Game<S, RootToken<Name>>
|
|
352
|
+
>(schema: S & ValidateFragments<[S]> & ValidateRequires<S>, root: RootToken<Name>): Schema.Game<S, RootToken<Name>>
|
|
270
353
|
export function bind<
|
|
271
354
|
const Fragments extends readonly [Schema.Any, Schema.Any, ...Array<Schema.Any>]
|
|
272
|
-
>(...fragments: Fragments & ValidateFragments<Fragments>): Schema.Game<BuildFragments<Fragments>, BuildFragments<Fragments>>
|
|
355
|
+
>(...fragments: Fragments & ValidateFragments<Fragments> & ValidateRequires<BuildFragments<Fragments>>): Schema.Game<BuildFragments<Fragments>, BuildFragments<Fragments>>
|
|
273
356
|
export function bind<
|
|
274
357
|
const Fragments extends readonly [Schema.Any, Schema.Any, ...Array<Schema.Any>],
|
|
275
358
|
const Name extends string
|
|
276
|
-
>(...args: [...Fragments, RootToken<Name>] & ValidateFragments<Fragments>): Schema.Game<BuildFragments<Fragments>, RootToken<Name>>
|
|
359
|
+
>(...args: [...Fragments, RootToken<Name>] & ValidateFragments<Fragments> & ValidateRequires<BuildFragments<Fragments>>): Schema.Game<BuildFragments<Fragments>, RootToken<Name>>
|
|
277
360
|
export function bind(...args: ReadonlyArray<Schema.Any | RootToken>) {
|
|
278
361
|
const maybeRoot = args[args.length - 1]
|
|
279
362
|
const fragments = (isRootToken(maybeRoot) ? args.slice(0, -1) : args) as Array<Schema.Any>
|
|
@@ -298,7 +381,7 @@ type QuerySelectionAccess<S extends Schema.Any, Root> =
|
|
|
298
381
|
|
|
299
382
|
interface BoundSystemAccess<S extends Schema.Any, Root> extends System.SystemAccessInput {
|
|
300
383
|
readonly queries?: Record<string, Query.Any<Root>>
|
|
301
|
-
readonly resources?: Record<string, System.ResourceRead<ResourceDescriptor<S>> | System.ResourceWrite<ResourceDescriptor<S>>>
|
|
384
|
+
readonly resources?: Record<string, System.ResourceRead<ResourceDescriptor<S>> | System.ResourceChanged<ResourceDescriptor<S>> | System.ResourceWrite<ResourceDescriptor<S>>>
|
|
302
385
|
readonly events?: Record<string, System.EventRead<EventDescriptor<S>> | System.EventWrite<EventDescriptor<S>>>
|
|
303
386
|
readonly machines?: Record<string, Machine.MachineRead<Schema.BoundStateMachine<Root>>>
|
|
304
387
|
readonly nextMachines?: Record<string, Machine.NextMachineWrite<Schema.BoundStateMachine<Root>>>
|
|
@@ -314,7 +397,7 @@ interface BoundSystemAccess<S extends Schema.Any, Root> extends System.SystemAcc
|
|
|
314
397
|
|
|
315
398
|
interface BoundCheckAccess<S extends Schema.Any, Root> extends Condition.CheckAccessInput {
|
|
316
399
|
readonly queries?: Record<string, Query.Any<Root>>
|
|
317
|
-
readonly resources?: Record<string, System.ResourceRead<ResourceDescriptor<S>>>
|
|
400
|
+
readonly resources?: Record<string, System.ResourceRead<ResourceDescriptor<S>> | System.ResourceChanged<ResourceDescriptor<S>> | System.ResourceWrite<ResourceDescriptor<S>>>
|
|
318
401
|
readonly machines?: Record<string, Machine.MachineRead<Schema.BoundStateMachine<Root>>>
|
|
319
402
|
}
|
|
320
403
|
|
|
@@ -376,12 +459,14 @@ export namespace Schema {
|
|
|
376
459
|
/**
|
|
377
460
|
* Any complete schema definition.
|
|
378
461
|
*/
|
|
379
|
-
export type Any = SchemaDefinition<Registry, Registry, Registry, Record<string, Relation.Relation.Any
|
|
462
|
+
export type Any = SchemaDefinition<Registry, Registry, Registry, Record<string, Relation.Relation.Any>, RequirementPair>
|
|
380
463
|
|
|
381
464
|
export type Components<T extends Any> = T["components"]
|
|
382
465
|
export type Resources<T extends Any> = T["resources"]
|
|
383
466
|
export type Events<T extends Any> = T["events"]
|
|
384
467
|
export type Relations<T extends Any> = T["relations"]
|
|
468
|
+
/** The component requirements of a schema, as `[requirer, required]` name pairs (`Schema.requires`). */
|
|
469
|
+
export type Requires<T extends Any> = T extends SchemaDefinition<any, any, any, any, infer Pairs> ? Pairs : never
|
|
385
470
|
|
|
386
471
|
export type ComponentValue<T extends Any, K extends keyof Components<T>> = Descriptor.Value<Components<T>[K]>
|
|
387
472
|
export type ResourceValue<T extends Any, K extends keyof Resources<T>> = Descriptor.Value<Resources<T>[K]>
|
|
@@ -434,8 +519,9 @@ export namespace Schema {
|
|
|
434
519
|
export type BoundStateMachine<
|
|
435
520
|
Root,
|
|
436
521
|
Name extends string = string,
|
|
437
|
-
Values extends readonly [Machine.StateValue, ...Machine.StateValue[]] = readonly [Machine.StateValue, ...Machine.StateValue[]]
|
|
438
|
-
|
|
522
|
+
Values extends readonly [Machine.StateValue, ...Machine.StateValue[]] = readonly [Machine.StateValue, ...Machine.StateValue[]],
|
|
523
|
+
Parent = unknown
|
|
524
|
+
> = Machine.StateMachineDefinition<Name, Values, Root, Parent>
|
|
439
525
|
|
|
440
526
|
export type BoundTransitionSchedule<
|
|
441
527
|
S extends Any,
|
|
@@ -473,11 +559,38 @@ export namespace Schema {
|
|
|
473
559
|
readonly schema: S
|
|
474
560
|
/**
|
|
475
561
|
* Creates a typed ownership scope for scene or level entities: a flat
|
|
476
|
-
* group
|
|
477
|
-
* `Descriptor.Hierarchy` instead for a
|
|
478
|
-
* children, traversal, and recursive
|
|
562
|
+
* group whose members are spawned with `commands.spawnIn(...)` and
|
|
563
|
+
* despawned together. Use `Descriptor.Hierarchy` instead for a
|
|
564
|
+
* parent-child tree with ordered children, traversal, and recursive
|
|
565
|
+
* despawn.
|
|
566
|
+
*
|
|
567
|
+
* When the group lives exactly as long as a state, name the state change
|
|
568
|
+
* that ends it: `despawnOnExit: [Flow, "Playing"]` despawns every member
|
|
569
|
+
* when `applyStateTransitions(...)` commits leaving `Playing` (after its
|
|
570
|
+
* `onExit` schedules, which still see the members), and
|
|
571
|
+
* `despawnOnEnter: [Flow, "Playing"]` when it commits entering it (before
|
|
572
|
+
* its `onEnter` schedules, so what they spawn survives). Use
|
|
573
|
+
* `commands.despawnScope(...)` instead when the wipe is not tied to a
|
|
574
|
+
* state change. Scope names are unique per game.
|
|
575
|
+
*
|
|
576
|
+
* @example
|
|
577
|
+
* ```ts
|
|
578
|
+
* const RunScope = Game.EntityScope("Shooter/Run", { despawnOnExit: [Flow, "Playing"] })
|
|
579
|
+
* ```
|
|
479
580
|
*/
|
|
480
|
-
readonly EntityScope: <
|
|
581
|
+
readonly EntityScope: <
|
|
582
|
+
const Name extends string,
|
|
583
|
+
Enter extends Machine.StateMachine.Any = never,
|
|
584
|
+
Exit extends Machine.StateMachine.Any = never
|
|
585
|
+
>(
|
|
586
|
+
name: Name,
|
|
587
|
+
options?: {
|
|
588
|
+
/** Despawns every member when the machine enters this state, before its `onEnter` schedules run. */
|
|
589
|
+
readonly despawnOnEnter?: readonly [Enter & Machine.SameGame<Enter, Root>, Machine.StateMachine.Value<Enter>]
|
|
590
|
+
/** Despawns every member when the machine leaves this state, after its `onExit` schedules run. */
|
|
591
|
+
readonly despawnOnExit?: readonly [Exit & Machine.SameGame<Exit, Root>, Machine.StateMachine.Value<Exit>]
|
|
592
|
+
}
|
|
593
|
+
) => EntityScope.EntityScope<Name, Root>
|
|
481
594
|
/** Declares a reusable, requirement-checked read-only world projection. */
|
|
482
595
|
readonly Inspector: <
|
|
483
596
|
const Name extends string,
|
|
@@ -520,10 +633,17 @@ export namespace Schema {
|
|
|
520
633
|
// Uninferred: an omitted list is `[]`, never inferred from the spec it is written in (`queries: { ... }`).
|
|
521
634
|
}): QueryModule.QuerySpec<Selection, QueryModule.Uninferred<With, ReadonlyArray<ComponentDescriptor<S>>>, QueryModule.Uninferred<Without, ReadonlyArray<ComponentDescriptor<S>>>, QueryModule.Uninferred<Filters, ReadonlyArray<QueryModule.Filter<ComponentDescriptor<S>>>>, QueryModule.Uninferred<WithRelations, ReadonlyArray<Relation.Relation.Any>>, QueryModule.Uninferred<WithoutRelations, ReadonlyArray<Relation.Relation.Any>>, QueryModule.Uninferred<WithRelated, ReadonlyArray<Relation.Relation.Any>>, QueryModule.Uninferred<WithoutRelated, ReadonlyArray<Relation.Relation.Any>>, Root>
|
|
522
635
|
read: <D extends ComponentDescriptor<S>>(descriptor: D) => QueryModule.ReadAccess<D>
|
|
523
|
-
write: <D extends ComponentDescriptor<S>>(descriptor: D) => QueryModule.WriteAccess<D>
|
|
636
|
+
write: <D extends ComponentDescriptor<S>>(descriptor: D & QueryModule.Writable<D>) => QueryModule.WriteAccess<D>
|
|
524
637
|
optional: <D extends ComponentDescriptor<S>>(descriptor: D) => QueryModule.OptionalReadAccess<D>
|
|
525
638
|
added: <D extends ComponentDescriptor<S>>(descriptor: D) => QueryModule.AddedFilter<D>
|
|
526
639
|
changed: <D extends ComponentDescriptor<S>>(descriptor: D) => QueryModule.ChangedFilter<D>
|
|
640
|
+
anyOf: <
|
|
641
|
+
const Filters extends readonly [
|
|
642
|
+
QueryModule.LifecycleFilter<ComponentDescriptor<S>>,
|
|
643
|
+
QueryModule.LifecycleFilter<ComponentDescriptor<S>>,
|
|
644
|
+
...ReadonlyArray<QueryModule.LifecycleFilter<ComponentDescriptor<S>>>
|
|
645
|
+
]
|
|
646
|
+
>(...filters: Filters) => QueryModule.AnyOfFilter<Filters[number]["descriptor"]>
|
|
527
647
|
readRelation: <R extends RelationDescriptor<S>>(descriptor: R) => Relation.RelationReadAccess<R, S, Root>
|
|
528
648
|
optionalRelation: <R extends RelationDescriptor<S>>(descriptor: R) => Relation.OptionalRelationReadAccess<R, S, Root>
|
|
529
649
|
readRelated: <R extends RelationDescriptor<S>>(descriptor: R) => Relation.RelatedReadAccess<R, S, Root>
|
|
@@ -564,10 +684,46 @@ export namespace Schema {
|
|
|
564
684
|
const Name extends string,
|
|
565
685
|
const Values extends readonly [Machine.StateValue, ...Machine.StateValue[]]
|
|
566
686
|
>(name: Name, values: Values): Schema.BoundStateMachine<Root, Name, Values>
|
|
687
|
+
/**
|
|
688
|
+
* Declares a sub-state: a machine that exists only while `within[0]` is
|
|
689
|
+
* in state `within[1]` (a battle phase that only means something while
|
|
690
|
+
* playing). Whenever the parent enters that state, the sub-state enters
|
|
691
|
+
* the value given to `Game.Runtime.machine(...)`; when the parent leaves
|
|
692
|
+
* it, the sub-state exits and has no value: `get()` is `undefined`,
|
|
693
|
+
* `Game.Condition.inState(Sub, x)` is false, and `nextMachines.sub.set(...)`
|
|
694
|
+
* returns a `SubStateInactive` failure. So `inState(Sub, x)` alone
|
|
695
|
+
* implies the parent's state.
|
|
696
|
+
*
|
|
697
|
+
* Use a second top-level machine instead when the two modes are
|
|
698
|
+
* independent (each keeps its value whatever the other does).
|
|
699
|
+
*
|
|
700
|
+
* @example
|
|
701
|
+
* ```ts
|
|
702
|
+
* const Phase = Game.StateMachine("Phase", ["Setup", "Battle"], { within: [Session, "Playing"] })
|
|
703
|
+
* ```
|
|
704
|
+
*/
|
|
705
|
+
<
|
|
706
|
+
const Name extends string,
|
|
707
|
+
const Values extends readonly [Machine.StateValue, ...Machine.StateValue[]],
|
|
708
|
+
Parent extends Machine.StateMachine.Any
|
|
709
|
+
>(
|
|
710
|
+
name: Name,
|
|
711
|
+
values: Values,
|
|
712
|
+
options: { readonly within: readonly [Parent & Machine.SameGame<Parent, Root>, Machine.StateMachine.Value<Parent>] }
|
|
713
|
+
): Schema.BoundStateMachine<Root, Name, Values, Parent>
|
|
567
714
|
}
|
|
568
715
|
readonly Condition: {
|
|
569
|
-
|
|
570
|
-
|
|
716
|
+
/**
|
|
717
|
+
* Passes while the machine's committed value is `value`. For a
|
|
718
|
+
* sub-state it fails while the parent is in another state. A machine
|
|
719
|
+
* of another game fails to compile with a message naming the fix.
|
|
720
|
+
*/
|
|
721
|
+
inState: <M extends Machine.StateMachine.Any>(
|
|
722
|
+
machine: M & Machine.SameGame<M, Root>,
|
|
723
|
+
value: Machine.StateMachine.Value<M>
|
|
724
|
+
) => Machine.InStateCondition<M>
|
|
725
|
+
/** Passes in the schedule run where `applyStateTransitions(...)` changed the machine. */
|
|
726
|
+
stateChanged: <M extends Machine.StateMachine.Any>(machine: M & Machine.SameGame<M, Root>) => Machine.StateChangedCondition<M>
|
|
571
727
|
not: typeof Machine.not
|
|
572
728
|
and: typeof Machine.and
|
|
573
729
|
or: typeof Machine.or
|
|
@@ -594,6 +750,7 @@ export namespace Schema {
|
|
|
594
750
|
): BoundSystem<S, Root, System.SystemSpec<S, Access, Root>, E, Name, System.SystemAccessNeeds<Access>>
|
|
595
751
|
readResource: <D extends ResourceDescriptor<S>>(descriptor: D) => System.ResourceRead<D>
|
|
596
752
|
writeResource: <D extends ResourceDescriptor<S>>(descriptor: D) => System.ResourceWrite<D>
|
|
753
|
+
changedResource: <D extends ResourceDescriptor<S>>(descriptor: D) => System.ResourceChanged<D>
|
|
597
754
|
readEvent: <D extends EventDescriptor<S>>(descriptor: D) => System.EventRead<D>
|
|
598
755
|
writeEvent: <D extends EventDescriptor<S>>(descriptor: D) => System.EventWrite<D>
|
|
599
756
|
/**
|
|
@@ -601,7 +758,7 @@ export namespace Schema {
|
|
|
601
758
|
* same `runtime.tick(...)` call; checked at compile time by `tick`.
|
|
602
759
|
* Accepts `readEvent(...)`, `readResource(...)`, or a query.
|
|
603
760
|
*/
|
|
604
|
-
thisTick: <Read extends System.EventRead<EventDescriptor<S>> | System.ResourceRead<ResourceDescriptor<S>> | Query.Any<Root>>(
|
|
761
|
+
thisTick: <Read extends System.EventRead<EventDescriptor<S>> | System.ResourceRead<ResourceDescriptor<S>> | System.ResourceChanged<ResourceDescriptor<S>> | Query.Any<Root>>(
|
|
605
762
|
read: Read
|
|
606
763
|
) => Read & System.ThisTick
|
|
607
764
|
service: typeof System.service
|
|
@@ -617,8 +774,8 @@ export namespace Schema {
|
|
|
617
774
|
* `Game.System.commands(Game.System.spawns(Enemy, Position), Game.System.despawns(Enemy))`.
|
|
618
775
|
*/
|
|
619
776
|
commands: typeof Command.commands
|
|
620
|
-
/** Allows spawning drafts whose components are all among these; see `Command.spawns`. */
|
|
621
|
-
spawns: <const Components extends ReadonlyArray<ComponentDescriptor<S>>>(...components: Components) => Command.Permission<"spawns", Components[number]
|
|
777
|
+
/** Allows spawning drafts whose components are all among these and what they require (`Schema.requires`); see `Command.spawns`. */
|
|
778
|
+
spawns: <const Components extends ReadonlyArray<ComponentDescriptor<S>>>(...components: Components) => Command.Permission<"spawns", Components[number] | Command.RequiredDescriptors<S, Components[number]["name"]>>
|
|
622
779
|
/** Allows `insert` (add or replace at the next `applyDeferred()`) of these; see `Command.inserts`. */
|
|
623
780
|
inserts: <const Components extends ReadonlyArray<ComponentDescriptor<S>>>(...components: Components) => Command.Permission<"inserts", Components[number]>
|
|
624
781
|
/** Allows `remove` of these. */
|
package/src/Snapshot.ts
CHANGED
|
@@ -115,12 +115,16 @@ export type RestoreError =
|
|
|
115
115
|
| { readonly _tag: "UnsupportedVersion"; readonly version: unknown }
|
|
116
116
|
| { readonly _tag: "UnknownComponent"; readonly entityId: number; readonly name: string }
|
|
117
117
|
| { readonly _tag: "InvalidComponent"; readonly entityId: number; readonly name: string; readonly error: unknown }
|
|
118
|
+
/** A saved entity holds `requiredBy` but lacks `name`, which `Schema.requires` says it needs. */
|
|
119
|
+
| { readonly _tag: "MissingRequiredComponent"; readonly entityId: number; readonly name: string; readonly requiredBy: string }
|
|
118
120
|
| { readonly _tag: "UnknownResource"; readonly name: string }
|
|
119
121
|
| { readonly _tag: "InvalidResource"; readonly name: string; readonly error: unknown }
|
|
120
122
|
| { readonly _tag: "UnknownRelation"; readonly name: string }
|
|
121
123
|
| { readonly _tag: "InvalidRelation"; readonly name: string; readonly sourceId: number; readonly targetId: number }
|
|
122
124
|
| { readonly _tag: "UnknownMachine"; readonly name: string }
|
|
123
125
|
| { readonly _tag: "InvalidMachineState"; readonly name: string; readonly value: unknown }
|
|
126
|
+
/** A saved sub-state value while its parent machine is not in the state the sub-state exists in. */
|
|
127
|
+
| { readonly _tag: "InactiveSubState"; readonly name: string; readonly parent: string; readonly requires: unknown }
|
|
124
128
|
| { readonly _tag: "DuplicateEntity"; readonly entityId: number }
|
|
125
129
|
/** A scope member that is not a saved entity, or is already in another scope. */
|
|
126
130
|
| { readonly _tag: "InvalidScope"; readonly name: string; readonly entityId: number }
|
package/src/System.ts
CHANGED
|
@@ -193,6 +193,50 @@ export const writeResource = <D extends Descriptor<"resource", string, any>>(
|
|
|
193
193
|
descriptor
|
|
194
194
|
})
|
|
195
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
|
+
|
|
196
240
|
/**
|
|
197
241
|
* Declares read-only access to an event stream.
|
|
198
242
|
*/
|
|
@@ -314,7 +358,7 @@ export interface ThisTick {
|
|
|
314
358
|
* ```
|
|
315
359
|
*/
|
|
316
360
|
export const thisTick = <
|
|
317
|
-
Read extends EventRead<Descriptor<"event", string, any>> | ResourceRead<Descriptor<"resource", string, any>> | Query.Any<any>
|
|
361
|
+
Read extends EventRead<Descriptor<"event", string, any>> | ResourceRead<Descriptor<"resource", string, any>> | ResourceChanged<Descriptor<"resource", string, any>> | Query.Any<any>
|
|
318
362
|
>(read: Read): Read & ThisTick => ({ ...read, thisTick: true })
|
|
319
363
|
|
|
320
364
|
/**
|
|
@@ -367,7 +411,10 @@ export const readTransitionEvent = <M extends Machine.StateMachine.Any>(
|
|
|
367
411
|
/**
|
|
368
412
|
* Internal union for all supported resource access declarations.
|
|
369
413
|
*/
|
|
370
|
-
type ResourceAccess =
|
|
414
|
+
type ResourceAccess =
|
|
415
|
+
| ResourceRead<Descriptor<"resource", string, any>>
|
|
416
|
+
| ResourceChanged<Descriptor<"resource", string, any>>
|
|
417
|
+
| ResourceWrite<Descriptor<"resource", string, any>>
|
|
371
418
|
/**
|
|
372
419
|
* Internal union for all supported event access declarations.
|
|
373
420
|
*/
|
|
@@ -378,6 +425,15 @@ type EventAccess = EventRead<Descriptor<"event", string, any>> | EventWrite<Desc
|
|
|
378
425
|
*/
|
|
379
426
|
export interface ResourceReadView<T> extends ReadCell<T> {}
|
|
380
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
|
+
|
|
381
437
|
/**
|
|
382
438
|
* A mutable view over a resource value.
|
|
383
439
|
*/
|
|
@@ -412,7 +468,8 @@ export interface EventWriteView<T> {
|
|
|
412
468
|
* A read-only view over one committed finite-state-machine value.
|
|
413
469
|
*/
|
|
414
470
|
export interface MachineReadView<M extends Machine.StateMachine.Any = Machine.StateMachine.Any> {
|
|
415
|
-
|
|
471
|
+
/** The committed value; `undefined` only for a sub-state whose parent is in another state. */
|
|
472
|
+
get(): Machine.CurrentValue<M>
|
|
416
473
|
is(value: Machine.StateMachine.Value<M>): boolean
|
|
417
474
|
}
|
|
418
475
|
|
|
@@ -421,20 +478,31 @@ export interface MachineReadView<M extends Machine.StateMachine.Any = Machine.St
|
|
|
421
478
|
*/
|
|
422
479
|
export interface NextMachineWriteView<M extends Machine.StateMachine.Any = Machine.StateMachine.Any> {
|
|
423
480
|
getPending(): Machine.StateMachine.Value<M> | undefined
|
|
424
|
-
|
|
425
|
-
|
|
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>
|
|
426
489
|
reset(): void
|
|
427
490
|
}
|
|
428
491
|
|
|
429
492
|
/**
|
|
430
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) }`.
|
|
431
497
|
*/
|
|
432
498
|
export interface TransitionReadView<M extends Machine.StateMachine.Any = Machine.StateMachine.Any> {
|
|
433
499
|
get(): Machine.TransitionSnapshot<M>
|
|
434
500
|
}
|
|
435
501
|
|
|
436
502
|
/**
|
|
437
|
-
* 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.
|
|
438
506
|
*/
|
|
439
507
|
export interface TransitionEventReadView<M extends Machine.StateMachine.Any = Machine.StateMachine.Any> {
|
|
440
508
|
all(): ReadonlyArray<Machine.TransitionSnapshot<M>>
|
|
@@ -847,6 +915,7 @@ export type AnySystemSpec = SystemSpec<any, any, any>
|
|
|
847
915
|
type ResourceContext<Spec extends AnySystemSpec> = {
|
|
848
916
|
readonly [K in keyof Spec["resources"]]:
|
|
849
917
|
Spec["resources"][K] extends ResourceRead<infer D> ? ResourceReadView<Descriptor.Value<D>>
|
|
918
|
+
: Spec["resources"][K] extends ResourceChanged<infer D> ? ResourceChangedView<Descriptor.Value<D>>
|
|
850
919
|
: Spec["resources"][K] extends ResourceWrite<infer D> ? ResourceWriteView<D>
|
|
851
920
|
: never
|
|
852
921
|
}
|