@codehz/ecs 0.6.8 → 0.6.10
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/builder.d.mts +54 -13
- package/package.json +1 -1
- package/world.mjs +375 -204
- package/world.mjs.map +1 -1
package/world.mjs
CHANGED
|
@@ -98,16 +98,15 @@ function decodeRelationId(relationId) {
|
|
|
98
98
|
function getIdType(id) {
|
|
99
99
|
if (isComponentId(id)) return "component";
|
|
100
100
|
if (isEntityId(id)) return "entity";
|
|
101
|
-
if (id < 0)
|
|
102
|
-
const decoded =
|
|
103
|
-
if (decoded
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
return "invalid";
|
|
101
|
+
if (id < 0) {
|
|
102
|
+
const decoded = decodeRelationRaw(id);
|
|
103
|
+
if (decoded === null) return "invalid";
|
|
104
|
+
const { componentId: rawComponentId, targetId: rawTargetId } = decoded;
|
|
105
|
+
if (!isValidComponentId(rawComponentId)) return "invalid";
|
|
106
|
+
if (rawTargetId === WILDCARD_TARGET_ID) return "wildcard-relation";
|
|
107
|
+
else if (isEntityId(rawTargetId)) return "entity-relation";
|
|
108
|
+
else if (isComponentId(rawTargetId)) return "component-relation";
|
|
109
|
+
else return "invalid";
|
|
111
110
|
}
|
|
112
111
|
return "invalid";
|
|
113
112
|
}
|
|
@@ -119,28 +118,29 @@ function getIdType(id) {
|
|
|
119
118
|
function getDetailedIdType(id) {
|
|
120
119
|
if (isComponentId(id)) return { type: "component" };
|
|
121
120
|
if (isEntityId(id)) return { type: "entity" };
|
|
122
|
-
if (id < 0)
|
|
123
|
-
const decoded =
|
|
124
|
-
if (decoded
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
case "wildcard":
|
|
134
|
-
type = "wildcard-relation";
|
|
135
|
-
break;
|
|
136
|
-
}
|
|
137
|
-
return {
|
|
138
|
-
type,
|
|
139
|
-
componentId: decoded.componentId,
|
|
140
|
-
targetId: decoded.targetId
|
|
121
|
+
if (id < 0) {
|
|
122
|
+
const decoded = decodeRelationRaw(id);
|
|
123
|
+
if (decoded === null) return { type: "invalid" };
|
|
124
|
+
const { componentId: rawComponentId, targetId: rawTargetId } = decoded;
|
|
125
|
+
if (!isValidComponentId(rawComponentId)) return { type: "invalid" };
|
|
126
|
+
const componentId = rawComponentId;
|
|
127
|
+
const targetId = rawTargetId;
|
|
128
|
+
if (targetId === WILDCARD_TARGET_ID) return {
|
|
129
|
+
type: "wildcard-relation",
|
|
130
|
+
componentId,
|
|
131
|
+
targetId
|
|
141
132
|
};
|
|
142
|
-
|
|
143
|
-
|
|
133
|
+
else if (isEntityId(targetId)) return {
|
|
134
|
+
type: "entity-relation",
|
|
135
|
+
componentId,
|
|
136
|
+
targetId
|
|
137
|
+
};
|
|
138
|
+
else if (isComponentId(targetId)) return {
|
|
139
|
+
type: "component-relation",
|
|
140
|
+
componentId,
|
|
141
|
+
targetId
|
|
142
|
+
};
|
|
143
|
+
else return { type: "invalid" };
|
|
144
144
|
}
|
|
145
145
|
return { type: "invalid" };
|
|
146
146
|
}
|
|
@@ -175,17 +175,18 @@ function isEntityRelation(id) {
|
|
|
175
175
|
*/
|
|
176
176
|
var EntityIdManager = class {
|
|
177
177
|
nextId = ENTITY_ID_START;
|
|
178
|
-
|
|
178
|
+
/**
|
|
179
|
+
* Free list uses a stack (LIFO) for better memory locality when reusing IDs.
|
|
180
|
+
* We use an array instead of a Set for significantly better performance.
|
|
181
|
+
*/
|
|
182
|
+
freelist = [];
|
|
179
183
|
/**
|
|
180
184
|
* Allocate a new entity ID
|
|
181
185
|
* Uses freelist if available, otherwise increments counter
|
|
182
186
|
*/
|
|
183
187
|
allocate() {
|
|
184
|
-
if (this.freelist.
|
|
185
|
-
|
|
186
|
-
this.freelist.delete(id);
|
|
187
|
-
return id;
|
|
188
|
-
} else {
|
|
188
|
+
if (this.freelist.length > 0) return this.freelist.pop();
|
|
189
|
+
else {
|
|
189
190
|
const id = this.nextId;
|
|
190
191
|
this.nextId++;
|
|
191
192
|
if (this.nextId >= Number.MAX_SAFE_INTEGER) throw new Error("Entity ID overflow: reached maximum safe integer");
|
|
@@ -199,13 +200,13 @@ var EntityIdManager = class {
|
|
|
199
200
|
deallocate(id) {
|
|
200
201
|
if (!isEntityId(id)) throw new Error("Can only deallocate valid entity IDs");
|
|
201
202
|
if (id >= this.nextId) throw new Error("Cannot deallocate an ID that was never allocated");
|
|
202
|
-
this.freelist.
|
|
203
|
+
this.freelist.push(id);
|
|
203
204
|
}
|
|
204
205
|
/**
|
|
205
206
|
* Get the current freelist size (for debugging/monitoring)
|
|
206
207
|
*/
|
|
207
208
|
getFreelistSize() {
|
|
208
|
-
return this.freelist.
|
|
209
|
+
return this.freelist.length;
|
|
209
210
|
}
|
|
210
211
|
/**
|
|
211
212
|
* Get the next ID that would be allocated (for debugging)
|
|
@@ -230,7 +231,7 @@ var EntityIdManager = class {
|
|
|
230
231
|
deserializeState(state) {
|
|
231
232
|
if (typeof state.nextId !== "number") throw new Error("Invalid state for EntityIdManager.deserializeState");
|
|
232
233
|
this.nextId = state.nextId;
|
|
233
|
-
this.freelist =
|
|
234
|
+
this.freelist = state.freelist || [];
|
|
234
235
|
}
|
|
235
236
|
};
|
|
236
237
|
/**
|
|
@@ -368,6 +369,7 @@ const componentNames = new Array(COMPONENT_ID_MAX + 1);
|
|
|
368
369
|
const exclusiveFlags = new BitSet(COMPONENT_ID_MAX + 1);
|
|
369
370
|
const cascadeDeleteFlags = new BitSet(COMPONENT_ID_MAX + 1);
|
|
370
371
|
const dontFragmentFlags = new BitSet(COMPONENT_ID_MAX + 1);
|
|
372
|
+
const componentMerges = new Array(COMPONENT_ID_MAX + 1);
|
|
371
373
|
/**
|
|
372
374
|
* Allocate a new component ID from the global allocator.
|
|
373
375
|
* @param nameOrOptions Optional name for the component (for serialization/debugging) or options object
|
|
@@ -400,6 +402,7 @@ function component(nameOrOptions) {
|
|
|
400
402
|
if (options.exclusive) exclusiveFlags.set(id);
|
|
401
403
|
if (options.cascadeDelete) cascadeDeleteFlags.set(id);
|
|
402
404
|
if (options.dontFragment) dontFragmentFlags.set(id);
|
|
405
|
+
if (options.merge) componentMerges[id] = options.merge;
|
|
403
406
|
}
|
|
404
407
|
return id;
|
|
405
408
|
}
|
|
@@ -418,6 +421,21 @@ function getComponentIdByName(name) {
|
|
|
418
421
|
function getComponentNameById(id) {
|
|
419
422
|
return componentNames[id];
|
|
420
423
|
}
|
|
424
|
+
function getBaseComponentId(componentType) {
|
|
425
|
+
if (isComponentId(componentType)) return componentType;
|
|
426
|
+
const decoded = decodeRelationRaw(componentType);
|
|
427
|
+
if (decoded === null) return void 0;
|
|
428
|
+
return isValidComponentId(decoded.componentId) ? decoded.componentId : void 0;
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Get merge callback for a componentType (including relation component types).
|
|
432
|
+
* Returns undefined if the base component has no merge callback.
|
|
433
|
+
*/
|
|
434
|
+
function getComponentMerge(componentType) {
|
|
435
|
+
const baseComponentId = getBaseComponentId(componentType);
|
|
436
|
+
if (baseComponentId === void 0) return void 0;
|
|
437
|
+
return componentMerges[baseComponentId];
|
|
438
|
+
}
|
|
421
439
|
/**
|
|
422
440
|
* Check if a component is marked as exclusive
|
|
423
441
|
* @param id The component ID
|
|
@@ -627,10 +645,17 @@ var ComponentChangeset = class {
|
|
|
627
645
|
//#endregion
|
|
628
646
|
//#region src/commands/command-buffer.ts
|
|
629
647
|
/**
|
|
648
|
+
* Maximum number of command buffer execution iterations to prevent infinite loops
|
|
649
|
+
*/
|
|
650
|
+
const MAX_COMMAND_ITERATIONS = 100;
|
|
651
|
+
/**
|
|
630
652
|
* Command buffer for deferred structural changes
|
|
631
653
|
*/
|
|
632
654
|
var CommandBuffer = class {
|
|
633
655
|
commands = [];
|
|
656
|
+
swapBuffer = [];
|
|
657
|
+
/** Reusable map to group commands by entity, avoids per-sync allocations */
|
|
658
|
+
entityCommands = /* @__PURE__ */ new Map();
|
|
634
659
|
executeEntityCommands;
|
|
635
660
|
/**
|
|
636
661
|
* Create a command buffer with an executor function
|
|
@@ -669,19 +694,22 @@ var CommandBuffer = class {
|
|
|
669
694
|
* Execute all commands and clear the buffer
|
|
670
695
|
*/
|
|
671
696
|
execute() {
|
|
672
|
-
const MAX_ITERATIONS = 100;
|
|
673
697
|
let iterations = 0;
|
|
674
698
|
while (this.commands.length > 0) {
|
|
675
|
-
if (iterations >=
|
|
699
|
+
if (iterations >= MAX_COMMAND_ITERATIONS) throw new Error("Command execution exceeded maximum iterations, possible infinite loop");
|
|
676
700
|
iterations++;
|
|
677
|
-
const currentCommands =
|
|
678
|
-
this.commands =
|
|
679
|
-
const entityCommands =
|
|
701
|
+
const currentCommands = this.commands;
|
|
702
|
+
this.commands = this.swapBuffer;
|
|
703
|
+
const entityCommands = this.entityCommands;
|
|
680
704
|
for (const cmd of currentCommands) {
|
|
681
|
-
|
|
682
|
-
|
|
705
|
+
const existing = entityCommands.get(cmd.entityId);
|
|
706
|
+
if (existing !== void 0) existing.push(cmd);
|
|
707
|
+
else entityCommands.set(cmd.entityId, [cmd]);
|
|
683
708
|
}
|
|
709
|
+
currentCommands.length = 0;
|
|
710
|
+
this.swapBuffer = currentCommands;
|
|
684
711
|
for (const [entityId, commands] of entityCommands) this.executeEntityCommands(entityId, commands);
|
|
712
|
+
entityCommands.clear();
|
|
685
713
|
}
|
|
686
714
|
}
|
|
687
715
|
/**
|
|
@@ -721,8 +749,8 @@ function matchesComponentTypes(archetype, componentTypes) {
|
|
|
721
749
|
});
|
|
722
750
|
else if ((detailedType.type === "entity-relation" || detailedType.type === "component-relation") && detailedType.componentId !== void 0 && isDontFragmentComponent(detailedType.componentId)) {
|
|
723
751
|
const wildcardMarker = relation(detailedType.componentId, "*");
|
|
724
|
-
return archetype.
|
|
725
|
-
} else return archetype.
|
|
752
|
+
return archetype.componentTypeSet.has(wildcardMarker);
|
|
753
|
+
} else return archetype.componentTypeSet.has(type);
|
|
726
754
|
});
|
|
727
755
|
}
|
|
728
756
|
/**
|
|
@@ -735,10 +763,20 @@ function matchesFilter(archetype, filter) {
|
|
|
735
763
|
if (!isRelationId(archetypeType)) return false;
|
|
736
764
|
return getComponentIdFromRelationId(archetypeType) === detailedType.componentId;
|
|
737
765
|
});
|
|
738
|
-
else return !archetype.
|
|
766
|
+
else return !archetype.componentTypeSet.has(type);
|
|
739
767
|
});
|
|
740
768
|
}
|
|
741
769
|
|
|
770
|
+
//#endregion
|
|
771
|
+
//#region src/core/component-type-utils.ts
|
|
772
|
+
/**
|
|
773
|
+
* Normalize component type collections into a stable ascending order.
|
|
774
|
+
* This keeps cache keys and archetype signatures deterministic.
|
|
775
|
+
*/
|
|
776
|
+
function normalizeComponentTypes(componentTypes) {
|
|
777
|
+
return [...componentTypes].sort((a, b) => a - b);
|
|
778
|
+
}
|
|
779
|
+
|
|
742
780
|
//#endregion
|
|
743
781
|
//#region src/query/query.ts
|
|
744
782
|
/**
|
|
@@ -750,13 +788,15 @@ var Query = class {
|
|
|
750
788
|
filter;
|
|
751
789
|
cachedArchetypes = [];
|
|
752
790
|
isDisposed = false;
|
|
791
|
+
/** Cache key assigned by World for O(1) releaseQuery lookup */
|
|
792
|
+
_cacheKey;
|
|
753
793
|
/** Cached wildcard component types for faster entity filtering */
|
|
754
794
|
wildcardTypes;
|
|
755
795
|
/** Cached specific dontFragment relation types that need entity-level filtering */
|
|
756
796
|
specificDontFragmentTypes;
|
|
757
797
|
constructor(world, componentTypes, filter = {}) {
|
|
758
798
|
this.world = world;
|
|
759
|
-
this.componentTypes =
|
|
799
|
+
this.componentTypes = normalizeComponentTypes(componentTypes);
|
|
760
800
|
this.filter = filter;
|
|
761
801
|
this.wildcardTypes = this.componentTypes.filter((ct) => getDetailedIdType(ct).type === "wildcard-relation");
|
|
762
802
|
this.specificDontFragmentTypes = this.componentTypes.filter((ct) => {
|
|
@@ -903,10 +943,10 @@ var Query = class {
|
|
|
903
943
|
* Get a value from cache or compute and cache it if not present
|
|
904
944
|
* @param cache The cache map
|
|
905
945
|
* @param key The cache key
|
|
906
|
-
* @param compute Function to compute the value if not cached
|
|
946
|
+
* @param compute Function to compute the value if not cached (may have side effects)
|
|
907
947
|
* @returns The cached or computed value
|
|
908
948
|
*/
|
|
909
|
-
function
|
|
949
|
+
function getOrCompute(cache, key, compute) {
|
|
910
950
|
let value = cache.get(key);
|
|
911
951
|
if (value === void 0) {
|
|
912
952
|
value = compute();
|
|
@@ -914,21 +954,6 @@ function getOrComputeCache(cache, key, compute) {
|
|
|
914
954
|
}
|
|
915
955
|
return value;
|
|
916
956
|
}
|
|
917
|
-
/**
|
|
918
|
-
* Get a value from cache or create and cache it if not present, allowing side effects during creation
|
|
919
|
-
* @param cache The cache map
|
|
920
|
-
* @param key The cache key
|
|
921
|
-
* @param create Function to create the value if not cached (can have side effects)
|
|
922
|
-
* @returns The cached or created value
|
|
923
|
-
*/
|
|
924
|
-
function getOrCreateWithSideEffect(cache, key, create) {
|
|
925
|
-
let value = cache.get(key);
|
|
926
|
-
if (value === void 0) {
|
|
927
|
-
value = create();
|
|
928
|
-
cache.set(key, value);
|
|
929
|
-
}
|
|
930
|
-
return value;
|
|
931
|
-
}
|
|
932
957
|
|
|
933
958
|
//#endregion
|
|
934
959
|
//#region src/core/types.ts
|
|
@@ -939,6 +964,18 @@ function isOptionalEntityId(type) {
|
|
|
939
964
|
//#endregion
|
|
940
965
|
//#region src/core/archetype-helpers.ts
|
|
941
966
|
/**
|
|
967
|
+
* Check if a components map has any wildcard relations matching a component ID
|
|
968
|
+
* @param components - Component entity's components map
|
|
969
|
+
* @param wildcardComponentId - The component ID to match
|
|
970
|
+
* @returns True if at least one matching relation exists
|
|
971
|
+
*/
|
|
972
|
+
function hasWildcardRelation(components, wildcardComponentId) {
|
|
973
|
+
for (const relId of components.keys()) if (isRelationId(relId)) {
|
|
974
|
+
if (getComponentIdFromRelationId(relId) === wildcardComponentId) return true;
|
|
975
|
+
}
|
|
976
|
+
return false;
|
|
977
|
+
}
|
|
978
|
+
/**
|
|
942
979
|
* Check if a detailed type represents a relation (entity or component)
|
|
943
980
|
*/
|
|
944
981
|
function isRelationType(detailedType) {
|
|
@@ -1036,6 +1073,10 @@ var Archetype = class {
|
|
|
1036
1073
|
*/
|
|
1037
1074
|
componentTypes;
|
|
1038
1075
|
/**
|
|
1076
|
+
* Set version of componentTypes for O(1) lookups in hot paths
|
|
1077
|
+
*/
|
|
1078
|
+
componentTypeSet;
|
|
1079
|
+
/**
|
|
1039
1080
|
* List of entities in this archetype
|
|
1040
1081
|
*/
|
|
1041
1082
|
entities = [];
|
|
@@ -1055,9 +1096,6 @@ var Archetype = class {
|
|
|
1055
1096
|
*/
|
|
1056
1097
|
dontFragmentRelations;
|
|
1057
1098
|
/**
|
|
1058
|
-
* Cache for pre-computed component data sources to avoid repeated calculations
|
|
1059
|
-
*/
|
|
1060
|
-
/**
|
|
1061
1099
|
* Multi-hooks that match this archetype
|
|
1062
1100
|
*/
|
|
1063
1101
|
matchingMultiHooks = /* @__PURE__ */ new Set();
|
|
@@ -1066,16 +1104,23 @@ var Archetype = class {
|
|
|
1066
1104
|
*/
|
|
1067
1105
|
componentDataSourcesCache = /* @__PURE__ */ new Map();
|
|
1068
1106
|
constructor(componentTypes, dontFragmentRelations) {
|
|
1069
|
-
this.componentTypes =
|
|
1107
|
+
this.componentTypes = normalizeComponentTypes(componentTypes);
|
|
1108
|
+
this.componentTypeSet = new Set(this.componentTypes);
|
|
1070
1109
|
this.dontFragmentRelations = dontFragmentRelations;
|
|
1071
1110
|
for (const componentType of this.componentTypes) this.componentData.set(componentType, []);
|
|
1072
1111
|
}
|
|
1073
1112
|
get size() {
|
|
1074
1113
|
return this.entities.length;
|
|
1075
1114
|
}
|
|
1115
|
+
/**
|
|
1116
|
+
* Check if the given component types match this archetype
|
|
1117
|
+
* @param componentTypes - Component types to check (can be in any order)
|
|
1118
|
+
* @returns true if the types match this archetype's component set
|
|
1119
|
+
* @note This method handles unsorted input by internally sorting for comparison
|
|
1120
|
+
*/
|
|
1076
1121
|
matches(componentTypes) {
|
|
1077
1122
|
if (this.componentTypes.length !== componentTypes.length) return false;
|
|
1078
|
-
const sortedTypes =
|
|
1123
|
+
const sortedTypes = normalizeComponentTypes(componentTypes);
|
|
1079
1124
|
return this.componentTypes.every((type, index) => type === sortedTypes[index]);
|
|
1080
1125
|
}
|
|
1081
1126
|
addEntity(entityId, componentData) {
|
|
@@ -1092,7 +1137,7 @@ var Archetype = class {
|
|
|
1092
1137
|
addDontFragmentRelations(entityId, componentData) {
|
|
1093
1138
|
const dontFragmentData = /* @__PURE__ */ new Map();
|
|
1094
1139
|
for (const [componentType, data] of componentData) {
|
|
1095
|
-
if (this.
|
|
1140
|
+
if (this.componentTypeSet.has(componentType)) continue;
|
|
1096
1141
|
const detailedType = getDetailedIdType(componentType);
|
|
1097
1142
|
if (isRelationType(detailedType) && isDontFragmentComponent(detailedType.componentId)) dontFragmentData.set(componentType, data);
|
|
1098
1143
|
}
|
|
@@ -1110,6 +1155,9 @@ var Archetype = class {
|
|
|
1110
1155
|
if (dontFragmentData) for (const [componentType, data] of dontFragmentData) entityData.set(componentType, data);
|
|
1111
1156
|
return entityData;
|
|
1112
1157
|
}
|
|
1158
|
+
getEntityDontFragmentRelations(entityId) {
|
|
1159
|
+
return this.dontFragmentRelations.get(entityId);
|
|
1160
|
+
}
|
|
1113
1161
|
dump() {
|
|
1114
1162
|
return this.entities.map((entity, i) => {
|
|
1115
1163
|
const components = /* @__PURE__ */ new Map();
|
|
@@ -1176,7 +1224,7 @@ var Archetype = class {
|
|
|
1176
1224
|
return relations;
|
|
1177
1225
|
}
|
|
1178
1226
|
getRegularComponent(entityId, index, componentType) {
|
|
1179
|
-
if (this.
|
|
1227
|
+
if (this.componentTypeSet.has(componentType)) {
|
|
1180
1228
|
const data = this.getComponentData(componentType)[index];
|
|
1181
1229
|
if (data === MISSING_COMPONENT) throw new Error(`Component type ${componentType} not found for entity ${entityId}`);
|
|
1182
1230
|
return data;
|
|
@@ -1188,7 +1236,7 @@ var Archetype = class {
|
|
|
1188
1236
|
getOptional(entityId, componentType) {
|
|
1189
1237
|
const index = this.entityToIndex.get(entityId);
|
|
1190
1238
|
if (index === void 0) throw new Error(`Entity ${entityId} is not in this archetype`);
|
|
1191
|
-
if (this.
|
|
1239
|
+
if (this.componentTypeSet.has(componentType)) {
|
|
1192
1240
|
const data = this.getComponentData(componentType)[index];
|
|
1193
1241
|
if (data === MISSING_COMPONENT) return void 0;
|
|
1194
1242
|
return { value: data };
|
|
@@ -1231,7 +1279,7 @@ var Archetype = class {
|
|
|
1231
1279
|
}
|
|
1232
1280
|
getCachedComponentDataSources(componentTypes) {
|
|
1233
1281
|
const cacheKey = buildCacheKey(componentTypes);
|
|
1234
|
-
return
|
|
1282
|
+
return getOrCompute(this.componentDataSourcesCache, cacheKey, () => componentTypes.map((compType) => this.getComponentDataSource(compType)));
|
|
1235
1283
|
}
|
|
1236
1284
|
getComponentDataSource(compType) {
|
|
1237
1285
|
const optional = isOptionalEntityId(compType);
|
|
@@ -1376,8 +1424,8 @@ function decodeSerializedId(sid) {
|
|
|
1376
1424
|
//#endregion
|
|
1377
1425
|
//#region src/core/world-commands.ts
|
|
1378
1426
|
function processCommands(entityId, currentArchetype, commands, changeset, handleExclusiveRelation) {
|
|
1379
|
-
for (const command of commands) if (command.type === "set"
|
|
1380
|
-
else if (command.type === "delete"
|
|
1427
|
+
for (const command of commands) if (command.type === "set") processSetCommand(entityId, currentArchetype, command.componentType, command.component, changeset, handleExclusiveRelation);
|
|
1428
|
+
else if (command.type === "delete") processDeleteCommand(entityId, currentArchetype, command.componentType, changeset);
|
|
1381
1429
|
}
|
|
1382
1430
|
function processSetCommand(entityId, currentArchetype, componentType, component$1, changeset, handleExclusiveRelation) {
|
|
1383
1431
|
const componentId = getComponentIdFromRelationId(componentType);
|
|
@@ -1385,9 +1433,15 @@ function processSetCommand(entityId, currentArchetype, componentType, component$
|
|
|
1385
1433
|
handleExclusiveRelation(entityId, currentArchetype, componentId);
|
|
1386
1434
|
if (isDontFragmentComponent(componentId)) {
|
|
1387
1435
|
const wildcardMarker = relation(componentId, "*");
|
|
1388
|
-
if (!currentArchetype.
|
|
1436
|
+
if (!currentArchetype.componentTypeSet.has(wildcardMarker)) changeset.set(wildcardMarker, void 0);
|
|
1389
1437
|
}
|
|
1390
1438
|
}
|
|
1439
|
+
const merge = getComponentMerge(componentType);
|
|
1440
|
+
if (merge !== void 0 && changeset.adds.has(componentType)) {
|
|
1441
|
+
const prev = changeset.adds.get(componentType);
|
|
1442
|
+
changeset.set(componentType, merge(prev, component$1));
|
|
1443
|
+
return;
|
|
1444
|
+
}
|
|
1391
1445
|
changeset.set(componentType, component$1);
|
|
1392
1446
|
}
|
|
1393
1447
|
function processDeleteCommand(entityId, currentArchetype, componentType, changeset) {
|
|
@@ -1403,10 +1457,9 @@ function removeMatchingRelations(entityId, archetype, baseComponentId, changeset
|
|
|
1403
1457
|
if (isWildcardRelationId(componentType)) continue;
|
|
1404
1458
|
if (getComponentIdFromRelationId(componentType) === baseComponentId) changeset.delete(componentType);
|
|
1405
1459
|
}
|
|
1406
|
-
const
|
|
1407
|
-
if (
|
|
1408
|
-
|
|
1409
|
-
if (getComponentIdFromRelationId(componentType) === baseComponentId) changeset.delete(componentType);
|
|
1460
|
+
const dontFragmentData = archetype.getEntityDontFragmentRelations(entityId);
|
|
1461
|
+
if (dontFragmentData) {
|
|
1462
|
+
for (const componentType of dontFragmentData.keys()) if (getComponentIdFromRelationId(componentType) === baseComponentId) changeset.delete(componentType);
|
|
1410
1463
|
}
|
|
1411
1464
|
}
|
|
1412
1465
|
function removeWildcardRelations(entityId, currentArchetype, baseComponentId, changeset) {
|
|
@@ -1416,35 +1469,66 @@ function removeWildcardRelations(entityId, currentArchetype, baseComponentId, ch
|
|
|
1416
1469
|
function maybeRemoveWildcardMarker(entityId, archetype, removedComponentType, componentId, changeset) {
|
|
1417
1470
|
if (componentId === void 0 || !isDontFragmentComponent(componentId)) return;
|
|
1418
1471
|
const wildcardMarker = relation(componentId, "*");
|
|
1419
|
-
const
|
|
1420
|
-
if (!entityData) {
|
|
1421
|
-
changeset.delete(wildcardMarker);
|
|
1422
|
-
return;
|
|
1423
|
-
}
|
|
1424
|
-
for (const [otherComponentType] of entityData) {
|
|
1472
|
+
for (const otherComponentType of archetype.componentTypes) {
|
|
1425
1473
|
if (otherComponentType === removedComponentType) continue;
|
|
1426
1474
|
if (otherComponentType === wildcardMarker) continue;
|
|
1427
1475
|
if (changeset.removes.has(otherComponentType)) continue;
|
|
1428
1476
|
if (getComponentIdFromRelationId(otherComponentType) === componentId) return;
|
|
1429
1477
|
}
|
|
1478
|
+
const dontFragmentData = archetype.getEntityDontFragmentRelations(entityId);
|
|
1479
|
+
if (dontFragmentData) for (const otherComponentType of dontFragmentData.keys()) {
|
|
1480
|
+
if (otherComponentType === removedComponentType) continue;
|
|
1481
|
+
if (changeset.removes.has(otherComponentType)) continue;
|
|
1482
|
+
if (getComponentIdFromRelationId(otherComponentType) === componentId) return;
|
|
1483
|
+
}
|
|
1430
1484
|
changeset.delete(wildcardMarker);
|
|
1431
1485
|
}
|
|
1486
|
+
function hasEntityComponent(archetype, entityId, componentType) {
|
|
1487
|
+
if (archetype.componentTypeSet.has(componentType)) return true;
|
|
1488
|
+
return archetype.getEntityDontFragmentRelations(entityId)?.has(componentType) ?? false;
|
|
1489
|
+
}
|
|
1490
|
+
function pruneMissingRemovals(changeset, archetype, entityId) {
|
|
1491
|
+
let toPrune;
|
|
1492
|
+
for (const componentType of changeset.removes) if (!hasEntityComponent(archetype, entityId, componentType)) {
|
|
1493
|
+
if (toPrune === void 0) toPrune = [];
|
|
1494
|
+
toPrune.push(componentType);
|
|
1495
|
+
}
|
|
1496
|
+
if (toPrune !== void 0) for (const componentType of toPrune) changeset.removes.delete(componentType);
|
|
1497
|
+
}
|
|
1498
|
+
function hasArchetypeStructuralChange(changeset, currentArchetype) {
|
|
1499
|
+
for (const componentType of changeset.removes) if (!isDontFragmentRelation(componentType) && currentArchetype.componentTypeSet.has(componentType)) return true;
|
|
1500
|
+
for (const componentType of changeset.adds.keys()) if (!isDontFragmentRelation(componentType) && !currentArchetype.componentTypeSet.has(componentType)) return true;
|
|
1501
|
+
return false;
|
|
1502
|
+
}
|
|
1503
|
+
function buildFinalRegularComponentTypes(currentArchetype, changeset) {
|
|
1504
|
+
const finalRegularTypes = new Set(currentArchetype.componentTypes);
|
|
1505
|
+
for (const componentType of changeset.removes) if (!isDontFragmentRelation(componentType)) finalRegularTypes.delete(componentType);
|
|
1506
|
+
for (const componentType of changeset.adds.keys()) if (!isDontFragmentRelation(componentType)) finalRegularTypes.add(componentType);
|
|
1507
|
+
return Array.from(finalRegularTypes);
|
|
1508
|
+
}
|
|
1432
1509
|
function applyChangeset(ctx, entityId, currentArchetype, changeset, entityToArchetype) {
|
|
1433
|
-
const currentEntityData = currentArchetype.getEntity(entityId);
|
|
1434
|
-
const allCurrentComponentTypes = currentEntityData ? Array.from(currentEntityData.keys()) : currentArchetype.componentTypes;
|
|
1435
|
-
const finalComponentTypes = changeset.getFinalComponentTypes(allCurrentComponentTypes);
|
|
1436
1510
|
const removedComponents = /* @__PURE__ */ new Map();
|
|
1437
|
-
|
|
1511
|
+
pruneMissingRemovals(changeset, currentArchetype, entityId);
|
|
1512
|
+
if (hasArchetypeStructuralChange(changeset, currentArchetype)) return {
|
|
1438
1513
|
removedComponents,
|
|
1439
|
-
newArchetype: moveEntityToNewArchetype(ctx, entityId, currentArchetype,
|
|
1514
|
+
newArchetype: moveEntityToNewArchetype(ctx, entityId, currentArchetype, buildFinalRegularComponentTypes(currentArchetype, changeset), changeset, removedComponents, entityToArchetype)
|
|
1440
1515
|
};
|
|
1441
|
-
|
|
1442
|
-
else updateEntityInSameArchetype(ctx, entityId, currentArchetype, changeset, removedComponents);
|
|
1516
|
+
updateEntityInSameArchetype(ctx, entityId, currentArchetype, changeset, removedComponents);
|
|
1443
1517
|
return {
|
|
1444
1518
|
removedComponents,
|
|
1445
1519
|
newArchetype: currentArchetype
|
|
1446
1520
|
};
|
|
1447
1521
|
}
|
|
1522
|
+
/**
|
|
1523
|
+
* Optimized variant of applyChangeset for when no lifecycle hooks are registered.
|
|
1524
|
+
* Skips creating the removedComponents map, reducing allocations in the hot path.
|
|
1525
|
+
*/
|
|
1526
|
+
function applyChangesetNoHooks(ctx, entityId, currentArchetype, changeset, entityToArchetype) {
|
|
1527
|
+
pruneMissingRemovals(changeset, currentArchetype, entityId);
|
|
1528
|
+
if (hasArchetypeStructuralChange(changeset, currentArchetype)) return moveEntityToNewArchetypeNoHooks(ctx, entityId, currentArchetype, buildFinalRegularComponentTypes(currentArchetype, changeset), changeset, entityToArchetype);
|
|
1529
|
+
updateEntityInSameArchetypeNoHooks(ctx, entityId, currentArchetype, changeset);
|
|
1530
|
+
return currentArchetype;
|
|
1531
|
+
}
|
|
1448
1532
|
function moveEntityToNewArchetype(ctx, entityId, currentArchetype, finalComponentTypes, changeset, removedComponents, entityToArchetype) {
|
|
1449
1533
|
const newArchetype = ctx.ensureArchetype(finalComponentTypes);
|
|
1450
1534
|
const currentComponents = currentArchetype.removeEntity(entityId);
|
|
@@ -1460,6 +1544,28 @@ function updateEntityInSameArchetype(ctx, entityId, currentArchetype, changeset,
|
|
|
1460
1544
|
currentArchetype.set(entityId, componentType, component$1);
|
|
1461
1545
|
}
|
|
1462
1546
|
}
|
|
1547
|
+
/**
|
|
1548
|
+
* No-hooks variant: moves entity to new archetype without collecting removed component data.
|
|
1549
|
+
* Only called from applyChangesetNoHooks when no lifecycle hooks are registered.
|
|
1550
|
+
*/
|
|
1551
|
+
function moveEntityToNewArchetypeNoHooks(ctx, entityId, currentArchetype, finalComponentTypes, changeset, entityToArchetype) {
|
|
1552
|
+
const newArchetype = ctx.ensureArchetype(finalComponentTypes);
|
|
1553
|
+
const currentComponents = currentArchetype.removeEntity(entityId);
|
|
1554
|
+
newArchetype.addEntity(entityId, changeset.applyTo(currentComponents));
|
|
1555
|
+
entityToArchetype.set(entityId, newArchetype);
|
|
1556
|
+
return newArchetype;
|
|
1557
|
+
}
|
|
1558
|
+
/**
|
|
1559
|
+
* No-hooks variant: updates entity in same archetype without tracking removed component data.
|
|
1560
|
+
* Only called from applyChangesetNoHooks when no lifecycle hooks are registered.
|
|
1561
|
+
*/
|
|
1562
|
+
function updateEntityInSameArchetypeNoHooks(ctx, entityId, currentArchetype, changeset) {
|
|
1563
|
+
applyDontFragmentChangesNoHooks(ctx.dontFragmentRelations, entityId, changeset);
|
|
1564
|
+
for (const [componentType, component$1] of changeset.adds) {
|
|
1565
|
+
if (isDontFragmentRelation(componentType)) continue;
|
|
1566
|
+
currentArchetype.set(entityId, componentType, component$1);
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1463
1569
|
function applyDontFragmentChanges(dontFragmentRelations, entityId, changeset, removedComponents) {
|
|
1464
1570
|
let entityRelations = dontFragmentRelations.get(entityId);
|
|
1465
1571
|
for (const componentType of changeset.removes) if (isDontFragmentRelation(componentType)) {
|
|
@@ -1480,6 +1586,23 @@ function applyDontFragmentChanges(dontFragmentRelations, entityId, changeset, re
|
|
|
1480
1586
|
}
|
|
1481
1587
|
if (entityRelations && entityRelations.size === 0) dontFragmentRelations.delete(entityId);
|
|
1482
1588
|
}
|
|
1589
|
+
/**
|
|
1590
|
+
* No-hooks variant of applyDontFragmentChanges that skips tracking removed component data.
|
|
1591
|
+
*/
|
|
1592
|
+
function applyDontFragmentChangesNoHooks(dontFragmentRelations, entityId, changeset) {
|
|
1593
|
+
let entityRelations = dontFragmentRelations.get(entityId);
|
|
1594
|
+
for (const componentType of changeset.removes) if (isDontFragmentRelation(componentType)) {
|
|
1595
|
+
if (entityRelations) entityRelations.delete(componentType);
|
|
1596
|
+
}
|
|
1597
|
+
for (const [componentType, component$1] of changeset.adds) if (isDontFragmentRelation(componentType)) {
|
|
1598
|
+
if (!entityRelations) {
|
|
1599
|
+
entityRelations = /* @__PURE__ */ new Map();
|
|
1600
|
+
dontFragmentRelations.set(entityId, entityRelations);
|
|
1601
|
+
}
|
|
1602
|
+
entityRelations.set(componentType, component$1);
|
|
1603
|
+
}
|
|
1604
|
+
if (entityRelations && entityRelations.size === 0) dontFragmentRelations.delete(entityId);
|
|
1605
|
+
}
|
|
1483
1606
|
function filterRegularComponentTypes(componentTypes) {
|
|
1484
1607
|
const regularTypes = [];
|
|
1485
1608
|
for (const componentType of componentTypes) {
|
|
@@ -1492,12 +1615,6 @@ function filterRegularComponentTypes(componentTypes) {
|
|
|
1492
1615
|
}
|
|
1493
1616
|
return regularTypes;
|
|
1494
1617
|
}
|
|
1495
|
-
function areComponentTypesEqual(types1, types2) {
|
|
1496
|
-
if (types1.length !== types2.length) return false;
|
|
1497
|
-
const sorted1 = [...types1].sort((a, b) => a - b);
|
|
1498
|
-
const sorted2 = [...types2].sort((a, b) => a - b);
|
|
1499
|
-
return sorted1.every((v, i) => v === sorted2[i]);
|
|
1500
|
-
}
|
|
1501
1618
|
|
|
1502
1619
|
//#endregion
|
|
1503
1620
|
//#region src/core/world-hooks.ts
|
|
@@ -1579,11 +1696,11 @@ function triggerMultiComponentHooks(ctx, entityId, addedComponents, removedCompo
|
|
|
1579
1696
|
}
|
|
1580
1697
|
function entityHasAllComponents(ctx, entityId, requiredComponents) {
|
|
1581
1698
|
return requiredComponents.every((c) => {
|
|
1582
|
-
if (isWildcardRelationId(c))
|
|
1583
|
-
const
|
|
1699
|
+
if (isWildcardRelationId(c)) {
|
|
1700
|
+
const wildcardResult = ctx.getOptional(entityId, c);
|
|
1701
|
+
if (!wildcardResult) return false;
|
|
1702
|
+
const wildcardData = wildcardResult.value;
|
|
1584
1703
|
return Array.isArray(wildcardData) && wildcardData.length > 0;
|
|
1585
|
-
} catch {
|
|
1586
|
-
return false;
|
|
1587
1704
|
}
|
|
1588
1705
|
return ctx.has(entityId, c);
|
|
1589
1706
|
});
|
|
@@ -1591,11 +1708,11 @@ function entityHasAllComponents(ctx, entityId, requiredComponents) {
|
|
|
1591
1708
|
function entityHadAllComponentsBefore(ctx, entityId, requiredComponents, removedComponents) {
|
|
1592
1709
|
return requiredComponents.every((c) => {
|
|
1593
1710
|
if (anyComponentMatches(removedComponents, c)) return true;
|
|
1594
|
-
if (isWildcardRelationId(c))
|
|
1595
|
-
const
|
|
1711
|
+
if (isWildcardRelationId(c)) {
|
|
1712
|
+
const wildcardResult = ctx.getOptional(entityId, c);
|
|
1713
|
+
if (!wildcardResult) return false;
|
|
1714
|
+
const wildcardData = wildcardResult.value;
|
|
1596
1715
|
return Array.isArray(wildcardData) && wildcardData.length > 0;
|
|
1597
|
-
} catch {
|
|
1598
|
-
return false;
|
|
1599
1716
|
}
|
|
1600
1717
|
return ctx.has(entityId, c);
|
|
1601
1718
|
});
|
|
@@ -1770,9 +1887,23 @@ var World = class {
|
|
|
1770
1887
|
relationEntityIdsByTarget = /* @__PURE__ */ new Map();
|
|
1771
1888
|
queries = [];
|
|
1772
1889
|
queryCache = /* @__PURE__ */ new Map();
|
|
1773
|
-
commandBuffer = new CommandBuffer((entityId, commands) => this.executeEntityCommands(entityId, commands));
|
|
1774
1890
|
legacyHooks = /* @__PURE__ */ new Map();
|
|
1775
1891
|
hooks = /* @__PURE__ */ new Set();
|
|
1892
|
+
commandBuffer = new CommandBuffer((entityId, commands) => this.executeEntityCommands(entityId, commands));
|
|
1893
|
+
_changeset = new ComponentChangeset();
|
|
1894
|
+
/** Cached command processor context to avoid per-entity object allocation */
|
|
1895
|
+
_commandCtx = {
|
|
1896
|
+
dontFragmentRelations: this.dontFragmentRelations,
|
|
1897
|
+
ensureArchetype: (ct) => this.ensureArchetype(ct)
|
|
1898
|
+
};
|
|
1899
|
+
/** Cached hooks context to avoid per-entity object allocation */
|
|
1900
|
+
_hooksCtx = {
|
|
1901
|
+
hooks: this.legacyHooks,
|
|
1902
|
+
multiHooks: this.hooks,
|
|
1903
|
+
has: (eid, ct) => this.has(eid, ct),
|
|
1904
|
+
get: (eid, ct) => this.get(eid, ct),
|
|
1905
|
+
getOptional: (eid, ct) => this.getOptional(eid, ct)
|
|
1906
|
+
};
|
|
1776
1907
|
constructor(snapshot) {
|
|
1777
1908
|
if (snapshot && typeof snapshot === "object") this.deserializeSnapshot(snapshot);
|
|
1778
1909
|
}
|
|
@@ -1879,8 +2010,9 @@ var World = class {
|
|
|
1879
2010
|
destroyEntityImmediate(entityId) {
|
|
1880
2011
|
const queue = [entityId];
|
|
1881
2012
|
const visited = /* @__PURE__ */ new Set();
|
|
1882
|
-
|
|
1883
|
-
|
|
2013
|
+
let queueIndex = 0;
|
|
2014
|
+
while (queueIndex < queue.length) {
|
|
2015
|
+
const cur = queue[queueIndex++];
|
|
1884
2016
|
if (visited.has(cur)) continue;
|
|
1885
2017
|
visited.add(cur);
|
|
1886
2018
|
const archetype = this.entityToArchetype.get(cur);
|
|
@@ -1915,40 +2047,77 @@ var World = class {
|
|
|
1915
2047
|
if (this.isComponentEntityId(entityId)) return true;
|
|
1916
2048
|
return this.entityToArchetype.has(entityId);
|
|
1917
2049
|
}
|
|
1918
|
-
|
|
2050
|
+
assertEntityExists(entityId, label) {
|
|
2051
|
+
if (!this.exists(entityId)) throw new Error(`${label} ${entityId} does not exist`);
|
|
2052
|
+
}
|
|
2053
|
+
assertComponentTypeValid(componentType) {
|
|
2054
|
+
if (getDetailedIdType(componentType).type === "invalid") throw new Error(`Invalid component type: ${componentType}`);
|
|
2055
|
+
}
|
|
2056
|
+
assertSetComponentTypeValid(componentType) {
|
|
2057
|
+
const detailedType = getDetailedIdType(componentType);
|
|
2058
|
+
if (detailedType.type === "invalid") throw new Error(`Invalid component type: ${componentType}`);
|
|
2059
|
+
if (detailedType.type === "wildcard-relation") throw new Error(`Cannot directly add wildcard relation components: ${componentType}`);
|
|
2060
|
+
}
|
|
2061
|
+
resolveSetOperation(entityId, componentTypeOrComponent, maybeComponent) {
|
|
1919
2062
|
if (maybeComponent === void 0 && componentTypeOrComponent !== void 0) {
|
|
1920
|
-
const detailedType
|
|
1921
|
-
if (detailedType
|
|
2063
|
+
const detailedType = getDetailedIdType(entityId);
|
|
2064
|
+
if (detailedType.type === "component" || detailedType.type === "component-relation") {
|
|
1922
2065
|
const componentId = entityId;
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
2066
|
+
this.assertEntityExists(componentId, "Component entity");
|
|
2067
|
+
this.assertSetComponentTypeValid(componentId);
|
|
2068
|
+
return {
|
|
2069
|
+
entityId: componentId,
|
|
2070
|
+
componentType: componentId,
|
|
2071
|
+
component: componentTypeOrComponent
|
|
2072
|
+
};
|
|
1930
2073
|
}
|
|
1931
2074
|
}
|
|
1932
|
-
const
|
|
2075
|
+
const targetEntityId = entityId;
|
|
1933
2076
|
const componentType = componentTypeOrComponent;
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
2077
|
+
this.assertEntityExists(targetEntityId, "Entity");
|
|
2078
|
+
this.assertSetComponentTypeValid(componentType);
|
|
2079
|
+
return {
|
|
2080
|
+
entityId: targetEntityId,
|
|
2081
|
+
componentType,
|
|
2082
|
+
component: maybeComponent
|
|
2083
|
+
};
|
|
1940
2084
|
}
|
|
1941
|
-
|
|
2085
|
+
resolveRemoveOperation(entityId, componentType) {
|
|
1942
2086
|
if (componentType === void 0) {
|
|
1943
2087
|
const componentId = entityId;
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
2088
|
+
this.assertEntityExists(componentId, "Component entity");
|
|
2089
|
+
return {
|
|
2090
|
+
entityId: componentId,
|
|
2091
|
+
componentType: componentId
|
|
2092
|
+
};
|
|
1947
2093
|
}
|
|
1948
|
-
const
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
2094
|
+
const targetEntityId = entityId;
|
|
2095
|
+
this.assertEntityExists(targetEntityId, "Entity");
|
|
2096
|
+
this.assertComponentTypeValid(componentType);
|
|
2097
|
+
return {
|
|
2098
|
+
entityId: targetEntityId,
|
|
2099
|
+
componentType
|
|
2100
|
+
};
|
|
2101
|
+
}
|
|
2102
|
+
getComponentEntityWildcardRelations(entityId, wildcardComponentType) {
|
|
2103
|
+
const componentId = getComponentIdFromRelationId(wildcardComponentType);
|
|
2104
|
+
const data = this.componentEntityComponents.get(entityId);
|
|
2105
|
+
if (componentId === void 0 || !data) return [];
|
|
2106
|
+
const relations = [];
|
|
2107
|
+
for (const [key, value] of data.entries()) {
|
|
2108
|
+
if (getComponentIdFromRelationId(key) !== componentId) continue;
|
|
2109
|
+
const detailed = getDetailedIdType(key);
|
|
2110
|
+
if (detailed.type === "entity-relation" || detailed.type === "component-relation") relations.push([detailed.targetId, value]);
|
|
2111
|
+
}
|
|
2112
|
+
return relations;
|
|
2113
|
+
}
|
|
2114
|
+
set(entityId, componentTypeOrComponent, maybeComponent) {
|
|
2115
|
+
const { entityId: targetEntityId, componentType, component: component$1 } = this.resolveSetOperation(entityId, componentTypeOrComponent, maybeComponent);
|
|
2116
|
+
this.commandBuffer.set(targetEntityId, componentType, component$1);
|
|
2117
|
+
}
|
|
2118
|
+
remove(entityId, componentType) {
|
|
2119
|
+
const { entityId: targetEntityId, componentType: targetComponentType } = this.resolveRemoveOperation(entityId, componentType);
|
|
2120
|
+
this.commandBuffer.remove(targetEntityId, targetComponentType);
|
|
1952
2121
|
}
|
|
1953
2122
|
/**
|
|
1954
2123
|
* Deletes an entity and all its components from the world.
|
|
@@ -1975,39 +2144,27 @@ var World = class {
|
|
|
1975
2144
|
if (componentId === void 0) return false;
|
|
1976
2145
|
const data = this.componentEntityComponents.get(entityId);
|
|
1977
2146
|
if (!data) return false;
|
|
1978
|
-
|
|
1979
|
-
return false;
|
|
2147
|
+
return hasWildcardRelation(data, componentId);
|
|
1980
2148
|
}
|
|
1981
2149
|
return this.componentEntityComponents.get(entityId)?.has(componentType) ?? false;
|
|
1982
2150
|
}
|
|
1983
2151
|
const archetype = this.entityToArchetype.get(entityId);
|
|
1984
2152
|
if (!archetype) return false;
|
|
1985
|
-
if (archetype.
|
|
2153
|
+
if (archetype.componentTypeSet.has(componentType)) return true;
|
|
1986
2154
|
if (isDontFragmentRelation(componentType)) return this.dontFragmentRelations.get(entityId)?.has(componentType) ?? false;
|
|
1987
2155
|
return false;
|
|
1988
2156
|
}
|
|
1989
2157
|
get(entityId, componentType = entityId) {
|
|
1990
2158
|
if (this.isComponentEntityId(entityId)) {
|
|
1991
|
-
if (isWildcardRelationId(componentType))
|
|
1992
|
-
const componentId = getComponentIdFromRelationId(componentType);
|
|
1993
|
-
const data$1 = this.componentEntityComponents.get(entityId);
|
|
1994
|
-
const relations = [];
|
|
1995
|
-
if (componentId !== void 0 && data$1) {
|
|
1996
|
-
for (const [key, value] of data$1.entries()) if (getComponentIdFromRelationId(key) === componentId) {
|
|
1997
|
-
const detailed = getDetailedIdType(key);
|
|
1998
|
-
if (detailed.type === "entity-relation" || detailed.type === "component-relation") relations.push([detailed.targetId, value]);
|
|
1999
|
-
}
|
|
2000
|
-
}
|
|
2001
|
-
return relations;
|
|
2002
|
-
}
|
|
2159
|
+
if (isWildcardRelationId(componentType)) return this.getComponentEntityWildcardRelations(entityId, componentType);
|
|
2003
2160
|
const data = this.componentEntityComponents.get(entityId);
|
|
2004
2161
|
if (!data || !data.has(componentType)) throw new Error(`Entity ${entityId} does not have component ${componentType}. Use has() to check component existence before calling get().`);
|
|
2005
2162
|
return data.get(componentType);
|
|
2006
2163
|
}
|
|
2007
2164
|
const archetype = this.entityToArchetype.get(entityId);
|
|
2008
2165
|
if (!archetype) throw new Error(`Entity ${entityId} does not exist`);
|
|
2009
|
-
if (componentType >= 0 || componentType %
|
|
2010
|
-
const inArchetype = archetype.
|
|
2166
|
+
if (componentType >= 0 || componentType % RELATION_SHIFT !== 0) {
|
|
2167
|
+
const inArchetype = archetype.componentTypeSet.has(componentType);
|
|
2011
2168
|
const hasDontFragment = isDontFragmentRelation(componentType);
|
|
2012
2169
|
if (!(inArchetype || hasDontFragment && this.dontFragmentRelations.get(entityId)?.has(componentType))) throw new Error(`Entity ${entityId} does not have component ${componentType}. Use has() to check component existence before calling get().`);
|
|
2013
2170
|
}
|
|
@@ -2016,15 +2173,7 @@ var World = class {
|
|
|
2016
2173
|
getOptional(entityId, componentType = entityId) {
|
|
2017
2174
|
if (this.isComponentEntityId(entityId)) {
|
|
2018
2175
|
if (isWildcardRelationId(componentType)) {
|
|
2019
|
-
const
|
|
2020
|
-
if (componentId === void 0) return void 0;
|
|
2021
|
-
const data$1 = this.componentEntityComponents.get(entityId);
|
|
2022
|
-
if (!data$1) return void 0;
|
|
2023
|
-
const relations = [];
|
|
2024
|
-
for (const [key, value] of data$1.entries()) if (getComponentIdFromRelationId(key) === componentId) {
|
|
2025
|
-
const detailed = getDetailedIdType(key);
|
|
2026
|
-
if (detailed.type === "entity-relation" || detailed.type === "component-relation") relations.push([detailed.targetId, value]);
|
|
2027
|
-
}
|
|
2176
|
+
const relations = this.getComponentEntityWildcardRelations(entityId, componentType);
|
|
2028
2177
|
if (relations.length === 0) return void 0;
|
|
2029
2178
|
return { value: relations };
|
|
2030
2179
|
}
|
|
@@ -2166,7 +2315,7 @@ var World = class {
|
|
|
2166
2315
|
* });
|
|
2167
2316
|
*/
|
|
2168
2317
|
createQuery(componentTypes, filter = {}) {
|
|
2169
|
-
const sortedTypes =
|
|
2318
|
+
const sortedTypes = normalizeComponentTypes(componentTypes);
|
|
2170
2319
|
const filterKey = serializeQueryFilter(filter);
|
|
2171
2320
|
const key = `${this.createArchetypeSignature(sortedTypes)}${filterKey ? `|${filterKey}` : ""}`;
|
|
2172
2321
|
const cached = this.queryCache.get(key);
|
|
@@ -2175,6 +2324,7 @@ var World = class {
|
|
|
2175
2324
|
return cached.query;
|
|
2176
2325
|
}
|
|
2177
2326
|
const query = new Query(this, sortedTypes, filter);
|
|
2327
|
+
query._cacheKey = key;
|
|
2178
2328
|
this.queryCache.set(key, {
|
|
2179
2329
|
query,
|
|
2180
2330
|
refCount: 1
|
|
@@ -2240,14 +2390,15 @@ var World = class {
|
|
|
2240
2390
|
* world.releaseQuery(query); // Optional cleanup
|
|
2241
2391
|
*/
|
|
2242
2392
|
releaseQuery(query) {
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2393
|
+
const key = query._cacheKey;
|
|
2394
|
+
if (!key) return;
|
|
2395
|
+
const cached = this.queryCache.get(key);
|
|
2396
|
+
if (!cached || cached.query !== query) return;
|
|
2397
|
+
cached.refCount--;
|
|
2398
|
+
if (cached.refCount <= 0) {
|
|
2399
|
+
this.queryCache.delete(key);
|
|
2400
|
+
this._unregisterQuery(query);
|
|
2401
|
+
cached.query._disposeInternal();
|
|
2251
2402
|
}
|
|
2252
2403
|
}
|
|
2253
2404
|
/**
|
|
@@ -2279,8 +2430,21 @@ var World = class {
|
|
|
2279
2430
|
getArchetypesWithComponents(componentTypes) {
|
|
2280
2431
|
if (componentTypes.length === 0) return [...this.archetypes];
|
|
2281
2432
|
if (componentTypes.length === 1) return this.archetypesByComponent.get(componentTypes[0]) || [];
|
|
2282
|
-
const archetypeLists = componentTypes.map((type) => this.archetypesByComponent.get(type) || []);
|
|
2283
|
-
|
|
2433
|
+
const archetypeLists = componentTypes.map((type) => this.archetypesByComponent.get(type) || []).sort((a, b) => a.length - b.length);
|
|
2434
|
+
const shortest = archetypeLists[0];
|
|
2435
|
+
if (shortest.length === 0) return [];
|
|
2436
|
+
if (archetypeLists.length === 2) {
|
|
2437
|
+
const second = archetypeLists[1];
|
|
2438
|
+
const secondSet = new Set(second);
|
|
2439
|
+
return shortest.filter((a) => secondSet.has(a));
|
|
2440
|
+
}
|
|
2441
|
+
let result = new Set(shortest);
|
|
2442
|
+
for (let i = 1; i < archetypeLists.length; i++) {
|
|
2443
|
+
const listSet = new Set(archetypeLists[i]);
|
|
2444
|
+
for (const item of result) if (!listSet.has(item)) result.delete(item);
|
|
2445
|
+
if (result.size === 0) return [];
|
|
2446
|
+
}
|
|
2447
|
+
return Array.from(result);
|
|
2284
2448
|
}
|
|
2285
2449
|
query(componentTypes, includeComponents) {
|
|
2286
2450
|
const matchingArchetypes = this.getMatchingArchetypes(componentTypes);
|
|
@@ -2295,7 +2459,8 @@ var World = class {
|
|
|
2295
2459
|
}
|
|
2296
2460
|
}
|
|
2297
2461
|
executeEntityCommands(entityId, commands) {
|
|
2298
|
-
const changeset =
|
|
2462
|
+
const changeset = this._changeset;
|
|
2463
|
+
changeset.clear();
|
|
2299
2464
|
if (this.isComponentEntityId(entityId)) {
|
|
2300
2465
|
this.executeComponentEntityCommands(entityId, commands);
|
|
2301
2466
|
return changeset;
|
|
@@ -2309,11 +2474,15 @@ var World = class {
|
|
|
2309
2474
|
processCommands(entityId, currentArchetype, commands, changeset, (eid, arch, compId) => {
|
|
2310
2475
|
if (isExclusiveComponent(compId)) removeMatchingRelations(eid, arch, compId, changeset);
|
|
2311
2476
|
});
|
|
2312
|
-
const
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2477
|
+
const hasHooks = this.legacyHooks.size > 0 || this.hooks.size > 0;
|
|
2478
|
+
const hasEntityRefs = changeset.removes.size > 0 || changeset.adds.size > 0;
|
|
2479
|
+
if (!hasHooks) {
|
|
2480
|
+
applyChangesetNoHooks(this._commandCtx, entityId, currentArchetype, changeset, this.entityToArchetype);
|
|
2481
|
+
if (hasEntityRefs) this.updateEntityReferences(entityId, changeset);
|
|
2482
|
+
return changeset;
|
|
2483
|
+
}
|
|
2484
|
+
const { removedComponents, newArchetype } = applyChangeset(this._commandCtx, entityId, currentArchetype, changeset, this.entityToArchetype);
|
|
2485
|
+
if (hasEntityRefs) this.updateEntityReferences(entityId, changeset);
|
|
2317
2486
|
triggerLifecycleHooks(this.createHooksContext(), entityId, changeset.adds, removedComponents, currentArchetype, newArchetype);
|
|
2318
2487
|
return changeset;
|
|
2319
2488
|
}
|
|
@@ -2322,27 +2491,32 @@ var World = class {
|
|
|
2322
2491
|
this.clearComponentEntityComponents(entityId);
|
|
2323
2492
|
return;
|
|
2324
2493
|
}
|
|
2325
|
-
|
|
2326
|
-
|
|
2494
|
+
const pendingSetValues = /* @__PURE__ */ new Map();
|
|
2495
|
+
for (const command of commands) if (command.type === "set" && command.componentType) {
|
|
2496
|
+
const merge = getComponentMerge(command.componentType);
|
|
2497
|
+
let nextValue = command.component;
|
|
2498
|
+
if (merge !== void 0 && pendingSetValues.has(command.componentType)) nextValue = merge(pendingSetValues.get(command.componentType), command.component);
|
|
2499
|
+
pendingSetValues.set(command.componentType, nextValue);
|
|
2500
|
+
this.getComponentEntityComponents(entityId, true).set(command.componentType, nextValue);
|
|
2501
|
+
} else if (command.type === "delete" && command.componentType) {
|
|
2327
2502
|
const data = this.componentEntityComponents.get(entityId);
|
|
2328
|
-
if (!data) continue;
|
|
2329
2503
|
if (isWildcardRelationId(command.componentType)) {
|
|
2330
2504
|
const componentId = getComponentIdFromRelationId(command.componentType);
|
|
2331
2505
|
if (componentId !== void 0) {
|
|
2332
|
-
|
|
2506
|
+
if (data) {
|
|
2507
|
+
for (const key of Array.from(data.keys())) if (getComponentIdFromRelationId(key) === componentId) data.delete(key);
|
|
2508
|
+
}
|
|
2509
|
+
for (const key of Array.from(pendingSetValues.keys())) if (getComponentIdFromRelationId(key) === componentId) pendingSetValues.delete(key);
|
|
2333
2510
|
}
|
|
2334
|
-
} else
|
|
2335
|
-
|
|
2511
|
+
} else {
|
|
2512
|
+
data?.delete(command.componentType);
|
|
2513
|
+
pendingSetValues.delete(command.componentType);
|
|
2514
|
+
}
|
|
2515
|
+
if (data?.size === 0) this.clearComponentEntityComponents(entityId);
|
|
2336
2516
|
}
|
|
2337
2517
|
}
|
|
2338
2518
|
createHooksContext() {
|
|
2339
|
-
return
|
|
2340
|
-
hooks: this.legacyHooks,
|
|
2341
|
-
multiHooks: this.hooks,
|
|
2342
|
-
has: (eid, ct) => this.has(eid, ct),
|
|
2343
|
-
get: (eid, ct) => this.get(eid, ct),
|
|
2344
|
-
getOptional: (eid, ct) => this.getOptional(eid, ct)
|
|
2345
|
-
};
|
|
2519
|
+
return this._hooksCtx;
|
|
2346
2520
|
}
|
|
2347
2521
|
removeComponentImmediate(entityId, componentType, targetEntityId) {
|
|
2348
2522
|
const sourceArchetype = this.entityToArchetype.get(entityId);
|
|
@@ -2351,10 +2525,7 @@ var World = class {
|
|
|
2351
2525
|
changeset.delete(componentType);
|
|
2352
2526
|
maybeRemoveWildcardMarker(entityId, sourceArchetype, componentType, getComponentIdFromRelationId(componentType), changeset);
|
|
2353
2527
|
const removedComponent = sourceArchetype.get(entityId, componentType);
|
|
2354
|
-
const { newArchetype } = applyChangeset(
|
|
2355
|
-
dontFragmentRelations: this.dontFragmentRelations,
|
|
2356
|
-
ensureArchetype: (ct) => this.ensureArchetype(ct)
|
|
2357
|
-
}, entityId, sourceArchetype, changeset, this.entityToArchetype);
|
|
2528
|
+
const { newArchetype } = applyChangeset(this._commandCtx, entityId, sourceArchetype, changeset, this.entityToArchetype);
|
|
2358
2529
|
untrackEntityReference(this.entityReferences, entityId, componentType, targetEntityId);
|
|
2359
2530
|
triggerLifecycleHooks(this.createHooksContext(), entityId, /* @__PURE__ */ new Map(), new Map([[componentType, removedComponent]]), sourceArchetype, newArchetype);
|
|
2360
2531
|
}
|
|
@@ -2362,16 +2533,16 @@ var World = class {
|
|
|
2362
2533
|
for (const componentType of changeset.removes) if (isEntityRelation(componentType)) {
|
|
2363
2534
|
const targetId = getTargetIdFromRelationId(componentType);
|
|
2364
2535
|
untrackEntityReference(this.entityReferences, entityId, componentType, targetId);
|
|
2365
|
-
} else if (componentType >=
|
|
2536
|
+
} else if (componentType >= ENTITY_ID_START) untrackEntityReference(this.entityReferences, entityId, componentType, componentType);
|
|
2366
2537
|
for (const [componentType] of changeset.adds) if (isEntityRelation(componentType)) {
|
|
2367
2538
|
const targetId = getTargetIdFromRelationId(componentType);
|
|
2368
2539
|
trackEntityReference(this.entityReferences, entityId, componentType, targetId);
|
|
2369
|
-
} else if (componentType >=
|
|
2540
|
+
} else if (componentType >= ENTITY_ID_START) trackEntityReference(this.entityReferences, entityId, componentType, componentType);
|
|
2370
2541
|
}
|
|
2371
2542
|
ensureArchetype(componentTypes) {
|
|
2372
|
-
const sortedTypes = filterRegularComponentTypes(componentTypes)
|
|
2543
|
+
const sortedTypes = normalizeComponentTypes(filterRegularComponentTypes(componentTypes));
|
|
2373
2544
|
const hashKey = this.createArchetypeSignature(sortedTypes);
|
|
2374
|
-
return
|
|
2545
|
+
return getOrCompute(this.archetypeBySignature, hashKey, () => this.createNewArchetype(sortedTypes));
|
|
2375
2546
|
}
|
|
2376
2547
|
createNewArchetype(componentTypes) {
|
|
2377
2548
|
const newArchetype = new Archetype(componentTypes, this.dontFragmentRelations);
|
|
@@ -2395,7 +2566,7 @@ var World = class {
|
|
|
2395
2566
|
const componentId = getComponentIdFromRelationId(c);
|
|
2396
2567
|
return componentId !== void 0 && archetype.hasRelationWithComponentId(componentId);
|
|
2397
2568
|
}
|
|
2398
|
-
return archetype.
|
|
2569
|
+
return archetype.componentTypeSet.has(c) || isDontFragmentRelation(c);
|
|
2399
2570
|
});
|
|
2400
2571
|
}
|
|
2401
2572
|
archetypeReferencesEntity(archetype, entityId) {
|