@codehz/ecs 0.6.9 → 0.6.11
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 +24 -1
- package/builder.d.mts +34 -5
- package/package.json +1 -1
- package/world.mjs +261 -121
- 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
|
|
@@ -1593,12 +1686,14 @@ function triggerMultiComponentHooks(ctx, entityId, addedComponents, removedCompo
|
|
|
1593
1686
|
const anyRequiredAdded = requiredComponents.some((c) => anyComponentMatches(addedComponents, c));
|
|
1594
1687
|
const anyOptionalAdded = optionalComponents.some((c) => anyComponentMatches(addedComponents, c));
|
|
1595
1688
|
const anyOptionalRemoved = optionalComponents.some((c) => anyComponentMatches(removedComponents, c));
|
|
1596
|
-
if ((anyRequiredAdded || anyOptionalAdded || anyOptionalRemoved) && entityHasAllComponents(ctx, entityId, requiredComponents)) hook.on_set(entityId, ...collectMultiHookComponents(ctx, entityId, componentTypes));
|
|
1689
|
+
if (!oldArchetype.matchingMultiHooks.has(entry) || (anyRequiredAdded || anyOptionalAdded || anyOptionalRemoved) && entityHasAllComponents(ctx, entityId, requiredComponents)) hook.on_set(entityId, ...collectMultiHookComponents(ctx, entityId, componentTypes));
|
|
1597
1690
|
}
|
|
1598
|
-
|
|
1691
|
+
for (const entry of oldArchetype.matchingMultiHooks) {
|
|
1599
1692
|
const { hook, requiredComponents, componentTypes } = entry;
|
|
1600
1693
|
if (!hook.on_remove) continue;
|
|
1601
|
-
|
|
1694
|
+
const lostRequiredMatch = requiredComponents.some((c) => anyComponentMatches(removedComponents, c)) && entityHadAllComponentsBefore(ctx, entityId, requiredComponents, removedComponents) && !entityHasAllComponents(ctx, entityId, requiredComponents);
|
|
1695
|
+
const exitedMatchingSet = !newArchetype.matchingMultiHooks.has(entry);
|
|
1696
|
+
if (lostRequiredMatch || exitedMatchingSet) hook.on_remove(entityId, ...collectMultiHookComponentsWithRemoved(ctx, entityId, componentTypes, removedComponents));
|
|
1602
1697
|
}
|
|
1603
1698
|
}
|
|
1604
1699
|
function entityHasAllComponents(ctx, entityId, requiredComponents) {
|
|
@@ -1794,9 +1889,23 @@ var World = class {
|
|
|
1794
1889
|
relationEntityIdsByTarget = /* @__PURE__ */ new Map();
|
|
1795
1890
|
queries = [];
|
|
1796
1891
|
queryCache = /* @__PURE__ */ new Map();
|
|
1797
|
-
commandBuffer = new CommandBuffer((entityId, commands) => this.executeEntityCommands(entityId, commands));
|
|
1798
1892
|
legacyHooks = /* @__PURE__ */ new Map();
|
|
1799
1893
|
hooks = /* @__PURE__ */ new Set();
|
|
1894
|
+
commandBuffer = new CommandBuffer((entityId, commands) => this.executeEntityCommands(entityId, commands));
|
|
1895
|
+
_changeset = new ComponentChangeset();
|
|
1896
|
+
/** Cached command processor context to avoid per-entity object allocation */
|
|
1897
|
+
_commandCtx = {
|
|
1898
|
+
dontFragmentRelations: this.dontFragmentRelations,
|
|
1899
|
+
ensureArchetype: (ct) => this.ensureArchetype(ct)
|
|
1900
|
+
};
|
|
1901
|
+
/** Cached hooks context to avoid per-entity object allocation */
|
|
1902
|
+
_hooksCtx = {
|
|
1903
|
+
hooks: this.legacyHooks,
|
|
1904
|
+
multiHooks: this.hooks,
|
|
1905
|
+
has: (eid, ct) => this.has(eid, ct),
|
|
1906
|
+
get: (eid, ct) => this.get(eid, ct),
|
|
1907
|
+
getOptional: (eid, ct) => this.getOptional(eid, ct)
|
|
1908
|
+
};
|
|
1800
1909
|
constructor(snapshot) {
|
|
1801
1910
|
if (snapshot && typeof snapshot === "object") this.deserializeSnapshot(snapshot);
|
|
1802
1911
|
}
|
|
@@ -1940,40 +2049,77 @@ var World = class {
|
|
|
1940
2049
|
if (this.isComponentEntityId(entityId)) return true;
|
|
1941
2050
|
return this.entityToArchetype.has(entityId);
|
|
1942
2051
|
}
|
|
1943
|
-
|
|
2052
|
+
assertEntityExists(entityId, label) {
|
|
2053
|
+
if (!this.exists(entityId)) throw new Error(`${label} ${entityId} does not exist`);
|
|
2054
|
+
}
|
|
2055
|
+
assertComponentTypeValid(componentType) {
|
|
2056
|
+
if (getDetailedIdType(componentType).type === "invalid") throw new Error(`Invalid component type: ${componentType}`);
|
|
2057
|
+
}
|
|
2058
|
+
assertSetComponentTypeValid(componentType) {
|
|
2059
|
+
const detailedType = getDetailedIdType(componentType);
|
|
2060
|
+
if (detailedType.type === "invalid") throw new Error(`Invalid component type: ${componentType}`);
|
|
2061
|
+
if (detailedType.type === "wildcard-relation") throw new Error(`Cannot directly add wildcard relation components: ${componentType}`);
|
|
2062
|
+
}
|
|
2063
|
+
resolveSetOperation(entityId, componentTypeOrComponent, maybeComponent) {
|
|
1944
2064
|
if (maybeComponent === void 0 && componentTypeOrComponent !== void 0) {
|
|
1945
|
-
const detailedType
|
|
1946
|
-
if (detailedType
|
|
2065
|
+
const detailedType = getDetailedIdType(entityId);
|
|
2066
|
+
if (detailedType.type === "component" || detailedType.type === "component-relation") {
|
|
1947
2067
|
const componentId = entityId;
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
2068
|
+
this.assertEntityExists(componentId, "Component entity");
|
|
2069
|
+
this.assertSetComponentTypeValid(componentId);
|
|
2070
|
+
return {
|
|
2071
|
+
entityId: componentId,
|
|
2072
|
+
componentType: componentId,
|
|
2073
|
+
component: componentTypeOrComponent
|
|
2074
|
+
};
|
|
1955
2075
|
}
|
|
1956
2076
|
}
|
|
1957
|
-
const
|
|
2077
|
+
const targetEntityId = entityId;
|
|
1958
2078
|
const componentType = componentTypeOrComponent;
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
2079
|
+
this.assertEntityExists(targetEntityId, "Entity");
|
|
2080
|
+
this.assertSetComponentTypeValid(componentType);
|
|
2081
|
+
return {
|
|
2082
|
+
entityId: targetEntityId,
|
|
2083
|
+
componentType,
|
|
2084
|
+
component: maybeComponent
|
|
2085
|
+
};
|
|
1965
2086
|
}
|
|
1966
|
-
|
|
2087
|
+
resolveRemoveOperation(entityId, componentType) {
|
|
1967
2088
|
if (componentType === void 0) {
|
|
1968
2089
|
const componentId = entityId;
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
2090
|
+
this.assertEntityExists(componentId, "Component entity");
|
|
2091
|
+
return {
|
|
2092
|
+
entityId: componentId,
|
|
2093
|
+
componentType: componentId
|
|
2094
|
+
};
|
|
1972
2095
|
}
|
|
1973
|
-
const
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
2096
|
+
const targetEntityId = entityId;
|
|
2097
|
+
this.assertEntityExists(targetEntityId, "Entity");
|
|
2098
|
+
this.assertComponentTypeValid(componentType);
|
|
2099
|
+
return {
|
|
2100
|
+
entityId: targetEntityId,
|
|
2101
|
+
componentType
|
|
2102
|
+
};
|
|
2103
|
+
}
|
|
2104
|
+
getComponentEntityWildcardRelations(entityId, wildcardComponentType) {
|
|
2105
|
+
const componentId = getComponentIdFromRelationId(wildcardComponentType);
|
|
2106
|
+
const data = this.componentEntityComponents.get(entityId);
|
|
2107
|
+
if (componentId === void 0 || !data) return [];
|
|
2108
|
+
const relations = [];
|
|
2109
|
+
for (const [key, value] of data.entries()) {
|
|
2110
|
+
if (getComponentIdFromRelationId(key) !== componentId) continue;
|
|
2111
|
+
const detailed = getDetailedIdType(key);
|
|
2112
|
+
if (detailed.type === "entity-relation" || detailed.type === "component-relation") relations.push([detailed.targetId, value]);
|
|
2113
|
+
}
|
|
2114
|
+
return relations;
|
|
2115
|
+
}
|
|
2116
|
+
set(entityId, componentTypeOrComponent, maybeComponent) {
|
|
2117
|
+
const { entityId: targetEntityId, componentType, component: component$1 } = this.resolveSetOperation(entityId, componentTypeOrComponent, maybeComponent);
|
|
2118
|
+
this.commandBuffer.set(targetEntityId, componentType, component$1);
|
|
2119
|
+
}
|
|
2120
|
+
remove(entityId, componentType) {
|
|
2121
|
+
const { entityId: targetEntityId, componentType: targetComponentType } = this.resolveRemoveOperation(entityId, componentType);
|
|
2122
|
+
this.commandBuffer.remove(targetEntityId, targetComponentType);
|
|
1977
2123
|
}
|
|
1978
2124
|
/**
|
|
1979
2125
|
* Deletes an entity and all its components from the world.
|
|
@@ -2012,18 +2158,7 @@ var World = class {
|
|
|
2012
2158
|
}
|
|
2013
2159
|
get(entityId, componentType = entityId) {
|
|
2014
2160
|
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
|
-
}
|
|
2161
|
+
if (isWildcardRelationId(componentType)) return this.getComponentEntityWildcardRelations(entityId, componentType);
|
|
2027
2162
|
const data = this.componentEntityComponents.get(entityId);
|
|
2028
2163
|
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
2164
|
return data.get(componentType);
|
|
@@ -2040,15 +2175,7 @@ var World = class {
|
|
|
2040
2175
|
getOptional(entityId, componentType = entityId) {
|
|
2041
2176
|
if (this.isComponentEntityId(entityId)) {
|
|
2042
2177
|
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
|
-
}
|
|
2178
|
+
const relations = this.getComponentEntityWildcardRelations(entityId, componentType);
|
|
2052
2179
|
if (relations.length === 0) return void 0;
|
|
2053
2180
|
return { value: relations };
|
|
2054
2181
|
}
|
|
@@ -2065,7 +2192,7 @@ var World = class {
|
|
|
2065
2192
|
}
|
|
2066
2193
|
return archetype.getOptional(entityId, componentType);
|
|
2067
2194
|
}
|
|
2068
|
-
hook(componentTypesOrSingle, hook) {
|
|
2195
|
+
hook(componentTypesOrSingle, hook, filter) {
|
|
2069
2196
|
if (typeof hook === "function") if (Array.isArray(componentTypesOrSingle)) {
|
|
2070
2197
|
const callback = hook;
|
|
2071
2198
|
hook = {
|
|
@@ -2092,14 +2219,15 @@ var World = class {
|
|
|
2092
2219
|
componentTypes,
|
|
2093
2220
|
requiredComponents,
|
|
2094
2221
|
optionalComponents,
|
|
2222
|
+
filter: filter || {},
|
|
2095
2223
|
hook
|
|
2096
2224
|
};
|
|
2097
2225
|
this.hooks.add(entry);
|
|
2098
2226
|
for (const archetype of this.archetypes) if (this.archetypeMatchesHook(archetype, entry)) archetype.matchingMultiHooks.add(entry);
|
|
2099
2227
|
const multiHook = hook;
|
|
2100
|
-
if (multiHook.on_init !== void 0) {
|
|
2101
|
-
|
|
2102
|
-
for (const
|
|
2228
|
+
if (multiHook.on_init !== void 0) for (const archetype of this.archetypes) {
|
|
2229
|
+
if (!this.archetypeMatchesHook(archetype, entry)) continue;
|
|
2230
|
+
for (const entityId of archetype.getEntities()) {
|
|
2103
2231
|
const components = collectMultiHookComponents(this.createHooksContext(), entityId, componentTypes);
|
|
2104
2232
|
multiHook.on_init(entityId, ...components);
|
|
2105
2233
|
}
|
|
@@ -2190,7 +2318,7 @@ var World = class {
|
|
|
2190
2318
|
* });
|
|
2191
2319
|
*/
|
|
2192
2320
|
createQuery(componentTypes, filter = {}) {
|
|
2193
|
-
const sortedTypes =
|
|
2321
|
+
const sortedTypes = normalizeComponentTypes(componentTypes);
|
|
2194
2322
|
const filterKey = serializeQueryFilter(filter);
|
|
2195
2323
|
const key = `${this.createArchetypeSignature(sortedTypes)}${filterKey ? `|${filterKey}` : ""}`;
|
|
2196
2324
|
const cached = this.queryCache.get(key);
|
|
@@ -2308,6 +2436,11 @@ var World = class {
|
|
|
2308
2436
|
const archetypeLists = componentTypes.map((type) => this.archetypesByComponent.get(type) || []).sort((a, b) => a.length - b.length);
|
|
2309
2437
|
const shortest = archetypeLists[0];
|
|
2310
2438
|
if (shortest.length === 0) return [];
|
|
2439
|
+
if (archetypeLists.length === 2) {
|
|
2440
|
+
const second = archetypeLists[1];
|
|
2441
|
+
const secondSet = new Set(second);
|
|
2442
|
+
return shortest.filter((a) => secondSet.has(a));
|
|
2443
|
+
}
|
|
2311
2444
|
let result = new Set(shortest);
|
|
2312
2445
|
for (let i = 1; i < archetypeLists.length; i++) {
|
|
2313
2446
|
const listSet = new Set(archetypeLists[i]);
|
|
@@ -2329,7 +2462,8 @@ var World = class {
|
|
|
2329
2462
|
}
|
|
2330
2463
|
}
|
|
2331
2464
|
executeEntityCommands(entityId, commands) {
|
|
2332
|
-
const changeset =
|
|
2465
|
+
const changeset = this._changeset;
|
|
2466
|
+
changeset.clear();
|
|
2333
2467
|
if (this.isComponentEntityId(entityId)) {
|
|
2334
2468
|
this.executeComponentEntityCommands(entityId, commands);
|
|
2335
2469
|
return changeset;
|
|
@@ -2343,11 +2477,15 @@ var World = class {
|
|
|
2343
2477
|
processCommands(entityId, currentArchetype, commands, changeset, (eid, arch, compId) => {
|
|
2344
2478
|
if (isExclusiveComponent(compId)) removeMatchingRelations(eid, arch, compId, changeset);
|
|
2345
2479
|
});
|
|
2346
|
-
const
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2480
|
+
const hasHooks = this.legacyHooks.size > 0 || this.hooks.size > 0;
|
|
2481
|
+
const hasEntityRefs = changeset.removes.size > 0 || changeset.adds.size > 0;
|
|
2482
|
+
if (!hasHooks) {
|
|
2483
|
+
applyChangesetNoHooks(this._commandCtx, entityId, currentArchetype, changeset, this.entityToArchetype);
|
|
2484
|
+
if (hasEntityRefs) this.updateEntityReferences(entityId, changeset);
|
|
2485
|
+
return changeset;
|
|
2486
|
+
}
|
|
2487
|
+
const { removedComponents, newArchetype } = applyChangeset(this._commandCtx, entityId, currentArchetype, changeset, this.entityToArchetype);
|
|
2488
|
+
if (hasEntityRefs) this.updateEntityReferences(entityId, changeset);
|
|
2351
2489
|
triggerLifecycleHooks(this.createHooksContext(), entityId, changeset.adds, removedComponents, currentArchetype, newArchetype);
|
|
2352
2490
|
return changeset;
|
|
2353
2491
|
}
|
|
@@ -2356,27 +2494,32 @@ var World = class {
|
|
|
2356
2494
|
this.clearComponentEntityComponents(entityId);
|
|
2357
2495
|
return;
|
|
2358
2496
|
}
|
|
2359
|
-
|
|
2360
|
-
|
|
2497
|
+
const pendingSetValues = /* @__PURE__ */ new Map();
|
|
2498
|
+
for (const command of commands) if (command.type === "set" && command.componentType) {
|
|
2499
|
+
const merge = getComponentMerge(command.componentType);
|
|
2500
|
+
let nextValue = command.component;
|
|
2501
|
+
if (merge !== void 0 && pendingSetValues.has(command.componentType)) nextValue = merge(pendingSetValues.get(command.componentType), command.component);
|
|
2502
|
+
pendingSetValues.set(command.componentType, nextValue);
|
|
2503
|
+
this.getComponentEntityComponents(entityId, true).set(command.componentType, nextValue);
|
|
2504
|
+
} else if (command.type === "delete" && command.componentType) {
|
|
2361
2505
|
const data = this.componentEntityComponents.get(entityId);
|
|
2362
|
-
if (!data) continue;
|
|
2363
2506
|
if (isWildcardRelationId(command.componentType)) {
|
|
2364
2507
|
const componentId = getComponentIdFromRelationId(command.componentType);
|
|
2365
2508
|
if (componentId !== void 0) {
|
|
2366
|
-
|
|
2509
|
+
if (data) {
|
|
2510
|
+
for (const key of Array.from(data.keys())) if (getComponentIdFromRelationId(key) === componentId) data.delete(key);
|
|
2511
|
+
}
|
|
2512
|
+
for (const key of Array.from(pendingSetValues.keys())) if (getComponentIdFromRelationId(key) === componentId) pendingSetValues.delete(key);
|
|
2367
2513
|
}
|
|
2368
|
-
} else
|
|
2369
|
-
|
|
2514
|
+
} else {
|
|
2515
|
+
data?.delete(command.componentType);
|
|
2516
|
+
pendingSetValues.delete(command.componentType);
|
|
2517
|
+
}
|
|
2518
|
+
if (data?.size === 0) this.clearComponentEntityComponents(entityId);
|
|
2370
2519
|
}
|
|
2371
2520
|
}
|
|
2372
2521
|
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
|
-
};
|
|
2522
|
+
return this._hooksCtx;
|
|
2380
2523
|
}
|
|
2381
2524
|
removeComponentImmediate(entityId, componentType, targetEntityId) {
|
|
2382
2525
|
const sourceArchetype = this.entityToArchetype.get(entityId);
|
|
@@ -2385,10 +2528,7 @@ var World = class {
|
|
|
2385
2528
|
changeset.delete(componentType);
|
|
2386
2529
|
maybeRemoveWildcardMarker(entityId, sourceArchetype, componentType, getComponentIdFromRelationId(componentType), changeset);
|
|
2387
2530
|
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);
|
|
2531
|
+
const { newArchetype } = applyChangeset(this._commandCtx, entityId, sourceArchetype, changeset, this.entityToArchetype);
|
|
2392
2532
|
untrackEntityReference(this.entityReferences, entityId, componentType, targetEntityId);
|
|
2393
2533
|
triggerLifecycleHooks(this.createHooksContext(), entityId, /* @__PURE__ */ new Map(), new Map([[componentType, removedComponent]]), sourceArchetype, newArchetype);
|
|
2394
2534
|
}
|
|
@@ -2403,9 +2543,9 @@ var World = class {
|
|
|
2403
2543
|
} else if (componentType >= ENTITY_ID_START) trackEntityReference(this.entityReferences, entityId, componentType, componentType);
|
|
2404
2544
|
}
|
|
2405
2545
|
ensureArchetype(componentTypes) {
|
|
2406
|
-
const sortedTypes = filterRegularComponentTypes(componentTypes)
|
|
2546
|
+
const sortedTypes = normalizeComponentTypes(filterRegularComponentTypes(componentTypes));
|
|
2407
2547
|
const hashKey = this.createArchetypeSignature(sortedTypes);
|
|
2408
|
-
return
|
|
2548
|
+
return getOrCompute(this.archetypeBySignature, hashKey, () => this.createNewArchetype(sortedTypes));
|
|
2409
2549
|
}
|
|
2410
2550
|
createNewArchetype(componentTypes) {
|
|
2411
2551
|
const newArchetype = new Archetype(componentTypes, this.dontFragmentRelations);
|
|
@@ -2430,7 +2570,7 @@ var World = class {
|
|
|
2430
2570
|
return componentId !== void 0 && archetype.hasRelationWithComponentId(componentId);
|
|
2431
2571
|
}
|
|
2432
2572
|
return archetype.componentTypeSet.has(c) || isDontFragmentRelation(c);
|
|
2433
|
-
});
|
|
2573
|
+
}) && matchesFilter(archetype, entry.filter);
|
|
2434
2574
|
}
|
|
2435
2575
|
archetypeReferencesEntity(archetype, entityId) {
|
|
2436
2576
|
return archetype.componentTypes.some((ct) => ct === entityId || isEntityRelation(ct) && getTargetIdFromRelationId(ct) === entityId);
|