@codehz/ecs 0.3.11 → 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.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
- if (!isRelationId(id)) return false;
42
- return -id % RELATION_SHIFT === WILDCARD_TARGET_ID;
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
- if (!isRelationId(relationId)) throw new Error("ID is not a relation ID");
51
- const absId = -relationId;
52
- const componentId = Math.floor(absId / RELATION_SHIFT);
53
- const targetId = absId % RELATION_SHIFT;
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 (!isComponentId(decoded.componentId) || decoded.type !== "wildcard" && !isEntityId(decoded.targetId) && !isComponentId(decoded.targetId)) return "invalid";
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 (!isComponentId(decoded.componentId) || decoded.type !== "wildcard" && !isEntityId(decoded.targetId) && !isComponentId(decoded.targetId)) return { type: "invalid" };
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 ComponentOptions = /* @__PURE__ */ new Map();
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
- ComponentNames.set(id, name);
364
+ componentNames[id] = name;
245
365
  ComponentIdForNames.set(name, id);
246
366
  }
247
- if (options) ComponentOptions.set(id, 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 ComponentNames.get(id);
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 ComponentOptions.get(id)?.exclusive ?? false;
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 ComponentOptions.get(id)?.dontFragment ?? false;
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 = decodeRelationId(componentType).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 decodedRel = decodeRelationId(relType);
646
- relations.push([decodedRel.targetId, data === MISSING_COMPONENT ? void 0 : data]);
825
+ const targetId = getTargetIdFromRelationId(relType);
826
+ relations.push([targetId, data === MISSING_COMPONENT ? void 0 : data]);
647
827
  }
648
- const targetComponentId = decodeRelationId(wildcardRelationType).componentId;
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 wildcardDecoded = decodeRelationId(wildcardRelationType);
657
- throw new Error(`No matching relations found for mandatory wildcard relation component ${wildcardDecoded.componentId} on entity ${entityId}`);
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 decodeRelationId(archetypeType).componentId === detailedType.componentId;
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 decodeRelationId(archetypeType).componentId === detailedType.componentId;
1188
+ return getComponentIdFromRelationId(archetypeType) === detailedType.componentId;
1009
1189
  });
1010
1190
  else return !archetype.componentTypes.includes(type);
1011
1191
  });
@@ -1332,26 +1512,12 @@ var World = class {
1332
1512
  if (!archetype) continue;
1333
1513
  const componentReferences = Array.from(this.getEntityReferences(cur));
1334
1514
  for (const [sourceEntityId, componentType] of componentReferences) {
1335
- const sourceArchetype = this.entityToArchetype.get(sourceEntityId);
1336
- if (!sourceArchetype) continue;
1337
- const detailedType = getDetailedIdType(componentType);
1338
- if (detailedType.type === "entity-relation" && isCascadeDeleteComponent(detailedType.componentId)) {
1515
+ if (!this.entityToArchetype.get(sourceEntityId)) continue;
1516
+ if (isCascadeDeleteRelation(componentType)) {
1339
1517
  if (!visited.has(sourceEntityId)) queue.push(sourceEntityId);
1340
1518
  continue;
1341
1519
  }
1342
- const currentComponents = /* @__PURE__ */ new Map();
1343
- let removedComponent = sourceArchetype.get(sourceEntityId, componentType);
1344
- for (const archetypeComponentType of sourceArchetype.componentTypes) if (archetypeComponentType !== componentType) {
1345
- const componentData = sourceArchetype.get(sourceEntityId, archetypeComponentType);
1346
- currentComponents.set(archetypeComponentType, componentData);
1347
- }
1348
- const newArchetype = this.ensureArchetype(currentComponents.keys());
1349
- sourceArchetype.removeEntity(sourceEntityId);
1350
- if (sourceArchetype.getEntities().length === 0) this.cleanupEmptyArchetype(sourceArchetype);
1351
- newArchetype.addEntity(sourceEntityId, currentComponents);
1352
- this.entityToArchetype.set(sourceEntityId, newArchetype);
1353
- this.untrackEntityReference(sourceEntityId, componentType, cur);
1354
- this.triggerLifecycleHooks(sourceEntityId, /* @__PURE__ */ new Map(), new Map([[componentType, removedComponent]]));
1520
+ this.removeComponentImmediate(sourceEntityId, componentType, cur);
1355
1521
  }
1356
1522
  this.entityReferences.delete(cur);
1357
1523
  archetype.removeEntity(cur);
@@ -1394,18 +1560,16 @@ var World = class {
1394
1560
  const archetype = this.entityToArchetype.get(entityId);
1395
1561
  if (!archetype) return false;
1396
1562
  if (archetype.componentTypes.includes(componentType)) return true;
1397
- const detailedType = getDetailedIdType(componentType);
1398
- 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;
1399
1564
  return false;
1400
1565
  }
1401
1566
  get(entityId, componentType) {
1402
1567
  const archetype = this.entityToArchetype.get(entityId);
1403
1568
  if (!archetype) throw new Error(`Entity ${entityId} does not exist`);
1404
- const detailedType = getDetailedIdType(componentType);
1405
- if (detailedType.type !== "wildcard-relation") {
1569
+ if (componentType >= 0 || componentType % 2 ** 42 !== 0) {
1406
1570
  const inArchetype = archetype.componentTypes.includes(componentType);
1407
- const isDontFragment = (detailedType.type === "entity-relation" || detailedType.type === "component-relation") && isDontFragmentComponent(detailedType.componentId);
1408
- if (!(inArchetype || isDontFragment && 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().`);
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().`);
1409
1573
  }
1410
1574
  return archetype.get(entityId, componentType);
1411
1575
  }
@@ -1512,14 +1676,13 @@ var World = class {
1512
1676
  if (componentTypes.length === 0) return [...this.archetypes];
1513
1677
  const regularComponents = [];
1514
1678
  const wildcardRelations = [];
1515
- for (const componentType of componentTypes) {
1516
- const detailedType = getDetailedIdType(componentType);
1517
- if (detailedType.type === "wildcard-relation") wildcardRelations.push({
1518
- componentId: detailedType.componentId,
1679
+ for (const componentType of componentTypes) if (isWildcardRelationId(componentType)) {
1680
+ const componentId = getComponentIdFromRelationId(componentType);
1681
+ if (componentId !== void 0) wildcardRelations.push({
1682
+ componentId,
1519
1683
  relationId: componentType
1520
1684
  });
1521
- else regularComponents.push(componentType);
1522
- }
1685
+ } else regularComponents.push(componentType);
1523
1686
  let matchingArchetypes = [];
1524
1687
  if (regularComponents.length > 0) {
1525
1688
  const sortedRegularTypes = [...regularComponents].sort((a, b) => a - b);
@@ -1592,11 +1755,13 @@ var World = class {
1592
1755
  * Process a set command, handling exclusive relations
1593
1756
  */
1594
1757
  processSetCommand(entityId, currentArchetype, componentType, component$1, changeset) {
1595
- const detailedType = getDetailedIdType(componentType);
1596
- if ((detailedType.type === "entity-relation" || detailedType.type === "component-relation") && isExclusiveComponent(detailedType.componentId)) this.removeExclusiveRelations(entityId, currentArchetype, detailedType.componentId, changeset);
1597
- if ((detailedType.type === "entity-relation" || detailedType.type === "component-relation") && isDontFragmentComponent(detailedType.componentId)) {
1598
- const wildcardMarker = relation(detailedType.componentId, "*");
1599
- if (!currentArchetype.componentTypes.includes(wildcardMarker)) changeset.set(wildcardMarker, void 0);
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
+ }
1600
1765
  }
1601
1766
  changeset.set(componentType, component$1);
1602
1767
  }
@@ -1611,30 +1776,26 @@ var World = class {
1611
1776
  if (this.isRelationWithComponent(componentType, baseComponentId)) changeset.delete(componentType);
1612
1777
  }
1613
1778
  }
1614
- /**
1615
- * Check if a component type is a relation with the given base component
1616
- */
1617
1779
  isRelationWithComponent(componentType, baseComponentId) {
1618
- const detailedType = getDetailedIdType(componentType);
1619
- return (detailedType.type === "entity-relation" || detailedType.type === "component-relation") && detailedType.componentId === baseComponentId;
1780
+ return getComponentIdFromRelationId(componentType) === baseComponentId;
1620
1781
  }
1621
1782
  /**
1622
1783
  * Process a delete command, handling wildcard relations
1623
1784
  */
1624
1785
  processDeleteCommand(entityId, currentArchetype, componentType, changeset) {
1625
- const detailedType = getDetailedIdType(componentType);
1626
- if (detailedType.type === "wildcard-relation") this.removeWildcardRelations(entityId, currentArchetype, detailedType.componentId, changeset);
1786
+ const componentId = getComponentIdFromRelationId(componentType);
1787
+ if (isWildcardRelationId(componentType) && componentId !== void 0) this.removeWildcardRelations(entityId, currentArchetype, componentId, changeset);
1627
1788
  else {
1628
1789
  changeset.delete(componentType);
1629
- if ((detailedType.type === "entity-relation" || detailedType.type === "component-relation") && isDontFragmentComponent(detailedType.componentId)) {
1630
- const wildcardMarker = relation(detailedType.componentId, "*");
1790
+ if (componentId !== void 0 && isDontFragmentComponent(componentId)) {
1791
+ const wildcardMarker = relation(componentId, "*");
1631
1792
  const entityData = currentArchetype.getEntity(entityId);
1632
1793
  let hasOtherRelations = false;
1633
1794
  if (entityData) for (const [otherComponentType] of entityData) {
1634
1795
  if (otherComponentType === componentType) continue;
1796
+ if (otherComponentType === wildcardMarker) continue;
1635
1797
  if (changeset.removes.has(otherComponentType)) continue;
1636
- const otherDetailedType = getDetailedIdType(otherComponentType);
1637
- if ((otherDetailedType.type === "entity-relation" || otherDetailedType.type === "component-relation") && otherDetailedType.componentId === detailedType.componentId) {
1798
+ if (getComponentIdFromRelationId(otherComponentType) === componentId) {
1638
1799
  hasOtherRelations = true;
1639
1800
  break;
1640
1801
  }
@@ -1659,6 +1820,35 @@ var World = class {
1659
1820
  }
1660
1821
  }
1661
1822
  /**
1823
+ * Remove a single component from an entity immediately, handling dontFragment relations correctly.
1824
+ * Used by destroyEntityImmediate for non-cascade relation cleanup.
1825
+ */
1826
+ removeComponentImmediate(entityId, componentType, targetEntityId) {
1827
+ const sourceArchetype = this.entityToArchetype.get(entityId);
1828
+ if (!sourceArchetype) return;
1829
+ const changeset = new ComponentChangeset();
1830
+ const componentId = getComponentIdFromRelationId(componentType);
1831
+ changeset.delete(componentType);
1832
+ if (componentId !== void 0 && isDontFragmentComponent(componentId)) {
1833
+ const wildcardMarker = relation(componentId, "*");
1834
+ const entityData = sourceArchetype.getEntity(entityId);
1835
+ let hasOtherRelations = false;
1836
+ if (entityData) for (const [otherComponentType] of entityData) {
1837
+ if (otherComponentType === componentType) continue;
1838
+ if (otherComponentType === wildcardMarker) continue;
1839
+ if (getComponentIdFromRelationId(otherComponentType) === componentId) {
1840
+ hasOtherRelations = true;
1841
+ break;
1842
+ }
1843
+ }
1844
+ if (!hasOtherRelations) changeset.delete(wildcardMarker);
1845
+ }
1846
+ const removedComponent = sourceArchetype.get(entityId, componentType);
1847
+ this.applyChangeset(entityId, sourceArchetype, changeset);
1848
+ this.untrackEntityReference(entityId, componentType, targetEntityId);
1849
+ this.triggerLifecycleHooks(entityId, /* @__PURE__ */ new Map(), new Map([[componentType, removedComponent]]));
1850
+ }
1851
+ /**
1662
1852
  * Apply changeset to entity, moving to new archetype if needed
1663
1853
  * @returns Map of removed components with their data
1664
1854
  */
@@ -1690,53 +1880,48 @@ var World = class {
1690
1880
  * Update entity in same archetype (no archetype change needed)
1691
1881
  */
1692
1882
  updateEntityInSameArchetype(entityId, currentArchetype, changeset, removedComponents) {
1693
- const currentComponents = currentArchetype.getEntity(entityId);
1694
- const hasDontFragmentChanges = this.hasDontFragmentChanges(changeset);
1695
- if (hasDontFragmentChanges) for (const componentType of changeset.removes) {
1696
- const detailedType = getDetailedIdType(componentType);
1697
- 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);
1698
1887
  }
1699
- if (hasDontFragmentChanges) this.readdEntityWithUpdatedComponents(entityId, currentArchetype, currentComponents, changeset);
1700
- else for (const [componentType, component$1] of changeset.adds) currentArchetype.set(entityId, componentType, component$1);
1701
1888
  }
1702
1889
  /**
1703
- * Check if changeset contains dontFragment relation changes
1890
+ * Apply dontFragment relation changes directly to World's storage
1891
+ * This is much more efficient than the removeEntity + addEntity approach
1704
1892
  */
1705
- hasDontFragmentChanges(changeset) {
1706
- for (const componentType of changeset.removes) {
1707
- const detailedType = getDetailedIdType(componentType);
1708
- if ((detailedType.type === "entity-relation" || detailedType.type === "component-relation") && isDontFragmentComponent(detailedType.componentId)) return true;
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
+ }
1709
1903
  }
1710
- for (const [componentType] of changeset.adds) {
1711
- const detailedType = getDetailedIdType(componentType);
1712
- if ((detailedType.type === "entity-relation" || detailedType.type === "component-relation") && isDontFragmentComponent(detailedType.componentId)) return true;
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);
1713
1910
  }
1714
- return false;
1715
- }
1716
- /**
1717
- * Remove and re-add entity with updated components (for dontFragment changes)
1718
- */
1719
- readdEntityWithUpdatedComponents(entityId, archetype, currentComponents, changeset) {
1720
- const newComponents = /* @__PURE__ */ new Map();
1721
- for (const [ct, value] of currentComponents) if (!changeset.removes.has(ct)) newComponents.set(ct, value);
1722
- for (const [ct, value] of changeset.adds) newComponents.set(ct, value);
1723
- archetype.removeEntity(entityId);
1724
- archetype.addEntity(entityId, newComponents);
1911
+ if (entityRelations && entityRelations.size === 0) this.dontFragmentRelations.delete(entityId);
1725
1912
  }
1726
1913
  /**
1727
1914
  * Update entity reference tracking based on changeset
1728
1915
  */
1729
1916
  updateEntityReferences(entityId, changeset) {
1730
- for (const componentType of changeset.removes) {
1731
- const detailedType = getDetailedIdType(componentType);
1732
- if (detailedType.type === "entity-relation") this.untrackEntityReference(entityId, componentType, detailedType.targetId);
1733
- else if (detailedType.type === "entity") this.untrackEntityReference(entityId, componentType, componentType);
1734
- }
1735
- for (const [componentType] of changeset.adds) {
1736
- const detailedType = getDetailedIdType(componentType);
1737
- if (detailedType.type === "entity-relation") this.trackEntityReference(entityId, componentType, detailedType.targetId);
1738
- else if (detailedType.type === "entity") this.trackEntityReference(entityId, componentType, componentType);
1739
- }
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);
1740
1925
  }
1741
1926
  /**
1742
1927
  * Get or create an archetype for the given component types
@@ -1763,12 +1948,11 @@ var World = class {
1763
1948
  filterRegularComponentTypes(componentTypes) {
1764
1949
  const regularTypes = [];
1765
1950
  for (const componentType of componentTypes) {
1766
- const detailedType = getDetailedIdType(componentType);
1767
- if (detailedType.type === "wildcard-relation" && isDontFragmentComponent(detailedType.componentId)) {
1951
+ if (isDontFragmentWildcard(componentType)) {
1768
1952
  regularTypes.push(componentType);
1769
1953
  continue;
1770
1954
  }
1771
- if ((detailedType.type === "entity-relation" || detailedType.type === "component-relation") && isDontFragmentComponent(detailedType.componentId)) continue;
1955
+ if (isDontFragmentRelation(componentType)) continue;
1772
1956
  regularTypes.push(componentType);
1773
1957
  }
1774
1958
  return regularTypes;
@@ -1882,9 +2066,9 @@ var World = class {
1882
2066
  for (const [componentType, component$1] of addedComponents) {
1883
2067
  const directHooks = this.hooks.get(componentType);
1884
2068
  if (directHooks) for (const lifecycleHook of directHooks) lifecycleHook.on_set?.(entityId, componentType, component$1);
1885
- const detailedType = getDetailedIdType(componentType);
1886
- if (detailedType.type === "entity-relation" || detailedType.type === "component-relation" || detailedType.type === "wildcard-relation") {
1887
- const wildcardRelationId = relation(detailedType.componentId, "*");
2069
+ const componentId = getComponentIdFromRelationId(componentType);
2070
+ if (componentId !== void 0) {
2071
+ const wildcardRelationId = relation(componentId, "*");
1888
2072
  const wildcardHooks = this.hooks.get(wildcardRelationId);
1889
2073
  if (wildcardHooks) for (const lifecycleHook of wildcardHooks) lifecycleHook.on_set?.(entityId, componentType, component$1);
1890
2074
  }
@@ -1892,9 +2076,9 @@ var World = class {
1892
2076
  for (const [componentType, component$1] of removedComponents) {
1893
2077
  const directHooks = this.hooks.get(componentType);
1894
2078
  if (directHooks) for (const lifecycleHook of directHooks) lifecycleHook.on_remove?.(entityId, componentType, component$1);
1895
- const detailedType = getDetailedIdType(componentType);
1896
- if (detailedType.type === "entity-relation" || detailedType.type === "component-relation" || detailedType.type === "wildcard-relation") {
1897
- const wildcardRelationId = relation(detailedType.componentId, "*");
2079
+ const componentId = getComponentIdFromRelationId(componentType);
2080
+ if (componentId !== void 0) {
2081
+ const wildcardRelationId = relation(componentId, "*");
1898
2082
  const wildcardHooks = this.hooks.get(wildcardRelationId);
1899
2083
  if (wildcardHooks) for (const hook of wildcardHooks) hook.on_remove?.(entityId, componentType, component$1);
1900
2084
  }
@@ -1950,5 +2134,5 @@ var World = class {
1950
2134
  };
1951
2135
 
1952
2136
  //#endregion
1953
- export { Query, World, component, decodeRelationId, getComponentIdByName, getComponentNameById, isComponentId, isEntityId, isRelationId, isWildcardRelationId, relation };
2137
+ export { Query, World, component, getComponentIdByName, getComponentNameById, isComponentId, isEntityId, isRelationId, isWildcardRelationId, relation };
1954
2138
  //# sourceMappingURL=index.mjs.map