@daneren2005/shared-memory-ecs 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Scott Jackson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -131,6 +131,63 @@ class DamageSystem extends ComponentSystem<Components, { health: Int32Array }, D
131
131
  }
132
132
  ```
133
133
 
134
+ ### Reporting back to the main thread
135
+
136
+ An update function runs on shared memory, so anything it writes is already visible on the main thread. What
137
+ it cannot do from there is touch the world, so the things that have to happen back on it go through
138
+ `callbacks`: `entityComponentChanged` (emitted on the entity as `component-property-updated`), `entityDied`
139
+ (as `death`), and `createEntity`. All of them are collected during the run and applied once it completes.
140
+
141
+ `emitEntityEvent` is the escape hatch for an event of your own: name it whatever you like and give it
142
+ whatever args suit it, and it is emitted on the entity under that name. It exists so a system does not have
143
+ to spend one `component-property-updated` per property when the listener only cares about all of them
144
+ together - a move that reports `x` and `y` as one `position-updated` is half the events of one per axis:
145
+
146
+ ```ts
147
+ // in the update function
148
+ callbacks.emitEntityEvent(entityId, 'position-updated', x, y);
149
+
150
+ // on the main thread
151
+ entity.on('position-updated', (x: number, y: number) => { ... });
152
+ ```
153
+
154
+ The args are structured-cloned across the worker boundary, so they have to be plain values - no functions,
155
+ no class instances. Nothing about the name or the args is checked against your component map, since the
156
+ event is the system's own concept rather than a component, so export both alongside the update function that
157
+ emits them.
158
+
159
+ ## Measuring performance
160
+
161
+ `PerformanceTiming` watches a world and reports what running it costs. Hand it the world and it hooks itself
162
+ up to the events the world already emits - there is nothing to call per frame and nothing added to the hot
163
+ path:
164
+
165
+ ```ts
166
+ const timing = new PerformanceTiming(world);
167
+ timing.on('stats-updated', (stats: PerformanceStats) => renderDebugPanel(stats));
168
+ ```
169
+
170
+ It gathers samples every frame and collapses them into a fresh `timing.stats` snapshot once
171
+ `ticksBetweenUpdates` (default `1_000`) worth of elapsed time has gone by, then emits `stats-updated` with it.
172
+ The window is measured in whatever unit you drive `world.update` with, so a game running on milliseconds gets
173
+ a snapshot a second. Frames the world was paused for are skipped, since it does no work on them.
174
+
175
+ Every entry is an `{ avg, min, max, samples }` over the window just closed:
176
+
177
+ - `stats.update` - one whole `world.update` call on the thread the world lives on.
178
+ - `stats.systems[]` - per system, in run order: `run` is the run itself on its worker, as the worker measured
179
+ it, and `events` is what handling that run's results (the events it reported onto entities, the entities it
180
+ asked to be created) cost back on the calling thread. A system running on the main-thread fallback never
181
+ reports either, so it sits at zero with `samples: 0` - which is what tells it apart from one that genuinely
182
+ cost nothing.
183
+ - `stats.events` - every system's event handling added together. Workers finish on their own schedule rather
184
+ than on a frame boundary, so there is no per-frame combined sample to take; these are the per-system figures
185
+ summed at the end of the window, giving what a run of every system costs the main thread between them.
186
+
187
+ `getSystemStats(name)` pulls one system out of the latest snapshot, `reset()` throws away everything collected
188
+ so far (worth doing after loading a new scene, when the samples either side are not comparable), and
189
+ `destroy()` unhooks it from the world.
190
+
134
191
  ## Building
135
192
 
136
193
  ```sh
package/dist/index.d.ts CHANGED
@@ -7,6 +7,8 @@ export type { EntityComponent, EntityComponentConfig, EntityComponentSerializati
7
7
  export { default as killEntity } from './actions/kill-entity';
8
8
  export { default as killEntityWorker } from './actions/kill-entity-worker';
9
9
  export { default as createEntityWorker } from './actions/create-entity-worker';
10
+ export { default as PerformanceTiming, DEFAULT_TICKS_BETWEEN_UPDATES } from './performance-timing';
11
+ export type { TimingStats, SystemTimingStats, PerformanceStats, PerformanceTimingOptions, } from './performance-timing';
10
12
  export { default as MemoryComponent } from './memory-component';
11
13
  export type { ComponentTypedArray } from './memory-component';
12
14
  export type { BaseComponent, ComponentMap, ComponentDefinition, ComponentDefinitionMap, ComponentRegistry, ComponentsOf, EntityConfigOf, RegisteredComponentDefinition, RegisteredComponentRegistry, } from './component-definition';
@@ -22,3 +24,4 @@ export { default as WebWorker } from './systems/workers/web-worker';
22
24
  export { default as ComponentWebWorker } from './systems/workers/component-web-worker';
23
25
  export { default as createComponentWorker } from './systems/workers/create-component-worker';
24
26
  export type { default as ComponentWorkerMessage } from './systems/workers/component-worker-message';
27
+ export type { EntityEvent } from './systems/workers/component-worker-message';
package/dist/index.js CHANGED
@@ -1,11 +1,106 @@
1
- import { EventEmitter as e } from "eventemitter3";
2
- import { LocalPool as t, MAX_BYTE_OFFSET_LENGTH as n, MemoryHeap as r } from "@daneren2005/shared-memory-objects";
3
- //#region src/memory-component.ts
4
- var i = class {
1
+ import { LocalPool as e, MAX_BYTE_OFFSET_LENGTH as t, MemoryHeap as n } from "@daneren2005/shared-memory-objects";
2
+ //#region \0rolldown/runtime.js
3
+ var r = Object.create, i = Object.defineProperty, a = Object.getOwnPropertyDescriptor, o = Object.getOwnPropertyNames, s = Object.getPrototypeOf, c = Object.prototype.hasOwnProperty, l = (e, t) => () => (t || (e((t = { exports: {} }).exports, t), e = null), t.exports), u = (e, t, n, r) => {
4
+ if (t && typeof t == "object" || typeof t == "function") for (var s = o(t), l = 0, u = s.length, d; l < u; l++) d = s[l], !c.call(e, d) && d !== n && i(e, d, {
5
+ get: ((e) => t[e]).bind(null, d),
6
+ enumerable: !(r = a(t, d)) || r.enumerable
7
+ });
8
+ return e;
9
+ }, d = /* @__PURE__ */ ((e, t, n) => (n = e == null ? {} : r(s(e)), u(t || !e || !e.__esModule ? i(n, "default", {
10
+ value: e,
11
+ enumerable: !0
12
+ }) : n, e)))((/* @__PURE__ */ l(((e, t) => {
13
+ var n = Object.prototype.hasOwnProperty, r = "~";
14
+ function i() {}
15
+ Object.create && (i.prototype = Object.create(null), new i().__proto__ || (r = !1));
16
+ function a(e, t, n) {
17
+ this.fn = e, this.context = t, this.once = n || !1;
18
+ }
19
+ function o(e, t, n, i, o) {
20
+ if (typeof n != "function") throw TypeError("The listener must be a function");
21
+ var s = new a(n, i || e, o), c = r ? r + t : t;
22
+ return e._events[c] ? e._events[c].fn ? e._events[c] = [e._events[c], s] : e._events[c].push(s) : (e._events[c] = s, e._eventsCount++), e;
23
+ }
24
+ function s(e, t) {
25
+ --e._eventsCount === 0 ? e._events = new i() : delete e._events[t];
26
+ }
27
+ function c() {
28
+ this._events = new i(), this._eventsCount = 0;
29
+ }
30
+ c.prototype.eventNames = function() {
31
+ var e = [], t, i;
32
+ if (this._eventsCount === 0) return e;
33
+ for (i in t = this._events) n.call(t, i) && e.push(r ? i.slice(1) : i);
34
+ return Object.getOwnPropertySymbols ? e.concat(Object.getOwnPropertySymbols(t)) : e;
35
+ }, c.prototype.listeners = function(e) {
36
+ var t = r ? r + e : e, n = this._events[t];
37
+ if (!n) return [];
38
+ if (n.fn) return [n.fn];
39
+ for (var i = 0, a = n.length, o = Array(a); i < a; i++) o[i] = n[i].fn;
40
+ return o;
41
+ }, c.prototype.listenerCount = function(e) {
42
+ var t = r ? r + e : e, n = this._events[t];
43
+ return n ? n.fn ? 1 : n.length : 0;
44
+ }, c.prototype.emit = function(e, t, n, i, a, o) {
45
+ var s = r ? r + e : e;
46
+ if (!this._events[s]) return !1;
47
+ var c = this._events[s], l = arguments.length, u, d;
48
+ if (c.fn) {
49
+ switch (c.once && this.removeListener(e, c.fn, void 0, !0), l) {
50
+ case 1: return c.fn.call(c.context), !0;
51
+ case 2: return c.fn.call(c.context, t), !0;
52
+ case 3: return c.fn.call(c.context, t, n), !0;
53
+ case 4: return c.fn.call(c.context, t, n, i), !0;
54
+ case 5: return c.fn.call(c.context, t, n, i, a), !0;
55
+ case 6: return c.fn.call(c.context, t, n, i, a, o), !0;
56
+ }
57
+ for (d = 1, u = Array(l - 1); d < l; d++) u[d - 1] = arguments[d];
58
+ c.fn.apply(c.context, u);
59
+ } else {
60
+ var f = c.length, p;
61
+ for (d = 0; d < f; d++) switch (c[d].once && this.removeListener(e, c[d].fn, void 0, !0), l) {
62
+ case 1:
63
+ c[d].fn.call(c[d].context);
64
+ break;
65
+ case 2:
66
+ c[d].fn.call(c[d].context, t);
67
+ break;
68
+ case 3:
69
+ c[d].fn.call(c[d].context, t, n);
70
+ break;
71
+ case 4:
72
+ c[d].fn.call(c[d].context, t, n, i);
73
+ break;
74
+ default:
75
+ if (!u) for (p = 1, u = Array(l - 1); p < l; p++) u[p - 1] = arguments[p];
76
+ c[d].fn.apply(c[d].context, u);
77
+ }
78
+ }
79
+ return !0;
80
+ }, c.prototype.on = function(e, t, n) {
81
+ return o(this, e, t, n, !1);
82
+ }, c.prototype.once = function(e, t, n) {
83
+ return o(this, e, t, n, !0);
84
+ }, c.prototype.removeListener = function(e, t, n, i) {
85
+ var a = r ? r + e : e;
86
+ if (!this._events[a]) return this;
87
+ if (!t) return s(this, a), this;
88
+ var o = this._events[a];
89
+ if (o.fn) o.fn === t && (!i || o.once) && (!n || o.context === n) && s(this, a);
90
+ else {
91
+ for (var c = 0, l = [], u = o.length; c < u; c++) (o[c].fn !== t || i && !o[c].once || n && o[c].context !== n) && l.push(o[c]);
92
+ l.length ? this._events[a] = l.length === 1 ? l[0] : l : s(this, a);
93
+ }
94
+ return this;
95
+ }, c.prototype.removeAllListeners = function(e) {
96
+ var t;
97
+ return e ? (t = r ? r + e : e, this._events[t] && s(this, t)) : (this._events = new i(), this._eventsCount = 0), this;
98
+ }, c.prototype.off = c.prototype.removeListener, c.prototype.addListener = c.prototype.on, c.prefixed = r, c.EventEmitter = c, t !== void 0 && (t.exports = c);
99
+ })))(), 1), f = class {
5
100
  heap;
6
101
  pool;
7
- constructor(e, n, r) {
8
- this.heap = e, this.pool = new t(e, {
102
+ constructor(t, n, r) {
103
+ this.heap = t, this.pool = new e(t, {
9
104
  type: n,
10
105
  dataLength: r
11
106
  });
@@ -35,7 +130,7 @@ var i = class {
35
130
  clear() {
36
131
  this.pool.clear();
37
132
  }
38
- }, a = class {
133
+ }, p = class {
39
134
  world;
40
135
  name;
41
136
  currentDelta = 0;
@@ -59,7 +154,7 @@ var i = class {
59
154
  return !0;
60
155
  }
61
156
  destroy() {}
62
- }, o = class extends a {
157
+ }, m = class extends p {
63
158
  remainingInstancesToRun = [];
64
159
  remainingInstancesStartTime = null;
65
160
  iterationsPerCheck;
@@ -87,7 +182,7 @@ var i = class {
87
182
  this.remainingInstancesToRun = [], this.remainingInstancesStartTime = null;
88
183
  }
89
184
  beforeRunIterables() {}
90
- }, s = class extends o {
185
+ }, h = class extends m {
91
186
  entities = [];
92
187
  options;
93
188
  constructor(e, t = { name: "EntitySystem" }) {
@@ -121,12 +216,12 @@ var i = class {
121
216
  shouldRun() {
122
217
  return this.entities.length > 0;
123
218
  }
124
- }, c = class {
219
+ }, g = class {
125
220
  onmessage(e, t = []) {}
126
221
  };
127
222
  //#endregion
128
223
  //#region src/systems/workers/apply-query-delta.ts
129
- function l(e, t) {
224
+ function _(e, t) {
130
225
  if (t.removed.length) {
131
226
  let n = new Set(t.removed);
132
227
  e = e.filter((e) => !n.has(e.entityId));
@@ -143,7 +238,7 @@ function l(e, t) {
143
238
  }
144
239
  //#endregion
145
240
  //#region src/systems/workers/component-web-worker.ts
146
- var u = class extends c {
241
+ var v = class extends g {
147
242
  updateFunction;
148
243
  entities = [];
149
244
  queryEntities = {};
@@ -154,10 +249,10 @@ var u = class extends c {
154
249
  if (e.type === "init") this.onMessageTyped({ type: "loaded" });
155
250
  else if (e.type === "run") {
156
251
  let t = [], n = [];
157
- this.entities = l(this.entities, e.entities);
252
+ this.entities = _(this.entities, e.entities);
158
253
  let r = {};
159
254
  Object.entries(e.queries).forEach(([e, t]) => {
160
- let n = l(this.queryEntities[e] ?? [], t);
255
+ let n = _(this.queryEntities[e] ?? [], t);
161
256
  this.queryEntities[e] = n, r[e] = n;
162
257
  });
163
258
  let i = {
@@ -172,6 +267,13 @@ var u = class extends c {
172
267
  ]
173
268
  });
174
269
  },
270
+ emitEntityEvent(e, n, ...r) {
271
+ t.push({
272
+ entityId: e,
273
+ event: n,
274
+ args: r
275
+ });
276
+ },
175
277
  entityDied(e) {
176
278
  t.push({
177
279
  entityId: e,
@@ -197,7 +299,7 @@ var u = class extends c {
197
299
  onMessageTyped(e) {
198
300
  this.onmessage({ data: e });
199
301
  }
200
- }, d = "___main", f = class extends a {
302
+ }, y = "___main", b = class extends p {
201
303
  entities = [];
202
304
  options;
203
305
  worker;
@@ -216,7 +318,7 @@ var u = class extends c {
216
318
  this.removeEntity(e);
217
319
  }), e.entities.forEach((e) => {
218
320
  this.checkAddEntity(e);
219
- }), !t.forceMainThread && globalThis.Worker !== void 0 && globalThis.SharedArrayBuffer !== void 0 ? (this.worker = t.getWorker(), this.isWorkerThread = !0) : (this.worker = new u(t.updateFunction), this.isWorkerThread = !1), this.initWorker();
321
+ }), !t.forceMainThread && globalThis.Worker !== void 0 && globalThis.SharedArrayBuffer !== void 0 ? (this.worker = t.getWorker(), this.isWorkerThread = !0) : (this.worker = new v(t.updateFunction), this.isWorkerThread = !1), this.initWorker();
220
322
  }
221
323
  initWorker() {
222
324
  this.worker.onmessage = (e) => {
@@ -251,7 +353,7 @@ var u = class extends c {
251
353
  elapsedTime: e
252
354
  };
253
355
  this.addDataToWorld?.(t), this.isRunning = !0;
254
- let n = this.buildQueryDelta(d, this.options), r = {};
356
+ let n = this.buildQueryDelta(y, this.options), r = {};
255
357
  Object.entries(this.options.queries ?? {}).forEach(([e, t]) => {
256
358
  r[e] = this.buildQueryDelta(e, t);
257
359
  });
@@ -314,13 +416,13 @@ var u = class extends c {
314
416
  }
315
417
  checkAddEntity(e) {
316
418
  let t = this.matchesQuery(e, this.options);
317
- return this.updateEntityList(d, this.entities, e, t), Object.entries(this.options.queries ?? {}).forEach(([t, n]) => {
419
+ return this.updateEntityList(y, this.entities, e, t), Object.entries(this.options.queries ?? {}).forEach(([t, n]) => {
318
420
  let r = this.queryEntities[t] ?? (this.queryEntities[t] = []);
319
421
  this.updateEntityList(t, r, e, this.matchesQuery(e, n));
320
422
  }), t;
321
423
  }
322
424
  removeEntity(e) {
323
- this.updateEntityList(d, this.entities, e, !1), Object.entries(this.queryEntities).forEach(([t, n]) => {
425
+ this.updateEntityList(y, this.entities, e, !1), Object.entries(this.queryEntities).forEach(([t, n]) => {
324
426
  this.updateEntityList(t, n, e, !1);
325
427
  });
326
428
  }
@@ -330,7 +432,7 @@ var u = class extends c {
330
432
  destroy() {
331
433
  "terminate" in this.worker && this.worker.terminate();
332
434
  }
333
- }, p = 0, m = 1, h = {
435
+ }, x = 0, S = 1, C = {
334
436
  type: Uint32Array,
335
437
  size: 2,
336
438
  loadProperties: ["type", "isStatic"],
@@ -357,14 +459,14 @@ var u = class extends c {
357
459
  let t = {};
358
460
  return e.type && (t.type = e.type), e.dead && (t.dead = !0), t;
359
461
  }
360
- }, g = class t extends e {
462
+ }, w = class e extends d.default {
361
463
  static eidCounter = 1;
362
464
  eid;
363
465
  config;
364
466
  world;
365
467
  components = {};
366
- constructor(e, n) {
367
- super(), this.world = e, this.eid = t.eidCounter++, this.loadComponent("entity", n ?? {}, !1), n && this.load(n);
468
+ constructor(t, n) {
469
+ super(), this.world = t, this.eid = e.eidCounter++, this.loadComponent("entity", n ?? {}, !1), n && this.load(n);
368
470
  }
369
471
  loadComponent(e, t, n = !0) {
370
472
  let r = this.world.registry[e], i = r.memoryComponent, a = r.load(this, i, t);
@@ -420,7 +522,7 @@ var u = class extends c {
420
522
  i.loadInFinishLoading && i.loadProperties.some((e) => e in t) && this.loadComponent(r, e, !1);
421
523
  }
422
524
  }
423
- }, _ = class {
525
+ }, T = class {
424
526
  world;
425
527
  configs;
426
528
  constructor(e = {}) {
@@ -441,9 +543,9 @@ var u = class extends c {
441
543
  return this.world.addEntity(n, t);
442
544
  }
443
545
  createEntity(e) {
444
- return new g(this.world, e);
546
+ return new w(this.world, e);
445
547
  }
446
- }, v = n, y = class extends e {
548
+ }, E = t, D = class extends d.default {
447
549
  heap;
448
550
  registry;
449
551
  factory;
@@ -456,19 +558,19 @@ var u = class extends c {
456
558
  paused = !1;
457
559
  destroyed = !1;
458
560
  constructor(e, t = {}) {
459
- super(), this.heap = new r({ bufferSize: t.heapSize ?? v });
460
- let n = {
561
+ super(), this.heap = new n({ bufferSize: t.heapSize ?? E });
562
+ let r = {
461
563
  ...e,
462
- entity: h
463
- }, a = {};
464
- for (let e of Object.keys(n)) {
465
- let t = n[e];
466
- a[e] = {
564
+ entity: C
565
+ }, i = {};
566
+ for (let e of Object.keys(r)) {
567
+ let t = r[e];
568
+ i[e] = {
467
569
  ...t,
468
- memoryComponent: new i(this.heap, t.type, t.size)
570
+ memoryComponent: new f(this.heap, t.type, t.size)
469
571
  };
470
572
  }
471
- this.registry = a, this.factory = t.factory ?? new _(), this.factory.world = this;
573
+ this.registry = i, this.factory = t.factory ?? new T(), this.factory.world = this;
472
574
  }
473
575
  async init() {
474
576
  await Promise.all(this.systems.map((e) => e.init()).filter((e) => e instanceof Promise));
@@ -505,16 +607,24 @@ var u = class extends c {
505
607
  return this.entitiesByEid[e];
506
608
  }
507
609
  addSystem(e) {
508
- return this.systems.push(e), e;
610
+ return this.systems.push(e), this.emit("system-added", e), e;
509
611
  }
510
612
  addSystemIfNotExists(e) {
511
- this.systems.findIndex((t) => e.name === t.name) === -1 && this.systems.push(e);
613
+ this.systems.findIndex((t) => e.name === t.name) === -1 && (this.systems.push(e), this.emit("system-added", e));
512
614
  }
513
615
  removeSystem(e) {
514
616
  let t = this.systems.findIndex((t) => t.name === e);
515
- t !== -1 && this.systems.splice(t, 1);
617
+ if (t !== -1) {
618
+ let [e] = this.systems.splice(t, 1);
619
+ this.emit("system-removed", e);
620
+ }
516
621
  }
517
622
  update(e) {
623
+ this.emit("update-started", e);
624
+ let t = this.runUpdate(e);
625
+ return this.emit("update-finished", e), t;
626
+ }
627
+ runUpdate(e) {
518
628
  if (this.playerTime += e, this.paused) return {};
519
629
  e = this.timeScale * e, this.gameTime += e;
520
630
  let t = null;
@@ -542,12 +652,12 @@ var u = class extends c {
542
652
  }
543
653
  addEntityToComponentSystem(e, t) {
544
654
  this.systems.forEach((n) => {
545
- (n instanceof s && n.options.components?.includes(t) || n instanceof f && this.componentAffectsComponentSystem(n, t)) && n.checkAddEntity(e);
655
+ (n instanceof h && n.options.components?.includes(t) || n instanceof b && this.componentAffectsComponentSystem(n, t)) && n.checkAddEntity(e);
546
656
  });
547
657
  }
548
658
  removeEntityFromComponentSystem(e, t) {
549
659
  this.systems.forEach((n) => {
550
- n instanceof s && n.options.components?.includes(t) ? n.removeEntity(e) : n instanceof f && this.componentAffectsComponentSystem(n, t) && n.checkAddEntity(e);
660
+ n instanceof h && n.options.components?.includes(t) ? n.removeEntity(e) : n instanceof b && this.componentAffectsComponentSystem(n, t) && n.checkAddEntity(e);
551
661
  });
552
662
  }
553
663
  componentAffectsComponentSystem(e, t) {
@@ -562,36 +672,137 @@ var u = class extends c {
562
672
  };
563
673
  //#endregion
564
674
  //#region src/actions/kill-entity.ts
565
- function b(e) {
675
+ function O(e) {
566
676
  e.components.entity.dead = !0, e.emit("death");
567
677
  }
568
678
  //#endregion
569
679
  //#region src/actions/kill-entity-worker.ts
570
- function x(e, t, n) {
680
+ function k(e, t, n) {
571
681
  let r = t.entity;
572
682
  r && (r[0] = 1), n.entityDied(e);
573
683
  }
574
684
  //#endregion
575
685
  //#region src/actions/create-entity-worker.ts
576
- function S(e, t) {
686
+ function A(e, t) {
577
687
  t.createEntity(e);
578
688
  }
579
689
  //#endregion
690
+ //#region src/performance-timing.ts
691
+ var j = 1e3, M = {
692
+ avg: 0,
693
+ min: 0,
694
+ max: 0,
695
+ samples: 0
696
+ };
697
+ function N(e) {
698
+ if (!e.length) return { ...M };
699
+ let t = 0, n = Infinity, r = 0;
700
+ for (let i of e) t += i, i < n && (n = i), i > r && (r = i);
701
+ return {
702
+ avg: t / e.length,
703
+ min: n,
704
+ max: r,
705
+ samples: e.length
706
+ };
707
+ }
708
+ var P = class extends d.default {
709
+ world;
710
+ ticksBetweenUpdates;
711
+ stats = {
712
+ update: { ...M },
713
+ systems: [],
714
+ events: { ...M }
715
+ };
716
+ ticks = 0;
717
+ updateStart = 0;
718
+ updateTimes = [];
719
+ systemTimings = /* @__PURE__ */ new Map();
720
+ destroyed = !1;
721
+ constructor(e, t = {}) {
722
+ super(), this.world = e, this.ticksBetweenUpdates = t.ticksBetweenUpdates ?? 1e3, e.on("update-started", this.onUpdateStarted), e.on("update-finished", this.onUpdateFinished), e.on("system-added", this.onSystemAdded), e.on("system-removed", this.onSystemRemoved), e.systems.forEach((e) => this.trackSystem(e));
723
+ }
724
+ getSystemStats(e) {
725
+ return this.stats.systems.find((t) => t.name === e);
726
+ }
727
+ reset() {
728
+ this.ticks = 0, this.updateTimes = [], this.systemTimings.forEach((e) => {
729
+ e.run = [], e.events = [], e.eventStart = -1;
730
+ }), this.stats = {
731
+ update: { ...M },
732
+ systems: [],
733
+ events: { ...M }
734
+ };
735
+ }
736
+ destroy() {
737
+ this.destroyed || (this.destroyed = !0, this.world.off("update-started", this.onUpdateStarted), this.world.off("update-finished", this.onUpdateFinished), this.world.off("system-added", this.onSystemAdded), this.world.off("system-removed", this.onSystemRemoved), Array.from(this.systemTimings.keys()).forEach((e) => this.untrackSystem(e)), this.removeAllListeners());
738
+ }
739
+ onUpdateStarted = () => {
740
+ this.updateStart = performance.now();
741
+ };
742
+ onUpdateFinished = (e) => {
743
+ this.world.paused || (this.updateTimes.push(performance.now() - this.updateStart), this.ticks += e, this.ticks >= this.ticksBetweenUpdates && this.recalculate());
744
+ };
745
+ onSystemAdded = (e) => {
746
+ this.trackSystem(e);
747
+ };
748
+ onSystemRemoved = (e) => {
749
+ this.untrackSystem(e.name);
750
+ };
751
+ trackSystem(e) {
752
+ if (this.systemTimings.has(e.name)) return;
753
+ let t = {
754
+ run: [],
755
+ events: [],
756
+ eventStart: -1,
757
+ onRunFinished: (e) => {
758
+ t.run.push(e), t.eventStart = performance.now();
759
+ },
760
+ onEventsFinished: () => {
761
+ t.eventStart < 0 || (t.events.push(performance.now() - t.eventStart), t.eventStart = -1);
762
+ }
763
+ };
764
+ this.world.on(`system-${e.name}-worker-finished`, t.onRunFinished), this.world.on(`system-${e.name}-worker-events-finished`, t.onEventsFinished), this.systemTimings.set(e.name, t);
765
+ }
766
+ untrackSystem(e) {
767
+ let t = this.systemTimings.get(e);
768
+ t && (this.world.off(`system-${e}-worker-finished`, t.onRunFinished), this.world.off(`system-${e}-worker-events-finished`, t.onEventsFinished), this.systemTimings.delete(e));
769
+ }
770
+ recalculate() {
771
+ let e = this.world.systems.map((e) => {
772
+ let t = this.systemTimings.get(e.name);
773
+ return {
774
+ name: e.name,
775
+ run: N(t?.run ?? []),
776
+ events: N(t?.events ?? [])
777
+ };
778
+ }), t = { ...M };
779
+ e.forEach((e) => {
780
+ t.avg += e.events.avg, t.min += e.events.min, t.max += e.events.max, t.samples += e.events.samples;
781
+ }), this.stats = {
782
+ update: N(this.updateTimes),
783
+ systems: e,
784
+ events: t
785
+ }, this.ticks = 0, this.updateTimes = [], this.systemTimings.forEach((e) => {
786
+ e.run = [], e.events = [];
787
+ }), this.emit("stats-updated", this.stats);
788
+ }
789
+ };
790
+ //#endregion
580
791
  //#region src/systems/workers/create-component-worker.ts
581
- function C(e, t) {
792
+ function F(e, t) {
582
793
  let n = [], r = {};
583
794
  e.onmessage = function(i) {
584
795
  let a = i.data;
585
- if (a.type === "init") w(e, { type: "loaded" });
796
+ if (a.type === "init") I(e, { type: "loaded" });
586
797
  else if (a.type === "run") {
587
798
  let i = performance.now(), o = [], s = [];
588
- n = l(n, a.entities);
799
+ n = _(n, a.entities);
589
800
  let c = {};
590
801
  Object.entries(a.queries).forEach(([e, t]) => {
591
- let n = l(r[e] ?? [], t);
802
+ let n = _(r[e] ?? [], t);
592
803
  r[e] = n, c[e] = n;
593
804
  });
594
- let u = {
805
+ let l = {
595
806
  entityComponentChanged(e, t, n, r) {
596
807
  o.push({
597
808
  entityId: e,
@@ -603,6 +814,13 @@ function C(e, t) {
603
814
  ]
604
815
  });
605
816
  },
817
+ emitEntityEvent(e, t, ...n) {
818
+ o.push({
819
+ entityId: e,
820
+ event: t,
821
+ args: n
822
+ });
823
+ },
606
824
  entityDied(e) {
607
825
  o.push({
608
826
  entityId: e,
@@ -614,11 +832,11 @@ function C(e, t) {
614
832
  s.push(e);
615
833
  }
616
834
  };
617
- t.preRun && t.preRun(a.world, n, c, u), n.forEach((e) => {
618
- t(a.world, e.entityId, e.components, c, u);
835
+ t.preRun && t.preRun(a.world, n, c, l), n.forEach((e) => {
836
+ t(a.world, e.entityId, e.components, c, l);
619
837
  }), t.entityRemoved && a.entities.removed.forEach((e) => {
620
- t.entityRemoved(a.world, e, u);
621
- }), w(e, {
838
+ t.entityRemoved(a.world, e, l);
839
+ }), I(e, {
622
840
  type: "run-complete",
623
841
  runTime: performance.now() - i,
624
842
  events: o,
@@ -627,10 +845,10 @@ function C(e, t) {
627
845
  }
628
846
  };
629
847
  }
630
- function w(e, t) {
848
+ function I(e, t) {
631
849
  e.postMessage(t);
632
850
  }
633
851
  //#endregion
634
- export { g as BaseEntity, y as BaseWorld, f as ComponentSystem, u as ComponentWebWorker, p as DEAD_INDEX, _ as EntityFactory, s as EntitySystem, o as IterableSystem, i as MemoryComponent, m as STATIC_INDEX, a as System, c as WebWorker, C as createComponentWorker, S as createEntityWorker, h as entityDefinition, b as killEntity, x as killEntityWorker };
852
+ export { w as BaseEntity, D as BaseWorld, b as ComponentSystem, v as ComponentWebWorker, x as DEAD_INDEX, j as DEFAULT_TICKS_BETWEEN_UPDATES, T as EntityFactory, h as EntitySystem, m as IterableSystem, f as MemoryComponent, P as PerformanceTiming, S as STATIC_INDEX, p as System, g as WebWorker, F as createComponentWorker, A as createEntityWorker, C as entityDefinition, O as killEntity, k as killEntityWorker };
635
853
 
636
854
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/memory-component.ts","../src/systems/system.ts","../src/systems/iterable-system.ts","../src/systems/entity-system.ts","../src/systems/workers/web-worker.ts","../src/systems/workers/apply-query-delta.ts","../src/systems/workers/component-web-worker.ts","../src/systems/component-system.ts","../src/entity-component.ts","../src/entity.ts","../src/entity-factory.ts","../src/world.ts","../src/actions/kill-entity.ts","../src/actions/kill-entity-worker.ts","../src/actions/create-entity-worker.ts","../src/systems/workers/create-component-worker.ts"],"sourcesContent":["import { LocalPool, type MemoryHeap, type TypedArrayConstructor } from '@daneren2005/shared-memory-objects';\n\n// The typed arrays a MemoryComponent can be backed by. Kept narrower than the package's TypedArray\n// union on purpose since these are the only types we allocate blocks of.\nexport type ComponentTypedArray = Uint32Array | Int32Array | Float32Array | Float64Array;\n\n// A pool of same sized blocks living inside a shared MemoryHeap. Each component instance owns one\n// block (referenced by its index) so component data can be shared with worker threads.\nexport default class MemoryComponent<T extends ComponentTypedArray = ComponentTypedArray> {\n\theap: MemoryHeap;\n\tpool: LocalPool<T>;\n\n\tconstructor(heap: MemoryHeap, type: TypedArrayConstructor<T>, dataLength: number) {\n\t\tthis.heap = heap;\n\t\tthis.pool = new LocalPool(heap, {\n\t\t\ttype,\n\t\t\tdataLength,\n\t\t});\n\t}\n\n\tget length() {\n\t\treturn this.pool.length;\n\t}\n\tget rawLength() {\n\t\treturn this.pool.bufferLength;\n\t}\n\n\tcreate(values: Array<number>): number {\n\t\treturn this.pool.push(values);\n\t}\n\n\tgetBlock(index: number): T {\n\t\treturn this.pool.at(index);\n\t}\n\tget(index: number, dataIndex: number): number {\n\t\treturn this.pool.get(index, dataIndex);\n\t}\n\tset(index: number, dataIndex: number, value: number) {\n\t\tlet array = this.pool.at(index);\n\t\tarray[dataIndex] = value;\n\t}\n\n\tdelete(index: number) {\n\t\tthis.pool.deleteIndex(index);\n\t}\n\tclear() {\n\t\tthis.pool.clear();\n\t}\n}\n","import type BaseWorld from '../world';\nimport type { ComponentDefinitionMap, ComponentMap } from '../component-definition';\n\n// Base system: runs on an optional fixed timestep (deltaBetweenRuns) and is driven by BaseWorld#update.\n// Systems only care about the component map `C`, not the World's registry (`R`) or config (`Cfg`), so they\n// reference the World through the widened `ComponentDefinitionMap` and let `Cfg` default.\nexport default abstract class System<C extends ComponentMap = ComponentMap> {\n\tworld: BaseWorld<ComponentDefinitionMap, C>;\n\tname: string;\n\tcurrentDelta: number = 0;\n\tdeltaBetweenRuns: number;\n\tfirstRun: boolean;\n\n\tconstructor(world: BaseWorld<ComponentDefinitionMap, C>, options: SystemConfig = { name: 'System' }) {\n\t\tthis.name = options.name;\n\t\tthis.world = world;\n\n\t\tthis.deltaBetweenRuns = options.deltaBetweenRuns ?? 0;\n\t\tthis.firstRun = options.firstRun !== undefined ? options.firstRun : false;\n\t}\n\n\tinit(): void | Promise<void> {}\n\n\tclear() {\n\t\tthis.currentDelta = 0;\n\t}\n\tfinishLoading() {}\n\n\tupdate(elapsedTime: number): boolean {\n\t\tthis.currentDelta += elapsedTime;\n\n\t\tif(this.currentDelta >= this.deltaBetweenRuns || this.firstRun) {\n\t\t\tlet leftOverDelta = 0;\n\t\t\tif(this.deltaBetweenRuns > 0) {\n\t\t\t\t// Without this if we take 100ms to run (ie: IterableSystem across multiple frames) then we will end up\n\t\t\t\t// actually running this in 300ms total instead of again in 100ms for a total of 200ms\n\t\t\t\t// With firstRun this ends up calling run(0) the first time - this makes it so things like events are\n\t\t\t\t// will trigger on the second instead of triggering a 1 second timer at 1.0166 seconds\n\t\t\t\tleftOverDelta = this.currentDelta % this.deltaBetweenRuns;\n\t\t\t}\n\n\t\t\tthis.run(this.currentDelta - leftOverDelta);\n\t\t\tthis.currentDelta = leftOverDelta;\n\t\t\tthis.firstRun = false;\n\n\t\t\treturn true;\n\t\t} else {\n\t\t\treturn false;\n\t\t}\n\t}\n\tabstract run(elapsedTime: number): void;\n\n\tshouldRun(): boolean {\n\t\treturn true;\n\t}\n\n\tdestroy() {}\n}\n\nexport interface SystemConfig {\n\tname: string\n\tdeltaBetweenRuns?: number\n\t// Defaults to false\n\tfirstRun?: boolean\n}\n","import type BaseWorld from '../world';\nimport type { ComponentDefinitionMap, ComponentMap } from '../component-definition';\nimport System, { type SystemConfig } from './system';\n\n// A system that iterates a list of instances, spreading the work across multiple frames if a single\n// pass would exceed maxMsPerFrame.\nexport default abstract class IterableSystem<C extends ComponentMap, T> extends System<C> {\n\tremainingInstancesToRun: Array<T> = [];\n\tremainingInstancesStartTime: number | null = null;\n\titerationsPerCheck: number;\n\tmaxMsPerFrame: number;\n\n\tconstructor(world: BaseWorld<ComponentDefinitionMap, C>, options: IterableSystemConfig) {\n\t\tsuper(world, options);\n\n\t\tthis.iterationsPerCheck = options.iterationsPerCheck ?? 1;\n\t\tthis.maxMsPerFrame = options.maxMsPerFrame ?? 10;\n\t}\n\n\tclear() {\n\t\tsuper.clear();\n\n\t\tthis.remainingInstancesToRun = [];\n\t\tthis.remainingInstancesStartTime = null;\n\t}\n\n\tupdate(elapsedTime: number): boolean {\n\t\tif(this.remainingInstancesToRun.length) {\n\t\t\tthis.runIterables(this.remainingInstancesToRun, this.remainingInstancesStartTime ?? 0);\n\t\t\tthis.currentDelta += elapsedTime;\n\n\t\t\treturn true;\n\t\t} else {\n\t\t\treturn super.update(elapsedTime);\n\t\t}\n\t}\n\trun(elapsedTime: number): void {\n\t\tlet iterables = this.getIterables();\n\t\tthis.runIterables(iterables, elapsedTime);\n\t}\n\trunIterables(iterables: Array<T>, elapsedTime: number) {\n\t\tlet started = performance.now();\n\t\tthis.beforeRunIterables();\n\t\tfor(let i = 0; i < iterables.length; i++) {\n\t\t\tthis.updateIterable(iterables[i], elapsedTime);\n\n\t\t\tif(i % this.iterationsPerCheck === 0) {\n\t\t\t\tlet now = performance.now();\n\t\t\t\tif(now - started >= this.maxMsPerFrame) {\n\t\t\t\t\tthis.remainingInstancesToRun = iterables.slice(i + 1);\n\t\t\t\t\tthis.remainingInstancesStartTime = elapsedTime;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthis.remainingInstancesToRun = [];\n\t\tthis.remainingInstancesStartTime = null;\n\t}\n\tbeforeRunIterables() {}\n\n\tabstract getIterables(): Array<T>;\n\tabstract updateIterable(iterable: T, elapsedTime: number): void;\n}\n\nexport interface IterableSystemConfig extends SystemConfig {\n\titerationsPerCheck?: number\n\tmaxMsPerFrame?: number\n}\n","import type BaseWorld from '../world';\nimport type BaseEntity from '../entity';\nimport type { ComponentDefinitionMap, ComponentMap } from '../component-definition';\nimport IterableSystem, { type IterableSystemConfig } from './iterable-system';\n\n// Iterates the entities that own a given set of components on the main thread. Entities are added\n// and removed automatically as the world emits entity-added / entity-removed / component changes.\nexport default abstract class EntitySystem<C extends ComponentMap, T extends BaseEntity<C> = BaseEntity<C>> extends IterableSystem<C, T> {\n\tentities: Array<T> = [];\n\toptions: EntitySystemConfig<C>;\n\n\tconstructor(world: BaseWorld<ComponentDefinitionMap, C>, options: EntitySystemConfig<C> = { name: 'EntitySystem' }) {\n\t\tif(!options.iterationsPerCheck) {\n\t\t\toptions.iterationsPerCheck = 10;\n\t\t}\n\t\tif(!options.maxMsPerFrame) {\n\t\t\toptions.maxMsPerFrame = 4;\n\t\t}\n\n\t\tsuper(world, options);\n\t\tthis.options = options;\n\n\t\tworld.on('entity-added', (entity: BaseEntity<C>) => {\n\t\t\tif(this.checkAddEntity(entity) && this.options.updateEntityOnAdd) {\n\t\t\t\tthis.updateEntity(entity as T, 0);\n\t\t\t}\n\t\t});\n\t\tworld.on('entity-removed', (entity: BaseEntity<C>) => {\n\t\t\tthis.removeEntity(entity);\n\t\t});\n\n\t\tworld.entities.forEach(entity => {\n\t\t\tthis.checkAddEntity(entity);\n\t\t});\n\t}\n\n\tgetIterables(): Array<T> {\n\t\treturn this.entities.filter(entity => !entity.components.entity.dead);\n\t}\n\tupdateIterable(entity: T, elapsedTime: number): void {\n\t\tif(entity.components.entity.dead) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.updateEntity(entity, elapsedTime);\n\t}\n\tfilterEntity(entity: BaseEntity<C>): boolean {\n\t\treturn !entity.components.entity.isStatic;\n\t}\n\tisEntityInSystem(entity: BaseEntity<C>) {\n\t\treturn this.entities.indexOf(entity as T) !== -1;\n\t}\n\tabstract updateEntity(entity: T, elapsedTime: number): void;\n\n\tcheckAddEntity(entity: BaseEntity<C>): boolean {\n\t\tif(this.options.components && this.options.components.filter(component => !!entity.components[component]).length !== this.options.components.length) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif(this.filterEntity(entity)) {\n\t\t\tthis.entities.push(entity as T);\n\t\t\treturn true;\n\t\t} else {\n\t\t\treturn false;\n\t\t}\n\t}\n\tremoveEntity(entity: BaseEntity<C>) {\n\t\tlet index = this.entities.indexOf(entity as T);\n\t\tif(index !== -1) {\n\t\t\tthis.entities.splice(index, 1);\n\t\t}\n\t}\n\n\tshouldRun(): boolean {\n\t\treturn this.entities.length > 0;\n\t}\n}\n\nexport interface EntitySystemConfig<C extends ComponentMap> extends IterableSystemConfig {\n\tcomponents?: Array<keyof C>\n\tupdateEntityOnAdd?: boolean\n}\n","// Minimal Worker-like base used for the main-thread fallback when real Web Workers / SharedArrayBuffer\n// are unavailable. Params are intentionally loose since this is the (serialization) boundary that\n// mirrors the DOM Worker interface.\nexport default abstract class WebWorker {\n\tabstract postMessage(message: any): void | Promise<void>;\n\tonmessage(message: any, transferrables: Array<any> = []): void {\n\n\t}\n}\n","import type { EntityUpdateComponents, QueryDelta, UpdateEntityConfigObject } from '../component-system';\n\n// A worker keeps one persistent list per query and applies the delta each run carries: drop the entities that\n// left, then upsert the ones that joined (or whose component blocks changed). Because the main thread only\n// sends changes, a steady-state run - where membership is unchanged - carries empty arrays and this returns the\n// existing list untouched with no allocation, which is the whole point of the delta protocol: we stop\n// re-transmitting (and re-cloning) every entity id every single frame.\n//\n// The list is returned (rather than mutated in place) because removals rebuild it via filter; callers must store\n// the returned reference back as their persistent list.\nexport function applyQueryDelta<T extends EntityUpdateComponents>(\n\tlist: Array<UpdateEntityConfigObject<T>>,\n\tdelta: QueryDelta<T>,\n): Array<UpdateEntityConfigObject<T>> {\n\tif(delta.removed.length) {\n\t\tconst removedSet = new Set(delta.removed);\n\t\tlist = list.filter(entry => !removedSet.has(entry.entityId));\n\t}\n\n\tif(delta.added.length) {\n\t\t// Map existing members so a re-added entity (its component set changed) replaces its entry in place\n\t\t// instead of being duplicated; genuinely new entities are appended.\n\t\tconst indexByEid = new Map<number, number>();\n\t\tlist.forEach((entry, index) => indexByEid.set(entry.entityId, index));\n\n\t\tfor(let entity of delta.added) {\n\t\t\tconst existing = indexByEid.get(entity.entityId);\n\t\t\tif(existing !== undefined) {\n\t\t\t\tlist[existing] = entity;\n\t\t\t} else {\n\t\t\t\tindexByEid.set(entity.entityId, list.length);\n\t\t\t\tlist.push(entity);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn list;\n}\n","import WebWorker from './web-worker';\nimport { applyQueryDelta } from './apply-query-delta';\nimport type ComponentWorkerMessage from './component-worker-message';\nimport type { ComponentMap } from '../../component-definition';\nimport type { ComponentSystemCallbacks, ComponentSystemWorld, EntityQueryComponents, EntityUpdateComponents, EntityUpdateFunction, QueryDelta, UpdateEntityConfigObject } from '../component-system';\n\n// Main-thread fallback that runs the update function synchronously when real Web Workers /\n// SharedArrayBuffer are unavailable. It runs in-process but still keeps persistent per-query lists and applies\n// the same membership deltas the real worker does, so both backends stay behavior-identical.\nexport default class ComponentWebWorker<C extends ComponentMap, T extends EntityUpdateComponents<C>, W extends ComponentSystemWorld = ComponentSystemWorld> extends WebWorker {\n\tprivate updateFunction: EntityUpdateFunction<C, T, W>;\n\tprivate entities: Array<UpdateEntityConfigObject<T>> = [];\n\tprivate queryEntities: { [key: string]: Array<UpdateEntityConfigObject<T>> } = {};\n\n\tconstructor(updateFunction: EntityUpdateFunction<C, T, W>) {\n\t\tsuper();\n\t\tthis.updateFunction = updateFunction;\n\t}\n\n\tpostMessage(message: ComponentWorkerMessage<W>): void {\n\t\tif(message.type === 'init') {\n\t\t\tthis.onMessageTyped({\n\t\t\t\ttype: 'loaded',\n\t\t\t});\n\t\t} else if(message.type === 'run') {\n\t\t\tlet entityEvents: Array<{ entityId: number, event: string, args: Array<any> }> = [];\n\t\t\tlet createdEntities: Array<Record<string, unknown>> = [];\n\n\t\t\tthis.entities = applyQueryDelta(this.entities, message.entities as QueryDelta<T>);\n\n\t\t\tlet queries: EntityQueryComponents<C> = {};\n\t\t\tObject.entries(message.queries).forEach(([queryKey, delta]) => {\n\t\t\t\tconst list = applyQueryDelta(this.queryEntities[queryKey] ?? [], delta as QueryDelta<T>);\n\t\t\t\tthis.queryEntities[queryKey] = list;\n\t\t\t\tqueries[queryKey] = list;\n\t\t\t});\n\n\t\t\tlet callbacks: ComponentSystemCallbacks<C> = {\n\t\t\t\tentityComponentChanged<K extends keyof C, P extends keyof C[K]>(entityId: number, componentName: K, prop: P, value: C[K][P]) {\n\t\t\t\t\tentityEvents.push({\n\t\t\t\t\t\tentityId,\n\t\t\t\t\t\tevent: 'component-property-updated',\n\t\t\t\t\t\targs: [componentName, prop, value],\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\tentityDied(entityId: number) {\n\t\t\t\t\tentityEvents.push({\n\t\t\t\t\t\tentityId,\n\t\t\t\t\t\tevent: 'death',\n\t\t\t\t\t\targs: [],\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\tcreateEntity(config: Record<string, unknown>) {\n\t\t\t\t\tcreatedEntities.push(config);\n\t\t\t\t},\n\t\t\t};\n\t\t\tif(this.updateFunction.preRun) {\n\t\t\t\tthis.updateFunction.preRun(message.world, this.entities, queries, callbacks);\n\t\t\t}\n\t\t\tthis.entities.forEach(entity => {\n\t\t\t\tthis.updateFunction(message.world, entity.entityId, entity.components, queries, callbacks);\n\t\t\t});\n\n\t\t\tif(this.updateFunction.entityRemoved) {\n\t\t\t\tfor(let entityId of message.entities.removed) {\n\t\t\t\t\tthis.updateFunction.entityRemoved(message.world, entityId, callbacks);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.onMessageTyped({\n\t\t\t\ttype: 'run-complete',\n\t\t\t\trunTime: 0,\n\t\t\t\tevents: entityEvents,\n\t\t\t\tcreated: createdEntities,\n\t\t\t});\n\t\t}\n\t}\n\n\tonMessageTyped(message: ComponentWorkerMessage<W>) {\n\t\tthis.onmessage({\n\t\t\tdata: message,\n\t\t});\n\t}\n}\n","import type BaseWorld from '../world';\nimport type BaseEntity from '../entity';\nimport type { BaseComponent, ComponentDefinitionMap, ComponentMap, RegisteredComponentRegistry } from '../component-definition';\nimport type { ComponentTypedArray } from '../memory-component';\nimport System, { type SystemConfig } from './system';\nimport type ComponentWorkerMessage from './workers/component-worker-message';\nimport ComponentWebWorker from './workers/component-web-worker';\n\nconst MAIN_QUERY_NAME = '___main';\n\n// Runs an update function over the raw shared-memory blocks of its matched entities, ideally on a\n// separate thread. When Web Workers + SharedArrayBuffer are available the work happens on `getWorker()`;\n// otherwise it falls back to running synchronously on the main thread via ComponentWebWorker.\n//\n// This is deliberately free of any game concepts (no factions, fog of war, position, etc). Games\n// inject whatever extra per-run data they need through `addDataToWorld`.\nexport default abstract class ComponentSystem<\n\tC extends ComponentMap,\n\tT extends EntityUpdateComponents<C>,\n\tW extends ComponentSystemWorld = ComponentSystemWorld,\n> extends System<C> {\n\tentities: Array<BaseEntity<C>> = [];\n\toptions: ComponentSystemConfig<C, T, W>;\n\n\tworker: Worker | ComponentWebWorker<C, T, W>;\n\tisWorkerThread: boolean;\n\n\tprivate loaded = false;\n\tprivate loadingPromise: { promise: Promise<void>, resolve: (value: void | PromiseLike<void>) => void } | null = null;\n\tprivate isRunning = false;\n\tprivate queryEntities: { [key: string]: Array<BaseEntity<C>> } = {};\n\t// Per-query membership changes accumulated since the last run(). Each run() flushes these to the worker as a\n\t// delta - added entities carry their component blocks, removed carry just their eid - so a steady-state run\n\t// (unchanged membership) sends empty arrays instead of re-transmitting every entity id every single frame.\n\t// The worker keeps its own persistent list and applies these deltas to it (see applyQueryDelta).\n\tprivate queryDeltas: { [key: string]: MembershipDelta<C> } = {};\n\n\t// Optional hook for subclasses to attach extra data to the world object sent to the worker. Subclasses\n\t// declare the concrete shape through `W` and fill in its fields here.\n\taddDataToWorld?(world: W): void;\n\n\tconstructor(world: BaseWorld<ComponentDefinitionMap, C>, options: ComponentSystemConfig<C, T, W>) {\n\t\tsuper(world, options);\n\t\tthis.options = options;\n\t\tObject.keys(this.options.queries ?? {}).forEach(queryName => {\n\t\t\tthis.queryEntities[queryName] = [];\n\t\t});\n\n\t\tworld.on('entity-added', (entity: BaseEntity<C>) => {\n\t\t\tthis.checkAddEntity(entity);\n\t\t});\n\t\tworld.on('entity-removed', (entity: BaseEntity<C>) => {\n\t\t\tthis.removeEntity(entity);\n\t\t});\n\n\t\tworld.entities.forEach(entity => {\n\t\t\tthis.checkAddEntity(entity);\n\t\t});\n\n\t\tif(!options.forceMainThread && typeof globalThis.Worker !== 'undefined' && typeof globalThis.SharedArrayBuffer !== 'undefined') {\n\t\t\tthis.worker = options.getWorker();\n\t\t\tthis.isWorkerThread = true;\n\t\t} else {\n\t\t\tthis.worker = new ComponentWebWorker(options.updateFunction);\n\t\t\tthis.isWorkerThread = false;\n\t\t}\n\n\t\tthis.initWorker();\n\t}\n\n\tprivate initWorker() {\n\t\tthis.worker.onmessage = (e: MessageEvent) => {\n\t\t\tlet message = e.data as ComponentWorkerMessage;\n\t\t\tif(message.type === 'loaded') {\n\t\t\t\tthis.loaded = true;\n\t\t\t\tif(this.loadingPromise) {\n\t\t\t\t\tthis.loadingPromise.resolve();\n\t\t\t\t\tthis.loadingPromise = null;\n\t\t\t\t}\n\t\t\t} else if(message.type === 'run-complete') {\n\t\t\t\tthis.isRunning = false;\n\t\t\t\tif(this.isWorkerThread) {\n\t\t\t\t\tthis.world.emit(`system-${this.name}-worker-finished`, message.runTime);\n\t\t\t\t}\n\n\t\t\t\tmessage.events.forEach(event => {\n\t\t\t\t\t// A system can report events (deaths, component changes) for entities it only knows through a\n\t\t\t\t\t// sub-query - e.g. a collision system that kills a station it found in a spatial query but that\n\t\t\t\t\t// isn't in its main query. Look the entity up on the world so those still route even when the\n\t\t\t\t\t// entity was never part of this system's main query cache.\n\t\t\t\t\tconst entity = this.world.getEntityByEid(event.entityId);\n\t\t\t\t\tif(!entity) {\n\t\t\t\t\t\tconsole.warn(`Could not find entity with id ${event.entityId}`);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tentity.emit(event.event, ...event.args);\n\t\t\t\t});\n\n\t\t\t\t// Fulfill any creation requests after deaths, so an entity created this run isn't immediately removed.\n\t\t\t\t// loadEntity runs the full factory expansion + finishLoading and emits `entity-added`, so every system\n\t\t\t\t// (including this one) picks the new entity up on the next run.\n\t\t\t\tmessage.created.forEach(config => {\n\t\t\t\t\tthis.world.loadEntity(config);\n\t\t\t\t});\n\n\t\t\t\tif(this.isWorkerThread) {\n\t\t\t\t\tthis.world.emit(`system-${this.name}-worker-events-finished`, message.runTime);\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\n\t\tconst message: ComponentWorkerMessage = {\n\t\t\ttype: 'init',\n\t\t};\n\n\t\tthis.worker.postMessage(message);\n\t}\n\n\tinit(): Promise<void> | void {\n\t\tif(this.loaded) {\n\t\t\treturn;\n\t\t} else if(this.loadingPromise) {\n\t\t\treturn this.loadingPromise.promise;\n\t\t}\n\n\t\tlet { promise, resolve } = Promise.withResolvers<void>();\n\t\tthis.loadingPromise = {\n\t\t\tpromise,\n\t\t\tresolve,\n\t\t};\n\t\treturn promise;\n\t}\n\n\tupdate(elapsedTime: number): boolean {\n\t\t// Only run worker if the last run completed already\n\t\tif(this.isRunning) {\n\t\t\tthis.currentDelta += elapsedTime;\n\n\t\t\treturn false;\n\t\t} else {\n\t\t\treturn super.update(elapsedTime);\n\t\t}\n\t}\n\trun(elapsedTime: number): void {\n\t\t// gameTime + elapsedTime are the only fields the base guarantees; addDataToWorld fills in the rest of `W`,\n\t\t// so the literal is built as the base shape and widened to W for that hook to complete.\n\t\tconst world = {\n\t\t\tgameTime: this.world.gameTime,\n\t\t\telapsedTime,\n\t\t} as W;\n\t\tthis.addDataToWorld?.(world);\n\n\t\tthis.isRunning = true;\n\t\tlet entities = this.buildQueryDelta(MAIN_QUERY_NAME, this.options);\n\t\tlet queries: { [key: string]: QueryDelta<T> } = {};\n\t\tObject.entries(this.options.queries ?? {}).forEach(([queryKey, query]) => {\n\t\t\tqueries[queryKey] = this.buildQueryDelta(queryKey, query);\n\t\t});\n\t\tlet message: ComponentWorkerMessage<W> = {\n\t\t\ttype: 'run',\n\t\t\tworld,\n\t\t\tentities,\n\t\t\tqueries,\n\t\t};\n\t\tthis.worker.postMessage(message);\n\t}\n\n\t// Flushes the pending membership changes for a query into the delta the worker applies to its persistent\n\t// list. Added entities are resolved to their shared-memory component blocks here (the only per-frame cost\n\t// left, and only for entities that actually joined/changed); removed entities are sent as bare eids. The\n\t// buffers are reset so the next run only carries what changed since this one.\n\tprivate buildQueryDelta(queryName: string, query: ComponentSystemQuery<C>): QueryDelta<T> {\n\t\tconst delta = this.getQueryDelta(queryName);\n\t\tconst added = delta.added.map(entity => ({\n\t\t\tentityId: entity.eid,\n\t\t\tcomponents: this.buildComponents(entity, query),\n\t\t}));\n\t\tconst removed = delta.removed;\n\t\tthis.queryDeltas[queryName] = { added: [], removed: [] };\n\n\t\treturn { added, removed };\n\t}\n\tprivate buildComponents(entity: BaseEntity<C>, query: ComponentSystemQuery<C>): T {\n\t\tconst components = {} as T;\n\t\tconst registry = this.world.registry as RegisteredComponentRegistry<C>;\n\t\t[\n\t\t\t...query.required,\n\t\t\t...query.optional ?? [],\n\t\t].forEach(componentName => {\n\t\t\tconst component = entity.components[componentName];\n\t\t\tconst memoryComponent = registry[componentName].memoryComponent;\n\t\t\tif(component && memoryComponent) {\n\t\t\t\tcomponents[componentName] = memoryComponent.getBlock(component.index) as T[typeof componentName];\n\t\t\t}\n\t\t});\n\n\t\treturn components;\n\t}\n\n\tisEntityInSystem(entity: BaseEntity<C>) {\n\t\treturn this.entities.indexOf(entity) !== -1;\n\t}\n\tprivate matchesQuery(entity: BaseEntity<C>, query: ComponentSystemQuery<C>): boolean {\n\t\tif(entity.components.entity.dead) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif(query.required.find(component => !entity.components[component])) {\n\t\t\treturn false;\n\t\t}\n\t\tif(query.not?.find(component => !!entity.components[component])) {\n\t\t\treturn false;\n\t\t}\n\t\tif(query.filter && !query.filter(entity)) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t}\n\tprivate getQueryDelta(queryName: string): MembershipDelta<C> {\n\t\tlet delta = this.queryDeltas[queryName];\n\t\tif(!delta) {\n\t\t\tdelta = this.queryDeltas[queryName] = { added: [], removed: [] };\n\t\t}\n\n\t\treturn delta;\n\t}\n\t// Records that an entity now belongs to a query, so the next run() sends its (possibly changed) component\n\t// blocks. An entity queued for removal this same frame is un-queued instead: the worker never learned it\n\t// left, so the net effect is a re-send of fresh components rather than a remove+add churn.\n\tprivate markAdded(delta: MembershipDelta<C>, entity: BaseEntity<C>) {\n\t\tconst removedIndex = delta.removed.indexOf(entity.eid);\n\t\tif(removedIndex !== -1) {\n\t\t\tdelta.removed.splice(removedIndex, 1);\n\t\t}\n\t\tif(delta.added.indexOf(entity) === -1) {\n\t\t\tdelta.added.push(entity);\n\t\t}\n\t}\n\t// Records that an entity left a query. If it was only queued to be added this same frame (never sent to the\n\t// worker), we just drop the pending add - the worker never knew about it, so there is nothing to remove.\n\tprivate markRemoved(delta: MembershipDelta<C>, entity: BaseEntity<C>) {\n\t\tconst addedIndex = delta.added.indexOf(entity);\n\t\tif(addedIndex !== -1) {\n\t\t\tdelta.added.splice(addedIndex, 1);\n\t\t\treturn;\n\t\t}\n\t\tif(delta.removed.indexOf(entity.eid) === -1) {\n\t\t\tdelta.removed.push(entity.eid);\n\t\t}\n\t}\n\tprivate updateEntityList(queryName: string, list: Array<BaseEntity<C>>, entity: BaseEntity<C>, shouldInclude: boolean) {\n\t\tconst index = list.indexOf(entity);\n\t\tconst delta = this.getQueryDelta(queryName);\n\t\tif(shouldInclude) {\n\t\t\tif(index === -1) {\n\t\t\t\tlist.push(entity);\n\t\t\t}\n\t\t\t// Always (re)queue the component blocks: the entity either just joined or a relevant component was\n\t\t\t// added/removed, so the blocks the worker holds for it may be stale.\n\t\t\tthis.markAdded(delta, entity);\n\t\t} else if(index !== -1) {\n\t\t\tlist.splice(index, 1);\n\t\t\tthis.markRemoved(delta, entity);\n\t\t}\n\t}\n\n\tcheckAddEntity(entity: BaseEntity<C>): boolean {\n\t\tconst shouldAddToMain = this.matchesQuery(entity, this.options);\n\t\tthis.updateEntityList(MAIN_QUERY_NAME, this.entities, entity, shouldAddToMain);\n\n\t\tObject.entries(this.options.queries ?? {}).forEach(([queryName, query]) => {\n\t\t\tconst queryList = this.queryEntities[queryName] ?? (this.queryEntities[queryName] = []);\n\t\t\tthis.updateEntityList(queryName, queryList, entity, this.matchesQuery(entity, query));\n\t\t});\n\n\t\treturn shouldAddToMain;\n\t}\n\tremoveEntity(entity: BaseEntity<C>) {\n\t\tthis.updateEntityList(MAIN_QUERY_NAME, this.entities, entity, false);\n\t\tObject.entries(this.queryEntities).forEach(([queryName, list]) => {\n\t\t\tthis.updateEntityList(queryName, list, entity, false);\n\t\t});\n\t}\n\n\tshouldRun(): boolean {\n\t\treturn this.entities.length > 0;\n\t}\n\n\tdestroy() {\n\t\tif('terminate' in this.worker) {\n\t\t\tthis.worker.terminate();\n\t\t}\n\t}\n}\n\nexport type EntityUpdateComponents<C extends ComponentMap = ComponentMap> = { [K in keyof C]?: ComponentTypedArray };\nexport type EntityQueryComponents<C extends ComponentMap = ComponentMap> = { [key: string]: Array<{ entityId: number, components: EntityUpdateComponents<C> }> };\ntype EntityUpdateFunctionImpl<C extends ComponentMap, T extends EntityUpdateComponents<C>, W extends ComponentSystemWorld = ComponentSystemWorld> = (\n\tworld: W,\n\tentityId: number,\n\tcomponents: T,\n\tqueries: EntityQueryComponents<C>,\n\tcallbacks: ComponentSystemCallbacks<C>,\n) => void;\n// `T` is required (no default) so every update function must spell out exactly which components it operates on\n// and their concrete typed arrays, rather than falling back to the full component list. `W` is the concrete\n// per-run world shape the owning system builds in addDataToWorld; it defaults to the bare ComponentSystemWorld.\nexport type EntityUpdateFunction<C extends ComponentMap, T extends EntityUpdateComponents<C>, W extends ComponentSystemWorld = ComponentSystemWorld> = EntityUpdateFunctionImpl<C, T, W> & {\n\tpreRun?: EntityUpdatePreRunFunction<C, T, W>\n\tentityRemoved?: EntityRemovedFunction<C, W>\n};\nexport type EntityUpdatePreRunFunction<C extends ComponentMap, T extends EntityUpdateComponents<C>, W extends ComponentSystemWorld = ComponentSystemWorld> = (\n\tworld: W,\n\tentities: Array<UpdateEntityConfigObject<T>>,\n\tqueries: EntityQueryComponents<C>,\n\tcallbacks: ComponentSystemCallbacks<C>,\n) => void;\nexport type EntityRemovedFunction<C extends ComponentMap = ComponentMap, W extends ComponentSystemWorld = ComponentSystemWorld> = (\n\tworld: W,\n\tentityId: number,\n\tcallbacks: ComponentSystemCallbacks<C>,\n) => void;\nexport type UpdateEntityConfig<T extends EntityUpdateComponents = EntityUpdateComponents> = number | UpdateEntityConfigObject<T>;\nexport type UpdateEntityConfigObject<T extends EntityUpdateComponents> = {\n\tentityId: number\n\tcomponents: T\n};\n\n// The membership changes for a single query since the last run, in the shape sent to the worker: entities that\n// joined (or whose component set changed) with their resolved component blocks, and the eids of entities that\n// left. Empty on a steady-state run, which is what keeps the per-frame postMessage small.\nexport interface QueryDelta<T extends EntityUpdateComponents = EntityUpdateComponents> {\n\tadded: Array<UpdateEntityConfigObject<T>>\n\tremoved: Array<number>\n}\n// The main thread's pre-serialization form of a QueryDelta: it holds the live entities (so their component\n// blocks can be resolved lazily at run time), whereas QueryDelta holds the resolved blocks that cross the wire.\ninterface MembershipDelta<C extends ComponentMap> {\n\tadded: Array<BaseEntity<C>>\n\tremoved: Array<number>\n}\n\n// Base per-run world data. gameTime + elapsedTime are always present; games attach anything else\n// they need via addDataToWorld, declaring the concrete shape through the `W` type parameter that\n// ComponentSystem (and its update function) are generic over.\nexport interface ComponentSystemWorld {\n\tgameTime: number\n\telapsedTime: number\n}\n// A flat entity config a worker asks the main thread to create. Kept as a plain record (not the game's `Cfg`)\n// because ComponentSystem is generic over `C`, not `Cfg`; the main thread hands it straight to world.loadEntity,\n// whose factory expands its `type` against the registered templates.\nexport type CreateEntityConfig = Record<string, unknown>;\n\nexport interface ComponentSystemCallbacks<C extends ComponentMap = ComponentMap> {\n\tentityComponentChanged<K extends keyof C, P extends keyof C[K]>(entityId: number, componentName: K, prop: P, value: C[K][P]): void\n\tentityDied(entityId: number): void\n\t// Requests that the main thread create an entity from `config` once the run completes. Unlike killing (which\n\t// flips an existing shared-memory flag in place), creation can't happen in the worker, so it is deferred to the\n\t// main thread where eid generation, allocation, and factory expansion already live.\n\tcreateEntity(config: CreateEntityConfig): void\n}\n\nexport interface ComponentSystemQuery<C extends ComponentMap = ComponentMap> {\n\trequired: Array<keyof C>\n\toptional?: Array<keyof C>\n\tnot?: Array<keyof C>\n\tfilter?: (entity: BaseEntity<C>) => boolean\n}\n\nexport interface ComponentSystemConfig<\n\tC extends ComponentMap,\n\tT extends EntityUpdateComponents<C>,\n\tW extends ComponentSystemWorld = ComponentSystemWorld,\n> extends SystemConfig, ComponentSystemQuery<C> {\n\tupdateFunction: EntityUpdateFunction<C, T, W>\n\tgetWorker: () => Worker\n\tforceMainThread?: boolean\n\n\tqueries?: { [key: string]: ComponentSystemQuery<C> }\n}\n\n// Re-exported so BaseComponent is reachable from the systems barrel if needed.\nexport type { BaseComponent };\n","import type { ComponentDefinition } from './component-definition';\n\n// The one component every entity is required to have. `dead` and `isStatic` are boolean flags stored in\n// the shared-memory block so a worker that pulls the `entity` component into its query can read them too.\n// `type` is a plain string that names the EntityFactory template this entity was built from; it lives off\n// to the side (not in shared memory), so - unlike `dead`/`isStatic` - it is NOT visible to workers.\n// Games that need a stable string id (or any other per-entity data) add their own component for it.\nexport interface EntityComponent {\n\tindex: number\n\ttype: string\n\tdead: boolean\n\tisStatic: boolean\n}\n\n// The defining config for the entity component. `type` is required - every entity is created from a factory\n// template, named by its type - while `isStatic` is an optional flag supplied up front.\nexport interface EntityComponentConfig {\n\ttype: string\n\tisStatic?: boolean\n}\n\n// Runtime-derived state plus `type`: `dead` is only meaningful once the entity is alive, and `type` is kept\n// so a save can be re-expanded through the factory. `isStatic` is intentionally absent - it comes back from\n// the type's template config, not the save.\nexport interface EntityComponentSerialization {\n\ttype?: string\n\tdead?: boolean\n}\n\nexport const DEAD_INDEX = 0;\nexport const STATIC_INDEX = 1;\n\nexport const entityDefinition: ComponentDefinition<EntityComponent, Uint32Array, EntityComponentConfig, EntityComponentSerialization> = {\n\ttype: Uint32Array,\n\tsize: 2,\n\t// The entity component is always loaded by BaseEntity's constructor, so this never actually gates\n\t// loading; it documents the defining config props and keeps it consistent with game components.\n\tloadProperties: ['type', 'isStatic'],\n\tload(entity, memory, config) {\n\t\tconst index = memory.create([config.dead ? 1 : 0, config.isStatic ? 1 : 0]);\n\t\tconst block = memory.getBlock(index);\n\n\t\treturn {\n\t\t\tindex,\n\t\t\t// Plain (non-memory) property, so it is main-thread only and not readable from workers.\n\t\t\ttype: config.type,\n\t\t\tget dead() {\n\t\t\t\treturn block[DEAD_INDEX] === 1;\n\t\t\t},\n\t\t\tset dead(value: boolean) {\n\t\t\t\tblock[DEAD_INDEX] = value ? 1 : 0;\n\t\t\t},\n\t\t\tget isStatic() {\n\t\t\t\treturn block[STATIC_INDEX] === 1;\n\t\t\t},\n\t\t\tset isStatic(value: boolean) {\n\t\t\t\tblock[STATIC_INDEX] = value ? 1 : 0;\n\t\t\t},\n\t\t};\n\t},\n\tsave(component) {\n\t\t// `type` + `dead` are serialized; `isStatic` (and the rest of the template) come back from the type's\n\t\t// config on reload, so they are deliberately left out.\n\t\tconst config: EntityComponentSerialization = {};\n\t\tif(component.type) {\n\t\t\tconfig.type = component.type;\n\t\t}\n\t\tif(component.dead) {\n\t\t\tconfig.dead = true;\n\t\t}\n\n\t\treturn config;\n\t},\n};\n","import { EventEmitter } from 'eventemitter3';\nimport type BaseWorld from './world';\nimport type { ComponentDefinitionMap, ComponentMap, RegisteredComponentRegistry } from './component-definition';\nimport type { EntityComponent } from './entity-component';\n\n// A bare entity: an eid and a bag of memory-backed components (including the required entity component).\n// `Cfg` is the flat config this entity loads from / saves to; it defaults to `any` so a bare `BaseEntity<C>`\n// stays usable, but a World derives it (via `EntityConfigOf`) so `config` and `load` are fully typed.\nexport default class BaseEntity<C extends ComponentMap = ComponentMap, Cfg = any> extends EventEmitter {\n\tstatic eidCounter = 1;\n\n\treadonly eid: number;\n\tconfig?: Cfg;\n\n\t// The World only uses its `R` param to derive `C`/`Cfg`, so entities reference it Cfg-agnostically via the\n\t// widened `ComponentDefinitionMap` - the instance shape depends on `C`/`Cfg`, not on the concrete registry.\n\tworld: BaseWorld<ComponentDefinitionMap, C, Cfg>;\n\t// The entity component is required on every entity, so it is always present (unlike the game\n\t// components, which are partial). Its `dead`/`isStatic` flags and `id` live here now.\n\tcomponents: Partial<C> & { entity: EntityComponent } = {} as Partial<C> & { entity: EntityComponent };\n\n\tconstructor(world: BaseWorld<ComponentDefinitionMap, C, Cfg>, config?: Cfg) {\n\t\tsuper();\n\n\t\tthis.world = world;\n\t\tthis.eid = BaseEntity.eidCounter++;\n\t\tthis.loadComponent('entity', config ?? {}, false);\n\t\tif(config) {\n\t\t\tthis.load(config);\n\t\t}\n\t}\n\n\t// `emitAdded` is true for runtime additions so systems can react; the loading path (constructor and\n\t// `load`) passes false, since those components are already accounted for when the entity is added.\n\tloadComponent<K extends keyof C>(name: K, config: any, emitAdded = true): C[K] {\n\t\t// registry carries the always-present entity component as an intersection; index the plain game map\n\t\t// here so the generic key stays cleanly typed as C[K].\n\t\tconst definition = (this.world.registry as RegisteredComponentRegistry<C>)[name];\n\t\t// The definition now owns its MemoryComponent, so the memory pool comes straight off it.\n\t\tconst memoryComponent = definition.memoryComponent;\n\t\tconst component = definition.load(this, memoryComponent, config);\n\t\t(this.components as Partial<C>)[name] = component;\n\t\tif(emitAdded) {\n\t\t\tthis.emit('component-added', name, component);\n\t\t}\n\n\t\treturn component;\n\t}\n\tremoveComponent<K extends keyof C>(name: K) {\n\t\tconst component = this.components[name];\n\t\tif(component) {\n\t\t\t(this.world.registry as RegisteredComponentRegistry<C>)[name].memoryComponent.delete(component.index);\n\t\t\tdelete this.components[name];\n\t\t\tthis.emit('component-removed', name);\n\t\t}\n\t}\n\tsetComponent<K extends keyof C, P extends keyof C[K]>(componentName: K, prop: P, value: C[K][P]) {\n\t\tconst component = this.components[componentName];\n\t\tif(!component) {\n\t\t\treturn;\n\t\t}\n\n\t\t// TS can't verify writing to a property of the generic C[K] by a keyof C[K] key, so index through a record.\n\t\t(component as unknown as Record<P, C[K][P]>)[prop] = value;\n\t\tthis.emit('component-property-updated', componentName, prop, value);\n\t}\n\t/**\n\t * NOTE: Does not emit component-property-updated!\n\t */\n\tsetComponentBulk<K extends keyof C>(componentName: K, values: Partial<C[K]>) {\n\t\tconst component = this.components[componentName];\n\t\tif(!component) {\n\t\t\treturn;\n\t\t}\n\n\t\tObject.assign(component, values);\n\t}\n\tdeleteComponent<K extends keyof C>(componentName: K, prop: keyof C[K]) {\n\t\tconst component = this.components[componentName];\n\t\tif(!component) {\n\t\t\treturn;\n\t\t}\n\n\t\tdelete (component as Partial<C[K]>)[prop];\n\t\tthis.emit('component-property-deleted', componentName, prop);\n\t}\n\n\tdeleteAllComponentMemory() {\n\t\tconst registry = this.world.registry as RegisteredComponentRegistry<C>;\n\t\tfor(let name of Object.keys(this.components) as Array<keyof C>) {\n\t\t\tconst component = this.components[name];\n\t\t\tif(component) {\n\t\t\t\tregistry[name].memoryComponent.delete(component.index);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Loads component data from a flat config: every registered component whose `loadProperties` appear in\n\t// the config is handed the entire config. The entity component is skipped here since the constructor\n\t// always loads it up front.\n\tload(config: Cfg) {\n\t\t// The flat config is keyed by string props; index it as a record for the `loadProperties` checks.\n\t\tconst props = config as Record<string, unknown>;\n\t\t// registry carries the intersected entity component; index the plain game map so `definition` stays typed.\n\t\tconst registry = this.world.registry as RegisteredComponentRegistry<C>;\n\t\tfor(let name of Object.keys(registry) as Array<keyof C>) {\n\t\t\tif(name === 'entity') {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst definition = registry[name];\n\t\t\t// Deferred components wait for finishLoading, once every entity in the batch exists.\n\t\t\tif(definition.loadInFinishLoading) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif(definition.loadProperties.some(prop => prop in props)) {\n\t\t\t\tthis.loadComponent(name, config, false);\n\t\t\t}\n\t\t}\n\n\t\tthis.config = config;\n\t}\n\tsave(): Cfg {\n\t\t// Every component's saver returns flat props merged into one shared config, mirroring how `load`\n\t\t// hands that same flat config to each loader.\n\t\tconst config: { [key: string]: any } = {};\n\n\t\t// registry carries the intersected entity component; index the plain game map here (the entity key\n\t\t// still resolves at runtime, so its definition is picked up as well).\n\t\tconst registry = this.world.registry as RegisteredComponentRegistry<C>;\n\t\tconst components = this.components as Partial<C>;\n\t\tfor(let name of Object.keys(components) as Array<keyof C>) {\n\t\t\tconst definition = registry[name];\n\t\t\tconst component = components[name];\n\t\t\tif(definition.save && component) {\n\t\t\t\tObject.assign(config, definition.save(component));\n\t\t\t}\n\t\t}\n\n\t\t// The merged serialization slices reconstruct a (partial) flat config, which is exactly `Cfg`.\n\t\treturn config as Cfg;\n\t}\n\t// Hook for games that need a second pass once every entity in a load batch exists. The base pass loads any\n\t// components flagged `loadInFinishLoading`, which `load` deliberately skipped; overrides should call super.\n\tfinishLoading() {\n\t\tconst config = this.config;\n\t\tif(!config) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst props = config as Record<string, unknown>;\n\t\tconst registry = this.world.registry as RegisteredComponentRegistry<C>;\n\t\tfor(let name of Object.keys(registry) as Array<keyof C>) {\n\t\t\tif(name === 'entity') {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst definition = registry[name];\n\t\t\tif(definition.loadInFinishLoading && definition.loadProperties.some(prop => prop in props)) {\n\t\t\t\tthis.loadComponent(name, config, false);\n\t\t\t}\n\t\t}\n\t}\n}\n","import BaseEntity from './entity';\nimport type BaseWorld from './world';\nimport type { ComponentDefinitionMap, ComponentMap } from './component-definition';\n\n// Maps an entity `type` name to a base (template) config. Loading an entity of a given type layers the\n// caller's config on top of that base, so shared static data (a goblin's maxHealth, say) is declared once\n// here instead of being saved on every entity - a save then only needs the `type` plus the entity's runtime\n// serialization. BaseWorld#loadEntity goes through the factory, so every load is type-expanded.\nexport default class EntityFactory<C extends ComponentMap = ComponentMap, Cfg = any> {\n\t// Set by BaseWorld when the factory is attached to it. The World only uses `R` to derive `C`/`Cfg`, so the\n\t// factory references it Cfg-agnostically through the widened `ComponentDefinitionMap`.\n\tworld!: BaseWorld<ComponentDefinitionMap, C, Cfg>;\n\t// type name -> its base config.\n\tconfigs: { [type: string]: Cfg };\n\n\tconstructor(configs: { [type: string]: Cfg } = {}) {\n\t\tthis.configs = configs;\n\t}\n\n\t// Register (or replace) the base config for an entity type.\n\tregister(type: string, config: Cfg) {\n\t\tthis.configs[type] = config;\n\t}\n\n\t// Layer a config over its type's base config. A config with no (or an unknown) `type` passes through\n\t// unchanged, so fully-specified configs can still be loaded directly.\n\tgetConfig(config: Cfg): Cfg {\n\t\tconst type = (config as { type?: string } | undefined)?.type;\n\t\tconst base = type ? this.configs[type] : undefined;\n\t\treturn base ? { ...base, ...config } : config;\n\t}\n\n\t// Build a type-expanded entity and add it to the world.\n\tloadEntity(config: Cfg, created = true): BaseEntity<C, Cfg> {\n\t\tconst entity = this.createEntity(this.getConfig(config));\n\t\treturn this.world.addEntity(entity, created);\n\t}\n\n\t// Hook for games that map types to BaseEntity subclasses; override to return a subclass per config.type.\n\tprotected createEntity(config: Cfg): BaseEntity<C, Cfg> {\n\t\treturn new BaseEntity<C, Cfg>(this.world, config);\n\t}\n}\n","import { EventEmitter } from 'eventemitter3';\nimport { MAX_BYTE_OFFSET_LENGTH, MemoryHeap } from '@daneren2005/shared-memory-objects';\nimport MemoryComponent from './memory-component';\nimport type BaseEntity from './entity';\nimport type System from './systems/system';\nimport EntitySystem from './systems/entity-system';\nimport ComponentSystem from './systems/component-system';\nimport type {\n\tBaseComponent, ComponentDefinitionMap, ComponentMap, ComponentRegistry, ComponentsOf,\n\tEntityConfigOf, RegisteredComponentDefinition, RegisteredComponentRegistry,\n} from './component-definition';\nimport { entityDefinition, type EntityComponent } from './entity-component';\nimport EntityFactory from './entity-factory';\n\nconst DEFAULT_HEAP_SIZE = MAX_BYTE_OFFSET_LENGTH;\n\nexport interface WorldOptions<C extends ComponentMap = ComponentMap, Cfg = any> {\n\theapSize?: number\n\t// Supplies the type -> base config templates; defaults to an empty factory that loads configs as-is.\n\tfactory?: EntityFactory<C, Cfg>\n}\n\n// The serialized shape of a whole world: the flat list of entity configs to (re)build plus the world clocks. A\n// game can layer its own fields on top; the World itself only needs `entities` (the same flat `Cfg` objects\n// `loadEntity` / `save` deal with) and the optional time state that `load` restores.\nexport interface WorldConfig<Cfg = any> {\n\tentities: Array<Cfg>\n\t// Simulation clock; restored so a saved world resumes at the same in-game time. Defaults to 0.\n\tgameTime?: number\n\t// Real elapsed play time; kept separate from gameTime and unaffected by timeScale. Defaults to 0.\n\tplayerTime?: number\n\t// Simulation speed multiplier. Defaults to 1.\n\ttimeScale?: number\n}\n\n// A simple wrapper around a set of entities + systems. On construction it creates one\n// MemoryComponent per entry in the component registry so entities can load/save themselves without\n// each game re-declaring loaders/savers. It has no game specific load/save/terrain/faction logic -\n// that all belongs in the game that consumes this library.\n// The World is generic over `R`, the registry of component definitions a game supplies. Both the\n// component-instance map `C` and the flat entity config `Cfg` are derived from `R` (via `ComponentsOf` /\n// `EntityConfigOf`) and default off it, so `new BaseWorld(registry)` infers `R` from its argument and every\n// entity's components and config are typed without the game declaring any composite types by hand.\nexport default class BaseWorld<\n\tR extends ComponentDefinitionMap = ComponentDefinitionMap,\n\tC extends ComponentMap = ComponentsOf<R>,\n\tCfg = EntityConfigOf<R>,\n> extends EventEmitter {\n\theap: MemoryHeap;\n\t// The entity component is always registered on top of the game's components so every entity can be\n\t// given one automatically. Each registered definition carries its own MemoryComponent, so a\n\t// component's memory pool is reachable straight from `registry[name].memoryComponent`.\n\tregistry: RegisteredComponentRegistry<C> & { entity: RegisteredComponentDefinition<EntityComponent> };\n\t// Every entity is built through the factory, which expands its `type` against the registered templates.\n\tfactory: EntityFactory<C, Cfg>;\n\n\tentities: Array<BaseEntity<C, Cfg>> = [];\n\tentitiesByEid: { [eid: number]: BaseEntity<C, Cfg> } = {};\n\tsystems: Array<System<C>> = [];\n\n\tgameTime = 0;\n\t// Real (unscaled) time the player has spent in the world. Unlike gameTime it keeps advancing while paused and\n\t// is never multiplied by timeScale.\n\tplayerTime = 0;\n\t// Multiplier applied to elapsedTime before it advances gameTime / runs systems, so a game can speed up or slow\n\t// down simulation without touching the real frame delta.\n\ttimeScale = 1;\n\t// While paused, update still accrues playerTime but skips advancing gameTime and running systems.\n\tpaused = false;\n\tdestroyed = false;\n\n\t// The game supplies its own components as `R`; the entity component is added automatically, so it must not\n\t// be part of the passed registry.\n\tconstructor(registry: R, options: WorldOptions<C, Cfg> = {}) {\n\t\tsuper();\n\n\t\tthis.heap = new MemoryHeap({ bufferSize: options.heapSize ?? DEFAULT_HEAP_SIZE });\n\n\t\t// Register every supplied definition (plus the always-present entity component) by attaching a freshly\n\t\t// allocated MemoryComponent to it, so the memory pool lives alongside the definition on the registry.\n\t\tconst inputRegistry = { ...registry, entity: entityDefinition } as ComponentRegistry<C> & { entity: typeof entityDefinition };\n\t\tconst registry_: Record<string, RegisteredComponentDefinition<BaseComponent>> = {};\n\t\tfor(let name of Object.keys(inputRegistry)) {\n\t\t\tconst definition = inputRegistry[name as keyof typeof inputRegistry];\n\t\t\tregistry_[name] = {\n\t\t\t\t...definition,\n\t\t\t\tmemoryComponent: new MemoryComponent(this.heap, definition.type, definition.size),\n\t\t\t};\n\t\t}\n\t\tthis.registry = registry_ as RegisteredComponentRegistry<C> & { entity: RegisteredComponentDefinition<EntityComponent> };\n\n\t\tthis.factory = options.factory ?? new EntityFactory<C, Cfg>();\n\t\tthis.factory.world = this;\n\t}\n\n\tasync init() {\n\t\tawait Promise.all(this.systems.map(system => system.init()).filter(promise => promise instanceof Promise));\n\t}\n\n\taddEntity(entity: BaseEntity<C, Cfg>, created = true): BaseEntity<C, Cfg> {\n\t\tthis.entities.push(entity);\n\t\tthis.entitiesByEid[entity.eid] = entity;\n\t\tentity.world = this;\n\n\t\tentity.on('component-added', (name: keyof C) => {\n\t\t\tthis.addEntityToComponentSystem(entity, name);\n\t\t});\n\t\tentity.on('component-removed', (name: keyof C) => {\n\t\t\tthis.removeEntityFromComponentSystem(entity, name);\n\t\t});\n\n\t\tif(created) {\n\t\t\tentity.finishLoading();\n\t\t\tthis.emit('entity-added', entity);\n\t\t\t// killEntity / killEntityWorker flag the entity dead and emit `death`; remove it here.\n\t\t\tentity.on('death', () => {\n\t\t\t\tthis.onEntityDied(entity);\n\t\t\t});\n\t\t}\n\n\t\treturn entity;\n\t}\n\tloadEntity(config: Cfg, created = true): BaseEntity<C, Cfg> {\n\t\t// The factory expands the config's `type` against the registered templates before building the entity.\n\t\treturn this.factory.loadEntity(config, created);\n\t}\n\t// Replace the world's contents with a saved config. The existing entities (their backing memory freed) are\n\t// cleared and each system is reset via `clear()` first, then each entity is loaded with `created = false` so\n\t// no finishLoading runs mid-batch. Systems themselves are set up once ahead of time (they persist across\n\t// loads); only their per-load state is cleared here. Once every entity exists, finishLoading is called on\n\t// each - so a component that depends on other entities (e.g. a lumbermill counting nearby trees) can resolve\n\t// them, since the whole batch is guaranteed loaded by then.\n\tload(config: WorldConfig<Cfg>) {\n\t\t// Iterate over a copy since removeEntity mutates `this.entities`.\n\t\tfor(let entity of this.entities.slice()) {\n\t\t\tthis.removeEntity(entity);\n\t\t}\n\t\tthis.systems.forEach(system => system.clear());\n\n\t\tconst entities = config.entities.map(entityConfig => this.loadEntity(entityConfig, false));\n\t\tfor(let entity of entities) {\n\t\t\tentity.finishLoading();\n\t\t\tthis.emit('entity-added', entity);\n\t\t\t// killEntity / killEntityWorker flag the entity dead and emit `death`; remove it here.\n\t\t\tentity.on('death', () => {\n\t\t\t\tthis.onEntityDied(entity);\n\t\t\t});\n\t\t}\n\n\t\t// Restore the world clocks, falling back to a fresh world's defaults when the config omits them.\n\t\tthis.gameTime = config.gameTime ?? 0;\n\t\tthis.playerTime = config.playerTime ?? 0;\n\t\tthis.timeScale = config.timeScale ?? 1;\n\t}\n\tremoveEntity(entity: BaseEntity<C, Cfg>) {\n\t\tlet index = this.entities.indexOf(entity);\n\t\tif(index !== -1) {\n\t\t\tthis.entities.splice(index, 1);\n\t\t\tdelete this.entitiesByEid[entity.eid];\n\t\t\tthis.emit('entity-removed', entity);\n\t\t}\n\n\t\tentity.deleteAllComponentMemory();\n\t}\n\tonEntityDied(entity: BaseEntity<C, Cfg>) {\n\t\tthis.removeEntity(entity);\n\t}\n\tgetEntityByEid(eid: number): BaseEntity<C, Cfg> | undefined {\n\t\treturn this.entitiesByEid[eid];\n\t}\n\n\taddSystem<T extends System<C>>(system: T): T {\n\t\tthis.systems.push(system);\n\t\treturn system;\n\t}\n\taddSystemIfNotExists(system: System<C>) {\n\t\tlet index = this.systems.findIndex(otherSystem => system.name === otherSystem.name);\n\t\tif(index === -1) {\n\t\t\tthis.systems.push(system);\n\t\t}\n\t}\n\tremoveSystem(name: string) {\n\t\tlet index = this.systems.findIndex(system => system.name === name);\n\t\tif(index !== -1) {\n\t\t\tthis.systems.splice(index, 1);\n\t\t}\n\t}\n\n\tupdate(elapsedTime: number): { lastSystemError?: Error | null } {\n\t\t// playerTime tracks real elapsed time and keeps ticking even while paused; gameTime is the scaled,\n\t\t// pausable simulation clock the systems run against.\n\t\tthis.playerTime += elapsedTime;\n\t\tif(this.paused) {\n\t\t\treturn {};\n\t\t}\n\t\telapsedTime = this.timeScale * elapsedTime;\n\n\t\tthis.gameTime += elapsedTime;\n\n\t\tlet lastSystemError: Error | null = null;\n\t\tthis.systems.forEach(system => {\n\t\t\tlet shouldRun = true;\n\t\t\tlet ran = false;\n\t\t\tlet failed = false;\n\t\t\tthis.emit(`system-${system.name}-started`);\n\t\t\ttry {\n\t\t\t\tshouldRun = system.shouldRun();\n\t\t\t\tif(shouldRun) {\n\t\t\t\t\tran = system.update(elapsedTime);\n\t\t\t\t}\n\t\t\t} catch(e) {\n\t\t\t\tconst error = e as Error;\n\t\t\t\tconsole.error(error.message, error);\n\t\t\t\tfailed = true;\n\t\t\t\tlastSystemError = error;\n\t\t\t}\n\t\t\tthis.emit(`system-${system.name}-finished`, {\n\t\t\t\tran,\n\t\t\t\tshouldRun,\n\t\t\t\tfailed,\n\t\t\t});\n\t\t});\n\n\t\treturn {\n\t\t\tlastSystemError,\n\t\t};\n\t}\n\tpause() {\n\t\tthis.paused = true;\n\t}\n\tresume() {\n\t\tthis.paused = false;\n\t}\n\n\taddEntityToComponentSystem(entity: BaseEntity<C>, component: keyof C) {\n\t\tthis.systems.forEach(system => {\n\t\t\tif(system instanceof EntitySystem && system.options.components?.includes(component)) {\n\t\t\t\tsystem.checkAddEntity(entity);\n\t\t\t} else if(system instanceof ComponentSystem) {\n\t\t\t\tif(this.componentAffectsComponentSystem(system, component)) {\n\t\t\t\t\tsystem.checkAddEntity(entity);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t}\n\tremoveEntityFromComponentSystem(entity: BaseEntity<C>, component: keyof C) {\n\t\tthis.systems.forEach(system => {\n\t\t\tif(system instanceof EntitySystem && system.options.components?.includes(component)) {\n\t\t\t\tsystem.removeEntity(entity);\n\t\t\t} else if(system instanceof ComponentSystem) {\n\t\t\t\tif(this.componentAffectsComponentSystem(system, component)) {\n\t\t\t\t\tsystem.checkAddEntity(entity);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t}\n\tprivate componentAffectsComponentSystem(system: ComponentSystem<C, any>, component: keyof C): boolean {\n\t\tconst affectsMainQuery = system.options.required.includes(component)\n\t\t\t|| !!system.options.not?.includes(component)\n\t\t\t|| !!system.options.optional?.includes(component);\n\t\tconst affectsSubQuery = Object.values(system.options.queries ?? {}).some(query => {\n\t\t\treturn query.required.includes(component)\n\t\t\t\t|| !!query.not?.includes(component)\n\t\t\t\t|| !!query.optional?.includes(component);\n\t\t});\n\n\t\treturn affectsMainQuery || affectsSubQuery;\n\t}\n\n\tdestroy() {\n\t\tif(this.destroyed) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.systems.forEach(system => {\n\t\t\tsystem.destroy();\n\t\t});\n\t\tthis.destroyed = true;\n\t}\n}\n","import type BaseEntity from '../entity';\n\n// Kills an entity on the main thread: flags it dead in shared memory and emits `death` so the world and\n// any game listeners can react. BaseWorld#addEntity listens for `death` to remove the entity.\nexport default function killEntity(entity: BaseEntity): void {\n\tentity.components.entity.dead = true;\n\tentity.emit('death');\n}\n","import type { ComponentTypedArray } from '../memory-component';\nimport type { ComponentSystemCallbacks, EntityUpdateComponents } from '../systems/component-system';\nimport { DEAD_INDEX } from '../entity-component';\n\n// The worker-thread counterpart of killEntity. Called from inside a component system's update function,\n// it flags the entity dead directly in its shared-memory block and reports the death back to the main\n// thread through the callbacks so the world can run the same cleanup killEntity would. The entity\n// component must be part of the system's query for its block to be available here.\nexport default function killEntityWorker(entityId: number, components: EntityUpdateComponents, callbacks: ComponentSystemCallbacks): void {\n\tconst block = (components as { entity?: ComponentTypedArray }).entity;\n\tif(block) {\n\t\tblock[DEAD_INDEX] = 1;\n\t}\n\n\tcallbacks.entityDied(entityId);\n}\n","import type { ComponentSystemCallbacks, CreateEntityConfig } from '../systems/component-system';\n\n// The worker-thread counterpart of loadEntity. Called from inside a component system's update function, it\n// can't build the entity itself (eid generation, shared-memory allocation, and factory expansion all live on\n// the main thread), so it buffers the flat config through the callbacks. The main thread creates the entity\n// via world.loadEntity once the run completes, meaning it first exists on the following frame.\nexport default function createEntityWorker(config: CreateEntityConfig, callbacks: ComponentSystemCallbacks): void {\n\tcallbacks.createEntity(config);\n}\n","import type ComponentWorkerMessage from './component-worker-message';\nimport type { ComponentMap } from '../../component-definition';\nimport type { ComponentSystemCallbacks, ComponentSystemWorld, EntityQueryComponents, EntityUpdateComponents, EntityUpdateFunction, QueryDelta, UpdateEntityConfigObject } from '../component-system';\nimport { applyQueryDelta } from './apply-query-delta';\n\n// The slice of the Web Worker global scope createComponentWorker actually touches. Worker files pass the\n// real `self` (e.g. `createComponentWorker(self, fn)`); passing it explicitly also lets test runners that\n// inject `self` as a module local rather than a true global - like @vitest/web-worker - drive the worker.\nexport interface ComponentWorkerScope {\n\tonmessage: ((e: MessageEvent) => void) | null\n\tpostMessage(message: ComponentWorkerMessage): void\n}\n\n// Entry point a game's worker file calls with its update function. It wires up `scope.onmessage`,\n// caches component blocks by entity id (so subsequent runs only need the id), and posts results back.\nexport default function createComponentWorker<\n\tC extends ComponentMap,\n\tT extends EntityUpdateComponents<C>,\n\tW extends ComponentSystemWorld = ComponentSystemWorld,\n>(scope: ComponentWorkerScope, updateFunction: EntityUpdateFunction<C, T, W>) {\n\t// The worker's persistent iteration lists. The main thread now sends only membership changes, so these are\n\t// carried across runs and mutated by the deltas each run brings (see applyQueryDelta).\n\tlet entities: Array<UpdateEntityConfigObject<T>> = [];\n\tconst queryEntities: { [key: string]: Array<UpdateEntityConfigObject<T>> } = {};\n\n\tscope.onmessage = function(e) {\n\t\tconst message = e.data as ComponentWorkerMessage<W>;\n\n\t\tif(message.type === 'init') {\n\t\t\tpostMessageTyped(scope, {\n\t\t\t\ttype: 'loaded',\n\t\t\t});\n\t\t} else if(message.type === 'run') {\n\t\t\tconst start = performance.now();\n\t\t\tlet entityEvents: Array<{ entityId: number, event: string, args: Array<any> }> = [];\n\t\t\tlet createdEntities: Array<Record<string, unknown>> = [];\n\n\t\t\tentities = applyQueryDelta(entities, message.entities as QueryDelta<T>);\n\n\t\t\tlet queries: EntityQueryComponents<C> = {};\n\t\t\tObject.entries(message.queries).forEach(([queryKey, delta]) => {\n\t\t\t\tconst list = applyQueryDelta(queryEntities[queryKey] ?? [], delta as QueryDelta<T>);\n\t\t\t\tqueryEntities[queryKey] = list;\n\t\t\t\tqueries[queryKey] = list;\n\t\t\t});\n\n\t\t\tlet callbacks: ComponentSystemCallbacks<C> = {\n\t\t\t\tentityComponentChanged<K extends keyof C, P extends keyof C[K]>(entityId: number, componentName: K, prop: P, value: C[K][P]) {\n\t\t\t\t\tentityEvents.push({\n\t\t\t\t\t\tentityId,\n\t\t\t\t\t\tevent: 'component-property-updated',\n\t\t\t\t\t\targs: [componentName, prop, value],\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\tentityDied(entityId: number) {\n\t\t\t\t\tentityEvents.push({\n\t\t\t\t\t\tentityId,\n\t\t\t\t\t\tevent: 'death',\n\t\t\t\t\t\targs: [],\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\tcreateEntity(config: Record<string, unknown>) {\n\t\t\t\t\tcreatedEntities.push(config);\n\t\t\t\t},\n\t\t\t};\n\t\t\tif(updateFunction.preRun) {\n\t\t\t\tupdateFunction.preRun(message.world, entities, queries, callbacks);\n\t\t\t}\n\n\t\t\tentities.forEach(entity => {\n\t\t\t\tupdateFunction(message.world, entity.entityId, entity.components, queries, callbacks);\n\t\t\t});\n\n\t\t\t// applyQueryDelta already dropped the removed entities from the persistent lists above; the hook just\n\t\t\t// lets the update function react to entities that left the system's main query.\n\t\t\tif(updateFunction.entityRemoved) {\n\t\t\t\tmessage.entities.removed.forEach(entityId => {\n\t\t\t\t\tupdateFunction.entityRemoved!(message.world, entityId, callbacks);\n\t\t\t\t});\n\t\t\t}\n\t\t\tconst runTime = performance.now() - start;\n\n\t\t\tpostMessageTyped(scope, {\n\t\t\t\ttype: 'run-complete',\n\t\t\t\trunTime,\n\t\t\t\tevents: entityEvents,\n\t\t\t\tcreated: createdEntities,\n\t\t\t});\n\t\t}\n\t};\n}\n\nfunction postMessageTyped(scope: ComponentWorkerScope, message: ComponentWorkerMessage) {\n\tscope.postMessage(message);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../node_modules/eventemitter3/index.js","../node_modules/eventemitter3/index.mjs","../src/memory-component.ts","../src/systems/system.ts","../src/systems/iterable-system.ts","../src/systems/entity-system.ts","../src/systems/workers/web-worker.ts","../src/systems/workers/apply-query-delta.ts","../src/systems/workers/component-web-worker.ts","../src/systems/component-system.ts","../src/entity-component.ts","../src/entity.ts","../src/entity-factory.ts","../src/world.ts","../src/actions/kill-entity.ts","../src/actions/kill-entity-worker.ts","../src/actions/create-entity-worker.ts","../src/performance-timing.ts","../src/systems/workers/create-component-worker.ts"],"sourcesContent":["'use strict';\n\nvar has = Object.prototype.hasOwnProperty\n , prefix = '~';\n\n/**\n * Constructor to create a storage for our `EE` objects.\n * An `Events` instance is a plain object whose properties are event names.\n *\n * @constructor\n * @private\n */\nfunction Events() {}\n\n//\n// We try to not inherit from `Object.prototype`. In some engines creating an\n// instance in this way is faster than calling `Object.create(null)` directly.\n// If `Object.create(null)` is not supported we prefix the event names with a\n// character to make sure that the built-in object properties are not\n// overridden or used as an attack vector.\n//\nif (Object.create) {\n Events.prototype = Object.create(null);\n\n //\n // This hack is needed because the `__proto__` property is still inherited in\n // some old browsers like Android 4, iPhone 5.1, Opera 11 and Safari 5.\n //\n if (!new Events().__proto__) prefix = false;\n}\n\n/**\n * Representation of a single event listener.\n *\n * @param {Function} fn The listener function.\n * @param {*} context The context to invoke the listener with.\n * @param {Boolean} [once=false] Specify if the listener is a one-time listener.\n * @constructor\n * @private\n */\nfunction EE(fn, context, once) {\n this.fn = fn;\n this.context = context;\n this.once = once || false;\n}\n\n/**\n * Add a listener for a given event.\n *\n * @param {EventEmitter} emitter Reference to the `EventEmitter` instance.\n * @param {(String|Symbol)} event The event name.\n * @param {Function} fn The listener function.\n * @param {*} context The context to invoke the listener with.\n * @param {Boolean} once Specify if the listener is a one-time listener.\n * @returns {EventEmitter}\n * @private\n */\nfunction addListener(emitter, event, fn, context, once) {\n if (typeof fn !== 'function') {\n throw new TypeError('The listener must be a function');\n }\n\n var listener = new EE(fn, context || emitter, once)\n , evt = prefix ? prefix + event : event;\n\n if (!emitter._events[evt]) emitter._events[evt] = listener, emitter._eventsCount++;\n else if (!emitter._events[evt].fn) emitter._events[evt].push(listener);\n else emitter._events[evt] = [emitter._events[evt], listener];\n\n return emitter;\n}\n\n/**\n * Clear event by name.\n *\n * @param {EventEmitter} emitter Reference to the `EventEmitter` instance.\n * @param {(String|Symbol)} evt The Event name.\n * @private\n */\nfunction clearEvent(emitter, evt) {\n if (--emitter._eventsCount === 0) emitter._events = new Events();\n else delete emitter._events[evt];\n}\n\n/**\n * Minimal `EventEmitter` interface that is molded against the Node.js\n * `EventEmitter` interface.\n *\n * @constructor\n * @public\n */\nfunction EventEmitter() {\n this._events = new Events();\n this._eventsCount = 0;\n}\n\n/**\n * Return an array listing the events for which the emitter has registered\n * listeners.\n *\n * @returns {Array}\n * @public\n */\nEventEmitter.prototype.eventNames = function eventNames() {\n var names = []\n , events\n , name;\n\n if (this._eventsCount === 0) return names;\n\n for (name in (events = this._events)) {\n if (has.call(events, name)) names.push(prefix ? name.slice(1) : name);\n }\n\n if (Object.getOwnPropertySymbols) {\n return names.concat(Object.getOwnPropertySymbols(events));\n }\n\n return names;\n};\n\n/**\n * Return the listeners registered for a given event.\n *\n * @param {(String|Symbol)} event The event name.\n * @returns {Array} The registered listeners.\n * @public\n */\nEventEmitter.prototype.listeners = function listeners(event) {\n var evt = prefix ? prefix + event : event\n , handlers = this._events[evt];\n\n if (!handlers) return [];\n if (handlers.fn) return [handlers.fn];\n\n for (var i = 0, l = handlers.length, ee = new Array(l); i < l; i++) {\n ee[i] = handlers[i].fn;\n }\n\n return ee;\n};\n\n/**\n * Return the number of listeners listening to a given event.\n *\n * @param {(String|Symbol)} event The event name.\n * @returns {Number} The number of listeners.\n * @public\n */\nEventEmitter.prototype.listenerCount = function listenerCount(event) {\n var evt = prefix ? prefix + event : event\n , listeners = this._events[evt];\n\n if (!listeners) return 0;\n if (listeners.fn) return 1;\n return listeners.length;\n};\n\n/**\n * Calls each of the listeners registered for a given event.\n *\n * @param {(String|Symbol)} event The event name.\n * @returns {Boolean} `true` if the event had listeners, else `false`.\n * @public\n */\nEventEmitter.prototype.emit = function emit(event, a1, a2, a3, a4, a5) {\n var evt = prefix ? prefix + event : event;\n\n if (!this._events[evt]) return false;\n\n var listeners = this._events[evt]\n , len = arguments.length\n , args\n , i;\n\n if (listeners.fn) {\n if (listeners.once) this.removeListener(event, listeners.fn, undefined, true);\n\n switch (len) {\n case 1: return listeners.fn.call(listeners.context), true;\n case 2: return listeners.fn.call(listeners.context, a1), true;\n case 3: return listeners.fn.call(listeners.context, a1, a2), true;\n case 4: return listeners.fn.call(listeners.context, a1, a2, a3), true;\n case 5: return listeners.fn.call(listeners.context, a1, a2, a3, a4), true;\n case 6: return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true;\n }\n\n for (i = 1, args = new Array(len -1); i < len; i++) {\n args[i - 1] = arguments[i];\n }\n\n listeners.fn.apply(listeners.context, args);\n } else {\n var length = listeners.length\n , j;\n\n for (i = 0; i < length; i++) {\n if (listeners[i].once) this.removeListener(event, listeners[i].fn, undefined, true);\n\n switch (len) {\n case 1: listeners[i].fn.call(listeners[i].context); break;\n case 2: listeners[i].fn.call(listeners[i].context, a1); break;\n case 3: listeners[i].fn.call(listeners[i].context, a1, a2); break;\n case 4: listeners[i].fn.call(listeners[i].context, a1, a2, a3); break;\n default:\n if (!args) for (j = 1, args = new Array(len -1); j < len; j++) {\n args[j - 1] = arguments[j];\n }\n\n listeners[i].fn.apply(listeners[i].context, args);\n }\n }\n }\n\n return true;\n};\n\n/**\n * Add a listener for a given event.\n *\n * @param {(String|Symbol)} event The event name.\n * @param {Function} fn The listener function.\n * @param {*} [context=this] The context to invoke the listener with.\n * @returns {EventEmitter} `this`.\n * @public\n */\nEventEmitter.prototype.on = function on(event, fn, context) {\n return addListener(this, event, fn, context, false);\n};\n\n/**\n * Add a one-time listener for a given event.\n *\n * @param {(String|Symbol)} event The event name.\n * @param {Function} fn The listener function.\n * @param {*} [context=this] The context to invoke the listener with.\n * @returns {EventEmitter} `this`.\n * @public\n */\nEventEmitter.prototype.once = function once(event, fn, context) {\n return addListener(this, event, fn, context, true);\n};\n\n/**\n * Remove the listeners of a given event.\n *\n * @param {(String|Symbol)} event The event name.\n * @param {Function} fn Only remove the listeners that match this function.\n * @param {*} context Only remove the listeners that have this context.\n * @param {Boolean} once Only remove one-time listeners.\n * @returns {EventEmitter} `this`.\n * @public\n */\nEventEmitter.prototype.removeListener = function removeListener(event, fn, context, once) {\n var evt = prefix ? prefix + event : event;\n\n if (!this._events[evt]) return this;\n if (!fn) {\n clearEvent(this, evt);\n return this;\n }\n\n var listeners = this._events[evt];\n\n if (listeners.fn) {\n if (\n listeners.fn === fn &&\n (!once || listeners.once) &&\n (!context || listeners.context === context)\n ) {\n clearEvent(this, evt);\n }\n } else {\n for (var i = 0, events = [], length = listeners.length; i < length; i++) {\n if (\n listeners[i].fn !== fn ||\n (once && !listeners[i].once) ||\n (context && listeners[i].context !== context)\n ) {\n events.push(listeners[i]);\n }\n }\n\n //\n // Reset the array, or remove it completely if we have no more listeners.\n //\n if (events.length) this._events[evt] = events.length === 1 ? events[0] : events;\n else clearEvent(this, evt);\n }\n\n return this;\n};\n\n/**\n * Remove all listeners, or those of the specified event.\n *\n * @param {(String|Symbol)} [event] The event name.\n * @returns {EventEmitter} `this`.\n * @public\n */\nEventEmitter.prototype.removeAllListeners = function removeAllListeners(event) {\n var evt;\n\n if (event) {\n evt = prefix ? prefix + event : event;\n if (this._events[evt]) clearEvent(this, evt);\n } else {\n this._events = new Events();\n this._eventsCount = 0;\n }\n\n return this;\n};\n\n//\n// Alias methods names because people roll like that.\n//\nEventEmitter.prototype.off = EventEmitter.prototype.removeListener;\nEventEmitter.prototype.addListener = EventEmitter.prototype.on;\n\n//\n// Expose the prefix.\n//\nEventEmitter.prefixed = prefix;\n\n//\n// Allow `EventEmitter` to be imported as module namespace.\n//\nEventEmitter.EventEmitter = EventEmitter;\n\n//\n// Expose the module.\n//\nif ('undefined' !== typeof module) {\n module.exports = EventEmitter;\n}\n","import EventEmitter from './index.js'\n\nexport { EventEmitter }\nexport default EventEmitter\n","import { LocalPool, type MemoryHeap, type TypedArrayConstructor } from '@daneren2005/shared-memory-objects';\n\n// The typed arrays a MemoryComponent can be backed by. Kept narrower than the package's TypedArray\n// union on purpose since these are the only types we allocate blocks of.\nexport type ComponentTypedArray = Uint32Array | Int32Array | Float32Array | Float64Array;\n\n// A pool of same sized blocks living inside a shared MemoryHeap. Each component instance owns one\n// block (referenced by its index) so component data can be shared with worker threads.\nexport default class MemoryComponent<T extends ComponentTypedArray = ComponentTypedArray> {\n\theap: MemoryHeap;\n\tpool: LocalPool<T>;\n\n\tconstructor(heap: MemoryHeap, type: TypedArrayConstructor<T>, dataLength: number) {\n\t\tthis.heap = heap;\n\t\tthis.pool = new LocalPool(heap, {\n\t\t\ttype,\n\t\t\tdataLength,\n\t\t});\n\t}\n\n\tget length() {\n\t\treturn this.pool.length;\n\t}\n\tget rawLength() {\n\t\treturn this.pool.bufferLength;\n\t}\n\n\tcreate(values: Array<number>): number {\n\t\treturn this.pool.push(values);\n\t}\n\n\tgetBlock(index: number): T {\n\t\treturn this.pool.at(index);\n\t}\n\tget(index: number, dataIndex: number): number {\n\t\treturn this.pool.get(index, dataIndex);\n\t}\n\tset(index: number, dataIndex: number, value: number) {\n\t\tlet array = this.pool.at(index);\n\t\tarray[dataIndex] = value;\n\t}\n\n\tdelete(index: number) {\n\t\tthis.pool.deleteIndex(index);\n\t}\n\tclear() {\n\t\tthis.pool.clear();\n\t}\n}\n","import type BaseWorld from '../world';\nimport type { ComponentDefinitionMap, ComponentMap } from '../component-definition';\n\n// Base system: runs on an optional fixed timestep (deltaBetweenRuns) and is driven by BaseWorld#update.\n// Systems only care about the component map `C`, not the World's registry (`R`) or config (`Cfg`), so they\n// reference the World through the widened `ComponentDefinitionMap` and let `Cfg` default.\nexport default abstract class System<C extends ComponentMap = ComponentMap> {\n\tworld: BaseWorld<ComponentDefinitionMap, C>;\n\tname: string;\n\tcurrentDelta: number = 0;\n\tdeltaBetweenRuns: number;\n\tfirstRun: boolean;\n\n\tconstructor(world: BaseWorld<ComponentDefinitionMap, C>, options: SystemConfig = { name: 'System' }) {\n\t\tthis.name = options.name;\n\t\tthis.world = world;\n\n\t\tthis.deltaBetweenRuns = options.deltaBetweenRuns ?? 0;\n\t\tthis.firstRun = options.firstRun !== undefined ? options.firstRun : false;\n\t}\n\n\tinit(): void | Promise<void> {}\n\n\tclear() {\n\t\tthis.currentDelta = 0;\n\t}\n\tfinishLoading() {}\n\n\tupdate(elapsedTime: number): boolean {\n\t\tthis.currentDelta += elapsedTime;\n\n\t\tif(this.currentDelta >= this.deltaBetweenRuns || this.firstRun) {\n\t\t\tlet leftOverDelta = 0;\n\t\t\tif(this.deltaBetweenRuns > 0) {\n\t\t\t\t// Without this if we take 100ms to run (ie: IterableSystem across multiple frames) then we will end up\n\t\t\t\t// actually running this in 300ms total instead of again in 100ms for a total of 200ms\n\t\t\t\t// With firstRun this ends up calling run(0) the first time - this makes it so things like events are\n\t\t\t\t// will trigger on the second instead of triggering a 1 second timer at 1.0166 seconds\n\t\t\t\tleftOverDelta = this.currentDelta % this.deltaBetweenRuns;\n\t\t\t}\n\n\t\t\tthis.run(this.currentDelta - leftOverDelta);\n\t\t\tthis.currentDelta = leftOverDelta;\n\t\t\tthis.firstRun = false;\n\n\t\t\treturn true;\n\t\t} else {\n\t\t\treturn false;\n\t\t}\n\t}\n\tabstract run(elapsedTime: number): void;\n\n\tshouldRun(): boolean {\n\t\treturn true;\n\t}\n\n\tdestroy() {}\n}\n\nexport interface SystemConfig {\n\tname: string\n\tdeltaBetweenRuns?: number\n\t// Defaults to false\n\tfirstRun?: boolean\n}\n","import type BaseWorld from '../world';\nimport type { ComponentDefinitionMap, ComponentMap } from '../component-definition';\nimport System, { type SystemConfig } from './system';\n\n// A system that iterates a list of instances, spreading the work across multiple frames if a single\n// pass would exceed maxMsPerFrame.\nexport default abstract class IterableSystem<C extends ComponentMap, T> extends System<C> {\n\tremainingInstancesToRun: Array<T> = [];\n\tremainingInstancesStartTime: number | null = null;\n\titerationsPerCheck: number;\n\tmaxMsPerFrame: number;\n\n\tconstructor(world: BaseWorld<ComponentDefinitionMap, C>, options: IterableSystemConfig) {\n\t\tsuper(world, options);\n\n\t\tthis.iterationsPerCheck = options.iterationsPerCheck ?? 1;\n\t\tthis.maxMsPerFrame = options.maxMsPerFrame ?? 10;\n\t}\n\n\tclear() {\n\t\tsuper.clear();\n\n\t\tthis.remainingInstancesToRun = [];\n\t\tthis.remainingInstancesStartTime = null;\n\t}\n\n\tupdate(elapsedTime: number): boolean {\n\t\tif(this.remainingInstancesToRun.length) {\n\t\t\tthis.runIterables(this.remainingInstancesToRun, this.remainingInstancesStartTime ?? 0);\n\t\t\tthis.currentDelta += elapsedTime;\n\n\t\t\treturn true;\n\t\t} else {\n\t\t\treturn super.update(elapsedTime);\n\t\t}\n\t}\n\trun(elapsedTime: number): void {\n\t\tlet iterables = this.getIterables();\n\t\tthis.runIterables(iterables, elapsedTime);\n\t}\n\trunIterables(iterables: Array<T>, elapsedTime: number) {\n\t\tlet started = performance.now();\n\t\tthis.beforeRunIterables();\n\t\tfor(let i = 0; i < iterables.length; i++) {\n\t\t\tthis.updateIterable(iterables[i], elapsedTime);\n\n\t\t\tif(i % this.iterationsPerCheck === 0) {\n\t\t\t\tlet now = performance.now();\n\t\t\t\tif(now - started >= this.maxMsPerFrame) {\n\t\t\t\t\tthis.remainingInstancesToRun = iterables.slice(i + 1);\n\t\t\t\t\tthis.remainingInstancesStartTime = elapsedTime;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthis.remainingInstancesToRun = [];\n\t\tthis.remainingInstancesStartTime = null;\n\t}\n\tbeforeRunIterables() {}\n\n\tabstract getIterables(): Array<T>;\n\tabstract updateIterable(iterable: T, elapsedTime: number): void;\n}\n\nexport interface IterableSystemConfig extends SystemConfig {\n\titerationsPerCheck?: number\n\tmaxMsPerFrame?: number\n}\n","import type BaseWorld from '../world';\nimport type BaseEntity from '../entity';\nimport type { ComponentDefinitionMap, ComponentMap } from '../component-definition';\nimport IterableSystem, { type IterableSystemConfig } from './iterable-system';\n\n// Iterates the entities that own a given set of components on the main thread. Entities are added\n// and removed automatically as the world emits entity-added / entity-removed / component changes.\nexport default abstract class EntitySystem<C extends ComponentMap, T extends BaseEntity<C> = BaseEntity<C>> extends IterableSystem<C, T> {\n\tentities: Array<T> = [];\n\toptions: EntitySystemConfig<C>;\n\n\tconstructor(world: BaseWorld<ComponentDefinitionMap, C>, options: EntitySystemConfig<C> = { name: 'EntitySystem' }) {\n\t\tif(!options.iterationsPerCheck) {\n\t\t\toptions.iterationsPerCheck = 10;\n\t\t}\n\t\tif(!options.maxMsPerFrame) {\n\t\t\toptions.maxMsPerFrame = 4;\n\t\t}\n\n\t\tsuper(world, options);\n\t\tthis.options = options;\n\n\t\tworld.on('entity-added', (entity: BaseEntity<C>) => {\n\t\t\tif(this.checkAddEntity(entity) && this.options.updateEntityOnAdd) {\n\t\t\t\tthis.updateEntity(entity as T, 0);\n\t\t\t}\n\t\t});\n\t\tworld.on('entity-removed', (entity: BaseEntity<C>) => {\n\t\t\tthis.removeEntity(entity);\n\t\t});\n\n\t\tworld.entities.forEach(entity => {\n\t\t\tthis.checkAddEntity(entity);\n\t\t});\n\t}\n\n\tgetIterables(): Array<T> {\n\t\treturn this.entities.filter(entity => !entity.components.entity.dead);\n\t}\n\tupdateIterable(entity: T, elapsedTime: number): void {\n\t\tif(entity.components.entity.dead) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.updateEntity(entity, elapsedTime);\n\t}\n\tfilterEntity(entity: BaseEntity<C>): boolean {\n\t\treturn !entity.components.entity.isStatic;\n\t}\n\tisEntityInSystem(entity: BaseEntity<C>) {\n\t\treturn this.entities.indexOf(entity as T) !== -1;\n\t}\n\tabstract updateEntity(entity: T, elapsedTime: number): void;\n\n\tcheckAddEntity(entity: BaseEntity<C>): boolean {\n\t\tif(this.options.components && this.options.components.filter(component => !!entity.components[component]).length !== this.options.components.length) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif(this.filterEntity(entity)) {\n\t\t\tthis.entities.push(entity as T);\n\t\t\treturn true;\n\t\t} else {\n\t\t\treturn false;\n\t\t}\n\t}\n\tremoveEntity(entity: BaseEntity<C>) {\n\t\tlet index = this.entities.indexOf(entity as T);\n\t\tif(index !== -1) {\n\t\t\tthis.entities.splice(index, 1);\n\t\t}\n\t}\n\n\tshouldRun(): boolean {\n\t\treturn this.entities.length > 0;\n\t}\n}\n\nexport interface EntitySystemConfig<C extends ComponentMap> extends IterableSystemConfig {\n\tcomponents?: Array<keyof C>\n\tupdateEntityOnAdd?: boolean\n}\n","// Minimal Worker-like base used for the main-thread fallback when real Web Workers / SharedArrayBuffer\n// are unavailable. Params are intentionally loose since this is the (serialization) boundary that\n// mirrors the DOM Worker interface.\nexport default abstract class WebWorker {\n\tabstract postMessage(message: any): void | Promise<void>;\n\tonmessage(message: any, transferrables: Array<any> = []): void {\n\n\t}\n}\n","import type { EntityUpdateComponents, QueryDelta, UpdateEntityConfigObject } from '../component-system';\n\n// A worker keeps one persistent list per query and applies the delta each run carries: drop the entities that\n// left, then upsert the ones that joined (or whose component blocks changed). Because the main thread only\n// sends changes, a steady-state run - where membership is unchanged - carries empty arrays and this returns the\n// existing list untouched with no allocation, which is the whole point of the delta protocol: we stop\n// re-transmitting (and re-cloning) every entity id every single frame.\n//\n// The list is returned (rather than mutated in place) because removals rebuild it via filter; callers must store\n// the returned reference back as their persistent list.\nexport function applyQueryDelta<T extends EntityUpdateComponents>(\n\tlist: Array<UpdateEntityConfigObject<T>>,\n\tdelta: QueryDelta<T>,\n): Array<UpdateEntityConfigObject<T>> {\n\tif(delta.removed.length) {\n\t\tconst removedSet = new Set(delta.removed);\n\t\tlist = list.filter(entry => !removedSet.has(entry.entityId));\n\t}\n\n\tif(delta.added.length) {\n\t\t// Map existing members so a re-added entity (its component set changed) replaces its entry in place\n\t\t// instead of being duplicated; genuinely new entities are appended.\n\t\tconst indexByEid = new Map<number, number>();\n\t\tlist.forEach((entry, index) => indexByEid.set(entry.entityId, index));\n\n\t\tfor(let entity of delta.added) {\n\t\t\tconst existing = indexByEid.get(entity.entityId);\n\t\t\tif(existing !== undefined) {\n\t\t\t\tlist[existing] = entity;\n\t\t\t} else {\n\t\t\t\tindexByEid.set(entity.entityId, list.length);\n\t\t\t\tlist.push(entity);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn list;\n}\n","import WebWorker from './web-worker';\nimport { applyQueryDelta } from './apply-query-delta';\nimport type ComponentWorkerMessage from './component-worker-message';\nimport type { EntityEvent } from './component-worker-message';\nimport type { ComponentMap } from '../../component-definition';\nimport type { ComponentSystemCallbacks, ComponentSystemWorld, EntityQueryComponents, EntityUpdateComponents, EntityUpdateFunction, QueryDelta, UpdateEntityConfigObject } from '../component-system';\n\n// Main-thread fallback that runs the update function synchronously when real Web Workers /\n// SharedArrayBuffer are unavailable. It runs in-process but still keeps persistent per-query lists and applies\n// the same membership deltas the real worker does, so both backends stay behavior-identical.\nexport default class ComponentWebWorker<C extends ComponentMap, T extends EntityUpdateComponents<C>, W extends ComponentSystemWorld = ComponentSystemWorld> extends WebWorker {\n\tprivate updateFunction: EntityUpdateFunction<C, T, W>;\n\tprivate entities: Array<UpdateEntityConfigObject<T>> = [];\n\tprivate queryEntities: { [key: string]: Array<UpdateEntityConfigObject<T>> } = {};\n\n\tconstructor(updateFunction: EntityUpdateFunction<C, T, W>) {\n\t\tsuper();\n\t\tthis.updateFunction = updateFunction;\n\t}\n\n\tpostMessage(message: ComponentWorkerMessage<W>): void {\n\t\tif(message.type === 'init') {\n\t\t\tthis.onMessageTyped({\n\t\t\t\ttype: 'loaded',\n\t\t\t});\n\t\t} else if(message.type === 'run') {\n\t\t\tlet entityEvents: Array<EntityEvent> = [];\n\t\t\tlet createdEntities: Array<Record<string, unknown>> = [];\n\n\t\t\tthis.entities = applyQueryDelta(this.entities, message.entities as QueryDelta<T>);\n\n\t\t\tlet queries: EntityQueryComponents<C> = {};\n\t\t\tObject.entries(message.queries).forEach(([queryKey, delta]) => {\n\t\t\t\tconst list = applyQueryDelta(this.queryEntities[queryKey] ?? [], delta as QueryDelta<T>);\n\t\t\t\tthis.queryEntities[queryKey] = list;\n\t\t\t\tqueries[queryKey] = list;\n\t\t\t});\n\n\t\t\tlet callbacks: ComponentSystemCallbacks<C> = {\n\t\t\t\tentityComponentChanged<K extends keyof C, P extends keyof C[K]>(entityId: number, componentName: K, prop: P, value: C[K][P]) {\n\t\t\t\t\tentityEvents.push({\n\t\t\t\t\t\tentityId,\n\t\t\t\t\t\tevent: 'component-property-updated',\n\t\t\t\t\t\targs: [componentName, prop, value],\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\temitEntityEvent(entityId: number, event: string, ...args: Array<unknown>) {\n\t\t\t\t\tentityEvents.push({\n\t\t\t\t\t\tentityId,\n\t\t\t\t\t\tevent,\n\t\t\t\t\t\targs,\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\tentityDied(entityId: number) {\n\t\t\t\t\tentityEvents.push({\n\t\t\t\t\t\tentityId,\n\t\t\t\t\t\tevent: 'death',\n\t\t\t\t\t\targs: [],\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\tcreateEntity(config: Record<string, unknown>) {\n\t\t\t\t\tcreatedEntities.push(config);\n\t\t\t\t},\n\t\t\t};\n\t\t\tif(this.updateFunction.preRun) {\n\t\t\t\tthis.updateFunction.preRun(message.world, this.entities, queries, callbacks);\n\t\t\t}\n\t\t\tthis.entities.forEach(entity => {\n\t\t\t\tthis.updateFunction(message.world, entity.entityId, entity.components, queries, callbacks);\n\t\t\t});\n\n\t\t\tif(this.updateFunction.entityRemoved) {\n\t\t\t\tfor(let entityId of message.entities.removed) {\n\t\t\t\t\tthis.updateFunction.entityRemoved(message.world, entityId, callbacks);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.onMessageTyped({\n\t\t\t\ttype: 'run-complete',\n\t\t\t\trunTime: 0,\n\t\t\t\tevents: entityEvents,\n\t\t\t\tcreated: createdEntities,\n\t\t\t});\n\t\t}\n\t}\n\n\tonMessageTyped(message: ComponentWorkerMessage<W>) {\n\t\tthis.onmessage({\n\t\t\tdata: message,\n\t\t});\n\t}\n}\n","import type BaseWorld from '../world';\nimport type BaseEntity from '../entity';\nimport type { BaseComponent, ComponentDefinitionMap, ComponentMap, RegisteredComponentRegistry } from '../component-definition';\nimport type { ComponentTypedArray } from '../memory-component';\nimport System, { type SystemConfig } from './system';\nimport type ComponentWorkerMessage from './workers/component-worker-message';\nimport ComponentWebWorker from './workers/component-web-worker';\n\nconst MAIN_QUERY_NAME = '___main';\n\n// Runs an update function over the raw shared-memory blocks of its matched entities, ideally on a\n// separate thread. When Web Workers + SharedArrayBuffer are available the work happens on `getWorker()`;\n// otherwise it falls back to running synchronously on the main thread via ComponentWebWorker.\n//\n// This is deliberately free of any game concepts (no factions, fog of war, position, etc). Games\n// inject whatever extra per-run data they need through `addDataToWorld`.\nexport default abstract class ComponentSystem<\n\tC extends ComponentMap,\n\tT extends EntityUpdateComponents<C>,\n\tW extends ComponentSystemWorld = ComponentSystemWorld,\n> extends System<C> {\n\tentities: Array<BaseEntity<C>> = [];\n\toptions: ComponentSystemConfig<C, T, W>;\n\n\tworker: Worker | ComponentWebWorker<C, T, W>;\n\tisWorkerThread: boolean;\n\n\tprivate loaded = false;\n\tprivate loadingPromise: { promise: Promise<void>, resolve: (value: void | PromiseLike<void>) => void } | null = null;\n\tprivate isRunning = false;\n\tprivate queryEntities: { [key: string]: Array<BaseEntity<C>> } = {};\n\t// Per-query membership changes accumulated since the last run(). Each run() flushes these to the worker as a\n\t// delta - added entities carry their component blocks, removed carry just their eid - so a steady-state run\n\t// (unchanged membership) sends empty arrays instead of re-transmitting every entity id every single frame.\n\t// The worker keeps its own persistent list and applies these deltas to it (see applyQueryDelta).\n\tprivate queryDeltas: { [key: string]: MembershipDelta<C> } = {};\n\n\t// Optional hook for subclasses to attach extra data to the world object sent to the worker. Subclasses\n\t// declare the concrete shape through `W` and fill in its fields here.\n\taddDataToWorld?(world: W): void;\n\n\tconstructor(world: BaseWorld<ComponentDefinitionMap, C>, options: ComponentSystemConfig<C, T, W>) {\n\t\tsuper(world, options);\n\t\tthis.options = options;\n\t\tObject.keys(this.options.queries ?? {}).forEach(queryName => {\n\t\t\tthis.queryEntities[queryName] = [];\n\t\t});\n\n\t\tworld.on('entity-added', (entity: BaseEntity<C>) => {\n\t\t\tthis.checkAddEntity(entity);\n\t\t});\n\t\tworld.on('entity-removed', (entity: BaseEntity<C>) => {\n\t\t\tthis.removeEntity(entity);\n\t\t});\n\n\t\tworld.entities.forEach(entity => {\n\t\t\tthis.checkAddEntity(entity);\n\t\t});\n\n\t\tif(!options.forceMainThread && typeof globalThis.Worker !== 'undefined' && typeof globalThis.SharedArrayBuffer !== 'undefined') {\n\t\t\tthis.worker = options.getWorker();\n\t\t\tthis.isWorkerThread = true;\n\t\t} else {\n\t\t\tthis.worker = new ComponentWebWorker(options.updateFunction);\n\t\t\tthis.isWorkerThread = false;\n\t\t}\n\n\t\tthis.initWorker();\n\t}\n\n\tprivate initWorker() {\n\t\tthis.worker.onmessage = (e: MessageEvent) => {\n\t\t\tlet message = e.data as ComponentWorkerMessage;\n\t\t\tif(message.type === 'loaded') {\n\t\t\t\tthis.loaded = true;\n\t\t\t\tif(this.loadingPromise) {\n\t\t\t\t\tthis.loadingPromise.resolve();\n\t\t\t\t\tthis.loadingPromise = null;\n\t\t\t\t}\n\t\t\t} else if(message.type === 'run-complete') {\n\t\t\t\tthis.isRunning = false;\n\t\t\t\tif(this.isWorkerThread) {\n\t\t\t\t\tthis.world.emit(`system-${this.name}-worker-finished`, message.runTime);\n\t\t\t\t}\n\n\t\t\t\tmessage.events.forEach(event => {\n\t\t\t\t\t// A system can report events (deaths, component changes) for entities it only knows through a\n\t\t\t\t\t// sub-query - e.g. a collision system that kills a station it found in a spatial query but that\n\t\t\t\t\t// isn't in its main query. Look the entity up on the world so those still route even when the\n\t\t\t\t\t// entity was never part of this system's main query cache.\n\t\t\t\t\tconst entity = this.world.getEntityByEid(event.entityId);\n\t\t\t\t\tif(!entity) {\n\t\t\t\t\t\tconsole.warn(`Could not find entity with id ${event.entityId}`);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tentity.emit(event.event, ...event.args);\n\t\t\t\t});\n\n\t\t\t\t// Fulfill any creation requests after deaths, so an entity created this run isn't immediately removed.\n\t\t\t\t// loadEntity runs the full factory expansion + finishLoading and emits `entity-added`, so every system\n\t\t\t\t// (including this one) picks the new entity up on the next run.\n\t\t\t\tmessage.created.forEach(config => {\n\t\t\t\t\tthis.world.loadEntity(config);\n\t\t\t\t});\n\n\t\t\t\tif(this.isWorkerThread) {\n\t\t\t\t\tthis.world.emit(`system-${this.name}-worker-events-finished`, message.runTime);\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\n\t\tconst message: ComponentWorkerMessage = {\n\t\t\ttype: 'init',\n\t\t};\n\n\t\tthis.worker.postMessage(message);\n\t}\n\n\tinit(): Promise<void> | void {\n\t\tif(this.loaded) {\n\t\t\treturn;\n\t\t} else if(this.loadingPromise) {\n\t\t\treturn this.loadingPromise.promise;\n\t\t}\n\n\t\tlet { promise, resolve } = Promise.withResolvers<void>();\n\t\tthis.loadingPromise = {\n\t\t\tpromise,\n\t\t\tresolve,\n\t\t};\n\t\treturn promise;\n\t}\n\n\tupdate(elapsedTime: number): boolean {\n\t\t// Only run worker if the last run completed already\n\t\tif(this.isRunning) {\n\t\t\tthis.currentDelta += elapsedTime;\n\n\t\t\treturn false;\n\t\t} else {\n\t\t\treturn super.update(elapsedTime);\n\t\t}\n\t}\n\trun(elapsedTime: number): void {\n\t\t// gameTime + elapsedTime are the only fields the base guarantees; addDataToWorld fills in the rest of `W`,\n\t\t// so the literal is built as the base shape and widened to W for that hook to complete.\n\t\tconst world = {\n\t\t\tgameTime: this.world.gameTime,\n\t\t\telapsedTime,\n\t\t} as W;\n\t\tthis.addDataToWorld?.(world);\n\n\t\tthis.isRunning = true;\n\t\tlet entities = this.buildQueryDelta(MAIN_QUERY_NAME, this.options);\n\t\tlet queries: { [key: string]: QueryDelta<T> } = {};\n\t\tObject.entries(this.options.queries ?? {}).forEach(([queryKey, query]) => {\n\t\t\tqueries[queryKey] = this.buildQueryDelta(queryKey, query);\n\t\t});\n\t\tlet message: ComponentWorkerMessage<W> = {\n\t\t\ttype: 'run',\n\t\t\tworld,\n\t\t\tentities,\n\t\t\tqueries,\n\t\t};\n\t\tthis.worker.postMessage(message);\n\t}\n\n\t// Flushes the pending membership changes for a query into the delta the worker applies to its persistent\n\t// list. Added entities are resolved to their shared-memory component blocks here (the only per-frame cost\n\t// left, and only for entities that actually joined/changed); removed entities are sent as bare eids. The\n\t// buffers are reset so the next run only carries what changed since this one.\n\tprivate buildQueryDelta(queryName: string, query: ComponentSystemQuery<C>): QueryDelta<T> {\n\t\tconst delta = this.getQueryDelta(queryName);\n\t\tconst added = delta.added.map(entity => ({\n\t\t\tentityId: entity.eid,\n\t\t\tcomponents: this.buildComponents(entity, query),\n\t\t}));\n\t\tconst removed = delta.removed;\n\t\tthis.queryDeltas[queryName] = { added: [], removed: [] };\n\n\t\treturn { added, removed };\n\t}\n\tprivate buildComponents(entity: BaseEntity<C>, query: ComponentSystemQuery<C>): T {\n\t\tconst components = {} as T;\n\t\tconst registry = this.world.registry as RegisteredComponentRegistry<C>;\n\t\t[\n\t\t\t...query.required,\n\t\t\t...query.optional ?? [],\n\t\t].forEach(componentName => {\n\t\t\tconst component = entity.components[componentName];\n\t\t\tconst memoryComponent = registry[componentName].memoryComponent;\n\t\t\tif(component && memoryComponent) {\n\t\t\t\tcomponents[componentName] = memoryComponent.getBlock(component.index) as T[typeof componentName];\n\t\t\t}\n\t\t});\n\n\t\treturn components;\n\t}\n\n\tisEntityInSystem(entity: BaseEntity<C>) {\n\t\treturn this.entities.indexOf(entity) !== -1;\n\t}\n\tprivate matchesQuery(entity: BaseEntity<C>, query: ComponentSystemQuery<C>): boolean {\n\t\tif(entity.components.entity.dead) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif(query.required.find(component => !entity.components[component])) {\n\t\t\treturn false;\n\t\t}\n\t\tif(query.not?.find(component => !!entity.components[component])) {\n\t\t\treturn false;\n\t\t}\n\t\tif(query.filter && !query.filter(entity)) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t}\n\tprivate getQueryDelta(queryName: string): MembershipDelta<C> {\n\t\tlet delta = this.queryDeltas[queryName];\n\t\tif(!delta) {\n\t\t\tdelta = this.queryDeltas[queryName] = { added: [], removed: [] };\n\t\t}\n\n\t\treturn delta;\n\t}\n\t// Records that an entity now belongs to a query, so the next run() sends its (possibly changed) component\n\t// blocks. An entity queued for removal this same frame is un-queued instead: the worker never learned it\n\t// left, so the net effect is a re-send of fresh components rather than a remove+add churn.\n\tprivate markAdded(delta: MembershipDelta<C>, entity: BaseEntity<C>) {\n\t\tconst removedIndex = delta.removed.indexOf(entity.eid);\n\t\tif(removedIndex !== -1) {\n\t\t\tdelta.removed.splice(removedIndex, 1);\n\t\t}\n\t\tif(delta.added.indexOf(entity) === -1) {\n\t\t\tdelta.added.push(entity);\n\t\t}\n\t}\n\t// Records that an entity left a query. If it was only queued to be added this same frame (never sent to the\n\t// worker), we just drop the pending add - the worker never knew about it, so there is nothing to remove.\n\tprivate markRemoved(delta: MembershipDelta<C>, entity: BaseEntity<C>) {\n\t\tconst addedIndex = delta.added.indexOf(entity);\n\t\tif(addedIndex !== -1) {\n\t\t\tdelta.added.splice(addedIndex, 1);\n\t\t\treturn;\n\t\t}\n\t\tif(delta.removed.indexOf(entity.eid) === -1) {\n\t\t\tdelta.removed.push(entity.eid);\n\t\t}\n\t}\n\tprivate updateEntityList(queryName: string, list: Array<BaseEntity<C>>, entity: BaseEntity<C>, shouldInclude: boolean) {\n\t\tconst index = list.indexOf(entity);\n\t\tconst delta = this.getQueryDelta(queryName);\n\t\tif(shouldInclude) {\n\t\t\tif(index === -1) {\n\t\t\t\tlist.push(entity);\n\t\t\t}\n\t\t\t// Always (re)queue the component blocks: the entity either just joined or a relevant component was\n\t\t\t// added/removed, so the blocks the worker holds for it may be stale.\n\t\t\tthis.markAdded(delta, entity);\n\t\t} else if(index !== -1) {\n\t\t\tlist.splice(index, 1);\n\t\t\tthis.markRemoved(delta, entity);\n\t\t}\n\t}\n\n\tcheckAddEntity(entity: BaseEntity<C>): boolean {\n\t\tconst shouldAddToMain = this.matchesQuery(entity, this.options);\n\t\tthis.updateEntityList(MAIN_QUERY_NAME, this.entities, entity, shouldAddToMain);\n\n\t\tObject.entries(this.options.queries ?? {}).forEach(([queryName, query]) => {\n\t\t\tconst queryList = this.queryEntities[queryName] ?? (this.queryEntities[queryName] = []);\n\t\t\tthis.updateEntityList(queryName, queryList, entity, this.matchesQuery(entity, query));\n\t\t});\n\n\t\treturn shouldAddToMain;\n\t}\n\tremoveEntity(entity: BaseEntity<C>) {\n\t\tthis.updateEntityList(MAIN_QUERY_NAME, this.entities, entity, false);\n\t\tObject.entries(this.queryEntities).forEach(([queryName, list]) => {\n\t\t\tthis.updateEntityList(queryName, list, entity, false);\n\t\t});\n\t}\n\n\tshouldRun(): boolean {\n\t\treturn this.entities.length > 0;\n\t}\n\n\tdestroy() {\n\t\tif('terminate' in this.worker) {\n\t\t\tthis.worker.terminate();\n\t\t}\n\t}\n}\n\nexport type EntityUpdateComponents<C extends ComponentMap = ComponentMap> = { [K in keyof C]?: ComponentTypedArray };\nexport type EntityQueryComponents<C extends ComponentMap = ComponentMap> = { [key: string]: Array<{ entityId: number, components: EntityUpdateComponents<C> }> };\ntype EntityUpdateFunctionImpl<C extends ComponentMap, T extends EntityUpdateComponents<C>, W extends ComponentSystemWorld = ComponentSystemWorld> = (\n\tworld: W,\n\tentityId: number,\n\tcomponents: T,\n\tqueries: EntityQueryComponents<C>,\n\tcallbacks: ComponentSystemCallbacks<C>,\n) => void;\n// `T` is required (no default) so every update function must spell out exactly which components it operates on\n// and their concrete typed arrays, rather than falling back to the full component list. `W` is the concrete\n// per-run world shape the owning system builds in addDataToWorld; it defaults to the bare ComponentSystemWorld.\nexport type EntityUpdateFunction<C extends ComponentMap, T extends EntityUpdateComponents<C>, W extends ComponentSystemWorld = ComponentSystemWorld> = EntityUpdateFunctionImpl<C, T, W> & {\n\tpreRun?: EntityUpdatePreRunFunction<C, T, W>\n\tentityRemoved?: EntityRemovedFunction<C, W>\n};\nexport type EntityUpdatePreRunFunction<C extends ComponentMap, T extends EntityUpdateComponents<C>, W extends ComponentSystemWorld = ComponentSystemWorld> = (\n\tworld: W,\n\tentities: Array<UpdateEntityConfigObject<T>>,\n\tqueries: EntityQueryComponents<C>,\n\tcallbacks: ComponentSystemCallbacks<C>,\n) => void;\nexport type EntityRemovedFunction<C extends ComponentMap = ComponentMap, W extends ComponentSystemWorld = ComponentSystemWorld> = (\n\tworld: W,\n\tentityId: number,\n\tcallbacks: ComponentSystemCallbacks<C>,\n) => void;\nexport type UpdateEntityConfig<T extends EntityUpdateComponents = EntityUpdateComponents> = number | UpdateEntityConfigObject<T>;\nexport type UpdateEntityConfigObject<T extends EntityUpdateComponents> = {\n\tentityId: number\n\tcomponents: T\n};\n\n// The membership changes for a single query since the last run, in the shape sent to the worker: entities that\n// joined (or whose component set changed) with their resolved component blocks, and the eids of entities that\n// left. Empty on a steady-state run, which is what keeps the per-frame postMessage small.\nexport interface QueryDelta<T extends EntityUpdateComponents = EntityUpdateComponents> {\n\tadded: Array<UpdateEntityConfigObject<T>>\n\tremoved: Array<number>\n}\n// The main thread's pre-serialization form of a QueryDelta: it holds the live entities (so their component\n// blocks can be resolved lazily at run time), whereas QueryDelta holds the resolved blocks that cross the wire.\ninterface MembershipDelta<C extends ComponentMap> {\n\tadded: Array<BaseEntity<C>>\n\tremoved: Array<number>\n}\n\n// Base per-run world data. gameTime + elapsedTime are always present; games attach anything else\n// they need via addDataToWorld, declaring the concrete shape through the `W` type parameter that\n// ComponentSystem (and its update function) are generic over.\nexport interface ComponentSystemWorld {\n\tgameTime: number\n\telapsedTime: number\n}\n// A flat entity config a worker asks the main thread to create. Kept as a plain record (not the game's `Cfg`)\n// because ComponentSystem is generic over `C`, not `Cfg`; the main thread hands it straight to world.loadEntity,\n// whose factory expands its `type` against the registered templates.\nexport type CreateEntityConfig = Record<string, unknown>;\n\nexport interface ComponentSystemCallbacks<C extends ComponentMap = ComponentMap> {\n\tentityComponentChanged<K extends keyof C, P extends keyof C[K]>(entityId: number, componentName: K, prop: P, value: C[K][P]): void\n\t// Reports an event of the update function's own choosing, emitted on the entity by that name once the run\n\t// completes. It is the escape hatch from the fixed callbacks above: a system that would otherwise send a\n\t// `component-property-updated` per property can send one event carrying all of them instead, and a listener\n\t// that only cares about that one thing does not have to filter every other property change out of its way.\n\t//\n\t// The args are structured-cloned across the worker boundary, so they have to be plain values. Nothing\n\t// here is checked against `C` - the event is the system's own concept, not a component - so a system that\n\t// emits one should export the name and the args it comes with alongside its update function.\n\temitEntityEvent(entityId: number, event: string, ...args: Array<unknown>): void\n\tentityDied(entityId: number): void\n\t// Requests that the main thread create an entity from `config` once the run completes. Unlike killing (which\n\t// flips an existing shared-memory flag in place), creation can't happen in the worker, so it is deferred to the\n\t// main thread where eid generation, allocation, and factory expansion already live.\n\tcreateEntity(config: CreateEntityConfig): void\n}\n\nexport interface ComponentSystemQuery<C extends ComponentMap = ComponentMap> {\n\trequired: Array<keyof C>\n\toptional?: Array<keyof C>\n\tnot?: Array<keyof C>\n\tfilter?: (entity: BaseEntity<C>) => boolean\n}\n\nexport interface ComponentSystemConfig<\n\tC extends ComponentMap,\n\tT extends EntityUpdateComponents<C>,\n\tW extends ComponentSystemWorld = ComponentSystemWorld,\n> extends SystemConfig, ComponentSystemQuery<C> {\n\tupdateFunction: EntityUpdateFunction<C, T, W>\n\tgetWorker: () => Worker\n\tforceMainThread?: boolean\n\n\tqueries?: { [key: string]: ComponentSystemQuery<C> }\n}\n\n// Re-exported so BaseComponent is reachable from the systems barrel if needed.\nexport type { BaseComponent };\n","import type { ComponentDefinition } from './component-definition';\n\n// The one component every entity is required to have. `dead` and `isStatic` are boolean flags stored in\n// the shared-memory block so a worker that pulls the `entity` component into its query can read them too.\n// `type` is a plain string that names the EntityFactory template this entity was built from; it lives off\n// to the side (not in shared memory), so - unlike `dead`/`isStatic` - it is NOT visible to workers.\n// Games that need a stable string id (or any other per-entity data) add their own component for it.\nexport interface EntityComponent {\n\tindex: number\n\ttype: string\n\tdead: boolean\n\tisStatic: boolean\n}\n\n// The defining config for the entity component. `type` is required - every entity is created from a factory\n// template, named by its type - while `isStatic` is an optional flag supplied up front.\nexport interface EntityComponentConfig {\n\ttype: string\n\tisStatic?: boolean\n}\n\n// Runtime-derived state plus `type`: `dead` is only meaningful once the entity is alive, and `type` is kept\n// so a save can be re-expanded through the factory. `isStatic` is intentionally absent - it comes back from\n// the type's template config, not the save.\nexport interface EntityComponentSerialization {\n\ttype?: string\n\tdead?: boolean\n}\n\nexport const DEAD_INDEX = 0;\nexport const STATIC_INDEX = 1;\n\nexport const entityDefinition: ComponentDefinition<EntityComponent, Uint32Array, EntityComponentConfig, EntityComponentSerialization> = {\n\ttype: Uint32Array,\n\tsize: 2,\n\t// The entity component is always loaded by BaseEntity's constructor, so this never actually gates\n\t// loading; it documents the defining config props and keeps it consistent with game components.\n\tloadProperties: ['type', 'isStatic'],\n\tload(entity, memory, config) {\n\t\tconst index = memory.create([config.dead ? 1 : 0, config.isStatic ? 1 : 0]);\n\t\tconst block = memory.getBlock(index);\n\n\t\treturn {\n\t\t\tindex,\n\t\t\t// Plain (non-memory) property, so it is main-thread only and not readable from workers.\n\t\t\ttype: config.type,\n\t\t\tget dead() {\n\t\t\t\treturn block[DEAD_INDEX] === 1;\n\t\t\t},\n\t\t\tset dead(value: boolean) {\n\t\t\t\tblock[DEAD_INDEX] = value ? 1 : 0;\n\t\t\t},\n\t\t\tget isStatic() {\n\t\t\t\treturn block[STATIC_INDEX] === 1;\n\t\t\t},\n\t\t\tset isStatic(value: boolean) {\n\t\t\t\tblock[STATIC_INDEX] = value ? 1 : 0;\n\t\t\t},\n\t\t};\n\t},\n\tsave(component) {\n\t\t// `type` + `dead` are serialized; `isStatic` (and the rest of the template) come back from the type's\n\t\t// config on reload, so they are deliberately left out.\n\t\tconst config: EntityComponentSerialization = {};\n\t\tif(component.type) {\n\t\t\tconfig.type = component.type;\n\t\t}\n\t\tif(component.dead) {\n\t\t\tconfig.dead = true;\n\t\t}\n\n\t\treturn config;\n\t},\n};\n","import { EventEmitter } from 'eventemitter3';\nimport type BaseWorld from './world';\nimport type { ComponentDefinitionMap, ComponentMap, RegisteredComponentRegistry } from './component-definition';\nimport type { EntityComponent } from './entity-component';\n\n// A bare entity: an eid and a bag of memory-backed components (including the required entity component).\n// `Cfg` is the flat config this entity loads from / saves to; it defaults to `any` so a bare `BaseEntity<C>`\n// stays usable, but a World derives it (via `EntityConfigOf`) so `config` and `load` are fully typed.\nexport default class BaseEntity<C extends ComponentMap = ComponentMap, Cfg = any> extends EventEmitter {\n\tstatic eidCounter = 1;\n\n\treadonly eid: number;\n\tconfig?: Cfg;\n\n\t// The World only uses its `R` param to derive `C`/`Cfg`, so entities reference it Cfg-agnostically via the\n\t// widened `ComponentDefinitionMap` - the instance shape depends on `C`/`Cfg`, not on the concrete registry.\n\tworld: BaseWorld<ComponentDefinitionMap, C, Cfg>;\n\t// The entity component is required on every entity, so it is always present (unlike the game\n\t// components, which are partial). Its `dead`/`isStatic` flags and `id` live here now.\n\tcomponents: Partial<C> & { entity: EntityComponent } = {} as Partial<C> & { entity: EntityComponent };\n\n\tconstructor(world: BaseWorld<ComponentDefinitionMap, C, Cfg>, config?: Cfg) {\n\t\tsuper();\n\n\t\tthis.world = world;\n\t\tthis.eid = BaseEntity.eidCounter++;\n\t\tthis.loadComponent('entity', config ?? {}, false);\n\t\tif(config) {\n\t\t\tthis.load(config);\n\t\t}\n\t}\n\n\t// `emitAdded` is true for runtime additions so systems can react; the loading path (constructor and\n\t// `load`) passes false, since those components are already accounted for when the entity is added.\n\tloadComponent<K extends keyof C>(name: K, config: any, emitAdded = true): C[K] {\n\t\t// registry carries the always-present entity component as an intersection; index the plain game map\n\t\t// here so the generic key stays cleanly typed as C[K].\n\t\tconst definition = (this.world.registry as RegisteredComponentRegistry<C>)[name];\n\t\t// The definition now owns its MemoryComponent, so the memory pool comes straight off it.\n\t\tconst memoryComponent = definition.memoryComponent;\n\t\tconst component = definition.load(this, memoryComponent, config);\n\t\t(this.components as Partial<C>)[name] = component;\n\t\tif(emitAdded) {\n\t\t\tthis.emit('component-added', name, component);\n\t\t}\n\n\t\treturn component;\n\t}\n\tremoveComponent<K extends keyof C>(name: K) {\n\t\tconst component = this.components[name];\n\t\tif(component) {\n\t\t\t(this.world.registry as RegisteredComponentRegistry<C>)[name].memoryComponent.delete(component.index);\n\t\t\tdelete this.components[name];\n\t\t\tthis.emit('component-removed', name);\n\t\t}\n\t}\n\tsetComponent<K extends keyof C, P extends keyof C[K]>(componentName: K, prop: P, value: C[K][P]) {\n\t\tconst component = this.components[componentName];\n\t\tif(!component) {\n\t\t\treturn;\n\t\t}\n\n\t\t// TS can't verify writing to a property of the generic C[K] by a keyof C[K] key, so index through a record.\n\t\t(component as unknown as Record<P, C[K][P]>)[prop] = value;\n\t\tthis.emit('component-property-updated', componentName, prop, value);\n\t}\n\t/**\n\t * NOTE: Does not emit component-property-updated!\n\t */\n\tsetComponentBulk<K extends keyof C>(componentName: K, values: Partial<C[K]>) {\n\t\tconst component = this.components[componentName];\n\t\tif(!component) {\n\t\t\treturn;\n\t\t}\n\n\t\tObject.assign(component, values);\n\t}\n\tdeleteComponent<K extends keyof C>(componentName: K, prop: keyof C[K]) {\n\t\tconst component = this.components[componentName];\n\t\tif(!component) {\n\t\t\treturn;\n\t\t}\n\n\t\tdelete (component as Partial<C[K]>)[prop];\n\t\tthis.emit('component-property-deleted', componentName, prop);\n\t}\n\n\tdeleteAllComponentMemory() {\n\t\tconst registry = this.world.registry as RegisteredComponentRegistry<C>;\n\t\tfor(let name of Object.keys(this.components) as Array<keyof C>) {\n\t\t\tconst component = this.components[name];\n\t\t\tif(component) {\n\t\t\t\tregistry[name].memoryComponent.delete(component.index);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Loads component data from a flat config: every registered component whose `loadProperties` appear in\n\t// the config is handed the entire config. The entity component is skipped here since the constructor\n\t// always loads it up front.\n\tload(config: Cfg) {\n\t\t// The flat config is keyed by string props; index it as a record for the `loadProperties` checks.\n\t\tconst props = config as Record<string, unknown>;\n\t\t// registry carries the intersected entity component; index the plain game map so `definition` stays typed.\n\t\tconst registry = this.world.registry as RegisteredComponentRegistry<C>;\n\t\tfor(let name of Object.keys(registry) as Array<keyof C>) {\n\t\t\tif(name === 'entity') {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst definition = registry[name];\n\t\t\t// Deferred components wait for finishLoading, once every entity in the batch exists.\n\t\t\tif(definition.loadInFinishLoading) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif(definition.loadProperties.some(prop => prop in props)) {\n\t\t\t\tthis.loadComponent(name, config, false);\n\t\t\t}\n\t\t}\n\n\t\tthis.config = config;\n\t}\n\tsave(): Cfg {\n\t\t// Every component's saver returns flat props merged into one shared config, mirroring how `load`\n\t\t// hands that same flat config to each loader.\n\t\tconst config: { [key: string]: any } = {};\n\n\t\t// registry carries the intersected entity component; index the plain game map here (the entity key\n\t\t// still resolves at runtime, so its definition is picked up as well).\n\t\tconst registry = this.world.registry as RegisteredComponentRegistry<C>;\n\t\tconst components = this.components as Partial<C>;\n\t\tfor(let name of Object.keys(components) as Array<keyof C>) {\n\t\t\tconst definition = registry[name];\n\t\t\tconst component = components[name];\n\t\t\tif(definition.save && component) {\n\t\t\t\tObject.assign(config, definition.save(component));\n\t\t\t}\n\t\t}\n\n\t\t// The merged serialization slices reconstruct a (partial) flat config, which is exactly `Cfg`.\n\t\treturn config as Cfg;\n\t}\n\t// Hook for games that need a second pass once every entity in a load batch exists. The base pass loads any\n\t// components flagged `loadInFinishLoading`, which `load` deliberately skipped; overrides should call super.\n\tfinishLoading() {\n\t\tconst config = this.config;\n\t\tif(!config) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst props = config as Record<string, unknown>;\n\t\tconst registry = this.world.registry as RegisteredComponentRegistry<C>;\n\t\tfor(let name of Object.keys(registry) as Array<keyof C>) {\n\t\t\tif(name === 'entity') {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst definition = registry[name];\n\t\t\tif(definition.loadInFinishLoading && definition.loadProperties.some(prop => prop in props)) {\n\t\t\t\tthis.loadComponent(name, config, false);\n\t\t\t}\n\t\t}\n\t}\n}\n","import BaseEntity from './entity';\nimport type BaseWorld from './world';\nimport type { ComponentDefinitionMap, ComponentMap } from './component-definition';\n\n// Maps an entity `type` name to a base (template) config. Loading an entity of a given type layers the\n// caller's config on top of that base, so shared static data (a goblin's maxHealth, say) is declared once\n// here instead of being saved on every entity - a save then only needs the `type` plus the entity's runtime\n// serialization. BaseWorld#loadEntity goes through the factory, so every load is type-expanded.\nexport default class EntityFactory<C extends ComponentMap = ComponentMap, Cfg = any> {\n\t// Set by BaseWorld when the factory is attached to it. The World only uses `R` to derive `C`/`Cfg`, so the\n\t// factory references it Cfg-agnostically through the widened `ComponentDefinitionMap`.\n\tworld!: BaseWorld<ComponentDefinitionMap, C, Cfg>;\n\t// type name -> its base config.\n\tconfigs: { [type: string]: Cfg };\n\n\tconstructor(configs: { [type: string]: Cfg } = {}) {\n\t\tthis.configs = configs;\n\t}\n\n\t// Register (or replace) the base config for an entity type.\n\tregister(type: string, config: Cfg) {\n\t\tthis.configs[type] = config;\n\t}\n\n\t// Layer a config over its type's base config. A config with no (or an unknown) `type` passes through\n\t// unchanged, so fully-specified configs can still be loaded directly.\n\tgetConfig(config: Cfg): Cfg {\n\t\tconst type = (config as { type?: string } | undefined)?.type;\n\t\tconst base = type ? this.configs[type] : undefined;\n\t\treturn base ? { ...base, ...config } : config;\n\t}\n\n\t// Build a type-expanded entity and add it to the world.\n\tloadEntity(config: Cfg, created = true): BaseEntity<C, Cfg> {\n\t\tconst entity = this.createEntity(this.getConfig(config));\n\t\treturn this.world.addEntity(entity, created);\n\t}\n\n\t// Hook for games that map types to BaseEntity subclasses; override to return a subclass per config.type.\n\tprotected createEntity(config: Cfg): BaseEntity<C, Cfg> {\n\t\treturn new BaseEntity<C, Cfg>(this.world, config);\n\t}\n}\n","import { EventEmitter } from 'eventemitter3';\nimport { MAX_BYTE_OFFSET_LENGTH, MemoryHeap } from '@daneren2005/shared-memory-objects';\nimport MemoryComponent from './memory-component';\nimport type BaseEntity from './entity';\nimport type System from './systems/system';\nimport EntitySystem from './systems/entity-system';\nimport ComponentSystem from './systems/component-system';\nimport type {\n\tBaseComponent, ComponentDefinitionMap, ComponentMap, ComponentRegistry, ComponentsOf,\n\tEntityConfigOf, RegisteredComponentDefinition, RegisteredComponentRegistry,\n} from './component-definition';\nimport { entityDefinition, type EntityComponent } from './entity-component';\nimport EntityFactory from './entity-factory';\n\nconst DEFAULT_HEAP_SIZE = MAX_BYTE_OFFSET_LENGTH;\n\nexport interface WorldOptions<C extends ComponentMap = ComponentMap, Cfg = any> {\n\theapSize?: number\n\t// Supplies the type -> base config templates; defaults to an empty factory that loads configs as-is.\n\tfactory?: EntityFactory<C, Cfg>\n}\n\n// The serialized shape of a whole world: the flat list of entity configs to (re)build plus the world clocks. A\n// game can layer its own fields on top; the World itself only needs `entities` (the same flat `Cfg` objects\n// `loadEntity` / `save` deal with) and the optional time state that `load` restores.\nexport interface WorldConfig<Cfg = any> {\n\tentities: Array<Cfg>\n\t// Simulation clock; restored so a saved world resumes at the same in-game time. Defaults to 0.\n\tgameTime?: number\n\t// Real elapsed play time; kept separate from gameTime and unaffected by timeScale. Defaults to 0.\n\tplayerTime?: number\n\t// Simulation speed multiplier. Defaults to 1.\n\ttimeScale?: number\n}\n\n// A simple wrapper around a set of entities + systems. On construction it creates one\n// MemoryComponent per entry in the component registry so entities can load/save themselves without\n// each game re-declaring loaders/savers. It has no game specific load/save/terrain/faction logic -\n// that all belongs in the game that consumes this library.\n// The World is generic over `R`, the registry of component definitions a game supplies. Both the\n// component-instance map `C` and the flat entity config `Cfg` are derived from `R` (via `ComponentsOf` /\n// `EntityConfigOf`) and default off it, so `new BaseWorld(registry)` infers `R` from its argument and every\n// entity's components and config are typed without the game declaring any composite types by hand.\nexport default class BaseWorld<\n\tR extends ComponentDefinitionMap = ComponentDefinitionMap,\n\tC extends ComponentMap = ComponentsOf<R>,\n\tCfg = EntityConfigOf<R>,\n> extends EventEmitter {\n\theap: MemoryHeap;\n\t// The entity component is always registered on top of the game's components so every entity can be\n\t// given one automatically. Each registered definition carries its own MemoryComponent, so a\n\t// component's memory pool is reachable straight from `registry[name].memoryComponent`.\n\tregistry: RegisteredComponentRegistry<C> & { entity: RegisteredComponentDefinition<EntityComponent> };\n\t// Every entity is built through the factory, which expands its `type` against the registered templates.\n\tfactory: EntityFactory<C, Cfg>;\n\n\tentities: Array<BaseEntity<C, Cfg>> = [];\n\tentitiesByEid: { [eid: number]: BaseEntity<C, Cfg> } = {};\n\tsystems: Array<System<C>> = [];\n\n\tgameTime = 0;\n\t// Real (unscaled) time the player has spent in the world. Unlike gameTime it keeps advancing while paused and\n\t// is never multiplied by timeScale.\n\tplayerTime = 0;\n\t// Multiplier applied to elapsedTime before it advances gameTime / runs systems, so a game can speed up or slow\n\t// down simulation without touching the real frame delta.\n\ttimeScale = 1;\n\t// While paused, update still accrues playerTime but skips advancing gameTime and running systems.\n\tpaused = false;\n\tdestroyed = false;\n\n\t// The game supplies its own components as `R`; the entity component is added automatically, so it must not\n\t// be part of the passed registry.\n\tconstructor(registry: R, options: WorldOptions<C, Cfg> = {}) {\n\t\tsuper();\n\n\t\tthis.heap = new MemoryHeap({ bufferSize: options.heapSize ?? DEFAULT_HEAP_SIZE });\n\n\t\t// Register every supplied definition (plus the always-present entity component) by attaching a freshly\n\t\t// allocated MemoryComponent to it, so the memory pool lives alongside the definition on the registry.\n\t\tconst inputRegistry = { ...registry, entity: entityDefinition } as ComponentRegistry<C> & { entity: typeof entityDefinition };\n\t\tconst registry_: Record<string, RegisteredComponentDefinition<BaseComponent>> = {};\n\t\tfor(let name of Object.keys(inputRegistry)) {\n\t\t\tconst definition = inputRegistry[name as keyof typeof inputRegistry];\n\t\t\tregistry_[name] = {\n\t\t\t\t...definition,\n\t\t\t\tmemoryComponent: new MemoryComponent(this.heap, definition.type, definition.size),\n\t\t\t};\n\t\t}\n\t\tthis.registry = registry_ as RegisteredComponentRegistry<C> & { entity: RegisteredComponentDefinition<EntityComponent> };\n\n\t\tthis.factory = options.factory ?? new EntityFactory<C, Cfg>();\n\t\tthis.factory.world = this;\n\t}\n\n\tasync init() {\n\t\tawait Promise.all(this.systems.map(system => system.init()).filter(promise => promise instanceof Promise));\n\t}\n\n\taddEntity(entity: BaseEntity<C, Cfg>, created = true): BaseEntity<C, Cfg> {\n\t\tthis.entities.push(entity);\n\t\tthis.entitiesByEid[entity.eid] = entity;\n\t\tentity.world = this;\n\n\t\tentity.on('component-added', (name: keyof C) => {\n\t\t\tthis.addEntityToComponentSystem(entity, name);\n\t\t});\n\t\tentity.on('component-removed', (name: keyof C) => {\n\t\t\tthis.removeEntityFromComponentSystem(entity, name);\n\t\t});\n\n\t\tif(created) {\n\t\t\tentity.finishLoading();\n\t\t\tthis.emit('entity-added', entity);\n\t\t\t// killEntity / killEntityWorker flag the entity dead and emit `death`; remove it here.\n\t\t\tentity.on('death', () => {\n\t\t\t\tthis.onEntityDied(entity);\n\t\t\t});\n\t\t}\n\n\t\treturn entity;\n\t}\n\tloadEntity(config: Cfg, created = true): BaseEntity<C, Cfg> {\n\t\t// The factory expands the config's `type` against the registered templates before building the entity.\n\t\treturn this.factory.loadEntity(config, created);\n\t}\n\t// Replace the world's contents with a saved config. The existing entities (their backing memory freed) are\n\t// cleared and each system is reset via `clear()` first, then each entity is loaded with `created = false` so\n\t// no finishLoading runs mid-batch. Systems themselves are set up once ahead of time (they persist across\n\t// loads); only their per-load state is cleared here. Once every entity exists, finishLoading is called on\n\t// each - so a component that depends on other entities (e.g. a lumbermill counting nearby trees) can resolve\n\t// them, since the whole batch is guaranteed loaded by then.\n\tload(config: WorldConfig<Cfg>) {\n\t\t// Iterate over a copy since removeEntity mutates `this.entities`.\n\t\tfor(let entity of this.entities.slice()) {\n\t\t\tthis.removeEntity(entity);\n\t\t}\n\t\tthis.systems.forEach(system => system.clear());\n\n\t\tconst entities = config.entities.map(entityConfig => this.loadEntity(entityConfig, false));\n\t\tfor(let entity of entities) {\n\t\t\tentity.finishLoading();\n\t\t\tthis.emit('entity-added', entity);\n\t\t\t// killEntity / killEntityWorker flag the entity dead and emit `death`; remove it here.\n\t\t\tentity.on('death', () => {\n\t\t\t\tthis.onEntityDied(entity);\n\t\t\t});\n\t\t}\n\n\t\t// Restore the world clocks, falling back to a fresh world's defaults when the config omits them.\n\t\tthis.gameTime = config.gameTime ?? 0;\n\t\tthis.playerTime = config.playerTime ?? 0;\n\t\tthis.timeScale = config.timeScale ?? 1;\n\t}\n\tremoveEntity(entity: BaseEntity<C, Cfg>) {\n\t\tlet index = this.entities.indexOf(entity);\n\t\tif(index !== -1) {\n\t\t\tthis.entities.splice(index, 1);\n\t\t\tdelete this.entitiesByEid[entity.eid];\n\t\t\tthis.emit('entity-removed', entity);\n\t\t}\n\n\t\tentity.deleteAllComponentMemory();\n\t}\n\tonEntityDied(entity: BaseEntity<C, Cfg>) {\n\t\tthis.removeEntity(entity);\n\t}\n\tgetEntityByEid(eid: number): BaseEntity<C, Cfg> | undefined {\n\t\treturn this.entitiesByEid[eid];\n\t}\n\n\taddSystem<T extends System<C>>(system: T): T {\n\t\tthis.systems.push(system);\n\t\tthis.emit('system-added', system);\n\t\treturn system;\n\t}\n\taddSystemIfNotExists(system: System<C>) {\n\t\tlet index = this.systems.findIndex(otherSystem => system.name === otherSystem.name);\n\t\tif(index === -1) {\n\t\t\tthis.systems.push(system);\n\t\t\tthis.emit('system-added', system);\n\t\t}\n\t}\n\tremoveSystem(name: string) {\n\t\tlet index = this.systems.findIndex(system => system.name === name);\n\t\tif(index !== -1) {\n\t\t\tconst [system] = this.systems.splice(index, 1);\n\t\t\tthis.emit('system-removed', system);\n\t\t}\n\t}\n\n\t// Brackets the whole frame with `update-started` / `update-finished` (both carrying the elapsed time as it was\n\t// passed in, before timeScale) so an observer - PerformanceTiming, chiefly - can time an update without the\n\t// world itself having to read a clock every frame. Both fire even while paused, where the update does\n\t// nothing but accrue playerTime.\n\tupdate(elapsedTime: number): { lastSystemError?: Error | null } {\n\t\tthis.emit('update-started', elapsedTime);\n\t\tconst result = this.runUpdate(elapsedTime);\n\t\tthis.emit('update-finished', elapsedTime);\n\n\t\treturn result;\n\t}\n\tprivate runUpdate(elapsedTime: number): { lastSystemError?: Error | null } {\n\t\t// playerTime tracks real elapsed time and keeps ticking even while paused; gameTime is the scaled,\n\t\t// pausable simulation clock the systems run against.\n\t\tthis.playerTime += elapsedTime;\n\t\tif(this.paused) {\n\t\t\treturn {};\n\t\t}\n\t\telapsedTime = this.timeScale * elapsedTime;\n\n\t\tthis.gameTime += elapsedTime;\n\n\t\tlet lastSystemError: Error | null = null;\n\t\tthis.systems.forEach(system => {\n\t\t\tlet shouldRun = true;\n\t\t\tlet ran = false;\n\t\t\tlet failed = false;\n\t\t\tthis.emit(`system-${system.name}-started`);\n\t\t\ttry {\n\t\t\t\tshouldRun = system.shouldRun();\n\t\t\t\tif(shouldRun) {\n\t\t\t\t\tran = system.update(elapsedTime);\n\t\t\t\t}\n\t\t\t} catch(e) {\n\t\t\t\tconst error = e as Error;\n\t\t\t\tconsole.error(error.message, error);\n\t\t\t\tfailed = true;\n\t\t\t\tlastSystemError = error;\n\t\t\t}\n\t\t\tthis.emit(`system-${system.name}-finished`, {\n\t\t\t\tran,\n\t\t\t\tshouldRun,\n\t\t\t\tfailed,\n\t\t\t});\n\t\t});\n\n\t\treturn {\n\t\t\tlastSystemError,\n\t\t};\n\t}\n\tpause() {\n\t\tthis.paused = true;\n\t}\n\tresume() {\n\t\tthis.paused = false;\n\t}\n\n\taddEntityToComponentSystem(entity: BaseEntity<C>, component: keyof C) {\n\t\tthis.systems.forEach(system => {\n\t\t\tif(system instanceof EntitySystem && system.options.components?.includes(component)) {\n\t\t\t\tsystem.checkAddEntity(entity);\n\t\t\t} else if(system instanceof ComponentSystem) {\n\t\t\t\tif(this.componentAffectsComponentSystem(system, component)) {\n\t\t\t\t\tsystem.checkAddEntity(entity);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t}\n\tremoveEntityFromComponentSystem(entity: BaseEntity<C>, component: keyof C) {\n\t\tthis.systems.forEach(system => {\n\t\t\tif(system instanceof EntitySystem && system.options.components?.includes(component)) {\n\t\t\t\tsystem.removeEntity(entity);\n\t\t\t} else if(system instanceof ComponentSystem) {\n\t\t\t\tif(this.componentAffectsComponentSystem(system, component)) {\n\t\t\t\t\tsystem.checkAddEntity(entity);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t}\n\tprivate componentAffectsComponentSystem(system: ComponentSystem<C, any>, component: keyof C): boolean {\n\t\tconst affectsMainQuery = system.options.required.includes(component)\n\t\t\t|| !!system.options.not?.includes(component)\n\t\t\t|| !!system.options.optional?.includes(component);\n\t\tconst affectsSubQuery = Object.values(system.options.queries ?? {}).some(query => {\n\t\t\treturn query.required.includes(component)\n\t\t\t\t|| !!query.not?.includes(component)\n\t\t\t\t|| !!query.optional?.includes(component);\n\t\t});\n\n\t\treturn affectsMainQuery || affectsSubQuery;\n\t}\n\n\tdestroy() {\n\t\tif(this.destroyed) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.systems.forEach(system => {\n\t\t\tsystem.destroy();\n\t\t});\n\t\tthis.destroyed = true;\n\t}\n}\n","import type BaseEntity from '../entity';\n\n// Kills an entity on the main thread: flags it dead in shared memory and emits `death` so the world and\n// any game listeners can react. BaseWorld#addEntity listens for `death` to remove the entity.\nexport default function killEntity(entity: BaseEntity): void {\n\tentity.components.entity.dead = true;\n\tentity.emit('death');\n}\n","import type { ComponentTypedArray } from '../memory-component';\nimport type { ComponentSystemCallbacks, EntityUpdateComponents } from '../systems/component-system';\nimport { DEAD_INDEX } from '../entity-component';\n\n// The worker-thread counterpart of killEntity. Called from inside a component system's update function,\n// it flags the entity dead directly in its shared-memory block and reports the death back to the main\n// thread through the callbacks so the world can run the same cleanup killEntity would. The entity\n// component must be part of the system's query for its block to be available here.\nexport default function killEntityWorker(entityId: number, components: EntityUpdateComponents, callbacks: ComponentSystemCallbacks): void {\n\tconst block = (components as { entity?: ComponentTypedArray }).entity;\n\tif(block) {\n\t\tblock[DEAD_INDEX] = 1;\n\t}\n\n\tcallbacks.entityDied(entityId);\n}\n","import type { ComponentSystemCallbacks, CreateEntityConfig } from '../systems/component-system';\n\n// The worker-thread counterpart of loadEntity. Called from inside a component system's update function, it\n// can't build the entity itself (eid generation, shared-memory allocation, and factory expansion all live on\n// the main thread), so it buffers the flat config through the callbacks. The main thread creates the entity\n// via world.loadEntity once the run completes, meaning it first exists on the following frame.\nexport default function createEntityWorker(config: CreateEntityConfig, callbacks: ComponentSystemCallbacks): void {\n\tcallbacks.createEntity(config);\n}\n","import { EventEmitter } from 'eventemitter3';\nimport type BaseWorld from './world';\nimport type { ComponentDefinitionMap, ComponentMap } from './component-definition';\nimport type System from './systems/system';\n\n// How much elapsed time piles up before a new set of stats is worked out. Matches the millisecond scale most\n// games drive `update` with, so the default is one snapshot a second.\nexport const DEFAULT_TICKS_BETWEEN_UPDATES = 1_000;\n\n// One measurement collapsed over a reporting window. `samples` is how many measurements went into it, which is\n// what tells a system that genuinely cost nothing apart from one that never reported in the first place - a\n// system running on the main thread never reports, and so sits at zero across the board.\nexport interface TimingStats {\n\tavg: number\n\tmin: number\n\tmax: number\n\tsamples: number\n}\n\n// One system's cost, split across the two threads it runs on.\nexport interface SystemTimingStats {\n\tname: string\n\t// The run itself, on the system's worker, as the worker measured it.\n\trun: TimingStats\n\t// The other half of the bill: what handling that run's results cost the thread the world lives on - the\n\t// events it reported onto entities, and the entities it asked to be created.\n\tevents: TimingStats\n}\n\n// A full snapshot, replaced wholesale once per reporting window.\nexport interface PerformanceStats {\n\t// One whole `world.update` call: every system's dispatch plus whatever the main thread does inline.\n\tupdate: TimingStats\n\tsystems: Array<SystemTimingStats>\n\t// Every system's event handling added together. Workers finish on their own schedule rather than on a frame\n\t// boundary, so there is no per-frame combined sample to take - a frame carries however many runs happened to\n\t// land in it. These are instead the per-system figures summed at the end of the window, so `avg` is what a\n\t// run of every system costs the main thread between them, and `min` / `max` are the matching best and worst\n\t// cases.\n\tevents: TimingStats\n}\n\nexport interface PerformanceTimingOptions {\n\t// Elapsed time - in whatever unit the world is driven with, so milliseconds for most games - to accumulate\n\t// before recalculating. Defaults to DEFAULT_TICKS_BETWEEN_UPDATES.\n\tticksBetweenUpdates?: number\n}\n\n// The open samples for one system, plus the handlers holding them, kept together so a system can be dropped\n// without hunting for its listeners.\ninterface SystemTiming {\n\trun: Array<number>\n\tevents: Array<number>\n\t// When the current run's event dispatch started, or -1 when no run is being dispatched.\n\teventStart: number\n\tonRunFinished: (runTime: number) => void\n\tonEventsFinished: () => void\n}\n\nconst EMPTY_TIMING: TimingStats = { avg: 0, min: 0, max: 0, samples: 0 };\n\n// Collapses a window's worth of samples into the numbers that get reported. An empty window reads as zeroes\n// rather than an infinite min, so a snapshot is always safe to render.\nfunction summarize(samples: Array<number>): TimingStats {\n\tif(!samples.length) {\n\t\treturn { ...EMPTY_TIMING };\n\t}\n\n\tlet total = 0;\n\tlet min = Infinity;\n\tlet max = 0;\n\tfor(let sample of samples) {\n\t\ttotal += sample;\n\t\tif(sample < min) {\n\t\t\tmin = sample;\n\t\t}\n\t\tif(sample > max) {\n\t\t\tmax = sample;\n\t\t}\n\t}\n\n\treturn {\n\t\tavg: total / samples.length,\n\t\tmin,\n\t\tmax,\n\t\tsamples: samples.length,\n\t};\n}\n\n// Watches a world and reports what it costs to run: how long `world.update` takes on the calling thread, how\n// long each system's run takes on its worker, and what handling each of those runs costs back on the calling\n// thread. Nothing is hooked into the hot path - it is all driven off events the world already emits - so a\n// game only pays for what it measures, and only while it has one of these alive.\n//\n// Samples are gathered every frame and collapsed into a fresh `stats` snapshot once `ticksBetweenUpdates` worth\n// of elapsed time has gone by, at which point `stats-updated` fires with it. Frames the world was paused for\n// are skipped entirely: the world does no work on them, so counting them would only drag every average down.\nexport default class PerformanceTiming<C extends ComponentMap = ComponentMap> extends EventEmitter {\n\tworld: BaseWorld<ComponentDefinitionMap, C>;\n\tticksBetweenUpdates: number;\n\t// The most recent snapshot. Zeroed until the first window elapses, so it can be rendered right away.\n\tstats: PerformanceStats = {\n\t\tupdate: { ...EMPTY_TIMING },\n\t\tsystems: [],\n\t\tevents: { ...EMPTY_TIMING },\n\t};\n\n\tprivate ticks = 0;\n\tprivate updateStart = 0;\n\tprivate updateTimes: Array<number> = [];\n\tprivate systemTimings = new Map<string, SystemTiming>();\n\tprivate destroyed = false;\n\n\tconstructor(world: BaseWorld<ComponentDefinitionMap, C>, options: PerformanceTimingOptions = {}) {\n\t\tsuper();\n\n\t\tthis.world = world;\n\t\tthis.ticksBetweenUpdates = options.ticksBetweenUpdates ?? DEFAULT_TICKS_BETWEEN_UPDATES;\n\n\t\tworld.on('update-started', this.onUpdateStarted);\n\t\tworld.on('update-finished', this.onUpdateFinished);\n\t\t// Systems are normally all in place before this is built, but a world can gain or lose one at any point -\n\t\t// so follow them rather than taking a one-off copy of the list.\n\t\tworld.on('system-added', this.onSystemAdded);\n\t\tworld.on('system-removed', this.onSystemRemoved);\n\t\tworld.systems.forEach(system => this.trackSystem(system));\n\t}\n\n\t// The stats for a single system by name, or undefined if the world has no such system.\n\tgetSystemStats(name: string): SystemTimingStats | undefined {\n\t\treturn this.stats.systems.find(system => system.name === name);\n\t}\n\n\t// Throws away everything collected so far, including the current snapshot, and starts a fresh window. Worth\n\t// calling after anything that makes the samples either side of it incomparable - loading a new scene, say.\n\treset() {\n\t\tthis.ticks = 0;\n\t\tthis.updateTimes = [];\n\t\tthis.systemTimings.forEach(timing => {\n\t\t\ttiming.run = [];\n\t\t\ttiming.events = [];\n\t\t\ttiming.eventStart = -1;\n\t\t});\n\t\tthis.stats = {\n\t\t\tupdate: { ...EMPTY_TIMING },\n\t\t\tsystems: [],\n\t\t\tevents: { ...EMPTY_TIMING },\n\t\t};\n\t}\n\n\tdestroy() {\n\t\tif(this.destroyed) {\n\t\t\treturn;\n\t\t}\n\t\tthis.destroyed = true;\n\n\t\tthis.world.off('update-started', this.onUpdateStarted);\n\t\tthis.world.off('update-finished', this.onUpdateFinished);\n\t\tthis.world.off('system-added', this.onSystemAdded);\n\t\tthis.world.off('system-removed', this.onSystemRemoved);\n\t\tArray.from(this.systemTimings.keys()).forEach(name => this.untrackSystem(name));\n\t\tthis.removeAllListeners();\n\t}\n\n\tprivate onUpdateStarted = () => {\n\t\tthis.updateStart = performance.now();\n\t};\n\n\tprivate onUpdateFinished = (elapsedTime: number) => {\n\t\t// A paused world only accrues its player clock, so there is nothing here worth measuring.\n\t\tif(this.world.paused) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.updateTimes.push(performance.now() - this.updateStart);\n\n\t\tthis.ticks += elapsedTime;\n\t\tif(this.ticks >= this.ticksBetweenUpdates) {\n\t\t\tthis.recalculate();\n\t\t}\n\t};\n\n\tprivate onSystemAdded = (system: System<C>) => {\n\t\tthis.trackSystem(system);\n\t};\n\n\tprivate onSystemRemoved = (system: System<C>) => {\n\t\tthis.untrackSystem(system.name);\n\t};\n\n\t// The world emits `-worker-finished` immediately before it dispatches a run's events onto entities and\n\t// `-worker-events-finished` immediately after, so the gap between the two is exactly what that run cost this\n\t// thread. Neither fires for a system running in the main-thread fallback, which has no off-thread run to\n\t// report in the first place.\n\tprivate trackSystem(system: System<C>) {\n\t\tif(this.systemTimings.has(system.name)) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst timing: SystemTiming = {\n\t\t\trun: [],\n\t\t\tevents: [],\n\t\t\teventStart: -1,\n\t\t\tonRunFinished: (runTime: number) => {\n\t\t\t\ttiming.run.push(runTime);\n\t\t\t\ttiming.eventStart = performance.now();\n\t\t\t},\n\t\t\tonEventsFinished: () => {\n\t\t\t\tif(timing.eventStart < 0) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\ttiming.events.push(performance.now() - timing.eventStart);\n\t\t\t\ttiming.eventStart = -1;\n\t\t\t},\n\t\t};\n\n\t\tthis.world.on(`system-${system.name}-worker-finished`, timing.onRunFinished);\n\t\tthis.world.on(`system-${system.name}-worker-events-finished`, timing.onEventsFinished);\n\t\tthis.systemTimings.set(system.name, timing);\n\t}\n\n\tprivate untrackSystem(name: string) {\n\t\tconst timing = this.systemTimings.get(name);\n\t\tif(!timing) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.world.off(`system-${name}-worker-finished`, timing.onRunFinished);\n\t\tthis.world.off(`system-${name}-worker-events-finished`, timing.onEventsFinished);\n\t\tthis.systemTimings.delete(name);\n\t}\n\n\tprivate recalculate() {\n\t\t// Driven off the world's list rather than the map so the snapshot is in the order the systems run, and so\n\t\t// a system added part way through a window still gets a (partial) entry.\n\t\tconst systems = this.world.systems.map(system => {\n\t\t\tconst timing = this.systemTimings.get(system.name);\n\t\t\treturn {\n\t\t\t\tname: system.name,\n\t\t\t\trun: summarize(timing?.run ?? []),\n\t\t\t\tevents: summarize(timing?.events ?? []),\n\t\t\t};\n\t\t});\n\n\t\tconst events: TimingStats = { ...EMPTY_TIMING };\n\t\tsystems.forEach(system => {\n\t\t\tevents.avg += system.events.avg;\n\t\t\tevents.min += system.events.min;\n\t\t\tevents.max += system.events.max;\n\t\t\tevents.samples += system.events.samples;\n\t\t});\n\n\t\tthis.stats = {\n\t\t\tupdate: summarize(this.updateTimes),\n\t\t\tsystems,\n\t\t\tevents,\n\t\t};\n\n\t\tthis.ticks = 0;\n\t\tthis.updateTimes = [];\n\t\tthis.systemTimings.forEach(timing => {\n\t\t\ttiming.run = [];\n\t\t\ttiming.events = [];\n\t\t});\n\n\t\tthis.emit('stats-updated', this.stats);\n\t}\n}\n","import type ComponentWorkerMessage from './component-worker-message';\nimport type { EntityEvent } from './component-worker-message';\nimport type { ComponentMap } from '../../component-definition';\nimport type { ComponentSystemCallbacks, ComponentSystemWorld, EntityQueryComponents, EntityUpdateComponents, EntityUpdateFunction, QueryDelta, UpdateEntityConfigObject } from '../component-system';\nimport { applyQueryDelta } from './apply-query-delta';\n\n// The slice of the Web Worker global scope createComponentWorker actually touches. Worker files pass the\n// real `self` (e.g. `createComponentWorker(self, fn)`); passing it explicitly also lets test runners that\n// inject `self` as a module local rather than a true global - like @vitest/web-worker - drive the worker.\nexport interface ComponentWorkerScope {\n\tonmessage: ((e: MessageEvent) => void) | null\n\tpostMessage(message: ComponentWorkerMessage): void\n}\n\n// Entry point a game's worker file calls with its update function. It wires up `scope.onmessage`,\n// caches component blocks by entity id (so subsequent runs only need the id), and posts results back.\nexport default function createComponentWorker<\n\tC extends ComponentMap,\n\tT extends EntityUpdateComponents<C>,\n\tW extends ComponentSystemWorld = ComponentSystemWorld,\n>(scope: ComponentWorkerScope, updateFunction: EntityUpdateFunction<C, T, W>) {\n\t// The worker's persistent iteration lists. The main thread now sends only membership changes, so these are\n\t// carried across runs and mutated by the deltas each run brings (see applyQueryDelta).\n\tlet entities: Array<UpdateEntityConfigObject<T>> = [];\n\tconst queryEntities: { [key: string]: Array<UpdateEntityConfigObject<T>> } = {};\n\n\tscope.onmessage = function(e) {\n\t\tconst message = e.data as ComponentWorkerMessage<W>;\n\n\t\tif(message.type === 'init') {\n\t\t\tpostMessageTyped(scope, {\n\t\t\t\ttype: 'loaded',\n\t\t\t});\n\t\t} else if(message.type === 'run') {\n\t\t\tconst start = performance.now();\n\t\t\tlet entityEvents: Array<EntityEvent> = [];\n\t\t\tlet createdEntities: Array<Record<string, unknown>> = [];\n\n\t\t\tentities = applyQueryDelta(entities, message.entities as QueryDelta<T>);\n\n\t\t\tlet queries: EntityQueryComponents<C> = {};\n\t\t\tObject.entries(message.queries).forEach(([queryKey, delta]) => {\n\t\t\t\tconst list = applyQueryDelta(queryEntities[queryKey] ?? [], delta as QueryDelta<T>);\n\t\t\t\tqueryEntities[queryKey] = list;\n\t\t\t\tqueries[queryKey] = list;\n\t\t\t});\n\n\t\t\tlet callbacks: ComponentSystemCallbacks<C> = {\n\t\t\t\tentityComponentChanged<K extends keyof C, P extends keyof C[K]>(entityId: number, componentName: K, prop: P, value: C[K][P]) {\n\t\t\t\t\tentityEvents.push({\n\t\t\t\t\t\tentityId,\n\t\t\t\t\t\tevent: 'component-property-updated',\n\t\t\t\t\t\targs: [componentName, prop, value],\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\temitEntityEvent(entityId: number, event: string, ...args: Array<unknown>) {\n\t\t\t\t\tentityEvents.push({\n\t\t\t\t\t\tentityId,\n\t\t\t\t\t\tevent,\n\t\t\t\t\t\targs,\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\tentityDied(entityId: number) {\n\t\t\t\t\tentityEvents.push({\n\t\t\t\t\t\tentityId,\n\t\t\t\t\t\tevent: 'death',\n\t\t\t\t\t\targs: [],\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\tcreateEntity(config: Record<string, unknown>) {\n\t\t\t\t\tcreatedEntities.push(config);\n\t\t\t\t},\n\t\t\t};\n\t\t\tif(updateFunction.preRun) {\n\t\t\t\tupdateFunction.preRun(message.world, entities, queries, callbacks);\n\t\t\t}\n\n\t\t\tentities.forEach(entity => {\n\t\t\t\tupdateFunction(message.world, entity.entityId, entity.components, queries, callbacks);\n\t\t\t});\n\n\t\t\t// applyQueryDelta already dropped the removed entities from the persistent lists above; the hook just\n\t\t\t// lets the update function react to entities that left the system's main query.\n\t\t\tif(updateFunction.entityRemoved) {\n\t\t\t\tmessage.entities.removed.forEach(entityId => {\n\t\t\t\t\tupdateFunction.entityRemoved!(message.world, entityId, callbacks);\n\t\t\t\t});\n\t\t\t}\n\t\t\tconst runTime = performance.now() - start;\n\n\t\t\tpostMessageTyped(scope, {\n\t\t\t\ttype: 'run-complete',\n\t\t\t\trunTime,\n\t\t\t\tevents: entityEvents,\n\t\t\t\tcreated: createdEntities,\n\t\t\t});\n\t\t}\n\t};\n}\n\nfunction postMessageTyped(scope: ComponentWorkerScope, message: ComponentWorkerMessage) {\n\tscope.postMessage(message);\n}\n"],"x_google_ignoreList":[0,1],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ import { EventEmitter } from 'eventemitter3';
2
+ import type BaseWorld from './world';
3
+ import type { ComponentDefinitionMap, ComponentMap } from './component-definition';
4
+ export declare const DEFAULT_TICKS_BETWEEN_UPDATES = 1000;
5
+ export interface TimingStats {
6
+ avg: number;
7
+ min: number;
8
+ max: number;
9
+ samples: number;
10
+ }
11
+ export interface SystemTimingStats {
12
+ name: string;
13
+ run: TimingStats;
14
+ events: TimingStats;
15
+ }
16
+ export interface PerformanceStats {
17
+ update: TimingStats;
18
+ systems: Array<SystemTimingStats>;
19
+ events: TimingStats;
20
+ }
21
+ export interface PerformanceTimingOptions {
22
+ ticksBetweenUpdates?: number;
23
+ }
24
+ export default class PerformanceTiming<C extends ComponentMap = ComponentMap> extends EventEmitter {
25
+ world: BaseWorld<ComponentDefinitionMap, C>;
26
+ ticksBetweenUpdates: number;
27
+ stats: PerformanceStats;
28
+ private ticks;
29
+ private updateStart;
30
+ private updateTimes;
31
+ private systemTimings;
32
+ private destroyed;
33
+ constructor(world: BaseWorld<ComponentDefinitionMap, C>, options?: PerformanceTimingOptions);
34
+ getSystemStats(name: string): SystemTimingStats | undefined;
35
+ reset(): void;
36
+ destroy(): void;
37
+ private onUpdateStarted;
38
+ private onUpdateFinished;
39
+ private onSystemAdded;
40
+ private onSystemRemoved;
41
+ private trackSystem;
42
+ private untrackSystem;
43
+ private recalculate;
44
+ }
@@ -65,6 +65,7 @@ export interface ComponentSystemWorld {
65
65
  export type CreateEntityConfig = Record<string, unknown>;
66
66
  export interface ComponentSystemCallbacks<C extends ComponentMap = ComponentMap> {
67
67
  entityComponentChanged<K extends keyof C, P extends keyof C[K]>(entityId: number, componentName: K, prop: P, value: C[K][P]): void;
68
+ emitEntityEvent(entityId: number, event: string, ...args: Array<unknown>): void;
68
69
  entityDied(entityId: number): void;
69
70
  createEntity(config: CreateEntityConfig): void;
70
71
  }
@@ -13,14 +13,15 @@ interface RunUpdateMessage<W extends ComponentSystemWorld = ComponentSystemWorld
13
13
  [key: string]: QueryDelta;
14
14
  };
15
15
  }
16
+ export interface EntityEvent {
17
+ entityId: number;
18
+ event: string;
19
+ args: Array<unknown>;
20
+ }
16
21
  interface EntityEventsMessage {
17
22
  type: 'run-complete';
18
23
  runTime: number;
19
- events: Array<{
20
- entityId: number;
21
- event: string;
22
- args: Array<any>;
23
- }>;
24
+ events: Array<EntityEvent>;
24
25
  created: Array<Record<string, unknown>>;
25
26
  }
26
27
  type ComponentWorkerMessage<W extends ComponentSystemWorld = ComponentSystemWorld> = InitMessage | LoadedMessage | RunUpdateMessage<W> | EntityEventsMessage;
package/dist/world.d.ts CHANGED
@@ -45,6 +45,7 @@ export default class BaseWorld<R extends ComponentDefinitionMap = ComponentDefin
45
45
  update(elapsedTime: number): {
46
46
  lastSystemError?: Error | null;
47
47
  };
48
+ private runUpdate;
48
49
  pause(): void;
49
50
  resume(): void;
50
51
  addEntityToComponentSystem(entity: BaseEntity<C>, component: keyof C): void;
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@daneren2005/shared-memory-ecs",
3
3
  "description": "A small, reusable Entity/Component/System core backed by shared memory.",
4
4
  "author": "Scott Jackson",
5
- "version": "1.1.0",
5
+ "version": "1.2.0",
6
6
  "type": "module",
7
7
  "repository": {
8
8
  "type": "git",
@@ -35,8 +35,7 @@
35
35
  "prepare": "husky"
36
36
  },
37
37
  "dependencies": {
38
- "@daneren2005/shared-memory-objects": "^1.0.0",
39
- "eventemitter3": "^3.1.2"
38
+ "eventemitter3": "^5.0.4"
40
39
  },
41
40
  "devDependencies": {
42
41
  "@semantic-release/git": "^10.0.1",
@@ -52,5 +51,8 @@
52
51
  "typescript": "^7.0.2",
53
52
  "vite": "^8.1.4",
54
53
  "vitest": "^4.1.10"
54
+ },
55
+ "peerDependencies": {
56
+ "@daneren2005/shared-memory-objects": "^1.0.0"
55
57
  }
56
58
  }