@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/Decode.ts
CHANGED
|
@@ -188,7 +188,7 @@ export const struct = <const Fields extends Readonly<Record<string, Codec<any>>>
|
|
|
188
188
|
/**
|
|
189
189
|
* A stored entity handle for schema root `Root`, optionally with an intent
|
|
190
190
|
* component. Decoding never proves the entity exists; resolve it with
|
|
191
|
-
* `
|
|
191
|
+
* `queries.x.getHandle(...)` (through a declared query) as usual.
|
|
192
192
|
*/
|
|
193
193
|
export const handle = <
|
|
194
194
|
Root,
|
package/src/Descriptor.ts
CHANGED
|
@@ -16,8 +16,9 @@
|
|
|
16
16
|
* 3. bind one `Game` with `Schema.bind(...)`
|
|
17
17
|
*
|
|
18
18
|
* Reach for this module first whenever you introduce a new component,
|
|
19
|
-
* resource, event, or service to the game.
|
|
20
|
-
* matter
|
|
19
|
+
* resource, event, or service to the game. A game-wide mode whose transitions
|
|
20
|
+
* matter is a state machine (`Game.StateMachine(...)`); a state each entity
|
|
21
|
+
* has on its own is a component (`Descriptor.EntityState(...)`).
|
|
21
22
|
*
|
|
22
23
|
* @module Descriptor
|
|
23
24
|
* @docGroup core
|
|
@@ -311,70 +312,77 @@ export const Tag = <const Name extends string>(
|
|
|
311
312
|
): DecodableDescriptor<"component", Name, {}, {}, DecodeModule.DecodeError> =>
|
|
312
313
|
ConstructedComponent(DecodeModule.struct({}))(name)
|
|
313
314
|
|
|
314
|
-
/** One state of a {@link
|
|
315
|
-
export type
|
|
315
|
+
/** One state of a {@link EntityState} component: a string or number literal. */
|
|
316
|
+
export type EntityStateValue = string | number
|
|
316
317
|
|
|
317
318
|
/**
|
|
318
|
-
* The allowed moves of a {@link
|
|
319
|
+
* The allowed moves of a {@link EntityState} component: for every state, the
|
|
319
320
|
* states it may move to (`[]` for a state with no way out).
|
|
320
321
|
*/
|
|
321
|
-
export type
|
|
322
|
+
export type EntityStateTransitions<States extends readonly [EntityStateValue, ...Array<EntityStateValue>]> = {
|
|
322
323
|
readonly [From in States[number]]: ReadonlyArray<States[number]>
|
|
323
324
|
}
|
|
324
325
|
|
|
325
326
|
/**
|
|
326
327
|
* A component whose value is one of a closed set of states, with an optional
|
|
327
|
-
* graph of allowed moves. Built with {@link
|
|
328
|
+
* graph of allowed moves. Built with {@link EntityState}.
|
|
328
329
|
*/
|
|
329
|
-
export interface
|
|
330
|
+
export interface EntityStateDescriptor<
|
|
330
331
|
out Name extends string,
|
|
331
|
-
in out States extends readonly [
|
|
332
|
-
in out Transitions extends
|
|
332
|
+
in out States extends readonly [EntityStateValue, ...Array<EntityStateValue>],
|
|
333
|
+
in out Transitions extends EntityStateTransitions<States> | undefined
|
|
333
334
|
> extends DecodableDescriptor<"component", Name, States[number], States[number], DecodeModule.DecodeError> {
|
|
334
335
|
readonly states: States
|
|
335
336
|
readonly transitions: Transitions
|
|
336
337
|
}
|
|
337
338
|
|
|
338
339
|
/**
|
|
339
|
-
* Defines a state component
|
|
340
|
-
* snapshots reject unknown states),
|
|
341
|
-
* (storage, change detection, rollback, traces).
|
|
340
|
+
* Defines a per-entity state: a component whose value is one of `states`,
|
|
341
|
+
* validated (so snapshots reject unknown states), that behaves like any other
|
|
342
|
+
* component (storage, change detection, rollback, traces).
|
|
343
|
+
*
|
|
344
|
+
* Use this when each entity has its own state: an enemy's attack windup, a
|
|
345
|
+
* door's open/closed, a unit's AI mode. Use `Game.StateMachine(...)` instead
|
|
346
|
+
* for one game-wide mode (menu, playing, paused, game over): machines change
|
|
347
|
+
* only at `applyStateTransitions(...)`, run `onEnter`/`onExit` schedules, and
|
|
348
|
+
* gate systems with `Game.Condition.inState(...)`. An entity state does none
|
|
349
|
+
* of that; it is data on an entity.
|
|
342
350
|
*
|
|
343
351
|
* With `transitions`, its write cell gains `transition(from, to)`: the pair
|
|
344
352
|
* is checked against the graph at compile time, and at runtime the move
|
|
345
|
-
* happens only if the current state is still `from`, otherwise it returns
|
|
346
|
-
* `
|
|
353
|
+
* happens only if the current state is still `from`, otherwise it returns an
|
|
354
|
+
* `EntityStateMismatch` failure. Transitions are ordinary component writes:
|
|
347
355
|
* immediate, visible to later systems, rolled back with a failed system.
|
|
348
356
|
*
|
|
349
357
|
* @example
|
|
350
358
|
* ```ts
|
|
351
|
-
* const
|
|
359
|
+
* const Attack = Descriptor.EntityState("Attack", ["ready", "windup", "active"] as const, {
|
|
352
360
|
* transitions: { ready: ["windup"], windup: ["active", "ready"], active: ["ready"] }
|
|
353
361
|
* })
|
|
354
|
-
* // in a system with a write slot `
|
|
355
|
-
* const moved = data.
|
|
362
|
+
* // in a system with a write slot `attack`:
|
|
363
|
+
* const moved = data.attack.transition("windup", "active") // "ready" -> "active" does not compile
|
|
356
364
|
* ```
|
|
357
365
|
*/
|
|
358
|
-
export const
|
|
366
|
+
export const EntityState = <
|
|
359
367
|
const Name extends string,
|
|
360
|
-
const States extends readonly [
|
|
361
|
-
const Transitions extends
|
|
368
|
+
const States extends readonly [EntityStateValue, ...Array<EntityStateValue>],
|
|
369
|
+
const Transitions extends EntityStateTransitions<States> | undefined = undefined
|
|
362
370
|
>(
|
|
363
371
|
name: Name,
|
|
364
372
|
states: States,
|
|
365
373
|
options?: {
|
|
366
374
|
readonly transitions: Transitions & { readonly [Key in Exclude<keyof Transitions, States[number]>]: never }
|
|
367
375
|
}
|
|
368
|
-
):
|
|
376
|
+
): EntityStateDescriptor<Name, States, Transitions> => ({
|
|
369
377
|
...makeConstructedDescriptor("component", name, DecodeModule.literal(...states)),
|
|
370
378
|
states,
|
|
371
379
|
transitions: options?.transitions
|
|
372
|
-
}) as unknown as
|
|
380
|
+
}) as unknown as EntityStateDescriptor<Name, States, Transitions>
|
|
373
381
|
|
|
374
|
-
/** Whether a descriptor was built with {@link
|
|
375
|
-
export const
|
|
382
|
+
/** Whether a descriptor was built with {@link EntityState}. */
|
|
383
|
+
export const isEntityState = (
|
|
376
384
|
descriptor: Descriptor.Any
|
|
377
|
-
): descriptor is
|
|
385
|
+
): descriptor is EntityStateDescriptor<string, readonly [EntityStateValue, ...Array<EntityStateValue>], EntityStateTransitions<readonly [EntityStateValue, ...Array<EntityStateValue>]> | undefined> =>
|
|
378
386
|
"states" in descriptor
|
|
379
387
|
|
|
380
388
|
/**
|
|
@@ -568,7 +576,9 @@ export function constructorOf<D extends Descriptor.Any>(
|
|
|
568
576
|
* `related` is the reverse collection maintained by the runtime.
|
|
569
577
|
*
|
|
570
578
|
* Use hierarchy when the relationship must support ordered children,
|
|
571
|
-
* ancestor/descendant traversal, and linked recursive despawn.
|
|
579
|
+
* ancestor/descendant traversal, and linked recursive despawn. Use
|
|
580
|
+
* `Game.EntityScope(...)` instead when entities only share a lifetime (a
|
|
581
|
+
* level, a menu) and need no tree, order, or traversal.
|
|
572
582
|
*/
|
|
573
583
|
export const Hierarchy = RelationModule.Hierarchy
|
|
574
584
|
|
package/src/Entity.ts
CHANGED
|
@@ -56,7 +56,8 @@ import * as Result from "./Result.ts"
|
|
|
56
56
|
* @example
|
|
57
57
|
* ```ts
|
|
58
58
|
* const handle = Game.Entity.handle(playerId, Player)
|
|
59
|
-
*
|
|
59
|
+
* // `players` is a query the system declares: queries: { players: PlayerQuery }
|
|
60
|
+
* const resolved = queries.players.getHandle(handle)
|
|
60
61
|
* if (!resolved.ok) return
|
|
61
62
|
* ```
|
|
62
63
|
*/
|
|
@@ -167,11 +168,17 @@ export type StagedComponent = readonly [Descriptor<"component", string, any>, un
|
|
|
167
168
|
*
|
|
168
169
|
* Query execution is the main source of `EntityRef` values.
|
|
169
170
|
*/
|
|
170
|
-
export interface EntityRef<S extends Schema.Any, out P extends ComponentProof, Root = unknown> {
|
|
171
|
+
export interface EntityRef<S extends Schema.Any, out P extends ComponentProof, Root = unknown, out Proves = unknown> {
|
|
171
172
|
/**
|
|
172
173
|
* Runtime tag for debugging and pattern matching.
|
|
173
174
|
*/
|
|
174
175
|
readonly kind: "EntityRef"
|
|
176
|
+
/**
|
|
177
|
+
* The component descriptors the query that produced this ref proves (its
|
|
178
|
+
* required slots and `with`): what `commands.despawn` checks against the
|
|
179
|
+
* system's `despawns` permission. Type-level only.
|
|
180
|
+
*/
|
|
181
|
+
readonly __proves?: (_: never) => Proves
|
|
175
182
|
/**
|
|
176
183
|
* The schema-bound entity identity.
|
|
177
184
|
*/
|
|
@@ -193,12 +200,15 @@ export interface EntityMut<
|
|
|
193
200
|
S extends Schema.Any,
|
|
194
201
|
out P extends ComponentProof,
|
|
195
202
|
out W extends ComponentProof,
|
|
196
|
-
Root = unknown
|
|
203
|
+
Root = unknown,
|
|
204
|
+
out Proves = unknown
|
|
197
205
|
> {
|
|
198
206
|
/**
|
|
199
207
|
* Runtime tag for debugging and pattern matching.
|
|
200
208
|
*/
|
|
201
209
|
readonly kind: "EntityMut"
|
|
210
|
+
/** The component descriptors the producing query proves; see `EntityRef`. */
|
|
211
|
+
readonly __proves?: (_: never) => Proves
|
|
202
212
|
/**
|
|
203
213
|
* The schema-bound entity identity.
|
|
204
214
|
*/
|
|
@@ -247,7 +257,7 @@ export type HandleTarget<S extends Schema.Any, Root = unknown> =
|
|
|
247
257
|
* Converts a current entity into a durable handle for storage.
|
|
248
258
|
*
|
|
249
259
|
* The handle is storage-safe, not a proof of liveness: resolve it later with
|
|
250
|
-
* `
|
|
260
|
+
* `queries.x.getHandle(...)` (through a declared query), which fails explicitly when the entity is gone.
|
|
251
261
|
*
|
|
252
262
|
* Pass an `intent` component when later code assumes a role such as "player"
|
|
253
263
|
* or "damage source". An intent does not prove the component is still present;
|
|
@@ -283,7 +293,7 @@ export interface InvalidHandle {
|
|
|
283
293
|
* handles, so restoring validates them like any other value.
|
|
284
294
|
*
|
|
285
295
|
* A handle only names an entity; it never proves the entity exists. Resolving
|
|
286
|
-
* it with `
|
|
296
|
+
* it with `queries.x.getHandle(...)` (through a declared query) stays the checked step, so any positive
|
|
287
297
|
* integer id decodes. Object-shaped handles and other non-numeric values
|
|
288
298
|
* are rejected.
|
|
289
299
|
*
|
package/src/EntityScope.ts
CHANGED
|
@@ -7,6 +7,12 @@
|
|
|
7
7
|
* build scenes without teaching the ECS runtime about menus, levels, overlays,
|
|
8
8
|
* or any other game-specific scene model.
|
|
9
9
|
*
|
|
10
|
+
* Use a scope when a set of entities shares one lifetime and nothing else: a
|
|
11
|
+
* level's walls, a menu's buttons. Membership is flat, unordered, and not
|
|
12
|
+
* queryable. Use `Descriptor.Hierarchy(...)` instead when entities form a
|
|
13
|
+
* parent-child tree: ordered children, `lookup.parent` / `descendants`
|
|
14
|
+
* traversal, and children despawned with their parent.
|
|
15
|
+
*
|
|
10
16
|
* @module EntityScope
|
|
11
17
|
* @docGroup core
|
|
12
18
|
*/
|
package/src/HostClock.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The host's wall clock, as an injected contract.
|
|
3
|
+
*
|
|
4
|
+
* Core and the generic devtools modules never read a platform clock
|
|
5
|
+
* (`performance`, `Date`) themselves: whatever measures host time (debug
|
|
6
|
+
* trace durations, session timings, replay windows) takes a `HostClock`.
|
|
7
|
+
* Platform packages provide implementations (`BrowserHostClock.make(window)`
|
|
8
|
+
* in the browser package); `standard` works in every JavaScript engine, and
|
|
9
|
+
* `test()` is a manual clock for deterministic tests.
|
|
10
|
+
*
|
|
11
|
+
* Host time is for measuring the host (how long a tick took), never for game
|
|
12
|
+
* logic: simulation time is a resource the schedule advances.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* const runtime = Game.Runtime.make({ services, debug: { hostClock: HostClock.standard } })
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* @module HostClock
|
|
20
|
+
* @docGroup runtime
|
|
21
|
+
*/
|
|
22
|
+
import * as Result from "./Result.ts"
|
|
23
|
+
|
|
24
|
+
/** A host clock: `now()` in milliseconds, from an arbitrary origin. */
|
|
25
|
+
export interface HostClock {
|
|
26
|
+
readonly now: () => number
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* `Date.now`, which every JavaScript engine provides. Its readings can jump
|
|
31
|
+
* (the system clock is adjusted, or the device sleeps), so a duration
|
|
32
|
+
* computed from two readings can be negative: consumers clamp durations to
|
|
33
|
+
* 0. Prefer a monotonic platform clock when there is one
|
|
34
|
+
* (`BrowserHostClock.make(window)`).
|
|
35
|
+
*/
|
|
36
|
+
export const standard: HostClock = { now: () => Date.now() }
|
|
37
|
+
|
|
38
|
+
/** `adjust` must leave a finite clock reading and never move backwards. */
|
|
39
|
+
export interface InvalidAdjustment {
|
|
40
|
+
readonly _tag: "InvalidAdjustment"
|
|
41
|
+
readonly ms: number
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** A manual clock for tests: starts at 0 and moves only through `adjust`. */
|
|
45
|
+
export interface TestHostClock extends HostClock {
|
|
46
|
+
/** Moves the clock forward by `ms` (0 or more). */
|
|
47
|
+
readonly adjust: (ms: number) => Result.Result<void, InvalidAdjustment>
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** A test clock at 0. Every reading is the same until `adjust` moves it. */
|
|
51
|
+
export const test = (): TestHostClock => {
|
|
52
|
+
let current = 0
|
|
53
|
+
return {
|
|
54
|
+
now: () => current,
|
|
55
|
+
adjust: (ms) => {
|
|
56
|
+
if (!Number.isFinite(ms) || ms < 0 || !Number.isFinite(current + ms)) return Result.failure({ _tag: "InvalidAdjustment", ms })
|
|
57
|
+
current += ms
|
|
58
|
+
return Result.success(undefined)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** A duration between two readings, clamped to 0 when the clock went backwards. */
|
|
64
|
+
export const elapsed = (from: number, to: number): number => Math.max(0, to - from)
|
package/src/Inspector.ts
CHANGED
|
@@ -6,6 +6,12 @@
|
|
|
6
6
|
* They run on demand through `runtime.inspect(...)` and return a value without
|
|
7
7
|
* advancing schedule visibility.
|
|
8
8
|
*
|
|
9
|
+
* An inspector is the one typed read-only view of the world: tests, saves, UI,
|
|
10
|
+
* `Game.Condition.check(...)` projections, and devtools invariants
|
|
11
|
+
* (`Invariant.fromInspector(...)`) are all built from it. `runtime.inspect`
|
|
12
|
+
* rejects at compile time an inspector that reads a resource, service, or
|
|
13
|
+
* machine the runtime was not given.
|
|
14
|
+
*
|
|
9
15
|
* @module Inspector
|
|
10
16
|
* @docGroup runtime
|
|
11
17
|
*/
|
|
@@ -74,7 +80,7 @@ export interface InspectorDefinition<
|
|
|
74
80
|
> {
|
|
75
81
|
readonly kind: "inspector"
|
|
76
82
|
readonly name: Name
|
|
77
|
-
readonly system: System.SystemDefinition<Spec,
|
|
83
|
+
readonly system: System.SystemDefinition<Spec, never, Root, Name, Needs>
|
|
78
84
|
readonly requirements: ReadonlyArray<Needs>
|
|
79
85
|
readonly read: (context: InspectorContext<Spec>) => Value
|
|
80
86
|
readonly __schemaRoot?: Root | undefined
|
|
@@ -111,7 +117,6 @@ export const make = <
|
|
|
111
117
|
() => undefined
|
|
112
118
|
) as unknown as System.SystemDefinition<
|
|
113
119
|
System.SystemSpec<S, Access, Root>,
|
|
114
|
-
void,
|
|
115
120
|
never,
|
|
116
121
|
Root,
|
|
117
122
|
Name,
|
package/src/Machine.ts
CHANGED
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
* run around that boundary. Menus, rounds, encounter phases, pause flows, and
|
|
10
10
|
* restart/reset pipelines are the canonical cases.
|
|
11
11
|
*
|
|
12
|
+
* A machine holds one value for the whole game. A state that each entity has
|
|
13
|
+
* on its own (an enemy's attack windup, a door's open/closed) is not a
|
|
14
|
+
* machine: use a `Descriptor.EntityState(...)` component instead.
|
|
15
|
+
*
|
|
12
16
|
* @example
|
|
13
17
|
* ```ts
|
|
14
18
|
* // Model a gameplay phase whose transition timing matters.
|
|
@@ -33,7 +37,7 @@
|
|
|
33
37
|
* @groupDescription Functions
|
|
34
38
|
* Public constructors for machines, conditions, and explicit transition bundles.
|
|
35
39
|
*/
|
|
36
|
-
import type { Schedule } from "./Schedule.ts"
|
|
40
|
+
import type { AnyScheduleOrder, Schedule } from "./Schedule.ts"
|
|
37
41
|
import * as Requirement from "./Requirement.ts"
|
|
38
42
|
import type { Schema } from "./Schema.ts"
|
|
39
43
|
import type { SystemFailure } from "./System.ts"
|
|
@@ -235,13 +239,16 @@ export interface TransitionScheduleDefinition<
|
|
|
235
239
|
M extends StateMachineDefinition = StateMachineDefinition,
|
|
236
240
|
Needs extends Requirement.Requirement = Requirement.Requirement,
|
|
237
241
|
Root = unknown,
|
|
238
|
-
Failure extends SystemFailure = never
|
|
242
|
+
Failure extends SystemFailure = never,
|
|
243
|
+
Order extends AnyScheduleOrder = AnyScheduleOrder
|
|
239
244
|
> {
|
|
240
245
|
readonly steps: ReadonlyArray<Schedule.Step>
|
|
241
246
|
readonly systems: ReadonlyArray<unknown>
|
|
242
247
|
readonly schema: S
|
|
243
248
|
readonly requirements: ReadonlyArray<Needs>
|
|
244
249
|
readonly __failure?: (_: never) => Failure
|
|
250
|
+
/** The plan's order, for `Game.System.thisTick` checks of the tick that applies it. */
|
|
251
|
+
readonly __order?: (_: never) => Order
|
|
245
252
|
readonly __schemaRoot?: Root | undefined
|
|
246
253
|
readonly transition: {
|
|
247
254
|
readonly machine: M
|
|
@@ -293,7 +300,7 @@ export type MachineNeedsFromConditions<C extends ReadonlyArray<Condition>> =
|
|
|
293
300
|
MachineNeedsFromCondition<C[number]>
|
|
294
301
|
|
|
295
302
|
/**
|
|
296
|
-
* Creates a schema-bound finite-state machine definition.
|
|
303
|
+
* Creates a schema-bound finite-state machine definition: one game-wide mode.
|
|
297
304
|
*
|
|
298
305
|
* This is the intended default for gameplay phases and other discrete modes
|
|
299
306
|
* where the transition boundary itself matters.
|
|
@@ -304,6 +311,9 @@ export type MachineNeedsFromConditions<C extends ReadonlyArray<Condition>> =
|
|
|
304
311
|
* - explicit `applyStateTransitions(...)`
|
|
305
312
|
* - `inState(...)` gating
|
|
306
313
|
* - transition events or enter/exit schedules
|
|
314
|
+
*
|
|
315
|
+
* Use `Descriptor.EntityState(...)` instead when every entity carries its own
|
|
316
|
+
* state (per-entity, written immediately, no enter/exit schedules).
|
|
307
317
|
*/
|
|
308
318
|
export const StateMachine = <
|
|
309
319
|
const Name extends string,
|
package/src/Query.ts
CHANGED
|
@@ -69,7 +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 type { Descriptor,
|
|
72
|
+
import type { Descriptor, EntityStateValue } from "./Descriptor.ts"
|
|
73
73
|
import type { EntityId, EntityMut, EntityRef } from "./Entity.ts"
|
|
74
74
|
import type * as Result from "./Result.ts"
|
|
75
75
|
import type * as Relation from "./Relation.ts"
|
|
@@ -384,6 +384,9 @@ export namespace Query {
|
|
|
384
384
|
? false
|
|
385
385
|
: true
|
|
386
386
|
|
|
387
|
+
/** The component descriptors a query proves its matches have: required selection slots and `with`. */
|
|
388
|
+
export type Proven<T extends Any> = RequiredSelectionDescriptors<T> | T["with"][number]
|
|
389
|
+
|
|
387
390
|
/**
|
|
388
391
|
* The readable proof produced by a query.
|
|
389
392
|
*/
|
|
@@ -488,24 +491,20 @@ export interface ReadCell<T> {
|
|
|
488
491
|
* A mutable cell returned from query or resource access.
|
|
489
492
|
*
|
|
490
493
|
* This gives a narrow mutation surface instead of exposing raw storage objects.
|
|
494
|
+
* Values written here are already valid. Data that still needs validation
|
|
495
|
+
* goes through its descriptor's own validator: declare the descriptor with
|
|
496
|
+
* `Descriptor.ConstructedComponent` / `ConstructedResource` and write with
|
|
497
|
+
* `setRaw` / `updateRaw` on its {@link ConstructedWriteCell}.
|
|
491
498
|
*/
|
|
492
499
|
export interface WriteCell<T> extends ReadCell<T> {
|
|
493
500
|
/**
|
|
494
501
|
* Replaces the current value.
|
|
495
502
|
*/
|
|
496
503
|
set(value: T): void
|
|
497
|
-
/**
|
|
498
|
-
* Replaces the current value from an explicit result.
|
|
499
|
-
*/
|
|
500
|
-
setResult<E>(result: Result.Result<T, E>): Result.Result<void, E>
|
|
501
504
|
/**
|
|
502
505
|
* Updates the current value based on the previous one.
|
|
503
506
|
*/
|
|
504
507
|
update(f: (current: ReadonlyValue<T>) => T): void
|
|
505
|
-
/**
|
|
506
|
-
* Updates the current value from an explicit result-producing callback.
|
|
507
|
-
*/
|
|
508
|
-
updateResult<E>(f: (current: ReadonlyValue<T>) => Result.Result<T, E>): Result.Result<void, E>
|
|
509
508
|
}
|
|
510
509
|
|
|
511
510
|
/**
|
|
@@ -540,13 +539,13 @@ export interface ConstructedWriteCell<T, Raw, Error> extends WriteCell<T> {
|
|
|
540
539
|
}
|
|
541
540
|
|
|
542
541
|
/**
|
|
543
|
-
* A `transition(from, to)` found the state
|
|
542
|
+
* A `transition(from, to)` found the entity state in another state, so
|
|
544
543
|
* nothing was written.
|
|
545
544
|
*/
|
|
546
|
-
export interface
|
|
547
|
-
readonly _tag: "
|
|
548
|
-
/** Name of the
|
|
549
|
-
readonly
|
|
545
|
+
export interface EntityStateMismatchError<T> {
|
|
546
|
+
readonly _tag: "EntityStateMismatch"
|
|
547
|
+
/** Name of the `Descriptor.EntityState` component. */
|
|
548
|
+
readonly component: string
|
|
550
549
|
/** The `from` state the transition expected. */
|
|
551
550
|
readonly expected: T
|
|
552
551
|
/** The state the component was actually in. */
|
|
@@ -554,20 +553,20 @@ export interface StateMismatchError<T> {
|
|
|
554
553
|
}
|
|
555
554
|
|
|
556
555
|
/**
|
|
557
|
-
* Writable cell for
|
|
556
|
+
* Writable cell for an entity state component (`Descriptor.EntityState`).
|
|
558
557
|
*
|
|
559
558
|
* `transition(from, to)` is a compare-and-set: it writes `to` only while the
|
|
560
|
-
* current state is `from`, and otherwise returns `
|
|
559
|
+
* current state is `from`, and otherwise returns `EntityStateMismatch` without
|
|
561
560
|
* writing. With a transition graph, `to` must be one of the moves the graph
|
|
562
561
|
* allows from `from`; without one, any pair of states is accepted.
|
|
563
562
|
*/
|
|
564
|
-
export interface
|
|
563
|
+
export interface EntityStateWriteCell<T, Transitions> extends ConstructedWriteCell<T, T, DecodeError> {
|
|
565
564
|
/**
|
|
566
565
|
* Moves from `from` to `to` if the component is still in `from`.
|
|
567
566
|
*
|
|
568
567
|
* @example
|
|
569
568
|
* ```ts
|
|
570
|
-
* const moved = data.
|
|
569
|
+
* const moved = data.attack.transition("windup", "active")
|
|
571
570
|
* if (!moved.ok) {
|
|
572
571
|
* // moved.error.actual is the state some earlier write left
|
|
573
572
|
* }
|
|
@@ -575,19 +574,19 @@ export interface StateWriteCell<T, Transitions> extends ConstructedWriteCell<T,
|
|
|
575
574
|
*/
|
|
576
575
|
transition<const From extends T & keyof Transitions>(
|
|
577
576
|
from: From,
|
|
578
|
-
to:
|
|
579
|
-
): Result.Result<void,
|
|
577
|
+
to: EntityStateTarget<T, Transitions, From>
|
|
578
|
+
): Result.Result<void, EntityStateMismatchError<T>>
|
|
580
579
|
}
|
|
581
580
|
|
|
582
|
-
type
|
|
581
|
+
type EntityStateTarget<T, Transitions, From extends keyof Transitions> =
|
|
583
582
|
Transitions[From] extends ReadonlyArray<infer To> ? To & T : never
|
|
584
583
|
|
|
585
584
|
/**
|
|
586
585
|
* Write-cell surface produced for one queried component descriptor.
|
|
587
586
|
*/
|
|
588
587
|
export type WriteCellForDescriptor<D extends ComponentDescriptor> =
|
|
589
|
-
D extends import("./Descriptor.ts").
|
|
590
|
-
?
|
|
588
|
+
D extends import("./Descriptor.ts").EntityStateDescriptor<string, infer States extends readonly [EntityStateValue, ...Array<EntityStateValue>], any>
|
|
589
|
+
? EntityStateWriteCell<States[number], D["transitions"] extends undefined ? { readonly [K in States[number]]: States } : D["transitions"]>
|
|
591
590
|
: D extends import("./Descriptor.ts").ConstructedDescriptor<"component", string, infer Value, infer Raw, infer Error>
|
|
592
591
|
? ConstructedWriteCell<Value, Raw, Error>
|
|
593
592
|
: WriteCell<Descriptor.Value<D>>
|
|
@@ -622,11 +621,11 @@ export type OptionalReadCell<T> = PresentOptionalReadCell<T> | AbsentOptionalRea
|
|
|
622
621
|
export type QueryMatch<S extends Schema.Any, Q extends Query.Any> =
|
|
623
622
|
keyof Query.WriteProof<Q> extends never
|
|
624
623
|
? {
|
|
625
|
-
readonly entity: EntityRef<S, Query.ReadProof<Q>, Query.Root<Q>>
|
|
624
|
+
readonly entity: EntityRef<S, Query.ReadProof<Q>, Query.Root<Q>, Query.Proven<Q>>
|
|
626
625
|
readonly data: Query.Cells<Q>
|
|
627
626
|
}
|
|
628
627
|
: {
|
|
629
|
-
readonly entity: EntityMut<S, Query.ReadProof<Q>, Query.WriteProof<Q>, Query.Root<Q>>
|
|
628
|
+
readonly entity: EntityMut<S, Query.ReadProof<Q>, Query.WriteProof<Q>, Query.Root<Q>, Query.Proven<Q>>
|
|
630
629
|
readonly data: Query.Cells<Q>
|
|
631
630
|
}
|
|
632
631
|
|
|
@@ -685,6 +684,14 @@ export const multipleEntitiesError = (count: number): Query.MultipleEntitiesErro
|
|
|
685
684
|
* })
|
|
686
685
|
* ```
|
|
687
686
|
*/
|
|
687
|
+
/**
|
|
688
|
+
* `T` itself, but not a place inference reads from: a list a query omits
|
|
689
|
+
* stays `[]` even when the call sits where a wider query type is expected
|
|
690
|
+
* (`queries: { ... }` in a spec). Unlike `NoInfer`, it prints as `T`.
|
|
691
|
+
*/
|
|
692
|
+
// `T` appears only as the check type, which inference never reads.
|
|
693
|
+
export type Uninferred<T, Bound> = [T] extends [infer U extends Bound] ? U : never
|
|
694
|
+
|
|
688
695
|
export const Query = <
|
|
689
696
|
const Selection extends Record<string, SelectionAccess<any, Root>>,
|
|
690
697
|
const With extends ReadonlyArray<ComponentDescriptor> = [],
|
|
@@ -704,14 +711,15 @@ export const Query = <
|
|
|
704
711
|
readonly withoutRelations?: WithoutRelations
|
|
705
712
|
readonly withRelated?: WithRelated
|
|
706
713
|
readonly withoutRelated?: WithoutRelated
|
|
707
|
-
|
|
714
|
+
// Uninferred: an omitted list is `[]`, never inferred from the spec it is written in (`queries: { ... }`).
|
|
715
|
+
}): QuerySpec<Selection, Uninferred<With, ReadonlyArray<ComponentDescriptor>>, Uninferred<Without, ReadonlyArray<ComponentDescriptor>>, Uninferred<Filters, ReadonlyArray<Filter<ComponentDescriptor>>>, Uninferred<WithRelations, ReadonlyArray<Relation.Relation.Any>>, Uninferred<WithoutRelations, ReadonlyArray<Relation.Relation.Any>>, Uninferred<WithRelated, ReadonlyArray<Relation.Relation.Any>>, Uninferred<WithoutRelated, ReadonlyArray<Relation.Relation.Any>>, Root> => ({
|
|
708
716
|
selection: spec.selection,
|
|
709
|
-
with: (spec.with ?? []) as With,
|
|
710
|
-
without: (spec.without ?? []) as Without,
|
|
711
|
-
filters: (spec.filters ?? []) as Filters,
|
|
712
|
-
withRelations: (spec.withRelations ?? []) as WithRelations,
|
|
713
|
-
withoutRelations: (spec.withoutRelations ?? []) as WithoutRelations,
|
|
714
|
-
withRelated: (spec.withRelated ?? []) as WithRelated,
|
|
715
|
-
withoutRelated: (spec.withoutRelated ?? []) as WithoutRelated,
|
|
717
|
+
with: (spec.with ?? []) as unknown as Uninferred<With, ReadonlyArray<ComponentDescriptor>>,
|
|
718
|
+
without: (spec.without ?? []) as unknown as Uninferred<Without, ReadonlyArray<ComponentDescriptor>>,
|
|
719
|
+
filters: (spec.filters ?? []) as unknown as Uninferred<Filters, ReadonlyArray<Filter<ComponentDescriptor>>>,
|
|
720
|
+
withRelations: (spec.withRelations ?? []) as unknown as Uninferred<WithRelations, ReadonlyArray<Relation.Relation.Any>>,
|
|
721
|
+
withoutRelations: (spec.withoutRelations ?? []) as unknown as Uninferred<WithoutRelations, ReadonlyArray<Relation.Relation.Any>>,
|
|
722
|
+
withRelated: (spec.withRelated ?? []) as unknown as Uninferred<WithRelated, ReadonlyArray<Relation.Relation.Any>>,
|
|
723
|
+
withoutRelated: (spec.withoutRelated ?? []) as unknown as Uninferred<WithoutRelated, ReadonlyArray<Relation.Relation.Any>>,
|
|
716
724
|
__schemaRoot: undefined as unknown as Root
|
|
717
725
|
})
|