koota 0.6.2 → 0.6.4

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/README.md CHANGED
@@ -91,10 +91,10 @@ function RocketView({ entity }) {
91
91
  Use actions to safely modify Koota from inside of React in either effects or events.
92
92
 
93
93
  ```js
94
- import { defineActions } from 'koota'
94
+ import { createActions } from 'koota'
95
95
  import { useActions } from 'koota/react'
96
96
 
97
- const actions = defineActions((world) => ({
97
+ const actions = createActions((world) => ({
98
98
  spawnShip: (position) => world.spawn(Position(position), Velocity),
99
99
  destroyAllShips: () => {
100
100
  world.query(Position, Velocity).forEach((entity) => {
@@ -136,7 +136,7 @@ useEffect(() => {
136
136
 
137
137
  ### Relations
138
138
 
139
- Koota supports relations between entities using the `relation` function. Relations allow you to create connections between entities and query them efficiently.
139
+ Koota supports relations between entities using the `relation` function. Relations allow you to build graphs by creating connections between entities with efficient queries.
140
140
 
141
141
  ```js
142
142
  const ChildOf = relation()
@@ -147,6 +147,24 @@ const child = world.spawn(ChildOf(parent))
147
147
  const entity = world.queryFirst(ChildOf(parent)) // Returns child
148
148
  ```
149
149
 
150
+ A relation is typically owned by the entity that needs to express it. The **source** is the entity that has the relation added, and the **target** is the entity it points to.
151
+
152
+ ```mermaid
153
+ flowchart BT
154
+ subgraph Parent
155
+ end
156
+ subgraph childA["Child"]
157
+ COA["ChildOf(Parent)"]
158
+ end
159
+ subgraph childB["Child"]
160
+ COB["ChildOf(Parent)"]
161
+ end
162
+ COA --> Parent
163
+ COB --> Parent
164
+ ```
165
+
166
+ In `child.add(ChildOf(parent))`, child is the source and parent is the target. This design means the parent doesn't need to know about its children, instead children care about their parent, optimzing batch queries.
167
+
150
168
  #### With data
151
169
 
152
170
  Relations can contain data like any trait.
@@ -167,12 +185,14 @@ inventory.set(Contains(gold), { amount: 20 })
167
185
  const data = inventory.get(Contains(gold)) // { amount: 20 }
168
186
  ```
169
187
 
170
- #### Auto remove target
188
+ #### Auto destroy
171
189
 
172
- Relations can automatically remove target entities and their descendants.
190
+ Relations can automatically destroy related entities when their counterpart is destroyed using the `autoDestroy` option.
191
+
192
+ **Destroy orphans.** When a target is destroyed, destroy all sources pointing to it. This is commonly used for hierarchies when you want to clean up any detached graphs. It can be enabled with the `'orphan'` or `'source'` option.
173
193
 
174
194
  ```js
175
- const ChildOf = relation({ autoRemoveTarget: true })
195
+ const ChildOf = relation({ autoDestroy: 'orphan' }) // Or 'source'
176
196
 
177
197
  const parent = world.spawn()
178
198
  const child = world.spawn(ChildOf(parent))
@@ -183,6 +203,21 @@ parent.destroy()
183
203
  world.has(child) // False, the child and grandchild are destroyed too
184
204
  ```
185
205
 
206
+ **Destroy targets.** When a source is destroyed, destroy all its targets.
207
+
208
+ ```js
209
+ const Contains = relation({ autoDestroy: 'target' })
210
+
211
+ const container = world.spawn()
212
+ const itemA = world.spawn()
213
+ const itemB = world.spawn()
214
+
215
+ container.add(Contains(itemA), Contains(itemB))
216
+ container.destroy()
217
+
218
+ world.has(itemA) // False, items are destroyed with container
219
+ ```
220
+
186
221
  #### Exclusive relations
187
222
 
188
223
  Exclusive relations ensure each entity can only have one target.
@@ -206,7 +241,27 @@ hero.has(Targeting(goblin)) // True
206
241
  > ⚠️ **Experimental**<br>
207
242
  > This API is experimental and may change in future versions. Please provide feedback on GitHub or Discord.
208
243
 
209
- Ordered relations maintain a list of related entities with bidirectional sync.
244
+ Ordered relations maintain a list of related entities with
245
+ bidirectional sync.
246
+
247
+ A query like `world.query(ChildOf(parent))` returns a flat list of children without any ordering. If you need an ordered list, you'd have to store an order field and sort every time you query.
248
+
249
+ An ordered relation solves this by caching the order on the target. It's a trait added to the parent that maintains a view of all entities targeting it.
250
+
251
+ ```mermaid
252
+ flowchart BT
253
+ subgraph Parent
254
+ OC["OrderedChildren → [Child B, Child A]"]
255
+ end
256
+ subgraph childA["Child A"]
257
+ COA["ChildOf(Parent)"]
258
+ end
259
+ subgraph childB["Child B"]
260
+ COB["ChildOf(Parent)"]
261
+ end
262
+ COA --> Parent
263
+ COB --> Parent
264
+ ```
210
265
 
211
266
  ```js
212
267
  import { relation, ordered } from 'koota'
@@ -227,7 +282,7 @@ child2.add(ChildOf(parent)) // child2 automatically added to list
227
282
  Ordered relations support array methods like `push()`, `pop()`, `shift()`, `unshift()`, and `splice()`, plus special methods `moveTo()` and `insert()` for precise control. Changes to the list automatically sync with relations, and vice versa.
228
283
 
229
284
  > ⚠️ **Performance note**<br>
230
- > Ordered relations are more expensive to update than regular relations but enable faster traversal when order matters. Use them only when entity order is essential.
285
+ > Ordered relations requires additional bookkeeping where the cost of ordering is paid during structural changes (add, remove, move) instead of at query time. Use ordered relations only when entity order is essential or when hierarchical search (looping over children) is necessary.
231
286
 
232
287
  #### Querying relations
233
288
 
@@ -1106,11 +1161,11 @@ function InventoryDisplay({ entity }) {
1106
1161
 
1107
1162
  ### `useActions`
1108
1163
 
1109
- Returns actions bound to the world that is in context. Use actions created by `defineActions`.
1164
+ Returns actions bound to the world that is in context. Use actions created by `createActions`.
1110
1165
 
1111
1166
  ```js
1112
1167
  // Create actions
1113
- const actions = defineActions((world) => ({
1168
+ const actions = createActions((world) => ({
1114
1169
  spawnPlayer: () => world.spawn(IsPlayer).
1115
1170
  destroyAllPlayers: () => {
1116
1171
  world.query(IsPlayer).forEach((player) => {
@@ -414,12 +414,14 @@ var OrderedList = class extends Array {
414
414
  world;
415
415
  parent;
416
416
  relation;
417
+ orderedTrait;
417
418
  _syncing = false;
418
- constructor(world, parent, relation2, items = []) {
419
+ constructor(world, parent, relation2, orderedTrait, items = []) {
419
420
  super(...items);
420
421
  this.world = world;
421
422
  this.parent = parent;
422
423
  this.relation = relation2;
424
+ this.orderedTrait = orderedTrait;
423
425
  }
424
426
  get [Symbol.toStringTag]() {
425
427
  return "OrderedList";
@@ -433,7 +435,9 @@ var OrderedList = class extends Array {
433
435
  for (const item of items) {
434
436
  addTrait(this.world, item, this.relation(this.parent));
435
437
  }
436
- return super.push(...items);
438
+ const result = super.push(...items);
439
+ setChanged(this.world, this.parent, this.orderedTrait);
440
+ return result;
437
441
  } finally {
438
442
  this._syncing = false;
439
443
  }
@@ -447,6 +451,7 @@ var OrderedList = class extends Array {
447
451
  const item = super.pop();
448
452
  if (item !== void 0) {
449
453
  removeTrait(this.world, item, this.relation(this.parent));
454
+ setChanged(this.world, this.parent, this.orderedTrait);
450
455
  }
451
456
  return item;
452
457
  } finally {
@@ -462,6 +467,7 @@ var OrderedList = class extends Array {
462
467
  const item = super.shift();
463
468
  if (item !== void 0) {
464
469
  removeTrait(this.world, item, this.relation(this.parent));
470
+ setChanged(this.world, this.parent, this.orderedTrait);
465
471
  }
466
472
  return item;
467
473
  } finally {
@@ -477,7 +483,9 @@ var OrderedList = class extends Array {
477
483
  for (const item of items) {
478
484
  addTrait(this.world, item, this.relation(this.parent));
479
485
  }
480
- return super.unshift(...items);
486
+ const result = super.unshift(...items);
487
+ setChanged(this.world, this.parent, this.orderedTrait);
488
+ return result;
481
489
  } finally {
482
490
  this._syncing = false;
483
491
  }
@@ -495,6 +503,9 @@ var OrderedList = class extends Array {
495
503
  for (const item of items) {
496
504
  addTrait(this.world, item, this.relation(this.parent));
497
505
  }
506
+ if (removed.length > 0 || items.length > 0) {
507
+ setChanged(this.world, this.parent, this.orderedTrait);
508
+ }
498
509
  return removed;
499
510
  } finally {
500
511
  this._syncing = false;
@@ -505,6 +516,7 @@ var OrderedList = class extends Array {
505
516
  */
506
517
  sort(compareFn) {
507
518
  super.sort(compareFn);
519
+ setChanged(this.world, this.parent, this.orderedTrait);
508
520
  return this;
509
521
  }
510
522
  /**
@@ -512,6 +524,7 @@ var OrderedList = class extends Array {
512
524
  */
513
525
  reverse() {
514
526
  super.reverse();
527
+ setChanged(this.world, this.parent, this.orderedTrait);
515
528
  return this;
516
529
  }
517
530
  /**
@@ -544,6 +557,7 @@ var OrderedList = class extends Array {
544
557
  if (fromIndex === toIndex) return;
545
558
  super.splice(fromIndex, 1);
546
559
  super.splice(toIndex, 0, item);
560
+ setChanged(this.world, this.parent, this.orderedTrait);
547
561
  }
548
562
  /**
549
563
  * Insert an entity at a specific index and add its relation pair.
@@ -553,6 +567,7 @@ var OrderedList = class extends Array {
553
567
  try {
554
568
  addTrait(this.world, item, this.relation(this.parent));
555
569
  super.splice(index, 0, item);
570
+ setChanged(this.world, this.parent, this.orderedTrait);
556
571
  } finally {
557
572
  this._syncing = false;
558
573
  }
@@ -564,6 +579,7 @@ var OrderedList = class extends Array {
564
579
  _appendWithoutSync(item) {
565
580
  if (!this._syncing) {
566
581
  super.push(item);
582
+ setChanged(this.world, this.parent, this.orderedTrait);
567
583
  }
568
584
  }
569
585
  /**
@@ -575,6 +591,7 @@ var OrderedList = class extends Array {
575
591
  const index = this.indexOf(item);
576
592
  if (index !== -1) {
577
593
  super.splice(index, 1);
594
+ setChanged(this.world, this.parent, this.orderedTrait);
578
595
  }
579
596
  }
580
597
  }
@@ -739,17 +756,17 @@ function registerTrait(world, trait2) {
739
756
  ctx.traitInstances[traitId_1_$f] = data;
740
757
  world.traits.add(trait2);
741
758
  if (traitCtx.relation) ctx.relations.add(traitCtx.relation);
742
- if ($orderedTargetsTrait in trait2) setupOrderedTraitSync(world, trait2);
743
- const ctx_3_$f = world[$internal];
744
- ctx_3_$f.bitflag *= 2;
745
- if (ctx_3_$f.bitflag >= 2 ** 31) {
746
- ctx_3_$f.bitflag = 1;
747
- ctx_3_$f.entityMasks.push([]);
759
+ const ctx_2_$f = world[$internal];
760
+ ctx_2_$f.bitflag *= 2;
761
+ if (ctx_2_$f.bitflag >= 2 ** 31) {
762
+ ctx_2_$f.bitflag = 1;
763
+ ctx_2_$f.entityMasks.push([]);
748
764
  }
765
+ if ($orderedTargetsTrait in trait2) setupOrderedTraitSync(world, trait2);
749
766
  }
750
767
  function getOrderedTrait(world, entity, trait2) {
751
768
  const relation2 = trait2[$orderedTargetsTrait].relation;
752
- return new OrderedList(world, entity, relation2);
769
+ return new OrderedList(world, entity, relation2, trait2);
753
770
  }
754
771
  function addTrait(world, entity, ...traits) {
755
772
  for (let i = 0; i < traits.length; i++) {
@@ -1200,10 +1217,20 @@ function createRelation(definition) {
1200
1217
  const relationTrait = trait(definition?.store ?? {});
1201
1218
  const traitCtx = relationTrait[$internal];
1202
1219
  traitCtx.relation = null;
1220
+ let autoDestroy = false;
1221
+ if (definition?.autoDestroy === "orphan" || definition?.autoDestroy === "source") {
1222
+ autoDestroy = "source";
1223
+ } else if (definition?.autoDestroy === "target") {
1224
+ autoDestroy = "target";
1225
+ }
1226
+ if (definition?.autoRemoveTarget) {
1227
+ console.warn(`Koota: 'autoRemoveTarget' is deprecated. Use 'autoDestroy: "orphan"' instead.`);
1228
+ autoDestroy = "source";
1229
+ }
1203
1230
  const relationCtx = {
1204
1231
  trait: relationTrait,
1205
1232
  exclusive: definition?.exclusive ?? false,
1206
- autoRemoveTarget: definition?.autoRemoveTarget ?? false
1233
+ autoDestroy
1207
1234
  };
1208
1235
  function relationFn(target, params) {
1209
1236
  if (target === void 0) throw Error("Relation target is undefined");
@@ -2117,10 +2144,10 @@ function createQueryInstance(world, parameters) {
2117
2144
  traitMatches = (oldMask & bitflag) === 0 && (currentMask & bitflag) === bitflag;
2118
2145
  break;
2119
2146
  case "remove":
2120
- traitMatches = (oldMask & bitflag) === bitflag && (currentMask & bitflag) === 0 || (oldMask & bitflag) === 0 && (currentMask & bitflag) === 0 && (dirtyMask[generationId][eid] & bitflag) === bitflag;
2147
+ traitMatches = (oldMask & bitflag) === bitflag && (currentMask & bitflag) === 0 || (oldMask & bitflag) === 0 && (currentMask & bitflag) === 0 && ((dirtyMask[generationId]?.[eid] ?? 0) & bitflag) === bitflag;
2121
2148
  break;
2122
2149
  case "change":
2123
- traitMatches = (changedMask[generationId][eid] & bitflag) === bitflag;
2150
+ traitMatches = ((changedMask[generationId]?.[eid] ?? 0) & bitflag) === bitflag;
2124
2151
  break;
2125
2152
  }
2126
2153
  if (!traitMatches) {
@@ -2147,9 +2174,7 @@ var queryId = 0;
2147
2174
  function createQuery(...parameters) {
2148
2175
  const hash = createQueryHash(parameters);
2149
2176
  const existing = universe.cachedQueries.get(hash);
2150
- if (existing) {
2151
- return existing;
2152
- }
2177
+ if (existing) return existing;
2153
2178
  const id = queryId++;
2154
2179
  const queryRef = Object.freeze({
2155
2180
  [$queryRef]: true,
@@ -2335,12 +2360,35 @@ function destroyEntity(world, entity) {
2335
2360
  if (processedEntities.has(currentEntity)) continue;
2336
2361
  processedEntities.add(currentEntity);
2337
2362
  for (const relation2 of ctx.relations) {
2338
- const subjects = getEntitiesWithRelationTo(world, relation2, currentEntity);
2339
- for (const subject of subjects) {
2340
- if (!world.has(subject)) continue;
2341
- const relationCtx = relation2[$internal];
2342
- cleanupRelationTarget(world, relation2, subject, currentEntity);
2343
- if (relationCtx.autoRemoveTarget) entityQueue.push(subject);
2363
+ const relationCtx = relation2[$internal];
2364
+ const sources = getEntitiesWithRelationTo(world, relation2, currentEntity);
2365
+ for (const source of sources) {
2366
+ if (!world.has(source)) continue;
2367
+ cleanupRelationTarget(world, relation2, source, currentEntity);
2368
+ if (relationCtx.autoDestroy === "source") entityQueue.push(source);
2369
+ }
2370
+ if (relationCtx.autoDestroy === "target") {
2371
+ let result_getRelationTargets_1_$f;
2372
+ const ctx_1_$f = world[$internal];
2373
+ const relationCtx_1_$f = relation2[$internal];
2374
+ const traitData_1_$f = ctx_1_$f.traitInstances[relationCtx_1_$f.trait.id];
2375
+ if (!traitData_1_$f || !traitData_1_$f.relationTargets) {
2376
+ result_getRelationTargets_1_$f = [];
2377
+ } else {
2378
+ const eid_1_$f = currentEntity & ENTITY_ID_MASK;
2379
+ if (relationCtx_1_$f.exclusive) {
2380
+ const target_1_$f = traitData_1_$f.relationTargets[eid_1_$f];
2381
+ result_getRelationTargets_1_$f = target_1_$f !== void 0 ? [target_1_$f] : [];
2382
+ } else {
2383
+ const targets_1_$f = traitData_1_$f.relationTargets[eid_1_$f];
2384
+ result_getRelationTargets_1_$f = targets_1_$f !== void 0 ? targets_1_$f.slice() : [];
2385
+ }
2386
+ }
2387
+ const targets = result_getRelationTargets_1_$f;
2388
+ for (const target of targets) {
2389
+ if (!world.has(target)) continue;
2390
+ if (!processedEntities.has(target)) entityQueue.push(target);
2391
+ }
2344
2392
  }
2345
2393
  }
2346
2394
  const entityTraits = ctx.entityTraits.get(currentEntity);
@@ -2364,6 +2412,7 @@ function destroyEntity(world, entity) {
2364
2412
  function createWorld(optionsOrFirstTrait, ...traits) {
2365
2413
  const id = allocateWorldId(universe.worldIndex);
2366
2414
  let isInitialized = false;
2415
+ let lazyTraits;
2367
2416
  const world = {
2368
2417
  [$internal]: {
2369
2418
  entityIndex: createEntityIndex(id),
@@ -2396,6 +2445,10 @@ function createWorld(optionsOrFirstTrait, ...traits) {
2396
2445
  }
2397
2446
  const traitId_0_$f = IsExcluded.id;
2398
2447
  if (!(traitId_0_$f < ctx.traitInstances.length && ctx.traitInstances[traitId_0_$f] !== void 0)) registerTrait(world, IsExcluded);
2448
+ if (lazyTraits) {
2449
+ initTraits = lazyTraits;
2450
+ lazyTraits = void 0;
2451
+ }
2399
2452
  ctx.worldEntity = createEntity(world, IsExcluded, ...initTraits);
2400
2453
  },
2401
2454
  spawn(...spawnTraits) {
@@ -2433,6 +2486,7 @@ function createWorld(optionsOrFirstTrait, ...traits) {
2433
2486
  universe.worlds[id] = null;
2434
2487
  },
2435
2488
  reset() {
2489
+ lazyTraits = void 0;
2436
2490
  const ctx = world[$internal];
2437
2491
  world.entities.forEach((entity) => {
2438
2492
  if (world.has(entity)) {
@@ -2602,7 +2656,11 @@ function createWorld(optionsOrFirstTrait, ...traits) {
2602
2656
  traits: optionTraits = [],
2603
2657
  lazy = false
2604
2658
  } = optionsOrFirstTrait;
2605
- if (!lazy) world.init(...optionTraits);
2659
+ if (!lazy) {
2660
+ world.init(...optionTraits);
2661
+ } else {
2662
+ lazyTraits = optionTraits;
2663
+ }
2606
2664
  } else {
2607
2665
  world.init(...optionsOrFirstTrait ? [optionsOrFirstTrait, ...traits] : traits);
2608
2666
  }
package/dist/index.cjs CHANGED
@@ -227,10 +227,20 @@ function createRelation(definition) {
227
227
  const relationTrait = trait(definition?.store ?? {});
228
228
  const traitCtx = relationTrait[$internal];
229
229
  traitCtx.relation = null;
230
+ let autoDestroy = false;
231
+ if (definition?.autoDestroy === "orphan" || definition?.autoDestroy === "source") {
232
+ autoDestroy = "source";
233
+ } else if (definition?.autoDestroy === "target") {
234
+ autoDestroy = "target";
235
+ }
236
+ if (definition?.autoRemoveTarget) {
237
+ console.warn(`Koota: 'autoRemoveTarget' is deprecated. Use 'autoDestroy: "orphan"' instead.`);
238
+ autoDestroy = "source";
239
+ }
230
240
  const relationCtx = {
231
241
  trait: relationTrait,
232
242
  exclusive: definition?.exclusive ?? false,
233
- autoRemoveTarget: definition?.autoRemoveTarget ?? false
243
+ autoDestroy
234
244
  };
235
245
  function relationFn(target, params) {
236
246
  if (target === void 0) throw Error("Relation target is undefined");
@@ -679,12 +689,14 @@ var OrderedList = class extends Array {
679
689
  world;
680
690
  parent;
681
691
  relation;
692
+ orderedTrait;
682
693
  _syncing = false;
683
- constructor(world, parent, relation2, items = []) {
694
+ constructor(world, parent, relation2, orderedTrait, items = []) {
684
695
  super(...items);
685
696
  this.world = world;
686
697
  this.parent = parent;
687
698
  this.relation = relation2;
699
+ this.orderedTrait = orderedTrait;
688
700
  }
689
701
  get [Symbol.toStringTag]() {
690
702
  return "OrderedList";
@@ -698,7 +710,9 @@ var OrderedList = class extends Array {
698
710
  for (const item of items) {
699
711
  addTrait(this.world, item, this.relation(this.parent));
700
712
  }
701
- return super.push(...items);
713
+ const result = super.push(...items);
714
+ setChanged(this.world, this.parent, this.orderedTrait);
715
+ return result;
702
716
  } finally {
703
717
  this._syncing = false;
704
718
  }
@@ -712,6 +726,7 @@ var OrderedList = class extends Array {
712
726
  const item = super.pop();
713
727
  if (item !== void 0) {
714
728
  removeTrait(this.world, item, this.relation(this.parent));
729
+ setChanged(this.world, this.parent, this.orderedTrait);
715
730
  }
716
731
  return item;
717
732
  } finally {
@@ -727,6 +742,7 @@ var OrderedList = class extends Array {
727
742
  const item = super.shift();
728
743
  if (item !== void 0) {
729
744
  removeTrait(this.world, item, this.relation(this.parent));
745
+ setChanged(this.world, this.parent, this.orderedTrait);
730
746
  }
731
747
  return item;
732
748
  } finally {
@@ -742,7 +758,9 @@ var OrderedList = class extends Array {
742
758
  for (const item of items) {
743
759
  addTrait(this.world, item, this.relation(this.parent));
744
760
  }
745
- return super.unshift(...items);
761
+ const result = super.unshift(...items);
762
+ setChanged(this.world, this.parent, this.orderedTrait);
763
+ return result;
746
764
  } finally {
747
765
  this._syncing = false;
748
766
  }
@@ -760,6 +778,9 @@ var OrderedList = class extends Array {
760
778
  for (const item of items) {
761
779
  addTrait(this.world, item, this.relation(this.parent));
762
780
  }
781
+ if (removed.length > 0 || items.length > 0) {
782
+ setChanged(this.world, this.parent, this.orderedTrait);
783
+ }
763
784
  return removed;
764
785
  } finally {
765
786
  this._syncing = false;
@@ -770,6 +791,7 @@ var OrderedList = class extends Array {
770
791
  */
771
792
  sort(compareFn) {
772
793
  super.sort(compareFn);
794
+ setChanged(this.world, this.parent, this.orderedTrait);
773
795
  return this;
774
796
  }
775
797
  /**
@@ -777,6 +799,7 @@ var OrderedList = class extends Array {
777
799
  */
778
800
  reverse() {
779
801
  super.reverse();
802
+ setChanged(this.world, this.parent, this.orderedTrait);
780
803
  return this;
781
804
  }
782
805
  /**
@@ -809,6 +832,7 @@ var OrderedList = class extends Array {
809
832
  if (fromIndex === toIndex) return;
810
833
  super.splice(fromIndex, 1);
811
834
  super.splice(toIndex, 0, item);
835
+ setChanged(this.world, this.parent, this.orderedTrait);
812
836
  }
813
837
  /**
814
838
  * Insert an entity at a specific index and add its relation pair.
@@ -818,6 +842,7 @@ var OrderedList = class extends Array {
818
842
  try {
819
843
  addTrait(this.world, item, this.relation(this.parent));
820
844
  super.splice(index, 0, item);
845
+ setChanged(this.world, this.parent, this.orderedTrait);
821
846
  } finally {
822
847
  this._syncing = false;
823
848
  }
@@ -829,6 +854,7 @@ var OrderedList = class extends Array {
829
854
  _appendWithoutSync(item) {
830
855
  if (!this._syncing) {
831
856
  super.push(item);
857
+ setChanged(this.world, this.parent, this.orderedTrait);
832
858
  }
833
859
  }
834
860
  /**
@@ -840,6 +866,7 @@ var OrderedList = class extends Array {
840
866
  const index = this.indexOf(item);
841
867
  if (index !== -1) {
842
868
  super.splice(index, 1);
869
+ setChanged(this.world, this.parent, this.orderedTrait);
843
870
  }
844
871
  }
845
872
  }
@@ -1004,17 +1031,17 @@ function registerTrait(world, trait2) {
1004
1031
  ctx.traitInstances[traitId_1_$f] = data;
1005
1032
  world.traits.add(trait2);
1006
1033
  if (traitCtx.relation) ctx.relations.add(traitCtx.relation);
1007
- if ($orderedTargetsTrait in trait2) setupOrderedTraitSync(world, trait2);
1008
- const ctx_3_$f = world[$internal];
1009
- ctx_3_$f.bitflag *= 2;
1010
- if (ctx_3_$f.bitflag >= 2 ** 31) {
1011
- ctx_3_$f.bitflag = 1;
1012
- ctx_3_$f.entityMasks.push([]);
1034
+ const ctx_2_$f = world[$internal];
1035
+ ctx_2_$f.bitflag *= 2;
1036
+ if (ctx_2_$f.bitflag >= 2 ** 31) {
1037
+ ctx_2_$f.bitflag = 1;
1038
+ ctx_2_$f.entityMasks.push([]);
1013
1039
  }
1040
+ if ($orderedTargetsTrait in trait2) setupOrderedTraitSync(world, trait2);
1014
1041
  }
1015
1042
  function getOrderedTrait(world, entity, trait2) {
1016
1043
  const relation2 = trait2[$orderedTargetsTrait].relation;
1017
- return new OrderedList(world, entity, relation2);
1044
+ return new OrderedList(world, entity, relation2, trait2);
1018
1045
  }
1019
1046
  function addTrait(world, entity, ...traits) {
1020
1047
  for (let i = 0; i < traits.length; i++) {
@@ -2162,10 +2189,10 @@ function createQueryInstance(world, parameters) {
2162
2189
  traitMatches = (oldMask & bitflag) === 0 && (currentMask & bitflag) === bitflag;
2163
2190
  break;
2164
2191
  case "remove":
2165
- traitMatches = (oldMask & bitflag) === bitflag && (currentMask & bitflag) === 0 || (oldMask & bitflag) === 0 && (currentMask & bitflag) === 0 && (dirtyMask[generationId][eid] & bitflag) === bitflag;
2192
+ traitMatches = (oldMask & bitflag) === bitflag && (currentMask & bitflag) === 0 || (oldMask & bitflag) === 0 && (currentMask & bitflag) === 0 && ((dirtyMask[generationId]?.[eid] ?? 0) & bitflag) === bitflag;
2166
2193
  break;
2167
2194
  case "change":
2168
- traitMatches = (changedMask[generationId][eid] & bitflag) === bitflag;
2195
+ traitMatches = ((changedMask[generationId]?.[eid] ?? 0) & bitflag) === bitflag;
2169
2196
  break;
2170
2197
  }
2171
2198
  if (!traitMatches) {
@@ -2192,9 +2219,7 @@ var queryId = 0;
2192
2219
  function createQuery(...parameters) {
2193
2220
  const hash = createQueryHash(parameters);
2194
2221
  const existing = universe.cachedQueries.get(hash);
2195
- if (existing) {
2196
- return existing;
2197
- }
2222
+ if (existing) return existing;
2198
2223
  const id = queryId++;
2199
2224
  const queryRef = Object.freeze({
2200
2225
  [$queryRef]: true,
@@ -2380,12 +2405,35 @@ function destroyEntity(world, entity) {
2380
2405
  if (processedEntities.has(currentEntity)) continue;
2381
2406
  processedEntities.add(currentEntity);
2382
2407
  for (const relation2 of ctx.relations) {
2383
- const subjects = getEntitiesWithRelationTo(world, relation2, currentEntity);
2384
- for (const subject of subjects) {
2385
- if (!world.has(subject)) continue;
2386
- const relationCtx = relation2[$internal];
2387
- cleanupRelationTarget(world, relation2, subject, currentEntity);
2388
- if (relationCtx.autoRemoveTarget) entityQueue.push(subject);
2408
+ const relationCtx = relation2[$internal];
2409
+ const sources = getEntitiesWithRelationTo(world, relation2, currentEntity);
2410
+ for (const source of sources) {
2411
+ if (!world.has(source)) continue;
2412
+ cleanupRelationTarget(world, relation2, source, currentEntity);
2413
+ if (relationCtx.autoDestroy === "source") entityQueue.push(source);
2414
+ }
2415
+ if (relationCtx.autoDestroy === "target") {
2416
+ let result_getRelationTargets_1_$f;
2417
+ const ctx_1_$f = world[$internal];
2418
+ const relationCtx_1_$f = relation2[$internal];
2419
+ const traitData_1_$f = ctx_1_$f.traitInstances[relationCtx_1_$f.trait.id];
2420
+ if (!traitData_1_$f || !traitData_1_$f.relationTargets) {
2421
+ result_getRelationTargets_1_$f = [];
2422
+ } else {
2423
+ const eid_1_$f = currentEntity & ENTITY_ID_MASK;
2424
+ if (relationCtx_1_$f.exclusive) {
2425
+ const target_1_$f = traitData_1_$f.relationTargets[eid_1_$f];
2426
+ result_getRelationTargets_1_$f = target_1_$f !== void 0 ? [target_1_$f] : [];
2427
+ } else {
2428
+ const targets_1_$f = traitData_1_$f.relationTargets[eid_1_$f];
2429
+ result_getRelationTargets_1_$f = targets_1_$f !== void 0 ? targets_1_$f.slice() : [];
2430
+ }
2431
+ }
2432
+ const targets = result_getRelationTargets_1_$f;
2433
+ for (const target of targets) {
2434
+ if (!world.has(target)) continue;
2435
+ if (!processedEntities.has(target)) entityQueue.push(target);
2436
+ }
2389
2437
  }
2390
2438
  }
2391
2439
  const entityTraits = ctx.entityTraits.get(currentEntity);
@@ -2409,6 +2457,7 @@ function destroyEntity(world, entity) {
2409
2457
  function createWorld(optionsOrFirstTrait, ...traits) {
2410
2458
  const id = allocateWorldId(universe.worldIndex);
2411
2459
  let isInitialized = false;
2460
+ let lazyTraits;
2412
2461
  const world = {
2413
2462
  [$internal]: {
2414
2463
  entityIndex: createEntityIndex(id),
@@ -2441,6 +2490,10 @@ function createWorld(optionsOrFirstTrait, ...traits) {
2441
2490
  }
2442
2491
  const traitId_0_$f = IsExcluded.id;
2443
2492
  if (!(traitId_0_$f < ctx.traitInstances.length && ctx.traitInstances[traitId_0_$f] !== void 0)) registerTrait(world, IsExcluded);
2493
+ if (lazyTraits) {
2494
+ initTraits = lazyTraits;
2495
+ lazyTraits = void 0;
2496
+ }
2444
2497
  ctx.worldEntity = createEntity(world, IsExcluded, ...initTraits);
2445
2498
  },
2446
2499
  spawn(...spawnTraits) {
@@ -2478,6 +2531,7 @@ function createWorld(optionsOrFirstTrait, ...traits) {
2478
2531
  universe.worlds[id] = null;
2479
2532
  },
2480
2533
  reset() {
2534
+ lazyTraits = void 0;
2481
2535
  const ctx = world[$internal];
2482
2536
  world.entities.forEach((entity) => {
2483
2537
  if (world.has(entity)) {
@@ -2647,7 +2701,11 @@ function createWorld(optionsOrFirstTrait, ...traits) {
2647
2701
  traits: optionTraits = [],
2648
2702
  lazy = false
2649
2703
  } = optionsOrFirstTrait;
2650
- if (!lazy) world.init(...optionTraits);
2704
+ if (!lazy) {
2705
+ world.init(...optionTraits);
2706
+ } else {
2707
+ lazyTraits = optionTraits;
2708
+ }
2651
2709
  } else {
2652
2710
  world.init(...optionsOrFirstTrait ? [optionsOrFirstTrait, ...traits] : traits);
2653
2711
  }