koota 0.1.8 → 0.1.10

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
@@ -20,7 +20,7 @@ const Velocity = trait({ x: 0, y: 0 });
20
20
 
21
21
  // Trait with a callback for initial value
22
22
  // ⚠️ Must be an object
23
- const Mesh = trait(() => THREE.Mesh());
23
+ const Mesh = trait(() => new THREE.Mesh());
24
24
 
25
25
  // Tag trait (no data)
26
26
  const IsActive = trait();
@@ -309,6 +309,20 @@ entity.set(Position, { x: 10, y: 20 });
309
309
  entity.remove(Position);
310
310
  ```
311
311
 
312
+ ### Change detection with `udpateEach`
313
+
314
+ By default, `updateEach` will automatically turn on change detection for traits that are being tracked via `onChange` or the `Changed` modifier. If you want to silence change detection for a loop or force it to always run, you can do so with an options config.
315
+
316
+ ```js
317
+ // Setting changeDetection to 'never' will silence it, triggering no change events
318
+ world.query(Position, Velocity).updateEach(([position, velocity]) => {
319
+ }, { changeDetection: 'never' });
320
+
321
+ // Setting changeDetection to 'always' will ignore selective tracking and always emit change events for all traits that are mutated
322
+ world.query(Position, Velocity).updateEach(([position, velocity]) => {
323
+ }, { changeDetection: 'never' });
324
+ ```
325
+
312
326
  ### World traits
313
327
 
314
328
  For global data like time, these can be traits added to the world. **World traits do not appear in queries.**
@@ -338,7 +352,7 @@ world.query(Position, Velocity, Mass)
338
352
 
339
353
  ### Modifying trait stores direclty
340
354
 
341
- For performance-critical operations, you can modify trait stores directly using the useStore hook. This approach bypasses some of the safety checks and event triggers, so use it with caution. All stores are structure of arrays for performance purposes.
355
+ For performance-critical operations, you can modify trait stores directly using the `useStore` hook. This approach bypasses some of the safety checks and event triggers, so use it with caution. All stores are structure of arrays for performance purposes.
342
356
 
343
357
  ```js
344
358
  // Returns the SoA stores
@@ -400,6 +414,11 @@ const time = world.get(Time)
400
414
 
401
415
  // Sets the trait and triggers a change event
402
416
  world.set(Time, { current: performance.now() })
417
+ // Can take a callback with the previous state passed in
418
+ world.set(Time, (prev) => ({
419
+ current: performance.now(),
420
+ delta: performance.now() - prev.current
421
+ }))
403
422
 
404
423
  // Subscribe to add, remove or change events for a set of query parameters
405
424
  // Anything you can put in a query is legal
@@ -445,6 +464,11 @@ const position = entity.get(Position)
445
464
 
446
465
  // Sets the trait and triggers a change event
447
466
  entity.set(Position, { x: 10, y: 10 })
467
+ // Can take a callback with the previous state passed in
468
+ entity.set(Position, (prev) => ({
469
+ x: prev + 1,
470
+ y: prev + 1
471
+ }))
448
472
 
449
473
  // Get the targets for a relationship
450
474
  // Return Entity[]
@@ -464,14 +488,37 @@ entity.destroy()
464
488
 
465
489
  ### Trait
466
490
 
467
- A trait defines a kind of data. If you are familiar with ECS, it is our version of a component. We call it a trait instead to not get confused with React or web components. From a high level, you just create traits either with a schema or with a callback returning an object.
491
+ A trait is a specific block of data. They are added to entities to build up its overall data signature. If you are familiar with ECS, it is our version of a component. It is called a trait instead to not get confused with React or web components.
492
+
493
+ A trait can be created with a schema that describes the kind of data it will hold.
468
494
 
469
495
  ```js
470
- // A schema
471
496
  const Position = trait({ x: 0, y: 0, z: 0 })
497
+ ```
498
+
499
+ In cases where the data needs to be initialized for each instance of the trait created, a callback can be passed in to be used a as a lazy initializer.
500
+
501
+ ```js
502
+ // ❌ The items array will be shared between every instance of this trait
503
+ const Inventory = trait({
504
+ items: [],
505
+ max: 10,
506
+ })
507
+
508
+ // ✅ With a lazy initializer, each instance will now get its own array
509
+ const Inventory = trait({
510
+ items: () => [],
511
+ max: 10,
512
+ })
513
+ ```
514
+
515
+ Sometimes a trait only has one field that points to an object instance. In cases like this, it is useful to skip the schema and use a callback directly in the trait.
516
+
517
+ ```js
518
+ const Velocity = trait(() => new THREE.Vector3())
472
519
 
473
- // A callback
474
- const Velocity = trait(() => THREE.Vector3())
520
+ // The returned state is simply the instance
521
+ const velocity = entity.get(Velocity)
475
522
  ```
476
523
 
477
524
  Both schema-based and callback-based traits are used similarly, but they have different performance implications due to how their data is stored internally:
@@ -512,7 +559,7 @@ const store = [
512
559
  ];
513
560
 
514
561
  // Similarly, this will create a new instance of Mesh in each index
515
- const Mesh = trait(() => THREE.Mesh())
562
+ const Mesh = trait(() => new THREE.Mesh())
516
563
  ```
517
564
 
518
565
  #### Typing traits
@@ -521,17 +568,17 @@ Traits can have a schema type passed into its generic. This can be useful if the
521
568
 
522
569
  ```js
523
570
  type AttackerSchema = {
524
- continueCombo: boolean | null,
525
- currentStageIndex: number | null,
526
- stages: Array<AttackStage> | null,
527
- startedAt: number | null,
571
+ continueCombo: boolean | null,
572
+ currentStageIndex: number | null,
573
+ stages: Array<AttackStage> | null,
574
+ startedAt: number | null,
528
575
  }
529
576
 
530
577
  const Attacker = trait<AttackerSchema>({
531
- continueCombo: null,
532
- currentStageIndex: null,
533
- stages: null,
534
- startedAt: null,
578
+ continueCombo: null,
579
+ currentStageIndex: null,
580
+ stages: null,
581
+ startedAt: null,
535
582
  })
536
583
  ```
537
584
 
@@ -540,18 +587,18 @@ Interfaces can be used with `Pick` to convert the key signatures into something
540
587
 
541
588
  ```js
542
589
  interface AttackerSchema {
543
- continueCombo: boolean | null,
544
- currentStageIndex: number | null,
545
- stages: Array<AttackStage> | null,
546
- startedAt: number | null,
590
+ continueCombo: boolean | null,
591
+ currentStageIndex: number | null,
592
+ stages: Array<AttackStage> | null,
593
+ startedAt: number | null,
547
594
  }
548
595
 
549
596
  // Pick is required to not get type errors
550
597
  const Attacker = trait<Pick<AttackerSchema, keyof AttackerSchema>>({
551
- continueCombo: null,
552
- currentStageIndex: null,
553
- stages: null,
554
- startedAt: null,
598
+ continueCombo: null,
599
+ currentStageIndex: null,
600
+ stages: null,
601
+ startedAt: null,
555
602
  })
556
603
  ```
557
604
 
@@ -553,11 +553,17 @@ Number.prototype.changed = function(trait2) {
553
553
  return setChanged(world, this, trait2);
554
554
  };
555
555
  Number.prototype.get = function(trait2) {
556
- const ctx = trait2[$internal];
557
- const index = this & ENTITY_ID_MASK;
558
556
  const worldId = this >>> WORLD_ID_SHIFT;
559
- const store = ctx.stores[worldId];
560
- return ctx.get(index, store);
557
+ const world = universe.worlds[worldId];
558
+ const worldCtx = world[$internal];
559
+ const data = worldCtx.traitData.get(trait2);
560
+ if (!data) return void 0;
561
+ const mask = worldCtx.entityMasks[data.generationId][this];
562
+ if ((mask & data.bitflag) !== data.bitflag) return void 0;
563
+ const traitCtx = trait2[$internal];
564
+ const index = this & ENTITY_ID_MASK;
565
+ const store = traitCtx.stores[worldId];
566
+ return traitCtx.get(index, store);
561
567
  };
562
568
  Number.prototype.set = function(trait2, value, triggerChanged = true) {
563
569
  const ctx = trait2[$internal];
@@ -794,6 +800,7 @@ var Query = class {
794
800
  __publicField(this, "entities", new SparseSet());
795
801
  __publicField(this, "isTracking", false);
796
802
  __publicField(this, "hasChangedModifiers", false);
803
+ __publicField(this, "changedTraits", /* @__PURE__ */ new Set());
797
804
  __publicField(this, "toRemove", new SparseSet());
798
805
  __publicField(this, "addSubscriptions", /* @__PURE__ */ new Set());
799
806
  __publicField(this, "removeSubscriptions", /* @__PURE__ */ new Set());
@@ -847,6 +854,7 @@ var Query = class {
847
854
  }
848
855
  if (parameter.type.includes("changed")) {
849
856
  for (const trait2 of traits) {
857
+ this.changedTraits.add(trait2);
850
858
  const data = ctx.traitData.get(trait2);
851
859
  this.traitData.changed.push(data);
852
860
  this.traits.push(trait2);
@@ -1084,27 +1092,60 @@ function createQueryResult(query, world, params) {
1084
1092
  const traits = [];
1085
1093
  getQueryStores(params, traits, stores, world);
1086
1094
  const results = Object.assign(entities, {
1087
- updateEach(callback, options) {
1088
- const changeDetection = options?.changeDetection ?? false;
1095
+ updateEach(callback, options = { changeDetection: "auto" }) {
1089
1096
  const state = new Array(traits.length);
1090
- if (!changeDetection) {
1097
+ if (options.changeDetection === "auto") {
1098
+ const changedPairs = [];
1099
+ const atomicSnapshots = [];
1100
+ const trackedTraits = [];
1101
+ const untrackedTraits = [];
1102
+ for (const trait2 of traits) {
1103
+ if (world[$internal].trackedTraits.has(trait2)) {
1104
+ trackedTraits.push(trait2);
1105
+ } else if (query.hasChangedModifiers && query.changedTraits.has(trait2)) {
1106
+ trackedTraits.push(trait2);
1107
+ } else {
1108
+ untrackedTraits.push(trait2);
1109
+ }
1110
+ }
1091
1111
  for (let i = 0; i < entities.length; i++) {
1092
1112
  const entity = entities[i];
1093
1113
  const eid = getEntityId(entity);
1094
1114
  for (let j = 0; j < traits.length; j++) {
1095
1115
  const trait2 = traits[j];
1096
1116
  const ctx = trait2[$internal];
1097
- state[j] = ctx.get(eid, stores[j]);
1117
+ const value = ctx.get(eid, stores[j]);
1118
+ state[j] = value;
1119
+ atomicSnapshots[j] = ctx.type === "aos" ? { ...value } : null;
1098
1120
  }
1099
1121
  callback(state, entity, i);
1100
1122
  if (!world.has(entity)) continue;
1101
- for (let j = 0; j < traits.length; j++) {
1102
- const trait2 = traits[j];
1123
+ for (let j = 0; j < trackedTraits.length; j++) {
1124
+ const trait2 = trackedTraits[j];
1125
+ const ctx = trait2[$internal];
1126
+ const newValue = state[j];
1127
+ let changed = false;
1128
+ if (ctx.type === "aos") {
1129
+ changed = ctx.fastSetWithChangeDetection(eid, stores[j], newValue);
1130
+ if (!changed) {
1131
+ changed = !shallowEqual(newValue, atomicSnapshots[j]);
1132
+ }
1133
+ } else {
1134
+ changed = ctx.fastSetWithChangeDetection(eid, stores[j], newValue);
1135
+ }
1136
+ if (changed) changedPairs.push([entity, trait2]);
1137
+ }
1138
+ for (let j = 0; j < untrackedTraits.length; j++) {
1139
+ const trait2 = untrackedTraits[j];
1103
1140
  const ctx = trait2[$internal];
1104
1141
  ctx.fastSet(eid, stores[j], state[j]);
1105
1142
  }
1106
1143
  }
1107
- } else {
1144
+ for (let i = 0; i < changedPairs.length; i++) {
1145
+ const [entity, trait2] = changedPairs[i];
1146
+ entity.changed(trait2);
1147
+ }
1148
+ } else if (options.changeDetection === "always") {
1108
1149
  const changedPairs = [];
1109
1150
  const atomicSnapshots = [];
1110
1151
  for (let i = 0; i < entities.length; i++) {
@@ -1139,6 +1180,23 @@ function createQueryResult(query, world, params) {
1139
1180
  const [entity, trait2] = changedPairs[i];
1140
1181
  entity.changed(trait2);
1141
1182
  }
1183
+ } else if (options.changeDetection === "never") {
1184
+ for (let i = 0; i < entities.length; i++) {
1185
+ const entity = entities[i];
1186
+ const eid = getEntityId(entity);
1187
+ for (let j = 0; j < traits.length; j++) {
1188
+ const trait2 = traits[j];
1189
+ const ctx = trait2[$internal];
1190
+ state[j] = ctx.get(eid, stores[j]);
1191
+ }
1192
+ callback(state, entity, i);
1193
+ if (!world.has(entity)) continue;
1194
+ for (let j = 0; j < traits.length; j++) {
1195
+ const trait2 = traits[j];
1196
+ const ctx = trait2[$internal];
1197
+ ctx.fastSet(eid, stores[j], state[j]);
1198
+ }
1199
+ }
1142
1200
  }
1143
1201
  return results;
1144
1202
  },
@@ -1194,7 +1252,8 @@ var World = class {
1194
1252
  dirtyMasks: /* @__PURE__ */ new Map(),
1195
1253
  trackingSnapshots: /* @__PURE__ */ new Map(),
1196
1254
  changedMasks: /* @__PURE__ */ new Map(),
1197
- worldEntity: null
1255
+ worldEntity: null,
1256
+ trackedTraits: /* @__PURE__ */ new Set()
1198
1257
  });
1199
1258
  __privateAdd(this, _isInitialized, false);
1200
1259
  __publicField(this, "traits", /* @__PURE__ */ new Set());
@@ -1268,6 +1327,7 @@ var World = class {
1268
1327
  ctx.trackingSnapshots.clear();
1269
1328
  ctx.dirtyMasks.clear();
1270
1329
  ctx.changedMasks.clear();
1330
+ ctx.trackedTraits.clear();
1271
1331
  ctx.worldEntity = createEntity(this, IsExcluded);
1272
1332
  }
1273
1333
  query(...args) {
@@ -1317,7 +1377,11 @@ var World = class {
1317
1377
  if (!ctx.traitData.has(trait2)) registerTrait(this, trait2);
1318
1378
  const data = ctx.traitData.get(trait2);
1319
1379
  data.changedSubscriptions.add(callback);
1320
- return () => data.changedSubscriptions.delete(callback);
1380
+ ctx.trackedTraits.add(trait2);
1381
+ return () => {
1382
+ data.changedSubscriptions.delete(callback);
1383
+ ctx.trackedTraits.delete(trait2);
1384
+ };
1321
1385
  }
1322
1386
  };
1323
1387
  _id = new WeakMap();
package/dist/index.cjs CHANGED
@@ -590,11 +590,17 @@ Number.prototype.changed = function(trait2) {
590
590
  return setChanged(world, this, trait2);
591
591
  };
592
592
  Number.prototype.get = function(trait2) {
593
- const ctx = trait2[$internal];
594
- const index = this & ENTITY_ID_MASK;
595
593
  const worldId = this >>> WORLD_ID_SHIFT;
596
- const store = ctx.stores[worldId];
597
- return ctx.get(index, store);
594
+ const world = universe.worlds[worldId];
595
+ const worldCtx = world[$internal];
596
+ const data = worldCtx.traitData.get(trait2);
597
+ if (!data) return void 0;
598
+ const mask = worldCtx.entityMasks[data.generationId][this];
599
+ if ((mask & data.bitflag) !== data.bitflag) return void 0;
600
+ const traitCtx = trait2[$internal];
601
+ const index = this & ENTITY_ID_MASK;
602
+ const store = traitCtx.stores[worldId];
603
+ return traitCtx.get(index, store);
598
604
  };
599
605
  Number.prototype.set = function(trait2, value, triggerChanged = true) {
600
606
  const ctx = trait2[$internal];
@@ -772,6 +778,7 @@ var Query = class {
772
778
  __publicField(this, "entities", new SparseSet());
773
779
  __publicField(this, "isTracking", false);
774
780
  __publicField(this, "hasChangedModifiers", false);
781
+ __publicField(this, "changedTraits", /* @__PURE__ */ new Set());
775
782
  __publicField(this, "toRemove", new SparseSet());
776
783
  __publicField(this, "addSubscriptions", /* @__PURE__ */ new Set());
777
784
  __publicField(this, "removeSubscriptions", /* @__PURE__ */ new Set());
@@ -825,6 +832,7 @@ var Query = class {
825
832
  }
826
833
  if (parameter.type.includes("changed")) {
827
834
  for (const trait2 of traits) {
835
+ this.changedTraits.add(trait2);
828
836
  const data = ctx.traitData.get(trait2);
829
837
  this.traitData.changed.push(data);
830
838
  this.traits.push(trait2);
@@ -1062,27 +1070,60 @@ function createQueryResult(query, world, params) {
1062
1070
  const traits = [];
1063
1071
  getQueryStores(params, traits, stores, world);
1064
1072
  const results = Object.assign(entities, {
1065
- updateEach(callback, options) {
1066
- const changeDetection = options?.changeDetection ?? false;
1073
+ updateEach(callback, options = { changeDetection: "auto" }) {
1067
1074
  const state = new Array(traits.length);
1068
- if (!changeDetection) {
1075
+ if (options.changeDetection === "auto") {
1076
+ const changedPairs = [];
1077
+ const atomicSnapshots = [];
1078
+ const trackedTraits = [];
1079
+ const untrackedTraits = [];
1080
+ for (const trait2 of traits) {
1081
+ if (world[$internal].trackedTraits.has(trait2)) {
1082
+ trackedTraits.push(trait2);
1083
+ } else if (query.hasChangedModifiers && query.changedTraits.has(trait2)) {
1084
+ trackedTraits.push(trait2);
1085
+ } else {
1086
+ untrackedTraits.push(trait2);
1087
+ }
1088
+ }
1069
1089
  for (let i = 0; i < entities.length; i++) {
1070
1090
  const entity = entities[i];
1071
1091
  const eid = getEntityId(entity);
1072
1092
  for (let j = 0; j < traits.length; j++) {
1073
1093
  const trait2 = traits[j];
1074
1094
  const ctx = trait2[$internal];
1075
- state[j] = ctx.get(eid, stores[j]);
1095
+ const value = ctx.get(eid, stores[j]);
1096
+ state[j] = value;
1097
+ atomicSnapshots[j] = ctx.type === "aos" ? { ...value } : null;
1076
1098
  }
1077
1099
  callback(state, entity, i);
1078
1100
  if (!world.has(entity)) continue;
1079
- for (let j = 0; j < traits.length; j++) {
1080
- const trait2 = traits[j];
1101
+ for (let j = 0; j < trackedTraits.length; j++) {
1102
+ const trait2 = trackedTraits[j];
1103
+ const ctx = trait2[$internal];
1104
+ const newValue = state[j];
1105
+ let changed = false;
1106
+ if (ctx.type === "aos") {
1107
+ changed = ctx.fastSetWithChangeDetection(eid, stores[j], newValue);
1108
+ if (!changed) {
1109
+ changed = !shallowEqual(newValue, atomicSnapshots[j]);
1110
+ }
1111
+ } else {
1112
+ changed = ctx.fastSetWithChangeDetection(eid, stores[j], newValue);
1113
+ }
1114
+ if (changed) changedPairs.push([entity, trait2]);
1115
+ }
1116
+ for (let j = 0; j < untrackedTraits.length; j++) {
1117
+ const trait2 = untrackedTraits[j];
1081
1118
  const ctx = trait2[$internal];
1082
1119
  ctx.fastSet(eid, stores[j], state[j]);
1083
1120
  }
1084
1121
  }
1085
- } else {
1122
+ for (let i = 0; i < changedPairs.length; i++) {
1123
+ const [entity, trait2] = changedPairs[i];
1124
+ entity.changed(trait2);
1125
+ }
1126
+ } else if (options.changeDetection === "always") {
1086
1127
  const changedPairs = [];
1087
1128
  const atomicSnapshots = [];
1088
1129
  for (let i = 0; i < entities.length; i++) {
@@ -1117,6 +1158,23 @@ function createQueryResult(query, world, params) {
1117
1158
  const [entity, trait2] = changedPairs[i];
1118
1159
  entity.changed(trait2);
1119
1160
  }
1161
+ } else if (options.changeDetection === "never") {
1162
+ for (let i = 0; i < entities.length; i++) {
1163
+ const entity = entities[i];
1164
+ const eid = getEntityId(entity);
1165
+ for (let j = 0; j < traits.length; j++) {
1166
+ const trait2 = traits[j];
1167
+ const ctx = trait2[$internal];
1168
+ state[j] = ctx.get(eid, stores[j]);
1169
+ }
1170
+ callback(state, entity, i);
1171
+ if (!world.has(entity)) continue;
1172
+ for (let j = 0; j < traits.length; j++) {
1173
+ const trait2 = traits[j];
1174
+ const ctx = trait2[$internal];
1175
+ ctx.fastSet(eid, stores[j], state[j]);
1176
+ }
1177
+ }
1120
1178
  }
1121
1179
  return results;
1122
1180
  },
@@ -1172,7 +1230,8 @@ var World = class {
1172
1230
  dirtyMasks: /* @__PURE__ */ new Map(),
1173
1231
  trackingSnapshots: /* @__PURE__ */ new Map(),
1174
1232
  changedMasks: /* @__PURE__ */ new Map(),
1175
- worldEntity: null
1233
+ worldEntity: null,
1234
+ trackedTraits: /* @__PURE__ */ new Set()
1176
1235
  });
1177
1236
  __privateAdd(this, _isInitialized, false);
1178
1237
  __publicField(this, "traits", /* @__PURE__ */ new Set());
@@ -1246,6 +1305,7 @@ var World = class {
1246
1305
  ctx.trackingSnapshots.clear();
1247
1306
  ctx.dirtyMasks.clear();
1248
1307
  ctx.changedMasks.clear();
1308
+ ctx.trackedTraits.clear();
1249
1309
  ctx.worldEntity = createEntity(this, IsExcluded);
1250
1310
  }
1251
1311
  query(...args) {
@@ -1295,7 +1355,11 @@ var World = class {
1295
1355
  if (!ctx.traitData.has(trait2)) registerTrait(this, trait2);
1296
1356
  const data = ctx.traitData.get(trait2);
1297
1357
  data.changedSubscriptions.add(callback);
1298
- return () => data.changedSubscriptions.delete(callback);
1358
+ ctx.trackedTraits.add(trait2);
1359
+ return () => {
1360
+ data.changedSubscriptions.delete(callback);
1361
+ ctx.trackedTraits.delete(trait2);
1362
+ };
1299
1363
  }
1300
1364
  };
1301
1365
  _id = new WeakMap();
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { S as Schema, T as Trait, N as Norm, M as ModifierData, W as World, Q as QueryParameter, R as Relation, a as RelationTarget, b as WildcardRelation } from './world-liWg9VB-.cjs';
2
- export { $ as $internal, A as AoSFactory, C as ConfigurableTrait, l as Entity, j as ExtractIsTag, E as ExtractSchema, i as ExtractStore, k as ExtractStores, r as InstancesFromParameters, s as IsNotModifier, I as IsTag, m as QueryModifier, p as QueryResult, o as QueryResultOptions, n as QuerySubscriber, h as Store, q as StoresFromParameters, g as TraitInstance, f as TraitTuple, d as TraitType, e as TraitValue, c as createWorld } from './world-liWg9VB-.cjs';
1
+ import { S as Schema, T as Trait, N as Norm, M as ModifierData, W as World, Q as QueryParameter, R as Relation, a as RelationTarget, b as WildcardRelation } from './world-CIrtrsKj.cjs';
2
+ export { $ as $internal, A as AoSFactory, C as ConfigurableTrait, l as Entity, j as ExtractIsTag, E as ExtractSchema, i as ExtractStore, k as ExtractStores, r as InstancesFromParameters, s as IsNotModifier, I as IsTag, m as QueryModifier, p as QueryResult, o as QueryResultOptions, n as QuerySubscriber, h as Store, q as StoresFromParameters, g as TraitInstance, f as TraitTuple, d as TraitType, e as TraitValue, c as createWorld } from './world-CIrtrsKj.cjs';
3
3
 
4
4
  declare function defineTrait<S extends Schema = {}>(schema?: S): Trait<Norm<S>>;
5
5
  declare const trait: typeof defineTrait;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { S as Schema, T as Trait, N as Norm, M as ModifierData, W as World, Q as QueryParameter, R as Relation, a as RelationTarget, b as WildcardRelation } from './world-liWg9VB-.js';
2
- export { $ as $internal, A as AoSFactory, C as ConfigurableTrait, l as Entity, j as ExtractIsTag, E as ExtractSchema, i as ExtractStore, k as ExtractStores, r as InstancesFromParameters, s as IsNotModifier, I as IsTag, m as QueryModifier, p as QueryResult, o as QueryResultOptions, n as QuerySubscriber, h as Store, q as StoresFromParameters, g as TraitInstance, f as TraitTuple, d as TraitType, e as TraitValue, c as createWorld } from './world-liWg9VB-.js';
1
+ import { S as Schema, T as Trait, N as Norm, M as ModifierData, W as World, Q as QueryParameter, R as Relation, a as RelationTarget, b as WildcardRelation } from './world-CIrtrsKj.js';
2
+ export { $ as $internal, A as AoSFactory, C as ConfigurableTrait, l as Entity, j as ExtractIsTag, E as ExtractSchema, i as ExtractStore, k as ExtractStores, r as InstancesFromParameters, s as IsNotModifier, I as IsTag, m as QueryModifier, p as QueryResult, o as QueryResultOptions, n as QuerySubscriber, h as Store, q as StoresFromParameters, g as TraitInstance, f as TraitTuple, d as TraitType, e as TraitValue, c as createWorld } from './world-CIrtrsKj.js';
3
3
 
4
4
  declare function defineTrait<S extends Schema = {}>(schema?: S): Trait<Norm<S>>;
5
5
  declare const trait: typeof defineTrait;
package/dist/index.js CHANGED
@@ -13,7 +13,7 @@ import {
13
13
  relation,
14
14
  trait,
15
15
  universe
16
- } from "./chunk-XQK5JJDD.js";
16
+ } from "./chunk-L2TCLFSQ.js";
17
17
  export {
18
18
  $internal,
19
19
  Not,
package/dist/react.cjs CHANGED
@@ -582,11 +582,17 @@ Number.prototype.changed = function(trait2) {
582
582
  return setChanged(world, this, trait2);
583
583
  };
584
584
  Number.prototype.get = function(trait2) {
585
- const ctx = trait2[$internal];
586
- const index = this & ENTITY_ID_MASK;
587
585
  const worldId = this >>> WORLD_ID_SHIFT;
588
- const store = ctx.stores[worldId];
589
- return ctx.get(index, store);
586
+ const world = universe.worlds[worldId];
587
+ const worldCtx = world[$internal];
588
+ const data = worldCtx.traitData.get(trait2);
589
+ if (!data) return void 0;
590
+ const mask = worldCtx.entityMasks[data.generationId][this];
591
+ if ((mask & data.bitflag) !== data.bitflag) return void 0;
592
+ const traitCtx = trait2[$internal];
593
+ const index = this & ENTITY_ID_MASK;
594
+ const store = traitCtx.stores[worldId];
595
+ return traitCtx.get(index, store);
590
596
  };
591
597
  Number.prototype.set = function(trait2, value, triggerChanged = true) {
592
598
  const ctx = trait2[$internal];
@@ -764,6 +770,7 @@ var Query = class {
764
770
  __publicField(this, "entities", new SparseSet());
765
771
  __publicField(this, "isTracking", false);
766
772
  __publicField(this, "hasChangedModifiers", false);
773
+ __publicField(this, "changedTraits", /* @__PURE__ */ new Set());
767
774
  __publicField(this, "toRemove", new SparseSet());
768
775
  __publicField(this, "addSubscriptions", /* @__PURE__ */ new Set());
769
776
  __publicField(this, "removeSubscriptions", /* @__PURE__ */ new Set());
@@ -817,6 +824,7 @@ var Query = class {
817
824
  }
818
825
  if (parameter.type.includes("changed")) {
819
826
  for (const trait2 of traits) {
827
+ this.changedTraits.add(trait2);
820
828
  const data = ctx.traitData.get(trait2);
821
829
  this.traitData.changed.push(data);
822
830
  this.traits.push(trait2);
@@ -1054,27 +1062,60 @@ function createQueryResult(query, world, params) {
1054
1062
  const traits = [];
1055
1063
  getQueryStores(params, traits, stores, world);
1056
1064
  const results = Object.assign(entities, {
1057
- updateEach(callback, options) {
1058
- const changeDetection = options?.changeDetection ?? false;
1065
+ updateEach(callback, options = { changeDetection: "auto" }) {
1059
1066
  const state = new Array(traits.length);
1060
- if (!changeDetection) {
1067
+ if (options.changeDetection === "auto") {
1068
+ const changedPairs = [];
1069
+ const atomicSnapshots = [];
1070
+ const trackedTraits = [];
1071
+ const untrackedTraits = [];
1072
+ for (const trait2 of traits) {
1073
+ if (world[$internal].trackedTraits.has(trait2)) {
1074
+ trackedTraits.push(trait2);
1075
+ } else if (query.hasChangedModifiers && query.changedTraits.has(trait2)) {
1076
+ trackedTraits.push(trait2);
1077
+ } else {
1078
+ untrackedTraits.push(trait2);
1079
+ }
1080
+ }
1061
1081
  for (let i = 0; i < entities.length; i++) {
1062
1082
  const entity = entities[i];
1063
1083
  const eid = getEntityId(entity);
1064
1084
  for (let j = 0; j < traits.length; j++) {
1065
1085
  const trait2 = traits[j];
1066
1086
  const ctx = trait2[$internal];
1067
- state[j] = ctx.get(eid, stores[j]);
1087
+ const value = ctx.get(eid, stores[j]);
1088
+ state[j] = value;
1089
+ atomicSnapshots[j] = ctx.type === "aos" ? { ...value } : null;
1068
1090
  }
1069
1091
  callback(state, entity, i);
1070
1092
  if (!world.has(entity)) continue;
1071
- for (let j = 0; j < traits.length; j++) {
1072
- const trait2 = traits[j];
1093
+ for (let j = 0; j < trackedTraits.length; j++) {
1094
+ const trait2 = trackedTraits[j];
1095
+ const ctx = trait2[$internal];
1096
+ const newValue = state[j];
1097
+ let changed = false;
1098
+ if (ctx.type === "aos") {
1099
+ changed = ctx.fastSetWithChangeDetection(eid, stores[j], newValue);
1100
+ if (!changed) {
1101
+ changed = !shallowEqual(newValue, atomicSnapshots[j]);
1102
+ }
1103
+ } else {
1104
+ changed = ctx.fastSetWithChangeDetection(eid, stores[j], newValue);
1105
+ }
1106
+ if (changed) changedPairs.push([entity, trait2]);
1107
+ }
1108
+ for (let j = 0; j < untrackedTraits.length; j++) {
1109
+ const trait2 = untrackedTraits[j];
1073
1110
  const ctx = trait2[$internal];
1074
1111
  ctx.fastSet(eid, stores[j], state[j]);
1075
1112
  }
1076
1113
  }
1077
- } else {
1114
+ for (let i = 0; i < changedPairs.length; i++) {
1115
+ const [entity, trait2] = changedPairs[i];
1116
+ entity.changed(trait2);
1117
+ }
1118
+ } else if (options.changeDetection === "always") {
1078
1119
  const changedPairs = [];
1079
1120
  const atomicSnapshots = [];
1080
1121
  for (let i = 0; i < entities.length; i++) {
@@ -1109,6 +1150,23 @@ function createQueryResult(query, world, params) {
1109
1150
  const [entity, trait2] = changedPairs[i];
1110
1151
  entity.changed(trait2);
1111
1152
  }
1153
+ } else if (options.changeDetection === "never") {
1154
+ for (let i = 0; i < entities.length; i++) {
1155
+ const entity = entities[i];
1156
+ const eid = getEntityId(entity);
1157
+ for (let j = 0; j < traits.length; j++) {
1158
+ const trait2 = traits[j];
1159
+ const ctx = trait2[$internal];
1160
+ state[j] = ctx.get(eid, stores[j]);
1161
+ }
1162
+ callback(state, entity, i);
1163
+ if (!world.has(entity)) continue;
1164
+ for (let j = 0; j < traits.length; j++) {
1165
+ const trait2 = traits[j];
1166
+ const ctx = trait2[$internal];
1167
+ ctx.fastSet(eid, stores[j], state[j]);
1168
+ }
1169
+ }
1112
1170
  }
1113
1171
  return results;
1114
1172
  },
@@ -1164,7 +1222,8 @@ var World = class {
1164
1222
  dirtyMasks: /* @__PURE__ */ new Map(),
1165
1223
  trackingSnapshots: /* @__PURE__ */ new Map(),
1166
1224
  changedMasks: /* @__PURE__ */ new Map(),
1167
- worldEntity: null
1225
+ worldEntity: null,
1226
+ trackedTraits: /* @__PURE__ */ new Set()
1168
1227
  });
1169
1228
  __privateAdd(this, _isInitialized, false);
1170
1229
  __publicField(this, "traits", /* @__PURE__ */ new Set());
@@ -1238,6 +1297,7 @@ var World = class {
1238
1297
  ctx.trackingSnapshots.clear();
1239
1298
  ctx.dirtyMasks.clear();
1240
1299
  ctx.changedMasks.clear();
1300
+ ctx.trackedTraits.clear();
1241
1301
  ctx.worldEntity = createEntity(this, IsExcluded);
1242
1302
  }
1243
1303
  query(...args) {
@@ -1287,7 +1347,11 @@ var World = class {
1287
1347
  if (!ctx.traitData.has(trait2)) registerTrait(this, trait2);
1288
1348
  const data = ctx.traitData.get(trait2);
1289
1349
  data.changedSubscriptions.add(callback);
1290
- return () => data.changedSubscriptions.delete(callback);
1350
+ ctx.trackedTraits.add(trait2);
1351
+ return () => {
1352
+ data.changedSubscriptions.delete(callback);
1353
+ ctx.trackedTraits.delete(trait2);
1354
+ };
1291
1355
  }
1292
1356
  };
1293
1357
  _id = new WeakMap();
package/dist/react.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { Q as QueryParameter, p as QueryResult, W as World, T as Trait, l as Entity, g as TraitInstance } from './world-liWg9VB-.cjs';
1
+ import { Q as QueryParameter, p as QueryResult, W as World, T as Trait, l as Entity, g as TraitInstance } from './world-CIrtrsKj.cjs';
2
2
  import * as react_jsx_runtime from 'react/jsx-runtime';
3
3
 
4
4
  declare function useQuery<T extends QueryParameter[]>(...parameters: T): QueryResult<T>;