@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/Debug.ts
CHANGED
|
@@ -116,6 +116,10 @@ export type Storage = "constructed" | "transient" | "plain"
|
|
|
116
116
|
export interface ComponentDescription {
|
|
117
117
|
readonly name: string
|
|
118
118
|
readonly storage: Storage
|
|
119
|
+
/** Declared `immutable: true`: queries only read it, `commands.insert` replaces it. */
|
|
120
|
+
readonly immutable: boolean
|
|
121
|
+
/** The components it directly requires (`Schema.requires`). */
|
|
122
|
+
readonly requires: ReadonlyArray<string>
|
|
119
123
|
}
|
|
120
124
|
|
|
121
125
|
export interface ResourceDescription {
|
|
@@ -136,8 +140,22 @@ export interface RelationDescription {
|
|
|
136
140
|
export interface MachineDescription {
|
|
137
141
|
readonly name: string
|
|
138
142
|
readonly states: ReadonlyArray<Machine.StateValue>
|
|
139
|
-
/**
|
|
143
|
+
/**
|
|
144
|
+
* The committed value, or `undefined` when the runtime does not provide the
|
|
145
|
+
* machine, or when it is a sub-state whose parent is in another state.
|
|
146
|
+
*/
|
|
140
147
|
readonly current: Machine.StateValue | undefined
|
|
148
|
+
/** Whether the runtime was given an initial value for the machine. */
|
|
149
|
+
readonly provided: boolean
|
|
150
|
+
/** For a sub-state, where it exists, as `Parent=state`. */
|
|
151
|
+
readonly within: string | undefined
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** An entity scope and the state changes that despawn it (`Game.EntityScope(name, { despawnOnExit, despawnOnEnter })`). */
|
|
155
|
+
export interface ScopeDescription {
|
|
156
|
+
readonly name: string
|
|
157
|
+
/** Triggers as `onExit(Flow=Playing)` / `onEnter(Flow=Menu)`; empty when only `commands.despawnScope` ends it. */
|
|
158
|
+
readonly despawnOn: ReadonlyArray<string>
|
|
141
159
|
}
|
|
142
160
|
|
|
143
161
|
export interface ServiceDescription {
|
|
@@ -155,6 +173,8 @@ export interface QueryDescription {
|
|
|
155
173
|
readonly without: ReadonlyArray<string>
|
|
156
174
|
readonly added: ReadonlyArray<string>
|
|
157
175
|
readonly changed: ReadonlyArray<string>
|
|
176
|
+
/** One entry per `anyOf` filter: the components of its `added` and `changed` members. */
|
|
177
|
+
readonly anyOf: ReadonlyArray<{ readonly added: ReadonlyArray<string>; readonly changed: ReadonlyArray<string> }>
|
|
158
178
|
/** Relation filters and reads, for example `with ChildOf` or `read Children`. */
|
|
159
179
|
readonly relations: ReadonlyArray<string>
|
|
160
180
|
}
|
|
@@ -165,7 +185,8 @@ export interface SystemDescription {
|
|
|
165
185
|
/** Where the system runs, as `schedule#step`. */
|
|
166
186
|
readonly placements: ReadonlyArray<string>
|
|
167
187
|
readonly queries: ReadonlyArray<QueryDescription>
|
|
168
|
-
|
|
188
|
+
/** Resource reads and writes. `changed` lists the reads declared with `changedResource` (also in `reads`). */
|
|
189
|
+
readonly resources: { readonly reads: ReadonlyArray<string>; readonly changed: ReadonlyArray<string>; readonly writes: ReadonlyArray<string> }
|
|
169
190
|
readonly events: { readonly reads: ReadonlyArray<string>; readonly writes: ReadonlyArray<string> }
|
|
170
191
|
readonly machines: {
|
|
171
192
|
readonly reads: ReadonlyArray<string>
|
|
@@ -217,11 +238,14 @@ export interface ScheduleDescription {
|
|
|
217
238
|
* A finding about the described schedules. `warning` is almost certainly a
|
|
218
239
|
* bug; `info` is worth a look but can be intentional.
|
|
219
240
|
*
|
|
220
|
-
* Most lints read declarations only.
|
|
221
|
-
* so far: events sent with `runtime.send(...)` count as written,
|
|
241
|
+
* Most lints read declarations only. Some also use what the runtime has seen
|
|
242
|
+
* so far: events sent with `runtime.send(...)` count as written,
|
|
222
243
|
* `relation-failure-never-read` fires for a relation that applied commands
|
|
223
244
|
* have tried to relate or reorder (which can fail) while no described system
|
|
224
|
-
* reads its failures,
|
|
245
|
+
* reads its failures, and `requirement-violated` lists commands skipped
|
|
246
|
+
* because they would have broken a `Schema.requires` requirement, so
|
|
247
|
+
* describe after the game has run for a while. `scope-trigger-never-applied`
|
|
248
|
+
* also reads which machines the runtime provides.
|
|
225
249
|
*/
|
|
226
250
|
export interface Lint {
|
|
227
251
|
readonly severity: "warning" | "info"
|
|
@@ -230,10 +254,13 @@ export interface Lint {
|
|
|
230
254
|
| "relation-failure-never-read"
|
|
231
255
|
| "event-never-written"
|
|
232
256
|
| "next-state-never-applied"
|
|
257
|
+
| "resource-changed-never-written"
|
|
233
258
|
| "component-never-read"
|
|
234
259
|
| "read-before-write"
|
|
235
260
|
| "this-tick-order"
|
|
236
261
|
| "command-permission-unused"
|
|
262
|
+
| "requirement-violated"
|
|
263
|
+
| "scope-trigger-never-applied"
|
|
237
264
|
readonly message: string
|
|
238
265
|
readonly subject: string
|
|
239
266
|
/** What to change to resolve it (or, for `info`, when it is fine as it is). */
|
|
@@ -254,6 +281,8 @@ export interface Description {
|
|
|
254
281
|
readonly events: ReadonlyArray<{ readonly name: string }>
|
|
255
282
|
readonly relations: ReadonlyArray<RelationDescription>
|
|
256
283
|
readonly machines: ReadonlyArray<MachineDescription>
|
|
284
|
+
/** Scopes defined through the game, in definition order. */
|
|
285
|
+
readonly scopes: ReadonlyArray<ScopeDescription>
|
|
257
286
|
readonly services: ReadonlyArray<ServiceDescription>
|
|
258
287
|
readonly schedules: ReadonlyArray<ScheduleDescription>
|
|
259
288
|
readonly systems: ReadonlyArray<SystemDescription>
|
|
@@ -393,6 +422,32 @@ export type Effect =
|
|
|
393
422
|
| { readonly kind: "relate"; readonly entity: number; readonly relation: string; readonly target: number }
|
|
394
423
|
| { readonly kind: "unrelate"; readonly entity: number; readonly relation: string }
|
|
395
424
|
| { readonly kind: "relationFailure"; readonly entity: number; readonly relation: string; readonly error: string }
|
|
425
|
+
/** A command skipped because it would leave `component` without `missing` (`Schema.requires`). */
|
|
426
|
+
| {
|
|
427
|
+
readonly kind: "requirementViolated"
|
|
428
|
+
readonly entity: number
|
|
429
|
+
readonly command: RequirementViolation["command"]
|
|
430
|
+
readonly component: string
|
|
431
|
+
readonly missing: string
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Commands of one system skipped because applying them would have left a
|
|
436
|
+
* component without one it requires (`Schema.requires`): the
|
|
437
|
+
* `requirement-violated` lint.
|
|
438
|
+
*/
|
|
439
|
+
export interface RequirementViolation {
|
|
440
|
+
readonly system: string
|
|
441
|
+
readonly command: "spawn" | "insert" | "remove"
|
|
442
|
+
/** The requirer. */
|
|
443
|
+
readonly component: string
|
|
444
|
+
/** The required component it would have lacked. */
|
|
445
|
+
readonly missing: string
|
|
446
|
+
/** The last entity it happened to. */
|
|
447
|
+
readonly entity: number
|
|
448
|
+
/** How many commands were skipped this way. */
|
|
449
|
+
readonly count: number
|
|
450
|
+
}
|
|
396
451
|
|
|
397
452
|
export interface AppliedCommand {
|
|
398
453
|
readonly tag: string
|
|
@@ -486,8 +541,10 @@ export interface TransitionEvent {
|
|
|
486
541
|
readonly tick: number
|
|
487
542
|
readonly schedule: string
|
|
488
543
|
readonly machine: string
|
|
489
|
-
|
|
490
|
-
readonly
|
|
544
|
+
/** `undefined` when a sub-state starts existing (its parent entered its state). */
|
|
545
|
+
readonly from: Machine.StateValue | undefined
|
|
546
|
+
/** `undefined` when a sub-state stops existing (its parent left its state). */
|
|
547
|
+
readonly to: Machine.StateValue | undefined
|
|
491
548
|
/**
|
|
492
549
|
* `applied` committed the new value; `unchanged` was a `setIfChanged` to the
|
|
493
550
|
* current value; `failed` stopped in an exit or transition schedule and was
|
package/src/Descriptor.ts
CHANGED
|
@@ -166,6 +166,44 @@ export interface TransientDescriptor<
|
|
|
166
166
|
readonly transient: true
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
+
/**
|
|
170
|
+
* Marks a component that is never edited in place: `Game.Query.write(...)`
|
|
171
|
+
* rejects it, and the only way to change its value is to replace it with
|
|
172
|
+
* `commands.insert(...)` (applied at `applyDeferred()`, seen by
|
|
173
|
+
* `Game.Query.changed(...)`).
|
|
174
|
+
*/
|
|
175
|
+
export interface Immutable {
|
|
176
|
+
readonly immutable: true
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Options of the component descriptor constructors (`Component`,
|
|
181
|
+
* `ConstructedComponent`, `TransientComponent`).
|
|
182
|
+
*/
|
|
183
|
+
export interface ComponentOptions {
|
|
184
|
+
/**
|
|
185
|
+
* `true` for data that says what an entity is (a kind, a team, a tile) and
|
|
186
|
+
* is replaced, never edited: queries can only read it, and
|
|
187
|
+
* `commands.insert(...)` replaces it. Leave it out for state that systems
|
|
188
|
+
* update every frame.
|
|
189
|
+
*/
|
|
190
|
+
readonly immutable?: true
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** The descriptor a component constructor returns for `Options`: `D`, plus `Immutable` when `immutable: true`. */
|
|
194
|
+
export type WithComponentOptions<D, Options extends ComponentOptions> =
|
|
195
|
+
Options extends { readonly immutable: true } ? D & Immutable : D
|
|
196
|
+
|
|
197
|
+
const applyComponentOptions = <D extends object>(descriptor: D, options: ComponentOptions | undefined): D =>
|
|
198
|
+
options?.immutable === true ? { ...descriptor, immutable: true } : descriptor
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Whether a component descriptor was declared `immutable: true` (readable by
|
|
202
|
+
* queries, replaced only with `commands.insert(...)`).
|
|
203
|
+
*/
|
|
204
|
+
export const isImmutable = (descriptor: Descriptor.Any): descriptor is Descriptor.Any & Immutable =>
|
|
205
|
+
"immutable" in descriptor && descriptor.immutable === true
|
|
206
|
+
|
|
169
207
|
/**
|
|
170
208
|
* Type-level helpers for working with descriptors.
|
|
171
209
|
*/
|
|
@@ -254,11 +292,15 @@ const makeConstructedDescriptor = <
|
|
|
254
292
|
* @example
|
|
255
293
|
* ```ts
|
|
256
294
|
* const Position = Descriptor.Component<{ x: number; y: number }>()("Position")
|
|
295
|
+
* // What an entity is, replaced with commands.insert and never written by a query:
|
|
296
|
+
* const EnemyKind = Descriptor.Component<"grunt" | "boss">()("EnemyKind", { immutable: true })
|
|
257
297
|
* ```
|
|
258
298
|
*/
|
|
259
|
-
export const Component = <Value>() => <const Name extends string>(
|
|
260
|
-
name: Name
|
|
261
|
-
|
|
299
|
+
export const Component = <Value>() => <const Name extends string, const Options extends ComponentOptions = {}>(
|
|
300
|
+
name: Name,
|
|
301
|
+
options?: Options
|
|
302
|
+
): WithComponentOptions<Descriptor<"component", Name, Value>, Options> =>
|
|
303
|
+
applyComponentOptions(makeDescriptor("component", name), options) as WithComponentOptions<Descriptor<"component", Name, Value>, Options>
|
|
262
304
|
|
|
263
305
|
/**
|
|
264
306
|
* Defines a component descriptor that also knows how to validate raw values.
|
|
@@ -275,10 +317,11 @@ export const Component = <Value>() => <const Name extends string>(
|
|
|
275
317
|
*/
|
|
276
318
|
export const ConstructedComponent = <Value, Raw, Error, Constructor extends {} = {}>(
|
|
277
319
|
constructor: ResultConstructor<Value, Raw, Error> & Constructor
|
|
278
|
-
) => <const Name extends string>(
|
|
279
|
-
name: Name
|
|
280
|
-
|
|
281
|
-
|
|
320
|
+
) => <const Name extends string, const Options extends ComponentOptions = {}>(
|
|
321
|
+
name: Name,
|
|
322
|
+
options?: Options
|
|
323
|
+
): WithComponentOptions<ConstructedFor<"component", Name, Value, Raw, Error, Constructor>, Options> =>
|
|
324
|
+
applyComponentOptions(makeConstructedDescriptor("component", name, constructor), options) as WithComponentOptions<ConstructedFor<"component", Name, Value, Raw, Error, Constructor>, Options>
|
|
282
325
|
|
|
283
326
|
/**
|
|
284
327
|
* Defines a component descriptor that snapshots skip.
|
|
@@ -291,9 +334,11 @@ export const ConstructedComponent = <Value, Raw, Error, Constructor extends {} =
|
|
|
291
334
|
* const SpriteRef = Descriptor.TransientComponent<{ frame: number }>()("SpriteRef")
|
|
292
335
|
* ```
|
|
293
336
|
*/
|
|
294
|
-
export const TransientComponent = <Value>() => <const Name extends string>(
|
|
295
|
-
name: Name
|
|
296
|
-
|
|
337
|
+
export const TransientComponent = <Value>() => <const Name extends string, const Options extends ComponentOptions = {}>(
|
|
338
|
+
name: Name,
|
|
339
|
+
options?: Options
|
|
340
|
+
): WithComponentOptions<TransientDescriptor<"component", Name, Value>, Options> =>
|
|
341
|
+
applyComponentOptions(makeTransientDescriptor("component", name), options) as WithComponentOptions<TransientDescriptor<"component", Name, Value>, Options>
|
|
297
342
|
|
|
298
343
|
/**
|
|
299
344
|
* Defines a marker component: no data, only presence (`[Player, {}]`).
|
package/src/Entity.ts
CHANGED
|
@@ -168,17 +168,23 @@ export type StagedComponent = readonly [Descriptor<"component", string, any>, un
|
|
|
168
168
|
*
|
|
169
169
|
* Query execution is the main source of `EntityRef` values.
|
|
170
170
|
*/
|
|
171
|
-
export interface EntityRef<S extends Schema.Any, out P extends ComponentProof, Root = unknown, out Proves = unknown> {
|
|
171
|
+
export interface EntityRef<S extends Schema.Any, out P extends ComponentProof, Root = unknown, out Proves = unknown, out Lacks = never> {
|
|
172
172
|
/**
|
|
173
173
|
* Runtime tag for debugging and pattern matching.
|
|
174
174
|
*/
|
|
175
175
|
readonly kind: "EntityRef"
|
|
176
176
|
/**
|
|
177
177
|
* The component descriptors the query that produced this ref proves (its
|
|
178
|
-
* required slots and `with`): what `commands.despawn`
|
|
179
|
-
*
|
|
178
|
+
* required slots and `with`): what `commands.despawn`, `insert`, and
|
|
179
|
+
* `remove` check. Type-level only.
|
|
180
180
|
*/
|
|
181
181
|
readonly __proves?: (_: never) => Proves
|
|
182
|
+
/**
|
|
183
|
+
* The component descriptors the producing query proves absent (its
|
|
184
|
+
* `without`): what `commands.remove` checks against `Schema.requires`.
|
|
185
|
+
* Type-level only.
|
|
186
|
+
*/
|
|
187
|
+
readonly __lacks?: (_: never) => Lacks
|
|
182
188
|
/**
|
|
183
189
|
* The schema-bound entity identity.
|
|
184
190
|
*/
|
|
@@ -201,7 +207,8 @@ export interface EntityMut<
|
|
|
201
207
|
out P extends ComponentProof,
|
|
202
208
|
out W extends ComponentProof,
|
|
203
209
|
Root = unknown,
|
|
204
|
-
out Proves = unknown
|
|
210
|
+
out Proves = unknown,
|
|
211
|
+
out Lacks = never
|
|
205
212
|
> {
|
|
206
213
|
/**
|
|
207
214
|
* Runtime tag for debugging and pattern matching.
|
|
@@ -209,6 +216,8 @@ export interface EntityMut<
|
|
|
209
216
|
readonly kind: "EntityMut"
|
|
210
217
|
/** The component descriptors the producing query proves; see `EntityRef`. */
|
|
211
218
|
readonly __proves?: (_: never) => Proves
|
|
219
|
+
/** The component descriptors the producing query proves absent; see `EntityRef`. */
|
|
220
|
+
readonly __lacks?: (_: never) => Lacks
|
|
212
221
|
/**
|
|
213
222
|
* The schema-bound entity identity.
|
|
214
223
|
*/
|
package/src/EntityScope.ts
CHANGED
|
@@ -13,6 +13,11 @@
|
|
|
13
13
|
* parent-child tree: ordered children, `lookup.parent` / `descendants`
|
|
14
14
|
* traversal, and children despawned with their parent.
|
|
15
15
|
*
|
|
16
|
+
* A scope whose lifetime is a game state declares it:
|
|
17
|
+
* `Game.EntityScope(name, { despawnOnExit: [Flow, "Playing"] })` despawns
|
|
18
|
+
* every member when `applyStateTransitions(...)` commits that state change,
|
|
19
|
+
* with no system, permission, or marker to place.
|
|
20
|
+
*
|
|
16
21
|
* @module EntityScope
|
|
17
22
|
* @docGroup core
|
|
18
23
|
*/
|
|
@@ -25,9 +30,21 @@ export interface EntityScope<
|
|
|
25
30
|
readonly kind: "entityScope"
|
|
26
31
|
readonly name: Name
|
|
27
32
|
readonly key: symbol
|
|
33
|
+
/** State changes that despawn every member, applied by `applyStateTransitions(...)`. */
|
|
34
|
+
readonly despawnOn: ReadonlyArray<Trigger>
|
|
28
35
|
readonly __schemaRoot?: Root | undefined
|
|
29
36
|
}
|
|
30
37
|
|
|
38
|
+
/**
|
|
39
|
+
* One state change that despawns a scope: leaving (`exit`) or entering
|
|
40
|
+
* (`enter`) `state` of `machine`.
|
|
41
|
+
*/
|
|
42
|
+
export interface Trigger {
|
|
43
|
+
readonly phase: "enter" | "exit"
|
|
44
|
+
readonly machine: { readonly key: symbol; readonly name: string }
|
|
45
|
+
readonly state: string | number
|
|
46
|
+
}
|
|
47
|
+
|
|
31
48
|
/** Type-level helpers for entity scopes. */
|
|
32
49
|
export namespace EntityScope {
|
|
33
50
|
export type Any = EntityScope<string, any>
|
|
@@ -42,11 +59,13 @@ export namespace EntityScope {
|
|
|
42
59
|
* branded to that game's root.
|
|
43
60
|
*/
|
|
44
61
|
export const make = <const Name extends string, Root = unknown>(
|
|
45
|
-
name: Name
|
|
62
|
+
name: Name,
|
|
63
|
+
despawnOn: ReadonlyArray<Trigger> = []
|
|
46
64
|
): EntityScope<Name, Root> => ({
|
|
47
65
|
kind: "entityScope",
|
|
48
66
|
name,
|
|
49
|
-
key: keyOf(name)
|
|
67
|
+
key: keyOf(name),
|
|
68
|
+
despawnOn
|
|
50
69
|
})
|
|
51
70
|
|
|
52
71
|
const keyPrefix = "ritmo/entityScope/"
|
package/src/Machine.ts
CHANGED
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
*/
|
|
40
40
|
import type { AnyScheduleOrder, Schedule } from "./Schedule.ts"
|
|
41
41
|
import * as Requirement from "./Requirement.ts"
|
|
42
|
+
import type * as Result from "./Result.ts"
|
|
42
43
|
import type { Schema } from "./Schema.ts"
|
|
43
44
|
import type { SystemFailure } from "./System.ts"
|
|
44
45
|
|
|
@@ -56,60 +57,71 @@ export type StateValue = string | number
|
|
|
56
57
|
export interface StateMachineDefinition<
|
|
57
58
|
out Name extends string = string,
|
|
58
59
|
out Values extends readonly [StateValue, ...StateValue[]] = readonly [StateValue, ...StateValue[]],
|
|
59
|
-
out Root = unknown
|
|
60
|
+
out Root = unknown,
|
|
61
|
+
out Parent = unknown
|
|
60
62
|
> {
|
|
61
63
|
readonly kind: "stateMachine"
|
|
62
64
|
readonly name: Name
|
|
63
65
|
readonly key: symbol
|
|
64
66
|
readonly values: Values
|
|
67
|
+
/** For a sub-state: the machine and the state it exists in. */
|
|
68
|
+
readonly within?: SubStateParent | undefined
|
|
65
69
|
readonly __schemaRoot?: Root | undefined
|
|
70
|
+
/** The machine a sub-state exists within; `unknown` for a top-level machine. */
|
|
71
|
+
readonly __parent?: (_: never) => Parent
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Where a sub-state exists: while `machine` is in `state`. */
|
|
75
|
+
export interface SubStateParent {
|
|
76
|
+
readonly machine: StateMachine.Any
|
|
77
|
+
readonly state: StateValue
|
|
66
78
|
}
|
|
67
79
|
|
|
68
80
|
/**
|
|
69
|
-
*
|
|
81
|
+
* Whether `M` is a sub-state (declared with `within`): it has no value while
|
|
82
|
+
* its parent machine is in another state.
|
|
70
83
|
*/
|
|
71
|
-
export
|
|
72
|
-
readonly
|
|
73
|
-
readonly to: StateMachine.Value<M>
|
|
74
|
-
}
|
|
84
|
+
export type IsSubState<M> =
|
|
85
|
+
M extends { readonly __parent?: (_: never) => infer Parent } ? unknown extends Parent ? false : true : false
|
|
75
86
|
|
|
76
87
|
/**
|
|
77
|
-
* A
|
|
88
|
+
* The committed value of `M`. A sub-state has none (`undefined`) while its
|
|
89
|
+
* parent machine is not in the state it exists in; a top-level machine
|
|
90
|
+
* always has one.
|
|
78
91
|
*/
|
|
79
|
-
export
|
|
80
|
-
|
|
81
|
-
is(value: StateMachine.Value<M>): boolean
|
|
82
|
-
}
|
|
92
|
+
export type CurrentValue<M extends StateMachine.Any> =
|
|
93
|
+
IsSubState<M> extends true ? StateMachine.Value<M> | undefined : StateMachine.Value<M>
|
|
83
94
|
|
|
84
95
|
/**
|
|
85
|
-
*
|
|
96
|
+
* Why queuing a sub-state's next value failed: its parent machine is not in
|
|
97
|
+
* the state the sub-state exists in, so there is nothing to change.
|
|
86
98
|
*/
|
|
87
|
-
export interface
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
99
|
+
export interface SubStateInactive {
|
|
100
|
+
readonly _tag: "SubStateInactive"
|
|
101
|
+
/** The sub-state's name. */
|
|
102
|
+
readonly machine: string
|
|
103
|
+
/** The parent machine's name. */
|
|
104
|
+
readonly parent: string
|
|
105
|
+
/** The parent state the sub-state exists in. */
|
|
106
|
+
readonly requires: StateValue
|
|
92
107
|
}
|
|
93
108
|
|
|
94
109
|
/**
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
* only exposed to systems that explicitly declare transition access.
|
|
110
|
+
* What `nextMachines.x.set(...)` returns: nothing for a top-level machine,
|
|
111
|
+
* and for a sub-state a `Result` that fails with `SubStateInactive` when its
|
|
112
|
+
* parent is in another state.
|
|
99
113
|
*/
|
|
100
|
-
export
|
|
101
|
-
|
|
102
|
-
}
|
|
114
|
+
export type SetResult<M extends StateMachine.Any> =
|
|
115
|
+
IsSubState<M> extends true ? Result.Result<void, SubStateInactive> : void
|
|
103
116
|
|
|
104
117
|
/**
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
* transition commits. Like events, each reader sees the transitions published
|
|
109
|
-
* since its own previous run.
|
|
118
|
+
* The runtime-visible transition payload for one machine. For a sub-state,
|
|
119
|
+
* `from` is `undefined` when it starts existing (its parent entered its
|
|
120
|
+
* state) and `to` is `undefined` when it stops.
|
|
110
121
|
*/
|
|
111
|
-
export interface
|
|
112
|
-
|
|
122
|
+
export interface TransitionSnapshot<M extends StateMachineDefinition = StateMachineDefinition> {
|
|
123
|
+
readonly from: CurrentValue<M>
|
|
124
|
+
readonly to: CurrentValue<M>
|
|
113
125
|
}
|
|
114
126
|
|
|
115
127
|
/**
|
|
@@ -263,7 +275,7 @@ export interface TransitionScheduleDefinition<
|
|
|
263
275
|
* Type-level and value-level helpers for finite-state machines.
|
|
264
276
|
*/
|
|
265
277
|
export namespace StateMachine {
|
|
266
|
-
export type Any = StateMachineDefinition<string, readonly [StateValue, ...StateValue[]], unknown>
|
|
278
|
+
export type Any = StateMachineDefinition<string, readonly [StateValue, ...StateValue[]], unknown, unknown>
|
|
267
279
|
export type Value<M extends Any> = M["values"][number]
|
|
268
280
|
export type Root<M extends Any> = M extends StateMachineDefinition<string, readonly [StateValue, ...StateValue[]], infer R> ? R : never
|
|
269
281
|
export type AnyCondition<Root = unknown> = Condition<Root>
|
|
@@ -271,6 +283,23 @@ export namespace StateMachine {
|
|
|
271
283
|
TransitionScheduleDefinition<S, Any, Requirement.Requirement, Root, SystemFailure>
|
|
272
284
|
}
|
|
273
285
|
|
|
286
|
+
/**
|
|
287
|
+
* `unknown` when `M` was defined by the game rooted at `Root`; otherwise a
|
|
288
|
+
* type naming the fix, so passing another game's machine fails with that
|
|
289
|
+
* message.
|
|
290
|
+
*/
|
|
291
|
+
export type SameGame<M, Root> =
|
|
292
|
+
M extends StateMachineDefinition<infer Name, readonly [StateValue, ...StateValue[]], infer MachineRoot>
|
|
293
|
+
? [Root] extends [MachineRoot]
|
|
294
|
+
? [MachineRoot] extends [Root]
|
|
295
|
+
? unknown
|
|
296
|
+
: ForeignMachine<`${Name} is a state machine of another game: use a machine defined with this game's Game.StateMachine(...)`>
|
|
297
|
+
: ForeignMachine<`${Name} is a state machine of another game: use a machine defined with this game's Game.StateMachine(...)`>
|
|
298
|
+
: unknown
|
|
299
|
+
|
|
300
|
+
/** The message type `SameGame` resolves to: a type error prints `Message`. */
|
|
301
|
+
type ForeignMachine<Message extends string> = { readonly __foreignMachine__: Message }
|
|
302
|
+
|
|
274
303
|
/** Extracts the machine token from one declared machine access slot. */
|
|
275
304
|
type MachineFromAccess<Access> =
|
|
276
305
|
Access extends { readonly machine: infer M extends StateMachine.Any } ? M : never
|
|
@@ -314,6 +343,9 @@ export type MachineNeedsFromConditions<C extends ReadonlyArray<Condition>> =
|
|
|
314
343
|
*
|
|
315
344
|
* Use `Descriptor.EntityState(...)` instead when every entity carries its own
|
|
316
345
|
* state (per-entity, written immediately, no enter/exit schedules).
|
|
346
|
+
*
|
|
347
|
+
* Pass `within` for a sub-state: a machine that exists only while another
|
|
348
|
+
* machine is in one state (`Game.StateMachine(name, values, { within })`).
|
|
317
349
|
*/
|
|
318
350
|
export const StateMachine = <
|
|
319
351
|
const Name extends string,
|
|
@@ -321,12 +353,14 @@ export const StateMachine = <
|
|
|
321
353
|
Root = unknown
|
|
322
354
|
>(
|
|
323
355
|
name: Name,
|
|
324
|
-
values: Values
|
|
356
|
+
values: Values,
|
|
357
|
+
within?: SubStateParent
|
|
325
358
|
): StateMachineDefinition<Name, Values, Root> => ({
|
|
326
359
|
kind: "stateMachine",
|
|
327
360
|
name,
|
|
328
361
|
key: Symbol.for(`ritmo/stateMachine/${name}`),
|
|
329
|
-
values
|
|
362
|
+
values,
|
|
363
|
+
...(within === undefined ? {} : { within })
|
|
330
364
|
}) as StateMachineDefinition<Name, Values, Root>
|
|
331
365
|
|
|
332
366
|
/**
|
package/src/Query.ts
CHANGED
|
@@ -69,6 +69,7 @@
|
|
|
69
69
|
* Query authoring helpers for selecting components and declaring explicit filters.
|
|
70
70
|
*/
|
|
71
71
|
import type { DecodeError } from "./Decode.ts"
|
|
72
|
+
import * as DescriptorModule from "./Descriptor.ts"
|
|
72
73
|
import type { Descriptor, EntityStateValue } from "./Descriptor.ts"
|
|
73
74
|
import type { EntityId, EntityMut, EntityRef } from "./Entity.ts"
|
|
74
75
|
import type * as Result from "./Result.ts"
|
|
@@ -179,10 +180,24 @@ export interface ChangedFilter<D extends ComponentDescriptor> {
|
|
|
179
180
|
readonly descriptor: D
|
|
180
181
|
}
|
|
181
182
|
|
|
182
|
-
|
|
183
|
+
/** An `added` or `changed` filter: the filters `anyOf` combines. */
|
|
184
|
+
export type LifecycleFilter<D extends ComponentDescriptor> =
|
|
183
185
|
| AddedFilter<D>
|
|
184
186
|
| ChangedFilter<D>
|
|
185
187
|
|
|
188
|
+
/**
|
|
189
|
+
* A filter that matches entities passing at least one of its `added` /
|
|
190
|
+
* `changed` filters since the reading system's previous run.
|
|
191
|
+
*/
|
|
192
|
+
export interface AnyOfFilter<D extends ComponentDescriptor> {
|
|
193
|
+
readonly kind: "anyOf"
|
|
194
|
+
readonly filters: ReadonlyArray<LifecycleFilter<D>>
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export type Filter<D extends ComponentDescriptor> =
|
|
198
|
+
| LifecycleFilter<D>
|
|
199
|
+
| AnyOfFilter<D>
|
|
200
|
+
|
|
186
201
|
/**
|
|
187
202
|
* Declares read-only access to a component in a query selection.
|
|
188
203
|
*
|
|
@@ -203,11 +218,29 @@ export const read = <D extends ComponentDescriptor>(descriptor: D): ReadAccess<D
|
|
|
203
218
|
* place. A write slot both requires component presence and exposes the slot as
|
|
204
219
|
* a `WriteCell`, so mutation capability stays visible in the query spec rather
|
|
205
220
|
* than appearing ad hoc in the loop body.
|
|
221
|
+
*
|
|
222
|
+
* Components declared `immutable: true` cannot be written: replace them with
|
|
223
|
+
* `commands.insert(...)` instead.
|
|
206
224
|
*/
|
|
207
|
-
export const write = <D extends ComponentDescriptor>(descriptor: D): WriteAccess<D> =>
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
225
|
+
export const write = <D extends ComponentDescriptor>(descriptor: D & Writable<D>): WriteAccess<D> => {
|
|
226
|
+
if (DescriptorModule.isImmutable(descriptor)) {
|
|
227
|
+
throw new Error(immutableMessage(descriptor.name))
|
|
228
|
+
}
|
|
229
|
+
return { mode: "write", descriptor }
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const immutableMessage = (name: string) =>
|
|
233
|
+
`${name} is immutable: replace it with commands.insert(entity, Game.Command.entry(${name}, ...))`
|
|
234
|
+
|
|
235
|
+
type NameOf<D> = D extends { readonly name: infer Name extends string } ? Name : never
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* `unknown` for a component a query may write; for one declared
|
|
239
|
+
* `immutable: true`, an argument error naming the fix.
|
|
240
|
+
*/
|
|
241
|
+
export type Writable<D> = D extends DescriptorModule.Immutable
|
|
242
|
+
? { readonly __immutable__: `${NameOf<D>} is immutable: replace it with commands.insert(entity, Game.Command.entry(${NameOf<D>}, ...))` }
|
|
243
|
+
: unknown
|
|
211
244
|
|
|
212
245
|
/**
|
|
213
246
|
* Declares maybe-present read-only access to a component in a query
|
|
@@ -282,6 +315,34 @@ export const changed = <D extends ComponentDescriptor>(descriptor: D): ChangedFi
|
|
|
282
315
|
descriptor
|
|
283
316
|
})
|
|
284
317
|
|
|
318
|
+
/**
|
|
319
|
+
* Declares a filter that matches entities passing at least one of `filters`
|
|
320
|
+
* (`added` / `changed`) since the reading system's previous run. Each entity
|
|
321
|
+
* is matched once, however many of its filters pass.
|
|
322
|
+
*
|
|
323
|
+
* Filters listed side by side in `filters: [...]` must all pass; use `anyOf`
|
|
324
|
+
* when any one of them is enough, for example to redraw when either the
|
|
325
|
+
* position or the sprite changed.
|
|
326
|
+
*
|
|
327
|
+
* @example
|
|
328
|
+
* ```ts
|
|
329
|
+
* const RedrawQuery = Game.Query({
|
|
330
|
+
* selection: { position: Game.Query.read(Position), sprite: Game.Query.read(Sprite) },
|
|
331
|
+
* filters: [Game.Query.anyOf(Game.Query.changed(Position), Game.Query.changed(Sprite))]
|
|
332
|
+
* })
|
|
333
|
+
* ```
|
|
334
|
+
*/
|
|
335
|
+
export const anyOf = <
|
|
336
|
+
const Filters extends readonly [
|
|
337
|
+
LifecycleFilter<ComponentDescriptor>,
|
|
338
|
+
LifecycleFilter<ComponentDescriptor>,
|
|
339
|
+
...ReadonlyArray<LifecycleFilter<ComponentDescriptor>>
|
|
340
|
+
]
|
|
341
|
+
>(...filters: Filters): AnyOfFilter<Filters[number]["descriptor"]> => ({
|
|
342
|
+
kind: "anyOf",
|
|
343
|
+
filters
|
|
344
|
+
})
|
|
345
|
+
|
|
285
346
|
/**
|
|
286
347
|
* A fully explicit query specification.
|
|
287
348
|
*
|
|
@@ -387,6 +448,9 @@ export namespace Query {
|
|
|
387
448
|
/** The component descriptors a query proves its matches have: required selection slots and `with`. */
|
|
388
449
|
export type Proven<T extends Any> = RequiredSelectionDescriptors<T> | T["with"][number]
|
|
389
450
|
|
|
451
|
+
/** The component descriptors a query proves its matches lack: its `without`. */
|
|
452
|
+
export type Excluded<T extends Any> = T["without"][number]
|
|
453
|
+
|
|
390
454
|
/**
|
|
391
455
|
* The readable proof produced by a query.
|
|
392
456
|
*/
|
|
@@ -621,11 +685,11 @@ export type OptionalReadCell<T> = PresentOptionalReadCell<T> | AbsentOptionalRea
|
|
|
621
685
|
export type QueryMatch<S extends Schema.Any, Q extends Query.Any> =
|
|
622
686
|
keyof Query.WriteProof<Q> extends never
|
|
623
687
|
? {
|
|
624
|
-
readonly entity: EntityRef<S, Query.ReadProof<Q>, Query.Root<Q>, Query.Proven<Q>>
|
|
688
|
+
readonly entity: EntityRef<S, Query.ReadProof<Q>, Query.Root<Q>, Query.Proven<Q>, Query.Excluded<Q>>
|
|
625
689
|
readonly data: Query.Cells<Q>
|
|
626
690
|
}
|
|
627
691
|
: {
|
|
628
|
-
readonly entity: EntityMut<S, Query.ReadProof<Q>, Query.WriteProof<Q>, Query.Root<Q>, Query.Proven<Q>>
|
|
692
|
+
readonly entity: EntityMut<S, Query.ReadProof<Q>, Query.WriteProof<Q>, Query.Root<Q>, Query.Proven<Q>, Query.Excluded<Q>>
|
|
629
693
|
readonly data: Query.Cells<Q>
|
|
630
694
|
}
|
|
631
695
|
|