@codehz/ecs 0.3.12 → 0.3.14

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 CHANGED
@@ -649,9 +649,6 @@ declare class World<UpdateParams extends any[] = []> {
649
649
  * Remove all relations with the same base component (for exclusive relations)
650
650
  */
651
651
  private removeExclusiveRelations;
652
- /**
653
- * Check if a component type is a relation with the given base component
654
- */
655
652
  private isRelationWithComponent;
656
653
  /**
657
654
  * Process a delete command, handling wildcard relations
@@ -680,13 +677,10 @@ declare class World<UpdateParams extends any[] = []> {
680
677
  */
681
678
  private updateEntityInSameArchetype;
682
679
  /**
683
- * Check if changeset contains dontFragment relation changes
684
- */
685
- private hasDontFragmentChanges;
686
- /**
687
- * Remove and re-add entity with updated components (for dontFragment changes)
680
+ * Apply dontFragment relation changes directly to World's storage
681
+ * This is much more efficient than the removeEntity + addEntity approach
688
682
  */
689
- private readdEntityWithUpdatedComponents;
683
+ private applyDontFragmentChanges;
690
684
  /**
691
685
  * Update entity reference tracking based on changeset
692
686
  */
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
  });
@@ -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
- const detailedType = getDetailedIdType(componentType);
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
- const detailedType = getDetailedIdType(componentType);
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
- const detailedType = getDetailedIdType(componentType);
1392
- if (detailedType.type !== "wildcard-relation") {
1569
+ if (componentType >= 0 || componentType % 2 ** 42 !== 0) {
1393
1570
  const inArchetype = archetype.componentTypes.includes(componentType);
1394
- const isDontFragment = (detailedType.type === "entity-relation" || detailedType.type === "component-relation") && isDontFragmentComponent(detailedType.componentId);
1395
- 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().`);
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 detailedType = getDetailedIdType(componentType);
1504
- if (detailedType.type === "wildcard-relation") wildcardRelations.push({
1505
- 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,
1506
1683
  relationId: componentType
1507
1684
  });
1508
- else regularComponents.push(componentType);
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 detailedType = getDetailedIdType(componentType);
1583
- if ((detailedType.type === "entity-relation" || detailedType.type === "component-relation") && isExclusiveComponent(detailedType.componentId)) this.removeExclusiveRelations(entityId, currentArchetype, detailedType.componentId, changeset);
1584
- if ((detailedType.type === "entity-relation" || detailedType.type === "component-relation") && isDontFragmentComponent(detailedType.componentId)) {
1585
- const wildcardMarker = relation(detailedType.componentId, "*");
1586
- 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
+ }
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
- const detailedType = getDetailedIdType(componentType);
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 detailedType = getDetailedIdType(componentType);
1613
- 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);
1614
1788
  else {
1615
1789
  changeset.delete(componentType);
1616
- if ((detailedType.type === "entity-relation" || detailedType.type === "component-relation") && isDontFragmentComponent(detailedType.componentId)) {
1617
- const wildcardMarker = relation(detailedType.componentId, "*");
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
- const otherDetailedType = getDetailedIdType(otherComponentType);
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 detailedType = getDetailedIdType(componentType);
1830
+ const componentId = getComponentIdFromRelationId(componentType);
1657
1831
  changeset.delete(componentType);
1658
- if ((detailedType.type === "entity-relation" || detailedType.type === "component-relation") && isDontFragmentComponent(detailedType.componentId)) {
1659
- const wildcardMarker = relation(detailedType.componentId, "*");
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
- const otherDetailedType = getDetailedIdType(otherComponentType);
1665
- if ((otherDetailedType.type === "entity-relation" || otherDetailedType.type === "component-relation") && otherDetailedType.componentId === detailedType.componentId) {
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
- const currentComponents = currentArchetype.getEntity(entityId);
1710
- const hasDontFragmentChanges = this.hasDontFragmentChanges(changeset);
1711
- if (hasDontFragmentChanges) for (const componentType of changeset.removes) {
1712
- const detailedType = getDetailedIdType(componentType);
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
- * 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
1720
1892
  */
1721
- hasDontFragmentChanges(changeset) {
1722
- for (const componentType of changeset.removes) {
1723
- const detailedType = getDetailedIdType(componentType);
1724
- 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
+ }
1725
1903
  }
1726
- for (const [componentType] of changeset.adds) {
1727
- const detailedType = getDetailedIdType(componentType);
1728
- 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);
1729
1910
  }
1730
- return false;
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 detailedType = getDetailedIdType(componentType);
1748
- if (detailedType.type === "entity-relation") this.untrackEntityReference(entityId, componentType, detailedType.targetId);
1749
- else if (detailedType.type === "entity") this.untrackEntityReference(entityId, componentType, componentType);
1750
- }
1751
- for (const [componentType] of changeset.adds) {
1752
- const detailedType = getDetailedIdType(componentType);
1753
- if (detailedType.type === "entity-relation") this.trackEntityReference(entityId, componentType, detailedType.targetId);
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
- const detailedType = getDetailedIdType(componentType);
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 ((detailedType.type === "entity-relation" || detailedType.type === "component-relation") && isDontFragmentComponent(detailedType.componentId)) continue;
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 detailedType = getDetailedIdType(componentType);
1902
- if (detailedType.type === "entity-relation" || detailedType.type === "component-relation" || detailedType.type === "wildcard-relation") {
1903
- const wildcardRelationId = relation(detailedType.componentId, "*");
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 detailedType = getDetailedIdType(componentType);
1912
- if (detailedType.type === "entity-relation" || detailedType.type === "component-relation" || detailedType.type === "wildcard-relation") {
1913
- const wildcardRelationId = relation(detailedType.componentId, "*");
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
  }