@georgeluo/ecs 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,813 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ Bus: () => Bus,
24
+ ComponentManager: () => ComponentManager,
25
+ EjectSystem: () => EjectSystem,
26
+ EntityManager: () => EntityManager,
27
+ EvaluationMessageType: () => EvaluationMessageType,
28
+ EvaluationPlayer: () => EvaluationPlayer,
29
+ FrameFilter: () => FrameFilter,
30
+ IOPlayer: () => IOPlayer,
31
+ InboundHandlerRegistry: () => InboundHandlerRegistry,
32
+ InjectFrame: () => InjectFrame,
33
+ InjectSystem: () => InjectSystem,
34
+ MessageHandler: () => MessageHandler,
35
+ Pause: () => Pause,
36
+ Player: () => Player,
37
+ SimulationMessageType: () => SimulationMessageType,
38
+ SimulationPlayer: () => SimulationPlayer,
39
+ Start: () => Start,
40
+ Stop: () => Stop,
41
+ System: () => System,
42
+ SystemManager: () => SystemManager,
43
+ TimeComponent: () => TimeComponent,
44
+ TimeSystem: () => TimeSystem,
45
+ createEntityId: () => createEntityId
46
+ });
47
+ module.exports = __toCommonJS(index_exports);
48
+
49
+ // src/entity/EntityManager.ts
50
+ var EntityManager = class {
51
+ constructor() {
52
+ this.nextId = 0;
53
+ this.active = /* @__PURE__ */ new Set();
54
+ }
55
+ /**
56
+ * Create and register a new entity identifier.
57
+ *
58
+ * @returns Entity id allocated for use in the environment.
59
+ */
60
+ create() {
61
+ const entity = this.nextId;
62
+ this.nextId += 1;
63
+ this.active.add(entity);
64
+ return entity;
65
+ }
66
+ /**
67
+ * Remove an entity and return whether it existed. This should trigger
68
+ * component cleanup via the ComponentManager (wired externally).
69
+ *
70
+ * @param entity Entity identifier to destroy.
71
+ */
72
+ remove(entity) {
73
+ return this.active.delete(entity);
74
+ }
75
+ /**
76
+ * Indicates whether the manager currently tracks the entity.
77
+ *
78
+ * @param entity Entity identifier to inspect.
79
+ */
80
+ has(entity) {
81
+ return this.active.has(entity);
82
+ }
83
+ /**
84
+ * Provide a snapshot list of active entities. Higher level systems
85
+ * may use this to iterate over the environment.
86
+ */
87
+ list() {
88
+ return Array.from(this.active.values());
89
+ }
90
+ /**
91
+ * Iterate over all active entities without allocating an intermediate array.
92
+ */
93
+ forEach(callback) {
94
+ this.active.forEach((entity) => {
95
+ callback(entity);
96
+ });
97
+ }
98
+ };
99
+
100
+ // src/entity/Entity.ts
101
+ var createEntityId = (value) => value;
102
+
103
+ // src/components/ComponentManager.ts
104
+ var ComponentManager = class {
105
+ constructor() {
106
+ this.componentsByEntity = /* @__PURE__ */ new Map();
107
+ this.entitiesByType = /* @__PURE__ */ new Map();
108
+ }
109
+ /**
110
+ * Attach a component instance to an entity. Existing component of the same
111
+ * type should be replaced or rejected per implementation decision.
112
+ */
113
+ addComponent(entity, type, payload) {
114
+ if (!type.validate(payload)) {
115
+ throw new Error(`Payload failed validation for component type ${type.id}`);
116
+ }
117
+ let components = this.componentsByEntity.get(entity);
118
+ if (!components) {
119
+ components = /* @__PURE__ */ new Map();
120
+ this.componentsByEntity.set(entity, components);
121
+ }
122
+ const instance = { type, payload };
123
+ components.set(type.id, instance);
124
+ let entities = this.entitiesByType.get(type.id);
125
+ if (!entities) {
126
+ entities = /* @__PURE__ */ new Set();
127
+ this.entitiesByType.set(type.id, entities);
128
+ }
129
+ entities.add(entity);
130
+ }
131
+ /**
132
+ * Remove all components for an entity, returning how many were removed.
133
+ */
134
+ removeAll(entity) {
135
+ const components = this.componentsByEntity.get(entity);
136
+ if (!components) {
137
+ return 0;
138
+ }
139
+ const removedCount = components.size;
140
+ components.forEach((_, typeId) => {
141
+ const entities = this.entitiesByType.get(typeId);
142
+ entities?.delete(entity);
143
+ if (entities && entities.size === 0) {
144
+ this.entitiesByType.delete(typeId);
145
+ }
146
+ });
147
+ this.componentsByEntity.delete(entity);
148
+ return removedCount;
149
+ }
150
+ /**
151
+ * Remove a specific component from an entity.
152
+ */
153
+ removeComponent(entity, type) {
154
+ const components = this.componentsByEntity.get(entity);
155
+ if (!components?.delete(type.id)) {
156
+ return false;
157
+ }
158
+ if (components.size === 0) {
159
+ this.componentsByEntity.delete(entity);
160
+ }
161
+ const entities = this.entitiesByType.get(type.id);
162
+ entities?.delete(entity);
163
+ if (entities && entities.size === 0) {
164
+ this.entitiesByType.delete(type.id);
165
+ }
166
+ return true;
167
+ }
168
+ /**
169
+ * Retrieve a component instance for the entity, if present.
170
+ */
171
+ getComponent(entity, type) {
172
+ const components = this.componentsByEntity.get(entity);
173
+ return components?.get(type.id);
174
+ }
175
+ /**
176
+ * Retrieve all components for the entity.
177
+ */
178
+ getComponents(entity) {
179
+ const components = this.componentsByEntity.get(entity);
180
+ return components ? Array.from(components.values()) : [];
181
+ }
182
+ /**
183
+ * Populate the provided array with the component instances for the entity,
184
+ * returning the number of components discovered. The target array is cleared
185
+ * before population to support buffer reuse.
186
+ */
187
+ collectComponents(entity, target) {
188
+ target.length = 0;
189
+ const components = this.componentsByEntity.get(entity);
190
+ if (!components) {
191
+ return 0;
192
+ }
193
+ let index = 0;
194
+ components.forEach((instance) => {
195
+ target[index] = instance;
196
+ index += 1;
197
+ });
198
+ target.length = index;
199
+ return index;
200
+ }
201
+ /**
202
+ * Retrieve entities that possess a component of the provided type.
203
+ */
204
+ getEntitiesWithComponent(type) {
205
+ const entities = this.entitiesByType.get(type.id);
206
+ return entities ? Array.from(entities.values()) : [];
207
+ }
208
+ };
209
+
210
+ // src/components/TimeComponent.ts
211
+ var TimeComponent = {
212
+ id: "core.time",
213
+ description: "Tracks the current simulation tick.",
214
+ validate(payload) {
215
+ if (payload == null) {
216
+ return false;
217
+ }
218
+ const { tick } = payload;
219
+ return Number.isInteger(tick) && tick >= 0;
220
+ }
221
+ };
222
+
223
+ // src/systems/SystemManager.ts
224
+ var SystemManager = class {
225
+ constructor(entityManager, componentManager) {
226
+ this.systems = [];
227
+ this.context = {
228
+ entityManager,
229
+ componentManager
230
+ };
231
+ }
232
+ /** Register a system at the end of the execution order or specified index. */
233
+ addSystem(system, index) {
234
+ const insertionIndex = index === void 0 || index < 0 || index > this.systems.length ? this.systems.length : index;
235
+ this.systems.splice(insertionIndex, 0, system);
236
+ system.initialize(this.context);
237
+ }
238
+ /** Remove a system and trigger destroy lifecycle hook. */
239
+ removeSystem(system) {
240
+ const idx = this.systems.indexOf(system);
241
+ if (idx === -1) {
242
+ return false;
243
+ }
244
+ this.systems.splice(idx, 1);
245
+ system.destroy(this.context);
246
+ return true;
247
+ }
248
+ /** Execute one update cycle across all systems in order. */
249
+ runCycle() {
250
+ for (const system of this.systems) {
251
+ system.update(this.context);
252
+ }
253
+ }
254
+ /** Retrieve current ordered list of systems. */
255
+ getSystems() {
256
+ return [...this.systems];
257
+ }
258
+ /** Expose the context passed into systems (entity/component managers). */
259
+ getContext() {
260
+ return this.context;
261
+ }
262
+ };
263
+
264
+ // src/systems/System.ts
265
+ var System = class {
266
+ /** Optional hook invoked once when the system is added to the engine. */
267
+ initialize(_context) {
268
+ }
269
+ /** Optional hook invoked when the system is removed from the engine. */
270
+ destroy(_context) {
271
+ }
272
+ };
273
+
274
+ // src/systems/TimeSystem.ts
275
+ var TimeSystem = class extends System {
276
+ initialize(context) {
277
+ if (this.timeEntity !== void 0) {
278
+ return;
279
+ }
280
+ const entity = context.entityManager.create();
281
+ this.timeEntity = entity;
282
+ context.componentManager.addComponent(entity, TimeComponent, { tick: 0 });
283
+ }
284
+ update(context) {
285
+ if (this.timeEntity === void 0) {
286
+ this.initialize(context);
287
+ }
288
+ const entity = this.timeEntity;
289
+ const current = context.componentManager.getComponent(entity, TimeComponent);
290
+ const nextTick = (current?.payload.tick ?? 0) + 1;
291
+ context.componentManager.addComponent(entity, TimeComponent, { tick: nextTick });
292
+ }
293
+ };
294
+
295
+ // src/Player.ts
296
+ var DEFAULT_CYCLE_INTERVAL_MS = 50;
297
+ var Player = class {
298
+ constructor(systemManager, cycleIntervalMs = DEFAULT_CYCLE_INTERVAL_MS) {
299
+ this.tick = 0;
300
+ this.isRunning = false;
301
+ this.cycleTimer = null;
302
+ this.nextSystemId = 1;
303
+ this.systemsById = /* @__PURE__ */ new Map();
304
+ this.idsBySystem = /* @__PURE__ */ new Map();
305
+ this.systemManager = systemManager;
306
+ this.cycleIntervalMs = cycleIntervalMs;
307
+ }
308
+ start() {
309
+ if (this.isRunning) {
310
+ return;
311
+ }
312
+ this.isRunning = true;
313
+ this.ensureTimer();
314
+ this.executeCycle();
315
+ }
316
+ pause() {
317
+ this.isRunning = false;
318
+ }
319
+ stop() {
320
+ this.isRunning = false;
321
+ this.clearTimer();
322
+ this.resetEnvironment();
323
+ }
324
+ injectSystem(payload) {
325
+ const systemId = this.generateSystemId();
326
+ this.systemsById.set(systemId, payload.system);
327
+ this.idsBySystem.set(payload.system, systemId);
328
+ this.systemManager.addSystem(payload.system);
329
+ return systemId;
330
+ }
331
+ ejectSystem(payload) {
332
+ const system = payload.system ?? (payload.systemId ? this.systemsById.get(payload.systemId) ?? null : null);
333
+ if (!system) {
334
+ return false;
335
+ }
336
+ const removed = this.systemManager.removeSystem(system);
337
+ if (!removed) {
338
+ return false;
339
+ }
340
+ const resolvedId = payload.systemId ?? this.idsBySystem.get(system);
341
+ if (resolvedId) {
342
+ this.systemsById.delete(resolvedId);
343
+ }
344
+ this.idsBySystem.delete(system);
345
+ return true;
346
+ }
347
+ getContext() {
348
+ return this.systemManager.getContext();
349
+ }
350
+ executeCycle() {
351
+ const currentTick = this.tick;
352
+ this.onBeforeCycle(currentTick);
353
+ this.systemManager.runCycle();
354
+ this.onAfterCycle(currentTick, this.getContext());
355
+ this.tick += 1;
356
+ }
357
+ // Intended extension hook for subclasses to prepare state prior to a cycle.
358
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
359
+ onBeforeCycle(_tick) {
360
+ }
361
+ // Intended extension hook for subclasses to inspect the environment after a cycle.
362
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
363
+ onAfterCycle(_tick, _context) {
364
+ }
365
+ ensureTimer() {
366
+ if (this.cycleTimer) {
367
+ return;
368
+ }
369
+ this.cycleTimer = setInterval(() => {
370
+ if (!this.isRunning) {
371
+ this.clearTimer();
372
+ return;
373
+ }
374
+ this.executeCycle();
375
+ }, this.cycleIntervalMs);
376
+ if (typeof this.cycleTimer.unref === "function") {
377
+ this.cycleTimer.unref();
378
+ }
379
+ }
380
+ clearTimer() {
381
+ if (this.cycleTimer) {
382
+ clearInterval(this.cycleTimer);
383
+ this.cycleTimer = null;
384
+ }
385
+ }
386
+ resetEnvironment() {
387
+ const context = this.getContext();
388
+ const entities = context.entityManager.list();
389
+ for (const entity of entities) {
390
+ context.componentManager.removeAll(entity);
391
+ context.entityManager.remove(entity);
392
+ }
393
+ this.tick = 0;
394
+ this.cleanupSystemRegistry();
395
+ }
396
+ generateSystemId() {
397
+ const id = `system-${this.nextSystemId}`;
398
+ this.nextSystemId += 1;
399
+ return id;
400
+ }
401
+ cleanupSystemRegistry() {
402
+ for (const [id, system] of this.systemsById.entries()) {
403
+ if (!this.idsBySystem.has(system)) {
404
+ this.systemsById.delete(id);
405
+ }
406
+ }
407
+ for (const [system, id] of this.idsBySystem.entries()) {
408
+ if (!this.systemsById.has(id)) {
409
+ this.idsBySystem.delete(system);
410
+ }
411
+ }
412
+ }
413
+ describe() {
414
+ const state = this.isRunning ? "running" : this.tick > 0 ? "paused" : "idle";
415
+ return {
416
+ state,
417
+ tick: this.tick,
418
+ systemCount: this.systemsById.size
419
+ };
420
+ }
421
+ };
422
+
423
+ // src/messaging/inbound/InboundHandlerRegistry.ts
424
+ var InboundHandlerRegistry = class {
425
+ constructor(handlers = /* @__PURE__ */ new Map()) {
426
+ this.handlers = new Map(handlers);
427
+ }
428
+ register(type, handler) {
429
+ this.handlers.set(type, handler);
430
+ }
431
+ handle(type, context, payload) {
432
+ const handler = this.handlers.get(type);
433
+ if (!handler) {
434
+ return null;
435
+ }
436
+ return handler.handle(context, payload);
437
+ }
438
+ };
439
+
440
+ // src/IOPlayer.ts
441
+ var IOPlayer = class extends Player {
442
+ constructor(systemManager, inbound, outbound, frameFilter, handlers, cycleIntervalMs) {
443
+ super(systemManager, cycleIntervalMs);
444
+ this.componentBuffer = [];
445
+ this.inbound = inbound;
446
+ this.outbound = outbound;
447
+ this.frameFilter = frameFilter;
448
+ this.handlers = handlers ?? new InboundHandlerRegistry();
449
+ this.unsubscribeInbound = this.inbound.subscribe((message) => this.handleInbound(message));
450
+ }
451
+ getInboundHandlers() {
452
+ return this.handlers;
453
+ }
454
+ onAfterCycle(tick, context) {
455
+ const frame = this.createFrameSnapshot(tick, context);
456
+ this.publishFrame(frame);
457
+ }
458
+ handleInbound(message) {
459
+ const inboundMessage = message;
460
+ if (!inboundMessage || typeof inboundMessage.type !== "string") {
461
+ return;
462
+ }
463
+ try {
464
+ const acknowledgement = this.handlers.handle(inboundMessage.type, this, inboundMessage.payload);
465
+ if (acknowledgement) {
466
+ this.outbound.publish(acknowledgement);
467
+ }
468
+ } catch (error) {
469
+ const messageId = extractMessageId(inboundMessage.payload);
470
+ if (!messageId) {
471
+ return;
472
+ }
473
+ this.outbound.publish({
474
+ messageId,
475
+ status: "error",
476
+ detail: error instanceof Error ? error.message : String(error)
477
+ });
478
+ }
479
+ }
480
+ publishFrame(frame) {
481
+ const filtered = this.frameFilter.apply(frame);
482
+ this.outbound.publish(filtered);
483
+ }
484
+ createFrameSnapshot(tick, context) {
485
+ const snapshot = /* @__PURE__ */ Object.create(null);
486
+ const entityManager = context.entityManager;
487
+ const componentManager = context.componentManager;
488
+ entityManager.forEach((entity) => {
489
+ snapshot[String(entity)] = this.collectComponents(entity, componentManager);
490
+ });
491
+ return {
492
+ tick,
493
+ entities: snapshot
494
+ };
495
+ }
496
+ collectComponents(entity, componentManager) {
497
+ const componentCount = componentManager.collectComponents(entity, this.componentBuffer);
498
+ if (componentCount === 0) {
499
+ return /* @__PURE__ */ Object.create(null);
500
+ }
501
+ const record = /* @__PURE__ */ Object.create(null);
502
+ for (let index = 0; index < componentCount; index += 1) {
503
+ const component = this.componentBuffer[index];
504
+ record[component.type.id] = component.payload;
505
+ }
506
+ return record;
507
+ }
508
+ };
509
+ function extractMessageId(payload) {
510
+ if (!payload || typeof payload !== "object") {
511
+ return null;
512
+ }
513
+ const candidate = payload.messageId;
514
+ return typeof candidate === "string" && candidate ? candidate : null;
515
+ }
516
+
517
+ // src/messaging/inbound/MessageHandler.ts
518
+ var MessageHandler = class {
519
+ constructor(operations) {
520
+ this.operations = operations;
521
+ }
522
+ handle(context, payload) {
523
+ let acknowledgement = null;
524
+ for (const operation of this.operations) {
525
+ acknowledgement = operation.execute(context, payload);
526
+ }
527
+ if (!acknowledgement) {
528
+ throw new Error("Inbound operations must return an acknowledgement");
529
+ }
530
+ return acknowledgement;
531
+ }
532
+ };
533
+
534
+ // src/simplayer/operations/Start.ts
535
+ var Start = class {
536
+ execute(player, payload) {
537
+ player.start();
538
+ return { messageId: payload.messageId, status: "success" };
539
+ }
540
+ };
541
+
542
+ // src/simplayer/operations/Pause.ts
543
+ var Pause = class {
544
+ execute(player, payload) {
545
+ player.pause();
546
+ return { messageId: payload.messageId, status: "success" };
547
+ }
548
+ };
549
+
550
+ // src/simplayer/operations/Stop.ts
551
+ var Stop = class {
552
+ execute(player, payload) {
553
+ player.stop();
554
+ return { messageId: payload.messageId, status: "success" };
555
+ }
556
+ };
557
+
558
+ // src/simplayer/operations/InjectSystem.ts
559
+ var InjectSystem = class {
560
+ execute(player, payload) {
561
+ if (!payload?.system) {
562
+ throw new Error("System payload is required for injection");
563
+ }
564
+ const systemId = player.injectSystem({ system: payload.system });
565
+ return { messageId: payload.messageId, status: "success", systemId };
566
+ }
567
+ };
568
+
569
+ // src/simplayer/operations/EjectSystem.ts
570
+ var EjectSystem = class {
571
+ execute(player, payload) {
572
+ const removed = player.ejectSystem({ system: payload.system, systemId: payload.systemId });
573
+ if (!removed) {
574
+ return {
575
+ messageId: payload.messageId,
576
+ status: "error",
577
+ detail: "System not found"
578
+ };
579
+ }
580
+ return { messageId: payload.messageId, status: "success" };
581
+ }
582
+ };
583
+
584
+ // src/simplayer/SimulationPlayer.ts
585
+ var SimulationMessageType = {
586
+ START: "start",
587
+ PAUSE: "pause",
588
+ STOP: "stop",
589
+ INJECT_SYSTEM: "inject-system",
590
+ EJECT_SYSTEM: "eject-system"
591
+ };
592
+ var SimulationPlayer = class extends IOPlayer {
593
+ constructor(systemManager, inbound, outbound, frameFilter, handlers, cycleIntervalMs) {
594
+ super(systemManager, inbound, outbound, frameFilter, handlers, cycleIntervalMs);
595
+ this.componentTypes = /* @__PURE__ */ new Map();
596
+ this.registerDefaultHandlers();
597
+ }
598
+ registerComponent(component) {
599
+ this.componentTypes.set(component.id, component);
600
+ }
601
+ removeComponent(componentId) {
602
+ return this.componentTypes.delete(componentId);
603
+ }
604
+ getRegisteredComponents() {
605
+ return Array.from(this.componentTypes.values());
606
+ }
607
+ registerDefaultHandlers() {
608
+ const registry = this.getInboundHandlers();
609
+ registry.register(
610
+ SimulationMessageType.START,
611
+ new MessageHandler([new Start()])
612
+ );
613
+ registry.register(
614
+ SimulationMessageType.PAUSE,
615
+ new MessageHandler([new Pause()])
616
+ );
617
+ registry.register(
618
+ SimulationMessageType.STOP,
619
+ new MessageHandler([new Stop()])
620
+ );
621
+ registry.register(
622
+ SimulationMessageType.INJECT_SYSTEM,
623
+ new MessageHandler([new InjectSystem()])
624
+ );
625
+ registry.register(
626
+ SimulationMessageType.EJECT_SYSTEM,
627
+ new MessageHandler([new EjectSystem()])
628
+ );
629
+ }
630
+ };
631
+
632
+ // src/evalplayer/operations/InjectFrame.ts
633
+ var InjectFrame = class {
634
+ execute(player, payload) {
635
+ if (typeof player.start === "function") {
636
+ player.start();
637
+ }
638
+ player.injectFrame(payload);
639
+ return { messageId: payload.messageId, status: "success" };
640
+ }
641
+ };
642
+
643
+ // src/evalplayer/EvaluationPlayer.ts
644
+ var EvaluationMessageType = {
645
+ INJECT_FRAME: "inject-frame"
646
+ };
647
+ var FrameComponent = {
648
+ id: "evaluation.frame",
649
+ description: "Stores a raw simulation frame for downstream evaluation systems.",
650
+ validate(payload) {
651
+ if (!payload || typeof payload !== "object") {
652
+ return false;
653
+ }
654
+ const { tick, entities } = payload;
655
+ const entitiesIsRecord = typeof entities === "object" && entities !== null;
656
+ return Number.isFinite(tick) && tick >= 0 && entitiesIsRecord;
657
+ }
658
+ };
659
+ var EvaluationPlayer = class extends IOPlayer {
660
+ constructor(systemManager, inbound, outbound, frameFilter, handlers, cycleIntervalMs) {
661
+ super(
662
+ systemManager,
663
+ inbound,
664
+ outbound,
665
+ frameFilter,
666
+ handlers ?? void 0,
667
+ cycleIntervalMs
668
+ );
669
+ this.frames = [];
670
+ this.componentTypes = /* @__PURE__ */ new Map();
671
+ this.frameComponentType = FrameComponent;
672
+ this.lastFrameTick = null;
673
+ this.registerDefaultHandlers();
674
+ }
675
+ injectFrame(payload) {
676
+ this.resetIfNewRun(payload.frame.tick);
677
+ const context = this.getContext();
678
+ const entity = context.entityManager.create();
679
+ context.componentManager.addComponent(entity, this.frameComponentType, payload.frame);
680
+ this.frames.push(payload);
681
+ this.publishFrame(payload.frame);
682
+ this.lastFrameTick = payload.frame.tick;
683
+ }
684
+ getFrames() {
685
+ return [...this.frames];
686
+ }
687
+ registerComponent(component) {
688
+ this.componentTypes.set(component.id, component);
689
+ }
690
+ removeComponent(componentId) {
691
+ return this.componentTypes.delete(componentId);
692
+ }
693
+ getRegisteredComponents() {
694
+ return Array.from(this.componentTypes.values());
695
+ }
696
+ resetIfNewRun(tick) {
697
+ if (this.lastFrameTick !== null && Number.isFinite(tick) && tick < this.lastFrameTick) {
698
+ this.frames.length = 0;
699
+ this.resetEnvironment();
700
+ this.lastFrameTick = null;
701
+ }
702
+ }
703
+ registerDefaultHandlers() {
704
+ const registry = this.getInboundHandlers();
705
+ registry.register(
706
+ EvaluationMessageType.INJECT_FRAME,
707
+ new MessageHandler([new InjectFrame()])
708
+ );
709
+ }
710
+ };
711
+
712
+ // src/messaging/Bus.ts
713
+ var Bus = class {
714
+ constructor() {
715
+ this.subscribers = [];
716
+ this.dispatchDepth = 0;
717
+ }
718
+ subscribe(callback) {
719
+ const subscription = { callback, active: true };
720
+ this.subscribers.push(subscription);
721
+ return () => {
722
+ if (!subscription.active) {
723
+ return;
724
+ }
725
+ subscription.active = false;
726
+ if (this.dispatchDepth === 0) {
727
+ this.compact();
728
+ }
729
+ };
730
+ }
731
+ publish(message) {
732
+ this.dispatchDepth += 1;
733
+ const snapshotLength = this.subscribers.length;
734
+ try {
735
+ for (let index = 0; index < snapshotLength; index += 1) {
736
+ const subscription = this.subscribers[index];
737
+ if (subscription?.active) {
738
+ subscription.callback(message);
739
+ }
740
+ }
741
+ } finally {
742
+ this.dispatchDepth -= 1;
743
+ if (this.dispatchDepth === 0) {
744
+ this.compact();
745
+ }
746
+ }
747
+ }
748
+ compact() {
749
+ let writeIndex = 0;
750
+ for (let readIndex = 0; readIndex < this.subscribers.length; readIndex += 1) {
751
+ const subscription = this.subscribers[readIndex];
752
+ if (subscription?.active) {
753
+ if (writeIndex !== readIndex) {
754
+ this.subscribers[writeIndex] = subscription;
755
+ }
756
+ writeIndex += 1;
757
+ }
758
+ }
759
+ this.subscribers.length = writeIndex;
760
+ }
761
+ };
762
+
763
+ // src/messaging/outbound/FrameFilter.ts
764
+ var FrameFilter = class {
765
+ constructor(componentBlacklist = []) {
766
+ this.blacklist = new Set(componentBlacklist);
767
+ }
768
+ apply(frame) {
769
+ if (this.blacklist.size === 0) {
770
+ return frame;
771
+ }
772
+ const entities = /* @__PURE__ */ Object.create(null);
773
+ for (const [entityId, components] of Object.entries(frame.entities)) {
774
+ const filtered = /* @__PURE__ */ Object.create(null);
775
+ for (const [componentId, value] of Object.entries(components)) {
776
+ if (!this.blacklist.has(componentId)) {
777
+ filtered[componentId] = value;
778
+ }
779
+ }
780
+ entities[entityId] = filtered;
781
+ }
782
+ return {
783
+ tick: frame.tick,
784
+ entities
785
+ };
786
+ }
787
+ };
788
+ // Annotate the CommonJS export names for ESM import in node:
789
+ 0 && (module.exports = {
790
+ Bus,
791
+ ComponentManager,
792
+ EjectSystem,
793
+ EntityManager,
794
+ EvaluationMessageType,
795
+ EvaluationPlayer,
796
+ FrameFilter,
797
+ IOPlayer,
798
+ InboundHandlerRegistry,
799
+ InjectFrame,
800
+ InjectSystem,
801
+ MessageHandler,
802
+ Pause,
803
+ Player,
804
+ SimulationMessageType,
805
+ SimulationPlayer,
806
+ Start,
807
+ Stop,
808
+ System,
809
+ SystemManager,
810
+ TimeComponent,
811
+ TimeSystem,
812
+ createEntityId
813
+ });