@codehz/ecs 0.6.8 → 0.6.9
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 +34 -10
- package/package.json +1 -1
- package/world.mjs +134 -100
- package/world.mjs.map +1 -1
package/builder.d.mts
CHANGED
|
@@ -163,13 +163,21 @@ declare class ComponentChangeset {
|
|
|
163
163
|
//#region src/commands/command-buffer.d.ts
|
|
164
164
|
/**
|
|
165
165
|
* Command for deferred execution
|
|
166
|
+
* Uses discriminated union for type safety
|
|
166
167
|
*/
|
|
167
|
-
|
|
168
|
-
type: "set"
|
|
168
|
+
type Command = {
|
|
169
|
+
type: "set";
|
|
169
170
|
entityId: EntityId;
|
|
170
|
-
componentType
|
|
171
|
-
component
|
|
172
|
-
}
|
|
171
|
+
componentType: EntityId<any>;
|
|
172
|
+
component: any;
|
|
173
|
+
} | {
|
|
174
|
+
type: "delete";
|
|
175
|
+
entityId: EntityId;
|
|
176
|
+
componentType: EntityId<any>;
|
|
177
|
+
} | {
|
|
178
|
+
type: "destroy";
|
|
179
|
+
entityId: EntityId;
|
|
180
|
+
};
|
|
173
181
|
//#endregion
|
|
174
182
|
//#region src/core/types.d.ts
|
|
175
183
|
/**
|
|
@@ -181,7 +189,7 @@ interface LegacyLifecycleHook<T = unknown> {
|
|
|
181
189
|
*/
|
|
182
190
|
on_init?: (entityId: EntityId, componentType: EntityId<T>, component: T) => void;
|
|
183
191
|
/**
|
|
184
|
-
* Called when a component is
|
|
192
|
+
* Called when a component is updated on an entity
|
|
185
193
|
*/
|
|
186
194
|
on_set?: (entityId: EntityId, componentType: EntityId<T>, component: T) => void;
|
|
187
195
|
/**
|
|
@@ -235,6 +243,10 @@ declare class Archetype {
|
|
|
235
243
|
* The component types that define this archetype
|
|
236
244
|
*/
|
|
237
245
|
readonly componentTypes: EntityId<any>[];
|
|
246
|
+
/**
|
|
247
|
+
* Set version of componentTypes for O(1) lookups in hot paths
|
|
248
|
+
*/
|
|
249
|
+
readonly componentTypeSet: ReadonlySet<EntityId<any>>;
|
|
238
250
|
/**
|
|
239
251
|
* List of entities in this archetype
|
|
240
252
|
*/
|
|
@@ -254,9 +266,6 @@ declare class Archetype {
|
|
|
254
266
|
* Stored in World to avoid migration overhead when entities change archetypes
|
|
255
267
|
*/
|
|
256
268
|
private dontFragmentRelations;
|
|
257
|
-
/**
|
|
258
|
-
* Cache for pre-computed component data sources to avoid repeated calculations
|
|
259
|
-
*/
|
|
260
269
|
/**
|
|
261
270
|
* Multi-hooks that match this archetype
|
|
262
271
|
*/
|
|
@@ -267,6 +276,12 @@ declare class Archetype {
|
|
|
267
276
|
private componentDataSourcesCache;
|
|
268
277
|
constructor(componentTypes: EntityId<any>[], dontFragmentRelations: Map<EntityId, Map<EntityId<any>, any>>);
|
|
269
278
|
get size(): number;
|
|
279
|
+
/**
|
|
280
|
+
* Check if the given component types match this archetype
|
|
281
|
+
* @param componentTypes - Component types to check (can be in any order)
|
|
282
|
+
* @returns true if the types match this archetype's component set
|
|
283
|
+
* @note This method handles unsorted input by internally sorting for comparison
|
|
284
|
+
*/
|
|
270
285
|
matches(componentTypes: EntityId<any>[]): boolean;
|
|
271
286
|
addEntity(entityId: EntityId, componentData: Map<EntityId<any>, any>): void;
|
|
272
287
|
private addDontFragmentRelations;
|
|
@@ -320,6 +335,8 @@ declare class Query {
|
|
|
320
335
|
private filter;
|
|
321
336
|
private cachedArchetypes;
|
|
322
337
|
private isDisposed;
|
|
338
|
+
/** Cache key assigned by World for O(1) releaseQuery lookup */
|
|
339
|
+
_cacheKey: string | undefined;
|
|
323
340
|
/** Cached wildcard component types for faster entity filtering */
|
|
324
341
|
private wildcardTypes;
|
|
325
342
|
/** Cached specific dontFragment relation types that need entity-level filtering */
|
|
@@ -401,9 +418,16 @@ type SerializedEntityId = number | string | {
|
|
|
401
418
|
component: string;
|
|
402
419
|
target: number | string | "*";
|
|
403
420
|
};
|
|
421
|
+
/**
|
|
422
|
+
* Serialized state of EntityIdManager
|
|
423
|
+
*/
|
|
424
|
+
interface SerializedEntityIdManager {
|
|
425
|
+
nextId: number;
|
|
426
|
+
freelist?: number[];
|
|
427
|
+
}
|
|
404
428
|
type SerializedWorld = {
|
|
405
429
|
version: number;
|
|
406
|
-
entityManager:
|
|
430
|
+
entityManager: SerializedEntityIdManager;
|
|
407
431
|
entities: SerializedEntity[];
|
|
408
432
|
componentEntities?: SerializedEntity[];
|
|
409
433
|
};
|
package/package.json
CHANGED
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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
|
132
|
+
};
|
|
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
|
|
141
142
|
};
|
|
142
|
-
|
|
143
|
-
return { type: "invalid" };
|
|
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
|
/**
|
|
@@ -627,10 +628,15 @@ var ComponentChangeset = class {
|
|
|
627
628
|
//#endregion
|
|
628
629
|
//#region src/commands/command-buffer.ts
|
|
629
630
|
/**
|
|
631
|
+
* Maximum number of command buffer execution iterations to prevent infinite loops
|
|
632
|
+
*/
|
|
633
|
+
const MAX_COMMAND_ITERATIONS = 100;
|
|
634
|
+
/**
|
|
630
635
|
* Command buffer for deferred structural changes
|
|
631
636
|
*/
|
|
632
637
|
var CommandBuffer = class {
|
|
633
638
|
commands = [];
|
|
639
|
+
swapBuffer = [];
|
|
634
640
|
executeEntityCommands;
|
|
635
641
|
/**
|
|
636
642
|
* Create a command buffer with an executor function
|
|
@@ -669,18 +675,19 @@ var CommandBuffer = class {
|
|
|
669
675
|
* Execute all commands and clear the buffer
|
|
670
676
|
*/
|
|
671
677
|
execute() {
|
|
672
|
-
const MAX_ITERATIONS = 100;
|
|
673
678
|
let iterations = 0;
|
|
674
679
|
while (this.commands.length > 0) {
|
|
675
|
-
if (iterations >=
|
|
680
|
+
if (iterations >= MAX_COMMAND_ITERATIONS) throw new Error("Command execution exceeded maximum iterations, possible infinite loop");
|
|
676
681
|
iterations++;
|
|
677
|
-
const currentCommands =
|
|
678
|
-
this.commands =
|
|
682
|
+
const currentCommands = this.commands;
|
|
683
|
+
this.commands = this.swapBuffer;
|
|
679
684
|
const entityCommands = /* @__PURE__ */ new Map();
|
|
680
685
|
for (const cmd of currentCommands) {
|
|
681
686
|
if (!entityCommands.has(cmd.entityId)) entityCommands.set(cmd.entityId, []);
|
|
682
687
|
entityCommands.get(cmd.entityId).push(cmd);
|
|
683
688
|
}
|
|
689
|
+
currentCommands.length = 0;
|
|
690
|
+
this.swapBuffer = currentCommands;
|
|
684
691
|
for (const [entityId, commands] of entityCommands) this.executeEntityCommands(entityId, commands);
|
|
685
692
|
}
|
|
686
693
|
}
|
|
@@ -721,8 +728,8 @@ function matchesComponentTypes(archetype, componentTypes) {
|
|
|
721
728
|
});
|
|
722
729
|
else if ((detailedType.type === "entity-relation" || detailedType.type === "component-relation") && detailedType.componentId !== void 0 && isDontFragmentComponent(detailedType.componentId)) {
|
|
723
730
|
const wildcardMarker = relation(detailedType.componentId, "*");
|
|
724
|
-
return archetype.
|
|
725
|
-
} else return archetype.
|
|
731
|
+
return archetype.componentTypeSet.has(wildcardMarker);
|
|
732
|
+
} else return archetype.componentTypeSet.has(type);
|
|
726
733
|
});
|
|
727
734
|
}
|
|
728
735
|
/**
|
|
@@ -735,7 +742,7 @@ function matchesFilter(archetype, filter) {
|
|
|
735
742
|
if (!isRelationId(archetypeType)) return false;
|
|
736
743
|
return getComponentIdFromRelationId(archetypeType) === detailedType.componentId;
|
|
737
744
|
});
|
|
738
|
-
else return !archetype.
|
|
745
|
+
else return !archetype.componentTypeSet.has(type);
|
|
739
746
|
});
|
|
740
747
|
}
|
|
741
748
|
|
|
@@ -750,6 +757,8 @@ var Query = class {
|
|
|
750
757
|
filter;
|
|
751
758
|
cachedArchetypes = [];
|
|
752
759
|
isDisposed = false;
|
|
760
|
+
/** Cache key assigned by World for O(1) releaseQuery lookup */
|
|
761
|
+
_cacheKey;
|
|
753
762
|
/** Cached wildcard component types for faster entity filtering */
|
|
754
763
|
wildcardTypes;
|
|
755
764
|
/** Cached specific dontFragment relation types that need entity-level filtering */
|
|
@@ -903,10 +912,10 @@ var Query = class {
|
|
|
903
912
|
* Get a value from cache or compute and cache it if not present
|
|
904
913
|
* @param cache The cache map
|
|
905
914
|
* @param key The cache key
|
|
906
|
-
* @param compute Function to compute the value if not cached
|
|
915
|
+
* @param compute Function to compute the value if not cached (may have side effects)
|
|
907
916
|
* @returns The cached or computed value
|
|
908
917
|
*/
|
|
909
|
-
function
|
|
918
|
+
function getOrCompute(cache, key, compute) {
|
|
910
919
|
let value = cache.get(key);
|
|
911
920
|
if (value === void 0) {
|
|
912
921
|
value = compute();
|
|
@@ -915,20 +924,15 @@ function getOrComputeCache(cache, key, compute) {
|
|
|
915
924
|
return value;
|
|
916
925
|
}
|
|
917
926
|
/**
|
|
918
|
-
*
|
|
919
|
-
* @
|
|
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
|
|
927
|
+
* Alias for getOrCompute - maintained for backwards compatibility
|
|
928
|
+
* @deprecated Use getOrCompute instead
|
|
923
929
|
*/
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
return value;
|
|
931
|
-
}
|
|
930
|
+
const getOrComputeCache = getOrCompute;
|
|
931
|
+
/**
|
|
932
|
+
* Alias for getOrCompute - maintained for backwards compatibility
|
|
933
|
+
* @deprecated Use getOrCompute instead
|
|
934
|
+
*/
|
|
935
|
+
const getOrCreateWithSideEffect = getOrCompute;
|
|
932
936
|
|
|
933
937
|
//#endregion
|
|
934
938
|
//#region src/core/types.ts
|
|
@@ -939,6 +943,18 @@ function isOptionalEntityId(type) {
|
|
|
939
943
|
//#endregion
|
|
940
944
|
//#region src/core/archetype-helpers.ts
|
|
941
945
|
/**
|
|
946
|
+
* Check if a components map has any wildcard relations matching a component ID
|
|
947
|
+
* @param components - Component entity's components map
|
|
948
|
+
* @param wildcardComponentId - The component ID to match
|
|
949
|
+
* @returns True if at least one matching relation exists
|
|
950
|
+
*/
|
|
951
|
+
function hasWildcardRelation(components, wildcardComponentId) {
|
|
952
|
+
for (const relId of components.keys()) if (isRelationId(relId)) {
|
|
953
|
+
if (getComponentIdFromRelationId(relId) === wildcardComponentId) return true;
|
|
954
|
+
}
|
|
955
|
+
return false;
|
|
956
|
+
}
|
|
957
|
+
/**
|
|
942
958
|
* Check if a detailed type represents a relation (entity or component)
|
|
943
959
|
*/
|
|
944
960
|
function isRelationType(detailedType) {
|
|
@@ -1036,6 +1052,10 @@ var Archetype = class {
|
|
|
1036
1052
|
*/
|
|
1037
1053
|
componentTypes;
|
|
1038
1054
|
/**
|
|
1055
|
+
* Set version of componentTypes for O(1) lookups in hot paths
|
|
1056
|
+
*/
|
|
1057
|
+
componentTypeSet;
|
|
1058
|
+
/**
|
|
1039
1059
|
* List of entities in this archetype
|
|
1040
1060
|
*/
|
|
1041
1061
|
entities = [];
|
|
@@ -1055,9 +1075,6 @@ var Archetype = class {
|
|
|
1055
1075
|
*/
|
|
1056
1076
|
dontFragmentRelations;
|
|
1057
1077
|
/**
|
|
1058
|
-
* Cache for pre-computed component data sources to avoid repeated calculations
|
|
1059
|
-
*/
|
|
1060
|
-
/**
|
|
1061
1078
|
* Multi-hooks that match this archetype
|
|
1062
1079
|
*/
|
|
1063
1080
|
matchingMultiHooks = /* @__PURE__ */ new Set();
|
|
@@ -1067,12 +1084,19 @@ var Archetype = class {
|
|
|
1067
1084
|
componentDataSourcesCache = /* @__PURE__ */ new Map();
|
|
1068
1085
|
constructor(componentTypes, dontFragmentRelations) {
|
|
1069
1086
|
this.componentTypes = [...componentTypes].sort((a, b) => a - b);
|
|
1087
|
+
this.componentTypeSet = new Set(this.componentTypes);
|
|
1070
1088
|
this.dontFragmentRelations = dontFragmentRelations;
|
|
1071
1089
|
for (const componentType of this.componentTypes) this.componentData.set(componentType, []);
|
|
1072
1090
|
}
|
|
1073
1091
|
get size() {
|
|
1074
1092
|
return this.entities.length;
|
|
1075
1093
|
}
|
|
1094
|
+
/**
|
|
1095
|
+
* Check if the given component types match this archetype
|
|
1096
|
+
* @param componentTypes - Component types to check (can be in any order)
|
|
1097
|
+
* @returns true if the types match this archetype's component set
|
|
1098
|
+
* @note This method handles unsorted input by internally sorting for comparison
|
|
1099
|
+
*/
|
|
1076
1100
|
matches(componentTypes) {
|
|
1077
1101
|
if (this.componentTypes.length !== componentTypes.length) return false;
|
|
1078
1102
|
const sortedTypes = [...componentTypes].sort((a, b) => a - b);
|
|
@@ -1092,7 +1116,7 @@ var Archetype = class {
|
|
|
1092
1116
|
addDontFragmentRelations(entityId, componentData) {
|
|
1093
1117
|
const dontFragmentData = /* @__PURE__ */ new Map();
|
|
1094
1118
|
for (const [componentType, data] of componentData) {
|
|
1095
|
-
if (this.
|
|
1119
|
+
if (this.componentTypeSet.has(componentType)) continue;
|
|
1096
1120
|
const detailedType = getDetailedIdType(componentType);
|
|
1097
1121
|
if (isRelationType(detailedType) && isDontFragmentComponent(detailedType.componentId)) dontFragmentData.set(componentType, data);
|
|
1098
1122
|
}
|
|
@@ -1176,7 +1200,7 @@ var Archetype = class {
|
|
|
1176
1200
|
return relations;
|
|
1177
1201
|
}
|
|
1178
1202
|
getRegularComponent(entityId, index, componentType) {
|
|
1179
|
-
if (this.
|
|
1203
|
+
if (this.componentTypeSet.has(componentType)) {
|
|
1180
1204
|
const data = this.getComponentData(componentType)[index];
|
|
1181
1205
|
if (data === MISSING_COMPONENT) throw new Error(`Component type ${componentType} not found for entity ${entityId}`);
|
|
1182
1206
|
return data;
|
|
@@ -1188,7 +1212,7 @@ var Archetype = class {
|
|
|
1188
1212
|
getOptional(entityId, componentType) {
|
|
1189
1213
|
const index = this.entityToIndex.get(entityId);
|
|
1190
1214
|
if (index === void 0) throw new Error(`Entity ${entityId} is not in this archetype`);
|
|
1191
|
-
if (this.
|
|
1215
|
+
if (this.componentTypeSet.has(componentType)) {
|
|
1192
1216
|
const data = this.getComponentData(componentType)[index];
|
|
1193
1217
|
if (data === MISSING_COMPONENT) return void 0;
|
|
1194
1218
|
return { value: data };
|
|
@@ -1376,8 +1400,8 @@ function decodeSerializedId(sid) {
|
|
|
1376
1400
|
//#endregion
|
|
1377
1401
|
//#region src/core/world-commands.ts
|
|
1378
1402
|
function processCommands(entityId, currentArchetype, commands, changeset, handleExclusiveRelation) {
|
|
1379
|
-
for (const command of commands) if (command.type === "set"
|
|
1380
|
-
else if (command.type === "delete"
|
|
1403
|
+
for (const command of commands) if (command.type === "set") processSetCommand(entityId, currentArchetype, command.componentType, command.component, changeset, handleExclusiveRelation);
|
|
1404
|
+
else if (command.type === "delete") processDeleteCommand(entityId, currentArchetype, command.componentType, changeset);
|
|
1381
1405
|
}
|
|
1382
1406
|
function processSetCommand(entityId, currentArchetype, componentType, component$1, changeset, handleExclusiveRelation) {
|
|
1383
1407
|
const componentId = getComponentIdFromRelationId(componentType);
|
|
@@ -1385,7 +1409,7 @@ function processSetCommand(entityId, currentArchetype, componentType, component$
|
|
|
1385
1409
|
handleExclusiveRelation(entityId, currentArchetype, componentId);
|
|
1386
1410
|
if (isDontFragmentComponent(componentId)) {
|
|
1387
1411
|
const wildcardMarker = relation(componentId, "*");
|
|
1388
|
-
if (!currentArchetype.
|
|
1412
|
+
if (!currentArchetype.componentTypeSet.has(wildcardMarker)) changeset.set(wildcardMarker, void 0);
|
|
1389
1413
|
}
|
|
1390
1414
|
}
|
|
1391
1415
|
changeset.set(componentType, component$1);
|
|
@@ -1405,7 +1429,7 @@ function removeMatchingRelations(entityId, archetype, baseComponentId, changeset
|
|
|
1405
1429
|
}
|
|
1406
1430
|
const entityData = archetype.getEntity(entityId);
|
|
1407
1431
|
if (entityData) for (const [componentType] of entityData) {
|
|
1408
|
-
if (archetype.
|
|
1432
|
+
if (archetype.componentTypeSet.has(componentType)) continue;
|
|
1409
1433
|
if (getComponentIdFromRelationId(componentType) === baseComponentId) changeset.delete(componentType);
|
|
1410
1434
|
}
|
|
1411
1435
|
}
|
|
@@ -1579,11 +1603,11 @@ function triggerMultiComponentHooks(ctx, entityId, addedComponents, removedCompo
|
|
|
1579
1603
|
}
|
|
1580
1604
|
function entityHasAllComponents(ctx, entityId, requiredComponents) {
|
|
1581
1605
|
return requiredComponents.every((c) => {
|
|
1582
|
-
if (isWildcardRelationId(c))
|
|
1583
|
-
const
|
|
1606
|
+
if (isWildcardRelationId(c)) {
|
|
1607
|
+
const wildcardResult = ctx.getOptional(entityId, c);
|
|
1608
|
+
if (!wildcardResult) return false;
|
|
1609
|
+
const wildcardData = wildcardResult.value;
|
|
1584
1610
|
return Array.isArray(wildcardData) && wildcardData.length > 0;
|
|
1585
|
-
} catch {
|
|
1586
|
-
return false;
|
|
1587
1611
|
}
|
|
1588
1612
|
return ctx.has(entityId, c);
|
|
1589
1613
|
});
|
|
@@ -1591,11 +1615,11 @@ function entityHasAllComponents(ctx, entityId, requiredComponents) {
|
|
|
1591
1615
|
function entityHadAllComponentsBefore(ctx, entityId, requiredComponents, removedComponents) {
|
|
1592
1616
|
return requiredComponents.every((c) => {
|
|
1593
1617
|
if (anyComponentMatches(removedComponents, c)) return true;
|
|
1594
|
-
if (isWildcardRelationId(c))
|
|
1595
|
-
const
|
|
1618
|
+
if (isWildcardRelationId(c)) {
|
|
1619
|
+
const wildcardResult = ctx.getOptional(entityId, c);
|
|
1620
|
+
if (!wildcardResult) return false;
|
|
1621
|
+
const wildcardData = wildcardResult.value;
|
|
1596
1622
|
return Array.isArray(wildcardData) && wildcardData.length > 0;
|
|
1597
|
-
} catch {
|
|
1598
|
-
return false;
|
|
1599
1623
|
}
|
|
1600
1624
|
return ctx.has(entityId, c);
|
|
1601
1625
|
});
|
|
@@ -1879,8 +1903,9 @@ var World = class {
|
|
|
1879
1903
|
destroyEntityImmediate(entityId) {
|
|
1880
1904
|
const queue = [entityId];
|
|
1881
1905
|
const visited = /* @__PURE__ */ new Set();
|
|
1882
|
-
|
|
1883
|
-
|
|
1906
|
+
let queueIndex = 0;
|
|
1907
|
+
while (queueIndex < queue.length) {
|
|
1908
|
+
const cur = queue[queueIndex++];
|
|
1884
1909
|
if (visited.has(cur)) continue;
|
|
1885
1910
|
visited.add(cur);
|
|
1886
1911
|
const archetype = this.entityToArchetype.get(cur);
|
|
@@ -1975,14 +2000,13 @@ var World = class {
|
|
|
1975
2000
|
if (componentId === void 0) return false;
|
|
1976
2001
|
const data = this.componentEntityComponents.get(entityId);
|
|
1977
2002
|
if (!data) return false;
|
|
1978
|
-
|
|
1979
|
-
return false;
|
|
2003
|
+
return hasWildcardRelation(data, componentId);
|
|
1980
2004
|
}
|
|
1981
2005
|
return this.componentEntityComponents.get(entityId)?.has(componentType) ?? false;
|
|
1982
2006
|
}
|
|
1983
2007
|
const archetype = this.entityToArchetype.get(entityId);
|
|
1984
2008
|
if (!archetype) return false;
|
|
1985
|
-
if (archetype.
|
|
2009
|
+
if (archetype.componentTypeSet.has(componentType)) return true;
|
|
1986
2010
|
if (isDontFragmentRelation(componentType)) return this.dontFragmentRelations.get(entityId)?.has(componentType) ?? false;
|
|
1987
2011
|
return false;
|
|
1988
2012
|
}
|
|
@@ -2006,8 +2030,8 @@ var World = class {
|
|
|
2006
2030
|
}
|
|
2007
2031
|
const archetype = this.entityToArchetype.get(entityId);
|
|
2008
2032
|
if (!archetype) throw new Error(`Entity ${entityId} does not exist`);
|
|
2009
|
-
if (componentType >= 0 || componentType %
|
|
2010
|
-
const inArchetype = archetype.
|
|
2033
|
+
if (componentType >= 0 || componentType % RELATION_SHIFT !== 0) {
|
|
2034
|
+
const inArchetype = archetype.componentTypeSet.has(componentType);
|
|
2011
2035
|
const hasDontFragment = isDontFragmentRelation(componentType);
|
|
2012
2036
|
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
2037
|
}
|
|
@@ -2175,6 +2199,7 @@ var World = class {
|
|
|
2175
2199
|
return cached.query;
|
|
2176
2200
|
}
|
|
2177
2201
|
const query = new Query(this, sortedTypes, filter);
|
|
2202
|
+
query._cacheKey = key;
|
|
2178
2203
|
this.queryCache.set(key, {
|
|
2179
2204
|
query,
|
|
2180
2205
|
refCount: 1
|
|
@@ -2240,14 +2265,15 @@ var World = class {
|
|
|
2240
2265
|
* world.releaseQuery(query); // Optional cleanup
|
|
2241
2266
|
*/
|
|
2242
2267
|
releaseQuery(query) {
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2268
|
+
const key = query._cacheKey;
|
|
2269
|
+
if (!key) return;
|
|
2270
|
+
const cached = this.queryCache.get(key);
|
|
2271
|
+
if (!cached || cached.query !== query) return;
|
|
2272
|
+
cached.refCount--;
|
|
2273
|
+
if (cached.refCount <= 0) {
|
|
2274
|
+
this.queryCache.delete(key);
|
|
2275
|
+
this._unregisterQuery(query);
|
|
2276
|
+
cached.query._disposeInternal();
|
|
2251
2277
|
}
|
|
2252
2278
|
}
|
|
2253
2279
|
/**
|
|
@@ -2279,8 +2305,16 @@ var World = class {
|
|
|
2279
2305
|
getArchetypesWithComponents(componentTypes) {
|
|
2280
2306
|
if (componentTypes.length === 0) return [...this.archetypes];
|
|
2281
2307
|
if (componentTypes.length === 1) return this.archetypesByComponent.get(componentTypes[0]) || [];
|
|
2282
|
-
const archetypeLists = componentTypes.map((type) => this.archetypesByComponent.get(type) || []);
|
|
2283
|
-
|
|
2308
|
+
const archetypeLists = componentTypes.map((type) => this.archetypesByComponent.get(type) || []).sort((a, b) => a.length - b.length);
|
|
2309
|
+
const shortest = archetypeLists[0];
|
|
2310
|
+
if (shortest.length === 0) return [];
|
|
2311
|
+
let result = new Set(shortest);
|
|
2312
|
+
for (let i = 1; i < archetypeLists.length; i++) {
|
|
2313
|
+
const listSet = new Set(archetypeLists[i]);
|
|
2314
|
+
for (const item of result) if (!listSet.has(item)) result.delete(item);
|
|
2315
|
+
if (result.size === 0) return [];
|
|
2316
|
+
}
|
|
2317
|
+
return Array.from(result);
|
|
2284
2318
|
}
|
|
2285
2319
|
query(componentTypes, includeComponents) {
|
|
2286
2320
|
const matchingArchetypes = this.getMatchingArchetypes(componentTypes);
|
|
@@ -2362,11 +2396,11 @@ var World = class {
|
|
|
2362
2396
|
for (const componentType of changeset.removes) if (isEntityRelation(componentType)) {
|
|
2363
2397
|
const targetId = getTargetIdFromRelationId(componentType);
|
|
2364
2398
|
untrackEntityReference(this.entityReferences, entityId, componentType, targetId);
|
|
2365
|
-
} else if (componentType >=
|
|
2399
|
+
} else if (componentType >= ENTITY_ID_START) untrackEntityReference(this.entityReferences, entityId, componentType, componentType);
|
|
2366
2400
|
for (const [componentType] of changeset.adds) if (isEntityRelation(componentType)) {
|
|
2367
2401
|
const targetId = getTargetIdFromRelationId(componentType);
|
|
2368
2402
|
trackEntityReference(this.entityReferences, entityId, componentType, targetId);
|
|
2369
|
-
} else if (componentType >=
|
|
2403
|
+
} else if (componentType >= ENTITY_ID_START) trackEntityReference(this.entityReferences, entityId, componentType, componentType);
|
|
2370
2404
|
}
|
|
2371
2405
|
ensureArchetype(componentTypes) {
|
|
2372
2406
|
const sortedTypes = filterRegularComponentTypes(componentTypes).sort((a, b) => a - b);
|
|
@@ -2395,7 +2429,7 @@ var World = class {
|
|
|
2395
2429
|
const componentId = getComponentIdFromRelationId(c);
|
|
2396
2430
|
return componentId !== void 0 && archetype.hasRelationWithComponentId(componentId);
|
|
2397
2431
|
}
|
|
2398
|
-
return archetype.
|
|
2432
|
+
return archetype.componentTypeSet.has(c) || isDontFragmentRelation(c);
|
|
2399
2433
|
});
|
|
2400
2434
|
}
|
|
2401
2435
|
archetypeReferencesEntity(archetype, entityId) {
|