@codehz/ecs 0.8.0 → 0.8.2
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/builder.d.mts +28 -9
- package/dist/world.mjs +247 -89
- package/dist/world.mjs.map +1 -1
- package/package.json +2 -1
- package/skills/ecs/SKILL.md +338 -0
- package/src/__tests__/core/archetype.test.ts +4 -2
- package/src/__tests__/perf/dontfragment-wildcard.perf.test.ts +107 -0
- package/src/__tests__/query/filter.test.ts +3 -2
- package/src/archetype/archetype.ts +64 -60
- package/src/archetype/helpers.ts +13 -6
- package/src/archetype/store.ts +222 -15
- package/src/world/commands.ts +22 -34
- package/src/world/references.ts +59 -0
- package/src/world/world.ts +9 -2
package/dist/builder.d.mts
CHANGED
|
@@ -561,15 +561,28 @@ interface LifecycleHookEntry {
|
|
|
561
561
|
//#endregion
|
|
562
562
|
//#region src/archetype/store.d.ts
|
|
563
563
|
/**
|
|
564
|
-
*
|
|
564
|
+
* Interface for storing dontFragment relation data.
|
|
565
565
|
*
|
|
566
|
-
*
|
|
567
|
-
*
|
|
566
|
+
* Storage is now primarily keyed by relation ComponentId (the "kind" of relation)
|
|
567
|
+
* rather than by entity. This provides O(1) or near-O(1) answers for the hot
|
|
568
|
+
* wildcard-related paths (hasRelationWithComponentId, wildcard materialization
|
|
569
|
+
* during iteration, hook matching, etc.).
|
|
570
|
+
*
|
|
571
|
+
* A lightweight reverse index (entity -> Set of base ComponentIds) is maintained
|
|
572
|
+
* to efficiently support the infrequent "get all dontFragment data for this entity"
|
|
573
|
+
* operations (removeEntity, dump, getEntity, serialization).
|
|
574
|
+
*
|
|
575
|
+
* The interface no longer leaks internal Map structures. Callers work with
|
|
576
|
+
* semantic operations only.
|
|
568
577
|
*/
|
|
569
578
|
interface DontFragmentStore {
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
579
|
+
getValue(entityId: EntityId, relationType: EntityId<any>): any | undefined;
|
|
580
|
+
setValue(entityId: EntityId, relationType: EntityId<any>, data: any): void;
|
|
581
|
+
deleteValue(entityId: EntityId, relationType: EntityId<any>): boolean;
|
|
582
|
+
hasAnyForComponent(componentId: EntityId<any>): boolean;
|
|
583
|
+
getRelationsForComponent(entityId: EntityId, componentId: EntityId<any>): [target: EntityId, data: any][];
|
|
584
|
+
getAllForEntity(entityId: EntityId): Array<[relationType: EntityId<any>, data: any]>;
|
|
585
|
+
deleteEntity(entityId: EntityId): void;
|
|
573
586
|
}
|
|
574
587
|
//#endregion
|
|
575
588
|
//#region src/archetype/archetype.d.ts
|
|
@@ -601,9 +614,9 @@ declare class Archetype {
|
|
|
601
614
|
*/
|
|
602
615
|
private entityToIndex;
|
|
603
616
|
/**
|
|
604
|
-
* DontFragmentStore
|
|
605
|
-
*
|
|
606
|
-
*
|
|
617
|
+
* DontFragmentStore (keyed primarily by relation ComponentId).
|
|
618
|
+
* Uses optimized RelationEntry (single/multi) for the common exclusive case.
|
|
619
|
+
* See store.ts for implementation details.
|
|
607
620
|
*/
|
|
608
621
|
private dontFragmentRelations;
|
|
609
622
|
/**
|
|
@@ -626,6 +639,12 @@ declare class Archetype {
|
|
|
626
639
|
addEntity(entityId: EntityId, componentData: Map<EntityId<any>, any>): void;
|
|
627
640
|
private addDontFragmentRelations;
|
|
628
641
|
getEntity(entityId: EntityId): Map<EntityId<any>, any> | undefined;
|
|
642
|
+
/**
|
|
643
|
+
* Returns all dontFragment relations for the given entity as an array of tuples.
|
|
644
|
+
* This is a compatibility adapter during the store refactor.
|
|
645
|
+
*
|
|
646
|
+
* Prefer the new DontFragmentStore methods when possible.
|
|
647
|
+
*/
|
|
629
648
|
getEntityDontFragmentRelations(entityId: EntityId): Map<EntityId<any>, any> | undefined;
|
|
630
649
|
dump(): Array<{
|
|
631
650
|
entity: EntityId;
|
package/dist/world.mjs
CHANGED
|
@@ -695,17 +695,6 @@ function matchesRelationComponentId(componentType, componentId) {
|
|
|
695
695
|
return isRelationType(detailedType) && detailedType.componentId === componentId;
|
|
696
696
|
}
|
|
697
697
|
/**
|
|
698
|
-
* Find all relations in dontFragment data that match a component ID
|
|
699
|
-
*/
|
|
700
|
-
function findMatchingDontFragmentRelations(dontFragmentData, componentId, relations = []) {
|
|
701
|
-
if (!dontFragmentData) return relations;
|
|
702
|
-
for (const [relType, data] of dontFragmentData) {
|
|
703
|
-
const relDetailed = getDetailedIdType(relType);
|
|
704
|
-
if (isRelationType(relDetailed) && relDetailed.componentId === componentId) relations.push([relDetailed.targetId, data]);
|
|
705
|
-
}
|
|
706
|
-
return relations;
|
|
707
|
-
}
|
|
708
|
-
/**
|
|
709
698
|
* Build cache key for component types
|
|
710
699
|
*/
|
|
711
700
|
function buildCacheKey(componentTypes) {
|
|
@@ -719,9 +708,10 @@ function getWildcardRelationDataSource(componentTypes, componentId, optional) {
|
|
|
719
708
|
return optional ? matchingRelations.length > 0 ? matchingRelations : void 0 : matchingRelations;
|
|
720
709
|
}
|
|
721
710
|
/**
|
|
722
|
-
* Build wildcard relation value from matching relations
|
|
711
|
+
* Build wildcard relation value from matching relations.
|
|
712
|
+
* Now receives the DontFragmentStore directly for efficient per-component lookups.
|
|
723
713
|
*/
|
|
724
|
-
function buildWildcardRelationValue(wildcardRelationType, matchingRelations, getDataAtIndex,
|
|
714
|
+
function buildWildcardRelationValue(wildcardRelationType, matchingRelations, getDataAtIndex, dontFragmentStore, entityId, optional) {
|
|
725
715
|
const relations = [];
|
|
726
716
|
const targetComponentId = getComponentIdFromRelationId(wildcardRelationType);
|
|
727
717
|
for (const relType of matchingRelations || []) {
|
|
@@ -729,7 +719,10 @@ function buildWildcardRelationValue(wildcardRelationType, matchingRelations, get
|
|
|
729
719
|
const targetId = getTargetIdFromRelationId(relType);
|
|
730
720
|
relations.push([targetId, data === MISSING_COMPONENT ? void 0 : data]);
|
|
731
721
|
}
|
|
732
|
-
if (targetComponentId !== void 0)
|
|
722
|
+
if (targetComponentId !== void 0) {
|
|
723
|
+
const dfMatches = dontFragmentStore.getRelationsForComponent(entityId, targetComponentId);
|
|
724
|
+
for (const m of dfMatches) relations.push(m);
|
|
725
|
+
}
|
|
733
726
|
if (relations.length === 0) {
|
|
734
727
|
if (!optional) {
|
|
735
728
|
const componentId = getComponentIdFromRelationId(wildcardRelationType);
|
|
@@ -757,7 +750,7 @@ function buildRegularComponentValue(dataSource, entityIndex, optional) {
|
|
|
757
750
|
function buildSingleComponent(compType, dataSource, entityIndex, entityId, getComponentData, dontFragmentRelations) {
|
|
758
751
|
const optional = isOptionalEntityId(compType);
|
|
759
752
|
const actualType = optional ? compType.optional : compType;
|
|
760
|
-
if (getIdType(actualType) === "wildcard-relation") return buildWildcardRelationValue(actualType, dataSource, (relType) => getComponentData(relType)[entityIndex], dontFragmentRelations
|
|
753
|
+
if (getIdType(actualType) === "wildcard-relation") return buildWildcardRelationValue(actualType, dataSource, (relType) => getComponentData(relType)[entityIndex], dontFragmentRelations, entityId, optional);
|
|
761
754
|
else return buildRegularComponentValue(dataSource, entityIndex, optional);
|
|
762
755
|
}
|
|
763
756
|
//#endregion
|
|
@@ -794,9 +787,9 @@ var Archetype = class {
|
|
|
794
787
|
*/
|
|
795
788
|
entityToIndex = /* @__PURE__ */ new Map();
|
|
796
789
|
/**
|
|
797
|
-
* DontFragmentStore
|
|
798
|
-
*
|
|
799
|
-
*
|
|
790
|
+
* DontFragmentStore (keyed primarily by relation ComponentId).
|
|
791
|
+
* Uses optimized RelationEntry (single/multi) for the common exclusive case.
|
|
792
|
+
* See store.ts for implementation details.
|
|
800
793
|
*/
|
|
801
794
|
dontFragmentRelations;
|
|
802
795
|
/**
|
|
@@ -839,13 +832,11 @@ var Archetype = class {
|
|
|
839
832
|
this.addDontFragmentRelations(entityId, componentData);
|
|
840
833
|
}
|
|
841
834
|
addDontFragmentRelations(entityId, componentData) {
|
|
842
|
-
const dontFragmentData = /* @__PURE__ */ new Map();
|
|
843
835
|
for (const [componentType, data] of componentData) {
|
|
844
836
|
if (this.componentTypeSet.has(componentType)) continue;
|
|
845
837
|
const detailedType = getDetailedIdType(componentType);
|
|
846
|
-
if (isRelationType(detailedType) && isDontFragmentComponent(detailedType.componentId))
|
|
838
|
+
if (isRelationType(detailedType) && isDontFragmentComponent(detailedType.componentId)) this.dontFragmentRelations.setValue(entityId, componentType, data);
|
|
847
839
|
}
|
|
848
|
-
if (dontFragmentData.size > 0) this.dontFragmentRelations.set(entityId, dontFragmentData);
|
|
849
840
|
}
|
|
850
841
|
getEntity(entityId) {
|
|
851
842
|
const index = this.entityToIndex.get(entityId);
|
|
@@ -855,12 +846,22 @@ var Archetype = class {
|
|
|
855
846
|
const data = this.getComponentData(componentType)[index];
|
|
856
847
|
entityData.set(componentType, data === MISSING_COMPONENT ? void 0 : data);
|
|
857
848
|
}
|
|
858
|
-
const
|
|
859
|
-
|
|
849
|
+
const dontFragmentTuples = this.dontFragmentRelations.getAllForEntity(entityId);
|
|
850
|
+
for (const [componentType, data] of dontFragmentTuples) entityData.set(componentType, data);
|
|
860
851
|
return entityData;
|
|
861
852
|
}
|
|
853
|
+
/**
|
|
854
|
+
* Returns all dontFragment relations for the given entity as an array of tuples.
|
|
855
|
+
* This is a compatibility adapter during the store refactor.
|
|
856
|
+
*
|
|
857
|
+
* Prefer the new DontFragmentStore methods when possible.
|
|
858
|
+
*/
|
|
862
859
|
getEntityDontFragmentRelations(entityId) {
|
|
863
|
-
|
|
860
|
+
const tuples = this.dontFragmentRelations.getAllForEntity(entityId);
|
|
861
|
+
if (tuples.length === 0) return void 0;
|
|
862
|
+
const map = /* @__PURE__ */ new Map();
|
|
863
|
+
for (const [relType, data] of tuples) map.set(relType, data);
|
|
864
|
+
return map;
|
|
864
865
|
}
|
|
865
866
|
dump() {
|
|
866
867
|
return this.entities.map((entity, i) => {
|
|
@@ -869,8 +870,8 @@ var Archetype = class {
|
|
|
869
870
|
const data = this.getComponentData(componentType)[i];
|
|
870
871
|
components.set(componentType, data === MISSING_COMPONENT ? void 0 : data);
|
|
871
872
|
}
|
|
872
|
-
const
|
|
873
|
-
|
|
873
|
+
const dontFragmentTuples = this.dontFragmentRelations.getAllForEntity(entity);
|
|
874
|
+
for (const [componentType, data] of dontFragmentTuples) components.set(componentType, data);
|
|
874
875
|
return {
|
|
875
876
|
entity,
|
|
876
877
|
components
|
|
@@ -882,11 +883,9 @@ var Archetype = class {
|
|
|
882
883
|
if (index === void 0) return void 0;
|
|
883
884
|
const removedData = /* @__PURE__ */ new Map();
|
|
884
885
|
for (const componentType of this.componentTypes) removedData.set(componentType, this.getComponentData(componentType)[index]);
|
|
885
|
-
const
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
this.dontFragmentRelations.delete(entityId);
|
|
889
|
-
}
|
|
886
|
+
const dontFragmentTuples = this.dontFragmentRelations.getAllForEntity(entityId);
|
|
887
|
+
for (const [componentType, data] of dontFragmentTuples) removedData.set(componentType, data);
|
|
888
|
+
this.dontFragmentRelations.deleteEntity(entityId);
|
|
890
889
|
this.entityToIndex.delete(entityId);
|
|
891
890
|
const lastIndex = this.entities.length - 1;
|
|
892
891
|
if (index !== lastIndex) {
|
|
@@ -924,7 +923,10 @@ var Archetype = class {
|
|
|
924
923
|
}
|
|
925
924
|
}
|
|
926
925
|
}
|
|
927
|
-
if (componentId !== void 0)
|
|
926
|
+
if (componentId !== void 0) {
|
|
927
|
+
const matches = this.dontFragmentRelations.getRelationsForComponent(entityId, componentId);
|
|
928
|
+
for (const m of matches) relations.push(m);
|
|
929
|
+
}
|
|
928
930
|
return relations;
|
|
929
931
|
}
|
|
930
932
|
getRegularComponent(entityId, index, componentType) {
|
|
@@ -933,8 +935,7 @@ var Archetype = class {
|
|
|
933
935
|
if (data === MISSING_COMPONENT) throw new Error(`Component type ${componentType} not found for entity ${entityId}`);
|
|
934
936
|
return data;
|
|
935
937
|
}
|
|
936
|
-
|
|
937
|
-
if (dontFragmentData?.has(componentType)) return dontFragmentData.get(componentType);
|
|
938
|
+
if (this.dontFragmentRelations.getValue(entityId, componentType) !== void 0 || this.dontFragmentRelations.getAllForEntity(entityId).some(([t]) => t === componentType)) return this.dontFragmentRelations.getValue(entityId, componentType);
|
|
938
939
|
throw new Error(`Component type ${componentType} not found for entity ${entityId}`);
|
|
939
940
|
}
|
|
940
941
|
getOptional(entityId, componentType) {
|
|
@@ -945,8 +946,9 @@ var Archetype = class {
|
|
|
945
946
|
if (data === MISSING_COMPONENT) return void 0;
|
|
946
947
|
return { value: data };
|
|
947
948
|
}
|
|
948
|
-
const
|
|
949
|
-
if (
|
|
949
|
+
const value = this.dontFragmentRelations.getValue(entityId, componentType);
|
|
950
|
+
if (value !== void 0) return { value };
|
|
951
|
+
if (this.dontFragmentRelations.getAllForEntity(entityId).some(([t]) => t === componentType)) return { value: this.dontFragmentRelations.getValue(entityId, componentType) };
|
|
950
952
|
}
|
|
951
953
|
set(entityId, componentType, data) {
|
|
952
954
|
const index = this.entityToIndex.get(entityId);
|
|
@@ -957,12 +959,7 @@ var Archetype = class {
|
|
|
957
959
|
}
|
|
958
960
|
const detailedType = getDetailedIdType(componentType);
|
|
959
961
|
if (isRelationType(detailedType) && isDontFragmentComponent(detailedType.componentId)) {
|
|
960
|
-
|
|
961
|
-
if (!dontFragmentData) {
|
|
962
|
-
dontFragmentData = /* @__PURE__ */ new Map();
|
|
963
|
-
this.dontFragmentRelations.set(entityId, dontFragmentData);
|
|
964
|
-
}
|
|
965
|
-
dontFragmentData.set(componentType, data);
|
|
962
|
+
this.dontFragmentRelations.setValue(entityId, componentType, data);
|
|
966
963
|
return;
|
|
967
964
|
}
|
|
968
965
|
throw new Error(`Component type ${componentType} is not in this archetype`);
|
|
@@ -1026,12 +1023,15 @@ var Archetype = class {
|
|
|
1026
1023
|
}
|
|
1027
1024
|
forEach(callback) {
|
|
1028
1025
|
for (let i = 0; i < this.entities.length; i++) {
|
|
1026
|
+
const entity = this.entities[i];
|
|
1029
1027
|
const components = /* @__PURE__ */ new Map();
|
|
1030
1028
|
for (const componentType of this.componentTypes) {
|
|
1031
1029
|
const data = this.getComponentData(componentType)[i];
|
|
1032
1030
|
components.set(componentType, data === MISSING_COMPONENT ? void 0 : data);
|
|
1033
1031
|
}
|
|
1034
|
-
|
|
1032
|
+
const dontFragmentTuples = this.dontFragmentRelations.getAllForEntity(entity);
|
|
1033
|
+
for (const [componentType, data] of dontFragmentTuples) components.set(componentType, data);
|
|
1034
|
+
callback(entity, components);
|
|
1035
1035
|
}
|
|
1036
1036
|
}
|
|
1037
1037
|
hasRelationWithComponentId(componentId) {
|
|
@@ -1039,32 +1039,165 @@ var Archetype = class {
|
|
|
1039
1039
|
const detailedType = getDetailedIdType(componentType);
|
|
1040
1040
|
if (isRelationType(detailedType) && detailedType.componentId === componentId) return true;
|
|
1041
1041
|
}
|
|
1042
|
-
for (const entityId of this.entities)
|
|
1043
|
-
const entityDontFragmentRelations = this.dontFragmentRelations.get(entityId);
|
|
1044
|
-
if (entityDontFragmentRelations) for (const relationType of entityDontFragmentRelations.keys()) {
|
|
1045
|
-
const detailedType = getDetailedIdType(relationType);
|
|
1046
|
-
if (isRelationType(detailedType) && detailedType.componentId === componentId) return true;
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1042
|
+
for (const entityId of this.entities) if (this.dontFragmentRelations.getRelationsForComponent(entityId, componentId).length > 0) return true;
|
|
1049
1043
|
return false;
|
|
1050
1044
|
}
|
|
1051
1045
|
};
|
|
1052
1046
|
//#endregion
|
|
1053
1047
|
//#region src/archetype/store.ts
|
|
1054
1048
|
/**
|
|
1055
|
-
*
|
|
1056
|
-
*
|
|
1049
|
+
* Production implementation of DontFragmentStore.
|
|
1050
|
+
*
|
|
1051
|
+
* Internal layout (optimized):
|
|
1052
|
+
* - byComponent: baseComponentId → (entityId → RelationEntry)
|
|
1053
|
+
* RelationEntry uses a single-value form for the common exclusive case (1 target),
|
|
1054
|
+
* avoiding Map allocation entirely for the vast majority of dontFragment usage.
|
|
1055
|
+
* - entityIndex: entityId → Set<baseComponentId>
|
|
1056
|
+
* Lightweight reverse index.
|
|
1057
1057
|
*/
|
|
1058
1058
|
var DontFragmentStoreImpl = class {
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1059
|
+
/**
|
|
1060
|
+
* Primary storage, keyed by the base relation component ID.
|
|
1061
|
+
*/
|
|
1062
|
+
byComponent = /* @__PURE__ */ new Map();
|
|
1063
|
+
/**
|
|
1064
|
+
* Reverse index: which base component kinds an entity participates in.
|
|
1065
|
+
* Used only by the infrequent getAllForEntity / deleteEntity paths.
|
|
1066
|
+
*/
|
|
1067
|
+
entityIndex = /* @__PURE__ */ new Map();
|
|
1068
|
+
getValue(entityId, relationType) {
|
|
1069
|
+
const componentId = getComponentIdFromRelationId(relationType);
|
|
1070
|
+
if (componentId === void 0) return void 0;
|
|
1071
|
+
const entities = this.byComponent.get(componentId);
|
|
1072
|
+
if (!entities) return void 0;
|
|
1073
|
+
const entry = entities.get(entityId);
|
|
1074
|
+
if (!entry) return void 0;
|
|
1075
|
+
const targetId = getTargetIdFromRelationId(relationType);
|
|
1076
|
+
if (entry.type === "single") return entry.target === targetId ? entry.data : void 0;
|
|
1077
|
+
else {
|
|
1078
|
+
const item = entry.targets.get(targetId);
|
|
1079
|
+
return item ? item.data : void 0;
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
setValue(entityId, relationType, data) {
|
|
1083
|
+
const componentId = getComponentIdFromRelationId(relationType);
|
|
1084
|
+
if (componentId === void 0) throw new Error("setValue called with a non-relation type on DontFragmentStore");
|
|
1085
|
+
let entities = this.byComponent.get(componentId);
|
|
1086
|
+
if (!entities) {
|
|
1087
|
+
entities = /* @__PURE__ */ new Map();
|
|
1088
|
+
this.byComponent.set(componentId, entities);
|
|
1089
|
+
}
|
|
1090
|
+
const targetId = getTargetIdFromRelationId(relationType);
|
|
1091
|
+
let entry = entities.get(entityId);
|
|
1092
|
+
if (!entry) {
|
|
1093
|
+
entry = {
|
|
1094
|
+
type: "single",
|
|
1095
|
+
relationType,
|
|
1096
|
+
target: targetId,
|
|
1097
|
+
data
|
|
1098
|
+
};
|
|
1099
|
+
entities.set(entityId, entry);
|
|
1100
|
+
} else if (entry.type === "single") if (entry.target === targetId) {
|
|
1101
|
+
entry.data = data;
|
|
1102
|
+
entry.relationType = relationType;
|
|
1103
|
+
} else {
|
|
1104
|
+
const targets = /* @__PURE__ */ new Map();
|
|
1105
|
+
targets.set(entry.target, {
|
|
1106
|
+
relationType: entry.relationType,
|
|
1107
|
+
data: entry.data
|
|
1108
|
+
});
|
|
1109
|
+
targets.set(targetId, {
|
|
1110
|
+
relationType,
|
|
1111
|
+
data
|
|
1112
|
+
});
|
|
1113
|
+
entities.set(entityId, {
|
|
1114
|
+
type: "multi",
|
|
1115
|
+
targets
|
|
1116
|
+
});
|
|
1117
|
+
}
|
|
1118
|
+
else entry.targets.set(targetId, {
|
|
1119
|
+
relationType,
|
|
1120
|
+
data
|
|
1121
|
+
});
|
|
1122
|
+
let components = this.entityIndex.get(entityId);
|
|
1123
|
+
if (!components) {
|
|
1124
|
+
components = /* @__PURE__ */ new Set();
|
|
1125
|
+
this.entityIndex.set(entityId, components);
|
|
1126
|
+
}
|
|
1127
|
+
components.add(componentId);
|
|
1128
|
+
}
|
|
1129
|
+
deleteValue(entityId, relationType) {
|
|
1130
|
+
const componentId = getComponentIdFromRelationId(relationType);
|
|
1131
|
+
if (componentId === void 0) return false;
|
|
1132
|
+
const entities = this.byComponent.get(componentId);
|
|
1133
|
+
if (!entities) return false;
|
|
1134
|
+
const entry = entities.get(entityId);
|
|
1135
|
+
if (!entry) return false;
|
|
1136
|
+
const targetId = getTargetIdFromRelationId(relationType);
|
|
1137
|
+
let existed = false;
|
|
1138
|
+
if (entry.type === "single") {
|
|
1139
|
+
if (entry.target === targetId) {
|
|
1140
|
+
existed = true;
|
|
1141
|
+
entities.delete(entityId);
|
|
1142
|
+
}
|
|
1143
|
+
} else {
|
|
1144
|
+
existed = entry.targets.delete(targetId);
|
|
1145
|
+
if (entry.targets.size === 0) entities.delete(entityId);
|
|
1146
|
+
else if (entry.targets.size === 1) {
|
|
1147
|
+
const [first] = entry.targets.entries();
|
|
1148
|
+
const [t, item] = first;
|
|
1149
|
+
entities.set(entityId, {
|
|
1150
|
+
type: "single",
|
|
1151
|
+
relationType: item.relationType,
|
|
1152
|
+
target: t,
|
|
1153
|
+
data: item.data
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
if (!entities.has(entityId) && entities.size === 0) this.byComponent.delete(componentId);
|
|
1158
|
+
const components = this.entityIndex.get(entityId);
|
|
1159
|
+
if (components && !entities.has(entityId)) {
|
|
1160
|
+
components.delete(componentId);
|
|
1161
|
+
if (components.size === 0) this.entityIndex.delete(entityId);
|
|
1162
|
+
}
|
|
1163
|
+
return existed;
|
|
1062
1164
|
}
|
|
1063
|
-
|
|
1064
|
-
this.
|
|
1165
|
+
hasAnyForComponent(componentId) {
|
|
1166
|
+
const entities = this.byComponent.get(componentId);
|
|
1167
|
+
return entities !== void 0 && entities.size > 0;
|
|
1065
1168
|
}
|
|
1066
|
-
|
|
1067
|
-
|
|
1169
|
+
getRelationsForComponent(entityId, componentId) {
|
|
1170
|
+
const result = [];
|
|
1171
|
+
const entities = this.byComponent.get(componentId);
|
|
1172
|
+
if (!entities) return result;
|
|
1173
|
+
const entry = entities.get(entityId);
|
|
1174
|
+
if (!entry) return result;
|
|
1175
|
+
if (entry.type === "single") result.push([entry.target, entry.data]);
|
|
1176
|
+
else for (const [target, item] of entry.targets) result.push([target, item.data]);
|
|
1177
|
+
return result;
|
|
1178
|
+
}
|
|
1179
|
+
getAllForEntity(entityId) {
|
|
1180
|
+
const components = this.entityIndex.get(entityId);
|
|
1181
|
+
if (!components || components.size === 0) return [];
|
|
1182
|
+
const result = [];
|
|
1183
|
+
for (const componentId of components) {
|
|
1184
|
+
const entry = this.byComponent.get(componentId)?.get(entityId);
|
|
1185
|
+
if (entry) if (entry.type === "single") result.push([entry.relationType, entry.data]);
|
|
1186
|
+
else for (const item of entry.targets.values()) result.push([item.relationType, item.data]);
|
|
1187
|
+
}
|
|
1188
|
+
return result;
|
|
1189
|
+
}
|
|
1190
|
+
deleteEntity(entityId) {
|
|
1191
|
+
const components = this.entityIndex.get(entityId);
|
|
1192
|
+
if (!components) return;
|
|
1193
|
+
for (const componentId of components) {
|
|
1194
|
+
const entities = this.byComponent.get(componentId);
|
|
1195
|
+
if (entities) {
|
|
1196
|
+
entities.delete(entityId);
|
|
1197
|
+
if (entities.size === 0) this.byComponent.delete(componentId);
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
this.entityIndex.delete(entityId);
|
|
1068
1201
|
}
|
|
1069
1202
|
};
|
|
1070
1203
|
//#endregion
|
|
@@ -1789,6 +1922,10 @@ function maybeRemoveWildcardMarker(entityId, archetype, removedComponentType, co
|
|
|
1789
1922
|
if (changeset.removes.has(otherComponentType)) continue;
|
|
1790
1923
|
if (getComponentIdFromRelationId(otherComponentType) === componentId) return;
|
|
1791
1924
|
}
|
|
1925
|
+
for (const addedType of changeset.adds.keys()) {
|
|
1926
|
+
if (addedType === removedComponentType) continue;
|
|
1927
|
+
if (getComponentIdFromRelationId(addedType) === componentId) return;
|
|
1928
|
+
}
|
|
1792
1929
|
changeset.delete(wildcardMarker);
|
|
1793
1930
|
}
|
|
1794
1931
|
function hasEntityComponent(archetype, entityId, componentType) {
|
|
@@ -1835,40 +1972,20 @@ function applyChangeset(ctx, entityId, currentArchetype, changeset, entityToArch
|
|
|
1835
1972
|
}
|
|
1836
1973
|
/**
|
|
1837
1974
|
* No-hooks variant of applyDontFragmentChanges that skips tracking removed component data.
|
|
1975
|
+
*
|
|
1976
|
+
* Rewritten for the new DontFragmentStore interface (ComponentId-primary storage).
|
|
1838
1977
|
*/
|
|
1839
1978
|
function applyDontFragmentChanges(dontFragmentRelations, entityId, changeset, removedComponents) {
|
|
1840
|
-
let entityRelations = dontFragmentRelations.get(entityId);
|
|
1841
1979
|
for (const componentType of changeset.removes) if (isDontFragmentRelation(componentType)) {
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
removedComponents.set(componentType, removedValue);
|
|
1846
|
-
entityRelations.delete(componentType);
|
|
1847
|
-
}
|
|
1848
|
-
}
|
|
1849
|
-
}
|
|
1850
|
-
for (const [componentType, component] of changeset.adds) if (isDontFragmentRelation(componentType)) {
|
|
1851
|
-
if (!entityRelations) {
|
|
1852
|
-
entityRelations = /* @__PURE__ */ new Map();
|
|
1853
|
-
dontFragmentRelations.set(entityId, entityRelations);
|
|
1854
|
-
}
|
|
1855
|
-
entityRelations.set(componentType, component);
|
|
1980
|
+
const removedValue = dontFragmentRelations.getValue(entityId, componentType);
|
|
1981
|
+
if (removedValue !== void 0 || dontFragmentRelations.getAllForEntity(entityId).some(([t]) => t === componentType)) removedComponents.set(componentType, removedValue);
|
|
1982
|
+
dontFragmentRelations.deleteValue(entityId, componentType);
|
|
1856
1983
|
}
|
|
1857
|
-
|
|
1984
|
+
for (const [componentType, component] of changeset.adds) if (isDontFragmentRelation(componentType)) dontFragmentRelations.setValue(entityId, componentType, component);
|
|
1858
1985
|
}
|
|
1859
1986
|
function applyDontFragmentChangesNoHooks(dontFragmentRelations, entityId, changeset) {
|
|
1860
|
-
|
|
1861
|
-
for (const componentType of changeset.
|
|
1862
|
-
if (entityRelations) entityRelations.delete(componentType);
|
|
1863
|
-
}
|
|
1864
|
-
for (const [componentType, component] of changeset.adds) if (isDontFragmentRelation(componentType)) {
|
|
1865
|
-
if (!entityRelations) {
|
|
1866
|
-
entityRelations = /* @__PURE__ */ new Map();
|
|
1867
|
-
dontFragmentRelations.set(entityId, entityRelations);
|
|
1868
|
-
}
|
|
1869
|
-
entityRelations.set(componentType, component);
|
|
1870
|
-
}
|
|
1871
|
-
if (entityRelations && entityRelations.size === 0) dontFragmentRelations.delete(entityId);
|
|
1987
|
+
for (const componentType of changeset.removes) if (isDontFragmentRelation(componentType)) dontFragmentRelations.deleteValue(entityId, componentType);
|
|
1988
|
+
for (const [componentType, component] of changeset.adds) if (isDontFragmentRelation(componentType)) dontFragmentRelations.setValue(entityId, componentType, component);
|
|
1872
1989
|
}
|
|
1873
1990
|
function filterRegularComponentTypes(componentTypes) {
|
|
1874
1991
|
const regularTypes = [];
|
|
@@ -2131,10 +2248,35 @@ var MultiMap = class {
|
|
|
2131
2248
|
};
|
|
2132
2249
|
//#endregion
|
|
2133
2250
|
//#region src/world/references.ts
|
|
2251
|
+
/**
|
|
2252
|
+
* Record that `sourceEntityId` holds a reference to `targetEntityId` via the given component/relation.
|
|
2253
|
+
*
|
|
2254
|
+
* Called when an entity-valued component or an entity-relation is added to an entity.
|
|
2255
|
+
*
|
|
2256
|
+
* @param entityReferences - The shared reverse index map
|
|
2257
|
+
* @param sourceEntityId - The entity that contains the reference
|
|
2258
|
+
* @param componentType - The component type or encoded relation ID used for the reference
|
|
2259
|
+
* @param targetEntityId - The entity being referenced
|
|
2260
|
+
*
|
|
2261
|
+
* @internal
|
|
2262
|
+
*/
|
|
2134
2263
|
function trackEntityReference(entityReferences, sourceEntityId, componentType, targetEntityId) {
|
|
2135
2264
|
if (!entityReferences.has(targetEntityId)) entityReferences.set(targetEntityId, new MultiMap());
|
|
2136
2265
|
entityReferences.get(targetEntityId).add(sourceEntityId, componentType);
|
|
2137
2266
|
}
|
|
2267
|
+
/**
|
|
2268
|
+
* Remove the record that `sourceEntityId` references `targetEntityId` via the given component/relation.
|
|
2269
|
+
*
|
|
2270
|
+
* Called when an entity-valued component or entity-relation is removed (or during deletion).
|
|
2271
|
+
* Automatically prunes empty target entries from the map.
|
|
2272
|
+
*
|
|
2273
|
+
* @param entityReferences - The shared reverse index map
|
|
2274
|
+
* @param sourceEntityId - The entity that no longer holds the reference
|
|
2275
|
+
* @param componentType - The component type or encoded relation ID that was used
|
|
2276
|
+
* @param targetEntityId - The previously referenced entity
|
|
2277
|
+
*
|
|
2278
|
+
* @internal
|
|
2279
|
+
*/
|
|
2138
2280
|
function untrackEntityReference(entityReferences, sourceEntityId, componentType, targetEntityId) {
|
|
2139
2281
|
const references = entityReferences.get(targetEntityId);
|
|
2140
2282
|
if (references) {
|
|
@@ -2142,6 +2284,19 @@ function untrackEntityReference(entityReferences, sourceEntityId, componentType,
|
|
|
2142
2284
|
if (references.keyCount === 0) entityReferences.delete(targetEntityId);
|
|
2143
2285
|
}
|
|
2144
2286
|
}
|
|
2287
|
+
/**
|
|
2288
|
+
* Iterate over all (sourceEntityId, componentOrRelationId) pairs that currently reference the given target.
|
|
2289
|
+
*
|
|
2290
|
+
* Returns an empty iterable when the target has no incoming references.
|
|
2291
|
+
* The returned iterable yields `[source, componentType]` pairs suitable for cleanup decisions
|
|
2292
|
+
* (e.g. whether to cascade-delete the source or just remove the specific component/relation).
|
|
2293
|
+
*
|
|
2294
|
+
* @param entityReferences - The shared reverse index map
|
|
2295
|
+
* @param targetEntityId - The entity whose referrers we want to inspect
|
|
2296
|
+
* @returns Iterable of [sourceEntityId, componentOrRelationId]
|
|
2297
|
+
*
|
|
2298
|
+
* @internal
|
|
2299
|
+
*/
|
|
2145
2300
|
function getEntityReferences(entityReferences, targetEntityId) {
|
|
2146
2301
|
return entityReferences.get(targetEntityId) ?? new MultiMap();
|
|
2147
2302
|
}
|
|
@@ -2523,7 +2678,10 @@ var World = class {
|
|
|
2523
2678
|
const archetype = this.entityToArchetype.get(entityId);
|
|
2524
2679
|
if (!archetype) return false;
|
|
2525
2680
|
if (archetype.componentTypeSet.has(componentType)) return true;
|
|
2526
|
-
if (isDontFragmentRelation(componentType))
|
|
2681
|
+
if (isDontFragmentRelation(componentType)) {
|
|
2682
|
+
if (this.dontFragmentStore.getValue(entityId, componentType) !== void 0) return true;
|
|
2683
|
+
return this.dontFragmentStore.getAllForEntity(entityId).some(([t]) => t === componentType);
|
|
2684
|
+
}
|
|
2527
2685
|
return false;
|
|
2528
2686
|
}
|
|
2529
2687
|
get(entityId, componentType = entityId) {
|
|
@@ -2536,7 +2694,7 @@ var World = class {
|
|
|
2536
2694
|
if (componentType >= 0 || componentType % RELATION_SHIFT !== 0) {
|
|
2537
2695
|
const inArchetype = archetype.componentTypeSet.has(componentType);
|
|
2538
2696
|
const hasDontFragment = isDontFragmentRelation(componentType);
|
|
2539
|
-
if (!(inArchetype || hasDontFragment && this.dontFragmentStore.
|
|
2697
|
+
if (!(inArchetype || hasDontFragment && (this.dontFragmentStore.getValue(entityId, componentType) !== void 0 || this.dontFragmentStore.getAllForEntity(entityId).some(([t]) => t === componentType)))) throw new Error(`Entity ${entityId} does not have component ${componentType}. Use has() to check component existence before calling get().`);
|
|
2540
2698
|
}
|
|
2541
2699
|
return archetype.get(entityId, componentType);
|
|
2542
2700
|
}
|