@codehz/ecs 0.0.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/index.js ADDED
@@ -0,0 +1,756 @@
1
+ // src/entity.ts
2
+ var INVALID_COMPONENT_ID = 0;
3
+ var COMPONENT_ID_MAX = 1023;
4
+ var ENTITY_ID_START = 1024;
5
+ var RELATION_SHIFT = 2 ** 42;
6
+ var WILDCARD_TARGET_ID = 0;
7
+ function createComponentId(id) {
8
+ if (id < 1 || id > COMPONENT_ID_MAX) {
9
+ throw new Error(`Component ID must be between 1 and ${COMPONENT_ID_MAX}`);
10
+ }
11
+ return id;
12
+ }
13
+ function createEntityId(id) {
14
+ if (id < ENTITY_ID_START) {
15
+ throw new Error(`Entity ID must be ${ENTITY_ID_START} or greater`);
16
+ }
17
+ return id;
18
+ }
19
+ function createRelationId(componentId, targetId) {
20
+ if (!isComponentId(componentId)) {
21
+ throw new Error("First argument must be a valid component ID");
22
+ }
23
+ let actualTargetId;
24
+ if (targetId === "*") {
25
+ actualTargetId = WILDCARD_TARGET_ID;
26
+ } else {
27
+ if (!isEntityId(targetId) && !isComponentId(targetId)) {
28
+ throw new Error("Second argument must be a valid entity ID, component ID, or '*'");
29
+ }
30
+ actualTargetId = targetId;
31
+ }
32
+ return -(componentId * RELATION_SHIFT + actualTargetId);
33
+ }
34
+ function isComponentId(id) {
35
+ return id >= 1 && id <= COMPONENT_ID_MAX;
36
+ }
37
+ function isEntityId(id) {
38
+ return id >= ENTITY_ID_START;
39
+ }
40
+ function isRelationId(id) {
41
+ return id < 0;
42
+ }
43
+ function decodeRelationId(relationId) {
44
+ if (!isRelationId(relationId)) {
45
+ throw new Error("ID is not a relation ID");
46
+ }
47
+ const absId = -relationId;
48
+ const componentId = Math.floor(absId / RELATION_SHIFT);
49
+ const targetId = absId % RELATION_SHIFT;
50
+ if (targetId === WILDCARD_TARGET_ID) {
51
+ return { componentId, targetId, type: "wildcard" };
52
+ } else if (isEntityId(targetId)) {
53
+ return { componentId, targetId, type: "entity" };
54
+ } else if (isComponentId(targetId)) {
55
+ return { componentId, targetId, type: "component" };
56
+ } else {
57
+ throw new Error("Invalid target ID in relation");
58
+ }
59
+ }
60
+ function getIdType(id) {
61
+ if (isComponentId(id))
62
+ return "component";
63
+ if (isEntityId(id))
64
+ return "entity";
65
+ if (isRelationId(id)) {
66
+ try {
67
+ const decoded = decodeRelationId(id);
68
+ if (!isComponentId(decoded.componentId) || decoded.type !== "wildcard" && !isEntityId(decoded.targetId) && !isComponentId(decoded.targetId)) {
69
+ return "invalid";
70
+ }
71
+ switch (decoded.type) {
72
+ case "entity":
73
+ return "entity-relation";
74
+ case "component":
75
+ return "component-relation";
76
+ case "wildcard":
77
+ return "wildcard-relation";
78
+ }
79
+ } catch (error) {
80
+ return "invalid";
81
+ }
82
+ }
83
+ return "invalid";
84
+ }
85
+ function getDetailedIdType(id) {
86
+ if (isComponentId(id)) {
87
+ return { type: "component" };
88
+ }
89
+ if (isEntityId(id)) {
90
+ return { type: "entity" };
91
+ }
92
+ if (isRelationId(id)) {
93
+ try {
94
+ const decoded = decodeRelationId(id);
95
+ if (!isComponentId(decoded.componentId) || decoded.type !== "wildcard" && !isEntityId(decoded.targetId) && !isComponentId(decoded.targetId)) {
96
+ return { type: "invalid" };
97
+ }
98
+ let type;
99
+ switch (decoded.type) {
100
+ case "entity":
101
+ type = "entity-relation";
102
+ break;
103
+ case "component":
104
+ type = "component-relation";
105
+ break;
106
+ case "wildcard":
107
+ type = "wildcard-relation";
108
+ break;
109
+ }
110
+ return {
111
+ type,
112
+ componentId: decoded.componentId,
113
+ targetId: decoded.targetId
114
+ };
115
+ } catch (error) {
116
+ return { type: "invalid" };
117
+ }
118
+ }
119
+ return { type: "invalid" };
120
+ }
121
+ function inspectEntityId(id) {
122
+ if (id === INVALID_COMPONENT_ID) {
123
+ return "Invalid Component ID (0)";
124
+ }
125
+ if (isComponentId(id)) {
126
+ return `Component ID (${id})`;
127
+ }
128
+ if (isEntityId(id)) {
129
+ return `Entity ID (${id})`;
130
+ }
131
+ if (isRelationId(id)) {
132
+ try {
133
+ const decoded = decodeRelationId(id);
134
+ if (!isComponentId(decoded.componentId) || decoded.type !== "wildcard" && !isEntityId(decoded.targetId) && !isComponentId(decoded.targetId)) {
135
+ return `Invalid Relation ID (${id})`;
136
+ }
137
+ const componentStr = `Component ID (${decoded.componentId})`;
138
+ const targetStr = decoded.type === "entity" ? `Entity ID (${decoded.targetId})` : decoded.type === "component" ? `Component ID (${decoded.targetId})` : "Wildcard (*)";
139
+ return `Relation ID: ${componentStr} -> ${targetStr}`;
140
+ } catch (error) {
141
+ return `Invalid Relation ID (${id})`;
142
+ }
143
+ }
144
+ return `Unknown ID (${id})`;
145
+ }
146
+
147
+ class EntityIdManager {
148
+ nextId = ENTITY_ID_START;
149
+ freelist = new Set;
150
+ allocate() {
151
+ if (this.freelist.size > 0) {
152
+ const id = this.freelist.values().next().value;
153
+ this.freelist.delete(id);
154
+ return id;
155
+ } else {
156
+ const id = this.nextId;
157
+ this.nextId++;
158
+ if (this.nextId >= Number.MAX_SAFE_INTEGER) {
159
+ throw new Error("Entity ID overflow: reached maximum safe integer");
160
+ }
161
+ return id;
162
+ }
163
+ }
164
+ deallocate(id) {
165
+ if (!isEntityId(id)) {
166
+ throw new Error("Can only deallocate valid entity IDs");
167
+ }
168
+ if (id >= this.nextId) {
169
+ throw new Error("Cannot deallocate an ID that was never allocated");
170
+ }
171
+ this.freelist.add(id);
172
+ }
173
+ getFreelistSize() {
174
+ return this.freelist.size;
175
+ }
176
+ getNextId() {
177
+ return this.nextId;
178
+ }
179
+ }
180
+
181
+ class ComponentIdManager {
182
+ nextId = 1;
183
+ allocate() {
184
+ if (this.nextId > COMPONENT_ID_MAX) {
185
+ throw new Error(`Component ID overflow: maximum ${COMPONENT_ID_MAX} components allowed`);
186
+ }
187
+ const id = this.nextId;
188
+ this.nextId++;
189
+ return id;
190
+ }
191
+ getNextId() {
192
+ return this.nextId;
193
+ }
194
+ hasAvailableIds() {
195
+ return this.nextId <= COMPONENT_ID_MAX;
196
+ }
197
+ }
198
+ // src/utils.ts
199
+ function getOrComputeCache(cache, key, compute) {
200
+ let value = cache.get(key);
201
+ if (value === undefined) {
202
+ value = compute();
203
+ cache.set(key, value);
204
+ }
205
+ return value;
206
+ }
207
+ function getOrCreateWithSideEffect(cache, key, create) {
208
+ let value = cache.get(key);
209
+ if (value === undefined) {
210
+ value = create();
211
+ cache.set(key, value);
212
+ }
213
+ return value;
214
+ }
215
+
216
+ // src/archetype.ts
217
+ class Archetype {
218
+ componentTypes;
219
+ entities = [];
220
+ componentData = new Map;
221
+ entityToIndex = new Map;
222
+ componentDataSourcesCache = new Map;
223
+ constructor(componentTypes) {
224
+ this.componentTypes = [...componentTypes].sort((a, b) => a - b);
225
+ for (const componentType of this.componentTypes) {
226
+ this.componentData.set(componentType, []);
227
+ }
228
+ }
229
+ get size() {
230
+ return this.entities.length;
231
+ }
232
+ matches(componentTypes) {
233
+ if (this.componentTypes.length !== componentTypes.length) {
234
+ return false;
235
+ }
236
+ const sortedTypes = [...componentTypes].sort((a, b) => a - b);
237
+ return this.componentTypes.every((type, index) => type === sortedTypes[index]);
238
+ }
239
+ addEntity(entityId, componentData) {
240
+ if (this.entityToIndex.has(entityId)) {
241
+ throw new Error(`Entity ${entityId} is already in this archetype`);
242
+ }
243
+ const index = this.entities.length;
244
+ this.entities.push(entityId);
245
+ this.entityToIndex.set(entityId, index);
246
+ for (const componentType of this.componentTypes) {
247
+ const data = componentData.get(componentType);
248
+ if (data === undefined) {
249
+ throw new Error(`Missing component data for type ${componentType}`);
250
+ }
251
+ this.componentData.get(componentType).push(data);
252
+ }
253
+ }
254
+ removeEntity(entityId) {
255
+ const index = this.entityToIndex.get(entityId);
256
+ if (index === undefined) {
257
+ return;
258
+ }
259
+ this.entities.splice(index, 1);
260
+ this.entityToIndex.delete(entityId);
261
+ const removedData = new Map;
262
+ for (const componentType of this.componentTypes) {
263
+ const dataArray = this.componentData.get(componentType);
264
+ removedData.set(componentType, dataArray[index]);
265
+ dataArray.splice(index, 1);
266
+ }
267
+ for (let i = index;i < this.entities.length; i++) {
268
+ this.entityToIndex.set(this.entities[i], i);
269
+ }
270
+ return removedData;
271
+ }
272
+ hasEntity(entityId) {
273
+ return this.entityToIndex.has(entityId);
274
+ }
275
+ getComponent(entityId, componentType) {
276
+ const index = this.entityToIndex.get(entityId);
277
+ if (index === undefined) {
278
+ return;
279
+ }
280
+ return this.componentData.get(componentType)?.[index];
281
+ }
282
+ setComponent(entityId, componentType, data) {
283
+ const index = this.entityToIndex.get(entityId);
284
+ if (index === undefined) {
285
+ throw new Error(`Entity ${entityId} is not in this archetype`);
286
+ }
287
+ const dataArray = this.componentData.get(componentType);
288
+ if (!dataArray) {
289
+ throw new Error(`Component type ${componentType} is not in this archetype`);
290
+ }
291
+ dataArray[index] = data;
292
+ }
293
+ getEntities() {
294
+ return [...this.entities];
295
+ }
296
+ getComponentData(componentType) {
297
+ return [...this.componentData.get(componentType) || []];
298
+ }
299
+ getEntitiesWithComponents(componentTypes) {
300
+ const result = [];
301
+ this.forEachWithComponents(componentTypes, (entity, ...components) => {
302
+ result.push({ entity, components });
303
+ });
304
+ return result;
305
+ }
306
+ forEachWithComponents(componentTypes, callback) {
307
+ const cacheKey = componentTypes.map((id) => id.toString()).join(",");
308
+ const componentDataSources = getOrComputeCache(this.componentDataSourcesCache, cacheKey, () => {
309
+ return componentTypes.map((compType) => {
310
+ if (getIdType(compType) === "wildcard-relation") {
311
+ const decoded = decodeRelationId(compType);
312
+ const componentId = decoded.componentId;
313
+ const matchingRelations = this.componentTypes.filter((ct) => {
314
+ const ctType = getIdType(ct);
315
+ if (ctType !== "entity-relation" && ctType !== "component-relation")
316
+ return false;
317
+ const decodedCt = decodeRelationId(ct);
318
+ return decodedCt.componentId === componentId;
319
+ });
320
+ return matchingRelations;
321
+ } else {
322
+ return this.componentData.get(compType);
323
+ }
324
+ });
325
+ });
326
+ for (let entityIndex = 0;entityIndex < this.entities.length; entityIndex++) {
327
+ const entity = this.entities[entityIndex];
328
+ const components = componentDataSources.map((dataSource, i) => {
329
+ const compType = componentTypes[i];
330
+ if (getIdType(compType) === "wildcard-relation") {
331
+ const matchingRelations = dataSource;
332
+ const relations = [];
333
+ for (const relType of matchingRelations) {
334
+ const dataArray = this.componentData.get(relType);
335
+ if (dataArray && dataArray[entityIndex] !== undefined) {
336
+ const decodedRel = decodeRelationId(relType);
337
+ relations.push([decodedRel.targetId, dataArray[entityIndex]]);
338
+ }
339
+ }
340
+ return relations;
341
+ } else {
342
+ const dataArray = dataSource;
343
+ return dataArray ? dataArray[entityIndex] : undefined;
344
+ }
345
+ });
346
+ callback(entity, ...components);
347
+ }
348
+ }
349
+ forEach(callback) {
350
+ for (let i = 0;i < this.entities.length; i++) {
351
+ const components = new Map;
352
+ for (const componentType of this.componentTypes) {
353
+ components.set(componentType, this.componentData.get(componentType)[i]);
354
+ }
355
+ callback(this.entities[i], components);
356
+ }
357
+ }
358
+ }
359
+
360
+ // src/command-buffer.ts
361
+ class CommandBuffer {
362
+ commands = [];
363
+ executeEntityCommands;
364
+ constructor(executeEntityCommands) {
365
+ this.executeEntityCommands = executeEntityCommands;
366
+ }
367
+ addComponent(entityId, componentType, component) {
368
+ this.commands.push({ type: "addComponent", entityId, componentType, component });
369
+ }
370
+ removeComponent(entityId, componentType) {
371
+ this.commands.push({ type: "removeComponent", entityId, componentType });
372
+ }
373
+ destroyEntity(entityId) {
374
+ this.commands.push({ type: "destroyEntity", entityId });
375
+ }
376
+ execute() {
377
+ const entityCommands = new Map;
378
+ for (const cmd of this.commands) {
379
+ if (!entityCommands.has(cmd.entityId)) {
380
+ entityCommands.set(cmd.entityId, []);
381
+ }
382
+ entityCommands.get(cmd.entityId).push(cmd);
383
+ }
384
+ for (const [entityId, commands] of entityCommands) {
385
+ this.executeEntityCommands(entityId, commands);
386
+ }
387
+ this.commands = [];
388
+ }
389
+ getCommands() {
390
+ return [...this.commands];
391
+ }
392
+ clear() {
393
+ this.commands = [];
394
+ }
395
+ }
396
+
397
+ // src/query-filter.ts
398
+ function matchesComponentTypes(archetype, componentTypes) {
399
+ return componentTypes.every((type) => {
400
+ const detailedType = getDetailedIdType(type);
401
+ if (detailedType.type === "wildcard-relation") {
402
+ return archetype.componentTypes.includes(detailedType.componentId);
403
+ } else {
404
+ return archetype.componentTypes.includes(type);
405
+ }
406
+ });
407
+ }
408
+ function matchesFilter(archetype, filter) {
409
+ const negativeTypes = filter.negativeComponentTypes || [];
410
+ return negativeTypes.every((type) => {
411
+ const detailedType = getDetailedIdType(type);
412
+ if (detailedType.type === "wildcard-relation") {
413
+ return !archetype.componentTypes.some((archetypeType) => {
414
+ if (!isRelationId(archetypeType))
415
+ return false;
416
+ const decoded = decodeRelationId(archetypeType);
417
+ return decoded.componentId === detailedType.componentId;
418
+ });
419
+ } else {
420
+ return !archetype.componentTypes.includes(type);
421
+ }
422
+ });
423
+ }
424
+
425
+ // src/query.ts
426
+ class Query {
427
+ world;
428
+ componentTypes;
429
+ filter;
430
+ cachedArchetypes = [];
431
+ isDisposed = false;
432
+ constructor(world, componentTypes, filter = {}) {
433
+ this.world = world;
434
+ this.componentTypes = [...componentTypes].sort((a, b) => a - b);
435
+ this.filter = filter;
436
+ this.updateCache();
437
+ world.registerQuery(this);
438
+ }
439
+ getEntities() {
440
+ if (this.isDisposed) {
441
+ throw new Error("Query has been disposed");
442
+ }
443
+ const result = [];
444
+ for (const archetype of this.cachedArchetypes) {
445
+ result.push(...archetype.getEntities());
446
+ }
447
+ return result;
448
+ }
449
+ getEntitiesWithComponents(componentTypes) {
450
+ if (this.isDisposed) {
451
+ throw new Error("Query has been disposed");
452
+ }
453
+ const result = [];
454
+ for (const archetype of this.cachedArchetypes) {
455
+ const entitiesWithData = archetype.getEntitiesWithComponents(componentTypes);
456
+ result.push(...entitiesWithData);
457
+ }
458
+ return result;
459
+ }
460
+ forEach(componentTypes, callback) {
461
+ if (this.isDisposed) {
462
+ throw new Error("Query has been disposed");
463
+ }
464
+ for (const archetype of this.cachedArchetypes) {
465
+ archetype.forEachWithComponents(componentTypes, callback);
466
+ }
467
+ }
468
+ getComponentData(componentType) {
469
+ if (this.isDisposed) {
470
+ throw new Error("Query has been disposed");
471
+ }
472
+ const result = [];
473
+ for (const archetype of this.cachedArchetypes) {
474
+ result.push(...archetype.getComponentData(componentType));
475
+ }
476
+ return result;
477
+ }
478
+ updateCache() {
479
+ if (this.isDisposed)
480
+ return;
481
+ this.cachedArchetypes = this.world.getMatchingArchetypes(this.componentTypes).filter((archetype) => matchesFilter(archetype, this.filter));
482
+ }
483
+ checkNewArchetype(archetype) {
484
+ if (this.isDisposed)
485
+ return;
486
+ if (matchesComponentTypes(archetype, this.componentTypes) && matchesFilter(archetype, this.filter) && !this.cachedArchetypes.includes(archetype)) {
487
+ this.cachedArchetypes.push(archetype);
488
+ }
489
+ }
490
+ dispose() {
491
+ if (!this.isDisposed) {
492
+ this.world.unregisterQuery(this);
493
+ this.cachedArchetypes = [];
494
+ this.isDisposed = true;
495
+ }
496
+ }
497
+ [Symbol.dispose]() {
498
+ this.dispose();
499
+ }
500
+ get disposed() {
501
+ return this.isDisposed;
502
+ }
503
+ }
504
+
505
+ // src/world.ts
506
+ class World {
507
+ entityIdManager = new EntityIdManager;
508
+ archetypes = [];
509
+ archetypeMap = new Map;
510
+ entityToArchetype = new Map;
511
+ systems = [];
512
+ queries = [];
513
+ commandBuffer;
514
+ componentToArchetypes = new Map;
515
+ constructor() {
516
+ this.commandBuffer = new CommandBuffer((entityId, commands) => this.executeEntityCommands(entityId, commands));
517
+ }
518
+ getComponentTypesHash(componentTypes) {
519
+ return componentTypes.join(",");
520
+ }
521
+ createEntity() {
522
+ const entityId = this.entityIdManager.allocate();
523
+ let emptyArchetype = this.getOrCreateArchetype([]);
524
+ emptyArchetype.addEntity(entityId, new Map);
525
+ this.entityToArchetype.set(entityId, emptyArchetype);
526
+ return entityId;
527
+ }
528
+ _destroyEntity(entityId) {
529
+ const archetype = this.entityToArchetype.get(entityId);
530
+ if (!archetype) {
531
+ return;
532
+ }
533
+ archetype.removeEntity(entityId);
534
+ this.entityToArchetype.delete(entityId);
535
+ this.entityIdManager.deallocate(entityId);
536
+ }
537
+ hasEntity(entityId) {
538
+ return this.entityToArchetype.has(entityId);
539
+ }
540
+ addComponent(entityId, componentType, component) {
541
+ if (!this.hasEntity(entityId)) {
542
+ throw new Error(`Entity ${entityId} does not exist`);
543
+ }
544
+ this.commandBuffer.addComponent(entityId, componentType, component);
545
+ }
546
+ removeComponent(entityId, componentType) {
547
+ if (!this.hasEntity(entityId)) {
548
+ throw new Error(`Entity ${entityId} does not exist`);
549
+ }
550
+ this.commandBuffer.removeComponent(entityId, componentType);
551
+ }
552
+ destroyEntity(entityId) {
553
+ this.commandBuffer.destroyEntity(entityId);
554
+ }
555
+ hasComponent(entityId, componentType) {
556
+ const archetype = this.entityToArchetype.get(entityId);
557
+ return archetype ? archetype.componentTypes.includes(componentType) : false;
558
+ }
559
+ getComponent(entityId, componentType) {
560
+ const archetype = this.entityToArchetype.get(entityId);
561
+ return archetype ? archetype.getComponent(entityId, componentType) : undefined;
562
+ }
563
+ registerSystem(system) {
564
+ this.systems.push(system);
565
+ }
566
+ unregisterSystem(system) {
567
+ const index = this.systems.indexOf(system);
568
+ if (index !== -1) {
569
+ this.systems.splice(index, 1);
570
+ }
571
+ }
572
+ update(...params) {
573
+ for (const system of this.systems) {
574
+ system.update(this, ...params);
575
+ }
576
+ this.commandBuffer.execute();
577
+ }
578
+ flushCommands() {
579
+ this.commandBuffer.execute();
580
+ }
581
+ createQuery(componentTypes, filter = {}) {
582
+ return new Query(this, componentTypes, filter);
583
+ }
584
+ registerQuery(query) {
585
+ this.queries.push(query);
586
+ }
587
+ unregisterQuery(query) {
588
+ const index = this.queries.indexOf(query);
589
+ if (index !== -1) {
590
+ this.queries.splice(index, 1);
591
+ }
592
+ }
593
+ getMatchingArchetypes(componentTypes) {
594
+ if (componentTypes.length === 0) {
595
+ return [...this.archetypes];
596
+ }
597
+ const regularComponents = [];
598
+ const wildcardRelations = [];
599
+ for (const type of componentTypes) {
600
+ const detailedType = getDetailedIdType(type);
601
+ if (detailedType.type === "wildcard-relation") {
602
+ wildcardRelations.push({
603
+ componentId: detailedType.componentId,
604
+ relationId: type
605
+ });
606
+ } else {
607
+ regularComponents.push(type);
608
+ }
609
+ }
610
+ let matchingArchetypes = [];
611
+ if (regularComponents.length > 0) {
612
+ const sortedRegularTypes = [...regularComponents].sort((a, b) => a - b);
613
+ if (sortedRegularTypes.length === 1) {
614
+ const componentType = sortedRegularTypes[0];
615
+ matchingArchetypes = this.componentToArchetypes.get(componentType) || [];
616
+ } else {
617
+ const archetypeLists = sortedRegularTypes.map((type) => this.componentToArchetypes.get(type) || []);
618
+ const firstList = archetypeLists[0] || [];
619
+ const intersection = new Set;
620
+ for (const archetype of firstList) {
621
+ let hasAllComponents = true;
622
+ for (let i = 1;i < archetypeLists.length; i++) {
623
+ const otherList = archetypeLists[i];
624
+ if (!otherList.includes(archetype)) {
625
+ hasAllComponents = false;
626
+ break;
627
+ }
628
+ }
629
+ if (hasAllComponents) {
630
+ intersection.add(archetype);
631
+ }
632
+ }
633
+ matchingArchetypes = Array.from(intersection);
634
+ }
635
+ } else {
636
+ matchingArchetypes = [...this.archetypes];
637
+ }
638
+ for (const wildcard of wildcardRelations) {
639
+ const componentArchetypes = this.componentToArchetypes.get(wildcard.componentId) || [];
640
+ matchingArchetypes = matchingArchetypes.filter((archetype) => componentArchetypes.includes(archetype));
641
+ }
642
+ return matchingArchetypes;
643
+ }
644
+ queryEntities(componentTypes, includeComponents) {
645
+ const matchingArchetypes = this.getMatchingArchetypes(componentTypes);
646
+ if (includeComponents) {
647
+ const result = [];
648
+ for (const archetype of matchingArchetypes) {
649
+ const entitiesWithData = archetype.getEntitiesWithComponents(componentTypes);
650
+ result.push(...entitiesWithData);
651
+ }
652
+ return result;
653
+ } else {
654
+ const result = [];
655
+ for (const archetype of matchingArchetypes) {
656
+ result.push(...archetype.getEntities());
657
+ }
658
+ return result;
659
+ }
660
+ }
661
+ executeEntityCommands(entityId, commands) {
662
+ const hasDestroy = commands.some((cmd) => cmd.type === "destroyEntity");
663
+ if (hasDestroy) {
664
+ this._destroyEntity(entityId);
665
+ return;
666
+ }
667
+ const currentArchetype = this.entityToArchetype.get(entityId);
668
+ if (!currentArchetype) {
669
+ return;
670
+ }
671
+ const currentComponents = new Map;
672
+ for (const componentType of currentArchetype.componentTypes) {
673
+ const data = currentArchetype.getComponent(entityId, componentType);
674
+ if (data !== undefined) {
675
+ currentComponents.set(componentType, data);
676
+ }
677
+ }
678
+ const adds = new Map;
679
+ const removes = new Set;
680
+ for (const cmd of commands) {
681
+ switch (cmd.type) {
682
+ case "addComponent":
683
+ if (cmd.componentType && cmd.component !== undefined) {
684
+ adds.set(cmd.componentType, cmd.component);
685
+ removes.delete(cmd.componentType);
686
+ }
687
+ break;
688
+ case "removeComponent":
689
+ if (cmd.componentType) {
690
+ removes.add(cmd.componentType);
691
+ adds.delete(cmd.componentType);
692
+ }
693
+ break;
694
+ }
695
+ }
696
+ const finalComponents = new Map(currentComponents);
697
+ for (const componentType of removes) {
698
+ finalComponents.delete(componentType);
699
+ }
700
+ for (const [componentType, component] of adds) {
701
+ finalComponents.set(componentType, component);
702
+ }
703
+ const finalComponentTypes = Array.from(finalComponents.keys()).sort((a, b) => a - b);
704
+ const currentComponentTypes = currentArchetype.componentTypes.sort((a, b) => a - b);
705
+ const needsArchetypeChange = finalComponentTypes.length !== currentComponentTypes.length || !finalComponentTypes.every((type, index) => type === currentComponentTypes[index]);
706
+ if (needsArchetypeChange) {
707
+ const newArchetype = this.getOrCreateArchetype(finalComponentTypes);
708
+ currentArchetype.removeEntity(entityId);
709
+ newArchetype.addEntity(entityId, finalComponents);
710
+ this.entityToArchetype.set(entityId, newArchetype);
711
+ } else {
712
+ for (const [componentType, component] of adds) {
713
+ currentArchetype.setComponent(entityId, componentType, component);
714
+ }
715
+ }
716
+ }
717
+ getOrCreateArchetype(componentTypes) {
718
+ const sortedTypes = [...componentTypes].sort((a, b) => a - b);
719
+ const hashKey = this.getComponentTypesHash(sortedTypes);
720
+ return getOrCreateWithSideEffect(this.archetypeMap, hashKey, () => {
721
+ const newArchetype = new Archetype(sortedTypes);
722
+ this.archetypes.push(newArchetype);
723
+ for (const componentType of sortedTypes) {
724
+ const archetypes = this.componentToArchetypes.get(componentType) || [];
725
+ archetypes.push(newArchetype);
726
+ this.componentToArchetypes.set(componentType, archetypes);
727
+ }
728
+ for (const query of this.queries) {
729
+ query.checkNewArchetype(newArchetype);
730
+ }
731
+ return newArchetype;
732
+ });
733
+ }
734
+ }
735
+ export {
736
+ isRelationId,
737
+ isEntityId,
738
+ isComponentId,
739
+ inspectEntityId,
740
+ getIdType,
741
+ getDetailedIdType,
742
+ decodeRelationId,
743
+ createRelationId,
744
+ createEntityId,
745
+ createComponentId,
746
+ World,
747
+ WILDCARD_TARGET_ID,
748
+ RELATION_SHIFT,
749
+ Query,
750
+ INVALID_COMPONENT_ID,
751
+ EntityIdManager,
752
+ ENTITY_ID_START,
753
+ ComponentIdManager,
754
+ COMPONENT_ID_MAX,
755
+ Archetype
756
+ };