@codehz/ecs 0.6.9 → 0.6.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/builder.d.mts +20 -3
- package/package.json +1 -1
- package/world.mjs +250 -113
- package/world.mjs.map +1 -1
package/world.mjs
CHANGED
|
@@ -369,6 +369,7 @@ const componentNames = new Array(COMPONENT_ID_MAX + 1);
|
|
|
369
369
|
const exclusiveFlags = new BitSet(COMPONENT_ID_MAX + 1);
|
|
370
370
|
const cascadeDeleteFlags = new BitSet(COMPONENT_ID_MAX + 1);
|
|
371
371
|
const dontFragmentFlags = new BitSet(COMPONENT_ID_MAX + 1);
|
|
372
|
+
const componentMerges = new Array(COMPONENT_ID_MAX + 1);
|
|
372
373
|
/**
|
|
373
374
|
* Allocate a new component ID from the global allocator.
|
|
374
375
|
* @param nameOrOptions Optional name for the component (for serialization/debugging) or options object
|
|
@@ -401,6 +402,7 @@ function component(nameOrOptions) {
|
|
|
401
402
|
if (options.exclusive) exclusiveFlags.set(id);
|
|
402
403
|
if (options.cascadeDelete) cascadeDeleteFlags.set(id);
|
|
403
404
|
if (options.dontFragment) dontFragmentFlags.set(id);
|
|
405
|
+
if (options.merge) componentMerges[id] = options.merge;
|
|
404
406
|
}
|
|
405
407
|
return id;
|
|
406
408
|
}
|
|
@@ -419,6 +421,21 @@ function getComponentIdByName(name) {
|
|
|
419
421
|
function getComponentNameById(id) {
|
|
420
422
|
return componentNames[id];
|
|
421
423
|
}
|
|
424
|
+
function getBaseComponentId(componentType) {
|
|
425
|
+
if (isComponentId(componentType)) return componentType;
|
|
426
|
+
const decoded = decodeRelationRaw(componentType);
|
|
427
|
+
if (decoded === null) return void 0;
|
|
428
|
+
return isValidComponentId(decoded.componentId) ? decoded.componentId : void 0;
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Get merge callback for a componentType (including relation component types).
|
|
432
|
+
* Returns undefined if the base component has no merge callback.
|
|
433
|
+
*/
|
|
434
|
+
function getComponentMerge(componentType) {
|
|
435
|
+
const baseComponentId = getBaseComponentId(componentType);
|
|
436
|
+
if (baseComponentId === void 0) return void 0;
|
|
437
|
+
return componentMerges[baseComponentId];
|
|
438
|
+
}
|
|
422
439
|
/**
|
|
423
440
|
* Check if a component is marked as exclusive
|
|
424
441
|
* @param id The component ID
|
|
@@ -637,6 +654,8 @@ const MAX_COMMAND_ITERATIONS = 100;
|
|
|
637
654
|
var CommandBuffer = class {
|
|
638
655
|
commands = [];
|
|
639
656
|
swapBuffer = [];
|
|
657
|
+
/** Reusable map to group commands by entity, avoids per-sync allocations */
|
|
658
|
+
entityCommands = /* @__PURE__ */ new Map();
|
|
640
659
|
executeEntityCommands;
|
|
641
660
|
/**
|
|
642
661
|
* Create a command buffer with an executor function
|
|
@@ -681,14 +700,16 @@ var CommandBuffer = class {
|
|
|
681
700
|
iterations++;
|
|
682
701
|
const currentCommands = this.commands;
|
|
683
702
|
this.commands = this.swapBuffer;
|
|
684
|
-
const entityCommands =
|
|
703
|
+
const entityCommands = this.entityCommands;
|
|
685
704
|
for (const cmd of currentCommands) {
|
|
686
|
-
|
|
687
|
-
|
|
705
|
+
const existing = entityCommands.get(cmd.entityId);
|
|
706
|
+
if (existing !== void 0) existing.push(cmd);
|
|
707
|
+
else entityCommands.set(cmd.entityId, [cmd]);
|
|
688
708
|
}
|
|
689
709
|
currentCommands.length = 0;
|
|
690
710
|
this.swapBuffer = currentCommands;
|
|
691
711
|
for (const [entityId, commands] of entityCommands) this.executeEntityCommands(entityId, commands);
|
|
712
|
+
entityCommands.clear();
|
|
692
713
|
}
|
|
693
714
|
}
|
|
694
715
|
/**
|
|
@@ -746,6 +767,16 @@ function matchesFilter(archetype, filter) {
|
|
|
746
767
|
});
|
|
747
768
|
}
|
|
748
769
|
|
|
770
|
+
//#endregion
|
|
771
|
+
//#region src/core/component-type-utils.ts
|
|
772
|
+
/**
|
|
773
|
+
* Normalize component type collections into a stable ascending order.
|
|
774
|
+
* This keeps cache keys and archetype signatures deterministic.
|
|
775
|
+
*/
|
|
776
|
+
function normalizeComponentTypes(componentTypes) {
|
|
777
|
+
return [...componentTypes].sort((a, b) => a - b);
|
|
778
|
+
}
|
|
779
|
+
|
|
749
780
|
//#endregion
|
|
750
781
|
//#region src/query/query.ts
|
|
751
782
|
/**
|
|
@@ -765,7 +796,7 @@ var Query = class {
|
|
|
765
796
|
specificDontFragmentTypes;
|
|
766
797
|
constructor(world, componentTypes, filter = {}) {
|
|
767
798
|
this.world = world;
|
|
768
|
-
this.componentTypes =
|
|
799
|
+
this.componentTypes = normalizeComponentTypes(componentTypes);
|
|
769
800
|
this.filter = filter;
|
|
770
801
|
this.wildcardTypes = this.componentTypes.filter((ct) => getDetailedIdType(ct).type === "wildcard-relation");
|
|
771
802
|
this.specificDontFragmentTypes = this.componentTypes.filter((ct) => {
|
|
@@ -923,16 +954,6 @@ function getOrCompute(cache, key, compute) {
|
|
|
923
954
|
}
|
|
924
955
|
return value;
|
|
925
956
|
}
|
|
926
|
-
/**
|
|
927
|
-
* Alias for getOrCompute - maintained for backwards compatibility
|
|
928
|
-
* @deprecated Use getOrCompute instead
|
|
929
|
-
*/
|
|
930
|
-
const getOrComputeCache = getOrCompute;
|
|
931
|
-
/**
|
|
932
|
-
* Alias for getOrCompute - maintained for backwards compatibility
|
|
933
|
-
* @deprecated Use getOrCompute instead
|
|
934
|
-
*/
|
|
935
|
-
const getOrCreateWithSideEffect = getOrCompute;
|
|
936
957
|
|
|
937
958
|
//#endregion
|
|
938
959
|
//#region src/core/types.ts
|
|
@@ -1083,7 +1104,7 @@ var Archetype = class {
|
|
|
1083
1104
|
*/
|
|
1084
1105
|
componentDataSourcesCache = /* @__PURE__ */ new Map();
|
|
1085
1106
|
constructor(componentTypes, dontFragmentRelations) {
|
|
1086
|
-
this.componentTypes =
|
|
1107
|
+
this.componentTypes = normalizeComponentTypes(componentTypes);
|
|
1087
1108
|
this.componentTypeSet = new Set(this.componentTypes);
|
|
1088
1109
|
this.dontFragmentRelations = dontFragmentRelations;
|
|
1089
1110
|
for (const componentType of this.componentTypes) this.componentData.set(componentType, []);
|
|
@@ -1099,7 +1120,7 @@ var Archetype = class {
|
|
|
1099
1120
|
*/
|
|
1100
1121
|
matches(componentTypes) {
|
|
1101
1122
|
if (this.componentTypes.length !== componentTypes.length) return false;
|
|
1102
|
-
const sortedTypes =
|
|
1123
|
+
const sortedTypes = normalizeComponentTypes(componentTypes);
|
|
1103
1124
|
return this.componentTypes.every((type, index) => type === sortedTypes[index]);
|
|
1104
1125
|
}
|
|
1105
1126
|
addEntity(entityId, componentData) {
|
|
@@ -1134,6 +1155,9 @@ var Archetype = class {
|
|
|
1134
1155
|
if (dontFragmentData) for (const [componentType, data] of dontFragmentData) entityData.set(componentType, data);
|
|
1135
1156
|
return entityData;
|
|
1136
1157
|
}
|
|
1158
|
+
getEntityDontFragmentRelations(entityId) {
|
|
1159
|
+
return this.dontFragmentRelations.get(entityId);
|
|
1160
|
+
}
|
|
1137
1161
|
dump() {
|
|
1138
1162
|
return this.entities.map((entity, i) => {
|
|
1139
1163
|
const components = /* @__PURE__ */ new Map();
|
|
@@ -1255,7 +1279,7 @@ var Archetype = class {
|
|
|
1255
1279
|
}
|
|
1256
1280
|
getCachedComponentDataSources(componentTypes) {
|
|
1257
1281
|
const cacheKey = buildCacheKey(componentTypes);
|
|
1258
|
-
return
|
|
1282
|
+
return getOrCompute(this.componentDataSourcesCache, cacheKey, () => componentTypes.map((compType) => this.getComponentDataSource(compType)));
|
|
1259
1283
|
}
|
|
1260
1284
|
getComponentDataSource(compType) {
|
|
1261
1285
|
const optional = isOptionalEntityId(compType);
|
|
@@ -1412,6 +1436,12 @@ function processSetCommand(entityId, currentArchetype, componentType, component$
|
|
|
1412
1436
|
if (!currentArchetype.componentTypeSet.has(wildcardMarker)) changeset.set(wildcardMarker, void 0);
|
|
1413
1437
|
}
|
|
1414
1438
|
}
|
|
1439
|
+
const merge = getComponentMerge(componentType);
|
|
1440
|
+
if (merge !== void 0 && changeset.adds.has(componentType)) {
|
|
1441
|
+
const prev = changeset.adds.get(componentType);
|
|
1442
|
+
changeset.set(componentType, merge(prev, component$1));
|
|
1443
|
+
return;
|
|
1444
|
+
}
|
|
1415
1445
|
changeset.set(componentType, component$1);
|
|
1416
1446
|
}
|
|
1417
1447
|
function processDeleteCommand(entityId, currentArchetype, componentType, changeset) {
|
|
@@ -1427,10 +1457,9 @@ function removeMatchingRelations(entityId, archetype, baseComponentId, changeset
|
|
|
1427
1457
|
if (isWildcardRelationId(componentType)) continue;
|
|
1428
1458
|
if (getComponentIdFromRelationId(componentType) === baseComponentId) changeset.delete(componentType);
|
|
1429
1459
|
}
|
|
1430
|
-
const
|
|
1431
|
-
if (
|
|
1432
|
-
|
|
1433
|
-
if (getComponentIdFromRelationId(componentType) === baseComponentId) changeset.delete(componentType);
|
|
1460
|
+
const dontFragmentData = archetype.getEntityDontFragmentRelations(entityId);
|
|
1461
|
+
if (dontFragmentData) {
|
|
1462
|
+
for (const componentType of dontFragmentData.keys()) if (getComponentIdFromRelationId(componentType) === baseComponentId) changeset.delete(componentType);
|
|
1434
1463
|
}
|
|
1435
1464
|
}
|
|
1436
1465
|
function removeWildcardRelations(entityId, currentArchetype, baseComponentId, changeset) {
|
|
@@ -1440,35 +1469,66 @@ function removeWildcardRelations(entityId, currentArchetype, baseComponentId, ch
|
|
|
1440
1469
|
function maybeRemoveWildcardMarker(entityId, archetype, removedComponentType, componentId, changeset) {
|
|
1441
1470
|
if (componentId === void 0 || !isDontFragmentComponent(componentId)) return;
|
|
1442
1471
|
const wildcardMarker = relation(componentId, "*");
|
|
1443
|
-
const
|
|
1444
|
-
if (!entityData) {
|
|
1445
|
-
changeset.delete(wildcardMarker);
|
|
1446
|
-
return;
|
|
1447
|
-
}
|
|
1448
|
-
for (const [otherComponentType] of entityData) {
|
|
1472
|
+
for (const otherComponentType of archetype.componentTypes) {
|
|
1449
1473
|
if (otherComponentType === removedComponentType) continue;
|
|
1450
1474
|
if (otherComponentType === wildcardMarker) continue;
|
|
1451
1475
|
if (changeset.removes.has(otherComponentType)) continue;
|
|
1452
1476
|
if (getComponentIdFromRelationId(otherComponentType) === componentId) return;
|
|
1453
1477
|
}
|
|
1478
|
+
const dontFragmentData = archetype.getEntityDontFragmentRelations(entityId);
|
|
1479
|
+
if (dontFragmentData) for (const otherComponentType of dontFragmentData.keys()) {
|
|
1480
|
+
if (otherComponentType === removedComponentType) continue;
|
|
1481
|
+
if (changeset.removes.has(otherComponentType)) continue;
|
|
1482
|
+
if (getComponentIdFromRelationId(otherComponentType) === componentId) return;
|
|
1483
|
+
}
|
|
1454
1484
|
changeset.delete(wildcardMarker);
|
|
1455
1485
|
}
|
|
1486
|
+
function hasEntityComponent(archetype, entityId, componentType) {
|
|
1487
|
+
if (archetype.componentTypeSet.has(componentType)) return true;
|
|
1488
|
+
return archetype.getEntityDontFragmentRelations(entityId)?.has(componentType) ?? false;
|
|
1489
|
+
}
|
|
1490
|
+
function pruneMissingRemovals(changeset, archetype, entityId) {
|
|
1491
|
+
let toPrune;
|
|
1492
|
+
for (const componentType of changeset.removes) if (!hasEntityComponent(archetype, entityId, componentType)) {
|
|
1493
|
+
if (toPrune === void 0) toPrune = [];
|
|
1494
|
+
toPrune.push(componentType);
|
|
1495
|
+
}
|
|
1496
|
+
if (toPrune !== void 0) for (const componentType of toPrune) changeset.removes.delete(componentType);
|
|
1497
|
+
}
|
|
1498
|
+
function hasArchetypeStructuralChange(changeset, currentArchetype) {
|
|
1499
|
+
for (const componentType of changeset.removes) if (!isDontFragmentRelation(componentType) && currentArchetype.componentTypeSet.has(componentType)) return true;
|
|
1500
|
+
for (const componentType of changeset.adds.keys()) if (!isDontFragmentRelation(componentType) && !currentArchetype.componentTypeSet.has(componentType)) return true;
|
|
1501
|
+
return false;
|
|
1502
|
+
}
|
|
1503
|
+
function buildFinalRegularComponentTypes(currentArchetype, changeset) {
|
|
1504
|
+
const finalRegularTypes = new Set(currentArchetype.componentTypes);
|
|
1505
|
+
for (const componentType of changeset.removes) if (!isDontFragmentRelation(componentType)) finalRegularTypes.delete(componentType);
|
|
1506
|
+
for (const componentType of changeset.adds.keys()) if (!isDontFragmentRelation(componentType)) finalRegularTypes.add(componentType);
|
|
1507
|
+
return Array.from(finalRegularTypes);
|
|
1508
|
+
}
|
|
1456
1509
|
function applyChangeset(ctx, entityId, currentArchetype, changeset, entityToArchetype) {
|
|
1457
|
-
const currentEntityData = currentArchetype.getEntity(entityId);
|
|
1458
|
-
const allCurrentComponentTypes = currentEntityData ? Array.from(currentEntityData.keys()) : currentArchetype.componentTypes;
|
|
1459
|
-
const finalComponentTypes = changeset.getFinalComponentTypes(allCurrentComponentTypes);
|
|
1460
1510
|
const removedComponents = /* @__PURE__ */ new Map();
|
|
1461
|
-
|
|
1511
|
+
pruneMissingRemovals(changeset, currentArchetype, entityId);
|
|
1512
|
+
if (hasArchetypeStructuralChange(changeset, currentArchetype)) return {
|
|
1462
1513
|
removedComponents,
|
|
1463
|
-
newArchetype: moveEntityToNewArchetype(ctx, entityId, currentArchetype,
|
|
1514
|
+
newArchetype: moveEntityToNewArchetype(ctx, entityId, currentArchetype, buildFinalRegularComponentTypes(currentArchetype, changeset), changeset, removedComponents, entityToArchetype)
|
|
1464
1515
|
};
|
|
1465
|
-
|
|
1466
|
-
else updateEntityInSameArchetype(ctx, entityId, currentArchetype, changeset, removedComponents);
|
|
1516
|
+
updateEntityInSameArchetype(ctx, entityId, currentArchetype, changeset, removedComponents);
|
|
1467
1517
|
return {
|
|
1468
1518
|
removedComponents,
|
|
1469
1519
|
newArchetype: currentArchetype
|
|
1470
1520
|
};
|
|
1471
1521
|
}
|
|
1522
|
+
/**
|
|
1523
|
+
* Optimized variant of applyChangeset for when no lifecycle hooks are registered.
|
|
1524
|
+
* Skips creating the removedComponents map, reducing allocations in the hot path.
|
|
1525
|
+
*/
|
|
1526
|
+
function applyChangesetNoHooks(ctx, entityId, currentArchetype, changeset, entityToArchetype) {
|
|
1527
|
+
pruneMissingRemovals(changeset, currentArchetype, entityId);
|
|
1528
|
+
if (hasArchetypeStructuralChange(changeset, currentArchetype)) return moveEntityToNewArchetypeNoHooks(ctx, entityId, currentArchetype, buildFinalRegularComponentTypes(currentArchetype, changeset), changeset, entityToArchetype);
|
|
1529
|
+
updateEntityInSameArchetypeNoHooks(ctx, entityId, currentArchetype, changeset);
|
|
1530
|
+
return currentArchetype;
|
|
1531
|
+
}
|
|
1472
1532
|
function moveEntityToNewArchetype(ctx, entityId, currentArchetype, finalComponentTypes, changeset, removedComponents, entityToArchetype) {
|
|
1473
1533
|
const newArchetype = ctx.ensureArchetype(finalComponentTypes);
|
|
1474
1534
|
const currentComponents = currentArchetype.removeEntity(entityId);
|
|
@@ -1484,6 +1544,28 @@ function updateEntityInSameArchetype(ctx, entityId, currentArchetype, changeset,
|
|
|
1484
1544
|
currentArchetype.set(entityId, componentType, component$1);
|
|
1485
1545
|
}
|
|
1486
1546
|
}
|
|
1547
|
+
/**
|
|
1548
|
+
* No-hooks variant: moves entity to new archetype without collecting removed component data.
|
|
1549
|
+
* Only called from applyChangesetNoHooks when no lifecycle hooks are registered.
|
|
1550
|
+
*/
|
|
1551
|
+
function moveEntityToNewArchetypeNoHooks(ctx, entityId, currentArchetype, finalComponentTypes, changeset, entityToArchetype) {
|
|
1552
|
+
const newArchetype = ctx.ensureArchetype(finalComponentTypes);
|
|
1553
|
+
const currentComponents = currentArchetype.removeEntity(entityId);
|
|
1554
|
+
newArchetype.addEntity(entityId, changeset.applyTo(currentComponents));
|
|
1555
|
+
entityToArchetype.set(entityId, newArchetype);
|
|
1556
|
+
return newArchetype;
|
|
1557
|
+
}
|
|
1558
|
+
/**
|
|
1559
|
+
* No-hooks variant: updates entity in same archetype without tracking removed component data.
|
|
1560
|
+
* Only called from applyChangesetNoHooks when no lifecycle hooks are registered.
|
|
1561
|
+
*/
|
|
1562
|
+
function updateEntityInSameArchetypeNoHooks(ctx, entityId, currentArchetype, changeset) {
|
|
1563
|
+
applyDontFragmentChangesNoHooks(ctx.dontFragmentRelations, entityId, changeset);
|
|
1564
|
+
for (const [componentType, component$1] of changeset.adds) {
|
|
1565
|
+
if (isDontFragmentRelation(componentType)) continue;
|
|
1566
|
+
currentArchetype.set(entityId, componentType, component$1);
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1487
1569
|
function applyDontFragmentChanges(dontFragmentRelations, entityId, changeset, removedComponents) {
|
|
1488
1570
|
let entityRelations = dontFragmentRelations.get(entityId);
|
|
1489
1571
|
for (const componentType of changeset.removes) if (isDontFragmentRelation(componentType)) {
|
|
@@ -1504,6 +1586,23 @@ function applyDontFragmentChanges(dontFragmentRelations, entityId, changeset, re
|
|
|
1504
1586
|
}
|
|
1505
1587
|
if (entityRelations && entityRelations.size === 0) dontFragmentRelations.delete(entityId);
|
|
1506
1588
|
}
|
|
1589
|
+
/**
|
|
1590
|
+
* No-hooks variant of applyDontFragmentChanges that skips tracking removed component data.
|
|
1591
|
+
*/
|
|
1592
|
+
function applyDontFragmentChangesNoHooks(dontFragmentRelations, entityId, changeset) {
|
|
1593
|
+
let entityRelations = dontFragmentRelations.get(entityId);
|
|
1594
|
+
for (const componentType of changeset.removes) if (isDontFragmentRelation(componentType)) {
|
|
1595
|
+
if (entityRelations) entityRelations.delete(componentType);
|
|
1596
|
+
}
|
|
1597
|
+
for (const [componentType, component$1] of changeset.adds) if (isDontFragmentRelation(componentType)) {
|
|
1598
|
+
if (!entityRelations) {
|
|
1599
|
+
entityRelations = /* @__PURE__ */ new Map();
|
|
1600
|
+
dontFragmentRelations.set(entityId, entityRelations);
|
|
1601
|
+
}
|
|
1602
|
+
entityRelations.set(componentType, component$1);
|
|
1603
|
+
}
|
|
1604
|
+
if (entityRelations && entityRelations.size === 0) dontFragmentRelations.delete(entityId);
|
|
1605
|
+
}
|
|
1507
1606
|
function filterRegularComponentTypes(componentTypes) {
|
|
1508
1607
|
const regularTypes = [];
|
|
1509
1608
|
for (const componentType of componentTypes) {
|
|
@@ -1516,12 +1615,6 @@ function filterRegularComponentTypes(componentTypes) {
|
|
|
1516
1615
|
}
|
|
1517
1616
|
return regularTypes;
|
|
1518
1617
|
}
|
|
1519
|
-
function areComponentTypesEqual(types1, types2) {
|
|
1520
|
-
if (types1.length !== types2.length) return false;
|
|
1521
|
-
const sorted1 = [...types1].sort((a, b) => a - b);
|
|
1522
|
-
const sorted2 = [...types2].sort((a, b) => a - b);
|
|
1523
|
-
return sorted1.every((v, i) => v === sorted2[i]);
|
|
1524
|
-
}
|
|
1525
1618
|
|
|
1526
1619
|
//#endregion
|
|
1527
1620
|
//#region src/core/world-hooks.ts
|
|
@@ -1794,9 +1887,23 @@ var World = class {
|
|
|
1794
1887
|
relationEntityIdsByTarget = /* @__PURE__ */ new Map();
|
|
1795
1888
|
queries = [];
|
|
1796
1889
|
queryCache = /* @__PURE__ */ new Map();
|
|
1797
|
-
commandBuffer = new CommandBuffer((entityId, commands) => this.executeEntityCommands(entityId, commands));
|
|
1798
1890
|
legacyHooks = /* @__PURE__ */ new Map();
|
|
1799
1891
|
hooks = /* @__PURE__ */ new Set();
|
|
1892
|
+
commandBuffer = new CommandBuffer((entityId, commands) => this.executeEntityCommands(entityId, commands));
|
|
1893
|
+
_changeset = new ComponentChangeset();
|
|
1894
|
+
/** Cached command processor context to avoid per-entity object allocation */
|
|
1895
|
+
_commandCtx = {
|
|
1896
|
+
dontFragmentRelations: this.dontFragmentRelations,
|
|
1897
|
+
ensureArchetype: (ct) => this.ensureArchetype(ct)
|
|
1898
|
+
};
|
|
1899
|
+
/** Cached hooks context to avoid per-entity object allocation */
|
|
1900
|
+
_hooksCtx = {
|
|
1901
|
+
hooks: this.legacyHooks,
|
|
1902
|
+
multiHooks: this.hooks,
|
|
1903
|
+
has: (eid, ct) => this.has(eid, ct),
|
|
1904
|
+
get: (eid, ct) => this.get(eid, ct),
|
|
1905
|
+
getOptional: (eid, ct) => this.getOptional(eid, ct)
|
|
1906
|
+
};
|
|
1800
1907
|
constructor(snapshot) {
|
|
1801
1908
|
if (snapshot && typeof snapshot === "object") this.deserializeSnapshot(snapshot);
|
|
1802
1909
|
}
|
|
@@ -1940,40 +2047,77 @@ var World = class {
|
|
|
1940
2047
|
if (this.isComponentEntityId(entityId)) return true;
|
|
1941
2048
|
return this.entityToArchetype.has(entityId);
|
|
1942
2049
|
}
|
|
1943
|
-
|
|
2050
|
+
assertEntityExists(entityId, label) {
|
|
2051
|
+
if (!this.exists(entityId)) throw new Error(`${label} ${entityId} does not exist`);
|
|
2052
|
+
}
|
|
2053
|
+
assertComponentTypeValid(componentType) {
|
|
2054
|
+
if (getDetailedIdType(componentType).type === "invalid") throw new Error(`Invalid component type: ${componentType}`);
|
|
2055
|
+
}
|
|
2056
|
+
assertSetComponentTypeValid(componentType) {
|
|
2057
|
+
const detailedType = getDetailedIdType(componentType);
|
|
2058
|
+
if (detailedType.type === "invalid") throw new Error(`Invalid component type: ${componentType}`);
|
|
2059
|
+
if (detailedType.type === "wildcard-relation") throw new Error(`Cannot directly add wildcard relation components: ${componentType}`);
|
|
2060
|
+
}
|
|
2061
|
+
resolveSetOperation(entityId, componentTypeOrComponent, maybeComponent) {
|
|
1944
2062
|
if (maybeComponent === void 0 && componentTypeOrComponent !== void 0) {
|
|
1945
|
-
const detailedType
|
|
1946
|
-
if (detailedType
|
|
2063
|
+
const detailedType = getDetailedIdType(entityId);
|
|
2064
|
+
if (detailedType.type === "component" || detailedType.type === "component-relation") {
|
|
1947
2065
|
const componentId = entityId;
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
2066
|
+
this.assertEntityExists(componentId, "Component entity");
|
|
2067
|
+
this.assertSetComponentTypeValid(componentId);
|
|
2068
|
+
return {
|
|
2069
|
+
entityId: componentId,
|
|
2070
|
+
componentType: componentId,
|
|
2071
|
+
component: componentTypeOrComponent
|
|
2072
|
+
};
|
|
1955
2073
|
}
|
|
1956
2074
|
}
|
|
1957
|
-
const
|
|
2075
|
+
const targetEntityId = entityId;
|
|
1958
2076
|
const componentType = componentTypeOrComponent;
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
2077
|
+
this.assertEntityExists(targetEntityId, "Entity");
|
|
2078
|
+
this.assertSetComponentTypeValid(componentType);
|
|
2079
|
+
return {
|
|
2080
|
+
entityId: targetEntityId,
|
|
2081
|
+
componentType,
|
|
2082
|
+
component: maybeComponent
|
|
2083
|
+
};
|
|
1965
2084
|
}
|
|
1966
|
-
|
|
2085
|
+
resolveRemoveOperation(entityId, componentType) {
|
|
1967
2086
|
if (componentType === void 0) {
|
|
1968
2087
|
const componentId = entityId;
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
2088
|
+
this.assertEntityExists(componentId, "Component entity");
|
|
2089
|
+
return {
|
|
2090
|
+
entityId: componentId,
|
|
2091
|
+
componentType: componentId
|
|
2092
|
+
};
|
|
1972
2093
|
}
|
|
1973
|
-
const
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
2094
|
+
const targetEntityId = entityId;
|
|
2095
|
+
this.assertEntityExists(targetEntityId, "Entity");
|
|
2096
|
+
this.assertComponentTypeValid(componentType);
|
|
2097
|
+
return {
|
|
2098
|
+
entityId: targetEntityId,
|
|
2099
|
+
componentType
|
|
2100
|
+
};
|
|
2101
|
+
}
|
|
2102
|
+
getComponentEntityWildcardRelations(entityId, wildcardComponentType) {
|
|
2103
|
+
const componentId = getComponentIdFromRelationId(wildcardComponentType);
|
|
2104
|
+
const data = this.componentEntityComponents.get(entityId);
|
|
2105
|
+
if (componentId === void 0 || !data) return [];
|
|
2106
|
+
const relations = [];
|
|
2107
|
+
for (const [key, value] of data.entries()) {
|
|
2108
|
+
if (getComponentIdFromRelationId(key) !== componentId) continue;
|
|
2109
|
+
const detailed = getDetailedIdType(key);
|
|
2110
|
+
if (detailed.type === "entity-relation" || detailed.type === "component-relation") relations.push([detailed.targetId, value]);
|
|
2111
|
+
}
|
|
2112
|
+
return relations;
|
|
2113
|
+
}
|
|
2114
|
+
set(entityId, componentTypeOrComponent, maybeComponent) {
|
|
2115
|
+
const { entityId: targetEntityId, componentType, component: component$1 } = this.resolveSetOperation(entityId, componentTypeOrComponent, maybeComponent);
|
|
2116
|
+
this.commandBuffer.set(targetEntityId, componentType, component$1);
|
|
2117
|
+
}
|
|
2118
|
+
remove(entityId, componentType) {
|
|
2119
|
+
const { entityId: targetEntityId, componentType: targetComponentType } = this.resolveRemoveOperation(entityId, componentType);
|
|
2120
|
+
this.commandBuffer.remove(targetEntityId, targetComponentType);
|
|
1977
2121
|
}
|
|
1978
2122
|
/**
|
|
1979
2123
|
* Deletes an entity and all its components from the world.
|
|
@@ -2012,18 +2156,7 @@ var World = class {
|
|
|
2012
2156
|
}
|
|
2013
2157
|
get(entityId, componentType = entityId) {
|
|
2014
2158
|
if (this.isComponentEntityId(entityId)) {
|
|
2015
|
-
if (isWildcardRelationId(componentType))
|
|
2016
|
-
const componentId = getComponentIdFromRelationId(componentType);
|
|
2017
|
-
const data$1 = this.componentEntityComponents.get(entityId);
|
|
2018
|
-
const relations = [];
|
|
2019
|
-
if (componentId !== void 0 && data$1) {
|
|
2020
|
-
for (const [key, value] of data$1.entries()) if (getComponentIdFromRelationId(key) === componentId) {
|
|
2021
|
-
const detailed = getDetailedIdType(key);
|
|
2022
|
-
if (detailed.type === "entity-relation" || detailed.type === "component-relation") relations.push([detailed.targetId, value]);
|
|
2023
|
-
}
|
|
2024
|
-
}
|
|
2025
|
-
return relations;
|
|
2026
|
-
}
|
|
2159
|
+
if (isWildcardRelationId(componentType)) return this.getComponentEntityWildcardRelations(entityId, componentType);
|
|
2027
2160
|
const data = this.componentEntityComponents.get(entityId);
|
|
2028
2161
|
if (!data || !data.has(componentType)) throw new Error(`Entity ${entityId} does not have component ${componentType}. Use has() to check component existence before calling get().`);
|
|
2029
2162
|
return data.get(componentType);
|
|
@@ -2040,15 +2173,7 @@ var World = class {
|
|
|
2040
2173
|
getOptional(entityId, componentType = entityId) {
|
|
2041
2174
|
if (this.isComponentEntityId(entityId)) {
|
|
2042
2175
|
if (isWildcardRelationId(componentType)) {
|
|
2043
|
-
const
|
|
2044
|
-
if (componentId === void 0) return void 0;
|
|
2045
|
-
const data$1 = this.componentEntityComponents.get(entityId);
|
|
2046
|
-
if (!data$1) return void 0;
|
|
2047
|
-
const relations = [];
|
|
2048
|
-
for (const [key, value] of data$1.entries()) if (getComponentIdFromRelationId(key) === componentId) {
|
|
2049
|
-
const detailed = getDetailedIdType(key);
|
|
2050
|
-
if (detailed.type === "entity-relation" || detailed.type === "component-relation") relations.push([detailed.targetId, value]);
|
|
2051
|
-
}
|
|
2176
|
+
const relations = this.getComponentEntityWildcardRelations(entityId, componentType);
|
|
2052
2177
|
if (relations.length === 0) return void 0;
|
|
2053
2178
|
return { value: relations };
|
|
2054
2179
|
}
|
|
@@ -2190,7 +2315,7 @@ var World = class {
|
|
|
2190
2315
|
* });
|
|
2191
2316
|
*/
|
|
2192
2317
|
createQuery(componentTypes, filter = {}) {
|
|
2193
|
-
const sortedTypes =
|
|
2318
|
+
const sortedTypes = normalizeComponentTypes(componentTypes);
|
|
2194
2319
|
const filterKey = serializeQueryFilter(filter);
|
|
2195
2320
|
const key = `${this.createArchetypeSignature(sortedTypes)}${filterKey ? `|${filterKey}` : ""}`;
|
|
2196
2321
|
const cached = this.queryCache.get(key);
|
|
@@ -2308,6 +2433,11 @@ var World = class {
|
|
|
2308
2433
|
const archetypeLists = componentTypes.map((type) => this.archetypesByComponent.get(type) || []).sort((a, b) => a.length - b.length);
|
|
2309
2434
|
const shortest = archetypeLists[0];
|
|
2310
2435
|
if (shortest.length === 0) return [];
|
|
2436
|
+
if (archetypeLists.length === 2) {
|
|
2437
|
+
const second = archetypeLists[1];
|
|
2438
|
+
const secondSet = new Set(second);
|
|
2439
|
+
return shortest.filter((a) => secondSet.has(a));
|
|
2440
|
+
}
|
|
2311
2441
|
let result = new Set(shortest);
|
|
2312
2442
|
for (let i = 1; i < archetypeLists.length; i++) {
|
|
2313
2443
|
const listSet = new Set(archetypeLists[i]);
|
|
@@ -2329,7 +2459,8 @@ var World = class {
|
|
|
2329
2459
|
}
|
|
2330
2460
|
}
|
|
2331
2461
|
executeEntityCommands(entityId, commands) {
|
|
2332
|
-
const changeset =
|
|
2462
|
+
const changeset = this._changeset;
|
|
2463
|
+
changeset.clear();
|
|
2333
2464
|
if (this.isComponentEntityId(entityId)) {
|
|
2334
2465
|
this.executeComponentEntityCommands(entityId, commands);
|
|
2335
2466
|
return changeset;
|
|
@@ -2343,11 +2474,15 @@ var World = class {
|
|
|
2343
2474
|
processCommands(entityId, currentArchetype, commands, changeset, (eid, arch, compId) => {
|
|
2344
2475
|
if (isExclusiveComponent(compId)) removeMatchingRelations(eid, arch, compId, changeset);
|
|
2345
2476
|
});
|
|
2346
|
-
const
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2477
|
+
const hasHooks = this.legacyHooks.size > 0 || this.hooks.size > 0;
|
|
2478
|
+
const hasEntityRefs = changeset.removes.size > 0 || changeset.adds.size > 0;
|
|
2479
|
+
if (!hasHooks) {
|
|
2480
|
+
applyChangesetNoHooks(this._commandCtx, entityId, currentArchetype, changeset, this.entityToArchetype);
|
|
2481
|
+
if (hasEntityRefs) this.updateEntityReferences(entityId, changeset);
|
|
2482
|
+
return changeset;
|
|
2483
|
+
}
|
|
2484
|
+
const { removedComponents, newArchetype } = applyChangeset(this._commandCtx, entityId, currentArchetype, changeset, this.entityToArchetype);
|
|
2485
|
+
if (hasEntityRefs) this.updateEntityReferences(entityId, changeset);
|
|
2351
2486
|
triggerLifecycleHooks(this.createHooksContext(), entityId, changeset.adds, removedComponents, currentArchetype, newArchetype);
|
|
2352
2487
|
return changeset;
|
|
2353
2488
|
}
|
|
@@ -2356,27 +2491,32 @@ var World = class {
|
|
|
2356
2491
|
this.clearComponentEntityComponents(entityId);
|
|
2357
2492
|
return;
|
|
2358
2493
|
}
|
|
2359
|
-
|
|
2360
|
-
|
|
2494
|
+
const pendingSetValues = /* @__PURE__ */ new Map();
|
|
2495
|
+
for (const command of commands) if (command.type === "set" && command.componentType) {
|
|
2496
|
+
const merge = getComponentMerge(command.componentType);
|
|
2497
|
+
let nextValue = command.component;
|
|
2498
|
+
if (merge !== void 0 && pendingSetValues.has(command.componentType)) nextValue = merge(pendingSetValues.get(command.componentType), command.component);
|
|
2499
|
+
pendingSetValues.set(command.componentType, nextValue);
|
|
2500
|
+
this.getComponentEntityComponents(entityId, true).set(command.componentType, nextValue);
|
|
2501
|
+
} else if (command.type === "delete" && command.componentType) {
|
|
2361
2502
|
const data = this.componentEntityComponents.get(entityId);
|
|
2362
|
-
if (!data) continue;
|
|
2363
2503
|
if (isWildcardRelationId(command.componentType)) {
|
|
2364
2504
|
const componentId = getComponentIdFromRelationId(command.componentType);
|
|
2365
2505
|
if (componentId !== void 0) {
|
|
2366
|
-
|
|
2506
|
+
if (data) {
|
|
2507
|
+
for (const key of Array.from(data.keys())) if (getComponentIdFromRelationId(key) === componentId) data.delete(key);
|
|
2508
|
+
}
|
|
2509
|
+
for (const key of Array.from(pendingSetValues.keys())) if (getComponentIdFromRelationId(key) === componentId) pendingSetValues.delete(key);
|
|
2367
2510
|
}
|
|
2368
|
-
} else
|
|
2369
|
-
|
|
2511
|
+
} else {
|
|
2512
|
+
data?.delete(command.componentType);
|
|
2513
|
+
pendingSetValues.delete(command.componentType);
|
|
2514
|
+
}
|
|
2515
|
+
if (data?.size === 0) this.clearComponentEntityComponents(entityId);
|
|
2370
2516
|
}
|
|
2371
2517
|
}
|
|
2372
2518
|
createHooksContext() {
|
|
2373
|
-
return
|
|
2374
|
-
hooks: this.legacyHooks,
|
|
2375
|
-
multiHooks: this.hooks,
|
|
2376
|
-
has: (eid, ct) => this.has(eid, ct),
|
|
2377
|
-
get: (eid, ct) => this.get(eid, ct),
|
|
2378
|
-
getOptional: (eid, ct) => this.getOptional(eid, ct)
|
|
2379
|
-
};
|
|
2519
|
+
return this._hooksCtx;
|
|
2380
2520
|
}
|
|
2381
2521
|
removeComponentImmediate(entityId, componentType, targetEntityId) {
|
|
2382
2522
|
const sourceArchetype = this.entityToArchetype.get(entityId);
|
|
@@ -2385,10 +2525,7 @@ var World = class {
|
|
|
2385
2525
|
changeset.delete(componentType);
|
|
2386
2526
|
maybeRemoveWildcardMarker(entityId, sourceArchetype, componentType, getComponentIdFromRelationId(componentType), changeset);
|
|
2387
2527
|
const removedComponent = sourceArchetype.get(entityId, componentType);
|
|
2388
|
-
const { newArchetype } = applyChangeset(
|
|
2389
|
-
dontFragmentRelations: this.dontFragmentRelations,
|
|
2390
|
-
ensureArchetype: (ct) => this.ensureArchetype(ct)
|
|
2391
|
-
}, entityId, sourceArchetype, changeset, this.entityToArchetype);
|
|
2528
|
+
const { newArchetype } = applyChangeset(this._commandCtx, entityId, sourceArchetype, changeset, this.entityToArchetype);
|
|
2392
2529
|
untrackEntityReference(this.entityReferences, entityId, componentType, targetEntityId);
|
|
2393
2530
|
triggerLifecycleHooks(this.createHooksContext(), entityId, /* @__PURE__ */ new Map(), new Map([[componentType, removedComponent]]), sourceArchetype, newArchetype);
|
|
2394
2531
|
}
|
|
@@ -2403,9 +2540,9 @@ var World = class {
|
|
|
2403
2540
|
} else if (componentType >= ENTITY_ID_START) trackEntityReference(this.entityReferences, entityId, componentType, componentType);
|
|
2404
2541
|
}
|
|
2405
2542
|
ensureArchetype(componentTypes) {
|
|
2406
|
-
const sortedTypes = filterRegularComponentTypes(componentTypes)
|
|
2543
|
+
const sortedTypes = normalizeComponentTypes(filterRegularComponentTypes(componentTypes));
|
|
2407
2544
|
const hashKey = this.createArchetypeSignature(sortedTypes);
|
|
2408
|
-
return
|
|
2545
|
+
return getOrCompute(this.archetypeBySignature, hashKey, () => this.createNewArchetype(sortedTypes));
|
|
2409
2546
|
}
|
|
2410
2547
|
createNewArchetype(componentTypes) {
|
|
2411
2548
|
const newArchetype = new Archetype(componentTypes, this.dontFragmentRelations);
|