@skewedaspect/sage-core 0.10.0-rc.2 → 0.10.0-rc.4
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 +1 -1
- package/dist/behavior.d.ts +3 -0
- package/dist/gameEntity.d.ts +7 -7
- package/dist/gameRandom.d.ts +26 -0
- package/dist/index.d.ts +5 -4
- package/dist/sage-core.js +157 -58
- package/dist/sage-core.js.map +1 -1
- package/dist/simulation.d.ts +31 -5
- package/package.json +1 -1
- package/dist/simulationContext.d.ts +0 -6
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# @skewedaspect/sage-core
|
|
2
2
|
|
|
3
|
-
The entity system: `GameEntity`, `Behavior`, the event map and the event bus, `
|
|
3
|
+
The entity system: `GameEntity`, `Behavior`, the event map and the event bus, `GameSimulationView`, `GameSimulation`, snapshot and restore, names and tags, and the action declaration type.
|
|
4
4
|
|
|
5
5
|
It imports nothing from Babylon and nothing from the DOM.
|
package/dist/behavior.d.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { Computation } from "./computedState.js";
|
|
2
2
|
import { EventSubscription, GameEvent } from "./events.js";
|
|
3
3
|
import { GameEntity } from "./gameEntity.js";
|
|
4
|
+
import { GameSimulationView } from "./simulation.js";
|
|
5
|
+
export type SimulationFor<Entity extends GameEntity> = Entity extends GameEntity<infer Context> ? Context : GameSimulationView;
|
|
4
6
|
export declare abstract class Behavior<State extends object, Requires extends object = object, Entity extends GameEntity = GameEntity> {
|
|
5
7
|
readonly state: State;
|
|
6
8
|
readonly ops: Requires;
|
|
7
9
|
readonly entity: Entity;
|
|
10
|
+
protected readonly simulation: SimulationFor<Entity>;
|
|
8
11
|
abstract readonly defaults: State;
|
|
9
12
|
onStateReady?(): void;
|
|
10
13
|
onCreate?(): void;
|
package/dist/gameEntity.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Computation } from "./computedState.js";
|
|
2
2
|
import { EventMap, GameEvent, Subscriber } from "./events.js";
|
|
3
3
|
import { Behavior, BehaviorClass } from "./behavior.js";
|
|
4
|
-
import {
|
|
4
|
+
import { GameSimulationView } from "./simulation.js";
|
|
5
5
|
export type StateValue = null | boolean | number | string | StateValue[] | {
|
|
6
6
|
[key: string]: StateValue;
|
|
7
7
|
};
|
|
@@ -59,7 +59,7 @@ export interface GameEntityType {
|
|
|
59
59
|
readonly behaviors: readonly BehaviorClass[];
|
|
60
60
|
readonly prototype: GameEntity;
|
|
61
61
|
}
|
|
62
|
-
export interface GameEntityClass<Context extends
|
|
62
|
+
export interface GameEntityClass<Context extends GameSimulationView = GameSimulationView> extends GameEntityType {
|
|
63
63
|
readonly create: (id: string, context: Context, state?: object, name?: string) => GameEntity;
|
|
64
64
|
readonly restore: (id: string, context: Context, state: GameEntityState, name?: string) => GameEntity;
|
|
65
65
|
}
|
|
@@ -72,21 +72,21 @@ export type NarrowedTo<Class extends BehaviorClass> = GameEntity & {
|
|
|
72
72
|
readonly ops: OpsOf<Class>;
|
|
73
73
|
};
|
|
74
74
|
type BehaviorInstance = Behavior<object, object>;
|
|
75
|
-
export declare abstract class GameEntity implements Subscriber {
|
|
75
|
+
export declare abstract class GameEntity<Context extends GameSimulationView = GameSimulationView> implements Subscriber {
|
|
76
76
|
#private;
|
|
77
77
|
readonly id: string;
|
|
78
78
|
readonly name: string;
|
|
79
|
-
protected readonly context:
|
|
79
|
+
protected readonly context: Context;
|
|
80
80
|
readonly state: object;
|
|
81
81
|
readonly ops: object;
|
|
82
82
|
static readonly behaviors: readonly BehaviorClass[];
|
|
83
83
|
static create<Type extends GameEntityType>(this: Type & {
|
|
84
84
|
behaviors: ValidTuple<Type['behaviors'], Type['prototype']>;
|
|
85
|
-
}, id: string, context:
|
|
85
|
+
}, id: string, context: GameSimulationView, state?: Partial<StateFor<Type['behaviors']>>, name?: string): ComposedOf<Type>;
|
|
86
86
|
static restore<Type extends GameEntityType>(this: Type & {
|
|
87
87
|
behaviors: ValidTuple<Type['behaviors'], Type['prototype']>;
|
|
88
|
-
}, id: string, context:
|
|
89
|
-
protected constructor(id: string, context:
|
|
88
|
+
}, id: string, context: GameSimulationView, state: StateFor<Type['behaviors']>, name?: string): ComposedOf<Type>;
|
|
89
|
+
protected constructor(id: string, context: Context, providedState: object, runCreateHooks: boolean, name?: string);
|
|
90
90
|
hasBehavior<Class extends BehaviorClass>(behaviorClass: Class): this is NarrowedTo<Class>;
|
|
91
91
|
getEntity(id: string): GameEntity | undefined;
|
|
92
92
|
emit<Type extends keyof EventMap & string>(type: Type, payload: EventMap[Type], targetID?: string): void;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export type RandomSeed = number | string;
|
|
2
|
+
export interface RandomState {
|
|
3
|
+
readonly a: number;
|
|
4
|
+
readonly b: number;
|
|
5
|
+
readonly c: number;
|
|
6
|
+
readonly d: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The seeded generator, running xoshiro128** over four 32-bit words (design/simulation.md#random-numbers).
|
|
10
|
+
*/
|
|
11
|
+
export declare class GameRandom {
|
|
12
|
+
#private;
|
|
13
|
+
readonly seed: number;
|
|
14
|
+
constructor(seed?: RandomSeed);
|
|
15
|
+
get state(): RandomState;
|
|
16
|
+
set state(value: RandomState);
|
|
17
|
+
nextInt(): number;
|
|
18
|
+
nextFloat(): number;
|
|
19
|
+
between(min: number, max: number): number;
|
|
20
|
+
wholeBetween(min: number, max: number): number;
|
|
21
|
+
chance(probability: number): boolean;
|
|
22
|
+
normal(mean?: number, deviation?: number): number;
|
|
23
|
+
pick<Item>(items: readonly Item[]): Item;
|
|
24
|
+
shuffle<Item>(items: readonly Item[]): Item[];
|
|
25
|
+
fork(): GameRandom;
|
|
26
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export interface EventMap {
|
|
|
2
2
|
}
|
|
3
3
|
export type { Action, AnalogAction, DigitalAction } from "./action.js";
|
|
4
4
|
export { Behavior } from "./behavior.js";
|
|
5
|
-
export type { BehaviorClass } from "./behavior.js";
|
|
5
|
+
export type { BehaviorClass, SimulationFor } from "./behavior.js";
|
|
6
6
|
export type { Computation } from "./computedState.js";
|
|
7
7
|
export { NamedBehavior } from "./namedBehavior.js";
|
|
8
8
|
export type { NamedState } from "./namedBehavior.js";
|
|
@@ -14,6 +14,7 @@ export { EventBus } from "./eventBus.js";
|
|
|
14
14
|
export type { EventCallback } from "./eventBus.js";
|
|
15
15
|
export { GameEntity } from "./gameEntity.js";
|
|
16
16
|
export type { ComposedOf, DeepReadonly, GameEntityClass, GameEntityState, GameEntityType, NarrowedTo, OpsFor, OpsOf, StateBag, StateFor, StateSliceOf, StateValue, ValidTuple, } from "./gameEntity.js";
|
|
17
|
-
export {
|
|
18
|
-
export type {
|
|
19
|
-
export
|
|
17
|
+
export { GameRandom } from "./gameRandom.js";
|
|
18
|
+
export type { RandomSeed, RandomState } from "./gameRandom.js";
|
|
19
|
+
export { GameSimulation } from "./simulation.js";
|
|
20
|
+
export type { GameEntityRegistry, GameEntitySnapshot, GameSimulationOptions, GameSimulationSnapshot, GameSimulationView, IDAllocator, ValidEntityRegistry, } from "./simulation.js";
|
package/dist/sage-core.js
CHANGED
|
@@ -247,12 +247,13 @@ var _ = class e {
|
|
|
247
247
|
this.ops = s;
|
|
248
248
|
let c = [];
|
|
249
249
|
for (let [e, t] of o.entries()) {
|
|
250
|
-
let
|
|
251
|
-
|
|
250
|
+
let r = this.#e[e];
|
|
251
|
+
r !== void 0 && (Object.assign(r, {
|
|
252
252
|
state: this.state,
|
|
253
253
|
ops: s,
|
|
254
|
-
entity: this
|
|
255
|
-
|
|
254
|
+
entity: this,
|
|
255
|
+
simulation: n
|
|
256
|
+
}), this.#s(r, t, s), this.#c(r, t, c));
|
|
256
257
|
}
|
|
257
258
|
this.declareComputedState(r);
|
|
258
259
|
for (let e of this.#e) e.onStateReady?.();
|
|
@@ -362,7 +363,100 @@ var _ = class e {
|
|
|
362
363
|
for (let n of e.patterns) if (n.test(t)) return !0;
|
|
363
364
|
return !1;
|
|
364
365
|
}
|
|
365
|
-
}, v =
|
|
366
|
+
}, v = 2166136261, y = 16777619, b = 2654435769, x = 569420461, S = 1935289751, C = 2 ** 32;
|
|
367
|
+
function w(e) {
|
|
368
|
+
return e >>> 0;
|
|
369
|
+
}
|
|
370
|
+
function T(e, t) {
|
|
371
|
+
return w(Math.imul(e, t));
|
|
372
|
+
}
|
|
373
|
+
function E(e, t) {
|
|
374
|
+
return w(e << t | e >>> 32 - t);
|
|
375
|
+
}
|
|
376
|
+
function D(e) {
|
|
377
|
+
let t = w(v);
|
|
378
|
+
for (let n = 0; n < e.length; n++) t = w(t ^ e.charCodeAt(n)), t = T(t, y);
|
|
379
|
+
return t;
|
|
380
|
+
}
|
|
381
|
+
function O(e) {
|
|
382
|
+
return typeof e == "string" ? D(e) : w(e);
|
|
383
|
+
}
|
|
384
|
+
function k(e) {
|
|
385
|
+
let t = e;
|
|
386
|
+
return t = w(t ^ t >>> 16), t = T(t, x), t = w(t ^ t >>> 15), t = T(t, S), t = w(t ^ t >>> 15), t;
|
|
387
|
+
}
|
|
388
|
+
function A(e) {
|
|
389
|
+
let t = w(e + b), n = w(t + b), r = w(n + b), i = w(r + b);
|
|
390
|
+
return {
|
|
391
|
+
a: k(t),
|
|
392
|
+
b: k(n),
|
|
393
|
+
c: k(r),
|
|
394
|
+
d: k(i)
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
var j = class e {
|
|
398
|
+
seed;
|
|
399
|
+
#e;
|
|
400
|
+
#t;
|
|
401
|
+
#n;
|
|
402
|
+
#r;
|
|
403
|
+
constructor(e) {
|
|
404
|
+
let t = e;
|
|
405
|
+
t === void 0 && (t = Date.now()), this.seed = O(t);
|
|
406
|
+
let n = A(this.seed);
|
|
407
|
+
this.#e = n.a, this.#t = n.b, this.#n = n.c, this.#r = n.d;
|
|
408
|
+
}
|
|
409
|
+
get state() {
|
|
410
|
+
return {
|
|
411
|
+
a: this.#e,
|
|
412
|
+
b: this.#t,
|
|
413
|
+
c: this.#n,
|
|
414
|
+
d: this.#r
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
set state(e) {
|
|
418
|
+
this.#e = w(e.a), this.#t = w(e.b), this.#n = w(e.c), this.#r = w(e.d);
|
|
419
|
+
}
|
|
420
|
+
nextInt() {
|
|
421
|
+
let e = T(E(T(this.#t, 5), 7), 9), t = w(this.#t << 9);
|
|
422
|
+
return this.#n = w(this.#n ^ this.#e), this.#r = w(this.#r ^ this.#t), this.#t = w(this.#t ^ this.#n), this.#e = w(this.#e ^ this.#r), this.#n = w(this.#n ^ t), this.#r = E(this.#r, 11), e;
|
|
423
|
+
}
|
|
424
|
+
nextFloat() {
|
|
425
|
+
return this.nextInt() / C;
|
|
426
|
+
}
|
|
427
|
+
between(e, t) {
|
|
428
|
+
if (t < e) throw RangeError(`A max of ${t} is below its min of ${e}.`);
|
|
429
|
+
return e + this.nextFloat() * (t - e);
|
|
430
|
+
}
|
|
431
|
+
wholeBetween(e, t) {
|
|
432
|
+
if (t < e) throw RangeError(`A max of ${t} is below its min of ${e}.`);
|
|
433
|
+
return e + Math.floor(this.nextFloat() * (t - e + 1));
|
|
434
|
+
}
|
|
435
|
+
chance(e) {
|
|
436
|
+
if (e < 0 || e > 1) throw RangeError(`A probability of ${e} is outside [0, 1].`);
|
|
437
|
+
return this.nextFloat() < e;
|
|
438
|
+
}
|
|
439
|
+
normal(e = 0, t = 1) {
|
|
440
|
+
if (t < 0) throw RangeError(`A deviation of ${t} is negative.`);
|
|
441
|
+
let n = 1 - this.nextFloat(), r = this.nextFloat();
|
|
442
|
+
return e + t * Math.sqrt(-2 * Math.log(n)) * Math.cos(2 * Math.PI * r);
|
|
443
|
+
}
|
|
444
|
+
pick(e) {
|
|
445
|
+
if (e.length === 0) throw RangeError("Cannot pick from an empty items array.");
|
|
446
|
+
return e[this.wholeBetween(0, e.length - 1)];
|
|
447
|
+
}
|
|
448
|
+
shuffle(e) {
|
|
449
|
+
let t = [...e];
|
|
450
|
+
for (let e = t.length - 1; e > 0; e--) {
|
|
451
|
+
let n = this.wholeBetween(0, e), r = t[e];
|
|
452
|
+
t[e] = t[n], t[n] = r;
|
|
453
|
+
}
|
|
454
|
+
return t;
|
|
455
|
+
}
|
|
456
|
+
fork() {
|
|
457
|
+
return new e(this.nextInt());
|
|
458
|
+
}
|
|
459
|
+
}, M = class extends p {
|
|
366
460
|
#e;
|
|
367
461
|
#t;
|
|
368
462
|
#n = [];
|
|
@@ -404,8 +498,7 @@ var _ = class e {
|
|
|
404
498
|
else super.drain();
|
|
405
499
|
}
|
|
406
500
|
attach() {
|
|
407
|
-
if (this.#t
|
|
408
|
-
if (this.#r === void 0) {
|
|
501
|
+
if (this.#t !== void 0 && this.#r === void 0) {
|
|
409
502
|
this.#r = this.#t.register({
|
|
410
503
|
id: this.#e,
|
|
411
504
|
receive: () => !1
|
|
@@ -424,21 +517,23 @@ var _ = class e {
|
|
|
424
517
|
#a() {
|
|
425
518
|
if (this.#r !== void 0) return this.#t;
|
|
426
519
|
}
|
|
427
|
-
},
|
|
520
|
+
}, N = "global", P = 128 * 2 ** -52, F = class e {
|
|
428
521
|
#e;
|
|
429
522
|
#t;
|
|
430
523
|
#n;
|
|
431
524
|
#r;
|
|
432
525
|
#i;
|
|
433
526
|
#a;
|
|
434
|
-
#o
|
|
435
|
-
#s
|
|
527
|
+
#o;
|
|
528
|
+
#s;
|
|
436
529
|
#c = 0;
|
|
437
|
-
#l =
|
|
438
|
-
#u =
|
|
439
|
-
#d =
|
|
530
|
+
#l = 0;
|
|
531
|
+
#u = 0;
|
|
532
|
+
#d = [];
|
|
533
|
+
#f = /* @__PURE__ */ new Map();
|
|
534
|
+
#p = /* @__PURE__ */ new Set();
|
|
440
535
|
constructor(e) {
|
|
441
|
-
if (this.#e = e.id, this.#r = e.fixedStepDuration, this.#i = e.entityClasses, this.#a = e.allocator, this.#n = e.bus, this.#t = new
|
|
536
|
+
if (this.#e = e.id, this.#r = e.fixedStepDuration, this.#i = e.entityClasses, this.#a = e.allocator, this.#o = e.seed ?? e.id, this.#s = new j(this.#o), this.#n = e.bus, this.#t = new M(e.id, e.bus), this.#e === N) throw Error(`The simulation name "${N}" is reserved for the persistent simulation.`);
|
|
442
537
|
if (!Number.isFinite(this.#r) || this.#r <= 0) throw RangeError("A fixed step duration must be a finite number greater than zero.");
|
|
443
538
|
}
|
|
444
539
|
get id() {
|
|
@@ -451,10 +546,10 @@ var _ = class e {
|
|
|
451
546
|
return this.#t.attached;
|
|
452
547
|
}
|
|
453
548
|
get tick() {
|
|
454
|
-
return this.#
|
|
549
|
+
return this.#c;
|
|
455
550
|
}
|
|
456
551
|
get entities() {
|
|
457
|
-
return [...this.#
|
|
552
|
+
return [...this.#d];
|
|
458
553
|
}
|
|
459
554
|
attach() {
|
|
460
555
|
this.#t.attach();
|
|
@@ -466,53 +561,56 @@ var _ = class e {
|
|
|
466
561
|
return this.spawnByName(e, t, n);
|
|
467
562
|
}
|
|
468
563
|
spawnByName(e, t, n) {
|
|
469
|
-
let r = this.#
|
|
470
|
-
if (i === void 0 && (i = this.#
|
|
471
|
-
if (this.#
|
|
472
|
-
let a = r.create(i, this.#
|
|
473
|
-
return this.#
|
|
564
|
+
let r = this.#S(e), i = n;
|
|
565
|
+
if (i === void 0 && (i = this.#y()), this.#f.has(i)) throw Error(`The simulation already holds an entity under the ID "${i}".`);
|
|
566
|
+
if (this.#p.has(i)) throw Error(`The simulation already retired the ID "${i}"; an ID is never reused.`);
|
|
567
|
+
let a = r.create(i, this.#x(), t ?? {}, e);
|
|
568
|
+
return this.#d.push(a), this.#f.set(i, a), a;
|
|
474
569
|
}
|
|
475
570
|
despawn(e) {
|
|
476
|
-
return this.#
|
|
571
|
+
return this.#m(e, /* @__PURE__ */ new Set());
|
|
477
572
|
}
|
|
478
|
-
#
|
|
479
|
-
let n = this.#
|
|
573
|
+
#m(e, t) {
|
|
574
|
+
let n = this.#f.get(e);
|
|
480
575
|
if (n === void 0) return !1;
|
|
481
576
|
t.add(e);
|
|
482
|
-
for (let n of this.#
|
|
483
|
-
return n.destroy(), this.#
|
|
577
|
+
for (let n of this.#v(e)) t.has(n.id) || this.#m(n.id, t);
|
|
578
|
+
return n.destroy(), this.#d.splice(this.#d.indexOf(n), 1), this.#f.delete(e), this.#p.add(e), !0;
|
|
484
579
|
}
|
|
485
580
|
getEntity(e) {
|
|
486
|
-
return this.#
|
|
581
|
+
return this.#f.get(e);
|
|
487
582
|
}
|
|
488
583
|
getEntitiesByTag(t) {
|
|
489
584
|
let n = [];
|
|
490
|
-
for (let r of this.#
|
|
585
|
+
for (let r of this.#d) e.#C(r).tags.includes(t) && n.push(r);
|
|
491
586
|
return n;
|
|
492
587
|
}
|
|
493
588
|
getEntitiesOfClass(e) {
|
|
494
589
|
let t = [];
|
|
495
|
-
for (let n of this.#
|
|
590
|
+
for (let n of this.#d) n instanceof e && t.push(n);
|
|
496
591
|
return t;
|
|
497
592
|
}
|
|
498
593
|
runStepsForElapsedTime(e) {
|
|
499
594
|
if (!Number.isFinite(e) || e < 0) throw RangeError("Elapsed time must be a finite number of zero or more.");
|
|
500
|
-
this.#
|
|
595
|
+
this.#u += e;
|
|
501
596
|
let t = 0;
|
|
502
|
-
for (; this.#
|
|
503
|
-
t === 5 && this.#
|
|
597
|
+
for (; this.#g() && t < 5;) this.#_(), t += 1;
|
|
598
|
+
t === 5 && this.#g() && (this.#u %= this.#r);
|
|
599
|
+
}
|
|
600
|
+
get random() {
|
|
601
|
+
return this.#s;
|
|
504
602
|
}
|
|
505
603
|
snapshot() {
|
|
506
604
|
let e = [];
|
|
507
|
-
for (let t of this.#
|
|
605
|
+
for (let t of this.#d) e.push({
|
|
508
606
|
id: t.id,
|
|
509
607
|
name: t.name,
|
|
510
608
|
state: g(t.state, t.id)
|
|
511
609
|
});
|
|
512
610
|
return {
|
|
513
611
|
id: this.#e,
|
|
514
|
-
tick: this.#
|
|
515
|
-
idCounter: this.#
|
|
612
|
+
tick: this.#c,
|
|
613
|
+
idCounter: this.#l,
|
|
516
614
|
entities: e
|
|
517
615
|
};
|
|
518
616
|
}
|
|
@@ -522,68 +620,69 @@ var _ = class e {
|
|
|
522
620
|
fixedStepDuration: this.#r,
|
|
523
621
|
entityClasses: this.#i,
|
|
524
622
|
allocator: this.#a,
|
|
623
|
+
seed: this.#o,
|
|
525
624
|
bus: this.#n
|
|
526
625
|
});
|
|
527
|
-
return t.#
|
|
626
|
+
return t.#s.state = this.#s.state, t.#h(e), t;
|
|
528
627
|
}
|
|
529
628
|
buildReplacementSimulation(t) {
|
|
530
629
|
return new e(t);
|
|
531
630
|
}
|
|
532
|
-
#
|
|
631
|
+
#h(e) {
|
|
533
632
|
let t = [];
|
|
534
633
|
try {
|
|
535
634
|
for (let n of e.entities) {
|
|
536
|
-
let e = this.#
|
|
635
|
+
let e = this.#S(n.name).restore(n.id, this.#x(), n.state, n.name);
|
|
537
636
|
t.push(e);
|
|
538
637
|
}
|
|
539
638
|
} catch (e) {
|
|
540
639
|
for (let e of t) e.destroy();
|
|
541
640
|
throw e;
|
|
542
641
|
}
|
|
543
|
-
this.#
|
|
544
|
-
for (let e of t) this.#
|
|
545
|
-
this.#
|
|
642
|
+
this.#d = t, this.#f = /* @__PURE__ */ new Map();
|
|
643
|
+
for (let e of t) this.#f.set(e.id, e);
|
|
644
|
+
this.#c = e.tick, this.#l = e.idCounter;
|
|
546
645
|
}
|
|
547
|
-
#
|
|
548
|
-
let e =
|
|
549
|
-
return this.#
|
|
646
|
+
#g() {
|
|
647
|
+
let e = P * this.#r;
|
|
648
|
+
return this.#u + e >= this.#r;
|
|
550
649
|
}
|
|
551
650
|
advanceFixedStep(e) {}
|
|
552
|
-
#
|
|
553
|
-
for (let e of [...this.#
|
|
651
|
+
#_() {
|
|
652
|
+
for (let e of [...this.#d]) this.#f.get(e.id) === e && e.update(this.#r);
|
|
554
653
|
this.advanceFixedStep(this.#r);
|
|
555
654
|
try {
|
|
556
655
|
this.#t.drain();
|
|
557
656
|
} finally {
|
|
558
|
-
this.#
|
|
657
|
+
this.#u -= this.#r, Math.abs(this.#u) <= P * this.#r && (this.#u = 0), this.#c += 1;
|
|
559
658
|
}
|
|
560
659
|
}
|
|
561
|
-
#
|
|
660
|
+
#v(e) {
|
|
562
661
|
let t = [];
|
|
563
|
-
for (let n of this.#
|
|
662
|
+
for (let n of this.#d) n.state.parentID === e && t.push(n);
|
|
564
663
|
return t;
|
|
565
664
|
}
|
|
566
|
-
#
|
|
665
|
+
#y() {
|
|
567
666
|
if (this.#a !== void 0) return this.#a();
|
|
568
|
-
for (this.#
|
|
569
|
-
return `${this.#e}-${this.#
|
|
667
|
+
for (this.#l += 1; this.#b(`${this.#e}-${this.#l}`);) this.#l += 1;
|
|
668
|
+
return `${this.#e}-${this.#l}`;
|
|
570
669
|
}
|
|
571
|
-
#
|
|
572
|
-
return this.#
|
|
670
|
+
#b(e) {
|
|
671
|
+
return this.#f.has(e) || this.#p.has(e);
|
|
573
672
|
}
|
|
574
|
-
#
|
|
673
|
+
#x() {
|
|
575
674
|
return this;
|
|
576
675
|
}
|
|
577
|
-
#
|
|
676
|
+
#S(e) {
|
|
578
677
|
let t = this.#i[e];
|
|
579
678
|
if (t === void 0) throw Error(`The entity registry holds no class named "${e}".`);
|
|
580
679
|
return t;
|
|
581
680
|
}
|
|
582
|
-
static #
|
|
681
|
+
static #C(e) {
|
|
583
682
|
return e.state;
|
|
584
683
|
}
|
|
585
684
|
};
|
|
586
685
|
//#endregion
|
|
587
|
-
export { a as Behavior, p as EventBus, _ as GameEntity,
|
|
686
|
+
export { a as Behavior, p as EventBus, _ as GameEntity, j as GameRandom, F as GameSimulation, o as NamedBehavior, c as StateMachineBehavior, s as defineStates, d as dilationLimit, u as drainPassLimit, l as operationCallDepthLimit };
|
|
588
687
|
|
|
589
688
|
//# sourceMappingURL=sage-core.js.map
|
package/dist/sage-core.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sage-core.js","names":[],"sources":["../src/computedState.ts","../src/behavior.ts","../src/namedBehavior.ts","../src/stateMachineBehavior.ts","../src/constants.ts","../src/events.ts","../src/eventBus.ts","../src/gameEntity.ts","../src/simulationBus.ts","../src/simulation.ts"],"sourcesContent":["//----------------------------------------------------------------------------------------------------------------------\n// Computed State\n//\n// A field can carry more than one computation, so what each one replaced is remembered rather than assumed to have\n// been a plain value (design/entities.md#computed-state).\n//----------------------------------------------------------------------------------------------------------------------\n\n/** What a computed field is read and written through (design/entities.md#computed-state). */\nexport interface Computation<Value>\n{\n get : () => Value;\n set : (value : Value) => void;\n}\n\ninterface Replaced\n{\n field : string;\n beneath : PropertyDescriptor;\n}\n\nconst replaced = new WeakMap<object, Replaced[]>();\n\n//----------------------------------------------------------------------------------------------------------------------\n\nfunction replacedOn(state : object) : Replaced[]\n{\n let entries = replaced.get(state);\n\n if(entries === undefined)\n {\n entries = [];\n replaced.set(state, entries);\n }\n\n return entries;\n}\n\nexport function defineComputed<Value>(state : object, field : string, computation : Computation<Value>) : void\n{\n let beneath = Object.getOwnPropertyDescriptor(state, field);\n\n if(beneath === undefined)\n {\n beneath = { value: undefined, writable: true, enumerable: true, configurable: true };\n }\n\n replacedOn(state).push({ field, beneath });\n\n Object.defineProperty(state, field, {\n get: computation.get,\n set: computation.set,\n enumerable: true,\n configurable: true,\n });\n}\n\n// A computation may answer with a live view of what it computes from, so what a removal leaves behind is copied\n// out of the answer rather than kept (design/entities.md#computed-state). Records carry no prototype, the way\n// every other record in a state bag does.\nfunction plainValue(value : unknown) : unknown\n{\n if(Array.isArray(value))\n {\n const items : unknown[] = [];\n\n for(const item of value)\n {\n items.push(plainValue(item));\n }\n\n return items;\n }\n\n if(typeof value === 'object' && value !== null)\n {\n const copy = Object.create(null) as Record<string, unknown>;\n\n for(const [ key, field ] of Object.entries(value))\n {\n copy[key] = plainValue(field);\n }\n\n return copy;\n }\n\n return value;\n}\n\n/**\n * Take every computation off a state bag, newest first, so a computation over a computation leaves the one beneath\n * it answering and the field ends as the plain value the last one answered with.\n */\nexport function removeComputed(state : object) : void\n{\n const entries = replaced.get(state);\n\n if(entries === undefined)\n {\n return;\n }\n\n for(let index = entries.length - 1; index >= 0; index--)\n {\n const entry = entries[index];\n const answered = plainValue((state as Record<string, unknown>)[entry.field]);\n\n if('value' in entry.beneath)\n {\n Object.defineProperty(state, entry.field, {\n value: answered,\n writable: true,\n enumerable: true,\n configurable: true,\n });\n }\n else\n {\n Object.defineProperty(state, entry.field, entry.beneath);\n }\n }\n\n entries.length = 0;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Behavior\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { type Computation, defineComputed } from './computedState.ts';\n\nimport type { EventSubscription, GameEvent } from './events.ts';\nimport type { GameEntity } from './gameEntity.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport abstract class Behavior<\n State extends object,\n Requires extends object = object,\n Entity extends GameEntity = GameEntity,\n>\n{\n declare readonly state : State;\n declare readonly ops : Requires;\n declare readonly entity : Entity;\n\n abstract readonly defaults : State;\n\n onStateReady?() : void;\n onCreate?() : void;\n onUpdate?(dt : number) : void;\n onEvent?(event : GameEvent) : boolean | undefined;\n\n // Reading the field asks `get` and writing it tells `set`, from here until the entity is destroyed\n // (design/entities.md#computed-state).\n protected computed<Field extends keyof State & string>(\n field : Field,\n computation : Computation<State[Field]>\n ) : void\n {\n defineComputed(this.state, field, computation);\n }\n}\n\nexport interface BehaviorClass\n{\n new () : Behavior<object, object>;\n readonly ops ?: readonly PropertyKey[];\n readonly events ?: readonly EventSubscription[];\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// NamedBehavior\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { Behavior } from './behavior.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport interface NamedState\n{\n displayName : string;\n}\n\nexport class NamedBehavior extends Behavior<NamedState>\n{\n readonly defaults : NamedState = { displayName: '' };\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// StateMachineBehavior\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { Behavior } from './behavior.ts';\n\nimport type { GameEntity } from './gameEntity.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport interface StateDefinition<Machine, States extends string>\n{\n enter ?(this : Machine, from : States) : void;\n exit ?(this : Machine, to : States) : void;\n update ?(this : Machine, dt : number) : void;\n}\n\n// A subclass assigns `states` from this call, passing its own `this`, so the object literal gets a contextual type\n// and every enter, exit, and update inside it sees `this.state` and its own parameter typed. The two plainer forms\n// both fail: a method returning the table cannot name `this` in its own return type without TS7022, and a field\n// annotated by hand repeats the union of state names that `currentState` already declares.\nexport function defineStates<Machine, States extends string>(\n _machine : Machine,\n states : Record<States, StateDefinition<Machine, States>>\n) : Record<States, StateDefinition<Machine, States>>\n{\n return states;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport abstract class StateMachineBehavior<\n OwnState extends { currentState : string },\n Requires extends object = object,\n Entity extends GameEntity = GameEntity,\n> extends Behavior<OwnState, Requires, Entity>\n{\n static readonly ops = [ 'transitionTo' ] as const;\n\n abstract readonly states : Record<OwnState['currentState'], StateDefinition<this, OwnState['currentState']>>;\n\n onUpdate(dt : number) : void\n {\n // `this.state.currentState` widens to `string` as an index expression on its own -- naming its type here\n // is what keeps it narrowed to `OwnState['currentState']`, which `states` is actually keyed by.\n const current : OwnState['currentState'] = this.state.currentState;\n\n if(!Object.hasOwn(this.states, current))\n {\n throw new Error(\n `StateMachineBehavior on entity \"${ this.entity.id }\": currentState is \"${ current }\", `\n + 'which states does not declare.'\n );\n }\n\n this.states[current].update?.call(this, dt);\n }\n\n transitionTo(to : OwnState['currentState']) : void\n {\n if(!Object.hasOwn(this.states, to))\n {\n throw new Error(`StateMachineBehavior: no state named \"${ to }\" is declared.`);\n }\n\n if(to === this.state.currentState)\n {\n return;\n }\n\n const from : OwnState['currentState'] = this.state.currentState;\n\n this.states[from].exit?.call(this, to);\n this.state.currentState = to;\n this.states[to].enter?.call(this, from);\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Tuning Constants\n//----------------------------------------------------------------------------------------------------------------------\n\nexport const operationCallDepthLimit = 32;\n\nexport const drainPassLimit = 64;\n\nexport const dilationLimit = 5;\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Events\n//\n// `EventMap` is declared in the package root because a `declare module` augmentation only merges into the module that\n// declares the interface, and the root is the only module a consumer can address.\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { EventMap } from './index.ts';\n\nexport type { EventMap };\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport type GameEvent = {\n [Type in keyof EventMap & string] : {\n type : Type;\n senderID : string;\n targetID ?: string;\n payload : EventMap[Type];\n }\n}[keyof EventMap & string];\n\ntype EventPrefix<Type extends string> = Type extends `${ infer Prefix }:${ infer Rest }`\n ? Prefix | `${ Prefix }:${ EventPrefix<Rest> }`\n : never;\n\nexport type EventSubscription = (keyof EventMap & string) | `${ EventPrefix<keyof EventMap & string> }:*`;\n\n// The events a subscription hears: the one type it names, or every type under the prefix a pattern names.\nexport type EventsMatching<Subscription extends EventSubscription> = Subscription extends `${ infer Prefix }:*`\n ? Extract<GameEvent, { type : `${ Prefix }:${ string }` }>\n : Extract<GameEvent, { type : Subscription }>;\n\nexport type Unsubscribe = () => void;\n\nexport interface Subscriber\n{\n readonly id : string;\n receive(event : GameEvent) : boolean;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n// The empty map boundary\n//----------------------------------------------------------------------------------------------------------------------\n\n// Until a game augments `EventMap`, it holds nothing, `GameEvent` is `never` everywhere inside this package, and no\n// event value can be built against it. `RawGameEvent` is the shape an event has whatever the map holds, and it is the\n// only place core steps outside the typed vocabulary: it stays internal, and every public signature keeps `GameEvent`.\nexport interface RawGameEvent\n{\n type : string;\n senderID : string;\n targetID ?: string;\n payload : unknown;\n}\n\n// A pattern spans every remaining segment, so `door:*` matches `door:opened` and `door:lock:opened` alike.\nexport function compileEventPattern(subscription : string) : RegExp\n{\n const escaped = subscription\n .replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/\\\\\\*/g, '.*');\n\n return new RegExp(`^${ escaped }$`);\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Event Bus\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { drainPassLimit } from './constants.ts';\nimport {\n type EventSubscription, type EventsMatching, type GameEvent, type RawGameEvent, type Subscriber,\n type Unsubscribe, compileEventPattern,\n} from './events.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport type EventCallback<Subscription extends EventSubscription = EventSubscription>\n = (event : EventsMatching<Subscription>) => void;\n\n//----------------------------------------------------------------------------------------------------------------------\n\ninterface PatternSubscription\n{\n pattern : RegExp;\n subscriber : Subscriber;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport class EventBus\n{\n #queue : RawGameEvent[] = [];\n #patterns : PatternSubscription[] = [];\n\n readonly #subscribers = new Map<string, Subscriber>();\n readonly #exact = new Map<string, Subscriber[]>();\n\n #mintedSubscribers = 0;\n\n register(subscriber : Subscriber, subscriptions : readonly EventSubscription[]) : Unsubscribe\n {\n if(this.#subscribers.has(subscriber.id))\n {\n throw new Error(`A subscriber is already registered under the ID \"${ subscriber.id }\".`);\n }\n\n this.#subscribers.set(subscriber.id, subscriber);\n\n const exactNames : string[] = [];\n const patternEntries : PatternSubscription[] = [];\n\n for(const subscription of subscriptions)\n {\n const name : string = subscription;\n\n if(name.endsWith(':*'))\n {\n const entry = { pattern: compileEventPattern(name), subscriber };\n patternEntries.push(entry);\n this.#patterns.push(entry);\n }\n else if(!exactNames.includes(name))\n {\n exactNames.push(name);\n this.#addExact(name, subscriber);\n }\n }\n\n return this.#teardown(subscriber, exactNames, patternEntries);\n }\n\n // Anything that is not an entity subscribes this way. The bus mints the subscriber ID, and what the callback\n // answers with is ignored: consuming is an entity's own chain mechanic (design/events.md#registration).\n on<Subscription extends EventSubscription>(\n subscription : Subscription,\n callback : EventCallback<Subscription>\n ) : Unsubscribe\n {\n this.#mintedSubscribers += 1;\n\n const subscriber : Subscriber = {\n id: this.mintSubscriberID(this.#mintedSubscribers),\n receive(event : GameEvent) : boolean\n {\n callback(event as EventsMatching<Subscription>);\n return false;\n },\n };\n\n return this.register(subscriber, [ subscription ]);\n }\n\n /**\n * The ID the n-th subscriber `on` makes registers under (design/events.md#registration).\n */\n protected mintSubscriberID(ordinal : number) : string\n {\n return `subscriber-${ ordinal }`;\n }\n\n emit(event : GameEvent) : void\n {\n this.#queue.push(event as RawGameEvent);\n }\n\n drain() : void\n {\n const errors : unknown[] = [];\n let passes = 0;\n\n while(this.#queue.length > 0)\n {\n passes += 1;\n\n if(passes > drainPassLimit)\n {\n this.#queue = [];\n errors.push(new Error(`The drain exceeded ${ drainPassLimit } passes.`));\n break;\n }\n\n const pass = this.#queue;\n this.#queue = [];\n\n for(const event of pass)\n {\n this.#deliver(event, errors);\n }\n }\n\n if(errors.length > 0)\n {\n throw new AggregateError(errors, 'The drain raised.');\n }\n }\n\n // What a simulation's bus hands over when the simulation attaches to the engine's, in emission order.\n protected takeQueuedEvents() : GameEvent[]\n {\n const queued = this.#queue;\n this.#queue = [];\n\n return queued as GameEvent[];\n }\n\n // What a simulation's bus hands back to itself on detach: whichever of its own emissions the given bus has not\n // yet drained, in their original relative order, pulled out of that bus's queue rather than everyone's queued\n // events (design/events.md#one-bus-per-engine).\n protected static reclaimQueuedEvents(bus : EventBus, events : readonly RawGameEvent[]) : GameEvent[]\n {\n const reclaimed : RawGameEvent[] = [];\n const remaining : RawGameEvent[] = [];\n\n for(const event of bus.#queue)\n {\n if(events.includes(event))\n {\n reclaimed.push(event);\n }\n else\n {\n remaining.push(event);\n }\n }\n\n bus.#queue = remaining;\n\n return reclaimed as GameEvent[];\n }\n\n #addExact(name : string, subscriber : Subscriber) : void\n {\n const subscribers = this.#exact.get(name) ?? [];\n subscribers.push(subscriber);\n this.#exact.set(name, subscribers);\n }\n\n #removeExact(name : string, subscriber : Subscriber) : void\n {\n const subscribers = this.#exact.get(name);\n\n if(subscribers === undefined)\n {\n return;\n }\n\n const index = subscribers.indexOf(subscriber);\n\n if(index >= 0)\n {\n subscribers.splice(index, 1);\n }\n\n if(subscribers.length === 0)\n {\n this.#exact.delete(name);\n }\n }\n\n // A teardown is idempotent, and it evicts the subscriber under its ID only while that is still the registration it\n // was handed out for; a later registration that took the same ID survives a stale teardown.\n #teardown(\n subscriber : Subscriber,\n exactNames : readonly string[],\n patternEntries : readonly PatternSubscription[]\n ) : Unsubscribe\n {\n let active = true;\n\n return () : void =>\n {\n if(!active)\n {\n return;\n }\n\n active = false;\n\n if(this.#subscribers.get(subscriber.id) === subscriber)\n {\n this.#subscribers.delete(subscriber.id);\n }\n\n for(const name of exactNames)\n {\n this.#removeExact(name, subscriber);\n }\n\n const remaining : PatternSubscription[] = [];\n\n for(const entry of this.#patterns)\n {\n if(!patternEntries.includes(entry))\n {\n remaining.push(entry);\n }\n }\n\n this.#patterns = remaining;\n };\n }\n\n #deliver(event : RawGameEvent, errors : unknown[]) : void\n {\n if(event.targetID !== undefined)\n {\n const addressed = this.#subscribers.get(event.targetID);\n\n if(addressed !== undefined)\n {\n EventBus.#handOff(addressed, event, errors);\n }\n\n return;\n }\n\n const delivered = new Set<Subscriber>();\n\n for(const subscriber of [ ...this.#exact.get(event.type) ?? [] ])\n {\n EventBus.#handOffOnce(subscriber, event, delivered, errors);\n }\n\n for(const entry of [ ...this.#patterns ])\n {\n if(entry.pattern.test(event.type))\n {\n EventBus.#handOffOnce(entry.subscriber, event, delivered, errors);\n }\n }\n }\n\n static #handOffOnce(\n subscriber : Subscriber,\n event : RawGameEvent,\n delivered : Set<Subscriber>,\n errors : unknown[]\n ) : void\n {\n if(delivered.has(subscriber))\n {\n return;\n }\n\n delivered.add(subscriber);\n EventBus.#handOff(subscriber, event, errors);\n }\n\n static #handOff(subscriber : Subscriber, event : RawGameEvent, errors : unknown[]) : void\n {\n try\n {\n subscriber.receive(event as GameEvent);\n }\n catch(error)\n {\n errors.push(error);\n }\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// GameEntity\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { type Computation, defineComputed, removeComputed } from './computedState.ts';\nimport { operationCallDepthLimit } from './constants.ts';\nimport {\n type EventMap, type EventSubscription, type GameEvent, type RawGameEvent, type Subscriber, type Unsubscribe,\n compileEventPattern,\n} from './events.ts';\n\nimport type { Behavior, BehaviorClass } from './behavior.ts';\nimport type { GameSimulationContext } from './simulationContext.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n// State\n//----------------------------------------------------------------------------------------------------------------------\n\nexport type StateValue = null | boolean | number | string | StateValue[] | { [key : string] : StateValue };\n\nexport type StateBag = Record<string, StateValue>;\n\nexport type DeepReadonly<Value> = Value extends (infer Item)[]\n ? readonly DeepReadonly<Item>[]\n : Value extends object\n ? { readonly [Key in keyof Value] : DeepReadonly<Value[Key]> }\n : Value;\n\nexport interface GameEntityState\n{\n tags : string[];\n}\n\nfunction rejectStateValue(id : string, path : string) : never\n{\n throw new TypeError(\n `Entity \"${ id }\" state holds a value that is not finite JSON-compatible data at \"${ path }\".`\n );\n}\n\n// Copies and checks in one walk, so every path that hands state around -- creation, restoration, snapshot --\n// rejects the same values (design/entities.md#state). Records are copied with no prototype, so a state field\n// named `__proto__` stays an ordinary field of the copy.\nfunction copyStateValue(value : unknown, id : string, path : string, seen : Set<object>) : StateValue\n{\n if(value === null || typeof value === 'string' || typeof value === 'boolean')\n {\n return value;\n }\n\n if(typeof value === 'number')\n {\n if(!Number.isFinite(value))\n {\n rejectStateValue(id, path);\n }\n\n return value;\n }\n\n if(Array.isArray(value))\n {\n if(seen.has(value))\n {\n rejectStateValue(id, path);\n }\n\n seen.add(value);\n\n const items : StateValue[] = [];\n\n for(const [ index, item ] of value.entries())\n {\n items.push(copyStateValue(item, id, `${ path }[${ index }]`, seen));\n }\n\n seen.delete(value);\n\n return items;\n }\n\n if(typeof value === 'object')\n {\n const prototype = Object.getPrototypeOf(value) as unknown;\n\n if((prototype !== Object.prototype && prototype !== null) || seen.has(value))\n {\n rejectStateValue(id, path);\n }\n\n seen.add(value);\n\n const copy = Object.create(null) as StateBag;\n\n for(const [ key, field ] of Object.entries(value))\n {\n let fieldPath = `${ path }.${ key }`;\n\n if(path === '')\n {\n fieldPath = key;\n }\n\n copy[key] = copyStateValue(field, id, fieldPath, seen);\n }\n\n seen.delete(value);\n\n return copy;\n }\n\n return rejectStateValue(id, path);\n}\n\nexport function copyStateBag(bag : Readonly<StateBag>, id : string) : StateBag\n{\n return copyStateValue(bag, id, '', new Set<object>()) as StateBag;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n// Composition\n//----------------------------------------------------------------------------------------------------------------------\n\ntype UnionToIntersection<Union>\n = (Union extends unknown ? (value : Union) => void : never) extends (value : infer Intersection) => void\n ? Intersection\n : never;\n\ntype StateOf<Class> = Class extends new () => { state : infer State } ? State : never;\n\ntype OperationNames<Class> = Class extends { ops : readonly (infer Name)[] } ? Name & PropertyKey : never;\n\ntype OpsFrom<Class> = Class extends BehaviorClass\n ? Pick<InstanceType<Class>, OperationNames<Class> & keyof InstanceType<Class>>\n : never;\n\ntype RequiresOf<Class> = Class extends new () => { ops : infer Requires } ? Requires : never;\n\nexport type StateFor<Behaviors extends readonly BehaviorClass[]>\n = GameEntityState & UnionToIntersection<StateOf<Behaviors[number]>> & object;\n\nexport type OpsFor<Behaviors extends readonly BehaviorClass[]>\n = UnionToIntersection<OpsFrom<Behaviors[number]>> & object;\n\nexport type OpsOf<Class extends BehaviorClass> = OpsFrom<Class>;\n\nexport type StateSliceOf<Class extends BehaviorClass> = StateOf<Class>;\n\ntype EntityOf<Class> = Class extends new () => { entity : infer Entity } ? Entity : GameEntity;\n\ninterface Unsatisfied<Missing> { 'ERROR: a behavior requires operations the entity does not provide' : Missing }\ninterface DuplicateOperation<Name> { 'ERROR: two behaviors expose the same operation name' : Name }\ninterface IncompatibleState<Name> { 'ERROR: two behaviors declare one state field with incompatible types' : Name }\ninterface WrongEntity<Class> { 'ERROR: a behavior attaches to an entity of another class' : Class }\n\ntype ValidateEntity<Behaviors extends readonly BehaviorClass[], Entity> = {\n [Index in keyof Behaviors] : Entity extends EntityOf<Behaviors[Index]>\n ? Behaviors[Index]\n : WrongEntity<Behaviors[Index]>\n};\n\ntype ValidateRequires<Behaviors extends readonly BehaviorClass[]> = {\n [Index in keyof Behaviors] : OpsFor<Behaviors> extends RequiresOf<Behaviors[Index]>\n ? Behaviors[Index]\n : Unsatisfied<Exclude<keyof RequiresOf<Behaviors[Index]>, keyof OpsFor<Behaviors>>>\n};\n\ntype DuplicateNames<Behaviors extends readonly unknown[], Seen extends PropertyKey = never>\n = Behaviors extends readonly [ infer Head, ...infer Tail ]\n ? [Extract<OperationNames<Head>, Seen>] extends [never]\n ? DuplicateNames<Tail, Seen | OperationNames<Head>>\n : Extract<OperationNames<Head>, Seen> | DuplicateNames<Tail, Seen | OperationNames<Head>>\n : never;\n\ntype IncompatibleKeys<First, Second> = {\n [Key in keyof First & keyof Second] : [First[Key]] extends [Second[Key]]\n ? [Second[Key]] extends [First[Key]]\n ? never\n : Key\n : Key;\n}[keyof First & keyof Second];\n\ntype IncompatibleStateKeys<\n Behaviors extends readonly unknown[],\n SeenState extends object = object,\n> = Behaviors extends readonly [ infer Head, ...infer Tail ]\n ? IncompatibleKeys<SeenState, StateOf<Head>> | IncompatibleStateKeys<Tail, SeenState & StateOf<Head>>\n : never;\n\nexport type ValidTuple<Behaviors extends readonly BehaviorClass[], Entity = GameEntity>\n = [DuplicateNames<Behaviors>] extends [never]\n ? [IncompatibleStateKeys<Behaviors>] extends [never]\n ? Behaviors extends ValidateRequires<Behaviors>\n ? Behaviors extends ValidateEntity<Behaviors, Entity>\n ? Behaviors\n : ValidateEntity<Behaviors, Entity>\n : ValidateRequires<Behaviors>\n : readonly IncompatibleState<IncompatibleStateKeys<Behaviors>>[]\n : readonly DuplicateOperation<DuplicateNames<Behaviors>>[];\n\nexport interface GameEntityType\n{\n readonly behaviors : readonly BehaviorClass[];\n readonly prototype : GameEntity;\n}\n\n// An entity class as an entity registry holds it: the construction signature is a function-typed property rather than a\n// method, so a subclass narrowing the context it constructs with is checked wherever the class is handed around as\n// a type, the entity registry included (design/entities.md#the-entity-registry).\nexport interface GameEntityClass<Context extends GameSimulationContext = GameSimulationContext> extends GameEntityType\n{\n readonly create : (id : string, context : Context, state ?: object, name ?: string) => GameEntity;\n readonly restore : (id : string, context : Context, state : GameEntityState, name ?: string) => GameEntity;\n}\n\n// An entity's own state is deep read-only; the one writable view of a slice is the behavior's own `this.state`\n// (design/entities.md#cross-entity-access).\nexport type ComposedOf<Type extends GameEntityType> = Type['prototype'] & {\n readonly state : DeepReadonly<StateFor<Type['behaviors']>>;\n readonly ops : OpsFor<Type['behaviors']>;\n};\n\nexport type NarrowedTo<Class extends BehaviorClass> = GameEntity & {\n readonly state : DeepReadonly<StateSliceOf<Class>>;\n readonly ops : OpsOf<Class>;\n};\n\n//----------------------------------------------------------------------------------------------------------------------\n\ntype BehaviorInstance = Behavior<object, object>;\n\ntype OperationTable = Record<PropertyKey, unknown>;\n\ntype EntityConstructor = new (\n id : string,\n context : GameSimulationContext,\n providedState : object,\n runCreateHooks : boolean,\n name ?: string\n) => GameEntity;\n\ninterface BehaviorListener\n{\n instance : BehaviorInstance;\n exact : string[];\n patterns : RegExp[];\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n// The entity\n//----------------------------------------------------------------------------------------------------------------------\n\nexport abstract class GameEntity implements Subscriber\n{\n readonly id : string;\n readonly name : string;\n protected readonly context : GameSimulationContext;\n readonly state : object;\n readonly ops : object;\n\n static readonly behaviors : readonly BehaviorClass[] = [];\n\n readonly #instances : BehaviorInstance[] = [];\n readonly #listeners : BehaviorListener[] = [];\n readonly #unsubscribe : Unsubscribe;\n\n #operationDepth = 0;\n #creationEvents : RawGameEvent[] | undefined;\n #destroyed = false;\n\n static create<Type extends GameEntityType>(\n this : Type & { behaviors : ValidTuple<Type['behaviors'], Type['prototype']> },\n id : string,\n context : GameSimulationContext,\n state ?: Partial<StateFor<Type['behaviors']>>,\n name ?: string\n ) : ComposedOf<Type>\n {\n return GameEntity.#construct(this, id, context, state ?? {}, true, name);\n }\n\n static restore<Type extends GameEntityType>(\n this : Type & { behaviors : ValidTuple<Type['behaviors'], Type['prototype']> },\n id : string,\n context : GameSimulationContext,\n state : StateFor<Type['behaviors']>,\n name ?: string\n ) : ComposedOf<Type>\n {\n return GameEntity.#construct(this, id, context, state, false, name);\n }\n\n static #construct<Type extends GameEntityType>(\n type : Type,\n id : string,\n context : GameSimulationContext,\n state : object,\n runCreateHooks : boolean,\n name ?: string\n ) : ComposedOf<Type>\n {\n const constructable = type as unknown as EntityConstructor;\n\n return new constructable(id, context, state, runCreateHooks, name) as ComposedOf<Type>;\n }\n\n protected constructor(\n id : string,\n context : GameSimulationContext,\n providedState : object,\n runCreateHooks : boolean,\n name ?: string\n )\n {\n this.id = id;\n this.name = name ?? (new.target as typeof GameEntity).name;\n this.context = context;\n\n const behaviorClasses = (new.target as typeof GameEntity).behaviors;\n\n for(const behaviorClass of behaviorClasses)\n {\n this.#instances.push(new behaviorClass());\n }\n\n this.state = GameEntity.#composeState(this.#instances, providedState, id);\n\n const ops = Object.create(null) as OperationTable;\n this.ops = ops;\n\n const subscriptions : EventSubscription[] = [];\n\n for(const [ index, behaviorClass ] of behaviorClasses.entries())\n {\n const instance = this.#instances[index];\n\n if(instance !== undefined)\n {\n Object.assign(instance, { state: this.state, ops, entity: this });\n this.#bindOperations(instance, behaviorClass, ops);\n this.#bindEvents(instance, behaviorClass, subscriptions);\n }\n }\n\n // A field the entity class computes itself goes on before any behavior's onStateReady, so a behavior\n // computing the same field lands over it rather than under it (design/entities.md#computed-state).\n // This runs inside the base constructor: a subclass's own fields and private methods do not exist yet, so\n // an override reaches this.state and nothing else of its own.\n this.declareComputedState(providedState);\n\n // Every behavior reaches its own state here, before a creation hook or a step could read it, and a restored\n // entity runs these exactly as a created one does (design/behaviors.md#hooks).\n for(const instance of this.#instances)\n {\n instance.onStateReady?.();\n }\n\n this.#unsubscribe = context.bus.register(this, subscriptions);\n\n if(runCreateHooks)\n {\n this.#runCreateHooks();\n }\n }\n\n hasBehavior<Class extends BehaviorClass>(behaviorClass : Class) : this is NarrowedTo<Class>\n {\n for(const instance of this.#instances)\n {\n if(instance instanceof behaviorClass)\n {\n return true;\n }\n }\n\n return false;\n }\n\n getEntity(id : string) : GameEntity | undefined\n {\n return this.context.getEntity(id);\n }\n\n emit<Type extends keyof EventMap & string>(type : Type, payload : EventMap[Type], targetID ?: string) : void\n {\n const event : RawGameEvent = { type, senderID: this.id, targetID, payload };\n\n if(this.#creationEvents !== undefined)\n {\n this.#creationEvents.push(event);\n return;\n }\n\n this.context.bus.emit(event as GameEvent);\n }\n\n update(dt : number) : void\n {\n for(const instance of this.#instances)\n {\n instance.onUpdate?.(dt);\n\n // A behavior that despawns this entity ends the loop here, so a despawn partway through leaves the\n // remaining behaviors' onUpdate unrun (design/entities.md#destruction).\n if(this.#destroyed)\n {\n return;\n }\n }\n }\n\n receive(event : GameEvent) : boolean\n {\n const raw = event as RawGameEvent;\n\n for(const listener of this.#listeners)\n {\n if(GameEntity.#matches(listener, raw.type) && listener.instance.onEvent?.(event) === true)\n {\n return true;\n }\n }\n\n return false;\n }\n\n destroy() : void\n {\n this.#destroyed = true;\n this.removeComputedState();\n this.#unsubscribe();\n }\n\n // Overridden by an entity class with a field of its own to compute. The state its caller supplied is what\n // tells a field the spawn asked for from one composition filled in (design/entities.md#computed-state).\n protected declareComputedState(suppliedState : object) : void\n {\n void suppliedState;\n }\n\n // Reading the field asks `get` and writing it tells `set`, from here until the entity is destroyed. The\n // declaring class names its own state, since an entity's `state` is typed by its behaviors rather than by\n // the fields the class declares beside them (design/entities.md#computed-state).\n protected computed<OwnState extends object, Field extends keyof OwnState & string>(\n field : Field,\n computation : Computation<OwnState[Field]>\n ) : void\n {\n defineComputed(this.state, field, computation);\n }\n\n // Called before a subclass runs any teardown of its own, so every getter is asked while whatever answers it\n // is still there (design/entities.md#computed-state).\n protected removeComputedState() : void\n {\n removeComputed(this.state);\n }\n\n // A copy, so an override reads the attachment order without reaching the list the entity dispatches from.\n protected attachedBehaviors() : readonly BehaviorInstance[]\n {\n return [ ...this.#instances ];\n }\n\n #bindOperations(instance : BehaviorInstance, behaviorClass : BehaviorClass, ops : OperationTable) : void\n {\n for(const name of behaviorClass.ops ?? [])\n {\n if(Object.hasOwn(ops, name))\n {\n throw new Error(`Two behaviors on entity \"${ this.id }\" expose the operation \"${ String(name) }\".`);\n }\n\n const method = (instance as unknown as OperationTable)[name];\n\n if(typeof method !== 'function')\n {\n throw new Error(\n `Behavior \"${ behaviorClass.name }\" declares the operation \"${ String(name) }\" `\n + 'with no method behind it.'\n );\n }\n\n ops[name] = this.#guardedOperation(name, method as (...args : unknown[]) => unknown, instance);\n }\n }\n\n #bindEvents(\n instance : BehaviorInstance,\n behaviorClass : BehaviorClass,\n subscriptions : EventSubscription[]\n ) : void\n {\n const exact : string[] = [];\n const patterns : RegExp[] = [];\n\n for(const subscription of behaviorClass.events ?? [])\n {\n const name : string = subscription;\n\n if(name.endsWith(':*'))\n {\n patterns.push(compileEventPattern(name));\n }\n else\n {\n exact.push(name);\n }\n\n if(!subscriptions.includes(subscription))\n {\n subscriptions.push(subscription);\n }\n }\n\n if(exact.length > 0 || patterns.length > 0)\n {\n this.#listeners.push({ instance, exact, patterns });\n }\n }\n\n #guardedOperation(\n name : PropertyKey,\n method : (...args : unknown[]) => unknown,\n instance : BehaviorInstance\n ) : (...args : unknown[]) => unknown\n {\n return (...args : unknown[]) : unknown =>\n {\n this.#operationDepth += 1;\n\n try\n {\n if(this.#operationDepth > operationCallDepthLimit)\n {\n throw new RangeError(\n `Operation \"${ String(name) }\" on entity \"${ this.id }\" ran deeper than the operation `\n + `call depth limit of ${ operationCallDepthLimit }.`\n );\n }\n\n return method.apply(instance, args);\n }\n finally\n {\n this.#operationDepth -= 1;\n }\n };\n }\n\n // The hooks emit into a held list rather than onto the queue, so a hook that throws leaves the bus as it found it.\n #runCreateHooks() : void\n {\n this.#creationEvents = [];\n\n try\n {\n for(const instance of this.#instances)\n {\n instance.onCreate?.();\n }\n }\n catch(error)\n {\n this.#creationEvents = undefined;\n this.#unsubscribe();\n throw error;\n }\n\n const held = this.#creationEvents;\n this.#creationEvents = undefined;\n\n for(const event of held)\n {\n this.context.bus.emit(event as GameEvent);\n }\n }\n\n // No prototype, so nothing here inherits a toString: String(state) and an untemplated interpolation of it throw\n // instead of reading \"[object Object]\".\n static #composeState(instances : readonly BehaviorInstance[], providedState : object, id : string) : StateBag\n {\n const state = Object.create(null) as StateBag;\n state['tags'] = [];\n\n for(const instance of instances)\n {\n Object.assign(state, copyStateBag(instance.defaults as StateBag, id));\n }\n\n Object.assign(state, copyStateBag(providedState as StateBag, id));\n GameEntity.#checkTags(state, id);\n\n return state;\n }\n\n static #checkTags(state : StateBag, id : string) : void\n {\n const tags = state['tags'];\n\n if(!Array.isArray(tags))\n {\n throw new TypeError(`Entity \"${ id }\" tags must be a list of strings.`);\n }\n\n const seen : string[] = [];\n\n for(const tag of tags)\n {\n if(typeof tag !== 'string')\n {\n throw new TypeError(`Entity \"${ id }\" tags must be a list of strings.`);\n }\n\n if(seen.includes(tag))\n {\n throw new Error(`Entity \"${ id }\" carries the tag \"${ tag }\" more than once.`);\n }\n\n seen.push(tag);\n }\n }\n\n static #matches(listener : BehaviorListener, type : string) : boolean\n {\n if(listener.exact.includes(type))\n {\n return true;\n }\n\n for(const pattern of listener.patterns)\n {\n if(pattern.test(type))\n {\n return true;\n }\n }\n\n return false;\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// SimulationBus\n//\n// The bus a simulation's entities see. Detached, it is an ordinary bus of its own; attached, everything it is asked\n// goes to the engine's bus instead, and what it held moves across (design/events.md#one-bus-per-engine).\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { EventBus } from './eventBus.ts';\n\nimport type { EventSubscription, GameEvent, RawGameEvent, Subscriber, Unsubscribe } from './events.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\ninterface Registration\n{\n subscriber : Subscriber;\n subscriptions : readonly EventSubscription[];\n teardown : Unsubscribe;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport class SimulationBus extends EventBus\n{\n readonly #name : string;\n readonly #engineBus : EventBus | undefined;\n readonly #registrations : Registration[] = [];\n\n #attachedAs : Unsubscribe | undefined;\n #forwarded : RawGameEvent[] = [];\n\n constructor(name : string, engineBus : EventBus | undefined)\n {\n super();\n\n this.#name = name;\n this.#engineBus = engineBus;\n }\n\n get attached() : boolean\n {\n return this.#attachedAs !== undefined;\n }\n\n // A subscriber made here reaches the engine bus beside the engine's own, so its ID carries the simulation's\n // name the way the simulation's entities' IDs do (design/events.md#registration).\n protected override mintSubscriberID(ordinal : number) : string\n {\n return `${ this.#name }-subscriber-${ ordinal }`;\n }\n\n override register(subscriber : Subscriber, subscriptions : readonly EventSubscription[]) : Unsubscribe\n {\n const attachedTo = this.#attachedTo();\n\n let teardown : Unsubscribe;\n\n if(attachedTo !== undefined)\n {\n teardown = attachedTo.register(subscriber, subscriptions);\n }\n else\n {\n teardown = super.register(subscriber, subscriptions);\n }\n\n const registration : Registration = { subscriber, subscriptions, teardown };\n this.#registrations.push(registration);\n\n return () : void =>\n {\n const index = this.#registrations.indexOf(registration);\n\n if(index < 0)\n {\n return;\n }\n\n this.#registrations.splice(index, 1);\n registration.teardown();\n };\n }\n\n override emit(event : GameEvent) : void\n {\n const attachedTo = this.#attachedTo();\n\n if(attachedTo !== undefined)\n {\n // Kept so detach can reclaim it if the engine bus has not drained it yet\n // (design/events.md#one-bus-per-engine).\n this.#forwarded.push(event as RawGameEvent);\n attachedTo.emit(event);\n }\n else\n {\n super.emit(event);\n }\n }\n\n override drain() : void\n {\n const attachedTo = this.#attachedTo();\n\n if(attachedTo !== undefined)\n {\n try\n {\n attachedTo.drain();\n }\n finally\n {\n this.#forwarded = [];\n }\n }\n else\n {\n super.drain();\n }\n }\n\n attach() : void\n {\n if(this.#engineBus === undefined)\n {\n throw new Error(`The simulation \"${ this.#name }\" was built with no engine bus to attach to.`);\n }\n\n if(this.#attachedAs !== undefined)\n {\n return;\n }\n\n // The simulation registers under its own name first, which is what refuses a second attached simulation\n // of the same name (design/simulation.md#the-simulations-name).\n this.#attachedAs = this.#engineBus.register({ id: this.#name, receive: () => false }, []);\n\n for(const registration of this.#registrations)\n {\n registration.teardown();\n registration.teardown = this.#engineBus.register(registration.subscriber, registration.subscriptions);\n }\n\n for(const event of this.takeQueuedEvents())\n {\n this.#engineBus.emit(event);\n }\n }\n\n detach() : void\n {\n if(this.#attachedAs === undefined)\n {\n return;\n }\n\n for(const registration of this.#registrations)\n {\n registration.teardown();\n registration.teardown = super.register(registration.subscriber, registration.subscriptions);\n }\n\n // Detaching reverses attaching in both directions: subscribers move back here, and so does whichever of\n // this simulation's own emissions the engine bus has not yet drained (design/events.md#one-bus-per-engine).\n const engineBus = this.#engineBus;\n\n if(engineBus !== undefined)\n {\n for(const event of EventBus.reclaimQueuedEvents(engineBus, this.#forwarded))\n {\n super.emit(event);\n }\n }\n\n this.#forwarded = [];\n\n this.#attachedAs();\n this.#attachedAs = undefined;\n }\n\n #attachedTo() : EventBus | undefined\n {\n if(this.#attachedAs === undefined)\n {\n return undefined;\n }\n\n return this.#engineBus;\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Simulation Manager\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { dilationLimit } from './constants.ts';\nimport { SimulationBus } from './simulationBus.ts';\nimport {\n type ComposedOf, type GameEntity, type GameEntityClass, type GameEntityState, type GameEntityType, type StateBag,\n type StateFor, type ValidTuple, copyStateBag,\n} from './gameEntity.ts';\n\nimport type { BehaviorClass } from './behavior.ts';\n\nimport type { EventBus } from './eventBus.ts';\nimport type { GameSimulationContext } from './simulationContext.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport interface GameEntitySnapshot\n{\n id : string;\n name : string;\n state : StateBag;\n}\n\nexport interface GameSimulationSnapshot\n{\n id : string;\n tick : number;\n idCounter : number;\n entities : GameEntitySnapshot[];\n}\n\nexport type IDAllocator = () => string;\n\n// The classes a simulation spawns from, each constructable with the context the simulation provides.\nexport type GameEntityRegistry<Context extends GameSimulationContext = GameSimulationContext>\n = Readonly<Record<string, GameEntityClass<Context>>>;\n\n// Every class in the registry carries the composition check its own `create` carries, so a composition the\n// compiler refuses is refused at the registry (design/entities.md#the-entity-registry).\nexport type ValidEntityRegistry<Registry extends GameEntityRegistry<never>> = {\n readonly [Name in keyof Registry] : Registry[Name] & {\n readonly behaviors : ValidTuple<Registry[Name]['behaviors'], Registry[Name]['prototype']>;\n };\n};\n\nexport interface GameSimulationOptions<Registry extends GameEntityRegistry<never> = GameEntityRegistry>\n{\n id : string;\n fixedStepDuration : number;\n entityClasses : Registry;\n allocator ?: IDAllocator;\n bus ?: EventBus;\n}\n\n// A table typed as any class at all types nothing about a spawn, so its state is the plain bag.\ntype SpawnState<Type extends GameEntityType> = readonly BehaviorClass[] extends Type['behaviors']\n ? StateBag\n : Partial<StateFor<Type['behaviors']>>;\n\n// The persistent simulation, built after 0.10.0, takes this name (design/simulation.md#the-simulations-name).\nconst reservedName = 'global';\n\n// How close to a whole interval the accumulator has to be to owe a fixed step, as a fraction of the fixed-step\n// duration. A unit in the last place is proportional to the value it sits in, so an absolute constant is a wide\n// tolerance for a duration in seconds and no tolerance at all for the same rate written in milliseconds\n// (design/simulation.md#the-fixed-step).\nconst fixedStepTolerance = 128 * Number.EPSILON;\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// The entity registry is typed by the context this simulation provides to `create`: a subclass providing a narrower one\n// names it as `Context` and implements it, which is what lets a class needing that context into its table\n// (design/entities.md#the-entity-registry).\nexport class GameSimulationManager<\n Registry extends GameEntityRegistry<Context> = GameEntityRegistry,\n Context extends GameSimulationContext = GameSimulationContext,\n> implements GameSimulationContext\n{\n readonly #id : string;\n readonly #bus : SimulationBus;\n readonly #engineBus : EventBus | undefined;\n readonly #fixedStepDuration : number;\n readonly #entityClasses : Registry;\n readonly #allocator : IDAllocator | undefined;\n\n #tick = 0;\n #idCounter = 0;\n #accumulator = 0;\n #entries : GameEntity[] = [];\n #byID = new Map<string, GameEntity>();\n #retiredIDs = new Set<string>();\n\n constructor(options : GameSimulationOptions<Registry> & { entityClasses : ValidEntityRegistry<Registry> })\n {\n this.#id = options.id;\n this.#fixedStepDuration = options.fixedStepDuration;\n this.#entityClasses = options.entityClasses;\n this.#allocator = options.allocator;\n this.#engineBus = options.bus;\n this.#bus = new SimulationBus(options.id, options.bus);\n\n if(this.#id === reservedName)\n {\n throw new Error(`The simulation name \"${ reservedName }\" is reserved for the persistent simulation.`);\n }\n\n if(!Number.isFinite(this.#fixedStepDuration) || this.#fixedStepDuration <= 0)\n {\n throw new RangeError('A fixed step duration must be a finite number greater than zero.');\n }\n }\n\n get id() : string\n {\n return this.#id;\n }\n\n get bus() : EventBus\n {\n return this.#bus;\n }\n\n get attached() : boolean\n {\n return this.#bus.attached;\n }\n\n get tick() : number\n {\n return this.#tick;\n }\n\n get entities() : readonly GameEntity[]\n {\n return [ ...this.#entries ];\n }\n\n attach() : void\n {\n this.#bus.attach();\n }\n\n detach() : void\n {\n this.#bus.detach();\n }\n\n spawn<Name extends keyof Registry & string>(\n name : Name,\n state ?: SpawnState<Registry[Name]>,\n id ?: string\n ) : ComposedOf<Registry[Name]>\n {\n return this.spawnByName(name, state as StateBag | undefined, id) as ComposedOf<Registry[Name]>;\n }\n\n /**\n * The same spawn for a name known only at run time, a marker's or a console's: the registry checks the name here\n * rather than the compiler at the call (design/simulation.md#spawn-and-despawn).\n */\n spawnByName(name : string, state ?: StateBag, id ?: string) : GameEntity\n {\n const entityClass = this.#entityClass(name);\n\n let entityID = id;\n\n if(entityID === undefined)\n {\n entityID = this.#mintID();\n }\n\n if(this.#byID.has(entityID))\n {\n throw new Error(`The simulation already holds an entity under the ID \"${ entityID }\".`);\n }\n\n // An ID is unique for the simulation's whole life, so one already despawned never spawns again\n // (design/entities.md#ids).\n if(this.#retiredIDs.has(entityID))\n {\n throw new Error(`The simulation already retired the ID \"${ entityID }\"; an ID is never reused.`);\n }\n\n const entity = entityClass.create(entityID, this.#context(), state ?? {}, name);\n\n this.#entries.push(entity);\n this.#byID.set(entityID, entity);\n\n return entity;\n }\n\n despawn(id : string) : boolean\n {\n return this.#despawn(id, new Set<string>());\n }\n\n #despawn(id : string, inFlight : Set<string>) : boolean\n {\n const entity = this.#byID.get(id);\n\n if(entity === undefined)\n {\n return false;\n }\n\n inFlight.add(id);\n\n // Children first, each found by the parent ID in its state (design/simulation.md#spawn-and-despawn). A\n // parent ID is a state field, and writing state notifies nobody, so the children are found by looking rather\n // than kept in an index that a write would leave stale. A parent ID cycle would otherwise recurse without\n // end; an ID already in flight is skipped instead, so a cycle despawns each entity once.\n for(const child of this.#childrenOf(id))\n {\n if(!inFlight.has(child.id))\n {\n this.#despawn(child.id, inFlight);\n }\n }\n\n entity.destroy();\n\n this.#entries.splice(this.#entries.indexOf(entity), 1);\n this.#byID.delete(id);\n this.#retiredIDs.add(id);\n\n return true;\n }\n\n getEntity(id : string) : GameEntity | undefined\n {\n return this.#byID.get(id);\n }\n\n getEntitiesByTag(tag : string) : readonly GameEntity[]\n {\n const found : GameEntity[] = [];\n\n for(const entity of this.#entries)\n {\n if(GameSimulationManager.#entityState(entity).tags.includes(tag))\n {\n found.push(entity);\n }\n }\n\n return found;\n }\n\n getEntitiesOfClass<Type extends GameEntityType>(entityClass : Type) : readonly ComposedOf<Type>[]\n {\n const found : ComposedOf<Type>[] = [];\n\n for(const entity of this.#entries)\n {\n if(entity instanceof (entityClass as unknown as abstract new () => GameEntity))\n {\n found.push(entity as ComposedOf<Type>);\n }\n }\n\n return found;\n }\n\n runStepsForElapsedTime(elapsed : number) : void\n {\n if(!Number.isFinite(elapsed) || elapsed < 0)\n {\n throw new RangeError('Elapsed time must be a finite number of zero or more.');\n }\n\n this.#accumulator += elapsed;\n\n let steps = 0;\n\n while(this.#owesAFixedStep() && steps < dilationLimit)\n {\n this.#runFixedStep();\n steps += 1;\n }\n\n // Chronic overrun degrades into simulation time running slower than wall clock: the whole intervals still owed\n // are dropped and only the fraction is carried.\n if(steps === dilationLimit && this.#owesAFixedStep())\n {\n this.#accumulator %= this.#fixedStepDuration;\n }\n }\n\n snapshot() : GameSimulationSnapshot\n {\n const entities : GameEntitySnapshot[] = [];\n\n for(const entity of this.#entries)\n {\n entities.push({\n id: entity.id,\n name: entity.name,\n state: copyStateBag(entity.state as StateBag, entity.id),\n });\n }\n\n return { id: this.#id, tick: this.#tick, idCounter: this.#idCounter, entities };\n }\n\n buildFromSnapshot(snapshot : GameSimulationSnapshot) : GameSimulationManager<Registry, Context>\n {\n const restored = this.buildReplacementSimulation({\n id: snapshot.id,\n fixedStepDuration: this.#fixedStepDuration,\n entityClasses: this.#entityClasses,\n allocator: this.#allocator,\n bus: this.#engineBus,\n });\n\n restored.#load(snapshot);\n\n return restored;\n }\n\n protected buildReplacementSimulation(\n options : GameSimulationOptions<Registry>\n ) : GameSimulationManager<Registry, Context>\n {\n return new GameSimulationManager<Registry, Context>(options as GameSimulationOptions<Registry> & {\n entityClasses : ValidEntityRegistry<Registry>;\n });\n }\n\n #load(snapshot : GameSimulationSnapshot) : void\n {\n const entries : GameEntity[] = [];\n\n try\n {\n for(const row of snapshot.entities)\n {\n const entityClass = this.#entityClass(row.name);\n const entity = entityClass.restore(\n row.id,\n this.#context(),\n row.state as GameEntityState & StateBag,\n row.name\n );\n entries.push(entity);\n }\n }\n catch(error)\n {\n for(const entity of entries)\n {\n entity.destroy();\n }\n\n throw error;\n }\n\n this.#entries = entries;\n this.#byID = new Map();\n\n for(const entity of entries)\n {\n this.#byID.set(entity.id, entity);\n }\n\n this.#tick = snapshot.tick;\n this.#idCounter = snapshot.idCounter;\n }\n\n // The accumulator holds a sum of binary floating point values, which lands a few units in the last place short of\n // the interval the caller meant; without the tolerance three calls of a third of a step run no step at all.\n #owesAFixedStep() : boolean\n {\n const tolerance = fixedStepTolerance * this.#fixedStepDuration;\n\n return this.#accumulator + tolerance >= this.#fixedStepDuration;\n }\n\n /**\n * Advances whatever else this simulation carries, once per fixed step, after every entity has updated\n * and before the bus drains.\n *\n * `sage-core` carries nothing and does nothing here. A simulation holding a physics engine steps it\n * from this, so what a behavior told a body during an update takes effect in the step it was told and\n * what physics reports drains in the step it happened (design/simulation.md#the-fixed-step). It is\n * synchronous because a fixed step is, and a throw from it leaves the simulation dead exactly as a\n * throw from an entity's update does (design/simulation.md#failure).\n *\n * @param duration - The fixed step's own duration, never a frame's elapsed time.\n */\n protected advanceFixedStep(duration : number) : void\n {\n void duration;\n }\n\n #runFixedStep() : void\n {\n // An entity already despawned by an earlier one's update this step never starts its own; an entity that\n // despawns itself partway through its own update stops there too, in GameEntity.update\n // (design/entities.md#destruction).\n for(const entity of [ ...this.#entries ])\n {\n if(this.#byID.get(entity.id) === entity)\n {\n entity.update(this.#fixedStepDuration);\n }\n }\n\n this.advanceFixedStep(this.#fixedStepDuration);\n\n // The step ran, so the tick and the accumulator settle whether or not the drain raised.\n try\n {\n this.#bus.drain();\n }\n finally\n {\n this.#accumulator -= this.#fixedStepDuration;\n\n if(Math.abs(this.#accumulator) <= fixedStepTolerance * this.#fixedStepDuration)\n {\n this.#accumulator = 0;\n }\n\n this.#tick += 1;\n }\n }\n\n #childrenOf(parentID : string) : GameEntity[]\n {\n const children : GameEntity[] = [];\n\n for(const entity of this.#entries)\n {\n if((entity.state as StateBag)['parentID'] === parentID)\n {\n children.push(entity);\n }\n }\n\n return children;\n }\n\n #mintID() : string\n {\n if(this.#allocator !== undefined)\n {\n return this.#allocator();\n }\n\n this.#idCounter += 1;\n\n while(this.#idIsSpent(`${ this.#id }-${ this.#idCounter }`))\n {\n this.#idCounter += 1;\n }\n\n return `${ this.#id }-${ this.#idCounter }`;\n }\n\n // A despawned ID is spent and never minted again (design/entities.md#ids).\n #idIsSpent(candidate : string) : boolean\n {\n return this.#byID.has(candidate) || this.#retiredIDs.has(candidate);\n }\n\n // This simulation is the context its entities are created with. The cast is unchecked: TypeScript accepts a\n // subclass naming a narrower Context whether or not the subclass provides it; only a subclass that also writes\n // its own `implements Context` is checked against the shape it named.\n #context() : Context\n {\n return this as unknown as Context;\n }\n\n #entityClass(name : string) : GameEntityClass<Context>\n {\n const entityClass = this.#entityClasses[name];\n\n if(entityClass === undefined)\n {\n throw new Error(`The entity registry holds no class named \"${ name }\".`);\n }\n\n return entityClass;\n }\n\n // Every entity carries the tags field whatever its behaviors declare, so the query reads it straight.\n static #entityState(entity : GameEntity) : GameEntityState\n {\n return entity.state as GameEntityState;\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sage-core.js","names":[],"sources":["../src/computedState.ts","../src/behavior.ts","../src/namedBehavior.ts","../src/stateMachineBehavior.ts","../src/constants.ts","../src/events.ts","../src/eventBus.ts","../src/gameEntity.ts","../src/gameRandom.ts","../src/simulationBus.ts","../src/simulation.ts"],"sourcesContent":["//----------------------------------------------------------------------------------------------------------------------\n// Computed State\n//\n// A field can carry more than one computation, so what each one replaced is remembered rather than assumed to have\n// been a plain value (design/entities.md#computed-state).\n//----------------------------------------------------------------------------------------------------------------------\n\n/** What a computed field is read and written through (design/entities.md#computed-state). */\nexport interface Computation<Value>\n{\n get : () => Value;\n set : (value : Value) => void;\n}\n\ninterface Replaced\n{\n field : string;\n beneath : PropertyDescriptor;\n}\n\nconst replaced = new WeakMap<object, Replaced[]>();\n\n//----------------------------------------------------------------------------------------------------------------------\n\nfunction replacedOn(state : object) : Replaced[]\n{\n let entries = replaced.get(state);\n\n if(entries === undefined)\n {\n entries = [];\n replaced.set(state, entries);\n }\n\n return entries;\n}\n\nexport function defineComputed<Value>(state : object, field : string, computation : Computation<Value>) : void\n{\n let beneath = Object.getOwnPropertyDescriptor(state, field);\n\n if(beneath === undefined)\n {\n beneath = { value: undefined, writable: true, enumerable: true, configurable: true };\n }\n\n replacedOn(state).push({ field, beneath });\n\n Object.defineProperty(state, field, {\n get: computation.get,\n set: computation.set,\n enumerable: true,\n configurable: true,\n });\n}\n\n// A computation may answer with a live view of what it computes from, so what a removal leaves behind is copied\n// out of the answer rather than kept (design/entities.md#computed-state). Records carry no prototype, the way\n// every other record in a state bag does.\nfunction plainValue(value : unknown) : unknown\n{\n if(Array.isArray(value))\n {\n const items : unknown[] = [];\n\n for(const item of value)\n {\n items.push(plainValue(item));\n }\n\n return items;\n }\n\n if(typeof value === 'object' && value !== null)\n {\n const copy = Object.create(null) as Record<string, unknown>;\n\n for(const [ key, field ] of Object.entries(value))\n {\n copy[key] = plainValue(field);\n }\n\n return copy;\n }\n\n return value;\n}\n\n/**\n * Take every computation off a state bag, newest first, so a computation over a computation leaves the one beneath\n * it answering and the field ends as the plain value the last one answered with.\n */\nexport function removeComputed(state : object) : void\n{\n const entries = replaced.get(state);\n\n if(entries === undefined)\n {\n return;\n }\n\n for(let index = entries.length - 1; index >= 0; index--)\n {\n const entry = entries[index];\n const answered = plainValue((state as Record<string, unknown>)[entry.field]);\n\n if('value' in entry.beneath)\n {\n Object.defineProperty(state, entry.field, {\n value: answered,\n writable: true,\n enumerable: true,\n configurable: true,\n });\n }\n else\n {\n Object.defineProperty(state, entry.field, entry.beneath);\n }\n }\n\n entries.length = 0;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Behavior\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { type Computation, defineComputed } from './computedState.ts';\n\nimport type { EventSubscription, GameEvent } from './events.ts';\nimport type { GameEntity } from './gameEntity.ts';\nimport type { GameSimulationView } from './simulation.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// The simulation an entity of this kind is created with: a `NodeEntity`'s behavior reads a `NodeSimulationContext`\n// through `this.simulation`, and every other behavior reads the plain view\n// (design/behaviors.md#inside-a-behavior).\nexport type SimulationFor<Entity extends GameEntity> = Entity extends GameEntity<infer Context>\n ? Context\n : GameSimulationView;\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport abstract class Behavior<\n State extends object,\n Requires extends object = object,\n Entity extends GameEntity = GameEntity,\n>\n{\n declare readonly state : State;\n declare readonly ops : Requires;\n declare readonly entity : Entity;\n\n // The simulation the entity was created with. `GameEntity` puts it on beside `entity`, and it is protected\n // because the raw bus behind it emits as anyone (design/entities.md#cross-entity-access).\n protected declare readonly simulation : SimulationFor<Entity>;\n\n abstract readonly defaults : State;\n\n onStateReady?() : void;\n onCreate?() : void;\n onUpdate?(dt : number) : void;\n onEvent?(event : GameEvent) : boolean | undefined;\n\n // Reading the field asks `get` and writing it tells `set`, from here until the entity is destroyed\n // (design/entities.md#computed-state).\n protected computed<Field extends keyof State & string>(\n field : Field,\n computation : Computation<State[Field]>\n ) : void\n {\n defineComputed(this.state, field, computation);\n }\n}\n\nexport interface BehaviorClass\n{\n new () : Behavior<object, object>;\n readonly ops ?: readonly PropertyKey[];\n readonly events ?: readonly EventSubscription[];\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// NamedBehavior\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { Behavior } from './behavior.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport interface NamedState\n{\n displayName : string;\n}\n\nexport class NamedBehavior extends Behavior<NamedState>\n{\n readonly defaults : NamedState = { displayName: '' };\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// StateMachineBehavior\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { Behavior } from './behavior.ts';\n\nimport type { GameEntity } from './gameEntity.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport interface StateDefinition<Machine, States extends string>\n{\n enter ?(this : Machine, from : States) : void;\n exit ?(this : Machine, to : States) : void;\n update ?(this : Machine, dt : number) : void;\n}\n\n// A subclass assigns `states` from this call, passing its own `this`, so the object literal gets a contextual type\n// and every enter, exit, and update inside it sees `this.state` and its own parameter typed. The two plainer forms\n// both fail: a method returning the table cannot name `this` in its own return type without TS7022, and a field\n// annotated by hand repeats the union of state names that `currentState` already declares.\nexport function defineStates<Machine, States extends string>(\n _machine : Machine,\n states : Record<States, StateDefinition<Machine, States>>\n) : Record<States, StateDefinition<Machine, States>>\n{\n return states;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport abstract class StateMachineBehavior<\n OwnState extends { currentState : string },\n Requires extends object = object,\n Entity extends GameEntity = GameEntity,\n> extends Behavior<OwnState, Requires, Entity>\n{\n static readonly ops = [ 'transitionTo' ] as const;\n\n abstract readonly states : Record<OwnState['currentState'], StateDefinition<this, OwnState['currentState']>>;\n\n onUpdate(dt : number) : void\n {\n // `this.state.currentState` widens to `string` as an index expression on its own -- naming its type here\n // is what keeps it narrowed to `OwnState['currentState']`, which `states` is actually keyed by.\n const current : OwnState['currentState'] = this.state.currentState;\n\n if(!Object.hasOwn(this.states, current))\n {\n throw new Error(\n `StateMachineBehavior on entity \"${ this.entity.id }\": currentState is \"${ current }\", `\n + 'which states does not declare.'\n );\n }\n\n this.states[current].update?.call(this, dt);\n }\n\n transitionTo(to : OwnState['currentState']) : void\n {\n if(!Object.hasOwn(this.states, to))\n {\n throw new Error(`StateMachineBehavior: no state named \"${ to }\" is declared.`);\n }\n\n if(to === this.state.currentState)\n {\n return;\n }\n\n const from : OwnState['currentState'] = this.state.currentState;\n\n this.states[from].exit?.call(this, to);\n this.state.currentState = to;\n this.states[to].enter?.call(this, from);\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Tuning Constants\n//----------------------------------------------------------------------------------------------------------------------\n\nexport const operationCallDepthLimit = 32;\n\nexport const drainPassLimit = 64;\n\nexport const dilationLimit = 5;\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Events\n//\n// `EventMap` is declared in the package root because a `declare module` augmentation only merges into the module that\n// declares the interface, and the root is the only module a consumer can address.\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { EventMap } from './index.ts';\n\nexport type { EventMap };\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport type GameEvent = {\n [Type in keyof EventMap & string] : {\n type : Type;\n senderID : string;\n targetID ?: string;\n payload : EventMap[Type];\n }\n}[keyof EventMap & string];\n\ntype EventPrefix<Type extends string> = Type extends `${ infer Prefix }:${ infer Rest }`\n ? Prefix | `${ Prefix }:${ EventPrefix<Rest> }`\n : never;\n\nexport type EventSubscription = (keyof EventMap & string) | `${ EventPrefix<keyof EventMap & string> }:*`;\n\n// The events a subscription hears: the one type it names, or every type under the prefix a pattern names.\nexport type EventsMatching<Subscription extends EventSubscription> = Subscription extends `${ infer Prefix }:*`\n ? Extract<GameEvent, { type : `${ Prefix }:${ string }` }>\n : Extract<GameEvent, { type : Subscription }>;\n\nexport type Unsubscribe = () => void;\n\nexport interface Subscriber\n{\n readonly id : string;\n receive(event : GameEvent) : boolean;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n// The empty map boundary\n//----------------------------------------------------------------------------------------------------------------------\n\n// Until a game augments `EventMap`, it holds nothing, `GameEvent` is `never` everywhere inside this package, and no\n// event value can be built against it. `RawGameEvent` is the shape an event has whatever the map holds, and it is the\n// only place core steps outside the typed vocabulary: it stays internal, and every public signature keeps `GameEvent`.\nexport interface RawGameEvent\n{\n type : string;\n senderID : string;\n targetID ?: string;\n payload : unknown;\n}\n\n// A pattern spans every remaining segment, so `door:*` matches `door:opened` and `door:lock:opened` alike.\nexport function compileEventPattern(subscription : string) : RegExp\n{\n const escaped = subscription\n .replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/\\\\\\*/g, '.*');\n\n return new RegExp(`^${ escaped }$`);\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Event Bus\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { drainPassLimit } from './constants.ts';\nimport {\n type EventSubscription, type EventsMatching, type GameEvent, type RawGameEvent, type Subscriber,\n type Unsubscribe, compileEventPattern,\n} from './events.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport type EventCallback<Subscription extends EventSubscription = EventSubscription>\n = (event : EventsMatching<Subscription>) => void;\n\n//----------------------------------------------------------------------------------------------------------------------\n\ninterface PatternSubscription\n{\n pattern : RegExp;\n subscriber : Subscriber;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport class EventBus\n{\n #queue : RawGameEvent[] = [];\n #patterns : PatternSubscription[] = [];\n\n readonly #subscribers = new Map<string, Subscriber>();\n readonly #exact = new Map<string, Subscriber[]>();\n\n #mintedSubscribers = 0;\n\n register(subscriber : Subscriber, subscriptions : readonly EventSubscription[]) : Unsubscribe\n {\n if(this.#subscribers.has(subscriber.id))\n {\n throw new Error(`A subscriber is already registered under the ID \"${ subscriber.id }\".`);\n }\n\n this.#subscribers.set(subscriber.id, subscriber);\n\n const exactNames : string[] = [];\n const patternEntries : PatternSubscription[] = [];\n\n for(const subscription of subscriptions)\n {\n const name : string = subscription;\n\n if(name.endsWith(':*'))\n {\n const entry = { pattern: compileEventPattern(name), subscriber };\n patternEntries.push(entry);\n this.#patterns.push(entry);\n }\n else if(!exactNames.includes(name))\n {\n exactNames.push(name);\n this.#addExact(name, subscriber);\n }\n }\n\n return this.#teardown(subscriber, exactNames, patternEntries);\n }\n\n // Anything that is not an entity subscribes this way. The bus mints the subscriber ID, and what the callback\n // answers with is ignored: consuming is an entity's own chain mechanic (design/events.md#registration).\n on<Subscription extends EventSubscription>(\n subscription : Subscription,\n callback : EventCallback<Subscription>\n ) : Unsubscribe\n {\n this.#mintedSubscribers += 1;\n\n const subscriber : Subscriber = {\n id: this.mintSubscriberID(this.#mintedSubscribers),\n receive(event : GameEvent) : boolean\n {\n callback(event as EventsMatching<Subscription>);\n return false;\n },\n };\n\n return this.register(subscriber, [ subscription ]);\n }\n\n /**\n * The ID the n-th subscriber `on` makes registers under (design/events.md#registration).\n */\n protected mintSubscriberID(ordinal : number) : string\n {\n return `subscriber-${ ordinal }`;\n }\n\n emit(event : GameEvent) : void\n {\n this.#queue.push(event as RawGameEvent);\n }\n\n drain() : void\n {\n const errors : unknown[] = [];\n let passes = 0;\n\n while(this.#queue.length > 0)\n {\n passes += 1;\n\n if(passes > drainPassLimit)\n {\n this.#queue = [];\n errors.push(new Error(`The drain exceeded ${ drainPassLimit } passes.`));\n break;\n }\n\n const pass = this.#queue;\n this.#queue = [];\n\n for(const event of pass)\n {\n this.#deliver(event, errors);\n }\n }\n\n if(errors.length > 0)\n {\n throw new AggregateError(errors, 'The drain raised.');\n }\n }\n\n // What a simulation's bus hands over when the simulation attaches to the engine's, in emission order.\n protected takeQueuedEvents() : GameEvent[]\n {\n const queued = this.#queue;\n this.#queue = [];\n\n return queued as GameEvent[];\n }\n\n // What a simulation's bus hands back to itself on detach: whichever of its own emissions the given bus has not\n // yet drained, in their original relative order, pulled out of that bus's queue rather than everyone's queued\n // events (design/events.md#one-bus-per-engine).\n protected static reclaimQueuedEvents(bus : EventBus, events : readonly RawGameEvent[]) : GameEvent[]\n {\n const reclaimed : RawGameEvent[] = [];\n const remaining : RawGameEvent[] = [];\n\n for(const event of bus.#queue)\n {\n if(events.includes(event))\n {\n reclaimed.push(event);\n }\n else\n {\n remaining.push(event);\n }\n }\n\n bus.#queue = remaining;\n\n return reclaimed as GameEvent[];\n }\n\n #addExact(name : string, subscriber : Subscriber) : void\n {\n const subscribers = this.#exact.get(name) ?? [];\n subscribers.push(subscriber);\n this.#exact.set(name, subscribers);\n }\n\n #removeExact(name : string, subscriber : Subscriber) : void\n {\n const subscribers = this.#exact.get(name);\n\n if(subscribers === undefined)\n {\n return;\n }\n\n const index = subscribers.indexOf(subscriber);\n\n if(index >= 0)\n {\n subscribers.splice(index, 1);\n }\n\n if(subscribers.length === 0)\n {\n this.#exact.delete(name);\n }\n }\n\n // A teardown is idempotent, and it evicts the subscriber under its ID only while that is still the registration it\n // was handed out for; a later registration that took the same ID survives a stale teardown.\n #teardown(\n subscriber : Subscriber,\n exactNames : readonly string[],\n patternEntries : readonly PatternSubscription[]\n ) : Unsubscribe\n {\n let active = true;\n\n return () : void =>\n {\n if(!active)\n {\n return;\n }\n\n active = false;\n\n if(this.#subscribers.get(subscriber.id) === subscriber)\n {\n this.#subscribers.delete(subscriber.id);\n }\n\n for(const name of exactNames)\n {\n this.#removeExact(name, subscriber);\n }\n\n const remaining : PatternSubscription[] = [];\n\n for(const entry of this.#patterns)\n {\n if(!patternEntries.includes(entry))\n {\n remaining.push(entry);\n }\n }\n\n this.#patterns = remaining;\n };\n }\n\n #deliver(event : RawGameEvent, errors : unknown[]) : void\n {\n if(event.targetID !== undefined)\n {\n const addressed = this.#subscribers.get(event.targetID);\n\n if(addressed !== undefined)\n {\n EventBus.#handOff(addressed, event, errors);\n }\n\n return;\n }\n\n const delivered = new Set<Subscriber>();\n\n for(const subscriber of [ ...this.#exact.get(event.type) ?? [] ])\n {\n EventBus.#handOffOnce(subscriber, event, delivered, errors);\n }\n\n for(const entry of [ ...this.#patterns ])\n {\n if(entry.pattern.test(event.type))\n {\n EventBus.#handOffOnce(entry.subscriber, event, delivered, errors);\n }\n }\n }\n\n static #handOffOnce(\n subscriber : Subscriber,\n event : RawGameEvent,\n delivered : Set<Subscriber>,\n errors : unknown[]\n ) : void\n {\n if(delivered.has(subscriber))\n {\n return;\n }\n\n delivered.add(subscriber);\n EventBus.#handOff(subscriber, event, errors);\n }\n\n static #handOff(subscriber : Subscriber, event : RawGameEvent, errors : unknown[]) : void\n {\n try\n {\n subscriber.receive(event as GameEvent);\n }\n catch(error)\n {\n errors.push(error);\n }\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// GameEntity\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { type Computation, defineComputed, removeComputed } from './computedState.ts';\nimport { operationCallDepthLimit } from './constants.ts';\nimport {\n type EventMap, type EventSubscription, type GameEvent, type RawGameEvent, type Subscriber, type Unsubscribe,\n compileEventPattern,\n} from './events.ts';\n\nimport type { Behavior, BehaviorClass } from './behavior.ts';\nimport type { GameSimulationView } from './simulation.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n// State\n//----------------------------------------------------------------------------------------------------------------------\n\nexport type StateValue = null | boolean | number | string | StateValue[] | { [key : string] : StateValue };\n\nexport type StateBag = Record<string, StateValue>;\n\nexport type DeepReadonly<Value> = Value extends (infer Item)[]\n ? readonly DeepReadonly<Item>[]\n : Value extends object\n ? { readonly [Key in keyof Value] : DeepReadonly<Value[Key]> }\n : Value;\n\nexport interface GameEntityState\n{\n tags : string[];\n}\n\nfunction rejectStateValue(id : string, path : string) : never\n{\n throw new TypeError(\n `Entity \"${ id }\" state holds a value that is not finite JSON-compatible data at \"${ path }\".`\n );\n}\n\n// Copies and checks in one walk, so every path that hands state around -- creation, restoration, snapshot --\n// rejects the same values (design/entities.md#state). Records are copied with no prototype, so a state field\n// named `__proto__` stays an ordinary field of the copy.\nfunction copyStateValue(value : unknown, id : string, path : string, seen : Set<object>) : StateValue\n{\n if(value === null || typeof value === 'string' || typeof value === 'boolean')\n {\n return value;\n }\n\n if(typeof value === 'number')\n {\n if(!Number.isFinite(value))\n {\n rejectStateValue(id, path);\n }\n\n return value;\n }\n\n if(Array.isArray(value))\n {\n if(seen.has(value))\n {\n rejectStateValue(id, path);\n }\n\n seen.add(value);\n\n const items : StateValue[] = [];\n\n for(const [ index, item ] of value.entries())\n {\n items.push(copyStateValue(item, id, `${ path }[${ index }]`, seen));\n }\n\n seen.delete(value);\n\n return items;\n }\n\n if(typeof value === 'object')\n {\n const prototype = Object.getPrototypeOf(value) as unknown;\n\n if((prototype !== Object.prototype && prototype !== null) || seen.has(value))\n {\n rejectStateValue(id, path);\n }\n\n seen.add(value);\n\n const copy = Object.create(null) as StateBag;\n\n for(const [ key, field ] of Object.entries(value))\n {\n let fieldPath = `${ path }.${ key }`;\n\n if(path === '')\n {\n fieldPath = key;\n }\n\n copy[key] = copyStateValue(field, id, fieldPath, seen);\n }\n\n seen.delete(value);\n\n return copy;\n }\n\n return rejectStateValue(id, path);\n}\n\nexport function copyStateBag(bag : Readonly<StateBag>, id : string) : StateBag\n{\n return copyStateValue(bag, id, '', new Set<object>()) as StateBag;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n// Composition\n//----------------------------------------------------------------------------------------------------------------------\n\ntype UnionToIntersection<Union>\n = (Union extends unknown ? (value : Union) => void : never) extends (value : infer Intersection) => void\n ? Intersection\n : never;\n\ntype StateOf<Class> = Class extends new () => { state : infer State } ? State : never;\n\ntype OperationNames<Class> = Class extends { ops : readonly (infer Name)[] } ? Name & PropertyKey : never;\n\ntype OpsFrom<Class> = Class extends BehaviorClass\n ? Pick<InstanceType<Class>, OperationNames<Class> & keyof InstanceType<Class>>\n : never;\n\ntype RequiresOf<Class> = Class extends new () => { ops : infer Requires } ? Requires : never;\n\nexport type StateFor<Behaviors extends readonly BehaviorClass[]>\n = GameEntityState & UnionToIntersection<StateOf<Behaviors[number]>> & object;\n\nexport type OpsFor<Behaviors extends readonly BehaviorClass[]>\n = UnionToIntersection<OpsFrom<Behaviors[number]>> & object;\n\nexport type OpsOf<Class extends BehaviorClass> = OpsFrom<Class>;\n\nexport type StateSliceOf<Class extends BehaviorClass> = StateOf<Class>;\n\ntype EntityOf<Class> = Class extends new () => { entity : infer Entity } ? Entity : GameEntity;\n\ninterface Unsatisfied<Missing> { 'ERROR: a behavior requires operations the entity does not provide' : Missing }\ninterface DuplicateOperation<Name> { 'ERROR: two behaviors expose the same operation name' : Name }\ninterface IncompatibleState<Name> { 'ERROR: two behaviors declare one state field with incompatible types' : Name }\ninterface WrongEntity<Class> { 'ERROR: a behavior attaches to an entity of another class' : Class }\n\ntype ValidateEntity<Behaviors extends readonly BehaviorClass[], Entity> = {\n [Index in keyof Behaviors] : Entity extends EntityOf<Behaviors[Index]>\n ? Behaviors[Index]\n : WrongEntity<Behaviors[Index]>\n};\n\ntype ValidateRequires<Behaviors extends readonly BehaviorClass[]> = {\n [Index in keyof Behaviors] : OpsFor<Behaviors> extends RequiresOf<Behaviors[Index]>\n ? Behaviors[Index]\n : Unsatisfied<Exclude<keyof RequiresOf<Behaviors[Index]>, keyof OpsFor<Behaviors>>>\n};\n\ntype DuplicateNames<Behaviors extends readonly unknown[], Seen extends PropertyKey = never>\n = Behaviors extends readonly [ infer Head, ...infer Tail ]\n ? [Extract<OperationNames<Head>, Seen>] extends [never]\n ? DuplicateNames<Tail, Seen | OperationNames<Head>>\n : Extract<OperationNames<Head>, Seen> | DuplicateNames<Tail, Seen | OperationNames<Head>>\n : never;\n\ntype IncompatibleKeys<First, Second> = {\n [Key in keyof First & keyof Second] : [First[Key]] extends [Second[Key]]\n ? [Second[Key]] extends [First[Key]]\n ? never\n : Key\n : Key;\n}[keyof First & keyof Second];\n\ntype IncompatibleStateKeys<\n Behaviors extends readonly unknown[],\n SeenState extends object = object,\n> = Behaviors extends readonly [ infer Head, ...infer Tail ]\n ? IncompatibleKeys<SeenState, StateOf<Head>> | IncompatibleStateKeys<Tail, SeenState & StateOf<Head>>\n : never;\n\nexport type ValidTuple<Behaviors extends readonly BehaviorClass[], Entity = GameEntity>\n = [DuplicateNames<Behaviors>] extends [never]\n ? [IncompatibleStateKeys<Behaviors>] extends [never]\n ? Behaviors extends ValidateRequires<Behaviors>\n ? Behaviors extends ValidateEntity<Behaviors, Entity>\n ? Behaviors\n : ValidateEntity<Behaviors, Entity>\n : ValidateRequires<Behaviors>\n : readonly IncompatibleState<IncompatibleStateKeys<Behaviors>>[]\n : readonly DuplicateOperation<DuplicateNames<Behaviors>>[];\n\nexport interface GameEntityType\n{\n readonly behaviors : readonly BehaviorClass[];\n readonly prototype : GameEntity;\n}\n\n// An entity class as an entity registry holds it: the construction signature is a function-typed property rather than a\n// method, so a subclass narrowing the context it constructs with is checked wherever the class is handed around as\n// a type, the entity registry included (design/entities.md#the-entity-registry).\nexport interface GameEntityClass<Context extends GameSimulationView = GameSimulationView> extends GameEntityType\n{\n readonly create : (id : string, context : Context, state ?: object, name ?: string) => GameEntity;\n readonly restore : (id : string, context : Context, state : GameEntityState, name ?: string) => GameEntity;\n}\n\n// An entity's own state is deep read-only; the one writable view of a slice is the behavior's own `this.state`\n// (design/entities.md#cross-entity-access).\nexport type ComposedOf<Type extends GameEntityType> = Type['prototype'] & {\n readonly state : DeepReadonly<StateFor<Type['behaviors']>>;\n readonly ops : OpsFor<Type['behaviors']>;\n};\n\nexport type NarrowedTo<Class extends BehaviorClass> = GameEntity & {\n readonly state : DeepReadonly<StateSliceOf<Class>>;\n readonly ops : OpsOf<Class>;\n};\n\n//----------------------------------------------------------------------------------------------------------------------\n\ntype BehaviorInstance = Behavior<object, object>;\n\ntype OperationTable = Record<PropertyKey, unknown>;\n\ntype EntityConstructor = new (\n id : string,\n context : GameSimulationView,\n providedState : object,\n runCreateHooks : boolean,\n name ?: string\n) => GameEntity;\n\ninterface BehaviorListener\n{\n instance : BehaviorInstance;\n exact : string[];\n patterns : RegExp[];\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n// The entity\n//----------------------------------------------------------------------------------------------------------------------\n\n// The context an entity is created with is its type parameter, so a behavior declaring that entity reads the same\n// kind through `this.simulation` (design/behaviors.md#inside-a-behavior).\nexport abstract class GameEntity<Context extends GameSimulationView = GameSimulationView> implements Subscriber\n{\n readonly id : string;\n readonly name : string;\n protected readonly context : Context;\n readonly state : object;\n readonly ops : object;\n\n static readonly behaviors : readonly BehaviorClass[] = [];\n\n readonly #instances : BehaviorInstance[] = [];\n readonly #listeners : BehaviorListener[] = [];\n readonly #unsubscribe : Unsubscribe;\n\n #operationDepth = 0;\n #creationEvents : RawGameEvent[] | undefined;\n #destroyed = false;\n\n static create<Type extends GameEntityType>(\n this : Type & { behaviors : ValidTuple<Type['behaviors'], Type['prototype']> },\n id : string,\n context : GameSimulationView,\n state ?: Partial<StateFor<Type['behaviors']>>,\n name ?: string\n ) : ComposedOf<Type>\n {\n return GameEntity.#construct(this, id, context, state ?? {}, true, name);\n }\n\n static restore<Type extends GameEntityType>(\n this : Type & { behaviors : ValidTuple<Type['behaviors'], Type['prototype']> },\n id : string,\n context : GameSimulationView,\n state : StateFor<Type['behaviors']>,\n name ?: string\n ) : ComposedOf<Type>\n {\n return GameEntity.#construct(this, id, context, state, false, name);\n }\n\n static #construct<Type extends GameEntityType>(\n type : Type,\n id : string,\n context : GameSimulationView,\n state : object,\n runCreateHooks : boolean,\n name ?: string\n ) : ComposedOf<Type>\n {\n const constructable = type as unknown as EntityConstructor;\n\n return new constructable(id, context, state, runCreateHooks, name) as ComposedOf<Type>;\n }\n\n protected constructor(\n id : string,\n context : Context,\n providedState : object,\n runCreateHooks : boolean,\n name ?: string\n )\n {\n this.id = id;\n this.name = name ?? (new.target as typeof GameEntity).name;\n this.context = context;\n\n const behaviorClasses = (new.target as typeof GameEntity).behaviors;\n\n for(const behaviorClass of behaviorClasses)\n {\n this.#instances.push(new behaviorClass());\n }\n\n this.state = GameEntity.#composeState(this.#instances, providedState, id);\n\n const ops = Object.create(null) as OperationTable;\n this.ops = ops;\n\n const subscriptions : EventSubscription[] = [];\n\n for(const [ index, behaviorClass ] of behaviorClasses.entries())\n {\n const instance = this.#instances[index];\n\n if(instance !== undefined)\n {\n Object.assign(instance, { state: this.state, ops, entity: this, simulation: context });\n this.#bindOperations(instance, behaviorClass, ops);\n this.#bindEvents(instance, behaviorClass, subscriptions);\n }\n }\n\n // A field the entity class computes itself goes on before any behavior's onStateReady, so a behavior\n // computing the same field lands over it rather than under it (design/entities.md#computed-state).\n // This runs inside the base constructor: a subclass's own fields and private methods do not exist yet, so\n // an override reaches this.state and nothing else of its own.\n this.declareComputedState(providedState);\n\n // Every behavior reaches its own state here, before a creation hook or a step could read it, and a restored\n // entity runs these exactly as a created one does (design/behaviors.md#hooks).\n for(const instance of this.#instances)\n {\n instance.onStateReady?.();\n }\n\n this.#unsubscribe = context.bus.register(this, subscriptions);\n\n if(runCreateHooks)\n {\n this.#runCreateHooks();\n }\n }\n\n hasBehavior<Class extends BehaviorClass>(behaviorClass : Class) : this is NarrowedTo<Class>\n {\n for(const instance of this.#instances)\n {\n if(instance instanceof behaviorClass)\n {\n return true;\n }\n }\n\n return false;\n }\n\n getEntity(id : string) : GameEntity | undefined\n {\n return this.context.getEntity(id);\n }\n\n emit<Type extends keyof EventMap & string>(type : Type, payload : EventMap[Type], targetID ?: string) : void\n {\n const event : RawGameEvent = { type, senderID: this.id, targetID, payload };\n\n if(this.#creationEvents !== undefined)\n {\n this.#creationEvents.push(event);\n return;\n }\n\n this.context.bus.emit(event as GameEvent);\n }\n\n update(dt : number) : void\n {\n for(const instance of this.#instances)\n {\n instance.onUpdate?.(dt);\n\n // A behavior that despawns this entity ends the loop here, so a despawn partway through leaves the\n // remaining behaviors' onUpdate unrun (design/entities.md#destruction).\n if(this.#destroyed)\n {\n return;\n }\n }\n }\n\n receive(event : GameEvent) : boolean\n {\n const raw = event as RawGameEvent;\n\n for(const listener of this.#listeners)\n {\n if(GameEntity.#matches(listener, raw.type) && listener.instance.onEvent?.(event) === true)\n {\n return true;\n }\n }\n\n return false;\n }\n\n destroy() : void\n {\n this.#destroyed = true;\n this.removeComputedState();\n this.#unsubscribe();\n }\n\n // Overridden by an entity class with a field of its own to compute. The state its caller supplied is what\n // tells a field the spawn asked for from one composition filled in (design/entities.md#computed-state).\n protected declareComputedState(suppliedState : object) : void\n {\n void suppliedState;\n }\n\n // Reading the field asks `get` and writing it tells `set`, from here until the entity is destroyed. The\n // declaring class names its own state, since an entity's `state` is typed by its behaviors rather than by\n // the fields the class declares beside them (design/entities.md#computed-state).\n protected computed<OwnState extends object, Field extends keyof OwnState & string>(\n field : Field,\n computation : Computation<OwnState[Field]>\n ) : void\n {\n defineComputed(this.state, field, computation);\n }\n\n // Called before a subclass runs any teardown of its own, so every getter is asked while whatever answers it\n // is still there (design/entities.md#computed-state).\n protected removeComputedState() : void\n {\n removeComputed(this.state);\n }\n\n // A copy, so an override reads the attachment order without reaching the list the entity dispatches from.\n protected attachedBehaviors() : readonly BehaviorInstance[]\n {\n return [ ...this.#instances ];\n }\n\n #bindOperations(instance : BehaviorInstance, behaviorClass : BehaviorClass, ops : OperationTable) : void\n {\n for(const name of behaviorClass.ops ?? [])\n {\n if(Object.hasOwn(ops, name))\n {\n throw new Error(`Two behaviors on entity \"${ this.id }\" expose the operation \"${ String(name) }\".`);\n }\n\n const method = (instance as unknown as OperationTable)[name];\n\n if(typeof method !== 'function')\n {\n throw new Error(\n `Behavior \"${ behaviorClass.name }\" declares the operation \"${ String(name) }\" `\n + 'with no method behind it.'\n );\n }\n\n ops[name] = this.#guardedOperation(name, method as (...args : unknown[]) => unknown, instance);\n }\n }\n\n #bindEvents(\n instance : BehaviorInstance,\n behaviorClass : BehaviorClass,\n subscriptions : EventSubscription[]\n ) : void\n {\n const exact : string[] = [];\n const patterns : RegExp[] = [];\n\n for(const subscription of behaviorClass.events ?? [])\n {\n const name : string = subscription;\n\n if(name.endsWith(':*'))\n {\n patterns.push(compileEventPattern(name));\n }\n else\n {\n exact.push(name);\n }\n\n if(!subscriptions.includes(subscription))\n {\n subscriptions.push(subscription);\n }\n }\n\n if(exact.length > 0 || patterns.length > 0)\n {\n this.#listeners.push({ instance, exact, patterns });\n }\n }\n\n #guardedOperation(\n name : PropertyKey,\n method : (...args : unknown[]) => unknown,\n instance : BehaviorInstance\n ) : (...args : unknown[]) => unknown\n {\n return (...args : unknown[]) : unknown =>\n {\n this.#operationDepth += 1;\n\n try\n {\n if(this.#operationDepth > operationCallDepthLimit)\n {\n throw new RangeError(\n `Operation \"${ String(name) }\" on entity \"${ this.id }\" ran deeper than the operation `\n + `call depth limit of ${ operationCallDepthLimit }.`\n );\n }\n\n return method.apply(instance, args);\n }\n finally\n {\n this.#operationDepth -= 1;\n }\n };\n }\n\n // The hooks emit into a held list rather than onto the queue, so a hook that throws leaves the bus as it found it.\n #runCreateHooks() : void\n {\n this.#creationEvents = [];\n\n try\n {\n for(const instance of this.#instances)\n {\n instance.onCreate?.();\n }\n }\n catch(error)\n {\n this.#creationEvents = undefined;\n this.#unsubscribe();\n throw error;\n }\n\n const held = this.#creationEvents;\n this.#creationEvents = undefined;\n\n for(const event of held)\n {\n this.context.bus.emit(event as GameEvent);\n }\n }\n\n // No prototype, so nothing here inherits a toString: String(state) and an untemplated interpolation of it throw\n // instead of reading \"[object Object]\".\n static #composeState(instances : readonly BehaviorInstance[], providedState : object, id : string) : StateBag\n {\n const state = Object.create(null) as StateBag;\n state['tags'] = [];\n\n for(const instance of instances)\n {\n Object.assign(state, copyStateBag(instance.defaults as StateBag, id));\n }\n\n Object.assign(state, copyStateBag(providedState as StateBag, id));\n GameEntity.#checkTags(state, id);\n\n return state;\n }\n\n static #checkTags(state : StateBag, id : string) : void\n {\n const tags = state['tags'];\n\n if(!Array.isArray(tags))\n {\n throw new TypeError(`Entity \"${ id }\" tags must be a list of strings.`);\n }\n\n const seen : string[] = [];\n\n for(const tag of tags)\n {\n if(typeof tag !== 'string')\n {\n throw new TypeError(`Entity \"${ id }\" tags must be a list of strings.`);\n }\n\n if(seen.includes(tag))\n {\n throw new Error(`Entity \"${ id }\" carries the tag \"${ tag }\" more than once.`);\n }\n\n seen.push(tag);\n }\n }\n\n static #matches(listener : BehaviorListener, type : string) : boolean\n {\n if(listener.exact.includes(type))\n {\n return true;\n }\n\n for(const pattern of listener.patterns)\n {\n if(pattern.test(type))\n {\n return true;\n }\n }\n\n return false;\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// GameRandom\n//----------------------------------------------------------------------------------------------------------------------\n\nexport type RandomSeed = number | string;\n\nexport interface RandomState\n{\n readonly a : number;\n readonly b : number;\n readonly c : number;\n readonly d : number;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// FNV-1a's offset basis and prime, which hash a string seed to one word (design/simulation.md#the-generator).\nconst hashOffsetBasis = 2166136261;\nconst hashPrime = 16777619;\n\n// splitmix32's three constants, which spread one seed word across the four state words.\nconst splitmixStep = 0x9e3779b9;\nconst splitmixFirstPrime = 0x21f0aaad;\nconst splitmixSecondPrime = 0x735a2d97;\n\nconst wordLimit = 2 ** 32;\n\nfunction toWord(value : number) : number\n{\n return value >>> 0;\n}\n\nfunction multiply(left : number, right : number) : number\n{\n return toWord(Math.imul(left, right));\n}\n\nfunction rotateLeft(word : number, bits : number) : number\n{\n return toWord((word << bits) | (word >>> (32 - bits)));\n}\n\nfunction hashString(text : string) : number\n{\n let hash = toWord(hashOffsetBasis);\n\n for(let index = 0; index < text.length; index++)\n {\n hash = toWord(hash ^ text.charCodeAt(index));\n hash = multiply(hash, hashPrime);\n }\n\n return hash;\n}\n\nfunction seedWord(seed : RandomSeed) : number\n{\n if(typeof seed === 'string')\n {\n return hashString(seed);\n }\n\n return toWord(seed);\n}\n\nfunction mix(word : number) : number\n{\n let mixed = word;\n\n mixed = toWord(mixed ^ (mixed >>> 16));\n mixed = multiply(mixed, splitmixFirstPrime);\n mixed = toWord(mixed ^ (mixed >>> 15));\n mixed = multiply(mixed, splitmixSecondPrime);\n mixed = toWord(mixed ^ (mixed >>> 15));\n\n return mixed;\n}\n\nfunction spread(word : number) : RandomState\n{\n const first = toWord(word + splitmixStep);\n const second = toWord(first + splitmixStep);\n const third = toWord(second + splitmixStep);\n const fourth = toWord(third + splitmixStep);\n\n return { a: mix(first), b: mix(second), c: mix(third), d: mix(fourth) };\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * The seeded generator, running xoshiro128** over four 32-bit words (design/simulation.md#random-numbers).\n */\nexport class GameRandom\n{\n readonly seed : number;\n\n #a : number;\n #b : number;\n #c : number;\n #d : number;\n\n constructor(seed ?: RandomSeed)\n {\n let chosen = seed;\n\n if(chosen === undefined)\n {\n // A generator built with no seed is the one sequence SAGE does not repeat, and this is the one clock\n // SAGE's own code reads (design/simulation.md#the-generator).\n chosen = Date.now();\n }\n\n this.seed = seedWord(chosen);\n\n const words = spread(this.seed);\n\n this.#a = words.a;\n this.#b = words.b;\n this.#c = words.c;\n this.#d = words.d;\n }\n\n get state() : RandomState\n {\n return { a: this.#a, b: this.#b, c: this.#c, d: this.#d };\n }\n\n set state(value : RandomState)\n {\n this.#a = toWord(value.a);\n this.#b = toWord(value.b);\n this.#c = toWord(value.c);\n this.#d = toWord(value.d);\n }\n\n nextInt() : number\n {\n const result = multiply(rotateLeft(multiply(this.#b, 5), 7), 9);\n const shifted = toWord(this.#b << 9);\n\n this.#c = toWord(this.#c ^ this.#a);\n this.#d = toWord(this.#d ^ this.#b);\n this.#b = toWord(this.#b ^ this.#c);\n this.#a = toWord(this.#a ^ this.#d);\n this.#c = toWord(this.#c ^ shifted);\n this.#d = rotateLeft(this.#d, 11);\n\n return result;\n }\n\n nextFloat() : number\n {\n return this.nextInt() / wordLimit;\n }\n\n between(min : number, max : number) : number\n {\n if(max < min)\n {\n throw new RangeError(`A max of ${ max } is below its min of ${ min }.`);\n }\n\n return min + (this.nextFloat() * (max - min));\n }\n\n wholeBetween(min : number, max : number) : number\n {\n if(max < min)\n {\n throw new RangeError(`A max of ${ max } is below its min of ${ min }.`);\n }\n\n return min + Math.floor(this.nextFloat() * (max - min + 1));\n }\n\n chance(probability : number) : boolean\n {\n if(probability < 0 || probability > 1)\n {\n throw new RangeError(`A probability of ${ probability } is outside [0, 1].`);\n }\n\n return this.nextFloat() < probability;\n }\n\n normal(mean = 0, deviation = 1) : number\n {\n if(deviation < 0)\n {\n throw new RangeError(`A deviation of ${ deviation } is negative.`);\n }\n\n // Box-Muller. The first draw is taken as 1 - nextFloat() so the logarithm never meets zero\n // (design/simulation.md#drawing).\n const first = 1 - this.nextFloat();\n const second = this.nextFloat();\n\n return mean + (deviation * Math.sqrt(-2 * Math.log(first)) * Math.cos(2 * Math.PI * second));\n }\n\n pick<Item>(items : readonly Item[]) : Item\n {\n if(items.length === 0)\n {\n throw new RangeError('Cannot pick from an empty items array.');\n }\n\n return items[this.wholeBetween(0, items.length - 1)];\n }\n\n shuffle<Item>(items : readonly Item[]) : Item[]\n {\n const shuffled = [ ...items ];\n\n for(let index = shuffled.length - 1; index > 0; index--)\n {\n const swap = this.wholeBetween(0, index);\n const held = shuffled[index];\n\n shuffled[index] = shuffled[swap];\n shuffled[swap] = held;\n }\n\n return shuffled;\n }\n\n fork() : GameRandom\n {\n return new GameRandom(this.nextInt());\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// SimulationBus\n//\n// The bus a simulation's entities see. Detached, it is an ordinary bus of its own; attached, everything it is asked\n// goes to the engine's bus instead, and what it held moves across (design/events.md#one-bus-per-engine).\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { EventBus } from './eventBus.ts';\n\nimport type { EventSubscription, GameEvent, RawGameEvent, Subscriber, Unsubscribe } from './events.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\ninterface Registration\n{\n subscriber : Subscriber;\n subscriptions : readonly EventSubscription[];\n teardown : Unsubscribe;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport class SimulationBus extends EventBus\n{\n readonly #name : string;\n readonly #engineBus : EventBus | undefined;\n readonly #registrations : Registration[] = [];\n\n #attachedAs : Unsubscribe | undefined;\n #forwarded : RawGameEvent[] = [];\n\n constructor(name : string, engineBus : EventBus | undefined)\n {\n super();\n\n this.#name = name;\n this.#engineBus = engineBus;\n }\n\n get attached() : boolean\n {\n return this.#attachedAs !== undefined;\n }\n\n // A subscriber made here reaches the engine bus beside the engine's own, so its ID carries the simulation's\n // name the way the simulation's entities' IDs do (design/events.md#registration).\n protected override mintSubscriberID(ordinal : number) : string\n {\n return `${ this.#name }-subscriber-${ ordinal }`;\n }\n\n override register(subscriber : Subscriber, subscriptions : readonly EventSubscription[]) : Unsubscribe\n {\n const attachedTo = this.#attachedTo();\n\n let teardown : Unsubscribe;\n\n if(attachedTo !== undefined)\n {\n teardown = attachedTo.register(subscriber, subscriptions);\n }\n else\n {\n teardown = super.register(subscriber, subscriptions);\n }\n\n const registration : Registration = { subscriber, subscriptions, teardown };\n this.#registrations.push(registration);\n\n return () : void =>\n {\n const index = this.#registrations.indexOf(registration);\n\n if(index < 0)\n {\n return;\n }\n\n this.#registrations.splice(index, 1);\n registration.teardown();\n };\n }\n\n override emit(event : GameEvent) : void\n {\n const attachedTo = this.#attachedTo();\n\n if(attachedTo !== undefined)\n {\n // Kept so detach can reclaim it if the engine bus has not drained it yet\n // (design/events.md#one-bus-per-engine).\n this.#forwarded.push(event as RawGameEvent);\n attachedTo.emit(event);\n }\n else\n {\n super.emit(event);\n }\n }\n\n override drain() : void\n {\n const attachedTo = this.#attachedTo();\n\n if(attachedTo !== undefined)\n {\n try\n {\n attachedTo.drain();\n }\n finally\n {\n this.#forwarded = [];\n }\n }\n else\n {\n super.drain();\n }\n }\n\n attach() : void\n {\n // With no engine bus this already holds every registration and every emission attaching would move, which\n // is the state attaching produces, so it changes nothing the way attaching an attached simulation does\n // (design/events.md#one-bus-per-engine).\n if(this.#engineBus === undefined || this.#attachedAs !== undefined)\n {\n return;\n }\n\n // The simulation registers under its own name first, which is what refuses a second attached simulation\n // of the same name (design/simulation.md#the-simulations-name).\n this.#attachedAs = this.#engineBus.register({ id: this.#name, receive: () => false }, []);\n\n for(const registration of this.#registrations)\n {\n registration.teardown();\n registration.teardown = this.#engineBus.register(registration.subscriber, registration.subscriptions);\n }\n\n for(const event of this.takeQueuedEvents())\n {\n this.#engineBus.emit(event);\n }\n }\n\n detach() : void\n {\n if(this.#attachedAs === undefined)\n {\n return;\n }\n\n for(const registration of this.#registrations)\n {\n registration.teardown();\n registration.teardown = super.register(registration.subscriber, registration.subscriptions);\n }\n\n // Detaching reverses attaching in both directions: subscribers move back here, and so does whichever of\n // this simulation's own emissions the engine bus has not yet drained (design/events.md#one-bus-per-engine).\n const engineBus = this.#engineBus;\n\n if(engineBus !== undefined)\n {\n for(const event of EventBus.reclaimQueuedEvents(engineBus, this.#forwarded))\n {\n super.emit(event);\n }\n }\n\n this.#forwarded = [];\n\n this.#attachedAs();\n this.#attachedAs = undefined;\n }\n\n #attachedTo() : EventBus | undefined\n {\n if(this.#attachedAs === undefined)\n {\n return undefined;\n }\n\n return this.#engineBus;\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// GameSimulation\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { dilationLimit } from './constants.ts';\nimport { GameRandom, type RandomSeed } from './gameRandom.ts';\nimport { SimulationBus } from './simulationBus.ts';\nimport {\n type ComposedOf, type GameEntity, type GameEntityClass, type GameEntityState, type GameEntityType, type StateBag,\n type StateFor, type ValidTuple, copyStateBag,\n} from './gameEntity.ts';\n\nimport type { BehaviorClass } from './behavior.ts';\n\nimport type { EventBus } from './eventBus.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport interface GameEntitySnapshot\n{\n id : string;\n name : string;\n state : StateBag;\n}\n\nexport interface GameSimulationSnapshot\n{\n id : string;\n tick : number;\n idCounter : number;\n entities : GameEntitySnapshot[];\n}\n\nexport type IDAllocator = () => string;\n\n// The classes a simulation spawns from, each constructable with the context the simulation provides.\nexport type GameEntityRegistry<Context extends GameSimulationView = GameSimulationView>\n = Readonly<Record<string, GameEntityClass<Context>>>;\n\n// Every class in the registry carries the composition check its own `create` carries, so a composition the\n// compiler refuses is refused at the registry (design/entities.md#the-entity-registry).\nexport type ValidEntityRegistry<Registry extends GameEntityRegistry<never>> = {\n readonly [Name in keyof Registry] : Registry[Name] & {\n readonly behaviors : ValidTuple<Registry[Name]['behaviors'], Registry[Name]['prototype']>;\n };\n};\n\nexport interface GameSimulationOptions<Registry extends GameEntityRegistry<never> = GameEntityRegistry>\n{\n id : string;\n fixedStepDuration : number;\n entityClasses : Registry;\n allocator ?: IDAllocator;\n seed ?: RandomSeed;\n bus ?: EventBus;\n}\n\n// A table typed as any class at all types nothing about a spawn, so its state is the plain bag.\ntype SpawnState<Type extends GameEntityType> = readonly BehaviorClass[] extends Type['behaviors']\n ? StateBag\n : Partial<StateFor<Type['behaviors']>>;\n\n// The persistent simulation, built after 0.10.0, takes this name (design/simulation.md#the-simulations-name).\nconst reservedName = 'global';\n\n// How close to a whole interval the accumulator has to be to owe a fixed step, as a fraction of the fixed-step\n// duration. A unit in the last place is proportional to the value it sits in, so an absolute constant is a wide\n// tolerance for a duration in seconds and no tolerance at all for the same rate written in milliseconds\n// (design/simulation.md#the-fixed-step).\nconst fixedStepTolerance = 128 * Number.EPSILON;\n/**\n * A simulation with its entity registry unknown: every member of `GameSimulation` whose type does not name the\n * registry. `GameSimulation` is invariant in its registry, so this is what code takes when it holds a simulation\n * without knowing what it spawns; `spawn` and `buildFromSnapshot` are the two members it lacks. It is also what an\n * entity is created with, and what a behavior reaches as `this.simulation`\n * (design/simulation.md#the-simulation, design/entities.md#cross-entity-access).\n */\nexport interface GameSimulationView\n{\n readonly id : string;\n readonly bus : EventBus;\n readonly attached : boolean;\n readonly tick : number;\n readonly entities : readonly GameEntity[];\n attach() : void;\n detach() : void;\n spawnByName(name : string, state ?: StateBag, id ?: string) : GameEntity;\n despawn(id : string) : boolean;\n getEntity(id : string) : GameEntity | undefined;\n getEntitiesByTag(tag : string) : readonly GameEntity[];\n getEntitiesOfClass<Type extends GameEntityType>(entityClass : Type) : readonly ComposedOf<Type>[];\n runStepsForElapsedTime(elapsed : number) : void;\n readonly random : GameRandom;\n snapshot() : GameSimulationSnapshot;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// The entity registry is typed by the context this simulation provides to `create`: a subclass providing a narrower one\n// names it as `Context` and implements it, which is what lets a class needing that context into its registry\n// (design/entities.md#the-entity-registry).\nexport class GameSimulation<\n Registry extends GameEntityRegistry<Context> = GameEntityRegistry,\n Context extends GameSimulationView = GameSimulationView,\n> implements GameSimulationView, GameSimulationView\n{\n readonly #id : string;\n readonly #bus : SimulationBus;\n readonly #engineBus : EventBus | undefined;\n readonly #fixedStepDuration : number;\n readonly #entityClasses : Registry;\n readonly #allocator : IDAllocator | undefined;\n readonly #seed : RandomSeed;\n readonly #random : GameRandom;\n\n #tick = 0;\n #idCounter = 0;\n #accumulator = 0;\n #entries : GameEntity[] = [];\n #byID = new Map<string, GameEntity>();\n #retiredIDs = new Set<string>();\n\n constructor(options : GameSimulationOptions<Registry> & { entityClasses : ValidEntityRegistry<Registry> })\n {\n this.#id = options.id;\n this.#fixedStepDuration = options.fixedStepDuration;\n this.#entityClasses = options.entityClasses;\n this.#allocator = options.allocator;\n\n // A simulation constructed with no seed seeds from its own name, so it rolls the same sequence every run\n // (design/simulation.md#the-simulations-generator).\n this.#seed = options.seed ?? options.id;\n this.#random = new GameRandom(this.#seed);\n\n this.#engineBus = options.bus;\n this.#bus = new SimulationBus(options.id, options.bus);\n\n if(this.#id === reservedName)\n {\n throw new Error(`The simulation name \"${ reservedName }\" is reserved for the persistent simulation.`);\n }\n\n if(!Number.isFinite(this.#fixedStepDuration) || this.#fixedStepDuration <= 0)\n {\n throw new RangeError('A fixed step duration must be a finite number greater than zero.');\n }\n }\n\n get id() : string\n {\n return this.#id;\n }\n\n get bus() : EventBus\n {\n return this.#bus;\n }\n\n get attached() : boolean\n {\n return this.#bus.attached;\n }\n\n get tick() : number\n {\n return this.#tick;\n }\n\n get entities() : readonly GameEntity[]\n {\n return [ ...this.#entries ];\n }\n\n attach() : void\n {\n this.#bus.attach();\n }\n\n detach() : void\n {\n this.#bus.detach();\n }\n\n spawn<Name extends keyof Registry & string>(\n name : Name,\n state ?: SpawnState<Registry[Name]>,\n id ?: string\n ) : ComposedOf<Registry[Name]>\n {\n return this.spawnByName(name, state as StateBag | undefined, id) as ComposedOf<Registry[Name]>;\n }\n\n /**\n * The same spawn for a name known only at run time, a marker's or a console's: the registry checks the name here\n * rather than the compiler at the call (design/simulation.md#spawn-and-despawn).\n */\n spawnByName(name : string, state ?: StateBag, id ?: string) : GameEntity\n {\n const entityClass = this.#entityClass(name);\n\n let entityID = id;\n\n if(entityID === undefined)\n {\n entityID = this.#mintID();\n }\n\n if(this.#byID.has(entityID))\n {\n throw new Error(`The simulation already holds an entity under the ID \"${ entityID }\".`);\n }\n\n // An ID is unique for the simulation's whole life, so one already despawned never spawns again\n // (design/entities.md#ids).\n if(this.#retiredIDs.has(entityID))\n {\n throw new Error(`The simulation already retired the ID \"${ entityID }\"; an ID is never reused.`);\n }\n\n const entity = entityClass.create(entityID, this.#context(), state ?? {}, name);\n\n this.#entries.push(entity);\n this.#byID.set(entityID, entity);\n\n return entity;\n }\n\n despawn(id : string) : boolean\n {\n return this.#despawn(id, new Set<string>());\n }\n\n #despawn(id : string, inFlight : Set<string>) : boolean\n {\n const entity = this.#byID.get(id);\n\n if(entity === undefined)\n {\n return false;\n }\n\n inFlight.add(id);\n\n // Children first, each found by the parent ID in its state (design/simulation.md#spawn-and-despawn). A\n // parent ID is a state field, and writing state notifies nobody, so the children are found by looking rather\n // than kept in an index that a write would leave stale. A parent ID cycle would otherwise recurse without\n // end; an ID already in flight is skipped instead, so a cycle despawns each entity once.\n for(const child of this.#childrenOf(id))\n {\n if(!inFlight.has(child.id))\n {\n this.#despawn(child.id, inFlight);\n }\n }\n\n entity.destroy();\n\n this.#entries.splice(this.#entries.indexOf(entity), 1);\n this.#byID.delete(id);\n this.#retiredIDs.add(id);\n\n return true;\n }\n\n getEntity(id : string) : GameEntity | undefined\n {\n return this.#byID.get(id);\n }\n\n getEntitiesByTag(tag : string) : readonly GameEntity[]\n {\n const found : GameEntity[] = [];\n\n for(const entity of this.#entries)\n {\n if(GameSimulation.#entityState(entity).tags.includes(tag))\n {\n found.push(entity);\n }\n }\n\n return found;\n }\n\n getEntitiesOfClass<Type extends GameEntityType>(entityClass : Type) : readonly ComposedOf<Type>[]\n {\n const found : ComposedOf<Type>[] = [];\n\n for(const entity of this.#entries)\n {\n if(entity instanceof (entityClass as unknown as abstract new () => GameEntity))\n {\n found.push(entity as ComposedOf<Type>);\n }\n }\n\n return found;\n }\n\n runStepsForElapsedTime(elapsed : number) : void\n {\n if(!Number.isFinite(elapsed) || elapsed < 0)\n {\n throw new RangeError('Elapsed time must be a finite number of zero or more.');\n }\n\n this.#accumulator += elapsed;\n\n let steps = 0;\n\n while(this.#owesAFixedStep() && steps < dilationLimit)\n {\n this.#runFixedStep();\n steps += 1;\n }\n\n // Chronic overrun degrades into simulation time running slower than wall clock: the whole intervals still owed\n // are dropped and only the fraction is carried.\n if(steps === dilationLimit && this.#owesAFixedStep())\n {\n this.#accumulator %= this.#fixedStepDuration;\n }\n }\n\n get random() : GameRandom\n {\n return this.#random;\n }\n\n snapshot() : GameSimulationSnapshot\n {\n const entities : GameEntitySnapshot[] = [];\n\n for(const entity of this.#entries)\n {\n entities.push({\n id: entity.id,\n name: entity.name,\n state: copyStateBag(entity.state as StateBag, entity.id),\n });\n }\n\n return { id: this.#id, tick: this.#tick, idCounter: this.#idCounter, entities };\n }\n\n buildFromSnapshot(snapshot : GameSimulationSnapshot) : GameSimulation<Registry, Context>\n {\n const restored = this.buildReplacementSimulation({\n id: snapshot.id,\n fixedStepDuration: this.#fixedStepDuration,\n entityClasses: this.#entityClasses,\n allocator: this.#allocator,\n seed: this.#seed,\n bus: this.#engineBus,\n });\n\n // The generator carries over by state, so the replacement rolls on from where this one stands rather than\n // from the seed again (design/simulation.md#the-simulations-generator).\n restored.#random.state = this.#random.state;\n\n restored.#load(snapshot);\n\n return restored;\n }\n\n protected buildReplacementSimulation(\n options : GameSimulationOptions<Registry>\n ) : GameSimulation<Registry, Context>\n {\n return new GameSimulation<Registry, Context>(options as GameSimulationOptions<Registry> & {\n entityClasses : ValidEntityRegistry<Registry>;\n });\n }\n\n #load(snapshot : GameSimulationSnapshot) : void\n {\n const entries : GameEntity[] = [];\n\n try\n {\n for(const row of snapshot.entities)\n {\n const entityClass = this.#entityClass(row.name);\n const entity = entityClass.restore(\n row.id,\n this.#context(),\n row.state as GameEntityState & StateBag,\n row.name\n );\n entries.push(entity);\n }\n }\n catch(error)\n {\n for(const entity of entries)\n {\n entity.destroy();\n }\n\n throw error;\n }\n\n this.#entries = entries;\n this.#byID = new Map();\n\n for(const entity of entries)\n {\n this.#byID.set(entity.id, entity);\n }\n\n this.#tick = snapshot.tick;\n this.#idCounter = snapshot.idCounter;\n }\n\n // The accumulator holds a sum of binary floating point values, which lands a few units in the last place short of\n // the interval the caller meant; without the tolerance three calls of a third of a step run no step at all.\n #owesAFixedStep() : boolean\n {\n const tolerance = fixedStepTolerance * this.#fixedStepDuration;\n\n return this.#accumulator + tolerance >= this.#fixedStepDuration;\n }\n\n /**\n * Advances whatever else this simulation carries, once per fixed step, after every entity has updated\n * and before the bus drains.\n *\n * `sage-core` carries nothing and does nothing here. A simulation holding a physics engine steps it\n * from this, so what a behavior told a body during an update takes effect in the step it was told and\n * what physics reports drains in the step it happened (design/simulation.md#the-fixed-step). It is\n * synchronous because a fixed step is, and a throw from it leaves the simulation dead exactly as a\n * throw from an entity's update does (design/simulation.md#failure).\n *\n * @param duration - The fixed step's own duration, never a frame's elapsed time.\n */\n protected advanceFixedStep(duration : number) : void\n {\n void duration;\n }\n\n #runFixedStep() : void\n {\n // An entity already despawned by an earlier one's update this step never starts its own; an entity that\n // despawns itself partway through its own update stops there too, in GameEntity.update\n // (design/entities.md#destruction).\n for(const entity of [ ...this.#entries ])\n {\n if(this.#byID.get(entity.id) === entity)\n {\n entity.update(this.#fixedStepDuration);\n }\n }\n\n this.advanceFixedStep(this.#fixedStepDuration);\n\n // The step ran, so the tick and the accumulator settle whether or not the drain raised.\n try\n {\n this.#bus.drain();\n }\n finally\n {\n this.#accumulator -= this.#fixedStepDuration;\n\n if(Math.abs(this.#accumulator) <= fixedStepTolerance * this.#fixedStepDuration)\n {\n this.#accumulator = 0;\n }\n\n this.#tick += 1;\n }\n }\n\n #childrenOf(parentID : string) : GameEntity[]\n {\n const children : GameEntity[] = [];\n\n for(const entity of this.#entries)\n {\n if((entity.state as StateBag)['parentID'] === parentID)\n {\n children.push(entity);\n }\n }\n\n return children;\n }\n\n #mintID() : string\n {\n if(this.#allocator !== undefined)\n {\n return this.#allocator();\n }\n\n this.#idCounter += 1;\n\n while(this.#idIsSpent(`${ this.#id }-${ this.#idCounter }`))\n {\n this.#idCounter += 1;\n }\n\n return `${ this.#id }-${ this.#idCounter }`;\n }\n\n // A despawned ID is spent and never minted again (design/entities.md#ids).\n #idIsSpent(candidate : string) : boolean\n {\n return this.#byID.has(candidate) || this.#retiredIDs.has(candidate);\n }\n\n // This simulation is the context its entities are created with. The cast is unchecked: TypeScript accepts a\n // subclass naming a narrower Context whether or not the subclass provides it; only a subclass that also writes\n // its own `implements Context` is checked against the shape it named.\n #context() : Context\n {\n return this as unknown as Context;\n }\n\n #entityClass(name : string) : GameEntityClass<Context>\n {\n const entityClass = this.#entityClasses[name];\n\n if(entityClass === undefined)\n {\n throw new Error(`The entity registry holds no class named \"${ name }\".`);\n }\n\n return entityClass;\n }\n\n // Every entity carries the tags field whatever its behaviors declare, so the query reads it straight.\n static #entityState(entity : GameEntity) : GameEntityState\n {\n return entity.state as GameEntityState;\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n"],"mappings":"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"}
|
package/dist/simulation.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { GameRandom, RandomSeed } from "./gameRandom.js";
|
|
1
2
|
import { ComposedOf, GameEntity, GameEntityClass, GameEntityType, StateBag, StateFor, ValidTuple } from "./gameEntity.js";
|
|
2
3
|
import { BehaviorClass } from "./behavior.js";
|
|
3
4
|
import { EventBus } from "./eventBus.js";
|
|
4
|
-
import { GameSimulationContext } from "./simulationContext.js";
|
|
5
5
|
export interface GameEntitySnapshot {
|
|
6
6
|
id: string;
|
|
7
7
|
name: string;
|
|
@@ -14,7 +14,7 @@ export interface GameSimulationSnapshot {
|
|
|
14
14
|
entities: GameEntitySnapshot[];
|
|
15
15
|
}
|
|
16
16
|
export type IDAllocator = () => string;
|
|
17
|
-
export type GameEntityRegistry<Context extends
|
|
17
|
+
export type GameEntityRegistry<Context extends GameSimulationView = GameSimulationView> = Readonly<Record<string, GameEntityClass<Context>>>;
|
|
18
18
|
export type ValidEntityRegistry<Registry extends GameEntityRegistry<never>> = {
|
|
19
19
|
readonly [Name in keyof Registry]: Registry[Name] & {
|
|
20
20
|
readonly behaviors: ValidTuple<Registry[Name]['behaviors'], Registry[Name]['prototype']>;
|
|
@@ -25,10 +25,35 @@ export interface GameSimulationOptions<Registry extends GameEntityRegistry<never
|
|
|
25
25
|
fixedStepDuration: number;
|
|
26
26
|
entityClasses: Registry;
|
|
27
27
|
allocator?: IDAllocator;
|
|
28
|
+
seed?: RandomSeed;
|
|
28
29
|
bus?: EventBus;
|
|
29
30
|
}
|
|
30
31
|
type SpawnState<Type extends GameEntityType> = readonly BehaviorClass[] extends Type['behaviors'] ? StateBag : Partial<StateFor<Type['behaviors']>>;
|
|
31
|
-
|
|
32
|
+
/**
|
|
33
|
+
* A simulation with its entity registry unknown: every member of `GameSimulation` whose type does not name the
|
|
34
|
+
* registry. `GameSimulation` is invariant in its registry, so this is what code takes when it holds a simulation
|
|
35
|
+
* without knowing what it spawns; `spawn` and `buildFromSnapshot` are the two members it lacks. It is also what an
|
|
36
|
+
* entity is created with, and what a behavior reaches as `this.simulation`
|
|
37
|
+
* (design/simulation.md#the-simulation, design/entities.md#cross-entity-access).
|
|
38
|
+
*/
|
|
39
|
+
export interface GameSimulationView {
|
|
40
|
+
readonly id: string;
|
|
41
|
+
readonly bus: EventBus;
|
|
42
|
+
readonly attached: boolean;
|
|
43
|
+
readonly tick: number;
|
|
44
|
+
readonly entities: readonly GameEntity[];
|
|
45
|
+
attach(): void;
|
|
46
|
+
detach(): void;
|
|
47
|
+
spawnByName(name: string, state?: StateBag, id?: string): GameEntity;
|
|
48
|
+
despawn(id: string): boolean;
|
|
49
|
+
getEntity(id: string): GameEntity | undefined;
|
|
50
|
+
getEntitiesByTag(tag: string): readonly GameEntity[];
|
|
51
|
+
getEntitiesOfClass<Type extends GameEntityType>(entityClass: Type): readonly ComposedOf<Type>[];
|
|
52
|
+
runStepsForElapsedTime(elapsed: number): void;
|
|
53
|
+
readonly random: GameRandom;
|
|
54
|
+
snapshot(): GameSimulationSnapshot;
|
|
55
|
+
}
|
|
56
|
+
export declare class GameSimulation<Registry extends GameEntityRegistry<Context> = GameEntityRegistry, Context extends GameSimulationView = GameSimulationView> implements GameSimulationView, GameSimulationView {
|
|
32
57
|
#private;
|
|
33
58
|
constructor(options: GameSimulationOptions<Registry> & {
|
|
34
59
|
entityClasses: ValidEntityRegistry<Registry>;
|
|
@@ -51,9 +76,10 @@ export declare class GameSimulationManager<Registry extends GameEntityRegistry<C
|
|
|
51
76
|
getEntitiesByTag(tag: string): readonly GameEntity[];
|
|
52
77
|
getEntitiesOfClass<Type extends GameEntityType>(entityClass: Type): readonly ComposedOf<Type>[];
|
|
53
78
|
runStepsForElapsedTime(elapsed: number): void;
|
|
79
|
+
get random(): GameRandom;
|
|
54
80
|
snapshot(): GameSimulationSnapshot;
|
|
55
|
-
buildFromSnapshot(snapshot: GameSimulationSnapshot):
|
|
56
|
-
protected buildReplacementSimulation(options: GameSimulationOptions<Registry>):
|
|
81
|
+
buildFromSnapshot(snapshot: GameSimulationSnapshot): GameSimulation<Registry, Context>;
|
|
82
|
+
protected buildReplacementSimulation(options: GameSimulationOptions<Registry>): GameSimulation<Registry, Context>;
|
|
57
83
|
/**
|
|
58
84
|
* Advances whatever else this simulation carries, once per fixed step, after every entity has updated
|
|
59
85
|
* and before the bus drains.
|
package/package.json
CHANGED