@codehz/ecs 0.8.2 → 0.9.0
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.en.md +26 -3
- package/README.md +28 -3
- package/dist/builder.d.mts +296 -46
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/testing.d.mts +1 -1
- package/dist/testing.mjs +1 -1
- package/dist/world.mjs +452 -179
- package/dist/world.mjs.map +1 -1
- package/examples/debug-observability.ts +92 -0
- package/examples/inventory-system-relations.ts +1 -1
- package/examples/parent-child-hierarchy.ts +18 -38
- package/package.json +1 -1
- package/skills/ecs/SKILL.md +4 -4
- package/src/__tests__/component/singleton.test.ts +40 -1
- package/src/__tests__/core/archetype.test.ts +155 -13
- package/src/__tests__/core/bitset.test.ts +12 -0
- package/src/__tests__/entity/entity.test.ts +33 -0
- package/src/__tests__/entity/id-system.test.ts +40 -0
- package/src/__tests__/perf/comprehensive.perf.test.ts +6 -9
- package/src/__tests__/perf/serialization.perf.test.ts +242 -0
- package/src/__tests__/perf/{dontfragment-wildcard.perf.test.ts → sparse-wildcard.perf.test.ts} +13 -16
- package/src/__tests__/query/caching.test.ts +62 -0
- package/src/__tests__/query/filter.test.ts +16 -22
- package/src/__tests__/query/perf.test.ts +3 -5
- package/src/__tests__/relations/hierarchy.test.ts +208 -0
- package/src/__tests__/relations/{dont-fragment → sparse}/basic.test.ts +64 -69
- package/src/__tests__/relations/{dont-fragment → sparse}/query-notification.test.ts +17 -9
- package/src/__tests__/serialization/bounds.test.ts +134 -1
- package/src/__tests__/world/commands.test.ts +337 -0
- package/src/__tests__/world/debug-stats.test.ts +206 -0
- package/src/__tests__/world/multi-component-hooks.test.ts +44 -0
- package/src/__tests__/world/serialize.test.ts +17 -0
- package/src/__tests__/world/wildcard-relation-hooks.test.ts +127 -0
- package/src/archetype/archetype.ts +96 -46
- package/src/archetype/helpers.ts +7 -29
- package/src/archetype/store.ts +35 -20
- package/src/commands/buffer.ts +5 -2
- package/src/commands/changeset.ts +0 -31
- package/src/component/registry.ts +64 -63
- package/src/entity/index.ts +6 -3
- package/src/index.ts +13 -0
- package/src/query/filter.ts +4 -10
- package/src/query/query.ts +12 -12
- package/src/storage/serialization.ts +29 -2
- package/src/types/index.ts +71 -0
- package/src/world/commands.ts +44 -56
- package/src/world/hooks.ts +8 -0
- package/src/world/serialization.ts +32 -18
- package/src/world/world.ts +387 -20
package/dist/world.mjs
CHANGED
|
@@ -366,7 +366,7 @@ const ComponentIdForNames = /* @__PURE__ */ new Map();
|
|
|
366
366
|
const componentNames = new Array(COMPONENT_ID_MAX + 1);
|
|
367
367
|
const exclusiveFlags = new BitSet(COMPONENT_ID_MAX + 1);
|
|
368
368
|
const cascadeDeleteFlags = new BitSet(COMPONENT_ID_MAX + 1);
|
|
369
|
-
const
|
|
369
|
+
const sparseFlags = new BitSet(COMPONENT_ID_MAX + 1);
|
|
370
370
|
const componentMerges = new Array(COMPONENT_ID_MAX + 1);
|
|
371
371
|
/**
|
|
372
372
|
* Allocate a new component ID from the global allocator.
|
|
@@ -399,7 +399,7 @@ function component(nameOrOptions) {
|
|
|
399
399
|
if (options) {
|
|
400
400
|
if (options.exclusive) exclusiveFlags.set(id);
|
|
401
401
|
if (options.cascadeDelete) cascadeDeleteFlags.set(id);
|
|
402
|
-
if (options.dontFragment)
|
|
402
|
+
if (options.sparse || options.dontFragment) sparseFlags.set(id);
|
|
403
403
|
if (options.merge) componentMerges[id] = options.merge;
|
|
404
404
|
}
|
|
405
405
|
return id;
|
|
@@ -468,20 +468,21 @@ function isExclusiveComponent(id) {
|
|
|
468
468
|
return exclusiveFlags.has(id);
|
|
469
469
|
}
|
|
470
470
|
/**
|
|
471
|
-
* Check if a component is marked as `
|
|
471
|
+
* Check if a component is marked as `sparse` (sparse storage for relations).
|
|
472
472
|
*
|
|
473
|
-
* When a component has `
|
|
474
|
-
*
|
|
475
|
-
*
|
|
473
|
+
* When a component has `sparse: true`, relations using it do not cause archetype
|
|
474
|
+
* fragmentation — entities with different relation targets can share the same
|
|
475
|
+
* archetype. This is a fast O(1) bitset lookup. The legacy `dontFragment` key
|
|
476
|
+
* is still accepted and sets the same internal flag.
|
|
476
477
|
*
|
|
477
478
|
* @param id - The component ID to check.
|
|
478
|
-
* @returns `true` if the component was created with `
|
|
479
|
+
* @returns `true` if the component was created with `sparse: true` (or the
|
|
480
|
+
* legacy `dontFragment: true`).
|
|
479
481
|
*
|
|
480
|
-
* @see {@link ComponentOptions.
|
|
481
|
-
* `dontFragment` prevents archetype fragmentation.
|
|
482
|
+
* @see {@link ComponentOptions.sparse} for the full explanation of sparse storage.
|
|
482
483
|
*/
|
|
483
|
-
function
|
|
484
|
-
return
|
|
484
|
+
function isSparseComponent(id) {
|
|
485
|
+
return sparseFlags.has(id);
|
|
485
486
|
}
|
|
486
487
|
/**
|
|
487
488
|
* Generic optimized function to check whether a relation ID's base component
|
|
@@ -491,9 +492,8 @@ function isDontFragmentComponent(id) {
|
|
|
491
492
|
* ID and checking: (1) the ID is a valid relation, (2) the component ID is in the
|
|
492
493
|
* valid range, (3) the target satisfies the condition, and (4) the flag bit is set.
|
|
493
494
|
*
|
|
494
|
-
* Used as the fast-path implementation for `
|
|
495
|
-
* `
|
|
496
|
-
* and `isCascadeDeleteRelation`.
|
|
495
|
+
* Used as the fast-path implementation for `isSparseRelation`, `isSparseWildcard`,
|
|
496
|
+
* `isExclusiveRelation`, `isExclusiveWildcard`, and `isCascadeDeleteRelation`.
|
|
497
497
|
*
|
|
498
498
|
* @param id - The entity/relation ID to check.
|
|
499
499
|
* @param flagBitSet - The bitset tracking which component IDs have the flag.
|
|
@@ -509,52 +509,40 @@ function checkRelationFlag(id, flagBitSet, targetCondition) {
|
|
|
509
509
|
return isValidComponentId(componentId) && targetCondition(targetId) && flagBitSet.has(componentId);
|
|
510
510
|
}
|
|
511
511
|
/**
|
|
512
|
-
* Check if an ID is a specific (non-wildcard) relation backed by a `
|
|
513
|
-
* component.
|
|
512
|
+
* Check if an ID is a specific (non-wildcard) relation backed by a `sparse`
|
|
513
|
+
* component (i.e. stored in the side sparse store rather than the archetype).
|
|
514
514
|
*
|
|
515
515
|
* This is used in hot paths (archetype resolution, command processing) to determine
|
|
516
|
-
* whether a relation should be excluded from the archetype signature.
|
|
517
|
-
* `dontFragment` components are stored in the shared {@link DontFragmentStore} instead
|
|
518
|
-
* of being part of the archetype's component type list.
|
|
519
|
-
*
|
|
520
|
-
* This is an optimized function that avoids the overhead of `getDetailedIdType`
|
|
521
|
-
* by directly decoding and checking the relation's component ID against the
|
|
522
|
-
* `dontFragment` bitset.
|
|
516
|
+
* whether a relation should be excluded from the archetype signature.
|
|
523
517
|
*
|
|
524
518
|
* @param id - The entity/relation ID to check (must be a relation ID, not a plain
|
|
525
519
|
* component ID).
|
|
526
520
|
* @returns `true` if this is a specific-target relation (not wildcard) whose base
|
|
527
|
-
* component was created with `dontFragment: true
|
|
521
|
+
* component was created with `sparse: true` (or legacy `dontFragment: true`).
|
|
528
522
|
*
|
|
529
|
-
* @see {@link
|
|
530
|
-
* @see {@link ComponentOptions.
|
|
523
|
+
* @see {@link isSparseWildcard} for the wildcard variant.
|
|
524
|
+
* @see {@link ComponentOptions.sparse} for the full explanation.
|
|
531
525
|
*/
|
|
532
|
-
function
|
|
533
|
-
return checkRelationFlag(id,
|
|
526
|
+
function isSparseRelation(id) {
|
|
527
|
+
return checkRelationFlag(id, sparseFlags, (targetId) => targetId !== 0);
|
|
534
528
|
}
|
|
535
529
|
/**
|
|
536
530
|
* Check if an ID is a wildcard relation (`relation(Comp, "*")`) backed by a
|
|
537
|
-
* `
|
|
538
|
-
*
|
|
539
|
-
* Wildcard markers for `dontFragment` components are placed in the archetype
|
|
540
|
-
* component list so that queries can discover archetypes containing entities
|
|
541
|
-
* with that relation type. This function is used in `filterRegularComponentTypes`
|
|
542
|
-
* to **keep** these wildcard markers in the archetype signature while stripping
|
|
543
|
-
* out specific-target `dontFragment` relations.
|
|
531
|
+
* `sparse` component.
|
|
544
532
|
*
|
|
545
|
-
*
|
|
546
|
-
*
|
|
547
|
-
*
|
|
533
|
+
* Wildcard markers for sparse components are placed in the archetype component
|
|
534
|
+
* list so that queries can discover archetypes containing entities with that
|
|
535
|
+
* relation type.
|
|
548
536
|
*
|
|
549
537
|
* @param id - The entity/relation ID to check.
|
|
550
538
|
* @returns `true` if this is a wildcard relation (`"*"` target) whose base
|
|
551
|
-
* component was created with `dontFragment: true
|
|
539
|
+
* component was created with `sparse: true` (or legacy `dontFragment: true`).
|
|
552
540
|
*
|
|
553
|
-
* @see {@link
|
|
554
|
-
* @see {@link ComponentOptions.
|
|
541
|
+
* @see {@link isSparseRelation} for the specific-target variant.
|
|
542
|
+
* @see {@link ComponentOptions.sparse} for the full explanation.
|
|
555
543
|
*/
|
|
556
|
-
function
|
|
557
|
-
return checkRelationFlag(id,
|
|
544
|
+
function isSparseWildcard(id) {
|
|
545
|
+
return checkRelationFlag(id, sparseFlags, (targetId) => targetId === 0);
|
|
558
546
|
}
|
|
559
547
|
/**
|
|
560
548
|
* Check if a relation ID is a cascade delete entity-relation.
|
|
@@ -709,9 +697,9 @@ function getWildcardRelationDataSource(componentTypes, componentId, optional) {
|
|
|
709
697
|
}
|
|
710
698
|
/**
|
|
711
699
|
* Build wildcard relation value from matching relations.
|
|
712
|
-
*
|
|
700
|
+
* Receives the SparseStore directly for efficient per-component lookups.
|
|
713
701
|
*/
|
|
714
|
-
function buildWildcardRelationValue(wildcardRelationType, matchingRelations, getDataAtIndex,
|
|
702
|
+
function buildWildcardRelationValue(wildcardRelationType, matchingRelations, getDataAtIndex, sparseStore, entityId, optional) {
|
|
715
703
|
const relations = [];
|
|
716
704
|
const targetComponentId = getComponentIdFromRelationId(wildcardRelationType);
|
|
717
705
|
for (const relType of matchingRelations || []) {
|
|
@@ -720,7 +708,7 @@ function buildWildcardRelationValue(wildcardRelationType, matchingRelations, get
|
|
|
720
708
|
relations.push([targetId, data === MISSING_COMPONENT ? void 0 : data]);
|
|
721
709
|
}
|
|
722
710
|
if (targetComponentId !== void 0) {
|
|
723
|
-
const dfMatches =
|
|
711
|
+
const dfMatches = sparseStore.getRelationsForComponent(entityId, targetComponentId);
|
|
724
712
|
for (const m of dfMatches) relations.push(m);
|
|
725
713
|
}
|
|
726
714
|
if (relations.length === 0) {
|
|
@@ -747,10 +735,10 @@ function buildRegularComponentValue(dataSource, entityIndex, optional) {
|
|
|
747
735
|
/**
|
|
748
736
|
* Build a single component value based on its type
|
|
749
737
|
*/
|
|
750
|
-
function buildSingleComponent(compType, dataSource, entityIndex, entityId, getComponentData,
|
|
738
|
+
function buildSingleComponent(compType, dataSource, entityIndex, entityId, getComponentData, sparseRelations) {
|
|
751
739
|
const optional = isOptionalEntityId(compType);
|
|
752
740
|
const actualType = optional ? compType.optional : compType;
|
|
753
|
-
if (getIdType(actualType) === "wildcard-relation") return buildWildcardRelationValue(actualType, dataSource, (relType) => getComponentData(relType)[entityIndex],
|
|
741
|
+
if (getIdType(actualType) === "wildcard-relation") return buildWildcardRelationValue(actualType, dataSource, (relType) => getComponentData(relType)[entityIndex], sparseRelations, entityId, optional);
|
|
754
742
|
else return buildRegularComponentValue(dataSource, entityIndex, optional);
|
|
755
743
|
}
|
|
756
744
|
//#endregion
|
|
@@ -787,11 +775,10 @@ var Archetype = class {
|
|
|
787
775
|
*/
|
|
788
776
|
entityToIndex = /* @__PURE__ */ new Map();
|
|
789
777
|
/**
|
|
790
|
-
*
|
|
791
|
-
* Uses optimized RelationEntry (single/multi) for the common exclusive case.
|
|
778
|
+
* SparseStore used for relations declared with `sparse: true`.
|
|
792
779
|
* See store.ts for implementation details.
|
|
793
780
|
*/
|
|
794
|
-
|
|
781
|
+
sparseRelations;
|
|
795
782
|
/**
|
|
796
783
|
* Multi-hooks that match this archetype
|
|
797
784
|
*/
|
|
@@ -800,10 +787,10 @@ var Archetype = class {
|
|
|
800
787
|
* Cache for pre-computed component data sources to avoid repeated calculations
|
|
801
788
|
*/
|
|
802
789
|
componentDataSourcesCache = /* @__PURE__ */ new Map();
|
|
803
|
-
constructor(componentTypes,
|
|
790
|
+
constructor(componentTypes, sparseStore) {
|
|
804
791
|
this.componentTypes = normalizeComponentTypes(componentTypes);
|
|
805
792
|
this.componentTypeSet = new Set(this.componentTypes);
|
|
806
|
-
this.
|
|
793
|
+
this.sparseRelations = sparseStore;
|
|
807
794
|
for (const componentType of this.componentTypes) this.componentData.set(componentType, []);
|
|
808
795
|
}
|
|
809
796
|
get size() {
|
|
@@ -829,13 +816,13 @@ var Archetype = class {
|
|
|
829
816
|
const data = componentData.get(componentType);
|
|
830
817
|
this.getComponentData(componentType).push(!componentData.has(componentType) ? MISSING_COMPONENT : data);
|
|
831
818
|
}
|
|
832
|
-
this.
|
|
819
|
+
this.addSparseRelations(entityId, componentData);
|
|
833
820
|
}
|
|
834
|
-
|
|
821
|
+
addSparseRelations(entityId, componentData) {
|
|
835
822
|
for (const [componentType, data] of componentData) {
|
|
836
823
|
if (this.componentTypeSet.has(componentType)) continue;
|
|
837
824
|
const detailedType = getDetailedIdType(componentType);
|
|
838
|
-
if (isRelationType(detailedType) &&
|
|
825
|
+
if (isRelationType(detailedType) && isSparseComponent(detailedType.componentId)) this.sparseRelations.setValue(entityId, componentType, data);
|
|
839
826
|
}
|
|
840
827
|
}
|
|
841
828
|
getEntity(entityId) {
|
|
@@ -846,18 +833,16 @@ var Archetype = class {
|
|
|
846
833
|
const data = this.getComponentData(componentType)[index];
|
|
847
834
|
entityData.set(componentType, data === MISSING_COMPONENT ? void 0 : data);
|
|
848
835
|
}
|
|
849
|
-
const
|
|
850
|
-
for (const [componentType, data] of
|
|
836
|
+
const sparseTuples = this.sparseRelations.getAllForEntity(entityId);
|
|
837
|
+
for (const [componentType, data] of sparseTuples) entityData.set(componentType, data);
|
|
851
838
|
return entityData;
|
|
852
839
|
}
|
|
853
840
|
/**
|
|
854
|
-
* Returns all
|
|
855
|
-
*
|
|
856
|
-
*
|
|
857
|
-
* Prefer the new DontFragmentStore methods when possible.
|
|
841
|
+
* Returns all sparse-stored relations for the given entity.
|
|
842
|
+
* Internal helper used by command processing and tests.
|
|
858
843
|
*/
|
|
859
|
-
|
|
860
|
-
const tuples = this.
|
|
844
|
+
getEntitySparseRelations(entityId) {
|
|
845
|
+
const tuples = this.sparseRelations.getAllForEntity(entityId);
|
|
861
846
|
if (tuples.length === 0) return void 0;
|
|
862
847
|
const map = /* @__PURE__ */ new Map();
|
|
863
848
|
for (const [relType, data] of tuples) map.set(relType, data);
|
|
@@ -870,22 +855,61 @@ var Archetype = class {
|
|
|
870
855
|
const data = this.getComponentData(componentType)[i];
|
|
871
856
|
components.set(componentType, data === MISSING_COMPONENT ? void 0 : data);
|
|
872
857
|
}
|
|
873
|
-
const
|
|
874
|
-
for (const [componentType, data] of
|
|
858
|
+
const sparseTuples = this.sparseRelations.getAllForEntity(entity);
|
|
859
|
+
for (const [componentType, data] of sparseTuples) components.set(componentType, data);
|
|
875
860
|
return {
|
|
876
861
|
entity,
|
|
877
862
|
components
|
|
878
863
|
};
|
|
879
864
|
});
|
|
880
865
|
}
|
|
866
|
+
/**
|
|
867
|
+
* @internal Serialization fast-path.
|
|
868
|
+
*
|
|
869
|
+
* Appends SerializedEntity records directly from the archetype's column storage
|
|
870
|
+
* (componentData arrays) plus sparse relations, avoiding per-entity Map
|
|
871
|
+
* allocation and repeated Array.from(entries()).
|
|
872
|
+
*
|
|
873
|
+
* Component type IDs should be pre-encoded by the caller (once per archetype)
|
|
874
|
+
* and passed in `encodedComponentTypes` (same order and length as this.componentTypes).
|
|
875
|
+
*
|
|
876
|
+
* The provided `encode` function should be the cached variant for best performance
|
|
877
|
+
* on entity IDs and any sparse relation type IDs.
|
|
878
|
+
*
|
|
879
|
+
* `sparseByEntity` is an optional pre-fetched map from a bulk
|
|
880
|
+
* `SparseStore.getAllForEntities` call (further reduces per-entity calls).
|
|
881
|
+
*/
|
|
882
|
+
appendSerializedEntities(out, encode, encodedComponentTypes, sparseByEntity) {
|
|
883
|
+
if (encodedComponentTypes.length !== this.componentTypes.length) throw new Error("encodedComponentTypes length must match archetype componentTypes");
|
|
884
|
+
for (let i = 0; i < this.entities.length; i++) {
|
|
885
|
+
const entity = this.entities[i];
|
|
886
|
+
const components = [];
|
|
887
|
+
for (let c = 0; c < this.componentTypes.length; c++) {
|
|
888
|
+
const data = this.getComponentData(this.componentTypes[c])[i];
|
|
889
|
+
components.push({
|
|
890
|
+
type: encodedComponentTypes[c],
|
|
891
|
+
value: data === MISSING_COMPONENT ? void 0 : data
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
const sparseTuples = sparseByEntity?.get(entity) ?? this.sparseRelations.getAllForEntity(entity);
|
|
895
|
+
for (const [componentType, data] of sparseTuples) components.push({
|
|
896
|
+
type: encode(componentType),
|
|
897
|
+
value: data
|
|
898
|
+
});
|
|
899
|
+
out.push({
|
|
900
|
+
id: encode(entity),
|
|
901
|
+
components
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
}
|
|
881
905
|
removeEntity(entityId) {
|
|
882
906
|
const index = this.entityToIndex.get(entityId);
|
|
883
907
|
if (index === void 0) return void 0;
|
|
884
908
|
const removedData = /* @__PURE__ */ new Map();
|
|
885
909
|
for (const componentType of this.componentTypes) removedData.set(componentType, this.getComponentData(componentType)[index]);
|
|
886
|
-
const
|
|
887
|
-
for (const [componentType, data] of
|
|
888
|
-
this.
|
|
910
|
+
const sparseTuples = this.sparseRelations.getAllForEntity(entityId);
|
|
911
|
+
for (const [componentType, data] of sparseTuples) removedData.set(componentType, data);
|
|
912
|
+
this.sparseRelations.deleteEntity(entityId);
|
|
889
913
|
this.entityToIndex.delete(entityId);
|
|
890
914
|
const lastIndex = this.entities.length - 1;
|
|
891
915
|
if (index !== lastIndex) {
|
|
@@ -924,7 +948,7 @@ var Archetype = class {
|
|
|
924
948
|
}
|
|
925
949
|
}
|
|
926
950
|
if (componentId !== void 0) {
|
|
927
|
-
const matches = this.
|
|
951
|
+
const matches = this.sparseRelations.getRelationsForComponent(entityId, componentId);
|
|
928
952
|
for (const m of matches) relations.push(m);
|
|
929
953
|
}
|
|
930
954
|
return relations;
|
|
@@ -935,7 +959,7 @@ var Archetype = class {
|
|
|
935
959
|
if (data === MISSING_COMPONENT) throw new Error(`Component type ${componentType} not found for entity ${entityId}`);
|
|
936
960
|
return data;
|
|
937
961
|
}
|
|
938
|
-
if (this.
|
|
962
|
+
if (this.sparseRelations.getValue(entityId, componentType) !== void 0 || this.sparseRelations.getAllForEntity(entityId).some(([t]) => t === componentType)) return this.sparseRelations.getValue(entityId, componentType);
|
|
939
963
|
throw new Error(`Component type ${componentType} not found for entity ${entityId}`);
|
|
940
964
|
}
|
|
941
965
|
getOptional(entityId, componentType) {
|
|
@@ -946,9 +970,9 @@ var Archetype = class {
|
|
|
946
970
|
if (data === MISSING_COMPONENT) return void 0;
|
|
947
971
|
return { value: data };
|
|
948
972
|
}
|
|
949
|
-
const value = this.
|
|
973
|
+
const value = this.sparseRelations.getValue(entityId, componentType);
|
|
950
974
|
if (value !== void 0) return { value };
|
|
951
|
-
if (this.
|
|
975
|
+
if (this.sparseRelations.getAllForEntity(entityId).some(([t]) => t === componentType)) return { value: this.sparseRelations.getValue(entityId, componentType) };
|
|
952
976
|
}
|
|
953
977
|
set(entityId, componentType, data) {
|
|
954
978
|
const index = this.entityToIndex.get(entityId);
|
|
@@ -958,8 +982,8 @@ var Archetype = class {
|
|
|
958
982
|
return;
|
|
959
983
|
}
|
|
960
984
|
const detailedType = getDetailedIdType(componentType);
|
|
961
|
-
if (isRelationType(detailedType) &&
|
|
962
|
-
this.
|
|
985
|
+
if (isRelationType(detailedType) && isSparseComponent(detailedType.componentId)) {
|
|
986
|
+
this.sparseRelations.setValue(entityId, componentType, data);
|
|
963
987
|
return;
|
|
964
988
|
}
|
|
965
989
|
throw new Error(`Component type ${componentType} is not in this archetype`);
|
|
@@ -992,7 +1016,7 @@ var Archetype = class {
|
|
|
992
1016
|
return optional ? this.getOptionalComponentData(actualType) : this.getComponentData(actualType);
|
|
993
1017
|
}
|
|
994
1018
|
buildComponentsForIndex(componentTypes, componentDataSources, entityIndex, entityId) {
|
|
995
|
-
return componentDataSources.map((dataSource, i) => buildSingleComponent(componentTypes[i], dataSource, entityIndex, entityId, (type) => this.getComponentData(type), this.
|
|
1019
|
+
return componentDataSources.map((dataSource, i) => buildSingleComponent(componentTypes[i], dataSource, entityIndex, entityId, (type) => this.getComponentData(type), this.sparseRelations));
|
|
996
1020
|
}
|
|
997
1021
|
getEntitiesWithComponents(componentTypes) {
|
|
998
1022
|
const result = [];
|
|
@@ -1029,8 +1053,8 @@ var Archetype = class {
|
|
|
1029
1053
|
const data = this.getComponentData(componentType)[i];
|
|
1030
1054
|
components.set(componentType, data === MISSING_COMPONENT ? void 0 : data);
|
|
1031
1055
|
}
|
|
1032
|
-
const
|
|
1033
|
-
for (const [componentType, data] of
|
|
1056
|
+
const sparseTuples = this.sparseRelations.getAllForEntity(entity);
|
|
1057
|
+
for (const [componentType, data] of sparseTuples) components.set(componentType, data);
|
|
1034
1058
|
callback(entity, components);
|
|
1035
1059
|
}
|
|
1036
1060
|
}
|
|
@@ -1039,30 +1063,31 @@ var Archetype = class {
|
|
|
1039
1063
|
const detailedType = getDetailedIdType(componentType);
|
|
1040
1064
|
if (isRelationType(detailedType) && detailedType.componentId === componentId) return true;
|
|
1041
1065
|
}
|
|
1042
|
-
for (const entityId of this.entities) if (this.
|
|
1066
|
+
for (const entityId of this.entities) if (this.sparseRelations.getRelationsForComponent(entityId, componentId).length > 0) return true;
|
|
1043
1067
|
return false;
|
|
1044
1068
|
}
|
|
1045
1069
|
};
|
|
1046
1070
|
//#endregion
|
|
1047
1071
|
//#region src/archetype/store.ts
|
|
1048
1072
|
/**
|
|
1049
|
-
* Production implementation of
|
|
1073
|
+
* Production implementation of SparseStore.
|
|
1050
1074
|
*
|
|
1051
1075
|
* Internal layout (optimized):
|
|
1052
1076
|
* - byComponent: baseComponentId → (entityId → RelationEntry)
|
|
1053
1077
|
* RelationEntry uses a single-value form for the common exclusive case (1 target),
|
|
1054
|
-
* avoiding Map allocation
|
|
1078
|
+
* avoiding Map allocation for the vast majority of usage.
|
|
1055
1079
|
* - entityIndex: entityId → Set<baseComponentId>
|
|
1056
1080
|
* Lightweight reverse index.
|
|
1057
1081
|
*/
|
|
1058
|
-
var
|
|
1082
|
+
var SparseStoreImpl = class {
|
|
1059
1083
|
/**
|
|
1060
1084
|
* Primary storage, keyed by the base relation component ID.
|
|
1061
1085
|
*/
|
|
1062
1086
|
byComponent = /* @__PURE__ */ new Map();
|
|
1063
1087
|
/**
|
|
1064
1088
|
* Reverse index: which base component kinds an entity participates in.
|
|
1065
|
-
*
|
|
1089
|
+
* Only required to support getAllForEntity and deleteEntity efficiently.
|
|
1090
|
+
* The primary storage (byComponent) is deliberately not optimized for these operations.
|
|
1066
1091
|
*/
|
|
1067
1092
|
entityIndex = /* @__PURE__ */ new Map();
|
|
1068
1093
|
getValue(entityId, relationType) {
|
|
@@ -1081,7 +1106,7 @@ var DontFragmentStoreImpl = class {
|
|
|
1081
1106
|
}
|
|
1082
1107
|
setValue(entityId, relationType, data) {
|
|
1083
1108
|
const componentId = getComponentIdFromRelationId(relationType);
|
|
1084
|
-
if (componentId === void 0) throw new Error("setValue called with a non-relation type on
|
|
1109
|
+
if (componentId === void 0) throw new Error("setValue called with a non-relation type on SparseStore");
|
|
1085
1110
|
let entities = this.byComponent.get(componentId);
|
|
1086
1111
|
if (!entities) {
|
|
1087
1112
|
entities = /* @__PURE__ */ new Map();
|
|
@@ -1199,6 +1224,14 @@ var DontFragmentStoreImpl = class {
|
|
|
1199
1224
|
}
|
|
1200
1225
|
this.entityIndex.delete(entityId);
|
|
1201
1226
|
}
|
|
1227
|
+
getAllForEntities(entityIds) {
|
|
1228
|
+
const result = /* @__PURE__ */ new Map();
|
|
1229
|
+
for (const eid of entityIds) {
|
|
1230
|
+
const data = this.getAllForEntity(eid);
|
|
1231
|
+
if (data.length > 0) result.set(eid, data);
|
|
1232
|
+
}
|
|
1233
|
+
return result;
|
|
1234
|
+
}
|
|
1202
1235
|
};
|
|
1203
1236
|
//#endregion
|
|
1204
1237
|
//#region src/commands/buffer.ts
|
|
@@ -1249,7 +1282,8 @@ var CommandBuffer = class {
|
|
|
1249
1282
|
});
|
|
1250
1283
|
}
|
|
1251
1284
|
/**
|
|
1252
|
-
* Execute all commands and clear the buffer
|
|
1285
|
+
* Execute all commands and clear the buffer.
|
|
1286
|
+
* Returns the number of iterations performed (for debug stats).
|
|
1253
1287
|
*/
|
|
1254
1288
|
execute() {
|
|
1255
1289
|
let iterations = 0;
|
|
@@ -1269,6 +1303,7 @@ var CommandBuffer = class {
|
|
|
1269
1303
|
for (const [entityId, commands] of entityCommands) this.executeEntityCommands(entityId, commands);
|
|
1270
1304
|
entityCommands.clear();
|
|
1271
1305
|
}
|
|
1306
|
+
return iterations;
|
|
1272
1307
|
}
|
|
1273
1308
|
/**
|
|
1274
1309
|
* Get current commands (for testing)
|
|
@@ -1339,29 +1374,6 @@ var ComponentChangeset = class {
|
|
|
1339
1374
|
for (const [componentType, component] of this.adds) existingComponents.set(componentType, component);
|
|
1340
1375
|
return existingComponents;
|
|
1341
1376
|
}
|
|
1342
|
-
/**
|
|
1343
|
-
* Get the final component types after applying the changeset
|
|
1344
|
-
* @param existingComponentTypes - The current component types on the entity
|
|
1345
|
-
* @returns The final component types or undefined if no changes
|
|
1346
|
-
*/
|
|
1347
|
-
getFinalComponentTypes(existingComponentTypes) {
|
|
1348
|
-
const finalComponentTypes = new Set(existingComponentTypes);
|
|
1349
|
-
let changed = false;
|
|
1350
|
-
for (const componentType of this.removes) {
|
|
1351
|
-
if (!finalComponentTypes.has(componentType)) {
|
|
1352
|
-
this.removes.delete(componentType);
|
|
1353
|
-
continue;
|
|
1354
|
-
}
|
|
1355
|
-
changed = true;
|
|
1356
|
-
finalComponentTypes.delete(componentType);
|
|
1357
|
-
}
|
|
1358
|
-
for (const componentType of this.adds.keys()) {
|
|
1359
|
-
if (finalComponentTypes.has(componentType)) continue;
|
|
1360
|
-
changed = true;
|
|
1361
|
-
finalComponentTypes.add(componentType);
|
|
1362
|
-
}
|
|
1363
|
-
return changed ? Array.from(finalComponentTypes) : void 0;
|
|
1364
|
-
}
|
|
1365
1377
|
};
|
|
1366
1378
|
//#endregion
|
|
1367
1379
|
//#region src/component/entity-store.ts
|
|
@@ -1550,7 +1562,7 @@ function matchesComponentTypes(archetype, componentTypes) {
|
|
|
1550
1562
|
if (!isRelationId(archetypeType)) return false;
|
|
1551
1563
|
return getComponentIdFromRelationId(archetypeType) === detailedType.componentId;
|
|
1552
1564
|
});
|
|
1553
|
-
else if ((detailedType.type === "entity-relation" || detailedType.type === "component-relation") && detailedType.componentId !== void 0 &&
|
|
1565
|
+
else if ((detailedType.type === "entity-relation" || detailedType.type === "component-relation") && detailedType.componentId !== void 0 && isSparseComponent(detailedType.componentId)) {
|
|
1554
1566
|
const wildcardMarker = relation(detailedType.componentId, "*");
|
|
1555
1567
|
return archetype.componentTypeSet.has(wildcardMarker);
|
|
1556
1568
|
} else return archetype.componentTypeSet.has(type);
|
|
@@ -1597,8 +1609,8 @@ var Query = class {
|
|
|
1597
1609
|
_cacheKey;
|
|
1598
1610
|
/** Cached wildcard component types for faster entity filtering */
|
|
1599
1611
|
wildcardTypes;
|
|
1600
|
-
/** Cached specific
|
|
1601
|
-
|
|
1612
|
+
/** Cached specific sparse relation types that need entity-level filtering */
|
|
1613
|
+
specificSparseRelationTypes;
|
|
1602
1614
|
/**
|
|
1603
1615
|
* @internal Queries should be created via {@link World.createQuery}, not instantiated directly.
|
|
1604
1616
|
*/
|
|
@@ -1607,9 +1619,9 @@ var Query = class {
|
|
|
1607
1619
|
this.componentTypes = normalizeComponentTypes(componentTypes);
|
|
1608
1620
|
this.filter = filter;
|
|
1609
1621
|
this.wildcardTypes = this.componentTypes.filter((ct) => getDetailedIdType(ct).type === "wildcard-relation");
|
|
1610
|
-
this.
|
|
1622
|
+
this.specificSparseRelationTypes = this.componentTypes.filter((ct) => {
|
|
1611
1623
|
const detailedType = getDetailedIdType(ct);
|
|
1612
|
-
return (detailedType.type === "entity-relation" || detailedType.type === "component-relation") && detailedType.componentId !== void 0 &&
|
|
1624
|
+
return (detailedType.type === "entity-relation" || detailedType.type === "component-relation") && detailedType.componentId !== void 0 && isSparseComponent(detailedType.componentId);
|
|
1613
1625
|
});
|
|
1614
1626
|
this.updateCache();
|
|
1615
1627
|
if (registry) registry.register(this);
|
|
@@ -1633,7 +1645,7 @@ var Query = class {
|
|
|
1633
1645
|
*/
|
|
1634
1646
|
getEntities() {
|
|
1635
1647
|
this.ensureNotDisposed();
|
|
1636
|
-
if (this.wildcardTypes.length === 0 && this.
|
|
1648
|
+
if (this.wildcardTypes.length === 0 && this.specificSparseRelationTypes.length === 0) {
|
|
1637
1649
|
const result = [];
|
|
1638
1650
|
for (const archetype of this.cachedArchetypes) for (const entity of archetype.getEntities()) result.push(entity);
|
|
1639
1651
|
return result;
|
|
@@ -1643,14 +1655,14 @@ var Query = class {
|
|
|
1643
1655
|
return result;
|
|
1644
1656
|
}
|
|
1645
1657
|
/**
|
|
1646
|
-
* Check if entity matches all query requirements (wildcards and specific
|
|
1658
|
+
* Check if entity matches all query requirements (wildcards and specific sparse relations)
|
|
1647
1659
|
*/
|
|
1648
1660
|
entityMatchesQuery(archetype, entity) {
|
|
1649
1661
|
for (const wildcardType of this.wildcardTypes) {
|
|
1650
1662
|
const relations = archetype.get(entity, wildcardType);
|
|
1651
1663
|
if (!relations || relations.length === 0) return false;
|
|
1652
1664
|
}
|
|
1653
|
-
for (const specificType of this.
|
|
1665
|
+
for (const specificType of this.specificSparseRelationTypes) if (archetype.getOptional(entity, specificType) === void 0) return false;
|
|
1654
1666
|
return true;
|
|
1655
1667
|
}
|
|
1656
1668
|
/**
|
|
@@ -1872,7 +1884,7 @@ function processSetCommand(entityId, currentArchetype, componentType, component,
|
|
|
1872
1884
|
const componentId = getComponentIdFromRelationId(componentType);
|
|
1873
1885
|
if (componentId !== void 0) {
|
|
1874
1886
|
handleExclusiveRelation(entityId, currentArchetype, componentId);
|
|
1875
|
-
if (
|
|
1887
|
+
if (isSparseComponent(componentId)) {
|
|
1876
1888
|
const wildcardMarker = relation(componentId, "*");
|
|
1877
1889
|
if (!currentArchetype.componentTypeSet.has(wildcardMarker)) changeset.set(wildcardMarker, void 0);
|
|
1878
1890
|
}
|
|
@@ -1898,17 +1910,17 @@ function removeMatchingRelations(entityId, archetype, baseComponentId, changeset
|
|
|
1898
1910
|
if (isWildcardRelationId(componentType)) continue;
|
|
1899
1911
|
if (getComponentIdFromRelationId(componentType) === baseComponentId) changeset.delete(componentType);
|
|
1900
1912
|
}
|
|
1901
|
-
const
|
|
1902
|
-
if (
|
|
1903
|
-
for (const componentType of
|
|
1913
|
+
const sparseData = archetype.getEntitySparseRelations(entityId);
|
|
1914
|
+
if (sparseData) {
|
|
1915
|
+
for (const componentType of sparseData.keys()) if (getComponentIdFromRelationId(componentType) === baseComponentId) changeset.delete(componentType);
|
|
1904
1916
|
}
|
|
1905
1917
|
}
|
|
1906
1918
|
function removeWildcardRelations(entityId, currentArchetype, baseComponentId, changeset) {
|
|
1907
1919
|
removeMatchingRelations(entityId, currentArchetype, baseComponentId, changeset);
|
|
1908
|
-
if (
|
|
1920
|
+
if (isSparseComponent(baseComponentId)) changeset.delete(relation(baseComponentId, "*"));
|
|
1909
1921
|
}
|
|
1910
1922
|
function maybeRemoveWildcardMarker(entityId, archetype, removedComponentType, componentId, changeset) {
|
|
1911
|
-
if (componentId === void 0 || !
|
|
1923
|
+
if (componentId === void 0 || !isSparseComponent(componentId)) return;
|
|
1912
1924
|
const wildcardMarker = relation(componentId, "*");
|
|
1913
1925
|
for (const otherComponentType of archetype.componentTypes) {
|
|
1914
1926
|
if (otherComponentType === removedComponentType) continue;
|
|
@@ -1916,8 +1928,8 @@ function maybeRemoveWildcardMarker(entityId, archetype, removedComponentType, co
|
|
|
1916
1928
|
if (changeset.removes.has(otherComponentType)) continue;
|
|
1917
1929
|
if (getComponentIdFromRelationId(otherComponentType) === componentId) return;
|
|
1918
1930
|
}
|
|
1919
|
-
const
|
|
1920
|
-
if (
|
|
1931
|
+
const sparseData = archetype.getEntitySparseRelations(entityId);
|
|
1932
|
+
if (sparseData) for (const otherComponentType of sparseData.keys()) {
|
|
1921
1933
|
if (otherComponentType === removedComponentType) continue;
|
|
1922
1934
|
if (changeset.removes.has(otherComponentType)) continue;
|
|
1923
1935
|
if (getComponentIdFromRelationId(otherComponentType) === componentId) return;
|
|
@@ -1930,7 +1942,7 @@ function maybeRemoveWildcardMarker(entityId, archetype, removedComponentType, co
|
|
|
1930
1942
|
}
|
|
1931
1943
|
function hasEntityComponent(archetype, entityId, componentType) {
|
|
1932
1944
|
if (archetype.componentTypeSet.has(componentType)) return true;
|
|
1933
|
-
return archetype.
|
|
1945
|
+
return archetype.getEntitySparseRelations(entityId)?.has(componentType) ?? false;
|
|
1934
1946
|
}
|
|
1935
1947
|
function pruneMissingRemovals(changeset, archetype, entityId) {
|
|
1936
1948
|
let toPrune;
|
|
@@ -1941,14 +1953,14 @@ function pruneMissingRemovals(changeset, archetype, entityId) {
|
|
|
1941
1953
|
if (toPrune !== void 0) for (const componentType of toPrune) changeset.removes.delete(componentType);
|
|
1942
1954
|
}
|
|
1943
1955
|
function hasArchetypeStructuralChange(changeset, currentArchetype) {
|
|
1944
|
-
for (const componentType of changeset.removes) if (!
|
|
1945
|
-
for (const componentType of changeset.adds.keys()) if (!
|
|
1956
|
+
for (const componentType of changeset.removes) if (!isSparseRelation(componentType) && currentArchetype.componentTypeSet.has(componentType)) return true;
|
|
1957
|
+
for (const componentType of changeset.adds.keys()) if (!isSparseRelation(componentType) && !currentArchetype.componentTypeSet.has(componentType)) return true;
|
|
1946
1958
|
return false;
|
|
1947
1959
|
}
|
|
1948
1960
|
function buildFinalRegularComponentTypes(currentArchetype, changeset) {
|
|
1949
1961
|
const finalRegularTypes = new Set(currentArchetype.componentTypes);
|
|
1950
|
-
for (const componentType of changeset.removes) if (!
|
|
1951
|
-
for (const [componentType] of changeset.adds) if (!
|
|
1962
|
+
for (const componentType of changeset.removes) if (!isSparseRelation(componentType)) finalRegularTypes.delete(componentType);
|
|
1963
|
+
for (const [componentType] of changeset.adds) if (!isSparseRelation(componentType)) finalRegularTypes.add(componentType);
|
|
1952
1964
|
return Array.from(finalRegularTypes);
|
|
1953
1965
|
}
|
|
1954
1966
|
function applyChangeset(ctx, entityId, currentArchetype, changeset, entityToArchetype, removedComponents) {
|
|
@@ -1962,39 +1974,34 @@ function applyChangeset(ctx, entityId, currentArchetype, changeset, entityToArch
|
|
|
1962
1974
|
entityToArchetype.set(entityId, newArchetype);
|
|
1963
1975
|
return newArchetype;
|
|
1964
1976
|
}
|
|
1965
|
-
if (removedComponents !== null)
|
|
1966
|
-
else
|
|
1977
|
+
if (removedComponents !== null) applySparseChanges(ctx.sparseStore, entityId, changeset, removedComponents);
|
|
1978
|
+
else applySparseChangesNoHooks(ctx.sparseStore, entityId, changeset);
|
|
1967
1979
|
for (const [componentType, component] of changeset.adds) {
|
|
1968
|
-
if (
|
|
1980
|
+
if (isSparseRelation(componentType)) continue;
|
|
1969
1981
|
currentArchetype.set(entityId, componentType, component);
|
|
1970
1982
|
}
|
|
1971
1983
|
return currentArchetype;
|
|
1972
1984
|
}
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
function applyDontFragmentChanges(dontFragmentRelations, entityId, changeset, removedComponents) {
|
|
1979
|
-
for (const componentType of changeset.removes) if (isDontFragmentRelation(componentType)) {
|
|
1980
|
-
const removedValue = dontFragmentRelations.getValue(entityId, componentType);
|
|
1981
|
-
if (removedValue !== void 0 || dontFragmentRelations.getAllForEntity(entityId).some(([t]) => t === componentType)) removedComponents.set(componentType, removedValue);
|
|
1982
|
-
dontFragmentRelations.deleteValue(entityId, componentType);
|
|
1985
|
+
function applySparseChanges(sparseStore, entityId, changeset, removedComponents) {
|
|
1986
|
+
for (const componentType of changeset.removes) if (isSparseRelation(componentType)) {
|
|
1987
|
+
const removedValue = sparseStore.getValue(entityId, componentType);
|
|
1988
|
+
if (removedValue !== void 0 || sparseStore.getAllForEntity(entityId).some(([t]) => t === componentType)) removedComponents.set(componentType, removedValue);
|
|
1989
|
+
sparseStore.deleteValue(entityId, componentType);
|
|
1983
1990
|
}
|
|
1984
|
-
for (const [componentType, component] of changeset.adds) if (
|
|
1991
|
+
for (const [componentType, component] of changeset.adds) if (isSparseRelation(componentType)) sparseStore.setValue(entityId, componentType, component);
|
|
1985
1992
|
}
|
|
1986
|
-
function
|
|
1987
|
-
for (const componentType of changeset.removes) if (
|
|
1988
|
-
for (const [componentType, component] of changeset.adds) if (
|
|
1993
|
+
function applySparseChangesNoHooks(sparseStore, entityId, changeset) {
|
|
1994
|
+
for (const componentType of changeset.removes) if (isSparseRelation(componentType)) sparseStore.deleteValue(entityId, componentType);
|
|
1995
|
+
for (const [componentType, component] of changeset.adds) if (isSparseRelation(componentType)) sparseStore.setValue(entityId, componentType, component);
|
|
1989
1996
|
}
|
|
1990
1997
|
function filterRegularComponentTypes(componentTypes) {
|
|
1991
1998
|
const regularTypes = [];
|
|
1992
1999
|
for (const componentType of componentTypes) {
|
|
1993
|
-
if (
|
|
2000
|
+
if (isSparseWildcard(componentType)) {
|
|
1994
2001
|
regularTypes.push(componentType);
|
|
1995
2002
|
continue;
|
|
1996
2003
|
}
|
|
1997
|
-
if (
|
|
2004
|
+
if (isSparseRelation(componentType)) continue;
|
|
1998
2005
|
regularTypes.push(componentType);
|
|
1999
2006
|
}
|
|
2000
2007
|
return regularTypes;
|
|
@@ -2002,9 +2009,15 @@ function filterRegularComponentTypes(componentTypes) {
|
|
|
2002
2009
|
//#endregion
|
|
2003
2010
|
//#region src/world/hooks.ts
|
|
2004
2011
|
/**
|
|
2012
|
+
* Debug-only counter incremented on every invokeHook call when armed.
|
|
2013
|
+
* World reads and resets this during armed syncs.
|
|
2014
|
+
*/
|
|
2015
|
+
const debugHookExecutionCounter = { value: 0 };
|
|
2016
|
+
/**
|
|
2005
2017
|
* Unified hook invocation: prefers entry.callback (callback style) over hook.on_* (object style).
|
|
2006
2018
|
*/
|
|
2007
2019
|
function invokeHook(entry, event, entityId, components) {
|
|
2020
|
+
debugHookExecutionCounter.value++;
|
|
2008
2021
|
if (entry.callback) {
|
|
2009
2022
|
entry.callback(event, entityId, ...components);
|
|
2010
2023
|
return;
|
|
@@ -2303,9 +2316,9 @@ function getEntityReferences(entityReferences, targetEntityId) {
|
|
|
2303
2316
|
//#endregion
|
|
2304
2317
|
//#region src/storage/serialization.ts
|
|
2305
2318
|
/**
|
|
2306
|
-
*
|
|
2319
|
+
* Core encoding logic (no cache). Extracted so cached wrapper can reuse it without duplication.
|
|
2307
2320
|
*/
|
|
2308
|
-
function
|
|
2321
|
+
function encodeEntityIdCore(id) {
|
|
2309
2322
|
const detailed = getDetailedIdType(id);
|
|
2310
2323
|
switch (detailed.type) {
|
|
2311
2324
|
case "component": {
|
|
@@ -2343,6 +2356,20 @@ function encodeEntityId(id) {
|
|
|
2343
2356
|
}
|
|
2344
2357
|
}
|
|
2345
2358
|
/**
|
|
2359
|
+
* Encode an EntityId, using an optional cache Map to avoid repeated getDetailedIdType
|
|
2360
|
+
* + name lookup work for IDs that appear many times (typical during full world snapshot).
|
|
2361
|
+
*/
|
|
2362
|
+
function encodeEntityIdCached(id, cache) {
|
|
2363
|
+
if (cache) {
|
|
2364
|
+
const cached = cache.get(id);
|
|
2365
|
+
if (cached !== void 0) return cached;
|
|
2366
|
+
const result = encodeEntityIdCore(id);
|
|
2367
|
+
cache.set(id, result);
|
|
2368
|
+
return result;
|
|
2369
|
+
}
|
|
2370
|
+
return encodeEntityIdCore(id);
|
|
2371
|
+
}
|
|
2372
|
+
/**
|
|
2346
2373
|
* Decode a SerializedEntityId back into an internal EntityId
|
|
2347
2374
|
*/
|
|
2348
2375
|
function decodeSerializedId(sid) {
|
|
@@ -2383,24 +2410,16 @@ function decodeSerializedId(sid) {
|
|
|
2383
2410
|
* Serializes the full world state to a plain JS object suitable for JSON encoding.
|
|
2384
2411
|
*/
|
|
2385
2412
|
function serializeWorld(archetypes, componentEntities, entityIdManager) {
|
|
2413
|
+
const idCache = /* @__PURE__ */ new Map();
|
|
2386
2414
|
const entities = [];
|
|
2387
2415
|
for (const archetype of archetypes) {
|
|
2388
|
-
const
|
|
2389
|
-
|
|
2390
|
-
id: encodeEntityId(entity),
|
|
2391
|
-
components: Array.from(components.entries()).map(([rawType, value]) => ({
|
|
2392
|
-
type: encodeEntityId(rawType),
|
|
2393
|
-
value: value === MISSING_COMPONENT ? void 0 : value
|
|
2394
|
-
}))
|
|
2395
|
-
});
|
|
2416
|
+
const encodedComponentTypes = archetype.componentTypes.map((t) => encodeEntityIdCached(t, idCache));
|
|
2417
|
+
archetype.appendSerializedEntities(entities, (id) => encodeEntityIdCached(id, idCache), encodedComponentTypes);
|
|
2396
2418
|
}
|
|
2397
2419
|
const componentEntitiesArr = [];
|
|
2398
2420
|
for (const [entityId, components] of componentEntities.entries()) componentEntitiesArr.push({
|
|
2399
|
-
id:
|
|
2400
|
-
components:
|
|
2401
|
-
type: encodeEntityId(rawType),
|
|
2402
|
-
value: value === MISSING_COMPONENT ? void 0 : value
|
|
2403
|
-
}))
|
|
2421
|
+
id: encodeEntityIdCached(entityId, idCache),
|
|
2422
|
+
components: serializeComponentsFromMap(components, idCache)
|
|
2404
2423
|
});
|
|
2405
2424
|
return {
|
|
2406
2425
|
version: 1,
|
|
@@ -2409,6 +2428,15 @@ function serializeWorld(archetypes, componentEntities, entityIdManager) {
|
|
|
2409
2428
|
componentEntities: componentEntitiesArr
|
|
2410
2429
|
};
|
|
2411
2430
|
}
|
|
2431
|
+
/** Small helper to avoid duplicating the "Map → SerializedComponent[] with cache" pattern. */
|
|
2432
|
+
function serializeComponentsFromMap(components, idCache) {
|
|
2433
|
+
const result = [];
|
|
2434
|
+
for (const [rawType, value] of components) result.push({
|
|
2435
|
+
type: encodeEntityIdCached(rawType, idCache),
|
|
2436
|
+
value: value === MISSING_COMPONENT ? void 0 : value
|
|
2437
|
+
});
|
|
2438
|
+
return result;
|
|
2439
|
+
}
|
|
2412
2440
|
/**
|
|
2413
2441
|
* Restores world state from a snapshot into the provided context.
|
|
2414
2442
|
* Intended to be called from `World`'s constructor.
|
|
@@ -2430,12 +2458,11 @@ function deserializeWorld(ctx, snapshot) {
|
|
|
2430
2458
|
const entityId = decodeSerializedId(entry.id);
|
|
2431
2459
|
const componentsArray = entry.components || [];
|
|
2432
2460
|
const componentMap = /* @__PURE__ */ new Map();
|
|
2433
|
-
const componentTypes = [];
|
|
2434
2461
|
for (const componentEntry of componentsArray) {
|
|
2435
2462
|
const componentType = decodeSerializedId(componentEntry.type);
|
|
2436
2463
|
componentMap.set(componentType, componentEntry.value);
|
|
2437
|
-
componentTypes.push(componentType);
|
|
2438
2464
|
}
|
|
2465
|
+
const componentTypes = Array.from(componentMap.keys());
|
|
2439
2466
|
const archetype = ctx.ensureArchetype(componentTypes);
|
|
2440
2467
|
archetype.addEntity(entityId, componentMap);
|
|
2441
2468
|
ctx.setEntityToArchetype(entityId, archetype);
|
|
@@ -2461,18 +2488,22 @@ var World = class {
|
|
|
2461
2488
|
entityReferences = /* @__PURE__ */ new Map();
|
|
2462
2489
|
/** Reverse index: entity ID → set of archetypes whose componentTypes include that entity ID */
|
|
2463
2490
|
entityToReferencingArchetypes = /* @__PURE__ */ new Map();
|
|
2464
|
-
/**
|
|
2465
|
-
|
|
2491
|
+
/** Sparse relation storage (for components created with `sparse: true`), shared with all Archetype instances */
|
|
2492
|
+
sparseStore = new SparseStoreImpl();
|
|
2466
2493
|
/** Component entity (singleton) storage */
|
|
2467
2494
|
componentEntities = new ComponentEntityStore();
|
|
2468
2495
|
queryRegistry = new QueryRegistry();
|
|
2469
2496
|
hooks = /* @__PURE__ */ new Set();
|
|
2497
|
+
_debugCollectors = /* @__PURE__ */ new Set();
|
|
2498
|
+
_debugMigrations = 0;
|
|
2499
|
+
_debugArchetypesCreated = 0;
|
|
2500
|
+
_debugArchetypesRemoved = 0;
|
|
2470
2501
|
commandBuffer = new CommandBuffer((entityId, commands) => this.executeEntityCommands(entityId, commands));
|
|
2471
2502
|
_changeset = new ComponentChangeset();
|
|
2472
2503
|
_removeChangeset = new ComponentChangeset();
|
|
2473
2504
|
/** Cached command processor context to avoid per-entity object allocation */
|
|
2474
2505
|
_commandCtx = {
|
|
2475
|
-
|
|
2506
|
+
sparseStore: this.sparseStore,
|
|
2476
2507
|
ensureArchetype: (ct) => this.ensureArchetype(ct)
|
|
2477
2508
|
};
|
|
2478
2509
|
/** Cached hooks context to avoid per-entity object allocation */
|
|
@@ -2678,9 +2709,9 @@ var World = class {
|
|
|
2678
2709
|
const archetype = this.entityToArchetype.get(entityId);
|
|
2679
2710
|
if (!archetype) return false;
|
|
2680
2711
|
if (archetype.componentTypeSet.has(componentType)) return true;
|
|
2681
|
-
if (
|
|
2682
|
-
if (this.
|
|
2683
|
-
return this.
|
|
2712
|
+
if (isSparseRelation(componentType)) {
|
|
2713
|
+
if (this.sparseStore.getValue(entityId, componentType) !== void 0) return true;
|
|
2714
|
+
return this.sparseStore.getAllForEntity(entityId).some(([t]) => t === componentType);
|
|
2684
2715
|
}
|
|
2685
2716
|
return false;
|
|
2686
2717
|
}
|
|
@@ -2693,8 +2724,8 @@ var World = class {
|
|
|
2693
2724
|
if (!archetype) throw new Error(`Entity ${entityId} does not exist`);
|
|
2694
2725
|
if (componentType >= 0 || componentType % RELATION_SHIFT !== 0) {
|
|
2695
2726
|
const inArchetype = archetype.componentTypeSet.has(componentType);
|
|
2696
|
-
const
|
|
2697
|
-
if (!(inArchetype ||
|
|
2727
|
+
const hasSparse = isSparseRelation(componentType);
|
|
2728
|
+
if (!(inArchetype || hasSparse && (this.sparseStore.getValue(entityId, componentType) !== void 0 || this.sparseStore.getAllForEntity(entityId).some(([t]) => t === componentType)))) throw new Error(`Entity ${entityId} does not have component ${componentType}. Use has() to check component existence before calling get().`);
|
|
2698
2729
|
}
|
|
2699
2730
|
return archetype.get(entityId, componentType);
|
|
2700
2731
|
}
|
|
@@ -2716,6 +2747,163 @@ var World = class {
|
|
|
2716
2747
|
}
|
|
2717
2748
|
return archetype.getOptional(entityId, componentType);
|
|
2718
2749
|
}
|
|
2750
|
+
/**
|
|
2751
|
+
* Retrieves all targets (and their associated data) for relations of a given
|
|
2752
|
+
* base component on an entity.
|
|
2753
|
+
*
|
|
2754
|
+
* This is the ergonomic replacement for the common pattern:
|
|
2755
|
+
* world.get(entity, relation(Comp, "*"))
|
|
2756
|
+
*
|
|
2757
|
+
* @example
|
|
2758
|
+
* const ChildOf = component({ exclusive: true, sparse: true });
|
|
2759
|
+
* const children = world.getRelationTargets(parent, ChildOf); // usually []
|
|
2760
|
+
* const items = world.getRelationTargets(player, InInventory);
|
|
2761
|
+
*
|
|
2762
|
+
* // For common hierarchy use cases, prefer the higher-level helpers:
|
|
2763
|
+
* // world.getChildren(parent, ChildOf), world.getParent(child, ChildOf)
|
|
2764
|
+
*/
|
|
2765
|
+
getRelationTargets(entityId, relationComp) {
|
|
2766
|
+
this.assertEntityExists(entityId, "Entity");
|
|
2767
|
+
const wildcard = relation(relationComp, "*");
|
|
2768
|
+
if (this.componentEntities.exists(entityId)) return this.componentEntities.getWildcard(entityId, wildcard);
|
|
2769
|
+
return this.get(entityId, wildcard);
|
|
2770
|
+
}
|
|
2771
|
+
/**
|
|
2772
|
+
* Returns every entity that currently holds a relation of the given base
|
|
2773
|
+
* component pointing at `targetId`.
|
|
2774
|
+
*
|
|
2775
|
+
* This is the efficient **reverse** lookup. For common hierarchy cases,
|
|
2776
|
+
* prefer the higher-level `world.getChildren(parent, ChildOf)` instead.
|
|
2777
|
+
*
|
|
2778
|
+
* @example
|
|
2779
|
+
* const ChildOf = component({ exclusive: true, sparse: true });
|
|
2780
|
+
* const directChildren = world.getRelationSources(ship, ChildOf);
|
|
2781
|
+
*/
|
|
2782
|
+
getRelationSources(targetId, relationComp) {
|
|
2783
|
+
const refs = getEntityReferences(this.entityReferences, targetId);
|
|
2784
|
+
const result = [];
|
|
2785
|
+
for (const [source, relType] of refs) {
|
|
2786
|
+
if (!this.entityToArchetype.has(source) && !this.componentEntities.exists(source)) continue;
|
|
2787
|
+
if (getComponentIdFromRelationId(relType) === relationComp) result.push(source);
|
|
2788
|
+
}
|
|
2789
|
+
return result;
|
|
2790
|
+
}
|
|
2791
|
+
/**
|
|
2792
|
+
* Returns true if the entity has any (or a specific-target) relation of the
|
|
2793
|
+
* given base component.
|
|
2794
|
+
*/
|
|
2795
|
+
hasRelation(entityId, relationComp, targetId) {
|
|
2796
|
+
this.assertEntityExists(entityId, "Entity");
|
|
2797
|
+
if (targetId !== void 0) {
|
|
2798
|
+
const specific = relation(relationComp, targetId);
|
|
2799
|
+
return this.has(entityId, specific);
|
|
2800
|
+
}
|
|
2801
|
+
return this.getRelationTargets(entityId, relationComp).length > 0;
|
|
2802
|
+
}
|
|
2803
|
+
/**
|
|
2804
|
+
* Returns the number of relations of the given base component held by the entity.
|
|
2805
|
+
*/
|
|
2806
|
+
countRelations(entityId, relationComp) {
|
|
2807
|
+
this.assertEntityExists(entityId, "Entity");
|
|
2808
|
+
return this.getRelationTargets(entityId, relationComp).length;
|
|
2809
|
+
}
|
|
2810
|
+
/**
|
|
2811
|
+
* For an *exclusive* relation (e.g. ChildOf, Owner), returns the single
|
|
2812
|
+
* target entity (or undefined if none).
|
|
2813
|
+
*
|
|
2814
|
+
* When the component was declared `exclusive: true`, this is the preferred
|
|
2815
|
+
* accessor (clearer intent than array destructuring).
|
|
2816
|
+
*/
|
|
2817
|
+
getSingleRelationTarget(entityId, relationComp) {
|
|
2818
|
+
const targets = this.getRelationTargets(entityId, relationComp);
|
|
2819
|
+
return targets.length > 0 ? targets[0][0] : void 0;
|
|
2820
|
+
}
|
|
2821
|
+
/**
|
|
2822
|
+
* Returns the direct children of `parent` for the given relationship component
|
|
2823
|
+
* (typically a `ChildOf` or similar exclusive `sparse` relation).
|
|
2824
|
+
*
|
|
2825
|
+
* This is the recommended high-level API for hierarchy traversal.
|
|
2826
|
+
* It uses the internal reverse reference index for efficiency.
|
|
2827
|
+
*
|
|
2828
|
+
* @example
|
|
2829
|
+
* const ChildOf = component({ exclusive: true, sparse: true });
|
|
2830
|
+
* const kids = world.getChildren(ship, ChildOf);
|
|
2831
|
+
*/
|
|
2832
|
+
getChildren(parent, childOf) {
|
|
2833
|
+
return this.getRelationSources(parent, childOf);
|
|
2834
|
+
}
|
|
2835
|
+
/**
|
|
2836
|
+
* Returns the parent of `child` for the given relationship component
|
|
2837
|
+
* (typically an exclusive `ChildOf` relation).
|
|
2838
|
+
*
|
|
2839
|
+
* @example
|
|
2840
|
+
* const ChildOf = component({ exclusive: true, sparse: true });
|
|
2841
|
+
* const parent = world.getParent(turret, ChildOf);
|
|
2842
|
+
*/
|
|
2843
|
+
getParent(child, childOf) {
|
|
2844
|
+
return this.getSingleRelationTarget(child, childOf);
|
|
2845
|
+
}
|
|
2846
|
+
/**
|
|
2847
|
+
* Returns the ancestor chain from the immediate parent up to (but not
|
|
2848
|
+
* including) the root for the given relationship component.
|
|
2849
|
+
*
|
|
2850
|
+
* @example
|
|
2851
|
+
* const ChildOf = component({ exclusive: true, sparse: true });
|
|
2852
|
+
* const ancestors = world.getAncestors(muzzle, ChildOf); // [turret, ship]
|
|
2853
|
+
*/
|
|
2854
|
+
getAncestors(entity, childOf) {
|
|
2855
|
+
const ancestors = [];
|
|
2856
|
+
let cur = this.getParent(entity, childOf);
|
|
2857
|
+
while (cur !== void 0) {
|
|
2858
|
+
ancestors.push(cur);
|
|
2859
|
+
cur = this.getParent(cur, childOf);
|
|
2860
|
+
}
|
|
2861
|
+
return ancestors;
|
|
2862
|
+
}
|
|
2863
|
+
/**
|
|
2864
|
+
* Iteratively traverses all descendants of `root` in DFS pre-order.
|
|
2865
|
+
* This is a generator and is safe for very deep hierarchies.
|
|
2866
|
+
*
|
|
2867
|
+
* @example
|
|
2868
|
+
* for (const { entity, depth, parent } of world.iterateDescendants(root, ChildOf)) {
|
|
2869
|
+
* console.log(depth, entity);
|
|
2870
|
+
* }
|
|
2871
|
+
*/
|
|
2872
|
+
*iterateDescendants(root, childOf, opts = {}) {
|
|
2873
|
+
const { includeSelf = false, maxDepth } = opts;
|
|
2874
|
+
const stack = [];
|
|
2875
|
+
if (includeSelf) stack.push({
|
|
2876
|
+
entity: root,
|
|
2877
|
+
depth: 0,
|
|
2878
|
+
parent: null
|
|
2879
|
+
});
|
|
2880
|
+
else for (const child of this.getChildren(root, childOf)) stack.push({
|
|
2881
|
+
entity: child,
|
|
2882
|
+
depth: 1,
|
|
2883
|
+
parent: root
|
|
2884
|
+
});
|
|
2885
|
+
while (stack.length > 0) {
|
|
2886
|
+
const current = stack.pop();
|
|
2887
|
+
if (maxDepth !== void 0 && current.depth > maxDepth) continue;
|
|
2888
|
+
yield current;
|
|
2889
|
+
const kids = this.getChildren(current.entity, childOf);
|
|
2890
|
+
for (let i = kids.length - 1; i >= 0; i--) {
|
|
2891
|
+
const k = kids[i];
|
|
2892
|
+
stack.push({
|
|
2893
|
+
entity: k,
|
|
2894
|
+
depth: current.depth + 1,
|
|
2895
|
+
parent: current.entity
|
|
2896
|
+
});
|
|
2897
|
+
}
|
|
2898
|
+
}
|
|
2899
|
+
}
|
|
2900
|
+
/**
|
|
2901
|
+
* Callback-based descendant traversal (hot path friendly).
|
|
2902
|
+
* Return `false` from the visitor to stop early.
|
|
2903
|
+
*/
|
|
2904
|
+
traverseDescendants(root, childOf, visitor, opts = {}) {
|
|
2905
|
+
for (const { entity, depth, parent } of this.iterateDescendants(root, childOf, opts)) if (visitor(entity, depth, parent) === false) return;
|
|
2906
|
+
}
|
|
2719
2907
|
hook(componentTypes, hook, filter) {
|
|
2720
2908
|
const isCallback = typeof hook === "function";
|
|
2721
2909
|
const callback = isCallback ? hook : void 0;
|
|
@@ -2751,6 +2939,74 @@ var World = class {
|
|
|
2751
2939
|
};
|
|
2752
2940
|
}
|
|
2753
2941
|
/**
|
|
2942
|
+
* Creates a debug stats collector that will receive a `SyncDebugStats` payload
|
|
2943
|
+
* after every subsequent `sync()`.
|
|
2944
|
+
*
|
|
2945
|
+
* The returned object is a pure lifecycle handle. It does not store data.
|
|
2946
|
+
* Collection stops when you call `[Symbol.dispose]()` (or use a `using` declaration).
|
|
2947
|
+
*
|
|
2948
|
+
* All active collectors receive the exact same stats object for a given sync.
|
|
2949
|
+
* Exceptions thrown by callbacks are ignored.
|
|
2950
|
+
*
|
|
2951
|
+
* This is intended for development/debugging and leak detection.
|
|
2952
|
+
*/
|
|
2953
|
+
createDebugStatsCollector(callback) {
|
|
2954
|
+
this._debugCollectors.add(callback);
|
|
2955
|
+
return { [Symbol.dispose]: () => {
|
|
2956
|
+
this._debugCollectors.delete(callback);
|
|
2957
|
+
} };
|
|
2958
|
+
}
|
|
2959
|
+
_resetDebugActivityCounters() {
|
|
2960
|
+
this._debugMigrations = 0;
|
|
2961
|
+
this._debugArchetypesCreated = 0;
|
|
2962
|
+
this._debugArchetypesRemoved = 0;
|
|
2963
|
+
debugHookExecutionCounter.value = 0;
|
|
2964
|
+
}
|
|
2965
|
+
_deliverDebugStats(timings) {
|
|
2966
|
+
const entityCount = this.entityToArchetype.size;
|
|
2967
|
+
let emptyArchetypes = 0;
|
|
2968
|
+
for (const arch of this.archetypes) if (arch.size === 0) emptyArchetypes++;
|
|
2969
|
+
let archetypesByComponentSize = 0;
|
|
2970
|
+
for (const set of this.archetypesByComponent.values()) archetypesByComponentSize += set.size;
|
|
2971
|
+
const stats = {
|
|
2972
|
+
timestamps: {
|
|
2973
|
+
syncStart: timings.syncStart,
|
|
2974
|
+
syncEnd: timings.syncEnd,
|
|
2975
|
+
commandBufferStart: timings.commandBufferStart,
|
|
2976
|
+
commandBufferEnd: timings.commandBufferEnd
|
|
2977
|
+
},
|
|
2978
|
+
commandIterations: timings.commandIterations,
|
|
2979
|
+
entities: {
|
|
2980
|
+
total: entityCount,
|
|
2981
|
+
freelistSize: this.entityIdManager.getFreelistSize(),
|
|
2982
|
+
nextId: this.entityIdManager.getNextId()
|
|
2983
|
+
},
|
|
2984
|
+
archetypes: {
|
|
2985
|
+
total: this.archetypes.length,
|
|
2986
|
+
empty: emptyArchetypes
|
|
2987
|
+
},
|
|
2988
|
+
queries: {
|
|
2989
|
+
cached: this.queryRegistry.cache?.size ?? 0,
|
|
2990
|
+
registered: this.queryRegistry.queries?.size ?? 0
|
|
2991
|
+
},
|
|
2992
|
+
hooks: { total: this.hooks.size },
|
|
2993
|
+
indices: {
|
|
2994
|
+
entityReferences: this.entityReferences.size,
|
|
2995
|
+
entityToReferencingArchetypes: this.entityToReferencingArchetypes.size,
|
|
2996
|
+
archetypesByComponent: archetypesByComponentSize
|
|
2997
|
+
},
|
|
2998
|
+
activity: {
|
|
2999
|
+
migrations: this._debugMigrations,
|
|
3000
|
+
hooksExecuted: debugHookExecutionCounter.value,
|
|
3001
|
+
archetypesCreated: this._debugArchetypesCreated,
|
|
3002
|
+
archetypesRemoved: this._debugArchetypesRemoved
|
|
3003
|
+
}
|
|
3004
|
+
};
|
|
3005
|
+
for (const cb of this._debugCollectors) try {
|
|
3006
|
+
cb(stats);
|
|
3007
|
+
} catch {}
|
|
3008
|
+
}
|
|
3009
|
+
/**
|
|
2754
3010
|
* Synchronizes all buffered commands (set/remove/delete) to the world.
|
|
2755
3011
|
* This method must be called after making changes via `set()`, `remove()`, or `delete()` for them to take effect.
|
|
2756
3012
|
* Typically called once per frame at the end of your game loop.
|
|
@@ -2761,7 +3017,20 @@ var World = class {
|
|
|
2761
3017
|
* world.sync(); // Apply all buffered changes
|
|
2762
3018
|
*/
|
|
2763
3019
|
sync() {
|
|
2764
|
-
this.
|
|
3020
|
+
const hasCollectors = this._debugCollectors.size > 0;
|
|
3021
|
+
const syncStart = hasCollectors ? performance.now() : 0;
|
|
3022
|
+
if (hasCollectors) this._resetDebugActivityCounters();
|
|
3023
|
+
const commandBufferStart = hasCollectors ? performance.now() : 0;
|
|
3024
|
+
const commandIterations = this.commandBuffer.execute();
|
|
3025
|
+
const commandBufferEnd = hasCollectors ? performance.now() : 0;
|
|
3026
|
+
const syncEnd = hasCollectors ? performance.now() : 0;
|
|
3027
|
+
if (hasCollectors) this._deliverDebugStats({
|
|
3028
|
+
syncStart,
|
|
3029
|
+
syncEnd,
|
|
3030
|
+
commandBufferStart,
|
|
3031
|
+
commandBufferEnd,
|
|
3032
|
+
commandIterations
|
|
3033
|
+
});
|
|
2765
3034
|
}
|
|
2766
3035
|
/**
|
|
2767
3036
|
* Creates a cached query for efficiently iterating entities with specific components.
|
|
@@ -2935,13 +3204,15 @@ var World = class {
|
|
|
2935
3204
|
});
|
|
2936
3205
|
const hasStructuralChange = changeset.removes.size > 0 || changeset.adds.size > 0;
|
|
2937
3206
|
if (this.hooks.size === 0) {
|
|
2938
|
-
applyChangeset(this._commandCtx, entityId, currentArchetype, changeset, this.entityToArchetype, null);
|
|
3207
|
+
const newArchetype = applyChangeset(this._commandCtx, entityId, currentArchetype, changeset, this.entityToArchetype, null);
|
|
2939
3208
|
if (hasStructuralChange) this.updateEntityReferences(entityId, changeset);
|
|
3209
|
+
if (this._debugCollectors.size > 0 && newArchetype !== currentArchetype) this._debugMigrations++;
|
|
2940
3210
|
return;
|
|
2941
3211
|
}
|
|
2942
3212
|
const removedComponents = /* @__PURE__ */ new Map();
|
|
2943
3213
|
const newArchetype = applyChangeset(this._commandCtx, entityId, currentArchetype, changeset, this.entityToArchetype, removedComponents);
|
|
2944
3214
|
if (hasStructuralChange) this.updateEntityReferences(entityId, changeset);
|
|
3215
|
+
if (this._debugCollectors.size > 0 && newArchetype !== currentArchetype) this._debugMigrations++;
|
|
2945
3216
|
triggerLifecycleHooks(this.createHooksContext(), entityId, changeset.adds, removedComponents, currentArchetype, newArchetype);
|
|
2946
3217
|
}
|
|
2947
3218
|
createHooksContext() {
|
|
@@ -3004,8 +3275,9 @@ var World = class {
|
|
|
3004
3275
|
}
|
|
3005
3276
|
}
|
|
3006
3277
|
createNewArchetype(componentTypes) {
|
|
3007
|
-
const newArchetype = new Archetype(componentTypes, this.
|
|
3278
|
+
const newArchetype = new Archetype(componentTypes, this.sparseStore);
|
|
3008
3279
|
this.archetypes.push(newArchetype);
|
|
3280
|
+
if (this._debugCollectors.size > 0) this._debugArchetypesCreated++;
|
|
3009
3281
|
for (const componentType of componentTypes) {
|
|
3010
3282
|
let archetypes = this.archetypesByComponent.get(componentType);
|
|
3011
3283
|
if (!archetypes) {
|
|
@@ -3028,11 +3300,11 @@ var World = class {
|
|
|
3028
3300
|
archetypeMatchesHook(archetype, entry) {
|
|
3029
3301
|
return entry.requiredComponents.every((c) => {
|
|
3030
3302
|
if (isWildcardRelationId(c)) {
|
|
3031
|
-
if (
|
|
3303
|
+
if (isSparseWildcard(c)) return true;
|
|
3032
3304
|
const componentId = getComponentIdFromRelationId(c);
|
|
3033
3305
|
return componentId !== void 0 && archetype.hasRelationWithComponentId(componentId);
|
|
3034
3306
|
}
|
|
3035
|
-
return archetype.componentTypeSet.has(c) ||
|
|
3307
|
+
return archetype.componentTypeSet.has(c) || isSparseRelation(c);
|
|
3036
3308
|
}) && matchesFilter(archetype, entry.filter);
|
|
3037
3309
|
}
|
|
3038
3310
|
cleanupArchetypesReferencingEntity(entityId) {
|
|
@@ -3048,6 +3320,7 @@ var World = class {
|
|
|
3048
3320
|
this.archetypes[index] = last;
|
|
3049
3321
|
this.archetypes.pop();
|
|
3050
3322
|
}
|
|
3323
|
+
if (this._debugCollectors.size > 0) this._debugArchetypesRemoved++;
|
|
3051
3324
|
this.archetypeBySignature.delete(this.createArchetypeSignature(archetype.componentTypes));
|
|
3052
3325
|
for (const componentType of archetype.componentTypes) {
|
|
3053
3326
|
const archetypes = this.archetypesByComponent.get(componentType);
|
|
@@ -3085,6 +3358,6 @@ var World = class {
|
|
|
3085
3358
|
}
|
|
3086
3359
|
};
|
|
3087
3360
|
//#endregion
|
|
3088
|
-
export { getComponentIdByName as a,
|
|
3361
|
+
export { getComponentIdByName as a, isSparseRelation as c, isWildcardRelationId as d, relation as f, isRelationId as h, component as i, isSparseWildcard as l, isEntityId as m, Query as n, getComponentNameById as o, isComponentId as p, EntityBuilder as r, isSparseComponent as s, World as t, decodeRelationId as u };
|
|
3089
3362
|
|
|
3090
3363
|
//# sourceMappingURL=world.mjs.map
|