@codehz/ecs 0.3.7 → 0.3.8

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 DELETED
@@ -1,1481 +0,0 @@
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 relation(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 isWildcardRelationId(id) {
44
- if (!isRelationId(id)) {
45
- return false;
46
- }
47
- const absId = -id;
48
- const targetId = absId % RELATION_SHIFT;
49
- return targetId === WILDCARD_TARGET_ID;
50
- }
51
- function decodeRelationId(relationId) {
52
- if (!isRelationId(relationId)) {
53
- throw new Error("ID is not a relation ID");
54
- }
55
- const absId = -relationId;
56
- const componentId = Math.floor(absId / RELATION_SHIFT);
57
- const targetId = absId % RELATION_SHIFT;
58
- if (targetId === WILDCARD_TARGET_ID) {
59
- return { componentId, targetId, type: "wildcard" };
60
- } else if (isEntityId(targetId)) {
61
- return { componentId, targetId, type: "entity" };
62
- } else if (isComponentId(targetId)) {
63
- return { componentId, targetId, type: "component" };
64
- } else {
65
- throw new Error("Invalid target ID in relation");
66
- }
67
- }
68
- function getIdType(id) {
69
- if (isComponentId(id))
70
- return "component";
71
- if (isEntityId(id))
72
- return "entity";
73
- if (isRelationId(id)) {
74
- try {
75
- const decoded = decodeRelationId(id);
76
- if (!isComponentId(decoded.componentId) || decoded.type !== "wildcard" && !isEntityId(decoded.targetId) && !isComponentId(decoded.targetId)) {
77
- return "invalid";
78
- }
79
- switch (decoded.type) {
80
- case "entity":
81
- return "entity-relation";
82
- case "component":
83
- return "component-relation";
84
- case "wildcard":
85
- return "wildcard-relation";
86
- }
87
- } catch (error) {
88
- return "invalid";
89
- }
90
- }
91
- return "invalid";
92
- }
93
- function getDetailedIdType(id) {
94
- if (isComponentId(id)) {
95
- return { type: "component" };
96
- }
97
- if (isEntityId(id)) {
98
- return { type: "entity" };
99
- }
100
- if (isRelationId(id)) {
101
- try {
102
- const decoded = decodeRelationId(id);
103
- if (!isComponentId(decoded.componentId) || decoded.type !== "wildcard" && !isEntityId(decoded.targetId) && !isComponentId(decoded.targetId)) {
104
- return { type: "invalid" };
105
- }
106
- let type;
107
- switch (decoded.type) {
108
- case "entity":
109
- type = "entity-relation";
110
- break;
111
- case "component":
112
- type = "component-relation";
113
- break;
114
- case "wildcard":
115
- type = "wildcard-relation";
116
- break;
117
- }
118
- return {
119
- type,
120
- componentId: decoded.componentId,
121
- targetId: decoded.targetId
122
- };
123
- } catch (error) {
124
- return { type: "invalid" };
125
- }
126
- }
127
- return { type: "invalid" };
128
- }
129
- function inspectEntityId(id) {
130
- if (id === INVALID_COMPONENT_ID) {
131
- return "Invalid Component ID (0)";
132
- }
133
- if (isComponentId(id)) {
134
- return `Component ID (${id})`;
135
- }
136
- if (isEntityId(id)) {
137
- return `Entity ID (${id})`;
138
- }
139
- if (isRelationId(id)) {
140
- try {
141
- const decoded = decodeRelationId(id);
142
- if (!isComponentId(decoded.componentId) || decoded.type !== "wildcard" && !isEntityId(decoded.targetId) && !isComponentId(decoded.targetId)) {
143
- return `Invalid Relation ID (${id})`;
144
- }
145
- const componentStr = `Component ID (${decoded.componentId})`;
146
- const targetStr = decoded.type === "entity" ? `Entity ID (${decoded.targetId})` : decoded.type === "component" ? `Component ID (${decoded.targetId})` : "Wildcard (*)";
147
- return `Relation ID: ${componentStr} -> ${targetStr}`;
148
- } catch (error) {
149
- return `Invalid Relation ID (${id})`;
150
- }
151
- }
152
- return `Unknown ID (${id})`;
153
- }
154
-
155
- class EntityIdManager {
156
- nextId = ENTITY_ID_START;
157
- freelist = new Set;
158
- allocate() {
159
- if (this.freelist.size > 0) {
160
- const id = this.freelist.values().next().value;
161
- this.freelist.delete(id);
162
- return id;
163
- } else {
164
- const id = this.nextId;
165
- this.nextId++;
166
- if (this.nextId >= Number.MAX_SAFE_INTEGER) {
167
- throw new Error("Entity ID overflow: reached maximum safe integer");
168
- }
169
- return id;
170
- }
171
- }
172
- deallocate(id) {
173
- if (!isEntityId(id)) {
174
- throw new Error("Can only deallocate valid entity IDs");
175
- }
176
- if (id >= this.nextId) {
177
- throw new Error("Cannot deallocate an ID that was never allocated");
178
- }
179
- this.freelist.add(id);
180
- }
181
- getFreelistSize() {
182
- return this.freelist.size;
183
- }
184
- getNextId() {
185
- return this.nextId;
186
- }
187
- serializeState() {
188
- return { nextId: this.nextId, freelist: Array.from(this.freelist) };
189
- }
190
- deserializeState(state) {
191
- if (typeof state.nextId !== "number") {
192
- throw new Error("Invalid state for EntityIdManager.deserializeState");
193
- }
194
- this.nextId = state.nextId;
195
- this.freelist = new Set(state.freelist || []);
196
- }
197
- }
198
-
199
- class ComponentIdAllocator {
200
- nextId = 1;
201
- allocate() {
202
- if (this.nextId > COMPONENT_ID_MAX) {
203
- throw new Error(`Component ID overflow: maximum ${COMPONENT_ID_MAX} components allowed`);
204
- }
205
- const id = this.nextId;
206
- this.nextId++;
207
- return id;
208
- }
209
- getNextId() {
210
- return this.nextId;
211
- }
212
- hasAvailableIds() {
213
- return this.nextId <= COMPONENT_ID_MAX;
214
- }
215
- }
216
- var globalComponentIdAllocator = new ComponentIdAllocator;
217
- var ComponentNames = new Map;
218
- var ComponentIdForNames = new Map;
219
- function component(name) {
220
- const id = globalComponentIdAllocator.allocate();
221
- if (name) {
222
- if (ComponentIdForNames.has(name)) {
223
- throw new Error(`Component name "${name}" is already registered`);
224
- }
225
- ComponentNames.set(id, name);
226
- ComponentIdForNames.set(name, id);
227
- }
228
- return id;
229
- }
230
- function getComponentIdByName(name) {
231
- return ComponentIdForNames.get(name);
232
- }
233
- function getComponentNameById(id) {
234
- return ComponentNames.get(id);
235
- }
236
- // src/types.ts
237
- function isOptionalEntityId(type) {
238
- return typeof type === "object" && type !== null && "optional" in type;
239
- }
240
-
241
- // src/utils.ts
242
- function getOrComputeCache(cache, key, compute) {
243
- let value = cache.get(key);
244
- if (value === undefined) {
245
- value = compute();
246
- cache.set(key, value);
247
- }
248
- return value;
249
- }
250
- function getOrCreateWithSideEffect(cache, key, create) {
251
- let value = cache.get(key);
252
- if (value === undefined) {
253
- value = create();
254
- cache.set(key, value);
255
- }
256
- return value;
257
- }
258
-
259
- // src/archetype.ts
260
- var MISSING_COMPONENT = Symbol("missing component");
261
-
262
- class Archetype {
263
- componentTypes;
264
- entities = [];
265
- componentData = new Map;
266
- entityToIndex = new Map;
267
- componentDataSourcesCache = new Map;
268
- constructor(componentTypes) {
269
- this.componentTypes = [...componentTypes].sort((a, b) => a - b);
270
- for (const componentType of this.componentTypes) {
271
- this.componentData.set(componentType, []);
272
- }
273
- }
274
- get size() {
275
- return this.entities.length;
276
- }
277
- matches(componentTypes) {
278
- if (this.componentTypes.length !== componentTypes.length) {
279
- return false;
280
- }
281
- const sortedTypes = [...componentTypes].sort((a, b) => a - b);
282
- return this.componentTypes.every((type, index) => type === sortedTypes[index]);
283
- }
284
- addEntity(entityId, componentData) {
285
- if (this.entityToIndex.has(entityId)) {
286
- throw new Error(`Entity ${entityId} is already in this archetype`);
287
- }
288
- const index = this.entities.length;
289
- this.entities.push(entityId);
290
- this.entityToIndex.set(entityId, index);
291
- for (const componentType of this.componentTypes) {
292
- const data = componentData.get(componentType);
293
- this.getComponentData(componentType).push(data === undefined ? MISSING_COMPONENT : data);
294
- }
295
- }
296
- getEntity(entityId) {
297
- const index = this.entityToIndex.get(entityId);
298
- if (index === undefined) {
299
- return;
300
- }
301
- const entityData = new Map;
302
- for (const componentType of this.componentTypes) {
303
- const dataArray = this.getComponentData(componentType);
304
- const data = dataArray[index];
305
- entityData.set(componentType, data === MISSING_COMPONENT ? undefined : data);
306
- }
307
- return entityData;
308
- }
309
- dump() {
310
- const result = [];
311
- for (let i = 0;i < this.entities.length; i++) {
312
- const entity = this.entities[i];
313
- const components = new Map;
314
- for (const componentType of this.componentTypes) {
315
- const dataArray = this.getComponentData(componentType);
316
- const data = dataArray[i];
317
- components.set(componentType, data === MISSING_COMPONENT ? undefined : data);
318
- }
319
- result.push({ entity, components });
320
- }
321
- return result;
322
- }
323
- removeEntity(entityId) {
324
- const index = this.entityToIndex.get(entityId);
325
- if (index === undefined) {
326
- return;
327
- }
328
- const removedData = new Map;
329
- for (const componentType of this.componentTypes) {
330
- const dataArray = this.getComponentData(componentType);
331
- removedData.set(componentType, dataArray[index]);
332
- }
333
- this.entityToIndex.delete(entityId);
334
- const lastIndex = this.entities.length - 1;
335
- if (index !== lastIndex) {
336
- const lastEntity = this.entities[lastIndex];
337
- this.entities[index] = lastEntity;
338
- this.entityToIndex.set(lastEntity, index);
339
- for (const componentType of this.componentTypes) {
340
- const dataArray = this.getComponentData(componentType);
341
- dataArray[index] = dataArray[lastIndex];
342
- }
343
- }
344
- this.entities.pop();
345
- for (const componentType of this.componentTypes) {
346
- this.getComponentData(componentType).pop();
347
- }
348
- return removedData;
349
- }
350
- exists(entityId) {
351
- return this.entityToIndex.has(entityId);
352
- }
353
- get(entityId, componentType) {
354
- const index = this.entityToIndex.get(entityId);
355
- if (index === undefined) {
356
- throw new Error(`Entity ${entityId} is not in this archetype`);
357
- }
358
- if (isWildcardRelationId(componentType)) {
359
- const decoded = decodeRelationId(componentType);
360
- const componentId = decoded.componentId;
361
- const relations = [];
362
- for (const relType of this.componentTypes) {
363
- const relDetailed = getDetailedIdType(relType);
364
- if ((relDetailed.type === "entity-relation" || relDetailed.type === "component-relation") && relDetailed.componentId === componentId) {
365
- const dataArray = this.getComponentData(relType);
366
- if (dataArray && dataArray[index] !== undefined) {
367
- const data = dataArray[index];
368
- relations.push([relDetailed.targetId, data === MISSING_COMPONENT ? undefined : data]);
369
- }
370
- }
371
- }
372
- return relations;
373
- } else {
374
- const data = this.getComponentData(componentType)[index];
375
- return data === MISSING_COMPONENT ? undefined : data;
376
- }
377
- }
378
- set(entityId, componentType, data) {
379
- if (!this.componentData.has(componentType)) {
380
- throw new Error(`Component type ${componentType} is not in this archetype`);
381
- }
382
- const index = this.entityToIndex.get(entityId);
383
- if (index === undefined) {
384
- throw new Error(`Entity ${entityId} is not in this archetype`);
385
- }
386
- const dataArray = this.getComponentData(componentType);
387
- dataArray[index] = data;
388
- }
389
- getEntities() {
390
- return this.entities;
391
- }
392
- getEntityToIndexMap() {
393
- return this.entityToIndex;
394
- }
395
- getComponentData(componentType) {
396
- const data = this.componentData.get(componentType);
397
- if (!data) {
398
- throw new Error(`Component type ${componentType} is not in this archetype`);
399
- }
400
- return data;
401
- }
402
- getOptionalComponentData(componentType) {
403
- return this.componentData.get(componentType);
404
- }
405
- getCachedComponentDataSources(componentTypes) {
406
- const cacheKey = componentTypes.map((id) => isOptionalEntityId(id) ? `opt(${id.optional})` : `${id}`).join(",");
407
- return getOrComputeCache(this.componentDataSourcesCache, cacheKey, () => {
408
- return componentTypes.map((compType) => {
409
- let optional = false;
410
- if (isOptionalEntityId(compType)) {
411
- compType = compType.optional;
412
- optional = true;
413
- }
414
- const detailedType = getDetailedIdType(compType);
415
- if (detailedType.type === "wildcard-relation") {
416
- const componentId = detailedType.componentId;
417
- const matchingRelations = this.componentTypes.filter((ct) => {
418
- const detailedCt = getDetailedIdType(ct);
419
- if (detailedCt.type !== "entity-relation" && detailedCt.type !== "component-relation")
420
- return false;
421
- return detailedCt.componentId === componentId;
422
- });
423
- return optional ? matchingRelations.length > 0 ? matchingRelations : undefined : matchingRelations;
424
- } else {
425
- return optional ? this.getOptionalComponentData(compType) : this.getComponentData(compType);
426
- }
427
- });
428
- });
429
- }
430
- buildComponentsForIndex(componentTypes, componentDataSources, entityIndex) {
431
- return componentDataSources.map((dataSource, i) => {
432
- let compType = componentTypes[i];
433
- let optional = false;
434
- if (isOptionalEntityId(compType)) {
435
- compType = compType.optional;
436
- optional = true;
437
- }
438
- if (getIdType(compType) === "wildcard-relation") {
439
- if (dataSource === undefined) {
440
- if (optional) {
441
- return;
442
- } else {
443
- throw new Error(`No matching relations found for mandatory wildcard relation component type`);
444
- }
445
- }
446
- const matchingRelations = dataSource;
447
- const relations = [];
448
- for (const relType of matchingRelations) {
449
- const dataArray = this.getComponentData(relType);
450
- const data = dataArray[entityIndex];
451
- const decodedRel = decodeRelationId(relType);
452
- relations.push([decodedRel.targetId, data === MISSING_COMPONENT ? undefined : data]);
453
- }
454
- return optional ? { value: relations } : relations;
455
- } else {
456
- if (dataSource === undefined) {
457
- if (optional) {
458
- return;
459
- } else {
460
- throw new Error(`No matching relations found for mandatory wildcard relation component type`);
461
- }
462
- }
463
- const dataArray = dataSource;
464
- const data = dataArray[entityIndex];
465
- const result = data === MISSING_COMPONENT ? undefined : data;
466
- return optional ? { value: result } : result;
467
- }
468
- });
469
- }
470
- getEntitiesWithComponents(componentTypes) {
471
- const result = [];
472
- this.forEachWithComponents(componentTypes, (entity, ...components) => {
473
- result.push({ entity, components });
474
- });
475
- return result;
476
- }
477
- *iterateWithComponents(componentTypes) {
478
- const componentDataSources = this.getCachedComponentDataSources(componentTypes);
479
- for (let entityIndex = 0;entityIndex < this.entities.length; entityIndex++) {
480
- const entity = this.entities[entityIndex];
481
- const components = this.buildComponentsForIndex(componentTypes, componentDataSources, entityIndex);
482
- yield [entity, ...components];
483
- }
484
- }
485
- forEachWithComponents(componentTypes, callback) {
486
- const componentDataSources = this.getCachedComponentDataSources(componentTypes);
487
- for (let entityIndex = 0;entityIndex < this.entities.length; entityIndex++) {
488
- const entity = this.entities[entityIndex];
489
- const components = this.buildComponentsForIndex(componentTypes, componentDataSources, entityIndex);
490
- callback(entity, ...components);
491
- }
492
- }
493
- forEach(callback) {
494
- for (let i = 0;i < this.entities.length; i++) {
495
- const components = new Map;
496
- for (const componentType of this.componentTypes) {
497
- const data = this.getComponentData(componentType)[i];
498
- components.set(componentType, data === MISSING_COMPONENT ? undefined : data);
499
- }
500
- callback(this.entities[i], components);
501
- }
502
- }
503
- }
504
-
505
- // src/changeset.ts
506
- class ComponentChangeset {
507
- adds = new Map;
508
- removes = new Set;
509
- set(componentType, component2) {
510
- this.adds.set(componentType, component2);
511
- this.removes.delete(componentType);
512
- }
513
- delete(componentType) {
514
- this.removes.add(componentType);
515
- this.adds.delete(componentType);
516
- }
517
- hasChanges() {
518
- return this.adds.size > 0 || this.removes.size > 0;
519
- }
520
- clear() {
521
- this.adds.clear();
522
- this.removes.clear();
523
- }
524
- merge(other) {
525
- for (const [componentType, component2] of other.adds) {
526
- this.adds.set(componentType, component2);
527
- this.removes.delete(componentType);
528
- }
529
- for (const componentType of other.removes) {
530
- this.removes.add(componentType);
531
- this.adds.delete(componentType);
532
- }
533
- }
534
- applyTo(existingComponents) {
535
- for (const componentType of this.removes) {
536
- existingComponents.delete(componentType);
537
- }
538
- for (const [componentType, component2] of this.adds) {
539
- existingComponents.set(componentType, component2);
540
- }
541
- return existingComponents;
542
- }
543
- getFinalComponentTypes(existingComponentTypes) {
544
- const finalComponentTypes = new Set(existingComponentTypes);
545
- let changed = false;
546
- for (const componentType of this.removes) {
547
- if (!finalComponentTypes.has(componentType)) {
548
- this.removes.delete(componentType);
549
- continue;
550
- }
551
- changed = true;
552
- finalComponentTypes.delete(componentType);
553
- }
554
- for (const componentType of this.adds.keys()) {
555
- if (finalComponentTypes.has(componentType)) {
556
- continue;
557
- }
558
- changed = true;
559
- finalComponentTypes.add(componentType);
560
- }
561
- return changed ? Array.from(finalComponentTypes) : undefined;
562
- }
563
- }
564
-
565
- // src/command-buffer.ts
566
- class CommandBuffer {
567
- commands = [];
568
- executeEntityCommands;
569
- constructor(executeEntityCommands) {
570
- this.executeEntityCommands = executeEntityCommands;
571
- }
572
- set(entityId, componentType, component2) {
573
- this.commands.push({ type: "set", entityId, componentType, component: component2 });
574
- }
575
- remove(entityId, componentType) {
576
- this.commands.push({ type: "delete", entityId, componentType });
577
- }
578
- delete(entityId) {
579
- this.commands.push({ type: "destroy", entityId });
580
- }
581
- execute() {
582
- const MAX_ITERATIONS = 100;
583
- let iterations = 0;
584
- while (this.commands.length > 0) {
585
- if (iterations >= MAX_ITERATIONS) {
586
- throw new Error("Command execution exceeded maximum iterations, possible infinite loop");
587
- }
588
- iterations++;
589
- const currentCommands = [...this.commands];
590
- this.commands = [];
591
- const entityCommands = new Map;
592
- for (const cmd of currentCommands) {
593
- if (!entityCommands.has(cmd.entityId)) {
594
- entityCommands.set(cmd.entityId, []);
595
- }
596
- entityCommands.get(cmd.entityId).push(cmd);
597
- }
598
- for (const [entityId, commands] of entityCommands) {
599
- this.executeEntityCommands(entityId, commands);
600
- }
601
- }
602
- }
603
- getCommands() {
604
- return [...this.commands];
605
- }
606
- clear() {
607
- this.commands = [];
608
- }
609
- }
610
-
611
- // src/multi-map.ts
612
- class MultiMap {
613
- map = new Map;
614
- _valueCount = 0;
615
- get valueCount() {
616
- return this._valueCount;
617
- }
618
- get keyCount() {
619
- return this.map.size;
620
- }
621
- hasKey(key) {
622
- return this.map.has(key);
623
- }
624
- has(key, value) {
625
- const set = this.map.get(key);
626
- if (!set)
627
- return false;
628
- if (arguments.length === 1)
629
- return true;
630
- return set.has(value);
631
- }
632
- add(key, value) {
633
- let set = this.map.get(key);
634
- if (!set) {
635
- set = new Set;
636
- this.map.set(key, set);
637
- }
638
- if (!set.has(value)) {
639
- set.add(value);
640
- this._valueCount++;
641
- }
642
- }
643
- remove(key, value) {
644
- const set = this.map.get(key);
645
- if (!set)
646
- return false;
647
- if (!set.has(value))
648
- return false;
649
- set.delete(value);
650
- this._valueCount--;
651
- if (set.size === 0)
652
- this.map.delete(key);
653
- return true;
654
- }
655
- deleteKey(key) {
656
- const set = this.map.get(key);
657
- if (!set)
658
- return false;
659
- this._valueCount -= set.size;
660
- this.map.delete(key);
661
- return true;
662
- }
663
- get(key) {
664
- const set = this.map.get(key);
665
- return set ? new Set(set) : new Set;
666
- }
667
- *keys() {
668
- yield* this.map.keys();
669
- }
670
- *values() {
671
- for (const set of this.map.values()) {
672
- for (const v of set)
673
- yield v;
674
- }
675
- }
676
- [Symbol.iterator]() {
677
- return this.entries();
678
- }
679
- *entries() {
680
- for (const [k, set] of this.map.entries()) {
681
- for (const v of set)
682
- yield [k, v];
683
- }
684
- }
685
- clear() {
686
- this.map.clear();
687
- this._valueCount = 0;
688
- }
689
- }
690
-
691
- // src/query-filter.ts
692
- function serializeQueryFilter(filter = {}) {
693
- const negative = (filter.negativeComponentTypes || []).slice().sort((a, b) => a - b);
694
- if (negative.length === 0)
695
- return "";
696
- return `neg:${negative.join(",")}`;
697
- }
698
- function matchesComponentTypes(archetype, componentTypes) {
699
- return componentTypes.every((type) => {
700
- const detailedType = getDetailedIdType(type);
701
- if (detailedType.type === "wildcard-relation") {
702
- return archetype.componentTypes.some((archetypeType) => {
703
- if (!isRelationId(archetypeType))
704
- return false;
705
- const decoded = decodeRelationId(archetypeType);
706
- return decoded.componentId === detailedType.componentId;
707
- });
708
- } else {
709
- return archetype.componentTypes.includes(type);
710
- }
711
- });
712
- }
713
- function matchesFilter(archetype, filter) {
714
- const negativeTypes = filter.negativeComponentTypes || [];
715
- return negativeTypes.every((type) => {
716
- const detailedType = getDetailedIdType(type);
717
- if (detailedType.type === "wildcard-relation") {
718
- return !archetype.componentTypes.some((archetypeType) => {
719
- if (!isRelationId(archetypeType))
720
- return false;
721
- const decoded = decodeRelationId(archetypeType);
722
- return decoded.componentId === detailedType.componentId;
723
- });
724
- } else {
725
- return !archetype.componentTypes.includes(type);
726
- }
727
- });
728
- }
729
-
730
- // src/query.ts
731
- class Query {
732
- world;
733
- componentTypes;
734
- filter;
735
- cachedArchetypes = [];
736
- isDisposed = false;
737
- constructor(world, componentTypes, filter = {}) {
738
- this.world = world;
739
- this.componentTypes = [...componentTypes].sort((a, b) => a - b);
740
- this.filter = filter;
741
- this.updateCache();
742
- world._registerQuery(this);
743
- }
744
- getEntities() {
745
- if (this.isDisposed) {
746
- throw new Error("Query has been disposed");
747
- }
748
- const result = [];
749
- for (const archetype of this.cachedArchetypes) {
750
- result.push(...archetype.getEntities());
751
- }
752
- return result;
753
- }
754
- getEntitiesWithComponents(componentTypes) {
755
- if (this.isDisposed) {
756
- throw new Error("Query has been disposed");
757
- }
758
- const result = [];
759
- for (const archetype of this.cachedArchetypes) {
760
- const entitiesWithData = archetype.getEntitiesWithComponents(componentTypes);
761
- result.push(...entitiesWithData);
762
- }
763
- return result;
764
- }
765
- forEach(componentTypes, callback) {
766
- if (this.isDisposed) {
767
- throw new Error("Query has been disposed");
768
- }
769
- for (const archetype of this.cachedArchetypes) {
770
- archetype.forEachWithComponents(componentTypes, callback);
771
- }
772
- }
773
- *iterate(componentTypes) {
774
- if (this.isDisposed) {
775
- throw new Error("Query has been disposed");
776
- }
777
- for (const archetype of this.cachedArchetypes) {
778
- yield* archetype.iterateWithComponents(componentTypes);
779
- }
780
- }
781
- getComponentData(componentType) {
782
- if (this.isDisposed) {
783
- throw new Error("Query has been disposed");
784
- }
785
- const result = [];
786
- for (const archetype of this.cachedArchetypes) {
787
- result.push(...archetype.getComponentData(componentType));
788
- }
789
- return result;
790
- }
791
- updateCache() {
792
- if (this.isDisposed)
793
- return;
794
- this.cachedArchetypes = this.world.getMatchingArchetypes(this.componentTypes).filter((archetype) => matchesFilter(archetype, this.filter));
795
- }
796
- checkNewArchetype(archetype) {
797
- if (this.isDisposed)
798
- return;
799
- if (matchesComponentTypes(archetype, this.componentTypes) && matchesFilter(archetype, this.filter) && !this.cachedArchetypes.includes(archetype)) {
800
- this.cachedArchetypes.push(archetype);
801
- }
802
- }
803
- removeArchetype(archetype) {
804
- if (this.isDisposed)
805
- return;
806
- const index = this.cachedArchetypes.indexOf(archetype);
807
- if (index !== -1) {
808
- this.cachedArchetypes.splice(index, 1);
809
- }
810
- }
811
- dispose() {
812
- this.world.releaseQuery(this);
813
- }
814
- _disposeInternal() {
815
- if (!this.isDisposed) {
816
- this.world._unregisterQuery(this);
817
- this.cachedArchetypes = [];
818
- this.isDisposed = true;
819
- }
820
- }
821
- [Symbol.dispose]() {
822
- this.dispose();
823
- }
824
- get disposed() {
825
- return this.isDisposed;
826
- }
827
- }
828
-
829
- // src/system-scheduler.ts
830
- class SystemScheduler {
831
- systems = new Set;
832
- systemDependencies = new Map;
833
- cachedExecutionOrder = null;
834
- addSystem(system, additionalDeps = []) {
835
- this.systems.add(system);
836
- for (const dep of system.dependencies || []) {
837
- this.systems.add(dep);
838
- }
839
- this.systemDependencies.set(system, new Set([...additionalDeps, ...system.dependencies || []]));
840
- this.cachedExecutionOrder = null;
841
- }
842
- getExecutionOrder() {
843
- if (this.cachedExecutionOrder !== null) {
844
- return this.cachedExecutionOrder;
845
- }
846
- const result = [];
847
- const visited = new Set;
848
- const visiting = new Set;
849
- const visit = (system) => {
850
- if (visited.has(system))
851
- return;
852
- if (visiting.has(system)) {
853
- throw new Error("Circular dependency detected in system scheduling");
854
- }
855
- visiting.add(system);
856
- for (const dep of this.systemDependencies.get(system) || []) {
857
- visit(dep);
858
- }
859
- visiting.delete(system);
860
- visited.add(system);
861
- result.push(system);
862
- };
863
- for (const system of this.systems) {
864
- if (!visited.has(system)) {
865
- visit(system);
866
- }
867
- }
868
- this.cachedExecutionOrder = result;
869
- return result;
870
- }
871
- update(...params) {
872
- const executionOrder = this.getExecutionOrder();
873
- const systemPromises = new Map;
874
- for (const system of executionOrder) {
875
- const deps = Array.from(this.systemDependencies.get(system) || []);
876
- const depPromises = deps.map((dep) => systemPromises.get(dep)).filter(Boolean);
877
- if (depPromises.length > 0) {
878
- const promise = Promise.all(depPromises).then(() => system.update(...params));
879
- systemPromises.set(system, promise);
880
- } else {
881
- const result = system.update(...params);
882
- if (result instanceof Promise) {
883
- systemPromises.set(system, result);
884
- }
885
- }
886
- }
887
- return Promise.all(systemPromises.values());
888
- }
889
- clear() {
890
- this.systems.clear();
891
- this.cachedExecutionOrder = null;
892
- }
893
- }
894
-
895
- // src/world.ts
896
- class World {
897
- entityIdManager = new EntityIdManager;
898
- archetypes = [];
899
- archetypeBySignature = new Map;
900
- entityToArchetype = new Map;
901
- archetypesByComponent = new Map;
902
- entityReferences = new Map;
903
- queries = [];
904
- queryCache = new Map;
905
- systemScheduler = new SystemScheduler;
906
- commandBuffer = new CommandBuffer((entityId, commands) => this.executeEntityCommands(entityId, commands));
907
- hooks = new Map;
908
- exclusiveComponents = new Set;
909
- cascadeDeleteComponents = new Set;
910
- constructor(snapshot) {
911
- if (snapshot && typeof snapshot === "object") {
912
- if (snapshot.entityManager) {
913
- this.entityIdManager.deserializeState(snapshot.entityManager);
914
- }
915
- if (Array.isArray(snapshot.entities)) {
916
- for (const entry of snapshot.entities) {
917
- const entityId = entry.id;
918
- const componentsArray = entry.components || [];
919
- const componentMap = new Map;
920
- const componentTypes = [];
921
- for (const componentEntry of componentsArray) {
922
- const componentTypeRaw = componentEntry.type;
923
- let componentType;
924
- if (typeof componentTypeRaw === "number") {
925
- componentType = componentTypeRaw;
926
- } else if (typeof componentTypeRaw === "string") {
927
- const compId = getComponentIdByName(componentTypeRaw);
928
- if (compId === undefined) {
929
- throw new Error(`Unknown component name in snapshot: ${componentTypeRaw}`);
930
- }
931
- componentType = compId;
932
- } else if (typeof componentTypeRaw === "object" && componentTypeRaw !== null && typeof componentTypeRaw.component === "string") {
933
- const compId = getComponentIdByName(componentTypeRaw.component);
934
- if (compId === undefined) {
935
- throw new Error(`Unknown component name in snapshot: ${componentTypeRaw.component}`);
936
- }
937
- if (typeof componentTypeRaw.target === "string") {
938
- const targetCompId = getComponentIdByName(componentTypeRaw.target);
939
- if (targetCompId === undefined) {
940
- throw new Error(`Unknown target component name in snapshot: ${componentTypeRaw.target}`);
941
- }
942
- componentType = relation(compId, targetCompId);
943
- } else {
944
- componentType = relation(compId, componentTypeRaw.target);
945
- }
946
- } else {
947
- throw new Error(`Invalid component type in snapshot: ${JSON.stringify(componentTypeRaw)}`);
948
- }
949
- componentMap.set(componentType, componentEntry.value);
950
- componentTypes.push(componentType);
951
- }
952
- const archetype = this.ensureArchetype(componentTypes);
953
- archetype.addEntity(entityId, componentMap);
954
- this.entityToArchetype.set(entityId, archetype);
955
- for (const compType of componentTypes) {
956
- const detailedType = getDetailedIdType(compType);
957
- if (detailedType.type === "entity-relation") {
958
- const targetEntityId = detailedType.targetId;
959
- this.trackEntityReference(entityId, compType, targetEntityId);
960
- } else if (detailedType.type === "entity") {
961
- this.trackEntityReference(entityId, compType, compType);
962
- }
963
- }
964
- }
965
- }
966
- }
967
- }
968
- createArchetypeSignature(componentTypes) {
969
- return componentTypes.join(",");
970
- }
971
- new() {
972
- const entityId = this.entityIdManager.allocate();
973
- let emptyArchetype = this.ensureArchetype([]);
974
- emptyArchetype.addEntity(entityId, new Map);
975
- this.entityToArchetype.set(entityId, emptyArchetype);
976
- return entityId;
977
- }
978
- destroyEntityImmediate(entityId) {
979
- const queue = [entityId];
980
- const visited = new Set;
981
- while (queue.length > 0) {
982
- const cur = queue.shift();
983
- if (visited.has(cur))
984
- continue;
985
- visited.add(cur);
986
- const archetype = this.entityToArchetype.get(cur);
987
- if (!archetype) {
988
- continue;
989
- }
990
- const componentReferences = Array.from(this.getEntityReferences(cur));
991
- for (const [sourceEntityId, componentType] of componentReferences) {
992
- const sourceArchetype = this.entityToArchetype.get(sourceEntityId);
993
- if (!sourceArchetype)
994
- continue;
995
- const detailedType = getDetailedIdType(componentType);
996
- if (detailedType.type === "entity-relation" && this.cascadeDeleteComponents.has(detailedType.componentId)) {
997
- if (!visited.has(sourceEntityId)) {
998
- queue.push(sourceEntityId);
999
- }
1000
- continue;
1001
- }
1002
- const currentComponents = new Map;
1003
- let removedComponent = sourceArchetype.get(sourceEntityId, componentType);
1004
- for (const archetypeComponentType of sourceArchetype.componentTypes) {
1005
- if (archetypeComponentType !== componentType) {
1006
- const componentData = sourceArchetype.get(sourceEntityId, archetypeComponentType);
1007
- currentComponents.set(archetypeComponentType, componentData);
1008
- }
1009
- }
1010
- const newArchetype = this.ensureArchetype(currentComponents.keys());
1011
- sourceArchetype.removeEntity(sourceEntityId);
1012
- if (sourceArchetype.getEntities().length === 0) {
1013
- this.cleanupEmptyArchetype(sourceArchetype);
1014
- }
1015
- newArchetype.addEntity(sourceEntityId, currentComponents);
1016
- this.entityToArchetype.set(sourceEntityId, newArchetype);
1017
- this.untrackEntityReference(sourceEntityId, componentType, cur);
1018
- this.triggerLifecycleHooks(sourceEntityId, new Map, new Map([[componentType, removedComponent]]));
1019
- }
1020
- this.entityReferences.delete(cur);
1021
- archetype.removeEntity(cur);
1022
- if (archetype.getEntities().length === 0) {
1023
- this.cleanupEmptyArchetype(archetype);
1024
- }
1025
- this.entityToArchetype.delete(cur);
1026
- this.entityIdManager.deallocate(cur);
1027
- }
1028
- }
1029
- exists(entityId) {
1030
- return this.entityToArchetype.has(entityId);
1031
- }
1032
- set(entityId, componentType, component2) {
1033
- if (!this.exists(entityId)) {
1034
- throw new Error(`Entity ${entityId} does not exist`);
1035
- }
1036
- const detailedType = getDetailedIdType(componentType);
1037
- if (detailedType.type === "invalid") {
1038
- throw new Error(`Invalid component type: ${componentType}`);
1039
- }
1040
- if (detailedType.type === "wildcard-relation") {
1041
- throw new Error(`Cannot directly add wildcard relation components: ${componentType}`);
1042
- }
1043
- this.commandBuffer.set(entityId, componentType, component2);
1044
- }
1045
- remove(entityId, componentType) {
1046
- if (!this.exists(entityId)) {
1047
- throw new Error(`Entity ${entityId} does not exist`);
1048
- }
1049
- const detailedType = getDetailedIdType(componentType);
1050
- if (detailedType.type === "invalid") {
1051
- throw new Error(`Invalid component type: ${componentType}`);
1052
- }
1053
- this.commandBuffer.remove(entityId, componentType);
1054
- }
1055
- delete(entityId) {
1056
- this.commandBuffer.delete(entityId);
1057
- }
1058
- has(entityId, componentType) {
1059
- const archetype = this.entityToArchetype.get(entityId);
1060
- return archetype ? archetype.componentTypes.includes(componentType) : false;
1061
- }
1062
- get(entityId, componentType) {
1063
- const archetype = this.entityToArchetype.get(entityId);
1064
- if (!archetype) {
1065
- throw new Error(`Entity ${entityId} does not exist`);
1066
- }
1067
- const detailedType = getDetailedIdType(componentType);
1068
- if (detailedType.type !== "wildcard-relation") {
1069
- if (!archetype.componentTypes.includes(componentType)) {
1070
- throw new Error(`Entity ${entityId} does not have component ${componentType}. Use has() to check component existence before calling get().`);
1071
- }
1072
- }
1073
- return archetype.get(entityId, componentType);
1074
- }
1075
- registerSystem(system, additionalDeps = []) {
1076
- this.systemScheduler.addSystem(system, additionalDeps);
1077
- }
1078
- hook(componentType, hook) {
1079
- if (!this.hooks.has(componentType)) {
1080
- this.hooks.set(componentType, new Set);
1081
- }
1082
- this.hooks.get(componentType).add(hook);
1083
- if (hook.on_init !== undefined) {
1084
- this.archetypesByComponent.get(componentType)?.forEach((archetype) => {
1085
- const entities = archetype.getEntityToIndexMap();
1086
- const componentData = archetype.getComponentData(componentType);
1087
- for (const [entity, index] of entities) {
1088
- const data = componentData[index];
1089
- const value = data === MISSING_COMPONENT ? undefined : data;
1090
- hook.on_init?.(entity, componentType, value);
1091
- }
1092
- });
1093
- }
1094
- }
1095
- unhook(componentType, hook) {
1096
- const hooks = this.hooks.get(componentType);
1097
- if (hooks) {
1098
- hooks.delete(hook);
1099
- if (hooks.size === 0) {
1100
- this.hooks.delete(componentType);
1101
- }
1102
- }
1103
- }
1104
- setExclusive(componentId) {
1105
- this.exclusiveComponents.add(componentId);
1106
- }
1107
- setCascadeDelete(componentId) {
1108
- this.cascadeDeleteComponents.add(componentId);
1109
- }
1110
- update(...params) {
1111
- const result = this.systemScheduler.update(...params);
1112
- if (result instanceof Promise) {
1113
- return result.then(() => this.commandBuffer.execute());
1114
- } else {
1115
- this.commandBuffer.execute();
1116
- }
1117
- }
1118
- sync() {
1119
- this.commandBuffer.execute();
1120
- }
1121
- createQuery(componentTypes, filter = {}) {
1122
- const sortedTypes = [...componentTypes].sort((a, b) => a - b);
1123
- const filterKey = serializeQueryFilter(filter);
1124
- const key = `${this.createArchetypeSignature(sortedTypes)}${filterKey ? `|${filterKey}` : ""}`;
1125
- const cached = this.queryCache.get(key);
1126
- if (cached) {
1127
- cached.refCount++;
1128
- return cached.query;
1129
- }
1130
- const query = new Query(this, sortedTypes, filter);
1131
- this.queryCache.set(key, { query, refCount: 1 });
1132
- return query;
1133
- }
1134
- _registerQuery(query) {
1135
- this.queries.push(query);
1136
- }
1137
- _unregisterQuery(query) {
1138
- const index = this.queries.indexOf(query);
1139
- if (index !== -1) {
1140
- this.queries.splice(index, 1);
1141
- }
1142
- }
1143
- releaseQuery(query) {
1144
- for (const [k, v] of this.queryCache.entries()) {
1145
- if (v.query === query) {
1146
- v.refCount--;
1147
- if (v.refCount <= 0) {
1148
- this.queryCache.delete(k);
1149
- this._unregisterQuery(query);
1150
- v.query._disposeInternal();
1151
- }
1152
- return;
1153
- }
1154
- }
1155
- }
1156
- getMatchingArchetypes(componentTypes) {
1157
- if (componentTypes.length === 0) {
1158
- return [...this.archetypes];
1159
- }
1160
- const regularComponents = [];
1161
- const wildcardRelations = [];
1162
- for (const componentType of componentTypes) {
1163
- const detailedType = getDetailedIdType(componentType);
1164
- if (detailedType.type === "wildcard-relation") {
1165
- wildcardRelations.push({
1166
- componentId: detailedType.componentId,
1167
- relationId: componentType
1168
- });
1169
- } else {
1170
- regularComponents.push(componentType);
1171
- }
1172
- }
1173
- let matchingArchetypes = [];
1174
- if (regularComponents.length > 0) {
1175
- const sortedRegularTypes = [...regularComponents].sort((a, b) => a - b);
1176
- if (sortedRegularTypes.length === 1) {
1177
- const componentType = sortedRegularTypes[0];
1178
- matchingArchetypes = this.archetypesByComponent.get(componentType) || [];
1179
- } else {
1180
- const archetypeLists = sortedRegularTypes.map((type) => this.archetypesByComponent.get(type) || []);
1181
- const firstList = archetypeLists[0] || [];
1182
- const intersection = new Set;
1183
- for (const archetype of firstList) {
1184
- let hasAllComponents = true;
1185
- for (let listIndex = 1;listIndex < archetypeLists.length; listIndex++) {
1186
- const otherList = archetypeLists[listIndex];
1187
- if (!otherList.includes(archetype)) {
1188
- hasAllComponents = false;
1189
- break;
1190
- }
1191
- }
1192
- if (hasAllComponents) {
1193
- intersection.add(archetype);
1194
- }
1195
- }
1196
- matchingArchetypes = Array.from(intersection);
1197
- }
1198
- } else {
1199
- matchingArchetypes = [...this.archetypes];
1200
- }
1201
- for (const wildcard of wildcardRelations) {
1202
- matchingArchetypes = matchingArchetypes.filter((archetype) => archetype.componentTypes.some((archetypeType) => {
1203
- if (!isRelationId(archetypeType))
1204
- return false;
1205
- const decoded = decodeRelationId(archetypeType);
1206
- return decoded.componentId === wildcard.componentId;
1207
- }));
1208
- }
1209
- return matchingArchetypes;
1210
- }
1211
- query(componentTypes, includeComponents) {
1212
- const matchingArchetypes = this.getMatchingArchetypes(componentTypes);
1213
- if (includeComponents) {
1214
- const result = [];
1215
- for (const archetype of matchingArchetypes) {
1216
- const entitiesWithData = archetype.getEntitiesWithComponents(componentTypes);
1217
- result.push(...entitiesWithData);
1218
- }
1219
- return result;
1220
- } else {
1221
- const result = [];
1222
- for (const archetype of matchingArchetypes) {
1223
- result.push(...archetype.getEntities());
1224
- }
1225
- return result;
1226
- }
1227
- }
1228
- executeEntityCommands(entityId, commands) {
1229
- const changeset = new ComponentChangeset;
1230
- const hasDestroy = commands.some((cmd) => cmd.type === "destroy");
1231
- if (hasDestroy) {
1232
- this.destroyEntityImmediate(entityId);
1233
- return changeset;
1234
- }
1235
- const currentArchetype = this.entityToArchetype.get(entityId);
1236
- if (!currentArchetype) {
1237
- return changeset;
1238
- }
1239
- for (const command of commands) {
1240
- switch (command.type) {
1241
- case "set":
1242
- if (command.componentType) {
1243
- const detailedType = getDetailedIdType(command.componentType);
1244
- if ((detailedType.type === "entity-relation" || detailedType.type === "component-relation") && this.exclusiveComponents.has(detailedType.componentId)) {
1245
- for (const componentType of currentArchetype.componentTypes) {
1246
- const componentDetailedType = getDetailedIdType(componentType);
1247
- if ((componentDetailedType.type === "entity-relation" || componentDetailedType.type === "component-relation") && componentDetailedType.componentId === detailedType.componentId) {
1248
- changeset.delete(componentType);
1249
- }
1250
- }
1251
- }
1252
- changeset.set(command.componentType, command.component);
1253
- }
1254
- break;
1255
- case "delete":
1256
- if (command.componentType) {
1257
- const detailedType = getDetailedIdType(command.componentType);
1258
- if (detailedType.type === "wildcard-relation") {
1259
- const baseComponentId = detailedType.componentId;
1260
- for (const componentType of currentArchetype.componentTypes) {
1261
- const componentDetailedType = getDetailedIdType(componentType);
1262
- if (componentDetailedType.type === "entity-relation" || componentDetailedType.type === "component-relation") {
1263
- if (componentDetailedType.componentId === baseComponentId) {
1264
- changeset.delete(componentType);
1265
- }
1266
- }
1267
- }
1268
- } else {
1269
- changeset.delete(command.componentType);
1270
- }
1271
- }
1272
- break;
1273
- }
1274
- }
1275
- const finalComponentTypes = changeset.getFinalComponentTypes(currentArchetype.componentTypes);
1276
- const removedCompoents = new Map;
1277
- if (finalComponentTypes) {
1278
- const newArchetype = this.ensureArchetype(finalComponentTypes);
1279
- const currentComponents = currentArchetype.removeEntity(entityId);
1280
- for (const componentType of changeset.removes) {
1281
- removedCompoents.set(componentType, currentComponents.get(componentType));
1282
- }
1283
- newArchetype.addEntity(entityId, changeset.applyTo(currentComponents));
1284
- this.entityToArchetype.set(entityId, newArchetype);
1285
- } else {
1286
- for (const [componentType, component2] of changeset.adds) {
1287
- currentArchetype.set(entityId, componentType, component2);
1288
- }
1289
- }
1290
- for (const componentType of changeset.removes) {
1291
- const detailedType = getDetailedIdType(componentType);
1292
- if (detailedType.type === "entity-relation") {
1293
- const targetEntityId = detailedType.targetId;
1294
- this.untrackEntityReference(entityId, componentType, targetEntityId);
1295
- } else if (detailedType.type === "entity") {
1296
- this.untrackEntityReference(entityId, componentType, componentType);
1297
- }
1298
- }
1299
- for (const [componentType, component2] of changeset.adds) {
1300
- const detailedType = getDetailedIdType(componentType);
1301
- if (detailedType.type === "entity-relation") {
1302
- const targetEntityId = detailedType.targetId;
1303
- this.trackEntityReference(entityId, componentType, targetEntityId);
1304
- } else if (detailedType.type === "entity") {
1305
- this.trackEntityReference(entityId, componentType, componentType);
1306
- }
1307
- }
1308
- this.triggerLifecycleHooks(entityId, changeset.adds, removedCompoents);
1309
- return changeset;
1310
- }
1311
- ensureArchetype(componentTypes) {
1312
- const sortedTypes = Array.from(componentTypes).sort((a, b) => a - b);
1313
- const hashKey = this.createArchetypeSignature(sortedTypes);
1314
- return getOrCreateWithSideEffect(this.archetypeBySignature, hashKey, () => {
1315
- const newArchetype = new Archetype(sortedTypes);
1316
- this.archetypes.push(newArchetype);
1317
- for (const componentType of sortedTypes) {
1318
- const archetypes = this.archetypesByComponent.get(componentType) || [];
1319
- archetypes.push(newArchetype);
1320
- this.archetypesByComponent.set(componentType, archetypes);
1321
- }
1322
- for (const query of this.queries) {
1323
- query.checkNewArchetype(newArchetype);
1324
- }
1325
- return newArchetype;
1326
- });
1327
- }
1328
- trackEntityReference(sourceEntityId, componentType, targetEntityId) {
1329
- if (!this.entityReferences.has(targetEntityId)) {
1330
- this.entityReferences.set(targetEntityId, new MultiMap);
1331
- }
1332
- this.entityReferences.get(targetEntityId).add(sourceEntityId, componentType);
1333
- }
1334
- untrackEntityReference(sourceEntityId, componentType, targetEntityId) {
1335
- const references = this.entityReferences.get(targetEntityId);
1336
- if (references) {
1337
- references.remove(sourceEntityId, componentType);
1338
- if (references.keyCount === 0) {
1339
- this.entityReferences.delete(targetEntityId);
1340
- }
1341
- }
1342
- }
1343
- getEntityReferences(targetEntityId) {
1344
- return this.entityReferences.get(targetEntityId) ?? new MultiMap;
1345
- }
1346
- cleanupEmptyArchetype(archetype) {
1347
- if (archetype.getEntities().length > 0) {
1348
- return;
1349
- }
1350
- const index = this.archetypes.indexOf(archetype);
1351
- if (index !== -1) {
1352
- this.archetypes.splice(index, 1);
1353
- }
1354
- const hashKey = this.createArchetypeSignature(archetype.componentTypes);
1355
- this.archetypeBySignature.delete(hashKey);
1356
- for (const componentType of archetype.componentTypes) {
1357
- const archetypes = this.archetypesByComponent.get(componentType);
1358
- if (archetypes) {
1359
- const compIndex = archetypes.indexOf(archetype);
1360
- if (compIndex !== -1) {
1361
- archetypes.splice(compIndex, 1);
1362
- if (archetypes.length === 0) {
1363
- this.archetypesByComponent.delete(componentType);
1364
- }
1365
- }
1366
- }
1367
- }
1368
- for (const query of this.queries) {
1369
- query.removeArchetype(archetype);
1370
- }
1371
- }
1372
- triggerLifecycleHooks(entityId, addedComponents, removedComponents) {
1373
- for (const [componentType, component2] of addedComponents) {
1374
- const directHooks = this.hooks.get(componentType);
1375
- if (directHooks) {
1376
- for (const lifecycleHook of directHooks) {
1377
- lifecycleHook.on_set?.(entityId, componentType, component2);
1378
- }
1379
- }
1380
- const detailedType = getDetailedIdType(componentType);
1381
- if (detailedType.type === "entity-relation" || detailedType.type === "component-relation" || detailedType.type === "wildcard-relation") {
1382
- const wildcardRelationId = relation(detailedType.componentId, "*");
1383
- const wildcardHooks = this.hooks.get(wildcardRelationId);
1384
- if (wildcardHooks) {
1385
- for (const lifecycleHook of wildcardHooks) {
1386
- lifecycleHook.on_set?.(entityId, componentType, component2);
1387
- }
1388
- }
1389
- }
1390
- }
1391
- for (const [componentType, component2] of removedComponents) {
1392
- const directHooks = this.hooks.get(componentType);
1393
- if (directHooks) {
1394
- for (const lifecycleHook of directHooks) {
1395
- lifecycleHook.on_remove?.(entityId, componentType, component2);
1396
- }
1397
- }
1398
- const detailedType = getDetailedIdType(componentType);
1399
- if (detailedType.type === "entity-relation" || detailedType.type === "component-relation" || detailedType.type === "wildcard-relation") {
1400
- const wildcardRelationId = relation(detailedType.componentId, "*");
1401
- const wildcardHooks = this.hooks.get(wildcardRelationId);
1402
- if (wildcardHooks) {
1403
- for (const hook of wildcardHooks) {
1404
- hook.on_remove?.(entityId, componentType, component2);
1405
- }
1406
- }
1407
- }
1408
- }
1409
- }
1410
- serialize() {
1411
- const entities = [];
1412
- for (const archetype of this.archetypes) {
1413
- const dumpedEntities = archetype.dump();
1414
- for (const { entity, components } of dumpedEntities) {
1415
- entities.push({
1416
- id: entity,
1417
- components: Array.from(components.entries()).map(([rawType, value]) => {
1418
- const detailedType = getDetailedIdType(rawType);
1419
- let type = rawType;
1420
- let componentName;
1421
- switch (detailedType.type) {
1422
- case "component":
1423
- type = getComponentNameById(rawType) || rawType;
1424
- break;
1425
- case "entity-relation":
1426
- componentName = getComponentNameById(detailedType.componentId);
1427
- if (componentName) {
1428
- type = { component: componentName, target: detailedType.targetId };
1429
- }
1430
- break;
1431
- case "component-relation":
1432
- componentName = getComponentNameById(detailedType.componentId);
1433
- if (componentName) {
1434
- type = {
1435
- component: componentName,
1436
- target: getComponentNameById(detailedType.targetId) || detailedType.targetId
1437
- };
1438
- }
1439
- break;
1440
- }
1441
- return { type, value: value === MISSING_COMPONENT ? undefined : value };
1442
- })
1443
- });
1444
- }
1445
- }
1446
- return {
1447
- version: 1,
1448
- entityManager: this.entityIdManager.serializeState(),
1449
- entities
1450
- };
1451
- }
1452
- }
1453
- export {
1454
- relation,
1455
- isWildcardRelationId,
1456
- isRelationId,
1457
- isOptionalEntityId,
1458
- isEntityId,
1459
- isComponentId,
1460
- inspectEntityId,
1461
- getIdType,
1462
- getDetailedIdType,
1463
- getComponentNameById,
1464
- getComponentIdByName,
1465
- decodeRelationId,
1466
- createEntityId,
1467
- createComponentId,
1468
- component,
1469
- World,
1470
- WILDCARD_TARGET_ID,
1471
- SystemScheduler,
1472
- RELATION_SHIFT,
1473
- Query,
1474
- MISSING_COMPONENT,
1475
- INVALID_COMPONENT_ID,
1476
- EntityIdManager,
1477
- ENTITY_ID_START,
1478
- ComponentIdAllocator,
1479
- COMPONENT_ID_MAX,
1480
- Archetype
1481
- };