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 +65 -10
- package/dist/{chunk-URTUZC7P.js → chunk-QE3Q3CT7.js} +81 -23
- package/dist/index.cjs +81 -23
- package/dist/index.d.cts +7 -5
- package/dist/index.d.ts +7 -5
- package/dist/index.js +1 -1
- package/dist/react.cjs +182 -97
- package/dist/react.d.cts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +134 -87
- package/dist/{types-DOpAZa4-.d.cts → types-Jqh6L5t_.d.cts} +11 -4
- package/dist/{types-DOpAZa4-.d.ts → types-Jqh6L5t_.d.ts} +11 -4
- package/package.json +1 -1
- package/react/index.cjs +182 -97
- package/react/index.d.cts +1 -1
- package/react/index.d.ts +1 -1
- package/react/index.js +134 -87
- package/react/package.json +5 -0
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 {
|
|
94
|
+
import { createActions } from 'koota'
|
|
95
95
|
import { useActions } from 'koota/react'
|
|
96
96
|
|
|
97
|
-
const actions =
|
|
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
|
|
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
|
|
188
|
+
#### Auto destroy
|
|
171
189
|
|
|
172
|
-
Relations can automatically
|
|
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({
|
|
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
|
|
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
|
|
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 `
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
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
|
-
|
|
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
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
cleanupRelationTarget(world, relation2,
|
|
2343
|
-
if (relationCtx.
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
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
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
cleanupRelationTarget(world, relation2,
|
|
2388
|
-
if (relationCtx.
|
|
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)
|
|
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
|
}
|