@codehz/ecs 0.5.1 → 0.5.2

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/world.mjs CHANGED
@@ -1,101 +1,4 @@
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
98
- //#region src/entity.ts
1
+ //#region src/core/entity-types.ts
99
2
  const COMPONENT_ID_MAX = 1023;
100
3
  const ENTITY_ID_START = 1024;
101
4
  /**
@@ -104,34 +7,11 @@ const ENTITY_ID_START = 1024;
104
7
  const RELATION_SHIFT = 2 ** 42;
105
8
  const WILDCARD_TARGET_ID = 0;
106
9
  /**
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
10
  * Check if a component ID is valid (1-1023)
121
11
  */
122
12
  function isValidComponentId(componentId) {
123
13
  return componentId >= 1 && componentId <= COMPONENT_ID_MAX;
124
14
  }
125
- function relation(componentId, targetId) {
126
- if (!isComponentId(componentId)) throw new Error("First argument must be a valid component ID");
127
- let actualTargetId;
128
- if (targetId === "*") actualTargetId = WILDCARD_TARGET_ID;
129
- else {
130
- if (!isEntityId(targetId) && !isComponentId(targetId)) throw new Error("Second argument must be a valid entity ID, component ID, or '*'");
131
- actualTargetId = targetId;
132
- }
133
- return -(componentId * RELATION_SHIFT + actualTargetId);
134
- }
135
15
  /**
136
16
  * Check if an ID is a component ID
137
17
  */
@@ -150,6 +30,32 @@ function isEntityId(id) {
150
30
  function isRelationId(id) {
151
31
  return id < 0;
152
32
  }
33
+
34
+ //#endregion
35
+ //#region src/core/entity-relation.ts
36
+ /**
37
+ * Internal function to decode a relation ID into raw component and target IDs
38
+ * @param id The EntityId to decode
39
+ * @returns Object with componentId and targetId, or null if not a relation
40
+ */
41
+ function decodeRelationRaw(id) {
42
+ if (id >= 0) return null;
43
+ const absId = -id;
44
+ return {
45
+ componentId: Math.floor(absId / RELATION_SHIFT),
46
+ targetId: absId % RELATION_SHIFT
47
+ };
48
+ }
49
+ function relation(componentId, targetId) {
50
+ if (!isComponentId(componentId)) throw new Error("First argument must be a valid component ID");
51
+ let actualTargetId;
52
+ if (targetId === "*") actualTargetId = WILDCARD_TARGET_ID;
53
+ else {
54
+ if (!isEntityId(targetId) && !isComponentId(targetId)) throw new Error("Second argument must be a valid entity ID, component ID, or '*'");
55
+ actualTargetId = targetId;
56
+ }
57
+ return -(componentId * RELATION_SHIFT + actualTargetId);
58
+ }
153
59
  /**
154
60
  * Check if an ID is a wildcard relation id
155
61
  */
@@ -192,7 +98,7 @@ function decodeRelationId(relationId) {
192
98
  function getIdType(id) {
193
99
  if (isComponentId(id)) return "component";
194
100
  if (isEntityId(id)) return "entity";
195
- if (isRelationId(id)) try {
101
+ if (id < 0) try {
196
102
  const decoded = decodeRelationId(id);
197
103
  if (decoded.type !== "wildcard" && !isEntityId(decoded.targetId) && !isComponentId(decoded.targetId)) return "invalid";
198
104
  switch (decoded.type) {
@@ -213,7 +119,7 @@ function getIdType(id) {
213
119
  function getDetailedIdType(id) {
214
120
  if (isComponentId(id)) return { type: "component" };
215
121
  if (isEntityId(id)) return { type: "entity" };
216
- if (isRelationId(id)) try {
122
+ if (id < 0) try {
217
123
  const decoded = decodeRelationId(id);
218
124
  if (decoded.type !== "wildcard" && !isEntityId(decoded.targetId) && !isComponentId(decoded.targetId)) return { type: "invalid" };
219
125
  let type;
@@ -239,6 +145,32 @@ function getDetailedIdType(id) {
239
145
  return { type: "invalid" };
240
146
  }
241
147
  /**
148
+ * Get the componentId from a relation ID without fully decoding the relation.
149
+ * Returns undefined for non-relation IDs or invalid component IDs.
150
+ */
151
+ function getComponentIdFromRelationId(id) {
152
+ const decoded = decodeRelationRaw(id);
153
+ if (decoded === null || !isValidComponentId(decoded.componentId)) return void 0;
154
+ return decoded.componentId;
155
+ }
156
+ /**
157
+ * Get the targetId from a relation ID without fully decoding the relation.
158
+ * Returns undefined for non-relation IDs.
159
+ */
160
+ function getTargetIdFromRelationId(id) {
161
+ return decodeRelationRaw(id)?.targetId;
162
+ }
163
+ /**
164
+ * Check if an ID is an entity-relation (relation targeting an entity, not a component or wildcard)
165
+ */
166
+ function isEntityRelation(id) {
167
+ const decoded = decodeRelationRaw(id);
168
+ return decoded !== null && decoded.targetId >= ENTITY_ID_START;
169
+ }
170
+
171
+ //#endregion
172
+ //#region src/core/entity-manager.ts
173
+ /**
242
174
  * Entity ID Manager for automatic allocation and freelist recycling
243
175
  */
244
176
  var EntityIdManager = class {
@@ -330,80 +262,180 @@ var ComponentIdAllocator = class {
330
262
  return this.nextId <= COMPONENT_ID_MAX;
331
263
  }
332
264
  };
333
- const globalComponentIdAllocator = new ComponentIdAllocator();
334
- const ComponentIdForNames = /* @__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);
339
- /**
340
- * Allocate a new component ID from the global allocator.
341
- * @param nameOrOptions Optional name for the component (for serialization/debugging) or options object
342
- * @returns The allocated component ID
343
- * @example
344
- * // Just a name
345
- * const Position = component<Position>("Position");
346
- *
347
- * // With options
348
- * const ChildOf = component({ exclusive: true, cascadeDelete: true });
349
- *
350
- * // With name and options
351
- * const ChildOf = component({ name: "ChildOf", exclusive: true });
352
- */
353
- function component(nameOrOptions) {
354
- const id = globalComponentIdAllocator.allocate();
355
- let name;
356
- let options;
357
- if (typeof nameOrOptions === "string") name = nameOrOptions;
358
- else if (typeof nameOrOptions === "object" && nameOrOptions !== null) {
359
- options = nameOrOptions;
360
- name = options.name;
265
+
266
+ //#endregion
267
+ //#region src/utils/bit-set.ts
268
+ var BitSet = class {
269
+ data;
270
+ _length;
271
+ constructor(length) {
272
+ this._length = length;
273
+ const numWords = Math.ceil(length / 32);
274
+ this.data = new Uint32Array(numWords);
361
275
  }
362
- if (name) {
363
- if (ComponentIdForNames.has(name)) throw new Error(`Component name "${name}" is already registered`);
364
- componentNames[id] = name;
365
- ComponentIdForNames.set(name, id);
276
+ get length() {
277
+ return this._length;
366
278
  }
367
- if (options) {
368
- if (options.exclusive) exclusiveFlags.set(id);
369
- if (options.cascadeDelete) cascadeDeleteFlags.set(id);
370
- if (options.dontFragment) dontFragmentFlags.set(id);
279
+ has(index) {
280
+ if (index < 0 || index >= this._length) return false;
281
+ const word = index >>> 5;
282
+ const bit = index & 31;
283
+ return (this.data[word] >>> bit & 1) !== 0;
371
284
  }
372
- return id;
373
- }
374
- /**
375
- * Get a component ID by its registered name
376
- * @param name The component name
377
- * @returns The component ID if found, undefined otherwise
378
- */
379
- function getComponentIdByName(name) {
380
- return ComponentIdForNames.get(name);
381
- }
382
- /** Get a component name by its ID
383
- * @param id The component ID
384
- * @returns The component name if found, undefined otherwise
385
- */
386
- function getComponentNameById(id) {
387
- return componentNames[id];
388
- }
389
- /**
390
- * Check if a component is marked as exclusive
391
- * @param id The component ID
392
- * @returns true if the component is exclusive, false otherwise
393
- */
394
- function isExclusiveComponent(id) {
395
- return exclusiveFlags.has(id);
396
- }
397
- /**
398
- * Check if a component is marked as dontFragment
399
- * @param id The component ID
400
- * @returns true if the component is dontFragment, false otherwise
401
- */
402
- function isDontFragmentComponent(id) {
403
- return dontFragmentFlags.has(id);
404
- }
405
- /**
406
- * Generic function to check relation flags with specific target conditions
285
+ set(index) {
286
+ if (index < 0 || index >= this._length) return;
287
+ const word = index >>> 5;
288
+ const bit = index & 31;
289
+ this.data[word] |= 1 << bit;
290
+ }
291
+ clear(index) {
292
+ if (index < 0 || index >= this._length) return;
293
+ const word = index >>> 5;
294
+ const bit = index & 31;
295
+ this.data[word] &= ~(1 << bit);
296
+ }
297
+ setRange(lo, hi) {
298
+ if (lo > hi) return;
299
+ if (lo < 0) lo = 0;
300
+ if (hi >= this._length) hi = this._length - 1;
301
+ const firstWord = lo >>> 5;
302
+ const lastWord = hi >>> 5;
303
+ const loBit = lo & 31;
304
+ const hiBit = hi & 31;
305
+ const maskFor = (a, b) => {
306
+ const width = b - a + 1;
307
+ if (width <= 0) return 0;
308
+ if (width >= 32) return 4294967295;
309
+ return (1 << width) - 1 << a >>> 0;
310
+ };
311
+ if (firstWord === lastWord) {
312
+ const mask = maskFor(loBit, hiBit);
313
+ this.data[firstWord] = (this.data[firstWord] | mask) >>> 0;
314
+ return;
315
+ }
316
+ const firstMask = maskFor(loBit, 31);
317
+ this.data[firstWord] = (this.data[firstWord] | firstMask) >>> 0;
318
+ for (let w = firstWord + 1; w <= lastWord - 1; w++) this.data[w] = 4294967295;
319
+ const lastMask = maskFor(0, hiBit);
320
+ this.data[lastWord] = (this.data[lastWord] | lastMask) >>> 0;
321
+ }
322
+ anyClearInRange(lo, hi) {
323
+ if (lo > hi) return false;
324
+ if (lo < 0) lo = 0;
325
+ if (hi >= this._length) hi = this._length - 1;
326
+ const firstWord = lo >>> 5;
327
+ const lastWord = hi >>> 5;
328
+ const loBit = lo & 31;
329
+ const hiBit = hi & 31;
330
+ const maskFor = (a, b) => {
331
+ const width = b - a + 1;
332
+ if (width <= 0) return 0;
333
+ if (width >= 32) return 4294967295;
334
+ return (1 << width) - 1 << a >>> 0;
335
+ };
336
+ if (firstWord === lastWord) {
337
+ const mask = maskFor(loBit, hiBit);
338
+ return (this.data[firstWord] & mask) >>> 0 !== mask >>> 0;
339
+ }
340
+ const firstMask = maskFor(loBit, 31);
341
+ if ((this.data[firstWord] & firstMask) >>> 0 !== firstMask >>> 0) return true;
342
+ for (let w = firstWord + 1; w <= lastWord - 1; w++) if (this.data[w] !== 4294967295) return true;
343
+ const lastMask = maskFor(0, hiBit);
344
+ if ((this.data[lastWord] & lastMask) >>> 0 !== lastMask >>> 0) return true;
345
+ return false;
346
+ }
347
+ reset() {
348
+ this.data.fill(0);
349
+ }
350
+ *[Symbol.iterator]() {
351
+ for (let wordIndex = 0; wordIndex < this.data.length; wordIndex++) {
352
+ let word = this.data[wordIndex];
353
+ if (word === 0) continue;
354
+ const baseIndex = wordIndex * 32;
355
+ for (let bit = 0; bit < 32 && baseIndex + bit < this._length; bit++) {
356
+ if (word & 1) yield baseIndex + bit;
357
+ word >>>= 1;
358
+ }
359
+ }
360
+ }
361
+ };
362
+
363
+ //#endregion
364
+ //#region src/core/component-registry.ts
365
+ const globalComponentIdAllocator = new ComponentIdAllocator();
366
+ const ComponentIdForNames = /* @__PURE__ */ new Map();
367
+ const componentNames = new Array(COMPONENT_ID_MAX + 1);
368
+ const exclusiveFlags = new BitSet(COMPONENT_ID_MAX + 1);
369
+ const cascadeDeleteFlags = new BitSet(COMPONENT_ID_MAX + 1);
370
+ const dontFragmentFlags = new BitSet(COMPONENT_ID_MAX + 1);
371
+ /**
372
+ * Allocate a new component ID from the global allocator.
373
+ * @param nameOrOptions Optional name for the component (for serialization/debugging) or options object
374
+ * @returns The allocated component ID
375
+ * @example
376
+ * // Just a name
377
+ * const Position = component<Position>("Position");
378
+ *
379
+ * // With options
380
+ * const ChildOf = component({ exclusive: true, cascadeDelete: true });
381
+ *
382
+ * // With name and options
383
+ * const ChildOf = component({ name: "ChildOf", exclusive: true });
384
+ */
385
+ function component(nameOrOptions) {
386
+ const id = globalComponentIdAllocator.allocate();
387
+ let name;
388
+ let options;
389
+ if (typeof nameOrOptions === "string") name = nameOrOptions;
390
+ else if (typeof nameOrOptions === "object" && nameOrOptions !== null) {
391
+ options = nameOrOptions;
392
+ name = options.name;
393
+ }
394
+ if (name) {
395
+ if (ComponentIdForNames.has(name)) throw new Error(`Component name "${name}" is already registered`);
396
+ componentNames[id] = name;
397
+ ComponentIdForNames.set(name, id);
398
+ }
399
+ if (options) {
400
+ if (options.exclusive) exclusiveFlags.set(id);
401
+ if (options.cascadeDelete) cascadeDeleteFlags.set(id);
402
+ if (options.dontFragment) dontFragmentFlags.set(id);
403
+ }
404
+ return id;
405
+ }
406
+ /**
407
+ * Get a component ID by its registered name
408
+ * @param name The component name
409
+ * @returns The component ID if found, undefined otherwise
410
+ */
411
+ function getComponentIdByName(name) {
412
+ return ComponentIdForNames.get(name);
413
+ }
414
+ /** Get a component name by its ID
415
+ * @param id The component ID
416
+ * @returns The component name if found, undefined otherwise
417
+ */
418
+ function getComponentNameById(id) {
419
+ return componentNames[id];
420
+ }
421
+ /**
422
+ * Check if a component is marked as exclusive
423
+ * @param id The component ID
424
+ * @returns true if the component is exclusive, false otherwise
425
+ */
426
+ function isExclusiveComponent(id) {
427
+ return exclusiveFlags.has(id);
428
+ }
429
+ /**
430
+ * Check if a component is marked as dontFragment
431
+ * @param id The component ID
432
+ * @returns true if the component is dontFragment, false otherwise
433
+ */
434
+ function isDontFragmentComponent(id) {
435
+ return dontFragmentFlags.has(id);
436
+ }
437
+ /**
438
+ * Generic function to check relation flags with specific target conditions
407
439
  * @param id The entity/relation ID to check
408
440
  * @param flagBitSet The bitset for the flag
409
441
  * @param targetCondition Function to check target ID condition
@@ -443,920 +475,803 @@ function isDontFragmentWildcard(id) {
443
475
  function isCascadeDeleteRelation(id) {
444
476
  return checkRelationFlag(id, cascadeDeleteFlags, (targetId) => targetId !== WILDCARD_TARGET_ID && targetId >= ENTITY_ID_START);
445
477
  }
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;
468
- }
469
478
 
470
479
  //#endregion
471
- //#region src/types.ts
472
- function isOptionalEntityId(type) {
473
- return typeof type === "object" && type !== null && "optional" in type;
474
- }
480
+ //#region src/core/builder.ts
481
+ var EntityBuilder = class {
482
+ world;
483
+ components = [];
484
+ constructor(world) {
485
+ this.world = world;
486
+ }
487
+ with(componentId, value) {
488
+ this.components.push({
489
+ type: "component",
490
+ id: componentId,
491
+ value
492
+ });
493
+ return this;
494
+ }
495
+ withTag(componentId) {
496
+ this.components.push({
497
+ type: "component",
498
+ id: componentId,
499
+ value: void 0
500
+ });
501
+ return this;
502
+ }
503
+ withRelation(componentId, targetEntity, value) {
504
+ this.components.push({
505
+ type: "relation",
506
+ componentId,
507
+ targetId: targetEntity,
508
+ value
509
+ });
510
+ return this;
511
+ }
512
+ withRelationTag(componentId, targetEntity) {
513
+ this.components.push({
514
+ type: "relation",
515
+ componentId,
516
+ targetId: targetEntity,
517
+ value: void 0
518
+ });
519
+ return this;
520
+ }
521
+ /**
522
+ * Create an entity and enqueue components to be applied. This method
523
+ * does NOT call `world.sync()` automatically; callers must invoke
524
+ * `world.sync()` to apply deferred commands.
525
+ * (Previously auto-synced; now a breaking change — buildDeferred() removed.)
526
+ */
527
+ build() {
528
+ const entity = this.world.new();
529
+ for (const def of this.components) if (def.type === "component") this.world.set(entity, def.id, def.value);
530
+ else {
531
+ const relationId = relation(def.componentId, def.targetId);
532
+ this.world.set(entity, relationId, def.value);
533
+ }
534
+ return entity;
535
+ }
536
+ };
475
537
 
476
538
  //#endregion
477
- //#region src/utils.ts
478
- /**
479
- * Utility functions for ECS library
480
- */
539
+ //#region src/commands/changeset.ts
481
540
  /**
482
- * Get a value from cache or compute and cache it if not present
483
- * @param cache The cache map
484
- * @param key The cache key
485
- * @param compute Function to compute the value if not cached
486
- * @returns The cached or computed value
541
+ * @internal Represents a set of component changes to be applied to an entity
487
542
  */
488
- function getOrComputeCache(cache, key, compute) {
489
- let value = cache.get(key);
490
- if (value === void 0) {
491
- value = compute();
492
- cache.set(key, value);
493
- }
494
- return value;
495
- }
496
- /**
497
- * Get a value from cache or create and cache it if not present, allowing side effects during creation
498
- * @param cache The cache map
499
- * @param key The cache key
500
- * @param create Function to create the value if not cached (can have side effects)
501
- * @returns The cached or created value
502
- */
503
- function getOrCreateWithSideEffect(cache, key, create) {
504
- let value = cache.get(key);
505
- if (value === void 0) {
506
- value = create();
507
- cache.set(key, value);
508
- }
509
- return value;
510
- }
511
-
512
- //#endregion
513
- //#region src/archetype.ts
514
- /**
515
- * Special value to represent missing component data
516
- */
517
- const MISSING_COMPONENT = Symbol("missing component");
518
- /**
519
- * Archetype class for ECS architecture
520
- * Represents a group of entities that share the same set of components
521
- * Optimized for fast iteration and component access
522
- */
523
- var Archetype = class {
524
- /**
525
- * The component types that define this archetype
526
- */
527
- componentTypes;
528
- /**
529
- * List of entities in this archetype
530
- */
531
- entities = [];
532
- /**
533
- * Component data storage - maps component type to array of component data
534
- * Each array index corresponds to the entity index in the entities array
535
- */
536
- componentData = /* @__PURE__ */ new Map();
537
- /**
538
- * Reverse mapping from entity to its index in this archetype
539
- */
540
- entityToIndex = /* @__PURE__ */ new Map();
541
- /**
542
- * Reference to dontFragment relations storage from World
543
- * This allows entities with different relation targets to share the same archetype
544
- * Stored in World to avoid migration overhead when entities change archetypes
545
- */
546
- dontFragmentRelations;
543
+ var ComponentChangeset = class {
544
+ adds = /* @__PURE__ */ new Map();
545
+ removes = /* @__PURE__ */ new Set();
547
546
  /**
548
- * Cache for pre-computed component data sources to avoid repeated calculations
549
- * For regular components: data array
550
- * For wildcards: matching relation types array
547
+ * Add a component to the changeset
551
548
  */
552
- componentDataSourcesCache = /* @__PURE__ */ new Map();
549
+ set(componentType, component$1) {
550
+ this.adds.set(componentType, component$1);
551
+ this.removes.delete(componentType);
552
+ }
553
553
  /**
554
- * Create a new archetype with the specified component types
555
- * @param componentTypes The component types that define this archetype
556
- * @param dontFragmentRelations Reference to the World's dontFragmentRelations storage
554
+ * Remove a component from the changeset
557
555
  */
558
- constructor(componentTypes, dontFragmentRelations) {
559
- this.componentTypes = [...componentTypes].sort((a, b) => a - b);
560
- this.dontFragmentRelations = dontFragmentRelations;
561
- for (const componentType of this.componentTypes) this.componentData.set(componentType, []);
556
+ delete(componentType) {
557
+ this.removes.add(componentType);
558
+ this.adds.delete(componentType);
562
559
  }
563
560
  /**
564
- * Get the number of entities in this archetype
561
+ * Check if the changeset has any changes
565
562
  */
566
- get size() {
567
- return this.entities.length;
563
+ hasChanges() {
564
+ return this.adds.size > 0 || this.removes.size > 0;
568
565
  }
569
566
  /**
570
- * Check if this archetype matches the given component types
571
- * @param componentTypes The component types to check
567
+ * Clear all changes
572
568
  */
573
- matches(componentTypes) {
574
- if (this.componentTypes.length !== componentTypes.length) return false;
575
- const sortedTypes = [...componentTypes].sort((a, b) => a - b);
576
- return this.componentTypes.every((type, index) => type === sortedTypes[index]);
569
+ clear() {
570
+ this.adds.clear();
571
+ this.removes.clear();
577
572
  }
578
573
  /**
579
- * Add an entity to this archetype with initial component data
580
- * @param entityId The entity to add
581
- * @param componentData Map of component type to component data (includes both regular and dontFragment components)
574
+ * Merge another changeset into this one
582
575
  */
583
- addEntity(entityId, componentData) {
584
- if (this.entityToIndex.has(entityId)) throw new Error(`Entity ${entityId} is already in this archetype`);
585
- const index = this.entities.length;
586
- this.entities.push(entityId);
587
- this.entityToIndex.set(entityId, index);
588
- for (const componentType of this.componentTypes) {
589
- const data = componentData.get(componentType);
590
- this.getComponentData(componentType).push(!componentData.has(componentType) ? MISSING_COMPONENT : data);
576
+ merge(other) {
577
+ for (const [componentType, component$1] of other.adds) {
578
+ this.adds.set(componentType, component$1);
579
+ this.removes.delete(componentType);
591
580
  }
592
- const dontFragmentData = /* @__PURE__ */ new Map();
593
- for (const [componentType, data] of componentData) {
594
- if (this.componentTypes.includes(componentType)) continue;
595
- const detailedType = getDetailedIdType(componentType);
596
- if ((detailedType.type === "entity-relation" || detailedType.type === "component-relation") && isDontFragmentComponent(detailedType.componentId)) dontFragmentData.set(componentType, data);
581
+ for (const componentType of other.removes) {
582
+ this.removes.add(componentType);
583
+ this.adds.delete(componentType);
597
584
  }
598
- if (dontFragmentData.size > 0) this.dontFragmentRelations.set(entityId, dontFragmentData);
599
585
  }
600
586
  /**
601
- * Get all component data for a specific entity
602
- * @param entityId The entity to get data for
603
- * @returns Map of component type to component data (includes both regular and dontFragment components)
587
+ * Apply the changeset to existing components and return the final state
604
588
  */
605
- getEntity(entityId) {
606
- const index = this.entityToIndex.get(entityId);
607
- if (index === void 0) return;
608
- const entityData = /* @__PURE__ */ new Map();
609
- for (const componentType of this.componentTypes) {
610
- const data = this.getComponentData(componentType)[index];
611
- entityData.set(componentType, data === MISSING_COMPONENT ? void 0 : data);
612
- }
613
- const dontFragmentData = this.dontFragmentRelations.get(entityId);
614
- if (dontFragmentData) for (const [componentType, data] of dontFragmentData) entityData.set(componentType, data);
615
- return entityData;
589
+ applyTo(existingComponents) {
590
+ for (const componentType of this.removes) existingComponents.delete(componentType);
591
+ for (const [componentType, component$1] of this.adds) existingComponents.set(componentType, component$1);
592
+ return existingComponents;
616
593
  }
617
594
  /**
618
- * Dump all entities and their component data in this archetype
619
- * @returns Array of objects with entity and component data (includes both regular and dontFragment components)
595
+ * Get the final component types after applying the changeset
596
+ * @param existingComponentTypes - The current component types on the entity
597
+ * @returns The final component types or undefined if no changes
620
598
  */
621
- dump() {
622
- const result = [];
623
- for (let i = 0; i < this.entities.length; i++) {
624
- const entity = this.entities[i];
625
- const components = /* @__PURE__ */ new Map();
626
- for (const componentType of this.componentTypes) {
627
- const data = this.getComponentData(componentType)[i];
628
- components.set(componentType, data === MISSING_COMPONENT ? void 0 : data);
599
+ getFinalComponentTypes(existingComponentTypes) {
600
+ const finalComponentTypes = new Set(existingComponentTypes);
601
+ let changed = false;
602
+ for (const componentType of this.removes) {
603
+ if (!finalComponentTypes.has(componentType)) {
604
+ this.removes.delete(componentType);
605
+ continue;
629
606
  }
630
- const dontFragmentData = this.dontFragmentRelations.get(entity);
631
- if (dontFragmentData) for (const [componentType, data] of dontFragmentData) components.set(componentType, data);
632
- result.push({
633
- entity,
634
- components
635
- });
607
+ changed = true;
608
+ finalComponentTypes.delete(componentType);
636
609
  }
637
- return result;
610
+ for (const componentType of this.adds.keys()) {
611
+ if (finalComponentTypes.has(componentType)) continue;
612
+ changed = true;
613
+ finalComponentTypes.add(componentType);
614
+ }
615
+ return changed ? Array.from(finalComponentTypes) : void 0;
638
616
  }
617
+ };
618
+
619
+ //#endregion
620
+ //#region src/commands/command-buffer.ts
621
+ /**
622
+ * Command buffer for deferred structural changes
623
+ */
624
+ var CommandBuffer = class {
625
+ commands = [];
626
+ executeEntityCommands;
639
627
  /**
640
- * Remove an entity from this archetype
641
- * @param entityId The entity to remove
642
- * @returns The component data of the removed entity (includes both regular and dontFragment components)
628
+ * Create a command buffer with an executor function
643
629
  */
644
- removeEntity(entityId) {
645
- const index = this.entityToIndex.get(entityId);
646
- if (index === void 0) return;
647
- const removedData = /* @__PURE__ */ new Map();
648
- for (const componentType of this.componentTypes) {
649
- const dataArray = this.getComponentData(componentType);
650
- removedData.set(componentType, dataArray[index]);
651
- }
652
- const dontFragmentData = this.dontFragmentRelations.get(entityId);
653
- if (dontFragmentData) {
654
- for (const [componentType, data] of dontFragmentData) removedData.set(componentType, data);
655
- this.dontFragmentRelations.delete(entityId);
656
- }
657
- this.entityToIndex.delete(entityId);
658
- const lastIndex = this.entities.length - 1;
659
- if (index !== lastIndex) {
660
- const lastEntity = this.entities[lastIndex];
661
- this.entities[index] = lastEntity;
662
- this.entityToIndex.set(lastEntity, index);
663
- for (const componentType of this.componentTypes) {
664
- const dataArray = this.getComponentData(componentType);
665
- dataArray[index] = dataArray[lastIndex];
666
- }
667
- }
668
- this.entities.pop();
669
- for (const componentType of this.componentTypes) this.getComponentData(componentType).pop();
670
- return removedData;
630
+ constructor(executeEntityCommands) {
631
+ this.executeEntityCommands = executeEntityCommands;
632
+ }
633
+ set(entityId, componentType, component$1) {
634
+ this.commands.push({
635
+ type: "set",
636
+ entityId,
637
+ componentType,
638
+ component: component$1
639
+ });
671
640
  }
672
641
  /**
673
- * Check if an entity is in this archetype
674
- * @param entityId The entity to check
642
+ * Remove a component from an entity (deferred)
675
643
  */
676
- exists(entityId) {
677
- return this.entityToIndex.has(entityId);
678
- }
679
- get(entityId, componentType) {
680
- const index = this.entityToIndex.get(entityId);
681
- if (index === void 0) throw new Error(`Entity ${entityId} is not in this archetype`);
682
- if (isWildcardRelationId(componentType)) {
683
- const componentId = getComponentIdFromRelationId(componentType);
684
- const relations = [];
685
- for (const relType of this.componentTypes) {
686
- const relDetailed = getDetailedIdType(relType);
687
- if ((relDetailed.type === "entity-relation" || relDetailed.type === "component-relation") && relDetailed.componentId === componentId) {
688
- const dataArray = this.getComponentData(relType);
689
- if (dataArray && dataArray[index] !== void 0) {
690
- const data = dataArray[index];
691
- relations.push([relDetailed.targetId, data === MISSING_COMPONENT ? void 0 : data]);
692
- }
693
- }
694
- }
695
- const dontFragmentData = this.dontFragmentRelations.get(entityId);
696
- if (dontFragmentData) for (const [relType, data] of dontFragmentData) {
697
- const relDetailed = getDetailedIdType(relType);
698
- if ((relDetailed.type === "entity-relation" || relDetailed.type === "component-relation") && relDetailed.componentId === componentId) relations.push([relDetailed.targetId, data]);
699
- }
700
- return relations;
701
- } else {
702
- if (this.componentTypes.includes(componentType)) {
703
- const data = this.getComponentData(componentType)[index];
704
- if (data === MISSING_COMPONENT) throw new Error(`Component type ${componentType} not found for entity ${entityId}`);
705
- return data;
706
- }
707
- const dontFragmentData = this.dontFragmentRelations.get(entityId);
708
- if (dontFragmentData && dontFragmentData.has(componentType)) return dontFragmentData.get(componentType);
709
- throw new Error(`Component type ${componentType} not found for entity ${entityId}`);
710
- }
644
+ remove(entityId, componentType) {
645
+ this.commands.push({
646
+ type: "delete",
647
+ entityId,
648
+ componentType
649
+ });
711
650
  }
712
651
  /**
713
- * Get optional component data for a specific entity and component type
714
- * @param entityId The entity
715
- * @param componentType The component type
716
- * @returns { value: T } if component exists, undefined otherwise
652
+ * Destroy an entity (deferred)
717
653
  */
718
- getOptional(entityId, componentType) {
719
- const index = this.entityToIndex.get(entityId);
720
- if (index === void 0) throw new Error(`Entity ${entityId} is not in this archetype`);
721
- if (this.componentTypes.includes(componentType)) {
722
- const data = this.getComponentData(componentType)[index];
723
- if (data === MISSING_COMPONENT) return;
724
- return { value: data };
725
- }
726
- const dontFragmentData = this.dontFragmentRelations.get(entityId);
727
- if (dontFragmentData && dontFragmentData.has(componentType)) return { value: dontFragmentData.get(componentType) };
654
+ delete(entityId) {
655
+ this.commands.push({
656
+ type: "destroy",
657
+ entityId
658
+ });
728
659
  }
729
660
  /**
730
- * Set component data for a specific entity and component type
731
- * @param entityId The entity
732
- * @param componentType The component type
733
- * @param data The component data
661
+ * Execute all commands and clear the buffer
734
662
  */
735
- set(entityId, componentType, data) {
736
- const index = this.entityToIndex.get(entityId);
737
- if (index === void 0) throw new Error(`Entity ${entityId} is not in this archetype`);
738
- if (this.componentData.has(componentType)) {
739
- const dataArray = this.getComponentData(componentType);
740
- dataArray[index] = data;
741
- return;
742
- }
743
- const detailedType = getDetailedIdType(componentType);
744
- if ((detailedType.type === "entity-relation" || detailedType.type === "component-relation") && isDontFragmentComponent(detailedType.componentId)) {
745
- let dontFragmentData = this.dontFragmentRelations.get(entityId);
746
- if (!dontFragmentData) {
747
- dontFragmentData = /* @__PURE__ */ new Map();
748
- this.dontFragmentRelations.set(entityId, dontFragmentData);
663
+ execute() {
664
+ const MAX_ITERATIONS = 100;
665
+ let iterations = 0;
666
+ while (this.commands.length > 0) {
667
+ if (iterations >= MAX_ITERATIONS) throw new Error("Command execution exceeded maximum iterations, possible infinite loop");
668
+ iterations++;
669
+ const currentCommands = [...this.commands];
670
+ this.commands = [];
671
+ const entityCommands = /* @__PURE__ */ new Map();
672
+ for (const cmd of currentCommands) {
673
+ if (!entityCommands.has(cmd.entityId)) entityCommands.set(cmd.entityId, []);
674
+ entityCommands.get(cmd.entityId).push(cmd);
749
675
  }
750
- dontFragmentData.set(componentType, data);
751
- return;
676
+ for (const [entityId, commands] of entityCommands) this.executeEntityCommands(entityId, commands);
752
677
  }
753
- throw new Error(`Component type ${componentType} is not in this archetype`);
754
678
  }
755
679
  /**
756
- * Get all entities in this archetype
680
+ * Get current commands (for testing)
757
681
  */
758
- getEntities() {
759
- return this.entities;
682
+ getCommands() {
683
+ return [...this.commands];
760
684
  }
761
685
  /**
762
- * Get the mapping of entities to their indices in this archetype
763
- */
764
- getEntityToIndexMap() {
765
- return this.entityToIndex;
766
- }
767
- /**
768
- * Get component data for all entities of a specific component type
769
- * @param componentType The component type
770
- */
771
- getComponentData(componentType) {
772
- const data = this.componentData.get(componentType);
773
- if (!data) throw new Error(`Component type ${componentType} is not in this archetype`);
774
- return data;
775
- }
776
- /**
777
- * Get optional component data for all entities of a specific component type
778
- * @param componentType The component type
779
- * @returns An array of component data or undefined if not present
780
- */
781
- getOptionalComponentData(componentType) {
782
- return this.componentData.get(componentType);
783
- }
784
- /**
785
- * Helper: compute or return cached data sources for provided componentTypes
786
- */
787
- getCachedComponentDataSources(componentTypes) {
788
- const cacheKey = this.buildCacheKey(componentTypes);
789
- return getOrComputeCache(this.componentDataSourcesCache, cacheKey, () => componentTypes.map((compType) => this.getComponentDataSource(compType)));
790
- }
791
- /**
792
- * Build cache key for component types
793
- */
794
- buildCacheKey(componentTypes) {
795
- return componentTypes.map((id) => isOptionalEntityId(id) ? `opt(${id.optional})` : `${id}`).join(",");
796
- }
797
- /**
798
- * Get data source for a single component type
686
+ * Clear all commands
799
687
  */
800
- getComponentDataSource(compType) {
801
- const optional = isOptionalEntityId(compType);
802
- const actualType = optional ? compType.optional : compType;
803
- const detailedType = getDetailedIdType(actualType);
804
- if (detailedType.type === "wildcard-relation") return this.getWildcardRelationDataSource(detailedType.componentId, optional);
805
- else return optional ? this.getOptionalComponentData(actualType) : this.getComponentData(actualType);
688
+ clear() {
689
+ this.commands = [];
806
690
  }
807
- /**
808
- * Get data source for wildcard relations
809
- */
810
- getWildcardRelationDataSource(componentId, optional) {
811
- const matchingRelations = this.componentTypes.filter((ct) => {
812
- const detailedCt = getDetailedIdType(ct);
813
- return (detailedCt.type === "entity-relation" || detailedCt.type === "component-relation") && detailedCt.componentId === componentId;
691
+ };
692
+
693
+ //#endregion
694
+ //#region src/query/filter.ts
695
+ /**
696
+ * Serialize a QueryFilter into a deterministic string suitable for cache keys.
697
+ * Currently only serializes `negativeComponentTypes`.
698
+ */
699
+ function serializeQueryFilter(filter = {}) {
700
+ const negative = (filter.negativeComponentTypes || []).slice().sort((a, b) => a - b);
701
+ if (negative.length === 0) return "";
702
+ return `neg:${negative.join(",")}`;
703
+ }
704
+ /**
705
+ * Check if an archetype matches the given component types
706
+ */
707
+ function matchesComponentTypes(archetype, componentTypes) {
708
+ return componentTypes.every((type) => {
709
+ const detailedType = getDetailedIdType(type);
710
+ if (detailedType.type === "wildcard-relation") return archetype.componentTypes.some((archetypeType) => {
711
+ if (!isRelationId(archetypeType)) return false;
712
+ return getComponentIdFromRelationId(archetypeType) === detailedType.componentId;
814
713
  });
815
- return optional ? matchingRelations.length > 0 ? matchingRelations : void 0 : matchingRelations;
816
- }
817
- /**
818
- * Helper: build component tuples for a specific entity index using precomputed data sources
819
- */
820
- buildComponentsForIndex(componentTypes, componentDataSources, entityIndex, entityId) {
821
- return componentDataSources.map((dataSource, i) => {
822
- const compType = componentTypes[i];
823
- return this.buildSingleComponent(compType, dataSource, entityIndex, entityId);
714
+ else return archetype.componentTypes.includes(type);
715
+ });
716
+ }
717
+ /**
718
+ * Check if an archetype matches the filter conditions (only filtering logic)
719
+ */
720
+ function matchesFilter(archetype, filter) {
721
+ return (filter.negativeComponentTypes || []).every((type) => {
722
+ const detailedType = getDetailedIdType(type);
723
+ if (detailedType.type === "wildcard-relation") return !archetype.componentTypes.some((archetypeType) => {
724
+ if (!isRelationId(archetypeType)) return false;
725
+ return getComponentIdFromRelationId(archetypeType) === detailedType.componentId;
824
726
  });
727
+ else return !archetype.componentTypes.includes(type);
728
+ });
729
+ }
730
+
731
+ //#endregion
732
+ //#region src/query/query.ts
733
+ /**
734
+ * Query class for efficient entity queries with cached archetypes
735
+ */
736
+ var Query = class {
737
+ world;
738
+ componentTypes;
739
+ filter;
740
+ cachedArchetypes = [];
741
+ isDisposed = false;
742
+ /** Cached wildcard component types for faster entity filtering */
743
+ wildcardTypes;
744
+ constructor(world, componentTypes, filter = {}) {
745
+ this.world = world;
746
+ this.componentTypes = [...componentTypes].sort((a, b) => a - b);
747
+ this.filter = filter;
748
+ this.wildcardTypes = this.componentTypes.filter((ct) => getDetailedIdType(ct).type === "wildcard-relation");
749
+ this.updateCache();
750
+ world._registerQuery(this);
825
751
  }
826
752
  /**
827
- * Build a single component value from its data source
753
+ * Check if query is disposed and throw error if so
828
754
  */
829
- buildSingleComponent(compType, dataSource, entityIndex, entityId) {
830
- const optional = isOptionalEntityId(compType);
831
- const actualType = optional ? compType.optional : compType;
832
- if (getIdType(actualType) === "wildcard-relation") return this.buildWildcardRelationValue(actualType, dataSource, entityIndex, entityId, optional);
833
- else return this.buildRegularComponentValue(dataSource, entityIndex, optional);
755
+ ensureNotDisposed() {
756
+ if (this.isDisposed) throw new Error("Query has been disposed");
834
757
  }
835
758
  /**
836
- * Build wildcard relation value from matching relations
759
+ * Get all entities matching the query
837
760
  */
838
- buildWildcardRelationValue(wildcardRelationType, dataSource, entityIndex, entityId, optional) {
839
- const matchingRelations = dataSource || [];
840
- const relations = [];
841
- for (const relType of matchingRelations) {
842
- const data = this.getComponentData(relType)[entityIndex];
843
- const targetId = getTargetIdFromRelationId(relType);
844
- relations.push([targetId, data === MISSING_COMPONENT ? void 0 : data]);
845
- }
846
- const targetComponentId = getComponentIdFromRelationId(wildcardRelationType);
847
- const dontFragmentData = this.dontFragmentRelations.get(entityId);
848
- if (dontFragmentData) for (const [relType, data] of dontFragmentData) {
849
- const relDetailed = getDetailedIdType(relType);
850
- if ((relDetailed.type === "entity-relation" || relDetailed.type === "component-relation") && relDetailed.componentId === targetComponentId) relations.push([relDetailed.targetId, data]);
851
- }
852
- if (relations.length === 0) {
853
- if (!optional) {
854
- const componentId = getComponentIdFromRelationId(wildcardRelationType);
855
- throw new Error(`No matching relations found for mandatory wildcard relation component ${componentId} on entity ${entityId}`);
856
- }
857
- return;
761
+ getEntities() {
762
+ this.ensureNotDisposed();
763
+ if (this.wildcardTypes.length === 0) {
764
+ const result$1 = [];
765
+ for (const archetype of this.cachedArchetypes) result$1.push(...archetype.getEntities());
766
+ return result$1;
858
767
  }
859
- return optional ? { value: relations } : relations;
768
+ const result = [];
769
+ for (const archetype of this.cachedArchetypes) for (const entity of archetype.getEntities()) if (this.entityHasAllWildcards(archetype, entity)) result.push(entity);
770
+ return result;
860
771
  }
861
772
  /**
862
- * Build regular component value from data source
773
+ * Check if entity has all required wildcard relations
863
774
  */
864
- buildRegularComponentValue(dataSource, entityIndex, optional) {
865
- if (dataSource === void 0) {
866
- if (optional) return;
867
- throw new Error(`Component data not found for mandatory component type`);
775
+ entityHasAllWildcards(archetype, entity) {
776
+ for (const wildcardType of this.wildcardTypes) {
777
+ const relations = archetype.get(entity, wildcardType);
778
+ if (!relations || relations.length === 0) return false;
868
779
  }
869
- const data = dataSource[entityIndex];
870
- const result = data === MISSING_COMPONENT ? void 0 : data;
871
- return optional ? { value: result } : result;
780
+ return true;
872
781
  }
873
782
  /**
874
- * Get entities with their component data for specified component types
875
- * Optimized for bulk component access with pre-computed indices
783
+ * Get entities with their component data
876
784
  * @param componentTypes Array of component types to retrieve
877
785
  * @returns Array of objects with entity and component data
878
786
  */
879
787
  getEntitiesWithComponents(componentTypes) {
788
+ this.ensureNotDisposed();
880
789
  const result = [];
881
- this.forEachWithComponents(componentTypes, (entity, ...components) => {
882
- result.push({
883
- entity,
884
- components
885
- });
886
- });
887
- return result;
888
- }
889
- /**
890
- * Iterate over entities with their component data for specified component types
891
- * implemented as a generator returning each entity/components pair lazily
892
- * @param componentTypes Array of component types to retrieve
893
- */
894
- *iterateWithComponents(componentTypes) {
895
- const componentDataSources = this.getCachedComponentDataSources(componentTypes);
896
- for (let entityIndex = 0; entityIndex < this.entities.length; entityIndex++) {
897
- const entity = this.entities[entityIndex];
898
- yield [entity, ...this.buildComponentsForIndex(componentTypes, componentDataSources, entityIndex, entity)];
790
+ for (const archetype of this.cachedArchetypes) {
791
+ const entitiesWithData = archetype.getEntitiesWithComponents(componentTypes);
792
+ result.push(...entitiesWithData);
899
793
  }
794
+ return result;
900
795
  }
901
796
  /**
902
- * Iterate over entities with their component data for specified component types
903
- * Optimized for bulk component access
797
+ * Iterate over entities with their component data
904
798
  * @param componentTypes Array of component types to retrieve
905
799
  * @param callback Function called for each entity with its components
906
800
  */
907
- forEachWithComponents(componentTypes, callback) {
908
- const componentDataSources = this.getCachedComponentDataSources(componentTypes);
909
- for (let entityIndex = 0; entityIndex < this.entities.length; entityIndex++) {
910
- const entity = this.entities[entityIndex];
911
- callback(entity, ...this.buildComponentsForIndex(componentTypes, componentDataSources, entityIndex, entity));
912
- }
801
+ forEach(componentTypes, callback) {
802
+ this.ensureNotDisposed();
803
+ for (const archetype of this.cachedArchetypes) archetype.forEachWithComponents(componentTypes, callback);
913
804
  }
914
805
  /**
915
- * Iterate over all entities with their component data
916
- * @param callback Function called for each entity with its component data
806
+ * Iterate over entities with their component data (generator)
807
+ * @param componentTypes Array of component types to retrieve
917
808
  */
918
- forEach(callback) {
919
- for (let i = 0; i < this.entities.length; i++) {
920
- const components = /* @__PURE__ */ new Map();
921
- for (const componentType of this.componentTypes) {
922
- const data = this.getComponentData(componentType)[i];
923
- components.set(componentType, data === MISSING_COMPONENT ? void 0 : data);
924
- }
925
- callback(this.entities[i], components);
926
- }
809
+ *iterate(componentTypes) {
810
+ this.ensureNotDisposed();
811
+ for (const archetype of this.cachedArchetypes) yield* archetype.iterateWithComponents(componentTypes);
927
812
  }
928
813
  /**
929
- * Check if any entity in this archetype has a relation matching the given component ID
930
- * This includes both regular relations in componentTypes and dontFragment relations
931
- * @param componentId The component ID to match
932
- * @returns true if any entity has a matching relation
814
+ * Get component data arrays for all matching entities
815
+ * @param componentType The component type to retrieve
816
+ * @returns Array of component data for all matching entities
933
817
  */
934
- hasRelationWithComponentId(componentId) {
935
- for (const componentType of this.componentTypes) {
936
- const detailedType = getDetailedIdType(componentType);
937
- if ((detailedType.type === "entity-relation" || detailedType.type === "component-relation") && detailedType.componentId === componentId) return true;
938
- }
939
- for (const entityId of this.entities) {
940
- const entityDontFragmentRelations = this.dontFragmentRelations.get(entityId);
941
- if (entityDontFragmentRelations) for (const relationType of entityDontFragmentRelations.keys()) {
942
- const detailedType = getDetailedIdType(relationType);
943
- if ((detailedType.type === "entity-relation" || detailedType.type === "component-relation") && detailedType.componentId === componentId) return true;
944
- }
945
- }
946
- return false;
818
+ getComponentData(componentType) {
819
+ this.ensureNotDisposed();
820
+ const result = [];
821
+ for (const archetype of this.cachedArchetypes) result.push(...archetype.getComponentData(componentType));
822
+ return result;
947
823
  }
948
- };
949
-
950
- //#endregion
951
- //#region src/changeset.ts
952
- /**
953
- * @internal Represents a set of component changes to be applied to an entity
954
- */
955
- var ComponentChangeset = class {
956
- adds = /* @__PURE__ */ new Map();
957
- removes = /* @__PURE__ */ new Set();
958
824
  /**
959
- * Add a component to the changeset
825
+ * Update the cached archetypes
826
+ * Called when new archetypes are created
960
827
  */
961
- set(componentType, component$1) {
962
- this.adds.set(componentType, component$1);
963
- this.removes.delete(componentType);
828
+ updateCache() {
829
+ if (this.isDisposed) return;
830
+ this.cachedArchetypes = this.world.getMatchingArchetypes(this.componentTypes).filter((archetype) => matchesFilter(archetype, this.filter));
964
831
  }
965
832
  /**
966
- * Remove a component from the changeset
833
+ * Check if a new archetype matches this query and add to cache if it does
967
834
  */
968
- delete(componentType) {
969
- this.removes.add(componentType);
970
- this.adds.delete(componentType);
835
+ checkNewArchetype(archetype) {
836
+ if (this.isDisposed) return;
837
+ if (matchesComponentTypes(archetype, this.componentTypes) && matchesFilter(archetype, this.filter) && !this.cachedArchetypes.includes(archetype)) this.cachedArchetypes.push(archetype);
971
838
  }
972
839
  /**
973
- * Check if the changeset has any changes
840
+ * Remove an archetype from the cached archetypes
974
841
  */
975
- hasChanges() {
976
- return this.adds.size > 0 || this.removes.size > 0;
842
+ removeArchetype(archetype) {
843
+ if (this.isDisposed) return;
844
+ const index = this.cachedArchetypes.indexOf(archetype);
845
+ if (index !== -1) this.cachedArchetypes.splice(index, 1);
977
846
  }
978
847
  /**
979
- * Clear all changes
848
+ * Request disposal of this query.
849
+ * This will decrement the world's reference count for the query.
850
+ * The query will only be fully disposed when the ref count reaches zero.
980
851
  */
981
- clear() {
982
- this.adds.clear();
983
- this.removes.clear();
852
+ dispose() {
853
+ this.world.releaseQuery(this);
984
854
  }
985
855
  /**
986
- * Merge another changeset into this one
856
+ * Internal full dispose called by World when refCount reaches zero.
987
857
  */
988
- merge(other) {
989
- for (const [componentType, component$1] of other.adds) {
990
- this.adds.set(componentType, component$1);
991
- this.removes.delete(componentType);
992
- }
993
- for (const componentType of other.removes) {
994
- this.removes.add(componentType);
995
- this.adds.delete(componentType);
858
+ _disposeInternal() {
859
+ if (!this.isDisposed) {
860
+ this.world._unregisterQuery(this);
861
+ this.cachedArchetypes = [];
862
+ this.isDisposed = true;
996
863
  }
997
864
  }
998
865
  /**
999
- * Apply the changeset to existing components and return the final state
866
+ * Symbol.dispose implementation for automatic resource management
1000
867
  */
1001
- applyTo(existingComponents) {
1002
- for (const componentType of this.removes) existingComponents.delete(componentType);
1003
- for (const [componentType, component$1] of this.adds) existingComponents.set(componentType, component$1);
1004
- return existingComponents;
868
+ [Symbol.dispose]() {
869
+ this.dispose();
1005
870
  }
1006
871
  /**
1007
- * Get the final component types after applying the changeset
1008
- * @param existingComponentTypes - The current component types on the entity
1009
- * @returns The final component types or undefined if no changes
872
+ * Check if the query has been disposed
1010
873
  */
1011
- getFinalComponentTypes(existingComponentTypes) {
1012
- const finalComponentTypes = new Set(existingComponentTypes);
1013
- let changed = false;
1014
- for (const componentType of this.removes) {
1015
- if (!finalComponentTypes.has(componentType)) {
1016
- this.removes.delete(componentType);
1017
- continue;
1018
- }
1019
- changed = true;
1020
- finalComponentTypes.delete(componentType);
1021
- }
1022
- for (const componentType of this.adds.keys()) {
1023
- if (finalComponentTypes.has(componentType)) continue;
1024
- changed = true;
1025
- finalComponentTypes.add(componentType);
1026
- }
1027
- return changed ? Array.from(finalComponentTypes) : void 0;
874
+ get disposed() {
875
+ return this.isDisposed;
1028
876
  }
1029
877
  };
1030
878
 
1031
879
  //#endregion
1032
- //#region src/command-buffer.ts
880
+ //#region src/utils/utils.ts
1033
881
  /**
1034
- * Command buffer for deferred structural changes
882
+ * Utility functions for ECS library
1035
883
  */
1036
- var CommandBuffer = class {
1037
- commands = [];
1038
- executeEntityCommands;
1039
- /**
1040
- * Create a command buffer with an executor function
1041
- */
1042
- constructor(executeEntityCommands) {
1043
- this.executeEntityCommands = executeEntityCommands;
1044
- }
1045
- set(entityId, componentType, component$1) {
1046
- this.commands.push({
1047
- type: "set",
1048
- entityId,
1049
- componentType,
1050
- component: component$1
1051
- });
1052
- }
1053
- /**
1054
- * Remove a component from an entity (deferred)
1055
- */
1056
- remove(entityId, componentType) {
1057
- this.commands.push({
1058
- type: "delete",
1059
- entityId,
1060
- componentType
1061
- });
1062
- }
1063
- /**
1064
- * Destroy an entity (deferred)
1065
- */
1066
- delete(entityId) {
1067
- this.commands.push({
1068
- type: "destroy",
1069
- entityId
1070
- });
1071
- }
1072
- /**
1073
- * Execute all commands and clear the buffer
1074
- */
1075
- execute() {
1076
- const MAX_ITERATIONS = 100;
1077
- let iterations = 0;
1078
- while (this.commands.length > 0) {
1079
- if (iterations >= MAX_ITERATIONS) throw new Error("Command execution exceeded maximum iterations, possible infinite loop");
1080
- iterations++;
1081
- const currentCommands = [...this.commands];
1082
- this.commands = [];
1083
- const entityCommands = /* @__PURE__ */ new Map();
1084
- for (const cmd of currentCommands) {
1085
- if (!entityCommands.has(cmd.entityId)) entityCommands.set(cmd.entityId, []);
1086
- entityCommands.get(cmd.entityId).push(cmd);
1087
- }
1088
- for (const [entityId, commands] of entityCommands) this.executeEntityCommands(entityId, commands);
1089
- }
1090
- }
1091
- /**
1092
- * Get current commands (for testing)
1093
- */
1094
- getCommands() {
1095
- return [...this.commands];
884
+ /**
885
+ * Get a value from cache or compute and cache it if not present
886
+ * @param cache The cache map
887
+ * @param key The cache key
888
+ * @param compute Function to compute the value if not cached
889
+ * @returns The cached or computed value
890
+ */
891
+ function getOrComputeCache(cache, key, compute) {
892
+ let value = cache.get(key);
893
+ if (value === void 0) {
894
+ value = compute();
895
+ cache.set(key, value);
1096
896
  }
1097
- /**
1098
- * Clear all commands
1099
- */
1100
- clear() {
1101
- this.commands = [];
897
+ return value;
898
+ }
899
+ /**
900
+ * Get a value from cache or create and cache it if not present, allowing side effects during creation
901
+ * @param cache The cache map
902
+ * @param key The cache key
903
+ * @param create Function to create the value if not cached (can have side effects)
904
+ * @returns The cached or created value
905
+ */
906
+ function getOrCreateWithSideEffect(cache, key, create) {
907
+ let value = cache.get(key);
908
+ if (value === void 0) {
909
+ value = create();
910
+ cache.set(key, value);
1102
911
  }
1103
- };
912
+ return value;
913
+ }
1104
914
 
1105
915
  //#endregion
1106
- //#region src/multi-map.ts
1107
- var MultiMap = class {
1108
- map = /* @__PURE__ */ new Map();
1109
- _valueCount = 0;
1110
- get valueCount() {
1111
- return this._valueCount;
1112
- }
1113
- get keyCount() {
1114
- return this.map.size;
1115
- }
1116
- hasKey(key) {
1117
- return this.map.has(key);
1118
- }
1119
- has(key, value) {
1120
- const set = this.map.get(key);
1121
- if (!set) return false;
1122
- if (arguments.length === 1) return true;
1123
- return set.has(value);
1124
- }
1125
- add(key, value) {
1126
- let set = this.map.get(key);
1127
- if (!set) {
1128
- set = /* @__PURE__ */ new Set();
1129
- this.map.set(key, set);
1130
- }
1131
- if (!set.has(value)) {
1132
- set.add(value);
1133
- this._valueCount++;
1134
- }
1135
- }
1136
- remove(key, value) {
1137
- const set = this.map.get(key);
1138
- if (!set) return false;
1139
- if (!set.has(value)) return false;
1140
- set.delete(value);
1141
- this._valueCount--;
1142
- if (set.size === 0) this.map.delete(key);
1143
- return true;
1144
- }
1145
- deleteKey(key) {
1146
- const set = this.map.get(key);
1147
- if (!set) return false;
1148
- this._valueCount -= set.size;
1149
- this.map.delete(key);
1150
- return true;
1151
- }
1152
- get(key) {
1153
- const set = this.map.get(key);
1154
- return set ? new Set(set) : /* @__PURE__ */ new Set();
1155
- }
1156
- *keys() {
1157
- yield* this.map.keys();
1158
- }
1159
- *values() {
1160
- for (const set of this.map.values()) for (const v of set) yield v;
1161
- }
1162
- [Symbol.iterator]() {
1163
- return this.entries();
1164
- }
1165
- *entries() {
1166
- for (const [k, set] of this.map.entries()) for (const v of set) yield [k, v];
1167
- }
1168
- clear() {
1169
- this.map.clear();
1170
- this._valueCount = 0;
1171
- }
1172
- };
916
+ //#region src/core/types.ts
917
+ function isOptionalEntityId(type) {
918
+ return typeof type === "object" && type !== null && "optional" in type;
919
+ }
1173
920
 
1174
921
  //#endregion
1175
- //#region src/query-filter.ts
922
+ //#region src/core/archetype-helpers.ts
1176
923
  /**
1177
- * Serialize a QueryFilter into a deterministic string suitable for cache keys.
1178
- * Currently only serializes `negativeComponentTypes`.
924
+ * Check if a detailed type represents a relation (entity or component)
1179
925
  */
1180
- function serializeQueryFilter(filter = {}) {
1181
- const negative = (filter.negativeComponentTypes || []).slice().sort((a, b) => a - b);
1182
- if (negative.length === 0) return "";
1183
- return `neg:${negative.join(",")}`;
926
+ function isRelationType(detailedType) {
927
+ return detailedType.type === "entity-relation" || detailedType.type === "component-relation";
1184
928
  }
1185
929
  /**
1186
- * Check if an archetype matches the given component types
930
+ * Check if a component type matches a given component ID for relations
1187
931
  */
1188
- function matchesComponentTypes(archetype, componentTypes) {
1189
- return componentTypes.every((type) => {
1190
- const detailedType = getDetailedIdType(type);
1191
- if (detailedType.type === "wildcard-relation") return archetype.componentTypes.some((archetypeType) => {
1192
- if (!isRelationId(archetypeType)) return false;
1193
- return getComponentIdFromRelationId(archetypeType) === detailedType.componentId;
1194
- });
1195
- else return archetype.componentTypes.includes(type);
1196
- });
932
+ function matchesRelationComponentId(componentType, componentId) {
933
+ const detailedType = getDetailedIdType(componentType);
934
+ return isRelationType(detailedType) && detailedType.componentId === componentId;
1197
935
  }
1198
936
  /**
1199
- * Check if an archetype matches the filter conditions (only filtering logic)
937
+ * Find all relations in dontFragment data that match a component ID
1200
938
  */
1201
- function matchesFilter(archetype, filter) {
1202
- return (filter.negativeComponentTypes || []).every((type) => {
1203
- const detailedType = getDetailedIdType(type);
1204
- if (detailedType.type === "wildcard-relation") return !archetype.componentTypes.some((archetypeType) => {
1205
- if (!isRelationId(archetypeType)) return false;
1206
- return getComponentIdFromRelationId(archetypeType) === detailedType.componentId;
1207
- });
1208
- else return !archetype.componentTypes.includes(type);
1209
- });
939
+ function findMatchingDontFragmentRelations(dontFragmentData, componentId) {
940
+ const relations = [];
941
+ if (!dontFragmentData) return relations;
942
+ for (const [relType, data] of dontFragmentData) {
943
+ const relDetailed = getDetailedIdType(relType);
944
+ if (isRelationType(relDetailed) && relDetailed.componentId === componentId) relations.push([relDetailed.targetId, data]);
945
+ }
946
+ return relations;
947
+ }
948
+ /**
949
+ * Build cache key for component types
950
+ */
951
+ function buildCacheKey(componentTypes) {
952
+ return componentTypes.map((id) => isOptionalEntityId(id) ? `opt(${id.optional})` : `${id}`).join(",");
953
+ }
954
+ /**
955
+ * Get data source for wildcard relations from component types
956
+ */
957
+ function getWildcardRelationDataSource(componentTypes, componentId, optional) {
958
+ const matchingRelations = componentTypes.filter((ct) => matchesRelationComponentId(ct, componentId));
959
+ return optional ? matchingRelations.length > 0 ? matchingRelations : void 0 : matchingRelations;
960
+ }
961
+ /**
962
+ * Build wildcard relation value from matching relations
963
+ */
964
+ function buildWildcardRelationValue(wildcardRelationType, matchingRelations, getDataAtIndex, dontFragmentData, entityId, optional) {
965
+ const relations = [];
966
+ const targetComponentId = getComponentIdFromRelationId(wildcardRelationType);
967
+ for (const relType of matchingRelations || []) {
968
+ const data = getDataAtIndex(relType);
969
+ const targetId = getTargetIdFromRelationId(relType);
970
+ relations.push([targetId, data === MISSING_COMPONENT ? void 0 : data]);
971
+ }
972
+ if (targetComponentId !== void 0) relations.push(...findMatchingDontFragmentRelations(dontFragmentData, targetComponentId));
973
+ if (relations.length === 0) {
974
+ if (!optional) {
975
+ const componentId = getComponentIdFromRelationId(wildcardRelationType);
976
+ throw new Error(`No matching relations found for mandatory wildcard relation component ${componentId} on entity ${entityId}`);
977
+ }
978
+ return;
979
+ }
980
+ return optional ? { value: relations } : relations;
981
+ }
982
+ /**
983
+ * Build regular component value from data source
984
+ */
985
+ function buildRegularComponentValue(dataSource, entityIndex, optional) {
986
+ if (dataSource === void 0) {
987
+ if (optional) return void 0;
988
+ throw new Error(`Component data not found for mandatory component type`);
989
+ }
990
+ const data = dataSource[entityIndex];
991
+ const result = data === MISSING_COMPONENT ? void 0 : data;
992
+ return optional ? { value: result } : result;
993
+ }
994
+ /**
995
+ * Build a single component value based on its type
996
+ */
997
+ function buildSingleComponent(compType, dataSource, entityIndex, entityId, getComponentData, dontFragmentRelations) {
998
+ const optional = isOptionalEntityId(compType);
999
+ const actualType = optional ? compType.optional : compType;
1000
+ if (getIdType(actualType) === "wildcard-relation") return buildWildcardRelationValue(actualType, dataSource, (relType) => getComponentData(relType)[entityIndex], dontFragmentRelations.get(entityId), entityId, optional);
1001
+ else return buildRegularComponentValue(dataSource, entityIndex, optional);
1210
1002
  }
1211
1003
 
1212
1004
  //#endregion
1213
- //#region src/query.ts
1005
+ //#region src/core/archetype.ts
1214
1006
  /**
1215
- * Query class for efficient entity queries with cached archetypes
1007
+ * Special value to represent missing component data
1216
1008
  */
1217
- var Query = class {
1218
- world;
1009
+ const MISSING_COMPONENT = Symbol("missing component");
1010
+ /**
1011
+ * Archetype class for ECS architecture
1012
+ * Represents a group of entities that share the same set of components
1013
+ * Optimized for fast iteration and component access
1014
+ */
1015
+ var Archetype = class {
1016
+ /**
1017
+ * The component types that define this archetype
1018
+ */
1219
1019
  componentTypes;
1220
- filter;
1221
- cachedArchetypes = [];
1222
- isDisposed = false;
1223
- constructor(world, componentTypes, filter = {}) {
1224
- this.world = world;
1225
- this.componentTypes = [...componentTypes].sort((a, b) => a - b);
1226
- this.filter = filter;
1227
- this.updateCache();
1228
- world._registerQuery(this);
1229
- }
1230
1020
  /**
1231
- * Check if query is disposed and throw error if so
1021
+ * List of entities in this archetype
1232
1022
  */
1233
- ensureNotDisposed() {
1234
- if (this.isDisposed) throw new Error("Query has been disposed");
1235
- }
1023
+ entities = [];
1236
1024
  /**
1237
- * Get all entities matching the query
1025
+ * Component data storage - maps component type to array of component data
1026
+ * Each array index corresponds to the entity index in the entities array
1238
1027
  */
1239
- getEntities() {
1240
- this.ensureNotDisposed();
1241
- const result = [];
1242
- if (this.componentTypes.some((ct) => {
1243
- return getDetailedIdType(ct).type === "wildcard-relation";
1244
- })) for (const archetype of this.cachedArchetypes) for (const entity of archetype.getEntities()) {
1245
- let hasAllRelations = true;
1246
- for (const componentType of this.componentTypes) if (getDetailedIdType(componentType).type === "wildcard-relation") {
1247
- const relations = archetype.get(entity, componentType);
1248
- if (!relations || relations.length === 0) {
1249
- hasAllRelations = false;
1250
- break;
1028
+ componentData = /* @__PURE__ */ new Map();
1029
+ /**
1030
+ * Reverse mapping from entity to its index in this archetype
1031
+ */
1032
+ entityToIndex = /* @__PURE__ */ new Map();
1033
+ /**
1034
+ * Reference to dontFragment relations storage from World
1035
+ * This allows entities with different relation targets to share the same archetype
1036
+ * Stored in World to avoid migration overhead when entities change archetypes
1037
+ */
1038
+ dontFragmentRelations;
1039
+ /**
1040
+ * Cache for pre-computed component data sources to avoid repeated calculations
1041
+ */
1042
+ componentDataSourcesCache = /* @__PURE__ */ new Map();
1043
+ constructor(componentTypes, dontFragmentRelations) {
1044
+ this.componentTypes = [...componentTypes].sort((a, b) => a - b);
1045
+ this.dontFragmentRelations = dontFragmentRelations;
1046
+ for (const componentType of this.componentTypes) this.componentData.set(componentType, []);
1047
+ }
1048
+ get size() {
1049
+ return this.entities.length;
1050
+ }
1051
+ matches(componentTypes) {
1052
+ if (this.componentTypes.length !== componentTypes.length) return false;
1053
+ const sortedTypes = [...componentTypes].sort((a, b) => a - b);
1054
+ return this.componentTypes.every((type, index) => type === sortedTypes[index]);
1055
+ }
1056
+ addEntity(entityId, componentData) {
1057
+ if (this.entityToIndex.has(entityId)) throw new Error(`Entity ${entityId} is already in this archetype`);
1058
+ const index = this.entities.length;
1059
+ this.entities.push(entityId);
1060
+ this.entityToIndex.set(entityId, index);
1061
+ for (const componentType of this.componentTypes) {
1062
+ const data = componentData.get(componentType);
1063
+ this.getComponentData(componentType).push(!componentData.has(componentType) ? MISSING_COMPONENT : data);
1064
+ }
1065
+ this.addDontFragmentRelations(entityId, componentData);
1066
+ }
1067
+ addDontFragmentRelations(entityId, componentData) {
1068
+ const dontFragmentData = /* @__PURE__ */ new Map();
1069
+ for (const [componentType, data] of componentData) {
1070
+ if (this.componentTypes.includes(componentType)) continue;
1071
+ const detailedType = getDetailedIdType(componentType);
1072
+ if (isRelationType(detailedType) && isDontFragmentComponent(detailedType.componentId)) dontFragmentData.set(componentType, data);
1073
+ }
1074
+ if (dontFragmentData.size > 0) this.dontFragmentRelations.set(entityId, dontFragmentData);
1075
+ }
1076
+ getEntity(entityId) {
1077
+ const index = this.entityToIndex.get(entityId);
1078
+ if (index === void 0) return void 0;
1079
+ const entityData = /* @__PURE__ */ new Map();
1080
+ for (const componentType of this.componentTypes) {
1081
+ const data = this.getComponentData(componentType)[index];
1082
+ entityData.set(componentType, data === MISSING_COMPONENT ? void 0 : data);
1083
+ }
1084
+ const dontFragmentData = this.dontFragmentRelations.get(entityId);
1085
+ if (dontFragmentData) for (const [componentType, data] of dontFragmentData) entityData.set(componentType, data);
1086
+ return entityData;
1087
+ }
1088
+ dump() {
1089
+ return this.entities.map((entity, i) => {
1090
+ const components = /* @__PURE__ */ new Map();
1091
+ for (const componentType of this.componentTypes) {
1092
+ const data = this.getComponentData(componentType)[i];
1093
+ components.set(componentType, data === MISSING_COMPONENT ? void 0 : data);
1094
+ }
1095
+ const dontFragmentData = this.dontFragmentRelations.get(entity);
1096
+ if (dontFragmentData) for (const [componentType, data] of dontFragmentData) components.set(componentType, data);
1097
+ return {
1098
+ entity,
1099
+ components
1100
+ };
1101
+ });
1102
+ }
1103
+ removeEntity(entityId) {
1104
+ const index = this.entityToIndex.get(entityId);
1105
+ if (index === void 0) return void 0;
1106
+ const removedData = /* @__PURE__ */ new Map();
1107
+ for (const componentType of this.componentTypes) removedData.set(componentType, this.getComponentData(componentType)[index]);
1108
+ const dontFragmentData = this.dontFragmentRelations.get(entityId);
1109
+ if (dontFragmentData) {
1110
+ for (const [componentType, data] of dontFragmentData) removedData.set(componentType, data);
1111
+ this.dontFragmentRelations.delete(entityId);
1112
+ }
1113
+ this.entityToIndex.delete(entityId);
1114
+ const lastIndex = this.entities.length - 1;
1115
+ if (index !== lastIndex) {
1116
+ const lastEntity = this.entities[lastIndex];
1117
+ this.entities[index] = lastEntity;
1118
+ this.entityToIndex.set(lastEntity, index);
1119
+ for (const componentType of this.componentTypes) {
1120
+ const dataArray = this.getComponentData(componentType);
1121
+ dataArray[index] = dataArray[lastIndex];
1122
+ }
1123
+ }
1124
+ this.entities.pop();
1125
+ for (const componentType of this.componentTypes) this.getComponentData(componentType).pop();
1126
+ return removedData;
1127
+ }
1128
+ exists(entityId) {
1129
+ return this.entityToIndex.has(entityId);
1130
+ }
1131
+ get(entityId, componentType) {
1132
+ const index = this.entityToIndex.get(entityId);
1133
+ if (index === void 0) throw new Error(`Entity ${entityId} is not in this archetype`);
1134
+ if (isWildcardRelationId(componentType)) return this.getWildcardRelations(entityId, index, componentType);
1135
+ return this.getRegularComponent(entityId, index, componentType);
1136
+ }
1137
+ getWildcardRelations(entityId, index, componentType) {
1138
+ const componentId = getComponentIdFromRelationId(componentType);
1139
+ const relations = [];
1140
+ for (const relType of this.componentTypes) {
1141
+ const relDetailed = getDetailedIdType(relType);
1142
+ if (isRelationType(relDetailed) && relDetailed.componentId === componentId) {
1143
+ const dataArray = this.getComponentData(relType);
1144
+ if (dataArray && dataArray[index] !== void 0) {
1145
+ const data = dataArray[index];
1146
+ relations.push([relDetailed.targetId, data === MISSING_COMPONENT ? void 0 : data]);
1251
1147
  }
1252
1148
  }
1253
- if (hasAllRelations) result.push(entity);
1254
1149
  }
1255
- else for (const archetype of this.cachedArchetypes) result.push(...archetype.getEntities());
1256
- return result;
1150
+ if (componentId !== void 0) relations.push(...findMatchingDontFragmentRelations(this.dontFragmentRelations.get(entityId), componentId));
1151
+ return relations;
1257
1152
  }
1258
- /**
1259
- * Get entities with their component data
1260
- * @param componentTypes Array of component types to retrieve
1261
- * @returns Array of objects with entity and component data
1262
- */
1263
- getEntitiesWithComponents(componentTypes) {
1264
- this.ensureNotDisposed();
1265
- const result = [];
1266
- for (const archetype of this.cachedArchetypes) {
1267
- const entitiesWithData = archetype.getEntitiesWithComponents(componentTypes);
1268
- result.push(...entitiesWithData);
1153
+ getRegularComponent(entityId, index, componentType) {
1154
+ if (this.componentTypes.includes(componentType)) {
1155
+ const data = this.getComponentData(componentType)[index];
1156
+ if (data === MISSING_COMPONENT) throw new Error(`Component type ${componentType} not found for entity ${entityId}`);
1157
+ return data;
1269
1158
  }
1270
- return result;
1159
+ const dontFragmentData = this.dontFragmentRelations.get(entityId);
1160
+ if (dontFragmentData?.has(componentType)) return dontFragmentData.get(componentType);
1161
+ throw new Error(`Component type ${componentType} not found for entity ${entityId}`);
1271
1162
  }
1272
- /**
1273
- * Iterate over entities with their component data
1274
- * @param componentTypes Array of component types to retrieve
1275
- * @param callback Function called for each entity with its components
1276
- */
1277
- forEach(componentTypes, callback) {
1278
- this.ensureNotDisposed();
1279
- for (const archetype of this.cachedArchetypes) archetype.forEachWithComponents(componentTypes, callback);
1163
+ getOptional(entityId, componentType) {
1164
+ const index = this.entityToIndex.get(entityId);
1165
+ if (index === void 0) throw new Error(`Entity ${entityId} is not in this archetype`);
1166
+ if (this.componentTypes.includes(componentType)) {
1167
+ const data = this.getComponentData(componentType)[index];
1168
+ if (data === MISSING_COMPONENT) return void 0;
1169
+ return { value: data };
1170
+ }
1171
+ const dontFragmentData = this.dontFragmentRelations.get(entityId);
1172
+ if (dontFragmentData?.has(componentType)) return { value: dontFragmentData.get(componentType) };
1173
+ }
1174
+ set(entityId, componentType, data) {
1175
+ const index = this.entityToIndex.get(entityId);
1176
+ if (index === void 0) throw new Error(`Entity ${entityId} is not in this archetype`);
1177
+ if (this.componentData.has(componentType)) {
1178
+ this.getComponentData(componentType)[index] = data;
1179
+ return;
1180
+ }
1181
+ const detailedType = getDetailedIdType(componentType);
1182
+ if (isRelationType(detailedType) && isDontFragmentComponent(detailedType.componentId)) {
1183
+ let dontFragmentData = this.dontFragmentRelations.get(entityId);
1184
+ if (!dontFragmentData) {
1185
+ dontFragmentData = /* @__PURE__ */ new Map();
1186
+ this.dontFragmentRelations.set(entityId, dontFragmentData);
1187
+ }
1188
+ dontFragmentData.set(componentType, data);
1189
+ return;
1190
+ }
1191
+ throw new Error(`Component type ${componentType} is not in this archetype`);
1280
1192
  }
1281
- /**
1282
- * Iterate over entities with their component data (generator)
1283
- * @param componentTypes Array of component types to retrieve
1284
- */
1285
- *iterate(componentTypes) {
1286
- this.ensureNotDisposed();
1287
- for (const archetype of this.cachedArchetypes) yield* archetype.iterateWithComponents(componentTypes);
1193
+ getEntities() {
1194
+ return this.entities;
1195
+ }
1196
+ getEntityToIndexMap() {
1197
+ return this.entityToIndex;
1288
1198
  }
1289
- /**
1290
- * Get component data arrays for all matching entities
1291
- * @param componentType The component type to retrieve
1292
- * @returns Array of component data for all matching entities
1293
- */
1294
1199
  getComponentData(componentType) {
1295
- this.ensureNotDisposed();
1296
- const result = [];
1297
- for (const archetype of this.cachedArchetypes) result.push(...archetype.getComponentData(componentType));
1298
- return result;
1200
+ const data = this.componentData.get(componentType);
1201
+ if (!data) throw new Error(`Component type ${componentType} is not in this archetype`);
1202
+ return data;
1299
1203
  }
1300
- /**
1301
- * Update the cached archetypes
1302
- * Called when new archetypes are created
1303
- */
1304
- updateCache() {
1305
- if (this.isDisposed) return;
1306
- this.cachedArchetypes = this.world.getMatchingArchetypes(this.componentTypes).filter((archetype) => matchesFilter(archetype, this.filter));
1204
+ getOptionalComponentData(componentType) {
1205
+ return this.componentData.get(componentType);
1307
1206
  }
1308
- /**
1309
- * Check if a new archetype matches this query and add to cache if it does
1310
- */
1311
- checkNewArchetype(archetype) {
1312
- if (this.isDisposed) return;
1313
- if (matchesComponentTypes(archetype, this.componentTypes) && matchesFilter(archetype, this.filter) && !this.cachedArchetypes.includes(archetype)) this.cachedArchetypes.push(archetype);
1207
+ getCachedComponentDataSources(componentTypes) {
1208
+ const cacheKey = buildCacheKey(componentTypes);
1209
+ return getOrComputeCache(this.componentDataSourcesCache, cacheKey, () => componentTypes.map((compType) => this.getComponentDataSource(compType)));
1314
1210
  }
1315
- /**
1316
- * Remove an archetype from the cached archetypes
1317
- */
1318
- removeArchetype(archetype) {
1319
- if (this.isDisposed) return;
1320
- const index = this.cachedArchetypes.indexOf(archetype);
1321
- if (index !== -1) this.cachedArchetypes.splice(index, 1);
1211
+ getComponentDataSource(compType) {
1212
+ const optional = isOptionalEntityId(compType);
1213
+ const actualType = optional ? compType.optional : compType;
1214
+ if (getIdType(actualType) === "wildcard-relation") {
1215
+ const componentId = getComponentIdFromRelationId(actualType);
1216
+ return getWildcardRelationDataSource(this.componentTypes, componentId, optional);
1217
+ }
1218
+ return optional ? this.getOptionalComponentData(actualType) : this.getComponentData(actualType);
1322
1219
  }
1323
- /**
1324
- * Dispose the query and disconnect from world
1325
- */
1326
- /**
1327
- * Request disposal of this query.
1328
- * This will decrement the world's reference count for the query.
1329
- * The query will only be fully disposed when the ref count reaches zero.
1330
- */
1331
- dispose() {
1332
- this.world.releaseQuery(this);
1220
+ buildComponentsForIndex(componentTypes, componentDataSources, entityIndex, entityId) {
1221
+ return componentDataSources.map((dataSource, i) => buildSingleComponent(componentTypes[i], dataSource, entityIndex, entityId, (type) => this.getComponentData(type), this.dontFragmentRelations));
1333
1222
  }
1334
- /**
1335
- * Internal full dispose called by World when refCount reaches zero.
1336
- */
1337
- _disposeInternal() {
1338
- if (!this.isDisposed) {
1339
- this.world._unregisterQuery(this);
1340
- this.cachedArchetypes = [];
1341
- this.isDisposed = true;
1223
+ getEntitiesWithComponents(componentTypes) {
1224
+ const result = [];
1225
+ this.forEachWithComponents(componentTypes, (entity, ...components) => {
1226
+ result.push({
1227
+ entity,
1228
+ components
1229
+ });
1230
+ });
1231
+ return result;
1232
+ }
1233
+ *iterateWithComponents(componentTypes) {
1234
+ const componentDataSources = this.getCachedComponentDataSources(componentTypes);
1235
+ for (let entityIndex = 0; entityIndex < this.entities.length; entityIndex++) {
1236
+ const entity = this.entities[entityIndex];
1237
+ yield [entity, ...this.buildComponentsForIndex(componentTypes, componentDataSources, entityIndex, entity)];
1342
1238
  }
1343
1239
  }
1344
- /**
1345
- * Symbol.dispose implementation for automatic resource management
1346
- */
1347
- [Symbol.dispose]() {
1348
- this.dispose();
1240
+ forEachWithComponents(componentTypes, callback) {
1241
+ const componentDataSources = this.getCachedComponentDataSources(componentTypes);
1242
+ for (let entityIndex = 0; entityIndex < this.entities.length; entityIndex++) {
1243
+ const entity = this.entities[entityIndex];
1244
+ callback(entity, ...this.buildComponentsForIndex(componentTypes, componentDataSources, entityIndex, entity));
1245
+ }
1349
1246
  }
1350
- /**
1351
- * Check if the query has been disposed
1352
- */
1353
- get disposed() {
1354
- return this.isDisposed;
1247
+ forEach(callback) {
1248
+ for (let i = 0; i < this.entities.length; i++) {
1249
+ const components = /* @__PURE__ */ new Map();
1250
+ for (const componentType of this.componentTypes) {
1251
+ const data = this.getComponentData(componentType)[i];
1252
+ components.set(componentType, data === MISSING_COMPONENT ? void 0 : data);
1253
+ }
1254
+ callback(this.entities[i], components);
1255
+ }
1256
+ }
1257
+ hasRelationWithComponentId(componentId) {
1258
+ for (const componentType of this.componentTypes) {
1259
+ const detailedType = getDetailedIdType(componentType);
1260
+ if (isRelationType(detailedType) && detailedType.componentId === componentId) return true;
1261
+ }
1262
+ for (const entityId of this.entities) {
1263
+ const entityDontFragmentRelations = this.dontFragmentRelations.get(entityId);
1264
+ if (entityDontFragmentRelations) for (const relationType of entityDontFragmentRelations.keys()) {
1265
+ const detailedType = getDetailedIdType(relationType);
1266
+ if (isRelationType(detailedType) && detailedType.componentId === componentId) return true;
1267
+ }
1268
+ }
1269
+ return false;
1355
1270
  }
1356
1271
  };
1357
1272
 
1358
1273
  //#endregion
1359
- //#region src/world.ts
1274
+ //#region src/core/serialization.ts
1360
1275
  /**
1361
1276
  * Encode an internal EntityId into a SerializedEntityId for snapshots
1362
1277
  */
@@ -1432,77 +1347,302 @@ function decodeSerializedId(sid) {
1432
1347
  }
1433
1348
  throw new Error(`Invalid ID in snapshot: ${JSON.stringify(sid)}`);
1434
1349
  }
1350
+
1351
+ //#endregion
1352
+ //#region src/core/world-commands.ts
1353
+ function processCommands(entityId, currentArchetype, commands, changeset, handleExclusiveRelation) {
1354
+ for (const command of commands) if (command.type === "set" && command.componentType) processSetCommand(entityId, currentArchetype, command.componentType, command.component, changeset, handleExclusiveRelation);
1355
+ else if (command.type === "delete" && command.componentType) processDeleteCommand(entityId, currentArchetype, command.componentType, changeset);
1356
+ }
1357
+ function processSetCommand(entityId, currentArchetype, componentType, component$1, changeset, handleExclusiveRelation) {
1358
+ const componentId = getComponentIdFromRelationId(componentType);
1359
+ if (componentId !== void 0) {
1360
+ handleExclusiveRelation(entityId, currentArchetype, componentId);
1361
+ if (isDontFragmentComponent(componentId)) {
1362
+ const wildcardMarker = relation(componentId, "*");
1363
+ if (!currentArchetype.componentTypes.includes(wildcardMarker)) changeset.set(wildcardMarker, void 0);
1364
+ }
1365
+ }
1366
+ changeset.set(componentType, component$1);
1367
+ }
1368
+ function processDeleteCommand(entityId, currentArchetype, componentType, changeset) {
1369
+ const componentId = getComponentIdFromRelationId(componentType);
1370
+ if (isWildcardRelationId(componentType) && componentId !== void 0) removeWildcardRelations(entityId, currentArchetype, componentId, changeset);
1371
+ else {
1372
+ changeset.delete(componentType);
1373
+ maybeRemoveWildcardMarker(entityId, currentArchetype, componentType, componentId, changeset);
1374
+ }
1375
+ }
1376
+ function removeMatchingRelations(entityId, archetype, baseComponentId, changeset) {
1377
+ for (const componentType of archetype.componentTypes) if (getComponentIdFromRelationId(componentType) === baseComponentId) changeset.delete(componentType);
1378
+ const entityData = archetype.getEntity(entityId);
1379
+ if (entityData) for (const [componentType] of entityData) {
1380
+ if (archetype.componentTypes.includes(componentType)) continue;
1381
+ if (getComponentIdFromRelationId(componentType) === baseComponentId) changeset.delete(componentType);
1382
+ }
1383
+ }
1384
+ function removeWildcardRelations(entityId, currentArchetype, baseComponentId, changeset) {
1385
+ removeMatchingRelations(entityId, currentArchetype, baseComponentId, changeset);
1386
+ if (isDontFragmentComponent(baseComponentId)) changeset.delete(relation(baseComponentId, "*"));
1387
+ }
1388
+ function maybeRemoveWildcardMarker(entityId, archetype, removedComponentType, componentId, changeset) {
1389
+ if (componentId === void 0 || !isDontFragmentComponent(componentId)) return;
1390
+ const wildcardMarker = relation(componentId, "*");
1391
+ const entityData = archetype.getEntity(entityId);
1392
+ if (!entityData) {
1393
+ changeset.delete(wildcardMarker);
1394
+ return;
1395
+ }
1396
+ for (const [otherComponentType] of entityData) {
1397
+ if (otherComponentType === removedComponentType) continue;
1398
+ if (otherComponentType === wildcardMarker) continue;
1399
+ if (changeset.removes.has(otherComponentType)) continue;
1400
+ if (getComponentIdFromRelationId(otherComponentType) === componentId) return;
1401
+ }
1402
+ changeset.delete(wildcardMarker);
1403
+ }
1404
+ function applyChangeset(ctx, entityId, currentArchetype, changeset, entityToArchetype) {
1405
+ const currentEntityData = currentArchetype.getEntity(entityId);
1406
+ const allCurrentComponentTypes = currentEntityData ? Array.from(currentEntityData.keys()) : currentArchetype.componentTypes;
1407
+ const finalComponentTypes = changeset.getFinalComponentTypes(allCurrentComponentTypes);
1408
+ const removedComponents = /* @__PURE__ */ new Map();
1409
+ if (finalComponentTypes) if (!areComponentTypesEqual(filterRegularComponentTypes(allCurrentComponentTypes), filterRegularComponentTypes(finalComponentTypes))) moveEntityToNewArchetype(ctx, entityId, currentArchetype, finalComponentTypes, changeset, removedComponents, entityToArchetype);
1410
+ else updateEntityInSameArchetype(ctx, entityId, currentArchetype, changeset, removedComponents);
1411
+ else updateEntityInSameArchetype(ctx, entityId, currentArchetype, changeset, removedComponents);
1412
+ return removedComponents;
1413
+ }
1414
+ function moveEntityToNewArchetype(ctx, entityId, currentArchetype, finalComponentTypes, changeset, removedComponents, entityToArchetype) {
1415
+ const newArchetype = ctx.ensureArchetype(finalComponentTypes);
1416
+ const currentComponents = currentArchetype.removeEntity(entityId);
1417
+ for (const componentType of changeset.removes) removedComponents.set(componentType, currentComponents.get(componentType));
1418
+ newArchetype.addEntity(entityId, changeset.applyTo(currentComponents));
1419
+ entityToArchetype.set(entityId, newArchetype);
1420
+ }
1421
+ function updateEntityInSameArchetype(ctx, entityId, currentArchetype, changeset, removedComponents) {
1422
+ applyDontFragmentChanges(ctx.dontFragmentRelations, entityId, changeset, removedComponents);
1423
+ for (const [componentType, component$1] of changeset.adds) {
1424
+ if (isDontFragmentRelation(componentType)) continue;
1425
+ currentArchetype.set(entityId, componentType, component$1);
1426
+ }
1427
+ }
1428
+ function applyDontFragmentChanges(dontFragmentRelations, entityId, changeset, removedComponents) {
1429
+ let entityRelations = dontFragmentRelations.get(entityId);
1430
+ for (const componentType of changeset.removes) if (isDontFragmentRelation(componentType)) {
1431
+ if (entityRelations) {
1432
+ const removedValue = entityRelations.get(componentType);
1433
+ if (removedValue !== void 0 || entityRelations.has(componentType)) {
1434
+ removedComponents.set(componentType, removedValue);
1435
+ entityRelations.delete(componentType);
1436
+ }
1437
+ }
1438
+ }
1439
+ for (const [componentType, component$1] of changeset.adds) if (isDontFragmentRelation(componentType)) {
1440
+ if (!entityRelations) {
1441
+ entityRelations = /* @__PURE__ */ new Map();
1442
+ dontFragmentRelations.set(entityId, entityRelations);
1443
+ }
1444
+ entityRelations.set(componentType, component$1);
1445
+ }
1446
+ if (entityRelations && entityRelations.size === 0) dontFragmentRelations.delete(entityId);
1447
+ }
1448
+ function filterRegularComponentTypes(componentTypes) {
1449
+ const regularTypes = [];
1450
+ for (const componentType of componentTypes) {
1451
+ if (isDontFragmentWildcard(componentType)) {
1452
+ regularTypes.push(componentType);
1453
+ continue;
1454
+ }
1455
+ if (isDontFragmentRelation(componentType)) continue;
1456
+ regularTypes.push(componentType);
1457
+ }
1458
+ return regularTypes;
1459
+ }
1460
+ function areComponentTypesEqual(types1, types2) {
1461
+ if (types1.length !== types2.length) return false;
1462
+ const sorted1 = [...types1].sort((a, b) => a - b);
1463
+ const sorted2 = [...types2].sort((a, b) => a - b);
1464
+ return sorted1.every((v, i) => v === sorted2[i]);
1465
+ }
1466
+
1467
+ //#endregion
1468
+ //#region src/core/world-hooks.ts
1469
+ function triggerLifecycleHooks(ctx, entityId, addedComponents, removedComponents) {
1470
+ invokeHooksForComponents(ctx.hooks, entityId, addedComponents, "on_set");
1471
+ invokeHooksForComponents(ctx.hooks, entityId, removedComponents, "on_remove");
1472
+ triggerMultiComponentHooks(ctx, entityId, addedComponents, removedComponents);
1473
+ }
1474
+ function invokeHooksForComponents(hooks, entityId, components, hookType) {
1475
+ for (const [componentType, component$1] of components) {
1476
+ const directHooks = hooks.get(componentType);
1477
+ if (directHooks) for (const hook of directHooks) hook[hookType]?.(entityId, componentType, component$1);
1478
+ const componentId = getComponentIdFromRelationId(componentType);
1479
+ if (componentId !== void 0) {
1480
+ const wildcardHooks = hooks.get(relation(componentId, "*"));
1481
+ if (wildcardHooks) for (const hook of wildcardHooks) hook[hookType]?.(entityId, componentType, component$1);
1482
+ }
1483
+ }
1484
+ }
1485
+ function triggerMultiComponentHooks(ctx, entityId, addedComponents, removedComponents) {
1486
+ for (const { componentTypes, requiredComponents, hook } of ctx.multiHooks) {
1487
+ const anyRequiredAdded = requiredComponents.some((c) => addedComponents.has(c));
1488
+ const anyRequiredRemoved = requiredComponents.some((c) => removedComponents.has(c));
1489
+ if (anyRequiredAdded && hook.on_set && entityHasAllComponents(ctx, entityId, requiredComponents)) hook.on_set(entityId, componentTypes, collectMultiHookComponents(ctx, entityId, componentTypes));
1490
+ if (anyRequiredRemoved && hook.on_remove && entityHadAllComponentsBefore(ctx, entityId, requiredComponents, removedComponents)) hook.on_remove(entityId, componentTypes, collectMultiHookComponentsWithRemoved(ctx, entityId, componentTypes, removedComponents));
1491
+ }
1492
+ }
1493
+ function entityHasAllComponents(ctx, entityId, requiredComponents) {
1494
+ return requiredComponents.every((c) => ctx.has(entityId, c));
1495
+ }
1496
+ function entityHadAllComponentsBefore(ctx, entityId, requiredComponents, removedComponents) {
1497
+ return requiredComponents.every((c) => removedComponents.has(c) || ctx.has(entityId, c));
1498
+ }
1499
+ function collectMultiHookComponents(ctx, entityId, componentTypes) {
1500
+ return componentTypes.map((ct) => isOptionalEntityId(ct) ? ctx.getOptional(entityId, ct.optional) : ctx.get(entityId, ct));
1501
+ }
1502
+ function collectMultiHookComponentsWithRemoved(ctx, entityId, componentTypes, removedComponents) {
1503
+ return componentTypes.map((ct) => {
1504
+ if (isOptionalEntityId(ct)) {
1505
+ const optionalId = ct.optional;
1506
+ return removedComponents.has(optionalId) ? { value: removedComponents.get(optionalId) } : ctx.getOptional(entityId, optionalId);
1507
+ }
1508
+ const compId = ct;
1509
+ return removedComponents.has(compId) ? removedComponents.get(compId) : ctx.get(entityId, compId);
1510
+ });
1511
+ }
1512
+
1513
+ //#endregion
1514
+ //#region src/utils/multi-map.ts
1515
+ var MultiMap = class {
1516
+ map = /* @__PURE__ */ new Map();
1517
+ _valueCount = 0;
1518
+ get valueCount() {
1519
+ return this._valueCount;
1520
+ }
1521
+ get keyCount() {
1522
+ return this.map.size;
1523
+ }
1524
+ hasKey(key) {
1525
+ return this.map.has(key);
1526
+ }
1527
+ has(key, value) {
1528
+ const set = this.map.get(key);
1529
+ if (!set) return false;
1530
+ if (arguments.length === 1) return true;
1531
+ return set.has(value);
1532
+ }
1533
+ add(key, value) {
1534
+ let set = this.map.get(key);
1535
+ if (!set) {
1536
+ set = /* @__PURE__ */ new Set();
1537
+ this.map.set(key, set);
1538
+ }
1539
+ if (!set.has(value)) {
1540
+ set.add(value);
1541
+ this._valueCount++;
1542
+ }
1543
+ }
1544
+ remove(key, value) {
1545
+ const set = this.map.get(key);
1546
+ if (!set) return false;
1547
+ if (!set.has(value)) return false;
1548
+ set.delete(value);
1549
+ this._valueCount--;
1550
+ if (set.size === 0) this.map.delete(key);
1551
+ return true;
1552
+ }
1553
+ deleteKey(key) {
1554
+ const set = this.map.get(key);
1555
+ if (!set) return false;
1556
+ this._valueCount -= set.size;
1557
+ this.map.delete(key);
1558
+ return true;
1559
+ }
1560
+ get(key) {
1561
+ const set = this.map.get(key);
1562
+ return set ? new Set(set) : /* @__PURE__ */ new Set();
1563
+ }
1564
+ *keys() {
1565
+ yield* this.map.keys();
1566
+ }
1567
+ *values() {
1568
+ for (const set of this.map.values()) for (const v of set) yield v;
1569
+ }
1570
+ [Symbol.iterator]() {
1571
+ return this.entries();
1572
+ }
1573
+ *entries() {
1574
+ for (const [k, set] of this.map.entries()) for (const v of set) yield [k, v];
1575
+ }
1576
+ clear() {
1577
+ this.map.clear();
1578
+ this._valueCount = 0;
1579
+ }
1580
+ };
1581
+
1582
+ //#endregion
1583
+ //#region src/core/world-references.ts
1584
+ function trackEntityReference(entityReferences, sourceEntityId, componentType, targetEntityId) {
1585
+ if (!entityReferences.has(targetEntityId)) entityReferences.set(targetEntityId, new MultiMap());
1586
+ entityReferences.get(targetEntityId).add(sourceEntityId, componentType);
1587
+ }
1588
+ function untrackEntityReference(entityReferences, sourceEntityId, componentType, targetEntityId) {
1589
+ const references = entityReferences.get(targetEntityId);
1590
+ if (references) {
1591
+ references.remove(sourceEntityId, componentType);
1592
+ if (references.keyCount === 0) entityReferences.delete(targetEntityId);
1593
+ }
1594
+ }
1595
+ function getEntityReferences(entityReferences, targetEntityId) {
1596
+ return entityReferences.get(targetEntityId) ?? new MultiMap();
1597
+ }
1598
+
1599
+ //#endregion
1600
+ //#region src/core/world.ts
1435
1601
  /**
1436
1602
  * World class for ECS architecture
1437
1603
  * Manages entities and components
1438
1604
  */
1439
1605
  var World = class {
1440
- /** Manages allocation and deallocation of entity IDs */
1441
1606
  entityIdManager = new EntityIdManager();
1442
- /** Array of all archetypes in the world */
1443
1607
  archetypes = [];
1444
- /** Maps archetype signatures (component type signatures) to archetype instances */
1445
1608
  archetypeBySignature = /* @__PURE__ */ new Map();
1446
- /** Maps entity IDs to their current archetype */
1447
1609
  entityToArchetype = /* @__PURE__ */ new Map();
1448
- /** Maps component types to arrays of archetypes that contain them */
1449
1610
  archetypesByComponent = /* @__PURE__ */ new Map();
1450
- /** Tracks which entities reference each entity as a component type */
1451
1611
  entityReferences = /* @__PURE__ */ new Map();
1452
- /** Storage for dontFragment relations - maps entity ID to a map of relation type to component data */
1453
1612
  dontFragmentRelations = /* @__PURE__ */ new Map();
1454
- /** Array of all active queries for archetype change notifications */
1455
1613
  queries = [];
1456
- /** Cache for queries keyed by component types and filter signatures */
1457
1614
  queryCache = /* @__PURE__ */ new Map();
1458
- /** Buffers structural changes for deferred execution */
1459
1615
  commandBuffer = new CommandBuffer((entityId, commands) => this.executeEntityCommands(entityId, commands));
1460
- /** Stores lifecycle hooks for component and relation events */
1461
1616
  hooks = /* @__PURE__ */ new Map();
1462
- /** Stores multi-component lifecycle hooks */
1463
1617
  multiHooks = /* @__PURE__ */ new Set();
1464
- /**
1465
- * Create a new World.
1466
- * If an optional snapshot object is provided (previously produced by `world.serialize()`),
1467
- * the world will be restored from that snapshot. The snapshot may contain non-JSON values.
1468
- */
1469
- constructor(snapshot) {
1470
- if (snapshot && typeof snapshot === "object") {
1471
- if (snapshot.entityManager) this.entityIdManager.deserializeState(snapshot.entityManager);
1472
- if (Array.isArray(snapshot.entities)) for (const entry of snapshot.entities) {
1473
- const entityId = decodeSerializedId(entry.id);
1474
- const componentsArray = entry.components || [];
1475
- const componentMap = /* @__PURE__ */ new Map();
1476
- const componentTypes = [];
1477
- for (const componentEntry of componentsArray) {
1478
- const componentType = decodeSerializedId(componentEntry.type);
1479
- componentMap.set(componentType, componentEntry.value);
1480
- componentTypes.push(componentType);
1481
- }
1482
- const archetype = this.ensureArchetype(componentTypes);
1483
- archetype.addEntity(entityId, componentMap);
1484
- this.entityToArchetype.set(entityId, archetype);
1485
- for (const compType of componentTypes) {
1486
- const detailedType = getDetailedIdType(compType);
1487
- if (detailedType.type === "entity-relation") {
1488
- const targetEntityId = detailedType.targetId;
1489
- this.trackEntityReference(entityId, compType, targetEntityId);
1490
- } else if (detailedType.type === "entity") this.trackEntityReference(entityId, compType, compType);
1491
- }
1618
+ constructor(snapshot) {
1619
+ if (snapshot && typeof snapshot === "object") this.deserializeSnapshot(snapshot);
1620
+ }
1621
+ deserializeSnapshot(snapshot) {
1622
+ if (snapshot.entityManager) this.entityIdManager.deserializeState(snapshot.entityManager);
1623
+ if (Array.isArray(snapshot.entities)) for (const entry of snapshot.entities) {
1624
+ const entityId = decodeSerializedId(entry.id);
1625
+ const componentsArray = entry.components || [];
1626
+ const componentMap = /* @__PURE__ */ new Map();
1627
+ const componentTypes = [];
1628
+ for (const componentEntry of componentsArray) {
1629
+ const componentType = decodeSerializedId(componentEntry.type);
1630
+ componentMap.set(componentType, componentEntry.value);
1631
+ componentTypes.push(componentType);
1632
+ }
1633
+ const archetype = this.ensureArchetype(componentTypes);
1634
+ archetype.addEntity(entityId, componentMap);
1635
+ this.entityToArchetype.set(entityId, archetype);
1636
+ for (const compType of componentTypes) {
1637
+ const detailedType = getDetailedIdType(compType);
1638
+ if (detailedType.type === "entity-relation") trackEntityReference(this.entityReferences, entityId, compType, detailedType.targetId);
1639
+ else if (detailedType.type === "entity") trackEntityReference(this.entityReferences, entityId, compType, compType);
1492
1640
  }
1493
1641
  }
1494
1642
  }
1495
- /**
1496
- * Generate a signature string for component types array
1497
- * @returns A string signature for the component types
1498
- */
1499
1643
  createArchetypeSignature(componentTypes) {
1500
1644
  return componentTypes.join(",");
1501
1645
  }
1502
- /**
1503
- * Create a new entity
1504
- * @returns The ID of the newly created entity
1505
- */
1506
1646
  new() {
1507
1647
  const entityId = this.entityIdManager.allocate();
1508
1648
  let emptyArchetype = this.ensureArchetype([]);
@@ -1510,9 +1650,6 @@ var World = class {
1510
1650
  this.entityToArchetype.set(entityId, emptyArchetype);
1511
1651
  return entityId;
1512
1652
  }
1513
- /**
1514
- * Destroy an entity and remove all its components (immediate execution)
1515
- */
1516
1653
  destroyEntityImmediate(entityId) {
1517
1654
  const queue = [entityId];
1518
1655
  const visited = /* @__PURE__ */ new Set();
@@ -1522,7 +1659,7 @@ var World = class {
1522
1659
  visited.add(cur);
1523
1660
  const archetype = this.entityToArchetype.get(cur);
1524
1661
  if (!archetype) continue;
1525
- const componentReferences = Array.from(this.getEntityReferences(cur));
1662
+ const componentReferences = Array.from(getEntityReferences(this.entityReferences, cur));
1526
1663
  for (const [sourceEntityId, componentType] of componentReferences) {
1527
1664
  if (!this.entityToArchetype.get(sourceEntityId)) continue;
1528
1665
  if (isCascadeDeleteRelation(componentType)) {
@@ -1538,9 +1675,6 @@ var World = class {
1538
1675
  this.entityIdManager.deallocate(cur);
1539
1676
  }
1540
1677
  }
1541
- /**
1542
- * Check if an entity exists
1543
- */
1544
1678
  exists(entityId) {
1545
1679
  return this.entityToArchetype.has(entityId);
1546
1680
  }
@@ -1551,23 +1685,14 @@ var World = class {
1551
1685
  if (detailedType.type === "wildcard-relation") throw new Error(`Cannot directly add wildcard relation components: ${componentType}`);
1552
1686
  this.commandBuffer.set(entityId, componentType, component$1);
1553
1687
  }
1554
- /**
1555
- * Remove a component from an entity (deferred)
1556
- */
1557
1688
  remove(entityId, componentType) {
1558
1689
  if (!this.exists(entityId)) throw new Error(`Entity ${entityId} does not exist`);
1559
1690
  if (getDetailedIdType(componentType).type === "invalid") throw new Error(`Invalid component type: ${componentType}`);
1560
1691
  this.commandBuffer.remove(entityId, componentType);
1561
1692
  }
1562
- /**
1563
- * Destroy an entity and remove all its components (deferred)
1564
- */
1565
1693
  delete(entityId) {
1566
1694
  this.commandBuffer.delete(entityId);
1567
1695
  }
1568
- /**
1569
- * Check if an entity has a specific component
1570
- */
1571
1696
  has(entityId, componentType) {
1572
1697
  const archetype = this.entityToArchetype.get(entityId);
1573
1698
  if (!archetype) return false;
@@ -1585,16 +1710,10 @@ var World = class {
1585
1710
  }
1586
1711
  return archetype.get(entityId, componentType);
1587
1712
  }
1588
- /**
1589
- * Get optional component data for a specific entity and component type
1590
- * @param entityId The entity
1591
- * @param componentType The component type
1592
- * @returns { value: T } if component exists, undefined otherwise
1593
- */
1594
1713
  getOptional(entityId, componentType) {
1595
1714
  const archetype = this.entityToArchetype.get(entityId);
1596
1715
  if (!archetype) throw new Error(`Entity ${entityId} does not exist`);
1597
- if (isWildcardRelationId(componentType)) return;
1716
+ if (isWildcardRelationId(componentType)) return void 0;
1598
1717
  return archetype.getOptional(entityId, componentType);
1599
1718
  }
1600
1719
  hook(componentTypesOrSingle, hook) {
@@ -1612,7 +1731,7 @@ var World = class {
1612
1731
  if (multiHook.on_init !== void 0) {
1613
1732
  const matchingArchetypes = this.getMatchingArchetypes(requiredComponents);
1614
1733
  for (const archetype of matchingArchetypes) for (const entityId of archetype.getEntities()) {
1615
- const components = this.collectMultiHookComponents(entityId, componentTypes);
1734
+ const components = collectMultiHookComponents(this.createHooksContext(), entityId, componentTypes);
1616
1735
  multiHook.on_init(entityId, componentTypes, components);
1617
1736
  }
1618
1737
  }
@@ -1647,16 +1766,9 @@ var World = class {
1647
1766
  }
1648
1767
  }
1649
1768
  }
1650
- /**
1651
- * Execute all deferred commands immediately
1652
- */
1653
1769
  sync() {
1654
1770
  this.commandBuffer.execute();
1655
1771
  }
1656
- /**
1657
- * Create a cached query for efficient entity lookups
1658
- * @returns A Query object for the specified component types and filter
1659
- */
1660
1772
  createQuery(componentTypes, filter = {}) {
1661
1773
  const sortedTypes = [...componentTypes].sort((a, b) => a - b);
1662
1774
  const filterKey = serializeQueryFilter(filter);
@@ -1673,19 +1785,9 @@ var World = class {
1673
1785
  });
1674
1786
  return query;
1675
1787
  }
1676
- /**
1677
- * Create an EntityBuilder for convenient entity creation.
1678
- * @returns EntityBuilder
1679
- */
1680
1788
  spawn() {
1681
1789
  return new EntityBuilder(this);
1682
1790
  }
1683
- /**
1684
- * Spawn multiple entities using an EntityBuilder configuration callback
1685
- * @param count number of entities
1686
- * @param configure builder configuration callback
1687
- * @returns Created entity IDs
1688
- */
1689
1791
  spawnMany(count, configure) {
1690
1792
  const entities = [];
1691
1793
  for (let i = 0; i < count; i++) {
@@ -1694,23 +1796,13 @@ var World = class {
1694
1796
  }
1695
1797
  return entities;
1696
1798
  }
1697
- /**
1698
- * @internal Register a query for archetype update notifications
1699
- */
1700
1799
  _registerQuery(query) {
1701
1800
  this.queries.push(query);
1702
1801
  }
1703
- /**
1704
- * @internal Unregister a query
1705
- */
1706
1802
  _unregisterQuery(query) {
1707
1803
  const index = this.queries.indexOf(query);
1708
1804
  if (index !== -1) this.queries.splice(index, 1);
1709
1805
  }
1710
- /**
1711
- * Release a query reference obtained from createQuery.
1712
- * Decrements the refCount and fully disposes the query when it reaches zero.
1713
- */
1714
1806
  releaseQuery(query) {
1715
1807
  for (const [k, v] of this.queryCache.entries()) if (v.query === query) {
1716
1808
  v.refCount--;
@@ -1722,9 +1814,6 @@ var World = class {
1722
1814
  return;
1723
1815
  }
1724
1816
  }
1725
- /**
1726
- * @internal Get archetypes that match specific component types (for internal use by queries)
1727
- */
1728
1817
  getMatchingArchetypes(componentTypes) {
1729
1818
  if (componentTypes.length === 0) return [...this.archetypes];
1730
1819
  const regularComponents = [];
@@ -1736,42 +1825,24 @@ var World = class {
1736
1825
  relationId: componentType
1737
1826
  });
1738
1827
  } else regularComponents.push(componentType);
1739
- let matchingArchetypes = [];
1740
- if (regularComponents.length > 0) {
1741
- const sortedRegularTypes = [...regularComponents].sort((a, b) => a - b);
1742
- if (sortedRegularTypes.length === 1) {
1743
- const componentType = sortedRegularTypes[0];
1744
- matchingArchetypes = this.archetypesByComponent.get(componentType) || [];
1745
- } else {
1746
- const archetypeLists = sortedRegularTypes.map((type) => this.archetypesByComponent.get(type) || []);
1747
- const firstList = archetypeLists[0] || [];
1748
- const intersection = /* @__PURE__ */ new Set();
1749
- for (const archetype of firstList) {
1750
- let hasAllComponents = true;
1751
- for (let listIndex = 1; listIndex < archetypeLists.length; listIndex++) if (!archetypeLists[listIndex].includes(archetype)) {
1752
- hasAllComponents = false;
1753
- break;
1754
- }
1755
- if (hasAllComponents) intersection.add(archetype);
1756
- }
1757
- matchingArchetypes = Array.from(intersection);
1758
- }
1759
- } else matchingArchetypes = [...this.archetypes];
1760
- for (const wildcard of wildcardRelations) if (isDontFragmentComponent(wildcard.componentId)) {
1761
- const archetypesWithMarker = this.archetypesByComponent.get(wildcard.relationId) || [];
1762
- if (matchingArchetypes.length === 0) matchingArchetypes = archetypesWithMarker;
1763
- else matchingArchetypes = matchingArchetypes.filter((archetype) => archetypesWithMarker.includes(archetype));
1764
- } else matchingArchetypes = matchingArchetypes.filter((archetype) => archetype.hasRelationWithComponentId(wildcard.componentId));
1828
+ let matchingArchetypes = this.getArchetypesWithComponents(regularComponents);
1829
+ for (const { componentId, relationId } of wildcardRelations) {
1830
+ const archetypesWithMarker = this.archetypesByComponent.get(relationId) || [];
1831
+ matchingArchetypes = matchingArchetypes.length === 0 ? archetypesWithMarker : matchingArchetypes.filter((a) => archetypesWithMarker.includes(a) || a.hasRelationWithComponentId(componentId));
1832
+ }
1765
1833
  return matchingArchetypes;
1766
1834
  }
1835
+ getArchetypesWithComponents(componentTypes) {
1836
+ if (componentTypes.length === 0) return [...this.archetypes];
1837
+ if (componentTypes.length === 1) return this.archetypesByComponent.get(componentTypes[0]) || [];
1838
+ const archetypeLists = componentTypes.map((type) => this.archetypesByComponent.get(type) || []);
1839
+ return archetypeLists[0].filter((archetype) => archetypeLists.slice(1).every((list) => list.includes(archetype)));
1840
+ }
1767
1841
  query(componentTypes, includeComponents) {
1768
1842
  const matchingArchetypes = this.getMatchingArchetypes(componentTypes);
1769
1843
  if (includeComponents) {
1770
1844
  const result = [];
1771
- for (const archetype of matchingArchetypes) {
1772
- const entitiesWithData = archetype.getEntitiesWithComponents(componentTypes);
1773
- result.push(...entitiesWithData);
1774
- }
1845
+ for (const archetype of matchingArchetypes) result.push(...archetype.getEntitiesWithComponents(componentTypes));
1775
1846
  return result;
1776
1847
  } else {
1777
1848
  const result = [];
@@ -1779,10 +1850,6 @@ var World = class {
1779
1850
  return result;
1780
1851
  }
1781
1852
  }
1782
- /**
1783
- * @internal Execute commands for a single entity (for internal use by CommandBuffer)
1784
- * @returns ComponentChangeset describing the changes made
1785
- */
1786
1853
  executeEntityCommands(entityId, commands) {
1787
1854
  const changeset = new ComponentChangeset();
1788
1855
  if (commands.some((cmd) => cmd.type === "destroy")) {
@@ -1791,327 +1858,79 @@ var World = class {
1791
1858
  }
1792
1859
  const currentArchetype = this.entityToArchetype.get(entityId);
1793
1860
  if (!currentArchetype) return changeset;
1794
- this.processCommands(entityId, currentArchetype, commands, changeset);
1795
- const removedComponents = this.applyChangeset(entityId, currentArchetype, changeset);
1861
+ processCommands(entityId, currentArchetype, commands, changeset, (eid, arch, compId) => {
1862
+ if (isExclusiveComponent(compId)) removeMatchingRelations(eid, arch, compId, changeset);
1863
+ });
1864
+ const removedComponents = applyChangeset({
1865
+ dontFragmentRelations: this.dontFragmentRelations,
1866
+ ensureArchetype: (ct) => this.ensureArchetype(ct)
1867
+ }, entityId, currentArchetype, changeset, this.entityToArchetype);
1796
1868
  this.updateEntityReferences(entityId, changeset);
1797
- this.triggerLifecycleHooks(entityId, changeset.adds, removedComponents);
1869
+ triggerLifecycleHooks(this.createHooksContext(), entityId, changeset.adds, removedComponents);
1798
1870
  return changeset;
1799
1871
  }
1800
- /**
1801
- * Process commands and populate the changeset
1802
- */
1803
- processCommands(entityId, currentArchetype, commands, changeset) {
1804
- for (const command of commands) if (command.type === "set" && command.componentType) this.processSetCommand(entityId, currentArchetype, command.componentType, command.component, changeset);
1805
- else if (command.type === "delete" && command.componentType) this.processDeleteCommand(entityId, currentArchetype, command.componentType, changeset);
1806
- }
1807
- /**
1808
- * Process a set command, handling exclusive relations
1809
- */
1810
- processSetCommand(entityId, currentArchetype, componentType, component$1, changeset) {
1811
- const componentId = getComponentIdFromRelationId(componentType);
1812
- if (componentId !== void 0) {
1813
- if (componentId !== void 0 && isExclusiveComponent(componentId)) this.removeExclusiveRelations(entityId, currentArchetype, componentId, changeset);
1814
- if (componentId !== void 0 && isDontFragmentComponent(componentId)) {
1815
- const wildcardMarker = relation(componentId, "*");
1816
- if (!currentArchetype.componentTypes.includes(wildcardMarker)) changeset.set(wildcardMarker, void 0);
1817
- }
1818
- }
1819
- changeset.set(componentType, component$1);
1820
- }
1821
- /**
1822
- * Remove all relations with the same base component (for exclusive relations)
1823
- */
1824
- removeExclusiveRelations(entityId, currentArchetype, baseComponentId, changeset) {
1825
- for (const componentType of currentArchetype.componentTypes) if (this.isRelationWithComponent(componentType, baseComponentId)) changeset.delete(componentType);
1826
- const entityData = currentArchetype.getEntity(entityId);
1827
- if (entityData) for (const [componentType] of entityData) {
1828
- if (currentArchetype.componentTypes.includes(componentType)) continue;
1829
- if (this.isRelationWithComponent(componentType, baseComponentId)) changeset.delete(componentType);
1830
- }
1831
- }
1832
- isRelationWithComponent(componentType, baseComponentId) {
1833
- return getComponentIdFromRelationId(componentType) === baseComponentId;
1834
- }
1835
- /**
1836
- * Process a delete command, handling wildcard relations
1837
- */
1838
- processDeleteCommand(entityId, currentArchetype, componentType, changeset) {
1839
- const componentId = getComponentIdFromRelationId(componentType);
1840
- if (isWildcardRelationId(componentType) && componentId !== void 0) this.removeWildcardRelations(entityId, currentArchetype, componentId, changeset);
1841
- else {
1842
- changeset.delete(componentType);
1843
- if (componentId !== void 0 && isDontFragmentComponent(componentId)) {
1844
- const wildcardMarker = relation(componentId, "*");
1845
- const entityData = currentArchetype.getEntity(entityId);
1846
- let hasOtherRelations = false;
1847
- if (entityData) for (const [otherComponentType] of entityData) {
1848
- if (otherComponentType === componentType) continue;
1849
- if (otherComponentType === wildcardMarker) continue;
1850
- if (changeset.removes.has(otherComponentType)) continue;
1851
- if (getComponentIdFromRelationId(otherComponentType) === componentId) {
1852
- hasOtherRelations = true;
1853
- break;
1854
- }
1855
- }
1856
- if (!hasOtherRelations) changeset.delete(wildcardMarker);
1857
- }
1858
- }
1859
- }
1860
- /**
1861
- * Remove all relations matching a wildcard component ID
1862
- */
1863
- removeWildcardRelations(entityId, currentArchetype, baseComponentId, changeset) {
1864
- for (const componentType of currentArchetype.componentTypes) if (this.isRelationWithComponent(componentType, baseComponentId)) changeset.delete(componentType);
1865
- const entityData = currentArchetype.getEntity(entityId);
1866
- if (entityData) for (const [componentType] of entityData) {
1867
- if (currentArchetype.componentTypes.includes(componentType)) continue;
1868
- if (this.isRelationWithComponent(componentType, baseComponentId)) changeset.delete(componentType);
1869
- }
1870
- if (isDontFragmentComponent(baseComponentId)) {
1871
- const wildcardMarker = relation(baseComponentId, "*");
1872
- changeset.delete(wildcardMarker);
1873
- }
1872
+ createHooksContext() {
1873
+ return {
1874
+ hooks: this.hooks,
1875
+ multiHooks: this.multiHooks,
1876
+ has: (eid, ct) => this.has(eid, ct),
1877
+ get: (eid, ct) => this.get(eid, ct),
1878
+ getOptional: (eid, ct) => this.getOptional(eid, ct)
1879
+ };
1874
1880
  }
1875
- /**
1876
- * Remove a single component from an entity immediately, handling dontFragment relations correctly.
1877
- * Used by destroyEntityImmediate for non-cascade relation cleanup.
1878
- */
1879
1881
  removeComponentImmediate(entityId, componentType, targetEntityId) {
1880
1882
  const sourceArchetype = this.entityToArchetype.get(entityId);
1881
1883
  if (!sourceArchetype) return;
1882
1884
  const changeset = new ComponentChangeset();
1883
- const componentId = getComponentIdFromRelationId(componentType);
1884
1885
  changeset.delete(componentType);
1885
- if (componentId !== void 0 && isDontFragmentComponent(componentId)) {
1886
- const wildcardMarker = relation(componentId, "*");
1887
- const entityData = sourceArchetype.getEntity(entityId);
1888
- let hasOtherRelations = false;
1889
- if (entityData) for (const [otherComponentType] of entityData) {
1890
- if (otherComponentType === componentType) continue;
1891
- if (otherComponentType === wildcardMarker) continue;
1892
- if (getComponentIdFromRelationId(otherComponentType) === componentId) {
1893
- hasOtherRelations = true;
1894
- break;
1895
- }
1896
- }
1897
- if (!hasOtherRelations) changeset.delete(wildcardMarker);
1898
- }
1886
+ maybeRemoveWildcardMarker(entityId, sourceArchetype, componentType, getComponentIdFromRelationId(componentType), changeset);
1899
1887
  const removedComponent = sourceArchetype.get(entityId, componentType);
1900
- this.applyChangeset(entityId, sourceArchetype, changeset);
1901
- this.untrackEntityReference(entityId, componentType, targetEntityId);
1902
- this.triggerLifecycleHooks(entityId, /* @__PURE__ */ new Map(), new Map([[componentType, removedComponent]]));
1903
- }
1904
- /**
1905
- * Apply changeset to entity, moving to new archetype if needed
1906
- * @returns Map of removed components with their data
1907
- */
1908
- applyChangeset(entityId, currentArchetype, changeset) {
1909
- const currentEntityData = currentArchetype.getEntity(entityId);
1910
- const allCurrentComponentTypes = currentEntityData ? Array.from(currentEntityData.keys()) : currentArchetype.componentTypes;
1911
- const finalComponentTypes = changeset.getFinalComponentTypes(allCurrentComponentTypes);
1912
- const removedComponents = /* @__PURE__ */ new Map();
1913
- if (finalComponentTypes) {
1914
- const currentRegularTypes = this.filterRegularComponentTypes(allCurrentComponentTypes);
1915
- const finalRegularTypes = this.filterRegularComponentTypes(finalComponentTypes);
1916
- if (!this.areComponentTypesEqual(currentRegularTypes, finalRegularTypes)) this.moveEntityToNewArchetype(entityId, currentArchetype, finalComponentTypes, changeset, removedComponents);
1917
- else this.updateEntityInSameArchetype(entityId, currentArchetype, changeset, removedComponents);
1918
- } else this.updateEntityInSameArchetype(entityId, currentArchetype, changeset, removedComponents);
1919
- return removedComponents;
1920
- }
1921
- /**
1922
- * Move entity to a new archetype with updated components
1923
- */
1924
- moveEntityToNewArchetype(entityId, currentArchetype, finalComponentTypes, changeset, removedComponents) {
1925
- const newArchetype = this.ensureArchetype(finalComponentTypes);
1926
- const currentComponents = currentArchetype.removeEntity(entityId);
1927
- for (const componentType of changeset.removes) removedComponents.set(componentType, currentComponents.get(componentType));
1928
- newArchetype.addEntity(entityId, changeset.applyTo(currentComponents));
1929
- this.entityToArchetype.set(entityId, newArchetype);
1930
- }
1931
- /**
1932
- * Update entity in same archetype (no archetype change needed)
1933
- */
1934
- updateEntityInSameArchetype(entityId, currentArchetype, changeset, removedComponents) {
1935
- this.applyDontFragmentChanges(entityId, changeset, removedComponents);
1936
- for (const [componentType, component$1] of changeset.adds) {
1937
- if (isDontFragmentRelation(componentType)) continue;
1938
- currentArchetype.set(entityId, componentType, component$1);
1939
- }
1940
- }
1941
- /**
1942
- * Apply dontFragment relation changes directly to World's storage
1943
- * This is much more efficient than the removeEntity + addEntity approach
1944
- */
1945
- applyDontFragmentChanges(entityId, changeset, removedComponents) {
1946
- let entityRelations = this.dontFragmentRelations.get(entityId);
1947
- for (const componentType of changeset.removes) if (isDontFragmentRelation(componentType)) {
1948
- if (entityRelations) {
1949
- const removedValue = entityRelations.get(componentType);
1950
- if (removedValue !== void 0 || entityRelations.has(componentType)) {
1951
- removedComponents.set(componentType, removedValue);
1952
- entityRelations.delete(componentType);
1953
- }
1954
- }
1955
- }
1956
- for (const [componentType, component$1] of changeset.adds) if (isDontFragmentRelation(componentType)) {
1957
- if (!entityRelations) {
1958
- entityRelations = /* @__PURE__ */ new Map();
1959
- this.dontFragmentRelations.set(entityId, entityRelations);
1960
- }
1961
- entityRelations.set(componentType, component$1);
1962
- }
1963
- if (entityRelations && entityRelations.size === 0) this.dontFragmentRelations.delete(entityId);
1888
+ applyChangeset({
1889
+ dontFragmentRelations: this.dontFragmentRelations,
1890
+ ensureArchetype: (ct) => this.ensureArchetype(ct)
1891
+ }, entityId, sourceArchetype, changeset, this.entityToArchetype);
1892
+ untrackEntityReference(this.entityReferences, entityId, componentType, targetEntityId);
1893
+ triggerLifecycleHooks(this.createHooksContext(), entityId, /* @__PURE__ */ new Map(), new Map([[componentType, removedComponent]]));
1964
1894
  }
1965
- /**
1966
- * Update entity reference tracking based on changeset
1967
- */
1968
1895
  updateEntityReferences(entityId, changeset) {
1969
1896
  for (const componentType of changeset.removes) if (isEntityRelation(componentType)) {
1970
1897
  const targetId = getTargetIdFromRelationId(componentType);
1971
- this.untrackEntityReference(entityId, componentType, targetId);
1972
- } else if (componentType >= 1024) this.untrackEntityReference(entityId, componentType, componentType);
1898
+ untrackEntityReference(this.entityReferences, entityId, componentType, targetId);
1899
+ } else if (componentType >= 1024) untrackEntityReference(this.entityReferences, entityId, componentType, componentType);
1973
1900
  for (const [componentType] of changeset.adds) if (isEntityRelation(componentType)) {
1974
1901
  const targetId = getTargetIdFromRelationId(componentType);
1975
- this.trackEntityReference(entityId, componentType, targetId);
1976
- } else if (componentType >= 1024) this.trackEntityReference(entityId, componentType, componentType);
1902
+ trackEntityReference(this.entityReferences, entityId, componentType, targetId);
1903
+ } else if (componentType >= 1024) trackEntityReference(this.entityReferences, entityId, componentType, componentType);
1977
1904
  }
1978
- /**
1979
- * Get or create an archetype for the given component types
1980
- * Filters out dontFragment relations from the archetype signature
1981
- * @returns The archetype for the given component types (excluding dontFragment relations)
1982
- */
1983
1905
  ensureArchetype(componentTypes) {
1984
- const sortedTypes = this.filterRegularComponentTypes(componentTypes).sort((a, b) => a - b);
1906
+ const sortedTypes = filterRegularComponentTypes(componentTypes).sort((a, b) => a - b);
1985
1907
  const hashKey = this.createArchetypeSignature(sortedTypes);
1986
1908
  return getOrCreateWithSideEffect(this.archetypeBySignature, hashKey, () => this.createNewArchetype(sortedTypes));
1987
1909
  }
1988
- /**
1989
- * Compare two arrays of component types for equality (order-independent)
1990
- */
1991
- areComponentTypesEqual(types1, types2) {
1992
- if (types1.length !== types2.length) return false;
1993
- const sorted1 = [...types1].sort((a, b) => a - b);
1994
- const sorted2 = [...types2].sort((a, b) => a - b);
1995
- return sorted1.every((v, i) => v === sorted2[i]);
1996
- }
1997
- /**
1998
- * Filter out dontFragment relations from component types, but keep wildcard markers
1999
- */
2000
- filterRegularComponentTypes(componentTypes) {
2001
- const regularTypes = [];
2002
- for (const componentType of componentTypes) {
2003
- if (isDontFragmentWildcard(componentType)) {
2004
- regularTypes.push(componentType);
2005
- continue;
2006
- }
2007
- if (isDontFragmentRelation(componentType)) continue;
2008
- regularTypes.push(componentType);
2009
- }
2010
- return regularTypes;
2011
- }
2012
- /**
2013
- * Create a new archetype and register it with all tracking structures
2014
- */
2015
1910
  createNewArchetype(componentTypes) {
2016
1911
  const newArchetype = new Archetype(componentTypes, this.dontFragmentRelations);
2017
1912
  this.archetypes.push(newArchetype);
2018
- this.registerArchetypeInComponentIndex(newArchetype, componentTypes);
2019
- this.notifyQueriesOfNewArchetype(newArchetype);
2020
- return newArchetype;
2021
- }
2022
- /**
2023
- * Register archetype in the component-to-archetype index
2024
- */
2025
- registerArchetypeInComponentIndex(archetype, componentTypes) {
2026
1913
  for (const componentType of componentTypes) {
2027
1914
  const archetypes = this.archetypesByComponent.get(componentType) || [];
2028
- archetypes.push(archetype);
1915
+ archetypes.push(newArchetype);
2029
1916
  this.archetypesByComponent.set(componentType, archetypes);
2030
1917
  }
1918
+ for (const query of this.queries) query.checkNewArchetype(newArchetype);
1919
+ return newArchetype;
2031
1920
  }
2032
- /**
2033
- * Notify all queries to check the new archetype
2034
- */
2035
- notifyQueriesOfNewArchetype(archetype) {
2036
- for (const query of this.queries) query.checkNewArchetype(archetype);
2037
- }
2038
- /**
2039
- * Add a component reference to the reverse index when an entity is used as a component type
2040
- * @param sourceEntityId The entity that has the component
2041
- * @param componentType The component type (which may be an entity ID used as component type)
2042
- * @param targetEntityId The entity being used as component type
2043
- */
2044
- trackEntityReference(sourceEntityId, componentType, targetEntityId) {
2045
- if (!this.entityReferences.has(targetEntityId)) this.entityReferences.set(targetEntityId, new MultiMap());
2046
- this.entityReferences.get(targetEntityId).add(sourceEntityId, componentType);
2047
- }
2048
- /**
2049
- * Remove a component reference from the reverse index
2050
- * @param sourceEntityId The entity that has the component
2051
- * @param componentType The component type
2052
- * @param targetEntityId The entity being used as component type
2053
- */
2054
- untrackEntityReference(sourceEntityId, componentType, targetEntityId) {
2055
- const references = this.entityReferences.get(targetEntityId);
2056
- if (references) {
2057
- references.remove(sourceEntityId, componentType);
2058
- if (references.keyCount === 0) this.entityReferences.delete(targetEntityId);
2059
- }
2060
- }
2061
- /**
2062
- * Get all component references where a target entity is used as a component type
2063
- * @param targetEntityId The target entity
2064
- * @returns A MultiMap of sourceEntityId to componentTypes that reference the target entity
2065
- */
2066
- getEntityReferences(targetEntityId) {
2067
- return this.entityReferences.get(targetEntityId) ?? new MultiMap();
2068
- }
2069
- /**
2070
- * Check if an archetype's signature references a specific entity
2071
- * (via entity-relation targeting the entity, or using entity as component type)
2072
- */
2073
1921
  archetypeReferencesEntity(archetype, entityId) {
2074
- for (const componentType of archetype.componentTypes) {
2075
- if (componentType === entityId) return true;
2076
- if (isEntityRelation(componentType)) {
2077
- if (getTargetIdFromRelationId(componentType) === entityId) return true;
2078
- }
2079
- }
2080
- return false;
1922
+ return archetype.componentTypes.some((ct) => ct === entityId || isEntityRelation(ct) && getTargetIdFromRelationId(ct) === entityId);
2081
1923
  }
2082
- /**
2083
- * Cleanup empty archetypes that reference a specific deleted entity
2084
- * Only removes archetypes whose component types reference the entity
2085
- */
2086
1924
  cleanupArchetypesReferencingEntity(entityId) {
2087
1925
  for (let i = this.archetypes.length - 1; i >= 0; i--) {
2088
1926
  const archetype = this.archetypes[i];
2089
- if (archetype.getEntities().length === 0 && this.archetypeReferencesEntity(archetype, entityId)) {
2090
- this.removeArchetypeFromList(archetype);
2091
- this.removeArchetypeFromSignatureMap(archetype);
2092
- this.removeArchetypeFromComponentIndex(archetype);
2093
- this.removeArchetypeFromQueries(archetype);
2094
- }
1927
+ if (archetype.getEntities().length === 0 && this.archetypeReferencesEntity(archetype, entityId)) this.removeArchetype(archetype);
2095
1928
  }
2096
1929
  }
2097
- /**
2098
- * Remove archetype from the main archetypes list
2099
- */
2100
- removeArchetypeFromList(archetype) {
1930
+ removeArchetype(archetype) {
2101
1931
  const index = this.archetypes.indexOf(archetype);
2102
1932
  if (index !== -1) this.archetypes.splice(index, 1);
2103
- }
2104
- /**
2105
- * Remove archetype from the signature-to-archetype map
2106
- */
2107
- removeArchetypeFromSignatureMap(archetype) {
2108
- const hashKey = this.createArchetypeSignature(archetype.componentTypes);
2109
- this.archetypeBySignature.delete(hashKey);
2110
- }
2111
- /**
2112
- * Remove archetype from the component-to-archetypes index
2113
- */
2114
- removeArchetypeFromComponentIndex(archetype) {
1933
+ this.archetypeBySignature.delete(this.createArchetypeSignature(archetype.componentTypes));
2115
1934
  for (const componentType of archetype.componentTypes) {
2116
1935
  const archetypes = this.archetypesByComponent.get(componentType);
2117
1936
  if (archetypes) {
@@ -2122,119 +1941,8 @@ var World = class {
2122
1941
  }
2123
1942
  }
2124
1943
  }
2125
- }
2126
- /**
2127
- * Remove archetype from all queries
2128
- */
2129
- removeArchetypeFromQueries(archetype) {
2130
1944
  for (const query of this.queries) query.removeArchetype(archetype);
2131
1945
  }
2132
- /**
2133
- * Execute component lifecycle hooks for added and removed components
2134
- */
2135
- triggerLifecycleHooks(entityId, addedComponents, removedComponents) {
2136
- for (const [componentType, component$1] of addedComponents) {
2137
- const directHooks = this.hooks.get(componentType);
2138
- if (directHooks) for (const lifecycleHook of directHooks) lifecycleHook.on_set?.(entityId, componentType, component$1);
2139
- const componentId = getComponentIdFromRelationId(componentType);
2140
- if (componentId !== void 0) {
2141
- const wildcardRelationId = relation(componentId, "*");
2142
- const wildcardHooks = this.hooks.get(wildcardRelationId);
2143
- if (wildcardHooks) for (const lifecycleHook of wildcardHooks) lifecycleHook.on_set?.(entityId, componentType, component$1);
2144
- }
2145
- }
2146
- for (const [componentType, component$1] of removedComponents) {
2147
- const directHooks = this.hooks.get(componentType);
2148
- if (directHooks) for (const lifecycleHook of directHooks) lifecycleHook.on_remove?.(entityId, componentType, component$1);
2149
- const componentId = getComponentIdFromRelationId(componentType);
2150
- if (componentId !== void 0) {
2151
- const wildcardRelationId = relation(componentId, "*");
2152
- const wildcardHooks = this.hooks.get(wildcardRelationId);
2153
- if (wildcardHooks) for (const hook of wildcardHooks) hook.on_remove?.(entityId, componentType, component$1);
2154
- }
2155
- }
2156
- this.triggerMultiComponentHooks(entityId, addedComponents, removedComponents);
2157
- }
2158
- /**
2159
- * Trigger multi-component hooks based on changes
2160
- */
2161
- triggerMultiComponentHooks(entityId, addedComponents, removedComponents) {
2162
- for (const entry of this.multiHooks) {
2163
- const { componentTypes, requiredComponents, hook } = entry;
2164
- let anyRequiredAdded = false;
2165
- for (const reqComp of requiredComponents) if (addedComponents.has(reqComp)) {
2166
- anyRequiredAdded = true;
2167
- break;
2168
- }
2169
- let anyRequiredRemoved = false;
2170
- for (const reqComp of requiredComponents) if (removedComponents.has(reqComp)) {
2171
- anyRequiredRemoved = true;
2172
- break;
2173
- }
2174
- if (anyRequiredAdded && hook.on_set) {
2175
- if (this.entityHasAllComponents(entityId, requiredComponents)) {
2176
- const components = this.collectMultiHookComponents(entityId, componentTypes);
2177
- hook.on_set(entityId, componentTypes, components);
2178
- }
2179
- }
2180
- if (anyRequiredRemoved && hook.on_remove) {
2181
- if (this.entityHadAllComponentsBefore(entityId, requiredComponents, removedComponents)) {
2182
- const components = this.collectMultiHookComponentsWithRemoved(entityId, componentTypes, removedComponents);
2183
- hook.on_remove(entityId, componentTypes, components);
2184
- }
2185
- }
2186
- }
2187
- }
2188
- /**
2189
- * Check if entity currently has all required components
2190
- */
2191
- entityHasAllComponents(entityId, requiredComponents) {
2192
- for (const reqComp of requiredComponents) if (!this.has(entityId, reqComp)) return false;
2193
- return true;
2194
- }
2195
- /**
2196
- * Check if entity had all required components before removal
2197
- */
2198
- entityHadAllComponentsBefore(entityId, requiredComponents, removedComponents) {
2199
- for (const reqComp of requiredComponents) {
2200
- const wasRemoved = removedComponents.has(reqComp);
2201
- const stillHas = this.has(entityId, reqComp);
2202
- if (!wasRemoved && !stillHas) return false;
2203
- }
2204
- return true;
2205
- }
2206
- /**
2207
- * Collect component values for multi-component hook (current state)
2208
- */
2209
- collectMultiHookComponents(entityId, componentTypes) {
2210
- const result = [];
2211
- for (const ct of componentTypes) if (isOptionalEntityId(ct)) {
2212
- const optionalId = ct.optional;
2213
- result.push(this.getOptional(entityId, optionalId));
2214
- } else result.push(this.get(entityId, ct));
2215
- return result;
2216
- }
2217
- /**
2218
- * Collect component values for multi-component hook with removed components (snapshot before removal)
2219
- */
2220
- collectMultiHookComponentsWithRemoved(entityId, componentTypes, removedComponents) {
2221
- const result = [];
2222
- for (const ct of componentTypes) if (isOptionalEntityId(ct)) {
2223
- const optionalId = ct.optional;
2224
- if (removedComponents.has(optionalId)) result.push({ value: removedComponents.get(optionalId) });
2225
- else result.push(this.getOptional(entityId, optionalId));
2226
- } else {
2227
- const compId = ct;
2228
- if (removedComponents.has(compId)) result.push(removedComponents.get(compId));
2229
- else result.push(this.get(entityId, compId));
2230
- }
2231
- return result;
2232
- }
2233
- /**
2234
- * Convert the world into a plain snapshot object.
2235
- * This returns an in-memory structure and does not perform JSON stringification.
2236
- * Component values are stored as-is (they may be non-JSON-serializable).
2237
- */
2238
1946
  serialize() {
2239
1947
  const entities = [];
2240
1948
  for (const archetype of this.archetypes) {
@@ -2254,63 +1962,7 @@ var World = class {
2254
1962
  };
2255
1963
  }
2256
1964
  };
2257
- var EntityBuilder = class {
2258
- world;
2259
- components = [];
2260
- constructor(world) {
2261
- this.world = world;
2262
- }
2263
- with(componentId, value) {
2264
- this.components.push({
2265
- type: "component",
2266
- id: componentId,
2267
- value
2268
- });
2269
- return this;
2270
- }
2271
- withTag(componentId) {
2272
- this.components.push({
2273
- type: "component",
2274
- id: componentId,
2275
- value: void 0
2276
- });
2277
- return this;
2278
- }
2279
- withRelation(componentId, targetEntity, value) {
2280
- this.components.push({
2281
- type: "relation",
2282
- componentId,
2283
- targetId: targetEntity,
2284
- value
2285
- });
2286
- return this;
2287
- }
2288
- withRelationTag(componentId, targetEntity) {
2289
- this.components.push({
2290
- type: "relation",
2291
- componentId,
2292
- targetId: targetEntity,
2293
- value: void 0
2294
- });
2295
- return this;
2296
- }
2297
- /**
2298
- * Create an entity and enqueue components to be applied. This method
2299
- * does NOT call `world.sync()` automatically; callers must invoke
2300
- * `world.sync()` to apply deferred commands.
2301
- * (Previously auto-synced; now a breaking change — buildDeferred() removed.)
2302
- */
2303
- build() {
2304
- const entity = this.world.new();
2305
- for (const def of this.components) if (def.type === "component") this.world.set(entity, def.id, def.value);
2306
- else {
2307
- const relationId = relation(def.componentId, def.targetId);
2308
- this.world.set(entity, relationId, def.value);
2309
- }
2310
- return entity;
2311
- }
2312
- };
2313
1965
 
2314
1966
  //#endregion
2315
- export { decodeRelationId as a, isComponentId as c, isWildcardRelationId as d, relation as f, component as i, isEntityId as l, World as n, getComponentIdByName as o, Query as r, getComponentNameById as s, EntityBuilder as t, isRelationId as u };
1967
+ export { getComponentIdByName as a, isWildcardRelationId as c, isEntityId as d, isRelationId as f, component as i, relation as l, Query as n, getComponentNameById as o, EntityBuilder as r, decodeRelationId as s, World as t, isComponentId as u };
2316
1968
  //# sourceMappingURL=world.mjs.map