@codehz/ecs 0.3.3 → 0.3.5
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 +120 -26
- package/multi-map.d.ts +19 -0
- package/package.json +1 -1
- package/world.d.ts +10 -1
package/index.js
CHANGED
|
@@ -594,6 +594,86 @@ class CommandBuffer {
|
|
|
594
594
|
}
|
|
595
595
|
}
|
|
596
596
|
|
|
597
|
+
// src/multi-map.ts
|
|
598
|
+
class MultiMap {
|
|
599
|
+
map = new Map;
|
|
600
|
+
_valueCount = 0;
|
|
601
|
+
get valueCount() {
|
|
602
|
+
return this._valueCount;
|
|
603
|
+
}
|
|
604
|
+
get keyCount() {
|
|
605
|
+
return this.map.size;
|
|
606
|
+
}
|
|
607
|
+
hasKey(key) {
|
|
608
|
+
return this.map.has(key);
|
|
609
|
+
}
|
|
610
|
+
has(key, value) {
|
|
611
|
+
const set = this.map.get(key);
|
|
612
|
+
if (!set)
|
|
613
|
+
return false;
|
|
614
|
+
if (arguments.length === 1)
|
|
615
|
+
return true;
|
|
616
|
+
return set.has(value);
|
|
617
|
+
}
|
|
618
|
+
add(key, value) {
|
|
619
|
+
let set = this.map.get(key);
|
|
620
|
+
if (!set) {
|
|
621
|
+
set = new Set;
|
|
622
|
+
this.map.set(key, set);
|
|
623
|
+
}
|
|
624
|
+
if (!set.has(value)) {
|
|
625
|
+
set.add(value);
|
|
626
|
+
this._valueCount++;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
remove(key, value) {
|
|
630
|
+
const set = this.map.get(key);
|
|
631
|
+
if (!set)
|
|
632
|
+
return false;
|
|
633
|
+
if (!set.has(value))
|
|
634
|
+
return false;
|
|
635
|
+
set.delete(value);
|
|
636
|
+
this._valueCount--;
|
|
637
|
+
if (set.size === 0)
|
|
638
|
+
this.map.delete(key);
|
|
639
|
+
return true;
|
|
640
|
+
}
|
|
641
|
+
deleteKey(key) {
|
|
642
|
+
const set = this.map.get(key);
|
|
643
|
+
if (!set)
|
|
644
|
+
return false;
|
|
645
|
+
this._valueCount -= set.size;
|
|
646
|
+
this.map.delete(key);
|
|
647
|
+
return true;
|
|
648
|
+
}
|
|
649
|
+
get(key) {
|
|
650
|
+
const set = this.map.get(key);
|
|
651
|
+
return set ? new Set(set) : new Set;
|
|
652
|
+
}
|
|
653
|
+
*keys() {
|
|
654
|
+
yield* this.map.keys();
|
|
655
|
+
}
|
|
656
|
+
*values() {
|
|
657
|
+
for (const set of this.map.values()) {
|
|
658
|
+
for (const v of set)
|
|
659
|
+
yield v;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
[Symbol.iterator]() {
|
|
663
|
+
return this.entries();
|
|
664
|
+
}
|
|
665
|
+
*entries() {
|
|
666
|
+
for (const [k, set] of this.map.entries()) {
|
|
667
|
+
for (const v of set)
|
|
668
|
+
yield [k, v];
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
clear() {
|
|
672
|
+
this.map.clear();
|
|
673
|
+
this._valueCount = 0;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
|
|
597
677
|
// src/query-filter.ts
|
|
598
678
|
function serializeQueryFilter(filter = {}) {
|
|
599
679
|
const negative = (filter.negativeComponentTypes || []).slice().sort((a, b) => a - b);
|
|
@@ -804,6 +884,7 @@ class World {
|
|
|
804
884
|
commandBuffer = new CommandBuffer((entityId, commands) => this.executeEntityCommands(entityId, commands));
|
|
805
885
|
hooks = new Map;
|
|
806
886
|
exclusiveComponents = new Set;
|
|
887
|
+
cascadeDeleteComponents = new Set;
|
|
807
888
|
constructor(snapshot) {
|
|
808
889
|
if (snapshot && typeof snapshot === "object") {
|
|
809
890
|
if (snapshot.entityManager) {
|
|
@@ -873,14 +954,29 @@ class World {
|
|
|
873
954
|
return entityId;
|
|
874
955
|
}
|
|
875
956
|
destroyEntityImmediate(entityId) {
|
|
876
|
-
const
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
957
|
+
const queue = [entityId];
|
|
958
|
+
const visited = new Set;
|
|
959
|
+
while (queue.length > 0) {
|
|
960
|
+
const cur = queue.shift();
|
|
961
|
+
if (visited.has(cur))
|
|
962
|
+
continue;
|
|
963
|
+
visited.add(cur);
|
|
964
|
+
const archetype = this.entityToArchetype.get(cur);
|
|
965
|
+
if (!archetype) {
|
|
966
|
+
continue;
|
|
967
|
+
}
|
|
968
|
+
const componentReferences = Array.from(this.getEntityReferences(cur));
|
|
969
|
+
for (const [sourceEntityId, componentType] of componentReferences) {
|
|
970
|
+
const sourceArchetype = this.entityToArchetype.get(sourceEntityId);
|
|
971
|
+
if (!sourceArchetype)
|
|
972
|
+
continue;
|
|
973
|
+
const detailedType = getDetailedIdType(componentType);
|
|
974
|
+
if (detailedType.type === "entity-relation" && this.cascadeDeleteComponents.has(detailedType.componentId)) {
|
|
975
|
+
if (!visited.has(sourceEntityId)) {
|
|
976
|
+
queue.push(sourceEntityId);
|
|
977
|
+
}
|
|
978
|
+
continue;
|
|
979
|
+
}
|
|
884
980
|
const currentComponents = new Map;
|
|
885
981
|
let removedComponent = sourceArchetype.get(sourceEntityId, componentType);
|
|
886
982
|
for (const archetypeComponentType of sourceArchetype.componentTypes) {
|
|
@@ -896,17 +992,17 @@ class World {
|
|
|
896
992
|
}
|
|
897
993
|
newArchetype.addEntity(sourceEntityId, currentComponents);
|
|
898
994
|
this.entityToArchetype.set(sourceEntityId, newArchetype);
|
|
899
|
-
this.untrackEntityReference(sourceEntityId, componentType,
|
|
995
|
+
this.untrackEntityReference(sourceEntityId, componentType, cur);
|
|
900
996
|
this.triggerLifecycleHooks(sourceEntityId, new Map, new Map([[componentType, removedComponent]]));
|
|
901
997
|
}
|
|
998
|
+
this.entityReferences.delete(cur);
|
|
999
|
+
archetype.removeEntity(cur);
|
|
1000
|
+
if (archetype.getEntities().length === 0) {
|
|
1001
|
+
this.cleanupEmptyArchetype(archetype);
|
|
1002
|
+
}
|
|
1003
|
+
this.entityToArchetype.delete(cur);
|
|
1004
|
+
this.entityIdManager.deallocate(cur);
|
|
902
1005
|
}
|
|
903
|
-
this.entityReferences.delete(entityId);
|
|
904
|
-
archetype.removeEntity(entityId);
|
|
905
|
-
if (archetype.getEntities().length === 0) {
|
|
906
|
-
this.cleanupEmptyArchetype(archetype);
|
|
907
|
-
}
|
|
908
|
-
this.entityToArchetype.delete(entityId);
|
|
909
|
-
this.entityIdManager.deallocate(entityId);
|
|
910
1006
|
}
|
|
911
1007
|
exists(entityId) {
|
|
912
1008
|
return this.entityToArchetype.has(entityId);
|
|
@@ -986,6 +1082,9 @@ class World {
|
|
|
986
1082
|
setExclusive(componentId) {
|
|
987
1083
|
this.exclusiveComponents.add(componentId);
|
|
988
1084
|
}
|
|
1085
|
+
setCascadeDelete(componentId) {
|
|
1086
|
+
this.cascadeDeleteComponents.add(componentId);
|
|
1087
|
+
}
|
|
989
1088
|
update(...params) {
|
|
990
1089
|
const result = this.systemScheduler.update(...params);
|
|
991
1090
|
if (result instanceof Promise) {
|
|
@@ -1206,26 +1305,21 @@ class World {
|
|
|
1206
1305
|
}
|
|
1207
1306
|
trackEntityReference(sourceEntityId, componentType, targetEntityId) {
|
|
1208
1307
|
if (!this.entityReferences.has(targetEntityId)) {
|
|
1209
|
-
this.entityReferences.set(targetEntityId, new
|
|
1308
|
+
this.entityReferences.set(targetEntityId, new MultiMap);
|
|
1210
1309
|
}
|
|
1211
|
-
this.entityReferences.get(targetEntityId).add(
|
|
1310
|
+
this.entityReferences.get(targetEntityId).add(sourceEntityId, componentType);
|
|
1212
1311
|
}
|
|
1213
1312
|
untrackEntityReference(sourceEntityId, componentType, targetEntityId) {
|
|
1214
1313
|
const references = this.entityReferences.get(targetEntityId);
|
|
1215
1314
|
if (references) {
|
|
1216
|
-
references.
|
|
1217
|
-
|
|
1218
|
-
references.delete(reference);
|
|
1219
|
-
}
|
|
1220
|
-
});
|
|
1221
|
-
if (references.size === 0) {
|
|
1315
|
+
references.remove(sourceEntityId, componentType);
|
|
1316
|
+
if (references.keyCount === 0) {
|
|
1222
1317
|
this.entityReferences.delete(targetEntityId);
|
|
1223
1318
|
}
|
|
1224
1319
|
}
|
|
1225
1320
|
}
|
|
1226
1321
|
getEntityReferences(targetEntityId) {
|
|
1227
|
-
|
|
1228
|
-
return references ? Array.from(references) : [];
|
|
1322
|
+
return this.entityReferences.get(targetEntityId) ?? new MultiMap;
|
|
1229
1323
|
}
|
|
1230
1324
|
cleanupEmptyArchetype(archetype) {
|
|
1231
1325
|
if (archetype.getEntities().length > 0) {
|
package/multi-map.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
declare class MultiMap<K, V> {
|
|
2
|
+
private map;
|
|
3
|
+
private _valueCount;
|
|
4
|
+
get valueCount(): number;
|
|
5
|
+
get keyCount(): number;
|
|
6
|
+
hasKey(key: K): boolean;
|
|
7
|
+
has(key: K, value?: V): boolean;
|
|
8
|
+
add(key: K, value: V): void;
|
|
9
|
+
remove(key: K, value: V): boolean;
|
|
10
|
+
deleteKey(key: K): boolean;
|
|
11
|
+
get(key: K): Set<V>;
|
|
12
|
+
keys(): IterableIterator<K>;
|
|
13
|
+
values(): IterableIterator<V>;
|
|
14
|
+
[Symbol.iterator](): IterableIterator<[K, V]>;
|
|
15
|
+
entries(): IterableIterator<[K, V]>;
|
|
16
|
+
clear(): void;
|
|
17
|
+
}
|
|
18
|
+
export { MultiMap };
|
|
19
|
+
export type { MultiMap as MultiMapType };
|
package/package.json
CHANGED
package/world.d.ts
CHANGED
|
@@ -35,6 +35,8 @@ export declare class World<UpdateParams extends any[] = []> {
|
|
|
35
35
|
private hooks;
|
|
36
36
|
/** Set of component IDs marked as exclusive relations */
|
|
37
37
|
private exclusiveComponents;
|
|
38
|
+
/** Set of component IDs that will cascade delete when the relation target is deleted */
|
|
39
|
+
private cascadeDeleteComponents;
|
|
38
40
|
/**
|
|
39
41
|
* Create a new World.
|
|
40
42
|
* If an optional snapshot object is provided (previously produced by `world.serialize()`),
|
|
@@ -108,6 +110,13 @@ export declare class World<UpdateParams extends any[] = []> {
|
|
|
108
110
|
* For exclusive relations, an entity can have at most one relation per base component
|
|
109
111
|
*/
|
|
110
112
|
setExclusive(componentId: EntityId): void;
|
|
113
|
+
/**
|
|
114
|
+
* Mark a component as cascade-delete relation
|
|
115
|
+
* For cascade relations, when the relation target entity is deleted,
|
|
116
|
+
* the referencing entity will also be deleted (cascade).
|
|
117
|
+
* Only applicable to entity-relation components
|
|
118
|
+
*/
|
|
119
|
+
setCascadeDelete(componentId: EntityId): void;
|
|
111
120
|
/**
|
|
112
121
|
* Update the world (run all systems in dependency order)
|
|
113
122
|
* This function is synchronous when all systems are synchronous,
|
|
@@ -176,7 +185,7 @@ export declare class World<UpdateParams extends any[] = []> {
|
|
|
176
185
|
/**
|
|
177
186
|
* Get all component references where a target entity is used as a component type
|
|
178
187
|
* @param targetEntityId The target entity
|
|
179
|
-
* @returns
|
|
188
|
+
* @returns A MultiMap of sourceEntityId to componentTypes that reference the target entity
|
|
180
189
|
*/
|
|
181
190
|
private getEntityReferences;
|
|
182
191
|
/**
|