@codehz/ecs 0.3.12 → 0.3.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.d.mts +4 -20
- package/index.mjs +280 -112
- package/index.mjs.map +1 -1
- package/package.json +1 -1
package/index.d.mts
CHANGED
|
@@ -50,16 +50,6 @@ declare function isRelationId<T>(id: EntityId<T>): id is RelationId<T>;
|
|
|
50
50
|
* Check if an ID is a wildcard relation id
|
|
51
51
|
*/
|
|
52
52
|
declare function isWildcardRelationId<T>(id: EntityId<T>): id is WildcardRelationId<T>;
|
|
53
|
-
/**
|
|
54
|
-
* Decode a relation ID into component and target IDs
|
|
55
|
-
* @param relationId The relation ID (must be negative)
|
|
56
|
-
* @returns Object with componentId, targetId, and relation type
|
|
57
|
-
*/
|
|
58
|
-
declare function decodeRelationId(relationId: RelationId<any>): {
|
|
59
|
-
componentId: ComponentId<any>;
|
|
60
|
-
targetId: EntityId<any>;
|
|
61
|
-
type: "entity" | "component" | "wildcard";
|
|
62
|
-
};
|
|
63
53
|
/**
|
|
64
54
|
* Component options that define intrinsic properties
|
|
65
55
|
*/
|
|
@@ -649,9 +639,6 @@ declare class World<UpdateParams extends any[] = []> {
|
|
|
649
639
|
* Remove all relations with the same base component (for exclusive relations)
|
|
650
640
|
*/
|
|
651
641
|
private removeExclusiveRelations;
|
|
652
|
-
/**
|
|
653
|
-
* Check if a component type is a relation with the given base component
|
|
654
|
-
*/
|
|
655
642
|
private isRelationWithComponent;
|
|
656
643
|
/**
|
|
657
644
|
* Process a delete command, handling wildcard relations
|
|
@@ -680,13 +667,10 @@ declare class World<UpdateParams extends any[] = []> {
|
|
|
680
667
|
*/
|
|
681
668
|
private updateEntityInSameArchetype;
|
|
682
669
|
/**
|
|
683
|
-
*
|
|
684
|
-
|
|
685
|
-
private hasDontFragmentChanges;
|
|
686
|
-
/**
|
|
687
|
-
* Remove and re-add entity with updated components (for dontFragment changes)
|
|
670
|
+
* Apply dontFragment relation changes directly to World's storage
|
|
671
|
+
* This is much more efficient than the removeEntity + addEntity approach
|
|
688
672
|
*/
|
|
689
|
-
private
|
|
673
|
+
private applyDontFragmentChanges;
|
|
690
674
|
/**
|
|
691
675
|
* Update entity reference tracking based on changeset
|
|
692
676
|
*/
|
|
@@ -785,5 +769,5 @@ type SerializedComponent = {
|
|
|
785
769
|
value: any;
|
|
786
770
|
};
|
|
787
771
|
//#endregion
|
|
788
|
-
export { type ComponentId, type ComponentOptions, type ComponentRelationId, type ComponentTuple, type ComponentType, type EntityId, type EntityRelationId, type LifecycleHook, Query, type RelationId, type SerializedWorld, type System, type WildcardRelationId, World, component,
|
|
772
|
+
export { type ComponentId, type ComponentOptions, type ComponentRelationId, type ComponentTuple, type ComponentType, type EntityId, type EntityRelationId, type LifecycleHook, Query, type RelationId, type SerializedWorld, type System, type WildcardRelationId, World, component, getComponentIdByName, getComponentNameById, isComponentId, isEntityId, isRelationId, isWildcardRelationId, relation };
|
|
789
773
|
//# sourceMappingURL=index.d.mts.map
|
package/index.mjs
CHANGED
|
@@ -1,3 +1,100 @@
|
|
|
1
|
+
//#region src/bit-set.ts
|
|
2
|
+
var BitSet = class {
|
|
3
|
+
data;
|
|
4
|
+
_length;
|
|
5
|
+
constructor(length) {
|
|
6
|
+
this._length = length;
|
|
7
|
+
const numWords = Math.ceil(length / 32);
|
|
8
|
+
this.data = new Uint32Array(numWords);
|
|
9
|
+
}
|
|
10
|
+
get length() {
|
|
11
|
+
return this._length;
|
|
12
|
+
}
|
|
13
|
+
has(index) {
|
|
14
|
+
if (index < 0 || index >= this._length) return false;
|
|
15
|
+
const word = index >>> 5;
|
|
16
|
+
const bit = index & 31;
|
|
17
|
+
return (this.data[word] >>> bit & 1) !== 0;
|
|
18
|
+
}
|
|
19
|
+
set(index) {
|
|
20
|
+
if (index < 0 || index >= this._length) return;
|
|
21
|
+
const word = index >>> 5;
|
|
22
|
+
const bit = index & 31;
|
|
23
|
+
this.data[word] |= 1 << bit;
|
|
24
|
+
}
|
|
25
|
+
clear(index) {
|
|
26
|
+
if (index < 0 || index >= this._length) return;
|
|
27
|
+
const word = index >>> 5;
|
|
28
|
+
const bit = index & 31;
|
|
29
|
+
this.data[word] &= ~(1 << bit);
|
|
30
|
+
}
|
|
31
|
+
setRange(lo, hi) {
|
|
32
|
+
if (lo > hi) return;
|
|
33
|
+
if (lo < 0) lo = 0;
|
|
34
|
+
if (hi >= this._length) hi = this._length - 1;
|
|
35
|
+
const firstWord = lo >>> 5;
|
|
36
|
+
const lastWord = hi >>> 5;
|
|
37
|
+
const loBit = lo & 31;
|
|
38
|
+
const hiBit = hi & 31;
|
|
39
|
+
const maskFor = (a, b) => {
|
|
40
|
+
const width = b - a + 1;
|
|
41
|
+
if (width <= 0) return 0;
|
|
42
|
+
if (width >= 32) return 4294967295;
|
|
43
|
+
return (1 << width) - 1 << a >>> 0;
|
|
44
|
+
};
|
|
45
|
+
if (firstWord === lastWord) {
|
|
46
|
+
const mask = maskFor(loBit, hiBit);
|
|
47
|
+
this.data[firstWord] = (this.data[firstWord] | mask) >>> 0;
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const firstMask = maskFor(loBit, 31);
|
|
51
|
+
this.data[firstWord] = (this.data[firstWord] | firstMask) >>> 0;
|
|
52
|
+
for (let w = firstWord + 1; w <= lastWord - 1; w++) this.data[w] = 4294967295;
|
|
53
|
+
const lastMask = maskFor(0, hiBit);
|
|
54
|
+
this.data[lastWord] = (this.data[lastWord] | lastMask) >>> 0;
|
|
55
|
+
}
|
|
56
|
+
anyClearInRange(lo, hi) {
|
|
57
|
+
if (lo > hi) return false;
|
|
58
|
+
if (lo < 0) lo = 0;
|
|
59
|
+
if (hi >= this._length) hi = this._length - 1;
|
|
60
|
+
const firstWord = lo >>> 5;
|
|
61
|
+
const lastWord = hi >>> 5;
|
|
62
|
+
const loBit = lo & 31;
|
|
63
|
+
const hiBit = hi & 31;
|
|
64
|
+
const maskFor = (a, b) => {
|
|
65
|
+
const width = b - a + 1;
|
|
66
|
+
if (width <= 0) return 0;
|
|
67
|
+
if (width >= 32) return 4294967295;
|
|
68
|
+
return (1 << width) - 1 << a >>> 0;
|
|
69
|
+
};
|
|
70
|
+
if (firstWord === lastWord) {
|
|
71
|
+
const mask = maskFor(loBit, hiBit);
|
|
72
|
+
return (this.data[firstWord] & mask) >>> 0 !== mask >>> 0;
|
|
73
|
+
}
|
|
74
|
+
const firstMask = maskFor(loBit, 31);
|
|
75
|
+
if ((this.data[firstWord] & firstMask) >>> 0 !== firstMask >>> 0) return true;
|
|
76
|
+
for (let w = firstWord + 1; w <= lastWord - 1; w++) if (this.data[w] !== 4294967295) return true;
|
|
77
|
+
const lastMask = maskFor(0, hiBit);
|
|
78
|
+
if ((this.data[lastWord] & lastMask) >>> 0 !== lastMask >>> 0) return true;
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
reset() {
|
|
82
|
+
this.data.fill(0);
|
|
83
|
+
}
|
|
84
|
+
*[Symbol.iterator]() {
|
|
85
|
+
for (let wordIndex = 0; wordIndex < this.data.length; wordIndex++) {
|
|
86
|
+
let word = this.data[wordIndex];
|
|
87
|
+
if (word === 0) continue;
|
|
88
|
+
const baseIndex = wordIndex * 32;
|
|
89
|
+
for (let bit = 0; bit < 32 && baseIndex + bit < this._length; bit++) {
|
|
90
|
+
if (word & 1) yield baseIndex + bit;
|
|
91
|
+
word >>>= 1;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
//#endregion
|
|
1
98
|
//#region src/entity.ts
|
|
2
99
|
const COMPONENT_ID_MAX = 1023;
|
|
3
100
|
const ENTITY_ID_START = 1024;
|
|
@@ -6,6 +103,25 @@ const ENTITY_ID_START = 1024;
|
|
|
6
103
|
*/
|
|
7
104
|
const RELATION_SHIFT = 2 ** 42;
|
|
8
105
|
const WILDCARD_TARGET_ID = 0;
|
|
106
|
+
/**
|
|
107
|
+
* Internal function to decode a relation ID into raw component and target IDs
|
|
108
|
+
* @param id The EntityId to decode
|
|
109
|
+
* @returns Object with componentId and targetId, or null if not a relation
|
|
110
|
+
*/
|
|
111
|
+
function decodeRelationRaw(id) {
|
|
112
|
+
if (id >= 0) return null;
|
|
113
|
+
const absId = -id;
|
|
114
|
+
return {
|
|
115
|
+
componentId: Math.floor(absId / RELATION_SHIFT),
|
|
116
|
+
targetId: absId % RELATION_SHIFT
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Check if a component ID is valid (1-1023)
|
|
121
|
+
*/
|
|
122
|
+
function isValidComponentId(componentId) {
|
|
123
|
+
return componentId >= 1 && componentId <= COMPONENT_ID_MAX;
|
|
124
|
+
}
|
|
9
125
|
function relation(componentId, targetId) {
|
|
10
126
|
if (!isComponentId(componentId)) throw new Error("First argument must be a valid component ID");
|
|
11
127
|
let actualTargetId;
|
|
@@ -38,8 +154,8 @@ function isRelationId(id) {
|
|
|
38
154
|
* Check if an ID is a wildcard relation id
|
|
39
155
|
*/
|
|
40
156
|
function isWildcardRelationId(id) {
|
|
41
|
-
|
|
42
|
-
return
|
|
157
|
+
const decoded = decodeRelationRaw(id);
|
|
158
|
+
return decoded !== null && decoded.targetId === WILDCARD_TARGET_ID;
|
|
43
159
|
}
|
|
44
160
|
/**
|
|
45
161
|
* Decode a relation ID into component and target IDs
|
|
@@ -47,10 +163,12 @@ function isWildcardRelationId(id) {
|
|
|
47
163
|
* @returns Object with componentId, targetId, and relation type
|
|
48
164
|
*/
|
|
49
165
|
function decodeRelationId(relationId) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const componentId
|
|
53
|
-
|
|
166
|
+
const decoded = decodeRelationRaw(relationId);
|
|
167
|
+
if (decoded === null) throw new Error("ID is not a relation ID");
|
|
168
|
+
const { componentId: rawComponentId, targetId: rawTargetId } = decoded;
|
|
169
|
+
if (!isValidComponentId(rawComponentId)) throw new Error("Invalid component ID in relation");
|
|
170
|
+
const componentId = rawComponentId;
|
|
171
|
+
const targetId = rawTargetId;
|
|
54
172
|
if (targetId === WILDCARD_TARGET_ID) return {
|
|
55
173
|
componentId,
|
|
56
174
|
targetId,
|
|
@@ -76,7 +194,7 @@ function getIdType(id) {
|
|
|
76
194
|
if (isEntityId(id)) return "entity";
|
|
77
195
|
if (isRelationId(id)) try {
|
|
78
196
|
const decoded = decodeRelationId(id);
|
|
79
|
-
if (
|
|
197
|
+
if (decoded.type !== "wildcard" && !isEntityId(decoded.targetId) && !isComponentId(decoded.targetId)) return "invalid";
|
|
80
198
|
switch (decoded.type) {
|
|
81
199
|
case "entity": return "entity-relation";
|
|
82
200
|
case "component": return "component-relation";
|
|
@@ -97,7 +215,7 @@ function getDetailedIdType(id) {
|
|
|
97
215
|
if (isEntityId(id)) return { type: "entity" };
|
|
98
216
|
if (isRelationId(id)) try {
|
|
99
217
|
const decoded = decodeRelationId(id);
|
|
100
|
-
if (
|
|
218
|
+
if (decoded.type !== "wildcard" && !isEntityId(decoded.targetId) && !isComponentId(decoded.targetId)) return { type: "invalid" };
|
|
101
219
|
let type;
|
|
102
220
|
switch (decoded.type) {
|
|
103
221
|
case "entity":
|
|
@@ -213,9 +331,11 @@ var ComponentIdAllocator = class {
|
|
|
213
331
|
}
|
|
214
332
|
};
|
|
215
333
|
const globalComponentIdAllocator = new ComponentIdAllocator();
|
|
216
|
-
const ComponentNames = /* @__PURE__ */ new Map();
|
|
217
334
|
const ComponentIdForNames = /* @__PURE__ */ new Map();
|
|
218
|
-
const
|
|
335
|
+
const componentNames = new Array(COMPONENT_ID_MAX + 1);
|
|
336
|
+
const exclusiveFlags = new BitSet(COMPONENT_ID_MAX + 1);
|
|
337
|
+
const cascadeDeleteFlags = new BitSet(COMPONENT_ID_MAX + 1);
|
|
338
|
+
const dontFragmentFlags = new BitSet(COMPONENT_ID_MAX + 1);
|
|
219
339
|
/**
|
|
220
340
|
* Allocate a new component ID from the global allocator.
|
|
221
341
|
* @param nameOrOptions Optional name for the component (for serialization/debugging) or options object
|
|
@@ -241,10 +361,14 @@ function component(nameOrOptions) {
|
|
|
241
361
|
}
|
|
242
362
|
if (name) {
|
|
243
363
|
if (ComponentIdForNames.has(name)) throw new Error(`Component name "${name}" is already registered`);
|
|
244
|
-
|
|
364
|
+
componentNames[id] = name;
|
|
245
365
|
ComponentIdForNames.set(name, id);
|
|
246
366
|
}
|
|
247
|
-
if (options)
|
|
367
|
+
if (options) {
|
|
368
|
+
if (options.exclusive) exclusiveFlags.set(id);
|
|
369
|
+
if (options.cascadeDelete) cascadeDeleteFlags.set(id);
|
|
370
|
+
if (options.dontFragment) dontFragmentFlags.set(id);
|
|
371
|
+
}
|
|
248
372
|
return id;
|
|
249
373
|
}
|
|
250
374
|
/**
|
|
@@ -260,7 +384,7 @@ function getComponentIdByName(name) {
|
|
|
260
384
|
* @returns The component name if found, undefined otherwise
|
|
261
385
|
*/
|
|
262
386
|
function getComponentNameById(id) {
|
|
263
|
-
return
|
|
387
|
+
return componentNames[id];
|
|
264
388
|
}
|
|
265
389
|
/**
|
|
266
390
|
* Check if a component is marked as exclusive
|
|
@@ -268,15 +392,7 @@ function getComponentNameById(id) {
|
|
|
268
392
|
* @returns true if the component is exclusive, false otherwise
|
|
269
393
|
*/
|
|
270
394
|
function isExclusiveComponent(id) {
|
|
271
|
-
return
|
|
272
|
-
}
|
|
273
|
-
/**
|
|
274
|
-
* Check if a component is marked as cascade delete
|
|
275
|
-
* @param id The component ID
|
|
276
|
-
* @returns true if the component is cascade delete, false otherwise
|
|
277
|
-
*/
|
|
278
|
-
function isCascadeDeleteComponent(id) {
|
|
279
|
-
return ComponentOptions.get(id)?.cascadeDelete ?? false;
|
|
395
|
+
return exclusiveFlags.has(id);
|
|
280
396
|
}
|
|
281
397
|
/**
|
|
282
398
|
* Check if a component is marked as dontFragment
|
|
@@ -284,7 +400,71 @@ function isCascadeDeleteComponent(id) {
|
|
|
284
400
|
* @returns true if the component is dontFragment, false otherwise
|
|
285
401
|
*/
|
|
286
402
|
function isDontFragmentComponent(id) {
|
|
287
|
-
return
|
|
403
|
+
return dontFragmentFlags.has(id);
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Generic function to check relation flags with specific target conditions
|
|
407
|
+
* @param id The entity/relation ID to check
|
|
408
|
+
* @param flagBitSet The bitset for the flag
|
|
409
|
+
* @param targetCondition Function to check target ID condition
|
|
410
|
+
* @returns true if the condition is met, false otherwise
|
|
411
|
+
*/
|
|
412
|
+
function checkRelationFlag(id, flagBitSet, targetCondition) {
|
|
413
|
+
const decoded = decodeRelationRaw(id);
|
|
414
|
+
if (decoded === null) return false;
|
|
415
|
+
const { componentId, targetId } = decoded;
|
|
416
|
+
return isValidComponentId(componentId) && targetCondition(targetId) && flagBitSet.has(componentId);
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Check if a relation ID is a dontFragment relation (entity-relation or component-relation with dontFragment component)
|
|
420
|
+
* This is an optimized function that avoids the overhead of getDetailedIdType
|
|
421
|
+
* @param id The entity/relation ID to check
|
|
422
|
+
* @returns true if this is a dontFragment relation, false otherwise
|
|
423
|
+
*/
|
|
424
|
+
function isDontFragmentRelation(id) {
|
|
425
|
+
return checkRelationFlag(id, dontFragmentFlags, (targetId) => targetId !== WILDCARD_TARGET_ID);
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Check if an ID is a wildcard relation with dontFragment component
|
|
429
|
+
* This is an optimized function for filtering archetype component types
|
|
430
|
+
* @param id The entity/relation ID to check
|
|
431
|
+
* @returns true if this is a wildcard relation with dontFragment component, false otherwise
|
|
432
|
+
*/
|
|
433
|
+
function isDontFragmentWildcard(id) {
|
|
434
|
+
return checkRelationFlag(id, dontFragmentFlags, (targetId) => targetId === WILDCARD_TARGET_ID);
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Check if a relation ID is a cascade delete entity-relation
|
|
438
|
+
* This is an optimized function that avoids the overhead of getDetailedIdType
|
|
439
|
+
* Note: Cascade delete only applies to entity-relations (not component-relations or wildcards)
|
|
440
|
+
* @param id The entity/relation ID to check
|
|
441
|
+
* @returns true if this is an entity-relation with cascade delete, false otherwise
|
|
442
|
+
*/
|
|
443
|
+
function isCascadeDeleteRelation(id) {
|
|
444
|
+
return checkRelationFlag(id, cascadeDeleteFlags, (targetId) => targetId !== WILDCARD_TARGET_ID && targetId >= ENTITY_ID_START);
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Get the componentId from a relation ID without fully decoding the relation.
|
|
448
|
+
* Returns undefined for non-relation IDs or invalid component IDs.
|
|
449
|
+
*/
|
|
450
|
+
function getComponentIdFromRelationId(id) {
|
|
451
|
+
const decoded = decodeRelationRaw(id);
|
|
452
|
+
if (decoded === null || !isValidComponentId(decoded.componentId)) return void 0;
|
|
453
|
+
return decoded.componentId;
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Get the targetId from a relation ID without fully decoding the relation.
|
|
457
|
+
* Returns undefined for non-relation IDs.
|
|
458
|
+
*/
|
|
459
|
+
function getTargetIdFromRelationId(id) {
|
|
460
|
+
return decodeRelationRaw(id)?.targetId;
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Check if an ID is an entity-relation (relation targeting an entity, not a component or wildcard)
|
|
464
|
+
*/
|
|
465
|
+
function isEntityRelation(id) {
|
|
466
|
+
const decoded = decodeRelationRaw(id);
|
|
467
|
+
return decoded !== null && decoded.targetId >= ENTITY_ID_START;
|
|
288
468
|
}
|
|
289
469
|
|
|
290
470
|
//#endregion
|
|
@@ -500,7 +680,7 @@ var Archetype = class {
|
|
|
500
680
|
const index = this.entityToIndex.get(entityId);
|
|
501
681
|
if (index === void 0) throw new Error(`Entity ${entityId} is not in this archetype`);
|
|
502
682
|
if (isWildcardRelationId(componentType)) {
|
|
503
|
-
const componentId =
|
|
683
|
+
const componentId = getComponentIdFromRelationId(componentType);
|
|
504
684
|
const relations = [];
|
|
505
685
|
for (const relType of this.componentTypes) {
|
|
506
686
|
const relDetailed = getDetailedIdType(relType);
|
|
@@ -642,10 +822,10 @@ var Archetype = class {
|
|
|
642
822
|
const relations = [];
|
|
643
823
|
for (const relType of matchingRelations) {
|
|
644
824
|
const data = this.getComponentData(relType)[entityIndex];
|
|
645
|
-
const
|
|
646
|
-
relations.push([
|
|
825
|
+
const targetId = getTargetIdFromRelationId(relType);
|
|
826
|
+
relations.push([targetId, data === MISSING_COMPONENT ? void 0 : data]);
|
|
647
827
|
}
|
|
648
|
-
const targetComponentId =
|
|
828
|
+
const targetComponentId = getComponentIdFromRelationId(wildcardRelationType);
|
|
649
829
|
const dontFragmentData = this.dontFragmentRelations.get(entityId);
|
|
650
830
|
if (dontFragmentData) for (const [relType, data] of dontFragmentData) {
|
|
651
831
|
const relDetailed = getDetailedIdType(relType);
|
|
@@ -653,8 +833,8 @@ var Archetype = class {
|
|
|
653
833
|
}
|
|
654
834
|
if (relations.length === 0) {
|
|
655
835
|
if (!optional) {
|
|
656
|
-
const
|
|
657
|
-
throw new Error(`No matching relations found for mandatory wildcard relation component ${
|
|
836
|
+
const componentId = getComponentIdFromRelationId(wildcardRelationType);
|
|
837
|
+
throw new Error(`No matching relations found for mandatory wildcard relation component ${componentId} on entity ${entityId}`);
|
|
658
838
|
}
|
|
659
839
|
return;
|
|
660
840
|
}
|
|
@@ -992,7 +1172,7 @@ function matchesComponentTypes(archetype, componentTypes) {
|
|
|
992
1172
|
const detailedType = getDetailedIdType(type);
|
|
993
1173
|
if (detailedType.type === "wildcard-relation") return archetype.componentTypes.some((archetypeType) => {
|
|
994
1174
|
if (!isRelationId(archetypeType)) return false;
|
|
995
|
-
return
|
|
1175
|
+
return getComponentIdFromRelationId(archetypeType) === detailedType.componentId;
|
|
996
1176
|
});
|
|
997
1177
|
else return archetype.componentTypes.includes(type);
|
|
998
1178
|
});
|
|
@@ -1005,7 +1185,7 @@ function matchesFilter(archetype, filter) {
|
|
|
1005
1185
|
const detailedType = getDetailedIdType(type);
|
|
1006
1186
|
if (detailedType.type === "wildcard-relation") return !archetype.componentTypes.some((archetypeType) => {
|
|
1007
1187
|
if (!isRelationId(archetypeType)) return false;
|
|
1008
|
-
return
|
|
1188
|
+
return getComponentIdFromRelationId(archetypeType) === detailedType.componentId;
|
|
1009
1189
|
});
|
|
1010
1190
|
else return !archetype.componentTypes.includes(type);
|
|
1011
1191
|
});
|
|
@@ -1333,8 +1513,7 @@ var World = class {
|
|
|
1333
1513
|
const componentReferences = Array.from(this.getEntityReferences(cur));
|
|
1334
1514
|
for (const [sourceEntityId, componentType] of componentReferences) {
|
|
1335
1515
|
if (!this.entityToArchetype.get(sourceEntityId)) continue;
|
|
1336
|
-
|
|
1337
|
-
if (detailedType.type === "entity-relation" && isCascadeDeleteComponent(detailedType.componentId)) {
|
|
1516
|
+
if (isCascadeDeleteRelation(componentType)) {
|
|
1338
1517
|
if (!visited.has(sourceEntityId)) queue.push(sourceEntityId);
|
|
1339
1518
|
continue;
|
|
1340
1519
|
}
|
|
@@ -1381,18 +1560,16 @@ var World = class {
|
|
|
1381
1560
|
const archetype = this.entityToArchetype.get(entityId);
|
|
1382
1561
|
if (!archetype) return false;
|
|
1383
1562
|
if (archetype.componentTypes.includes(componentType)) return true;
|
|
1384
|
-
|
|
1385
|
-
if ((detailedType.type === "entity-relation" || detailedType.type === "component-relation") && isDontFragmentComponent(detailedType.componentId)) return this.dontFragmentRelations.get(entityId)?.has(componentType) ?? false;
|
|
1563
|
+
if (isDontFragmentRelation(componentType)) return this.dontFragmentRelations.get(entityId)?.has(componentType) ?? false;
|
|
1386
1564
|
return false;
|
|
1387
1565
|
}
|
|
1388
1566
|
get(entityId, componentType) {
|
|
1389
1567
|
const archetype = this.entityToArchetype.get(entityId);
|
|
1390
1568
|
if (!archetype) throw new Error(`Entity ${entityId} does not exist`);
|
|
1391
|
-
|
|
1392
|
-
if (detailedType.type !== "wildcard-relation") {
|
|
1569
|
+
if (componentType >= 0 || componentType % 2 ** 42 !== 0) {
|
|
1393
1570
|
const inArchetype = archetype.componentTypes.includes(componentType);
|
|
1394
|
-
const
|
|
1395
|
-
if (!(inArchetype ||
|
|
1571
|
+
const hasDontFragment = isDontFragmentRelation(componentType);
|
|
1572
|
+
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().`);
|
|
1396
1573
|
}
|
|
1397
1574
|
return archetype.get(entityId, componentType);
|
|
1398
1575
|
}
|
|
@@ -1499,14 +1676,13 @@ var World = class {
|
|
|
1499
1676
|
if (componentTypes.length === 0) return [...this.archetypes];
|
|
1500
1677
|
const regularComponents = [];
|
|
1501
1678
|
const wildcardRelations = [];
|
|
1502
|
-
for (const componentType of componentTypes) {
|
|
1503
|
-
const
|
|
1504
|
-
if (
|
|
1505
|
-
componentId
|
|
1679
|
+
for (const componentType of componentTypes) if (isWildcardRelationId(componentType)) {
|
|
1680
|
+
const componentId = getComponentIdFromRelationId(componentType);
|
|
1681
|
+
if (componentId !== void 0) wildcardRelations.push({
|
|
1682
|
+
componentId,
|
|
1506
1683
|
relationId: componentType
|
|
1507
1684
|
});
|
|
1508
|
-
|
|
1509
|
-
}
|
|
1685
|
+
} else regularComponents.push(componentType);
|
|
1510
1686
|
let matchingArchetypes = [];
|
|
1511
1687
|
if (regularComponents.length > 0) {
|
|
1512
1688
|
const sortedRegularTypes = [...regularComponents].sort((a, b) => a - b);
|
|
@@ -1579,11 +1755,13 @@ var World = class {
|
|
|
1579
1755
|
* Process a set command, handling exclusive relations
|
|
1580
1756
|
*/
|
|
1581
1757
|
processSetCommand(entityId, currentArchetype, componentType, component$1, changeset) {
|
|
1582
|
-
const
|
|
1583
|
-
if (
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1758
|
+
const componentId = getComponentIdFromRelationId(componentType);
|
|
1759
|
+
if (componentId !== void 0) {
|
|
1760
|
+
if (componentId !== void 0 && isExclusiveComponent(componentId)) this.removeExclusiveRelations(entityId, currentArchetype, componentId, changeset);
|
|
1761
|
+
if (componentId !== void 0 && isDontFragmentComponent(componentId)) {
|
|
1762
|
+
const wildcardMarker = relation(componentId, "*");
|
|
1763
|
+
if (!currentArchetype.componentTypes.includes(wildcardMarker)) changeset.set(wildcardMarker, void 0);
|
|
1764
|
+
}
|
|
1587
1765
|
}
|
|
1588
1766
|
changeset.set(componentType, component$1);
|
|
1589
1767
|
}
|
|
@@ -1598,30 +1776,26 @@ var World = class {
|
|
|
1598
1776
|
if (this.isRelationWithComponent(componentType, baseComponentId)) changeset.delete(componentType);
|
|
1599
1777
|
}
|
|
1600
1778
|
}
|
|
1601
|
-
/**
|
|
1602
|
-
* Check if a component type is a relation with the given base component
|
|
1603
|
-
*/
|
|
1604
1779
|
isRelationWithComponent(componentType, baseComponentId) {
|
|
1605
|
-
|
|
1606
|
-
return (detailedType.type === "entity-relation" || detailedType.type === "component-relation") && detailedType.componentId === baseComponentId;
|
|
1780
|
+
return getComponentIdFromRelationId(componentType) === baseComponentId;
|
|
1607
1781
|
}
|
|
1608
1782
|
/**
|
|
1609
1783
|
* Process a delete command, handling wildcard relations
|
|
1610
1784
|
*/
|
|
1611
1785
|
processDeleteCommand(entityId, currentArchetype, componentType, changeset) {
|
|
1612
|
-
const
|
|
1613
|
-
if (
|
|
1786
|
+
const componentId = getComponentIdFromRelationId(componentType);
|
|
1787
|
+
if (isWildcardRelationId(componentType) && componentId !== void 0) this.removeWildcardRelations(entityId, currentArchetype, componentId, changeset);
|
|
1614
1788
|
else {
|
|
1615
1789
|
changeset.delete(componentType);
|
|
1616
|
-
if (
|
|
1617
|
-
const wildcardMarker = relation(
|
|
1790
|
+
if (componentId !== void 0 && isDontFragmentComponent(componentId)) {
|
|
1791
|
+
const wildcardMarker = relation(componentId, "*");
|
|
1618
1792
|
const entityData = currentArchetype.getEntity(entityId);
|
|
1619
1793
|
let hasOtherRelations = false;
|
|
1620
1794
|
if (entityData) for (const [otherComponentType] of entityData) {
|
|
1621
1795
|
if (otherComponentType === componentType) continue;
|
|
1796
|
+
if (otherComponentType === wildcardMarker) continue;
|
|
1622
1797
|
if (changeset.removes.has(otherComponentType)) continue;
|
|
1623
|
-
|
|
1624
|
-
if ((otherDetailedType.type === "entity-relation" || otherDetailedType.type === "component-relation") && otherDetailedType.componentId === detailedType.componentId) {
|
|
1798
|
+
if (getComponentIdFromRelationId(otherComponentType) === componentId) {
|
|
1625
1799
|
hasOtherRelations = true;
|
|
1626
1800
|
break;
|
|
1627
1801
|
}
|
|
@@ -1653,16 +1827,16 @@ var World = class {
|
|
|
1653
1827
|
const sourceArchetype = this.entityToArchetype.get(entityId);
|
|
1654
1828
|
if (!sourceArchetype) return;
|
|
1655
1829
|
const changeset = new ComponentChangeset();
|
|
1656
|
-
const
|
|
1830
|
+
const componentId = getComponentIdFromRelationId(componentType);
|
|
1657
1831
|
changeset.delete(componentType);
|
|
1658
|
-
if (
|
|
1659
|
-
const wildcardMarker = relation(
|
|
1832
|
+
if (componentId !== void 0 && isDontFragmentComponent(componentId)) {
|
|
1833
|
+
const wildcardMarker = relation(componentId, "*");
|
|
1660
1834
|
const entityData = sourceArchetype.getEntity(entityId);
|
|
1661
1835
|
let hasOtherRelations = false;
|
|
1662
1836
|
if (entityData) for (const [otherComponentType] of entityData) {
|
|
1663
1837
|
if (otherComponentType === componentType) continue;
|
|
1664
|
-
|
|
1665
|
-
if ((
|
|
1838
|
+
if (otherComponentType === wildcardMarker) continue;
|
|
1839
|
+
if (getComponentIdFromRelationId(otherComponentType) === componentId) {
|
|
1666
1840
|
hasOtherRelations = true;
|
|
1667
1841
|
break;
|
|
1668
1842
|
}
|
|
@@ -1706,53 +1880,48 @@ var World = class {
|
|
|
1706
1880
|
* Update entity in same archetype (no archetype change needed)
|
|
1707
1881
|
*/
|
|
1708
1882
|
updateEntityInSameArchetype(entityId, currentArchetype, changeset, removedComponents) {
|
|
1709
|
-
|
|
1710
|
-
const
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
if ((detailedType.type === "entity-relation" || detailedType.type === "component-relation") && isDontFragmentComponent(detailedType.componentId)) removedComponents.set(componentType, currentComponents.get(componentType));
|
|
1883
|
+
this.applyDontFragmentChanges(entityId, changeset, removedComponents);
|
|
1884
|
+
for (const [componentType, component$1] of changeset.adds) {
|
|
1885
|
+
if (isDontFragmentRelation(componentType)) continue;
|
|
1886
|
+
currentArchetype.set(entityId, componentType, component$1);
|
|
1714
1887
|
}
|
|
1715
|
-
if (hasDontFragmentChanges) this.readdEntityWithUpdatedComponents(entityId, currentArchetype, currentComponents, changeset);
|
|
1716
|
-
else for (const [componentType, component$1] of changeset.adds) currentArchetype.set(entityId, componentType, component$1);
|
|
1717
1888
|
}
|
|
1718
1889
|
/**
|
|
1719
|
-
*
|
|
1890
|
+
* Apply dontFragment relation changes directly to World's storage
|
|
1891
|
+
* This is much more efficient than the removeEntity + addEntity approach
|
|
1720
1892
|
*/
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
if (
|
|
1893
|
+
applyDontFragmentChanges(entityId, changeset, removedComponents) {
|
|
1894
|
+
let entityRelations = this.dontFragmentRelations.get(entityId);
|
|
1895
|
+
for (const componentType of changeset.removes) if (isDontFragmentRelation(componentType)) {
|
|
1896
|
+
if (entityRelations) {
|
|
1897
|
+
const removedValue = entityRelations.get(componentType);
|
|
1898
|
+
if (removedValue !== void 0 || entityRelations.has(componentType)) {
|
|
1899
|
+
removedComponents.set(componentType, removedValue);
|
|
1900
|
+
entityRelations.delete(componentType);
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1725
1903
|
}
|
|
1726
|
-
for (const [componentType] of changeset.adds) {
|
|
1727
|
-
|
|
1728
|
-
|
|
1904
|
+
for (const [componentType, component$1] of changeset.adds) if (isDontFragmentRelation(componentType)) {
|
|
1905
|
+
if (!entityRelations) {
|
|
1906
|
+
entityRelations = /* @__PURE__ */ new Map();
|
|
1907
|
+
this.dontFragmentRelations.set(entityId, entityRelations);
|
|
1908
|
+
}
|
|
1909
|
+
entityRelations.set(componentType, component$1);
|
|
1729
1910
|
}
|
|
1730
|
-
|
|
1731
|
-
}
|
|
1732
|
-
/**
|
|
1733
|
-
* Remove and re-add entity with updated components (for dontFragment changes)
|
|
1734
|
-
*/
|
|
1735
|
-
readdEntityWithUpdatedComponents(entityId, archetype, currentComponents, changeset) {
|
|
1736
|
-
const newComponents = /* @__PURE__ */ new Map();
|
|
1737
|
-
for (const [ct, value] of currentComponents) if (!changeset.removes.has(ct)) newComponents.set(ct, value);
|
|
1738
|
-
for (const [ct, value] of changeset.adds) newComponents.set(ct, value);
|
|
1739
|
-
archetype.removeEntity(entityId);
|
|
1740
|
-
archetype.addEntity(entityId, newComponents);
|
|
1911
|
+
if (entityRelations && entityRelations.size === 0) this.dontFragmentRelations.delete(entityId);
|
|
1741
1912
|
}
|
|
1742
1913
|
/**
|
|
1743
1914
|
* Update entity reference tracking based on changeset
|
|
1744
1915
|
*/
|
|
1745
1916
|
updateEntityReferences(entityId, changeset) {
|
|
1746
|
-
for (const componentType of changeset.removes) {
|
|
1747
|
-
const
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
else if (detailedType.type === "entity") this.trackEntityReference(entityId, componentType, componentType);
|
|
1755
|
-
}
|
|
1917
|
+
for (const componentType of changeset.removes) if (isEntityRelation(componentType)) {
|
|
1918
|
+
const targetId = getTargetIdFromRelationId(componentType);
|
|
1919
|
+
this.untrackEntityReference(entityId, componentType, targetId);
|
|
1920
|
+
} else if (componentType >= 1024) this.untrackEntityReference(entityId, componentType, componentType);
|
|
1921
|
+
for (const [componentType] of changeset.adds) if (isEntityRelation(componentType)) {
|
|
1922
|
+
const targetId = getTargetIdFromRelationId(componentType);
|
|
1923
|
+
this.trackEntityReference(entityId, componentType, targetId);
|
|
1924
|
+
} else if (componentType >= 1024) this.trackEntityReference(entityId, componentType, componentType);
|
|
1756
1925
|
}
|
|
1757
1926
|
/**
|
|
1758
1927
|
* Get or create an archetype for the given component types
|
|
@@ -1779,12 +1948,11 @@ var World = class {
|
|
|
1779
1948
|
filterRegularComponentTypes(componentTypes) {
|
|
1780
1949
|
const regularTypes = [];
|
|
1781
1950
|
for (const componentType of componentTypes) {
|
|
1782
|
-
|
|
1783
|
-
if (detailedType.type === "wildcard-relation" && isDontFragmentComponent(detailedType.componentId)) {
|
|
1951
|
+
if (isDontFragmentWildcard(componentType)) {
|
|
1784
1952
|
regularTypes.push(componentType);
|
|
1785
1953
|
continue;
|
|
1786
1954
|
}
|
|
1787
|
-
if ((
|
|
1955
|
+
if (isDontFragmentRelation(componentType)) continue;
|
|
1788
1956
|
regularTypes.push(componentType);
|
|
1789
1957
|
}
|
|
1790
1958
|
return regularTypes;
|
|
@@ -1898,9 +2066,9 @@ var World = class {
|
|
|
1898
2066
|
for (const [componentType, component$1] of addedComponents) {
|
|
1899
2067
|
const directHooks = this.hooks.get(componentType);
|
|
1900
2068
|
if (directHooks) for (const lifecycleHook of directHooks) lifecycleHook.on_set?.(entityId, componentType, component$1);
|
|
1901
|
-
const
|
|
1902
|
-
if (
|
|
1903
|
-
const wildcardRelationId = relation(
|
|
2069
|
+
const componentId = getComponentIdFromRelationId(componentType);
|
|
2070
|
+
if (componentId !== void 0) {
|
|
2071
|
+
const wildcardRelationId = relation(componentId, "*");
|
|
1904
2072
|
const wildcardHooks = this.hooks.get(wildcardRelationId);
|
|
1905
2073
|
if (wildcardHooks) for (const lifecycleHook of wildcardHooks) lifecycleHook.on_set?.(entityId, componentType, component$1);
|
|
1906
2074
|
}
|
|
@@ -1908,9 +2076,9 @@ var World = class {
|
|
|
1908
2076
|
for (const [componentType, component$1] of removedComponents) {
|
|
1909
2077
|
const directHooks = this.hooks.get(componentType);
|
|
1910
2078
|
if (directHooks) for (const lifecycleHook of directHooks) lifecycleHook.on_remove?.(entityId, componentType, component$1);
|
|
1911
|
-
const
|
|
1912
|
-
if (
|
|
1913
|
-
const wildcardRelationId = relation(
|
|
2079
|
+
const componentId = getComponentIdFromRelationId(componentType);
|
|
2080
|
+
if (componentId !== void 0) {
|
|
2081
|
+
const wildcardRelationId = relation(componentId, "*");
|
|
1914
2082
|
const wildcardHooks = this.hooks.get(wildcardRelationId);
|
|
1915
2083
|
if (wildcardHooks) for (const hook of wildcardHooks) hook.on_remove?.(entityId, componentType, component$1);
|
|
1916
2084
|
}
|
|
@@ -1966,5 +2134,5 @@ var World = class {
|
|
|
1966
2134
|
};
|
|
1967
2135
|
|
|
1968
2136
|
//#endregion
|
|
1969
|
-
export { Query, World, component,
|
|
2137
|
+
export { Query, World, component, getComponentIdByName, getComponentNameById, isComponentId, isEntityId, isRelationId, isWildcardRelationId, relation };
|
|
1970
2138
|
//# sourceMappingURL=index.mjs.map
|