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