@codehz/ecs 0.9.0 → 0.10.1

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/dist/world.mjs CHANGED
@@ -622,124 +622,327 @@ var EntityBuilder = class {
622
622
  }
623
623
  };
624
624
  //#endregion
625
- //#region src/component/type-utils.ts
626
- /**
627
- * Normalize component type collections into a stable ascending order.
628
- * This keeps cache keys and archetype signatures deterministic.
629
- */
630
- function normalizeComponentTypes(componentTypes) {
631
- return [...componentTypes].sort((a, b) => a - b);
632
- }
633
- //#endregion
634
- //#region src/types/index.ts
635
- function isOptionalEntityId(type) {
636
- return typeof type === "object" && type !== null && "optional" in type;
637
- }
638
- //#endregion
639
- //#region src/utils/utils.ts
625
+ //#region src/world/singleton.ts
640
626
  /**
641
- * Utility functions for ECS library
642
- */
643
- /**
644
- * Get a value from cache or compute and cache it if not present
645
- * @param cache The cache map
646
- * @param key The cache key
647
- * @param compute Function to compute the value if not cached (may have side effects)
648
- * @returns The cached or computed value
627
+ * Explicit handle for a singleton component (component-as-entity).
628
+ *
629
+ * This is the preferred API for singleton components.
630
+ * `world.set(componentId, value)` remains available only as a deprecated
631
+ * compatibility shorthand.
632
+ *
633
+ * @example
634
+ * const config = world.singleton(Config);
635
+ * config.set({ debug: true });
636
+ * world.sync();
637
+ * console.log(config.get());
649
638
  */
650
- function getOrCompute(cache, key, compute) {
651
- let value = cache.get(key);
652
- if (value === void 0) {
653
- value = compute();
654
- cache.set(key, value);
639
+ var SingletonHandle = class {
640
+ componentId;
641
+ ops;
642
+ constructor(componentId, ops) {
643
+ this.componentId = componentId;
644
+ this.ops = ops;
655
645
  }
656
- return value;
657
- }
646
+ has() {
647
+ return this.ops.has();
648
+ }
649
+ get() {
650
+ return this.ops.get();
651
+ }
652
+ getOptional() {
653
+ return this.ops.getOptional();
654
+ }
655
+ remove() {
656
+ this.ops.remove();
657
+ }
658
+ set(...args) {
659
+ this.ops.set(args[0]);
660
+ }
661
+ };
658
662
  //#endregion
659
- //#region src/archetype/helpers.ts
663
+ //#region src/archetype/store.ts
660
664
  /**
661
- * Check if a components map has any wildcard relations matching a component ID
662
- * @param components - Component entity's components map
663
- * @param wildcardComponentId - The component ID to match
664
- * @returns True if at least one matching relation exists
665
+ * Production implementation of SparseStore.
666
+ *
667
+ * Internal layout (optimized):
668
+ * - byComponent: baseComponentId (entityId RelationEntry)
669
+ * RelationEntry uses a single-value form for the common exclusive case (1 target),
670
+ * avoiding Map allocation for the vast majority of usage.
671
+ * - entityIndex: entityId → Set<baseComponentId>
672
+ * Lightweight reverse index.
665
673
  */
666
- function hasWildcardRelation(components, wildcardComponentId) {
667
- for (const relId of components.keys()) if (isRelationId(relId)) {
668
- if (getComponentIdFromRelationId(relId) === wildcardComponentId) return true;
674
+ var SparseStoreImpl = class {
675
+ /**
676
+ * Primary storage, keyed by the base relation component ID.
677
+ */
678
+ byComponent = /* @__PURE__ */ new Map();
679
+ /**
680
+ * Reverse index: which base component kinds an entity participates in.
681
+ * Only required to support getAllForEntity and deleteEntity efficiently.
682
+ * The primary storage (byComponent) is deliberately not optimized for these operations.
683
+ */
684
+ entityIndex = /* @__PURE__ */ new Map();
685
+ getValue(entityId, relationType) {
686
+ const componentId = getComponentIdFromRelationId(relationType);
687
+ if (componentId === void 0) return void 0;
688
+ const entities = this.byComponent.get(componentId);
689
+ if (!entities) return void 0;
690
+ const entry = entities.get(entityId);
691
+ if (!entry) return void 0;
692
+ const targetId = getTargetIdFromRelationId(relationType);
693
+ if (entry.type === "single") return entry.target === targetId ? entry.data : void 0;
694
+ else {
695
+ const item = entry.targets.get(targetId);
696
+ return item ? item.data : void 0;
697
+ }
669
698
  }
670
- return false;
671
- }
672
- /**
673
- * Check if a detailed type represents a relation (entity or component)
674
- */
675
- function isRelationType(detailedType) {
676
- return detailedType.type === "entity-relation" || detailedType.type === "component-relation";
677
- }
678
- /**
679
- * Check if a component type matches a given component ID for relations
680
- */
681
- function matchesRelationComponentId(componentType, componentId) {
682
- const detailedType = getDetailedIdType(componentType);
683
- return isRelationType(detailedType) && detailedType.componentId === componentId;
684
- }
685
- /**
686
- * Build cache key for component types
687
- */
688
- function buildCacheKey(componentTypes) {
689
- return componentTypes.map((id) => isOptionalEntityId(id) ? `opt(${id.optional})` : `${id}`).join(",");
690
- }
699
+ setValue(entityId, relationType, data) {
700
+ const componentId = getComponentIdFromRelationId(relationType);
701
+ if (componentId === void 0) throw new Error("setValue called with a non-relation type on SparseStore");
702
+ let entities = this.byComponent.get(componentId);
703
+ if (!entities) {
704
+ entities = /* @__PURE__ */ new Map();
705
+ this.byComponent.set(componentId, entities);
706
+ }
707
+ const targetId = getTargetIdFromRelationId(relationType);
708
+ let entry = entities.get(entityId);
709
+ if (!entry) {
710
+ entry = {
711
+ type: "single",
712
+ relationType,
713
+ target: targetId,
714
+ data
715
+ };
716
+ entities.set(entityId, entry);
717
+ } else if (entry.type === "single") if (entry.target === targetId) {
718
+ entry.data = data;
719
+ entry.relationType = relationType;
720
+ } else {
721
+ const targets = /* @__PURE__ */ new Map();
722
+ targets.set(entry.target, {
723
+ relationType: entry.relationType,
724
+ data: entry.data
725
+ });
726
+ targets.set(targetId, {
727
+ relationType,
728
+ data
729
+ });
730
+ entities.set(entityId, {
731
+ type: "multi",
732
+ targets
733
+ });
734
+ }
735
+ else entry.targets.set(targetId, {
736
+ relationType,
737
+ data
738
+ });
739
+ let components = this.entityIndex.get(entityId);
740
+ if (!components) {
741
+ components = /* @__PURE__ */ new Set();
742
+ this.entityIndex.set(entityId, components);
743
+ }
744
+ components.add(componentId);
745
+ }
746
+ deleteValue(entityId, relationType) {
747
+ const componentId = getComponentIdFromRelationId(relationType);
748
+ if (componentId === void 0) return false;
749
+ const entities = this.byComponent.get(componentId);
750
+ if (!entities) return false;
751
+ const entry = entities.get(entityId);
752
+ if (!entry) return false;
753
+ const targetId = getTargetIdFromRelationId(relationType);
754
+ let existed = false;
755
+ if (entry.type === "single") {
756
+ if (entry.target === targetId) {
757
+ existed = true;
758
+ entities.delete(entityId);
759
+ }
760
+ } else {
761
+ existed = entry.targets.delete(targetId);
762
+ if (entry.targets.size === 0) entities.delete(entityId);
763
+ else if (entry.targets.size === 1) {
764
+ const [first] = entry.targets.entries();
765
+ const [t, item] = first;
766
+ entities.set(entityId, {
767
+ type: "single",
768
+ relationType: item.relationType,
769
+ target: t,
770
+ data: item.data
771
+ });
772
+ }
773
+ }
774
+ if (!entities.has(entityId) && entities.size === 0) this.byComponent.delete(componentId);
775
+ const components = this.entityIndex.get(entityId);
776
+ if (components && !entities.has(entityId)) {
777
+ components.delete(componentId);
778
+ if (components.size === 0) this.entityIndex.delete(entityId);
779
+ }
780
+ return existed;
781
+ }
782
+ hasAnyForComponent(componentId) {
783
+ const entities = this.byComponent.get(componentId);
784
+ return entities !== void 0 && entities.size > 0;
785
+ }
786
+ getRelationsForComponent(entityId, componentId) {
787
+ const result = [];
788
+ const entities = this.byComponent.get(componentId);
789
+ if (!entities) return result;
790
+ const entry = entities.get(entityId);
791
+ if (!entry) return result;
792
+ if (entry.type === "single") result.push([entry.target, entry.data]);
793
+ else for (const [target, item] of entry.targets) result.push([target, item.data]);
794
+ return result;
795
+ }
796
+ getAllForEntity(entityId) {
797
+ const components = this.entityIndex.get(entityId);
798
+ if (!components || components.size === 0) return [];
799
+ const result = [];
800
+ for (const componentId of components) {
801
+ const entry = this.byComponent.get(componentId)?.get(entityId);
802
+ if (entry) if (entry.type === "single") result.push([entry.relationType, entry.data]);
803
+ else for (const item of entry.targets.values()) result.push([item.relationType, item.data]);
804
+ }
805
+ return result;
806
+ }
807
+ deleteEntity(entityId) {
808
+ const components = this.entityIndex.get(entityId);
809
+ if (!components) return;
810
+ for (const componentId of components) {
811
+ const entities = this.byComponent.get(componentId);
812
+ if (entities) {
813
+ entities.delete(entityId);
814
+ if (entities.size === 0) this.byComponent.delete(componentId);
815
+ }
816
+ }
817
+ this.entityIndex.delete(entityId);
818
+ }
819
+ getAllForEntities(entityIds) {
820
+ const result = /* @__PURE__ */ new Map();
821
+ for (const eid of entityIds) {
822
+ const data = this.getAllForEntity(eid);
823
+ if (data.length > 0) result.set(eid, data);
824
+ }
825
+ return result;
826
+ }
827
+ };
828
+ //#endregion
829
+ //#region src/commands/buffer.ts
691
830
  /**
692
- * Get data source for wildcard relations from component types
831
+ * Maximum number of command buffer execution iterations to prevent infinite loops
693
832
  */
694
- function getWildcardRelationDataSource(componentTypes, componentId, optional) {
695
- const matchingRelations = componentTypes.filter((ct) => matchesRelationComponentId(ct, componentId));
696
- return optional ? matchingRelations.length > 0 ? matchingRelations : void 0 : matchingRelations;
697
- }
833
+ const MAX_COMMAND_ITERATIONS = 100;
698
834
  /**
699
- * Build wildcard relation value from matching relations.
700
- * Receives the SparseStore directly for efficient per-component lookups.
835
+ * Command buffer for deferred structural changes
701
836
  */
702
- function buildWildcardRelationValue(wildcardRelationType, matchingRelations, getDataAtIndex, sparseStore, entityId, optional) {
703
- const relations = [];
704
- const targetComponentId = getComponentIdFromRelationId(wildcardRelationType);
705
- for (const relType of matchingRelations || []) {
706
- const data = getDataAtIndex(relType);
707
- const targetId = getTargetIdFromRelationId(relType);
708
- relations.push([targetId, data === MISSING_COMPONENT ? void 0 : data]);
837
+ var CommandBuffer = class {
838
+ commands = [];
839
+ swapBuffer = [];
840
+ /** Reusable map to group commands by entity, avoids per-sync allocations */
841
+ entityCommands = /* @__PURE__ */ new Map();
842
+ executeEntityCommands;
843
+ /**
844
+ * Create a command buffer with an executor function
845
+ */
846
+ constructor(executeEntityCommands) {
847
+ this.executeEntityCommands = executeEntityCommands;
709
848
  }
710
- if (targetComponentId !== void 0) {
711
- const dfMatches = sparseStore.getRelationsForComponent(entityId, targetComponentId);
712
- for (const m of dfMatches) relations.push(m);
849
+ set(entityId, componentType, component) {
850
+ this.commands.push({
851
+ type: "set",
852
+ entityId,
853
+ componentType,
854
+ component
855
+ });
713
856
  }
714
- if (relations.length === 0) {
715
- if (!optional) {
716
- const componentId = getComponentIdFromRelationId(wildcardRelationType);
717
- throw new Error(`No matching relations found for mandatory wildcard relation component ${componentId} on entity ${entityId}`);
857
+ /**
858
+ * Remove a component from an entity (deferred)
859
+ */
860
+ remove(entityId, componentType) {
861
+ this.commands.push({
862
+ type: "delete",
863
+ entityId,
864
+ componentType
865
+ });
866
+ }
867
+ /**
868
+ * Destroy an entity (deferred)
869
+ */
870
+ delete(entityId) {
871
+ this.commands.push({
872
+ type: "destroy",
873
+ entityId
874
+ });
875
+ }
876
+ /**
877
+ * Execute all commands and clear the buffer.
878
+ * Returns the number of iterations performed (for debug stats).
879
+ */
880
+ execute() {
881
+ let iterations = 0;
882
+ while (this.commands.length > 0) {
883
+ if (iterations >= MAX_COMMAND_ITERATIONS) throw new Error("Command execution exceeded maximum iterations, possible infinite loop");
884
+ iterations++;
885
+ const currentCommands = this.commands;
886
+ this.commands = this.swapBuffer;
887
+ const entityCommands = this.entityCommands;
888
+ for (const cmd of currentCommands) {
889
+ const existing = entityCommands.get(cmd.entityId);
890
+ if (existing !== void 0) existing.push(cmd);
891
+ else entityCommands.set(cmd.entityId, [cmd]);
892
+ }
893
+ currentCommands.length = 0;
894
+ this.swapBuffer = currentCommands;
895
+ for (const [entityId, commands] of entityCommands) this.executeEntityCommands(entityId, commands);
896
+ entityCommands.clear();
718
897
  }
719
- return;
898
+ return iterations;
720
899
  }
721
- return optional ? { value: relations } : relations;
900
+ /**
901
+ * Get current commands (for testing)
902
+ */
903
+ getCommands() {
904
+ return [...this.commands];
905
+ }
906
+ /**
907
+ * Clear all commands
908
+ */
909
+ clear() {
910
+ this.commands = [];
911
+ }
912
+ };
913
+ //#endregion
914
+ //#region src/types/index.ts
915
+ function isOptionalEntityId(type) {
916
+ return typeof type === "object" && type !== null && "optional" in type;
917
+ }
918
+ //#endregion
919
+ //#region src/component/type-utils.ts
920
+ /**
921
+ * Normalize component type collections into a stable ascending order.
922
+ * This keeps cache keys and archetype signatures deterministic.
923
+ */
924
+ function normalizeComponentTypes(componentTypes) {
925
+ return [...componentTypes].sort((a, b) => a - b);
722
926
  }
927
+ //#endregion
928
+ //#region src/utils/utils.ts
723
929
  /**
724
- * Build regular component value from data source
930
+ * Utility functions for ECS library
725
931
  */
726
- function buildRegularComponentValue(dataSource, entityIndex, optional) {
727
- if (dataSource === void 0) {
728
- if (optional) return void 0;
729
- throw new Error(`Component data not found for mandatory component type`);
730
- }
731
- const data = dataSource[entityIndex];
732
- const result = data === MISSING_COMPONENT ? void 0 : data;
733
- return optional ? { value: result } : result;
734
- }
735
932
  /**
736
- * Build a single component value based on its type
933
+ * Get a value from cache or compute and cache it if not present
934
+ * @param cache The cache map
935
+ * @param key The cache key
936
+ * @param compute Function to compute the value if not cached (may have side effects)
937
+ * @returns The cached or computed value
737
938
  */
738
- function buildSingleComponent(compType, dataSource, entityIndex, entityId, getComponentData, sparseRelations) {
739
- const optional = isOptionalEntityId(compType);
740
- const actualType = optional ? compType.optional : compType;
741
- if (getIdType(actualType) === "wildcard-relation") return buildWildcardRelationValue(actualType, dataSource, (relType) => getComponentData(relType)[entityIndex], sparseRelations, entityId, optional);
742
- else return buildRegularComponentValue(dataSource, entityIndex, optional);
939
+ function getOrCompute(cache, key, compute) {
940
+ let value = cache.get(key);
941
+ if (value === void 0) {
942
+ value = compute();
943
+ cache.set(key, value);
944
+ }
945
+ return value;
743
946
  }
744
947
  //#endregion
745
948
  //#region src/archetype/archetype.ts
@@ -957,424 +1160,202 @@ var Archetype = class {
957
1160
  if (this.componentTypeSet.has(componentType)) {
958
1161
  const data = this.getComponentData(componentType)[index];
959
1162
  if (data === MISSING_COMPONENT) throw new Error(`Component type ${componentType} not found for entity ${entityId}`);
960
- return data;
961
- }
962
- if (this.sparseRelations.getValue(entityId, componentType) !== void 0 || this.sparseRelations.getAllForEntity(entityId).some(([t]) => t === componentType)) return this.sparseRelations.getValue(entityId, componentType);
963
- throw new Error(`Component type ${componentType} not found for entity ${entityId}`);
964
- }
965
- getOptional(entityId, componentType) {
966
- const index = this.entityToIndex.get(entityId);
967
- if (index === void 0) throw new Error(`Entity ${entityId} is not in this archetype`);
968
- if (this.componentTypeSet.has(componentType)) {
969
- const data = this.getComponentData(componentType)[index];
970
- if (data === MISSING_COMPONENT) return void 0;
971
- return { value: data };
972
- }
973
- const value = this.sparseRelations.getValue(entityId, componentType);
974
- if (value !== void 0) return { value };
975
- if (this.sparseRelations.getAllForEntity(entityId).some(([t]) => t === componentType)) return { value: this.sparseRelations.getValue(entityId, componentType) };
976
- }
977
- set(entityId, componentType, data) {
978
- const index = this.entityToIndex.get(entityId);
979
- if (index === void 0) throw new Error(`Entity ${entityId} is not in this archetype`);
980
- if (this.componentData.has(componentType)) {
981
- this.getComponentData(componentType)[index] = data;
982
- return;
983
- }
984
- const detailedType = getDetailedIdType(componentType);
985
- if (isRelationType(detailedType) && isSparseComponent(detailedType.componentId)) {
986
- this.sparseRelations.setValue(entityId, componentType, data);
987
- return;
988
- }
989
- throw new Error(`Component type ${componentType} is not in this archetype`);
990
- }
991
- getEntities() {
992
- return this.entities;
993
- }
994
- getEntityToIndexMap() {
995
- return this.entityToIndex;
996
- }
997
- getComponentData(componentType) {
998
- const data = this.componentData.get(componentType);
999
- if (!data) throw new Error(`Component type ${componentType} is not in this archetype`);
1000
- return data;
1001
- }
1002
- getOptionalComponentData(componentType) {
1003
- return this.componentData.get(componentType);
1004
- }
1005
- getCachedComponentDataSources(componentTypes) {
1006
- const cacheKey = buildCacheKey(componentTypes);
1007
- return getOrCompute(this.componentDataSourcesCache, cacheKey, () => componentTypes.map((compType) => this.getComponentDataSource(compType)));
1008
- }
1009
- getComponentDataSource(compType) {
1010
- const optional = isOptionalEntityId(compType);
1011
- const actualType = optional ? compType.optional : compType;
1012
- if (getIdType(actualType) === "wildcard-relation") {
1013
- const componentId = getComponentIdFromRelationId(actualType);
1014
- return getWildcardRelationDataSource(this.componentTypes, componentId, optional);
1015
- }
1016
- return optional ? this.getOptionalComponentData(actualType) : this.getComponentData(actualType);
1017
- }
1018
- buildComponentsForIndex(componentTypes, componentDataSources, entityIndex, entityId) {
1019
- return componentDataSources.map((dataSource, i) => buildSingleComponent(componentTypes[i], dataSource, entityIndex, entityId, (type) => this.getComponentData(type), this.sparseRelations));
1020
- }
1021
- getEntitiesWithComponents(componentTypes) {
1022
- const result = [];
1023
- this.appendEntitiesWithComponents(componentTypes, result);
1024
- return result;
1025
- }
1026
- appendEntitiesWithComponents(componentTypes, result) {
1027
- this.forEachWithComponents(componentTypes, (entity, ...components) => {
1028
- result.push({
1029
- entity,
1030
- components
1031
- });
1032
- });
1033
- }
1034
- *iterateWithComponents(componentTypes) {
1035
- const componentDataSources = this.getCachedComponentDataSources(componentTypes);
1036
- for (let entityIndex = 0; entityIndex < this.entities.length; entityIndex++) {
1037
- const entity = this.entities[entityIndex];
1038
- yield [entity, ...this.buildComponentsForIndex(componentTypes, componentDataSources, entityIndex, entity)];
1039
- }
1040
- }
1041
- forEachWithComponents(componentTypes, callback) {
1042
- const componentDataSources = this.getCachedComponentDataSources(componentTypes);
1043
- for (let entityIndex = 0; entityIndex < this.entities.length; entityIndex++) {
1044
- const entity = this.entities[entityIndex];
1045
- callback(entity, ...this.buildComponentsForIndex(componentTypes, componentDataSources, entityIndex, entity));
1046
- }
1047
- }
1048
- forEach(callback) {
1049
- for (let i = 0; i < this.entities.length; i++) {
1050
- const entity = this.entities[i];
1051
- const components = /* @__PURE__ */ new Map();
1052
- for (const componentType of this.componentTypes) {
1053
- const data = this.getComponentData(componentType)[i];
1054
- components.set(componentType, data === MISSING_COMPONENT ? void 0 : data);
1055
- }
1056
- const sparseTuples = this.sparseRelations.getAllForEntity(entity);
1057
- for (const [componentType, data] of sparseTuples) components.set(componentType, data);
1058
- callback(entity, components);
1059
- }
1060
- }
1061
- hasRelationWithComponentId(componentId) {
1062
- for (const componentType of this.componentTypes) {
1063
- const detailedType = getDetailedIdType(componentType);
1064
- if (isRelationType(detailedType) && detailedType.componentId === componentId) return true;
1065
- }
1066
- for (const entityId of this.entities) if (this.sparseRelations.getRelationsForComponent(entityId, componentId).length > 0) return true;
1067
- return false;
1068
- }
1069
- };
1070
- //#endregion
1071
- //#region src/archetype/store.ts
1072
- /**
1073
- * Production implementation of SparseStore.
1074
- *
1075
- * Internal layout (optimized):
1076
- * - byComponent: baseComponentId → (entityId → RelationEntry)
1077
- * RelationEntry uses a single-value form for the common exclusive case (1 target),
1078
- * avoiding Map allocation for the vast majority of usage.
1079
- * - entityIndex: entityId → Set<baseComponentId>
1080
- * Lightweight reverse index.
1081
- */
1082
- var SparseStoreImpl = class {
1083
- /**
1084
- * Primary storage, keyed by the base relation component ID.
1085
- */
1086
- byComponent = /* @__PURE__ */ new Map();
1087
- /**
1088
- * Reverse index: which base component kinds an entity participates in.
1089
- * Only required to support getAllForEntity and deleteEntity efficiently.
1090
- * The primary storage (byComponent) is deliberately not optimized for these operations.
1091
- */
1092
- entityIndex = /* @__PURE__ */ new Map();
1093
- getValue(entityId, relationType) {
1094
- const componentId = getComponentIdFromRelationId(relationType);
1095
- if (componentId === void 0) return void 0;
1096
- const entities = this.byComponent.get(componentId);
1097
- if (!entities) return void 0;
1098
- const entry = entities.get(entityId);
1099
- if (!entry) return void 0;
1100
- const targetId = getTargetIdFromRelationId(relationType);
1101
- if (entry.type === "single") return entry.target === targetId ? entry.data : void 0;
1102
- else {
1103
- const item = entry.targets.get(targetId);
1104
- return item ? item.data : void 0;
1105
- }
1106
- }
1107
- setValue(entityId, relationType, data) {
1108
- const componentId = getComponentIdFromRelationId(relationType);
1109
- if (componentId === void 0) throw new Error("setValue called with a non-relation type on SparseStore");
1110
- let entities = this.byComponent.get(componentId);
1111
- if (!entities) {
1112
- entities = /* @__PURE__ */ new Map();
1113
- this.byComponent.set(componentId, entities);
1114
- }
1115
- const targetId = getTargetIdFromRelationId(relationType);
1116
- let entry = entities.get(entityId);
1117
- if (!entry) {
1118
- entry = {
1119
- type: "single",
1120
- relationType,
1121
- target: targetId,
1122
- data
1123
- };
1124
- entities.set(entityId, entry);
1125
- } else if (entry.type === "single") if (entry.target === targetId) {
1126
- entry.data = data;
1127
- entry.relationType = relationType;
1128
- } else {
1129
- const targets = /* @__PURE__ */ new Map();
1130
- targets.set(entry.target, {
1131
- relationType: entry.relationType,
1132
- data: entry.data
1133
- });
1134
- targets.set(targetId, {
1135
- relationType,
1136
- data
1137
- });
1138
- entities.set(entityId, {
1139
- type: "multi",
1140
- targets
1141
- });
1142
- }
1143
- else entry.targets.set(targetId, {
1144
- relationType,
1145
- data
1146
- });
1147
- let components = this.entityIndex.get(entityId);
1148
- if (!components) {
1149
- components = /* @__PURE__ */ new Set();
1150
- this.entityIndex.set(entityId, components);
1151
- }
1152
- components.add(componentId);
1153
- }
1154
- deleteValue(entityId, relationType) {
1155
- const componentId = getComponentIdFromRelationId(relationType);
1156
- if (componentId === void 0) return false;
1157
- const entities = this.byComponent.get(componentId);
1158
- if (!entities) return false;
1159
- const entry = entities.get(entityId);
1160
- if (!entry) return false;
1161
- const targetId = getTargetIdFromRelationId(relationType);
1162
- let existed = false;
1163
- if (entry.type === "single") {
1164
- if (entry.target === targetId) {
1165
- existed = true;
1166
- entities.delete(entityId);
1167
- }
1168
- } else {
1169
- existed = entry.targets.delete(targetId);
1170
- if (entry.targets.size === 0) entities.delete(entityId);
1171
- else if (entry.targets.size === 1) {
1172
- const [first] = entry.targets.entries();
1173
- const [t, item] = first;
1174
- entities.set(entityId, {
1175
- type: "single",
1176
- relationType: item.relationType,
1177
- target: t,
1178
- data: item.data
1179
- });
1180
- }
1163
+ return data;
1181
1164
  }
1182
- if (!entities.has(entityId) && entities.size === 0) this.byComponent.delete(componentId);
1183
- const components = this.entityIndex.get(entityId);
1184
- if (components && !entities.has(entityId)) {
1185
- components.delete(componentId);
1186
- if (components.size === 0) this.entityIndex.delete(entityId);
1165
+ if (this.sparseRelations.getValue(entityId, componentType) !== void 0 || this.sparseRelations.getAllForEntity(entityId).some(([t]) => t === componentType)) return this.sparseRelations.getValue(entityId, componentType);
1166
+ throw new Error(`Component type ${componentType} not found for entity ${entityId}`);
1167
+ }
1168
+ getOptional(entityId, componentType) {
1169
+ const index = this.entityToIndex.get(entityId);
1170
+ if (index === void 0) throw new Error(`Entity ${entityId} is not in this archetype`);
1171
+ if (this.componentTypeSet.has(componentType)) {
1172
+ const data = this.getComponentData(componentType)[index];
1173
+ if (data === MISSING_COMPONENT) return void 0;
1174
+ return { value: data };
1187
1175
  }
1188
- return existed;
1176
+ const value = this.sparseRelations.getValue(entityId, componentType);
1177
+ if (value !== void 0) return { value };
1178
+ if (this.sparseRelations.getAllForEntity(entityId).some(([t]) => t === componentType)) return { value: this.sparseRelations.getValue(entityId, componentType) };
1189
1179
  }
1190
- hasAnyForComponent(componentId) {
1191
- const entities = this.byComponent.get(componentId);
1192
- return entities !== void 0 && entities.size > 0;
1180
+ set(entityId, componentType, data) {
1181
+ const index = this.entityToIndex.get(entityId);
1182
+ if (index === void 0) throw new Error(`Entity ${entityId} is not in this archetype`);
1183
+ if (this.componentData.has(componentType)) {
1184
+ this.getComponentData(componentType)[index] = data;
1185
+ return;
1186
+ }
1187
+ const detailedType = getDetailedIdType(componentType);
1188
+ if (isRelationType(detailedType) && isSparseComponent(detailedType.componentId)) {
1189
+ this.sparseRelations.setValue(entityId, componentType, data);
1190
+ return;
1191
+ }
1192
+ throw new Error(`Component type ${componentType} is not in this archetype`);
1193
1193
  }
1194
- getRelationsForComponent(entityId, componentId) {
1195
- const result = [];
1196
- const entities = this.byComponent.get(componentId);
1197
- if (!entities) return result;
1198
- const entry = entities.get(entityId);
1199
- if (!entry) return result;
1200
- if (entry.type === "single") result.push([entry.target, entry.data]);
1201
- else for (const [target, item] of entry.targets) result.push([target, item.data]);
1202
- return result;
1194
+ getEntities() {
1195
+ return this.entities;
1203
1196
  }
1204
- getAllForEntity(entityId) {
1205
- const components = this.entityIndex.get(entityId);
1206
- if (!components || components.size === 0) return [];
1207
- const result = [];
1208
- for (const componentId of components) {
1209
- const entry = this.byComponent.get(componentId)?.get(entityId);
1210
- if (entry) if (entry.type === "single") result.push([entry.relationType, entry.data]);
1211
- else for (const item of entry.targets.values()) result.push([item.relationType, item.data]);
1212
- }
1213
- return result;
1197
+ getEntityToIndexMap() {
1198
+ return this.entityToIndex;
1214
1199
  }
1215
- deleteEntity(entityId) {
1216
- const components = this.entityIndex.get(entityId);
1217
- if (!components) return;
1218
- for (const componentId of components) {
1219
- const entities = this.byComponent.get(componentId);
1220
- if (entities) {
1221
- entities.delete(entityId);
1222
- if (entities.size === 0) this.byComponent.delete(componentId);
1223
- }
1224
- }
1225
- this.entityIndex.delete(entityId);
1200
+ getComponentData(componentType) {
1201
+ const data = this.componentData.get(componentType);
1202
+ if (!data) throw new Error(`Component type ${componentType} is not in this archetype`);
1203
+ return data;
1226
1204
  }
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);
1205
+ getOptionalComponentData(componentType) {
1206
+ return this.componentData.get(componentType);
1207
+ }
1208
+ getCachedComponentDataSources(componentTypes) {
1209
+ const cacheKey = buildCacheKey(componentTypes);
1210
+ return getOrCompute(this.componentDataSourcesCache, cacheKey, () => componentTypes.map((compType) => this.getComponentDataSource(compType)));
1211
+ }
1212
+ getComponentDataSource(compType) {
1213
+ const optional = isOptionalEntityId(compType);
1214
+ const actualType = optional ? compType.optional : compType;
1215
+ if (getIdType(actualType) === "wildcard-relation") {
1216
+ const componentId = getComponentIdFromRelationId(actualType);
1217
+ return getWildcardRelationDataSource(this.componentTypes, componentId, optional);
1232
1218
  }
1233
- return result;
1219
+ return optional ? this.getOptionalComponentData(actualType) : this.getComponentData(actualType);
1234
1220
  }
1235
- };
1236
- //#endregion
1237
- //#region src/commands/buffer.ts
1238
- /**
1239
- * Maximum number of command buffer execution iterations to prevent infinite loops
1240
- */
1241
- const MAX_COMMAND_ITERATIONS = 100;
1242
- /**
1243
- * Command buffer for deferred structural changes
1244
- */
1245
- var CommandBuffer = class {
1246
- commands = [];
1247
- swapBuffer = [];
1248
- /** Reusable map to group commands by entity, avoids per-sync allocations */
1249
- entityCommands = /* @__PURE__ */ new Map();
1250
- executeEntityCommands;
1251
- /**
1252
- * Create a command buffer with an executor function
1253
- */
1254
- constructor(executeEntityCommands) {
1255
- this.executeEntityCommands = executeEntityCommands;
1221
+ buildComponentsForIndex(componentTypes, componentDataSources, entityIndex, entityId) {
1222
+ return componentDataSources.map((dataSource, i) => buildSingleComponent(componentTypes[i], dataSource, entityIndex, entityId, (type) => this.getComponentData(type), this.sparseRelations));
1256
1223
  }
1257
- set(entityId, componentType, component) {
1258
- this.commands.push({
1259
- type: "set",
1260
- entityId,
1261
- componentType,
1262
- component
1263
- });
1224
+ getEntitiesWithComponents(componentTypes) {
1225
+ const result = [];
1226
+ this.appendEntitiesWithComponents(componentTypes, result);
1227
+ return result;
1264
1228
  }
1265
- /**
1266
- * Remove a component from an entity (deferred)
1267
- */
1268
- remove(entityId, componentType) {
1269
- this.commands.push({
1270
- type: "delete",
1271
- entityId,
1272
- componentType
1229
+ appendEntitiesWithComponents(componentTypes, result) {
1230
+ this.forEachWithComponents(componentTypes, (entity, ...components) => {
1231
+ result.push({
1232
+ entity,
1233
+ components
1234
+ });
1273
1235
  });
1274
1236
  }
1275
- /**
1276
- * Destroy an entity (deferred)
1277
- */
1278
- delete(entityId) {
1279
- this.commands.push({
1280
- type: "destroy",
1281
- entityId
1282
- });
1237
+ *iterateWithComponents(componentTypes) {
1238
+ const componentDataSources = this.getCachedComponentDataSources(componentTypes);
1239
+ for (let entityIndex = 0; entityIndex < this.entities.length; entityIndex++) {
1240
+ const entity = this.entities[entityIndex];
1241
+ yield [entity, ...this.buildComponentsForIndex(componentTypes, componentDataSources, entityIndex, entity)];
1242
+ }
1283
1243
  }
1284
- /**
1285
- * Execute all commands and clear the buffer.
1286
- * Returns the number of iterations performed (for debug stats).
1287
- */
1288
- execute() {
1289
- let iterations = 0;
1290
- while (this.commands.length > 0) {
1291
- if (iterations >= MAX_COMMAND_ITERATIONS) throw new Error("Command execution exceeded maximum iterations, possible infinite loop");
1292
- iterations++;
1293
- const currentCommands = this.commands;
1294
- this.commands = this.swapBuffer;
1295
- const entityCommands = this.entityCommands;
1296
- for (const cmd of currentCommands) {
1297
- const existing = entityCommands.get(cmd.entityId);
1298
- if (existing !== void 0) existing.push(cmd);
1299
- else entityCommands.set(cmd.entityId, [cmd]);
1300
- }
1301
- currentCommands.length = 0;
1302
- this.swapBuffer = currentCommands;
1303
- for (const [entityId, commands] of entityCommands) this.executeEntityCommands(entityId, commands);
1304
- entityCommands.clear();
1244
+ forEachWithComponents(componentTypes, callback) {
1245
+ const componentDataSources = this.getCachedComponentDataSources(componentTypes);
1246
+ for (let entityIndex = 0; entityIndex < this.entities.length; entityIndex++) {
1247
+ const entity = this.entities[entityIndex];
1248
+ callback(entity, ...this.buildComponentsForIndex(componentTypes, componentDataSources, entityIndex, entity));
1305
1249
  }
1306
- return iterations;
1307
1250
  }
1308
- /**
1309
- * Get current commands (for testing)
1310
- */
1311
- getCommands() {
1312
- return [...this.commands];
1251
+ forEach(callback) {
1252
+ for (let i = 0; i < this.entities.length; i++) {
1253
+ const entity = this.entities[i];
1254
+ const components = /* @__PURE__ */ new Map();
1255
+ for (const componentType of this.componentTypes) {
1256
+ const data = this.getComponentData(componentType)[i];
1257
+ components.set(componentType, data === MISSING_COMPONENT ? void 0 : data);
1258
+ }
1259
+ const sparseTuples = this.sparseRelations.getAllForEntity(entity);
1260
+ for (const [componentType, data] of sparseTuples) components.set(componentType, data);
1261
+ callback(entity, components);
1262
+ }
1313
1263
  }
1314
- /**
1315
- * Clear all commands
1316
- */
1317
- clear() {
1318
- this.commands = [];
1264
+ hasRelationWithComponentId(componentId) {
1265
+ for (const componentType of this.componentTypes) {
1266
+ const detailedType = getDetailedIdType(componentType);
1267
+ if (isRelationType(detailedType) && detailedType.componentId === componentId) return true;
1268
+ }
1269
+ for (const entityId of this.entities) if (this.sparseRelations.getRelationsForComponent(entityId, componentId).length > 0) return true;
1270
+ return false;
1319
1271
  }
1320
1272
  };
1321
1273
  //#endregion
1322
- //#region src/commands/changeset.ts
1274
+ //#region src/archetype/helpers.ts
1323
1275
  /**
1324
- * @internal Represents a set of component changes to be applied to an entity
1276
+ * Check if a components map has any wildcard relations matching a component ID
1277
+ * @param components - Component entity's components map
1278
+ * @param wildcardComponentId - The component ID to match
1279
+ * @returns True if at least one matching relation exists
1325
1280
  */
1326
- var ComponentChangeset = class {
1327
- adds = /* @__PURE__ */ new Map();
1328
- removes = /* @__PURE__ */ new Set();
1329
- /**
1330
- * Add a component to the changeset
1331
- */
1332
- set(componentType, component) {
1333
- this.adds.set(componentType, component);
1334
- this.removes.delete(componentType);
1335
- }
1336
- /**
1337
- * Remove a component from the changeset
1338
- */
1339
- delete(componentType) {
1340
- this.removes.add(componentType);
1341
- this.adds.delete(componentType);
1281
+ function hasWildcardRelation(components, wildcardComponentId) {
1282
+ for (const relId of components.keys()) if (isRelationId(relId)) {
1283
+ if (getComponentIdFromRelationId(relId) === wildcardComponentId) return true;
1342
1284
  }
1343
- /**
1344
- * Check if the changeset has any changes
1345
- */
1346
- hasChanges() {
1347
- return this.adds.size > 0 || this.removes.size > 0;
1285
+ return false;
1286
+ }
1287
+ /**
1288
+ * Check if a detailed type represents a relation (entity or component)
1289
+ */
1290
+ function isRelationType(detailedType) {
1291
+ return detailedType.type === "entity-relation" || detailedType.type === "component-relation";
1292
+ }
1293
+ /**
1294
+ * Check if a component type matches a given component ID for relations
1295
+ */
1296
+ function matchesRelationComponentId(componentType, componentId) {
1297
+ const detailedType = getDetailedIdType(componentType);
1298
+ return isRelationType(detailedType) && detailedType.componentId === componentId;
1299
+ }
1300
+ /**
1301
+ * Build cache key for component types
1302
+ */
1303
+ function buildCacheKey(componentTypes) {
1304
+ return componentTypes.map((id) => isOptionalEntityId(id) ? `opt(${id.optional})` : `${id}`).join(",");
1305
+ }
1306
+ /**
1307
+ * Get data source for wildcard relations from component types
1308
+ */
1309
+ function getWildcardRelationDataSource(componentTypes, componentId, optional) {
1310
+ const matchingRelations = componentTypes.filter((ct) => matchesRelationComponentId(ct, componentId));
1311
+ return optional ? matchingRelations.length > 0 ? matchingRelations : void 0 : matchingRelations;
1312
+ }
1313
+ /**
1314
+ * Build wildcard relation value from matching relations.
1315
+ * Receives the SparseStore directly for efficient per-component lookups.
1316
+ */
1317
+ function buildWildcardRelationValue(wildcardRelationType, matchingRelations, getDataAtIndex, sparseStore, entityId, optional) {
1318
+ const relations = [];
1319
+ const targetComponentId = getComponentIdFromRelationId(wildcardRelationType);
1320
+ for (const relType of matchingRelations || []) {
1321
+ const data = getDataAtIndex(relType);
1322
+ const targetId = getTargetIdFromRelationId(relType);
1323
+ relations.push([targetId, data === MISSING_COMPONENT ? void 0 : data]);
1348
1324
  }
1349
- /**
1350
- * Clear all changes
1351
- */
1352
- clear() {
1353
- this.adds.clear();
1354
- this.removes.clear();
1325
+ if (targetComponentId !== void 0) {
1326
+ const dfMatches = sparseStore.getRelationsForComponent(entityId, targetComponentId);
1327
+ for (const m of dfMatches) relations.push(m);
1355
1328
  }
1356
- /**
1357
- * Merge another changeset into this one
1358
- */
1359
- merge(other) {
1360
- for (const [componentType, component] of other.adds) {
1361
- this.adds.set(componentType, component);
1362
- this.removes.delete(componentType);
1363
- }
1364
- for (const componentType of other.removes) {
1365
- this.removes.add(componentType);
1366
- this.adds.delete(componentType);
1329
+ if (relations.length === 0) {
1330
+ if (!optional) {
1331
+ const componentId = getComponentIdFromRelationId(wildcardRelationType);
1332
+ throw new Error(`No matching relations found for mandatory wildcard relation component ${componentId} on entity ${entityId}`);
1367
1333
  }
1334
+ return;
1368
1335
  }
1369
- /**
1370
- * Apply the changeset to existing components and return the final state
1371
- */
1372
- applyTo(existingComponents) {
1373
- for (const componentType of this.removes) existingComponents.delete(componentType);
1374
- for (const [componentType, component] of this.adds) existingComponents.set(componentType, component);
1375
- return existingComponents;
1336
+ return optional ? { value: relations } : relations;
1337
+ }
1338
+ /**
1339
+ * Build regular component value from data source
1340
+ */
1341
+ function buildRegularComponentValue(dataSource, entityIndex, optional) {
1342
+ if (dataSource === void 0) {
1343
+ if (optional) return void 0;
1344
+ throw new Error(`Component data not found for mandatory component type`);
1376
1345
  }
1377
- };
1346
+ const data = dataSource[entityIndex];
1347
+ const result = data === MISSING_COMPONENT ? void 0 : data;
1348
+ return optional ? { value: result } : result;
1349
+ }
1350
+ /**
1351
+ * Build a single component value based on its type
1352
+ */
1353
+ function buildSingleComponent(compType, dataSource, entityIndex, entityId, getComponentData, sparseRelations) {
1354
+ const optional = isOptionalEntityId(compType);
1355
+ const actualType = optional ? compType.optional : compType;
1356
+ if (getIdType(actualType) === "wildcard-relation") return buildWildcardRelationValue(actualType, dataSource, (relType) => getComponentData(relType)[entityIndex], sparseRelations, entityId, optional);
1357
+ else return buildRegularComponentValue(dataSource, entityIndex, optional);
1358
+ }
1378
1359
  //#endregion
1379
1360
  //#region src/component/entity-store.ts
1380
1361
  /**
@@ -1934,78 +1915,299 @@ function maybeRemoveWildcardMarker(entityId, archetype, removedComponentType, co
1934
1915
  if (changeset.removes.has(otherComponentType)) continue;
1935
1916
  if (getComponentIdFromRelationId(otherComponentType) === componentId) return;
1936
1917
  }
1937
- for (const addedType of changeset.adds.keys()) {
1938
- if (addedType === removedComponentType) continue;
1939
- if (getComponentIdFromRelationId(addedType) === componentId) return;
1918
+ for (const addedType of changeset.adds.keys()) {
1919
+ if (addedType === removedComponentType) continue;
1920
+ if (getComponentIdFromRelationId(addedType) === componentId) return;
1921
+ }
1922
+ changeset.delete(wildcardMarker);
1923
+ }
1924
+ function hasEntityComponent(archetype, entityId, componentType) {
1925
+ if (archetype.componentTypeSet.has(componentType)) return true;
1926
+ return archetype.getEntitySparseRelations(entityId)?.has(componentType) ?? false;
1927
+ }
1928
+ function pruneMissingRemovals(changeset, archetype, entityId) {
1929
+ let toPrune;
1930
+ for (const componentType of changeset.removes) if (!hasEntityComponent(archetype, entityId, componentType)) {
1931
+ if (toPrune === void 0) toPrune = [];
1932
+ toPrune.push(componentType);
1933
+ }
1934
+ if (toPrune !== void 0) for (const componentType of toPrune) changeset.removes.delete(componentType);
1935
+ }
1936
+ function hasArchetypeStructuralChange(changeset, currentArchetype) {
1937
+ for (const componentType of changeset.removes) if (!isSparseRelation(componentType) && currentArchetype.componentTypeSet.has(componentType)) return true;
1938
+ for (const componentType of changeset.adds.keys()) if (!isSparseRelation(componentType) && !currentArchetype.componentTypeSet.has(componentType)) return true;
1939
+ return false;
1940
+ }
1941
+ function buildFinalRegularComponentTypes(currentArchetype, changeset) {
1942
+ const finalRegularTypes = new Set(currentArchetype.componentTypes);
1943
+ for (const componentType of changeset.removes) if (!isSparseRelation(componentType)) finalRegularTypes.delete(componentType);
1944
+ for (const [componentType] of changeset.adds) if (!isSparseRelation(componentType)) finalRegularTypes.add(componentType);
1945
+ return Array.from(finalRegularTypes);
1946
+ }
1947
+ function applyChangeset(ctx, entityId, currentArchetype, changeset, entityToArchetype, removedComponents) {
1948
+ pruneMissingRemovals(changeset, currentArchetype, entityId);
1949
+ if (hasArchetypeStructuralChange(changeset, currentArchetype)) {
1950
+ const finalRegularTypes = buildFinalRegularComponentTypes(currentArchetype, changeset);
1951
+ const newArchetype = ctx.ensureArchetype(finalRegularTypes);
1952
+ const currentComponents = currentArchetype.removeEntity(entityId);
1953
+ if (removedComponents !== null) for (const componentType of changeset.removes) removedComponents.set(componentType, currentComponents.get(componentType));
1954
+ newArchetype.addEntity(entityId, changeset.applyTo(currentComponents));
1955
+ entityToArchetype.set(entityId, newArchetype);
1956
+ return newArchetype;
1957
+ }
1958
+ if (removedComponents !== null) applySparseChanges(ctx.sparseStore, entityId, changeset, removedComponents);
1959
+ else applySparseChangesNoHooks(ctx.sparseStore, entityId, changeset);
1960
+ for (const [componentType, component] of changeset.adds) {
1961
+ if (isSparseRelation(componentType)) continue;
1962
+ currentArchetype.set(entityId, componentType, component);
1963
+ }
1964
+ return currentArchetype;
1965
+ }
1966
+ function applySparseChanges(sparseStore, entityId, changeset, removedComponents) {
1967
+ for (const componentType of changeset.removes) if (isSparseRelation(componentType)) {
1968
+ const removedValue = sparseStore.getValue(entityId, componentType);
1969
+ if (removedValue !== void 0 || sparseStore.getAllForEntity(entityId).some(([t]) => t === componentType)) removedComponents.set(componentType, removedValue);
1970
+ sparseStore.deleteValue(entityId, componentType);
1971
+ }
1972
+ for (const [componentType, component] of changeset.adds) if (isSparseRelation(componentType)) sparseStore.setValue(entityId, componentType, component);
1973
+ }
1974
+ function applySparseChangesNoHooks(sparseStore, entityId, changeset) {
1975
+ for (const componentType of changeset.removes) if (isSparseRelation(componentType)) sparseStore.deleteValue(entityId, componentType);
1976
+ for (const [componentType, component] of changeset.adds) if (isSparseRelation(componentType)) sparseStore.setValue(entityId, componentType, component);
1977
+ }
1978
+ function filterRegularComponentTypes(componentTypes) {
1979
+ const regularTypes = [];
1980
+ for (const componentType of componentTypes) {
1981
+ if (isSparseWildcard(componentType)) {
1982
+ regularTypes.push(componentType);
1983
+ continue;
1984
+ }
1985
+ if (isSparseRelation(componentType)) continue;
1986
+ regularTypes.push(componentType);
1987
+ }
1988
+ return regularTypes;
1989
+ }
1990
+ //#endregion
1991
+ //#region src/world/archetype-manager.ts
1992
+ /**
1993
+ * Encapsulates all archetype storage, indexing, creation, removal, and reverse
1994
+ * referencing logic that was previously scattered as private methods + maps
1995
+ * directly on the World class.
1996
+ *
1997
+ * Responsibilities:
1998
+ * - Archetype memoization by signature
1999
+ * - Component-type reverse index (archetypesByComponent)
2000
+ * - Entity → current Archetype map
2001
+ * - Reverse "who references this entity via component/relation" index
2002
+ * - Creation + removal with proper notifications to QueryRegistry + hook matching
2003
+ * - Cleanup of empty archetypes after entity cascades
2004
+ *
2005
+ * This extraction shrinks World while keeping the same behavior and hot-path characteristics.
2006
+ */
2007
+ var ArchetypeManager = class {
2008
+ archetypes = [];
2009
+ archetypeBySignature = /* @__PURE__ */ new Map();
2010
+ entityToArchetype = /* @__PURE__ */ new Map();
2011
+ archetypesByComponent = /* @__PURE__ */ new Map();
2012
+ entityToReferencingArchetypes = /* @__PURE__ */ new Map();
2013
+ sparseStore;
2014
+ ctx;
2015
+ constructor(ctx, sparseStore) {
2016
+ this.ctx = ctx;
2017
+ this.sparseStore = sparseStore;
2018
+ }
2019
+ /** Primary entry point — memoized archetype creation/lookup. */
2020
+ ensureArchetype(componentTypes) {
2021
+ const sortedTypes = normalizeComponentTypes(filterRegularComponentTypes(componentTypes));
2022
+ const hashKey = this.createArchetypeSignature(sortedTypes);
2023
+ return getOrCompute(this.archetypeBySignature, hashKey, () => this.createNewArchetype(sortedTypes));
2024
+ }
2025
+ getArchetypeForEntity(entityId) {
2026
+ return this.entityToArchetype.get(entityId);
2027
+ }
2028
+ setEntityToArchetype(entityId, archetype) {
2029
+ this.entityToArchetype.set(entityId, archetype);
2030
+ }
2031
+ getMatchingArchetypes(componentTypes) {
2032
+ if (componentTypes.length === 0) return [...this.archetypes];
2033
+ const regularComponents = [];
2034
+ const wildcardRelations = [];
2035
+ for (const componentType of componentTypes) if (isWildcardRelationId(componentType)) {
2036
+ const componentId = getComponentIdFromRelationId(componentType);
2037
+ if (componentId !== void 0) wildcardRelations.push({
2038
+ componentId,
2039
+ relationId: componentType
2040
+ });
2041
+ } else regularComponents.push(componentType);
2042
+ let matchingArchetypes = this.getArchetypesWithComponents(regularComponents);
2043
+ for (const { componentId, relationId } of wildcardRelations) {
2044
+ const markerSet = this.archetypesByComponent.get(relationId);
2045
+ const archetypesWithMarker = markerSet ? Array.from(markerSet) : [];
2046
+ matchingArchetypes = matchingArchetypes.length === 0 ? archetypesWithMarker : matchingArchetypes.filter((a) => markerSet?.has(a) || a.hasRelationWithComponentId(componentId));
2047
+ }
2048
+ return matchingArchetypes;
2049
+ }
2050
+ getArchetypesWithComponents(componentTypes) {
2051
+ if (componentTypes.length === 0) return [...this.archetypes];
2052
+ if (componentTypes.length === 1) {
2053
+ const set = this.archetypesByComponent.get(componentTypes[0]);
2054
+ return set ? Array.from(set) : [];
2055
+ }
2056
+ const sets = componentTypes.map((type) => this.archetypesByComponent.get(type)).filter((s) => s !== void 0 && s.size > 0).sort((a, b) => a.size - b.size);
2057
+ if (sets.length === 0) return [];
2058
+ if (sets.length < componentTypes.length) return [];
2059
+ const smallest = sets[0];
2060
+ if (sets.length === 2) {
2061
+ const other = sets[1];
2062
+ return Array.from(smallest).filter((a) => other.has(a));
2063
+ }
2064
+ let result = new Set(smallest);
2065
+ for (let i = 1; i < sets.length; i++) {
2066
+ for (const item of result) if (!sets[i].has(item)) result.delete(item);
2067
+ if (result.size === 0) return [];
2068
+ }
2069
+ return Array.from(result);
2070
+ }
2071
+ createArchetypeSignature(componentTypes) {
2072
+ return componentTypes.join(",");
2073
+ }
2074
+ /** Deduplicated version of the original pair of methods. */
2075
+ updateReferencingIndex(componentType, archetype, isAdd) {
2076
+ const detailedType = getDetailedIdType(componentType);
2077
+ let entityId;
2078
+ if (detailedType.type === "entity") entityId = componentType;
2079
+ else if (detailedType.type === "entity-relation") entityId = detailedType.targetId;
2080
+ if (entityId !== void 0) {
2081
+ let refs = this.entityToReferencingArchetypes.get(entityId);
2082
+ if (isAdd) {
2083
+ if (!refs) {
2084
+ refs = /* @__PURE__ */ new Set();
2085
+ this.entityToReferencingArchetypes.set(entityId, refs);
2086
+ }
2087
+ refs.add(archetype);
2088
+ } else if (refs) {
2089
+ refs.delete(archetype);
2090
+ if (refs.size === 0) this.entityToReferencingArchetypes.delete(entityId);
2091
+ }
2092
+ }
2093
+ }
2094
+ createNewArchetype(componentTypes) {
2095
+ const newArchetype = new Archetype(componentTypes, this.sparseStore);
2096
+ this.archetypes.push(newArchetype);
2097
+ this.ctx.recordArchetypeCreated?.();
2098
+ for (const componentType of componentTypes) {
2099
+ let archetypes = this.archetypesByComponent.get(componentType);
2100
+ if (!archetypes) {
2101
+ archetypes = /* @__PURE__ */ new Set();
2102
+ this.archetypesByComponent.set(componentType, archetypes);
2103
+ }
2104
+ archetypes.add(newArchetype);
2105
+ this.updateReferencingIndex(componentType, newArchetype, true);
2106
+ }
2107
+ this.ctx.queryRegistry.onNewArchetype(newArchetype);
2108
+ this.updateArchetypeHookMatches(newArchetype);
2109
+ return newArchetype;
2110
+ }
2111
+ updateArchetypeHookMatches(archetype) {
2112
+ for (const entry of this.ctx.hooks) if (this.archetypeMatchesHook(archetype, entry)) {
2113
+ archetype.matchingMultiHooks.add(entry);
2114
+ if (entry.matchedArchetypes) entry.matchedArchetypes.add(archetype);
2115
+ }
2116
+ }
2117
+ archetypeMatchesHook(archetype, entry) {
2118
+ return entry.requiredComponents.every((c) => {
2119
+ if (isWildcardRelationId(c)) {
2120
+ if (isSparseWildcard(c)) return true;
2121
+ const componentId = getComponentIdFromRelationId(c);
2122
+ return componentId !== void 0 && archetype.hasRelationWithComponentId(componentId);
2123
+ }
2124
+ return archetype.componentTypeSet.has(c) || isSparseRelation(c);
2125
+ }) && matchesFilter(archetype, entry.filter);
2126
+ }
2127
+ /** Called during cascade deletion cleanup. */
2128
+ cleanupArchetypesReferencingEntity(entityId) {
2129
+ const refs = this.entityToReferencingArchetypes.get(entityId);
2130
+ if (!refs) return;
2131
+ for (const archetype of refs) if (archetype.getEntities().length === 0) this.removeArchetype(archetype);
2132
+ this.entityToReferencingArchetypes.delete(entityId);
1940
2133
  }
1941
- changeset.delete(wildcardMarker);
1942
- }
1943
- function hasEntityComponent(archetype, entityId, componentType) {
1944
- if (archetype.componentTypeSet.has(componentType)) return true;
1945
- return archetype.getEntitySparseRelations(entityId)?.has(componentType) ?? false;
1946
- }
1947
- function pruneMissingRemovals(changeset, archetype, entityId) {
1948
- let toPrune;
1949
- for (const componentType of changeset.removes) if (!hasEntityComponent(archetype, entityId, componentType)) {
1950
- if (toPrune === void 0) toPrune = [];
1951
- toPrune.push(componentType);
2134
+ removeArchetype(archetype) {
2135
+ const index = this.archetypes.indexOf(archetype);
2136
+ if (index !== -1) {
2137
+ const last = this.archetypes[this.archetypes.length - 1];
2138
+ this.archetypes[index] = last;
2139
+ this.archetypes.pop();
2140
+ }
2141
+ this.ctx.recordArchetypeRemoved?.();
2142
+ this.archetypeBySignature.delete(this.createArchetypeSignature(archetype.componentTypes));
2143
+ for (const componentType of archetype.componentTypes) {
2144
+ const archetypes = this.archetypesByComponent.get(componentType);
2145
+ if (archetypes) {
2146
+ archetypes.delete(archetype);
2147
+ if (archetypes.size === 0) this.archetypesByComponent.delete(componentType);
2148
+ }
2149
+ this.updateReferencingIndex(componentType, archetype, false);
2150
+ }
2151
+ this.ctx.queryRegistry.onArchetypeRemoved(archetype);
1952
2152
  }
1953
- if (toPrune !== void 0) for (const componentType of toPrune) changeset.removes.delete(componentType);
1954
- }
1955
- function hasArchetypeStructuralChange(changeset, currentArchetype) {
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;
1958
- return false;
1959
- }
1960
- function buildFinalRegularComponentTypes(currentArchetype, changeset) {
1961
- const finalRegularTypes = new Set(currentArchetype.componentTypes);
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);
1964
- return Array.from(finalRegularTypes);
1965
- }
1966
- function applyChangeset(ctx, entityId, currentArchetype, changeset, entityToArchetype, removedComponents) {
1967
- pruneMissingRemovals(changeset, currentArchetype, entityId);
1968
- if (hasArchetypeStructuralChange(changeset, currentArchetype)) {
1969
- const finalRegularTypes = buildFinalRegularComponentTypes(currentArchetype, changeset);
1970
- const newArchetype = ctx.ensureArchetype(finalRegularTypes);
1971
- const currentComponents = currentArchetype.removeEntity(entityId);
1972
- if (removedComponents !== null) for (const componentType of changeset.removes) removedComponents.set(componentType, currentComponents.get(componentType));
1973
- newArchetype.addEntity(entityId, changeset.applyTo(currentComponents));
1974
- entityToArchetype.set(entityId, newArchetype);
1975
- return newArchetype;
2153
+ };
2154
+ //#endregion
2155
+ //#region src/commands/changeset.ts
2156
+ /**
2157
+ * @internal Represents a set of component changes to be applied to an entity
2158
+ */
2159
+ var ComponentChangeset = class {
2160
+ adds = /* @__PURE__ */ new Map();
2161
+ removes = /* @__PURE__ */ new Set();
2162
+ /**
2163
+ * Add a component to the changeset
2164
+ */
2165
+ set(componentType, component) {
2166
+ this.adds.set(componentType, component);
2167
+ this.removes.delete(componentType);
1976
2168
  }
1977
- if (removedComponents !== null) applySparseChanges(ctx.sparseStore, entityId, changeset, removedComponents);
1978
- else applySparseChangesNoHooks(ctx.sparseStore, entityId, changeset);
1979
- for (const [componentType, component] of changeset.adds) {
1980
- if (isSparseRelation(componentType)) continue;
1981
- currentArchetype.set(entityId, componentType, component);
2169
+ /**
2170
+ * Remove a component from the changeset
2171
+ */
2172
+ delete(componentType) {
2173
+ this.removes.add(componentType);
2174
+ this.adds.delete(componentType);
1982
2175
  }
1983
- return currentArchetype;
1984
- }
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);
2176
+ /**
2177
+ * Check if the changeset has any changes
2178
+ */
2179
+ hasChanges() {
2180
+ return this.adds.size > 0 || this.removes.size > 0;
1990
2181
  }
1991
- for (const [componentType, component] of changeset.adds) if (isSparseRelation(componentType)) sparseStore.setValue(entityId, componentType, component);
1992
- }
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);
1996
- }
1997
- function filterRegularComponentTypes(componentTypes) {
1998
- const regularTypes = [];
1999
- for (const componentType of componentTypes) {
2000
- if (isSparseWildcard(componentType)) {
2001
- regularTypes.push(componentType);
2002
- continue;
2182
+ /**
2183
+ * Clear all changes
2184
+ */
2185
+ clear() {
2186
+ this.adds.clear();
2187
+ this.removes.clear();
2188
+ }
2189
+ /**
2190
+ * Merge another changeset into this one
2191
+ */
2192
+ merge(other) {
2193
+ for (const [componentType, component] of other.adds) {
2194
+ this.adds.set(componentType, component);
2195
+ this.removes.delete(componentType);
2196
+ }
2197
+ for (const componentType of other.removes) {
2198
+ this.removes.add(componentType);
2199
+ this.adds.delete(componentType);
2003
2200
  }
2004
- if (isSparseRelation(componentType)) continue;
2005
- regularTypes.push(componentType);
2006
2201
  }
2007
- return regularTypes;
2008
- }
2202
+ /**
2203
+ * Apply the changeset to existing components and return the final state
2204
+ */
2205
+ applyTo(existingComponents) {
2206
+ for (const componentType of this.removes) existingComponents.delete(componentType);
2207
+ for (const [componentType, component] of this.adds) existingComponents.set(componentType, component);
2208
+ return existingComponents;
2209
+ }
2210
+ };
2009
2211
  //#endregion
2010
2212
  //#region src/world/hooks.ts
2011
2213
  /**
@@ -2310,8 +2512,282 @@ function untrackEntityReference(entityReferences, sourceEntityId, componentType,
2310
2512
  *
2311
2513
  * @internal
2312
2514
  */
2313
- function getEntityReferences(entityReferences, targetEntityId) {
2314
- return entityReferences.get(targetEntityId) ?? new MultiMap();
2515
+ function getEntityReferences(entityReferences, targetEntityId) {
2516
+ return entityReferences.get(targetEntityId) ?? new MultiMap();
2517
+ }
2518
+ //#endregion
2519
+ //#region src/world/command-executor.ts
2520
+ /**
2521
+ * Encapsulates the command execution pipeline, reusable changesets,
2522
+ * and related orchestration that was previously private methods + fields on World.
2523
+ *
2524
+ * Responsibilities:
2525
+ * - executeEntityCommands (routing for singletons / destroy / structural changes)
2526
+ * - applyEntityCommands (changeset processing + exclusive relations + apply + refs + hooks)
2527
+ * - removeComponentImmediate (used by cascade deletion)
2528
+ * - updateEntityReferences (keeps the reverse index in sync)
2529
+ *
2530
+ * This extraction significantly reduces World line count while preserving
2531
+ * every fast-path branch and allocation-avoidance characteristic.
2532
+ */
2533
+ var CommandExecutor = class {
2534
+ ctx;
2535
+ _changeset = new ComponentChangeset();
2536
+ _removeChangeset = new ComponentChangeset();
2537
+ _commandCtx;
2538
+ _hooksCtx;
2539
+ constructor(ctx) {
2540
+ this.ctx = ctx;
2541
+ this._commandCtx = {
2542
+ sparseStore: ctx.sparseStore,
2543
+ ensureArchetype: ctx.ensureArchetype
2544
+ };
2545
+ this._hooksCtx = {
2546
+ multiHooks: ctx.hooks,
2547
+ has: ctx.has,
2548
+ get: ctx.get,
2549
+ getOptional: ctx.getOptional
2550
+ };
2551
+ }
2552
+ /**
2553
+ * Entry point used by the CommandBuffer.
2554
+ * Routes to singleton handling, destroy fast path, or structural apply.
2555
+ */
2556
+ executeEntityCommands(entityId, commands) {
2557
+ this._changeset.clear();
2558
+ if (this.ctx.componentEntities.exists(entityId)) {
2559
+ this.ctx.componentEntities.executeCommands(entityId, commands);
2560
+ return;
2561
+ }
2562
+ if (commands.some((cmd) => cmd.type === "destroy")) {
2563
+ this.ctx.destroyEntityImmediate(entityId);
2564
+ return;
2565
+ }
2566
+ this.applyEntityCommands(entityId, commands);
2567
+ }
2568
+ applyEntityCommands(entityId, commands) {
2569
+ const currentArchetype = this.ctx.entityToArchetype.get(entityId);
2570
+ if (!currentArchetype) return;
2571
+ const changeset = this._changeset;
2572
+ processCommands(entityId, currentArchetype, commands, changeset, (eid, arch, compId) => {
2573
+ if (isExclusiveComponent(compId)) removeMatchingRelations(eid, arch, compId, changeset);
2574
+ });
2575
+ const hasStructuralChange = changeset.removes.size > 0 || changeset.adds.size > 0;
2576
+ if (this.ctx.hooks.size === 0) {
2577
+ const newArchetype = applyChangeset(this._commandCtx, entityId, currentArchetype, changeset, this.ctx.entityToArchetype, null);
2578
+ if (hasStructuralChange) this.updateEntityReferences(entityId, changeset);
2579
+ if (newArchetype !== currentArchetype) this.ctx.incrementMigrations();
2580
+ return;
2581
+ }
2582
+ const removedComponents = /* @__PURE__ */ new Map();
2583
+ const newArchetype = applyChangeset(this._commandCtx, entityId, currentArchetype, changeset, this.ctx.entityToArchetype, removedComponents);
2584
+ if (hasStructuralChange) this.updateEntityReferences(entityId, changeset);
2585
+ if (newArchetype !== currentArchetype) this.ctx.incrementMigrations();
2586
+ this.ctx.triggerLifecycleHooks(this._hooksCtx, entityId, changeset.adds, removedComponents, currentArchetype, newArchetype);
2587
+ }
2588
+ /**
2589
+ * Immediate (non-buffered) component removal used during cascade deletion.
2590
+ * Called from destroy* paths (which remain in World).
2591
+ */
2592
+ removeComponentImmediate(entityId, componentType, targetEntityId) {
2593
+ const sourceArchetype = this.ctx.entityToArchetype.get(entityId);
2594
+ if (!sourceArchetype) return;
2595
+ const changeset = this._removeChangeset;
2596
+ changeset.clear();
2597
+ changeset.delete(componentType);
2598
+ maybeRemoveWildcardMarker(entityId, sourceArchetype, componentType, getComponentIdFromRelationId(componentType), changeset);
2599
+ const removedComponent = sourceArchetype.get(entityId, componentType);
2600
+ const newArchetype = applyChangeset(this._commandCtx, entityId, sourceArchetype, changeset, this.ctx.entityToArchetype, null);
2601
+ untrackEntityReference(this.ctx.entityReferences, entityId, componentType, targetEntityId);
2602
+ this.ctx.triggerLifecycleHooks(this._hooksCtx, entityId, /* @__PURE__ */ new Map(), new Map([[componentType, removedComponent]]), sourceArchetype, newArchetype);
2603
+ }
2604
+ /**
2605
+ * Keeps the entity reference reverse index in sync after structural changes.
2606
+ * Called from apply paths.
2607
+ */
2608
+ updateEntityReferences(entityId, changeset) {
2609
+ for (const componentType of changeset.removes) if (isEntityRelation(componentType)) {
2610
+ const targetId = getTargetIdFromRelationId(componentType);
2611
+ untrackEntityReference(this.ctx.entityReferences, entityId, componentType, targetId);
2612
+ } else if (componentType >= 1024) untrackEntityReference(this.ctx.entityReferences, entityId, componentType, componentType);
2613
+ for (const [componentType] of changeset.adds) if (isEntityRelation(componentType)) {
2614
+ const targetId = getTargetIdFromRelationId(componentType);
2615
+ trackEntityReference(this.ctx.entityReferences, entityId, componentType, targetId);
2616
+ } else if (componentType >= 1024) trackEntityReference(this.ctx.entityReferences, entityId, componentType, componentType);
2617
+ }
2618
+ /**
2619
+ * Exposed for any future direct needs (currently not required outside the executor).
2620
+ */
2621
+ getHooksContext() {
2622
+ return this._hooksCtx;
2623
+ }
2624
+ };
2625
+ //#endregion
2626
+ //#region src/world/debug-stats.ts
2627
+ /**
2628
+ * Manages debug stats collectors and transient activity counters for World#sync().
2629
+ *
2630
+ * Extracted from World to shrink the main class while keeping the entire debug/observability
2631
+ * path isolated, zero-cost when no collectors are active, and easy to test/maintain.
2632
+ *
2633
+ * Follows the same context/callback injection style as ArchetypeManager, CommandProcessorContext,
2634
+ * and HooksContext to avoid tight coupling.
2635
+ *
2636
+ * All collectors receive the *exact same* stats object for a given sync (as before).
2637
+ * Exceptions in user callbacks are swallowed (as before).
2638
+ */
2639
+ var DebugStatsManager = class {
2640
+ collectors = /* @__PURE__ */ new Set();
2641
+ migrations = 0;
2642
+ archetypesCreated = 0;
2643
+ archetypesRemoved = 0;
2644
+ /** Fast check used to arm timing + reset + counting in hot paths. */
2645
+ hasActiveCollectors() {
2646
+ return this.collectors.size > 0;
2647
+ }
2648
+ /**
2649
+ * Registers a collector. Returns a disposable handle (supports `using`).
2650
+ * Collection stops when the handle is disposed.
2651
+ */
2652
+ createCollector(callback) {
2653
+ this.collectors.add(callback);
2654
+ return { [Symbol.dispose]: () => {
2655
+ this.collectors.delete(callback);
2656
+ } };
2657
+ }
2658
+ recordArchetypeCreated() {
2659
+ if (this.hasActiveCollectors()) this.archetypesCreated++;
2660
+ }
2661
+ recordArchetypeRemoved() {
2662
+ if (this.hasActiveCollectors()) this.archetypesRemoved++;
2663
+ }
2664
+ incrementMigrations() {
2665
+ if (this.hasActiveCollectors()) this.migrations++;
2666
+ }
2667
+ /** Reset all activity counters + the shared hook execution counter. Called at start of an armed sync. */
2668
+ resetActivity() {
2669
+ this.migrations = 0;
2670
+ this.archetypesCreated = 0;
2671
+ this.archetypesRemoved = 0;
2672
+ debugHookExecutionCounter.value = 0;
2673
+ }
2674
+ /**
2675
+ * Build and deliver a SyncDebugStats payload to every active collector.
2676
+ * World supplies the pre-computed snapshot numbers (keeps debug manager decoupled from
2677
+ * internal World maps/registries while preserving exact original stats shape and values).
2678
+ */
2679
+ deliver(timings, data) {
2680
+ const stats = {
2681
+ timestamps: {
2682
+ syncStart: timings.syncStart,
2683
+ syncEnd: timings.syncEnd,
2684
+ commandBufferStart: timings.commandBufferStart,
2685
+ commandBufferEnd: timings.commandBufferEnd
2686
+ },
2687
+ commandIterations: timings.commandIterations,
2688
+ entities: {
2689
+ total: data.entityCount,
2690
+ freelistSize: data.freelistSize,
2691
+ nextId: data.nextId
2692
+ },
2693
+ archetypes: {
2694
+ total: data.archetypeCount,
2695
+ empty: data.emptyArchetypes
2696
+ },
2697
+ queries: {
2698
+ cached: data.cachedQueryCount,
2699
+ registered: data.registeredQueryCount
2700
+ },
2701
+ hooks: { total: data.hookCount },
2702
+ indices: {
2703
+ entityReferences: data.entityReferencesSize,
2704
+ entityToReferencingArchetypes: data.entityToReferencingArchetypesSize,
2705
+ archetypesByComponent: data.archetypesByComponentSize
2706
+ },
2707
+ activity: {
2708
+ migrations: this.migrations,
2709
+ hooksExecuted: debugHookExecutionCounter.value,
2710
+ archetypesCreated: this.archetypesCreated,
2711
+ archetypesRemoved: this.archetypesRemoved
2712
+ }
2713
+ };
2714
+ for (const cb of this.collectors) try {
2715
+ cb(stats);
2716
+ } catch {}
2717
+ }
2718
+ };
2719
+ //#endregion
2720
+ //#region src/world/operations.ts
2721
+ /**
2722
+ * Validation and overload-resolution helpers extracted from World.
2723
+ *
2724
+ * These were previously private methods on World. Moving them reduces line count
2725
+ * in the core class with almost zero coupling (the only dep is a liveness predicate
2726
+ * for assertEntityExists, supplied by the caller).
2727
+ *
2728
+ * Pure type checks (assert*TypeValid) and the resolve* helpers for set/remove
2729
+ * overloads live here.
2730
+ */
2731
+ /**
2732
+ * Assert that an entity (or component-entity) is alive in the world.
2733
+ * The caller supplies the liveness check (World.exists or equivalent) to keep
2734
+ * this module free of direct references to stores.
2735
+ */
2736
+ function assertEntityExists(entityId, label, exists) {
2737
+ if (!exists(entityId)) throw new Error(`${label} ${entityId} does not exist`);
2738
+ }
2739
+ function assertComponentTypeValid(componentType) {
2740
+ if (getDetailedIdType(componentType).type === "invalid") throw new Error(`Invalid component type: ${componentType}`);
2741
+ }
2742
+ function assertSetComponentTypeValid(componentType) {
2743
+ const detailedType = getDetailedIdType(componentType);
2744
+ if (detailedType.type === "invalid") throw new Error(`Invalid component type: ${componentType}`);
2745
+ if (detailedType.type === "wildcard-relation") throw new Error(`Cannot directly add wildcard relation components: ${componentType}`);
2746
+ }
2747
+ /**
2748
+ * Resolve the (entity, componentType, value) for a set() call.
2749
+ */
2750
+ function resolveSetOperation(entityId, componentTypeOrComponent, maybeComponent, argCount = 3, exists = () => true) {
2751
+ const targetEntityId = entityId;
2752
+ if (argCount === 2 && isComponentId(targetEntityId) && typeof componentTypeOrComponent !== "number") {
2753
+ assertEntityExists(targetEntityId, "Component entity", exists);
2754
+ return {
2755
+ entityId: targetEntityId,
2756
+ componentType: targetEntityId,
2757
+ component: componentTypeOrComponent,
2758
+ deprecatedSingletonShorthand: true
2759
+ };
2760
+ }
2761
+ const componentType = componentTypeOrComponent;
2762
+ assertEntityExists(targetEntityId, "Entity", exists);
2763
+ assertSetComponentTypeValid(componentType);
2764
+ return {
2765
+ entityId: targetEntityId,
2766
+ componentType,
2767
+ component: maybeComponent,
2768
+ deprecatedSingletonShorthand: false
2769
+ };
2770
+ }
2771
+ /**
2772
+ * Resolve the (entity, componentType) for a remove() call, handling the
2773
+ * singleton component overload (remove(componentId)).
2774
+ */
2775
+ function resolveRemoveOperation(entityId, componentType, exists = () => true) {
2776
+ if (componentType === void 0) {
2777
+ const componentId = entityId;
2778
+ assertEntityExists(componentId, "Component entity", exists);
2779
+ return {
2780
+ entityId: componentId,
2781
+ componentType: componentId
2782
+ };
2783
+ }
2784
+ const targetEntityId = entityId;
2785
+ assertEntityExists(targetEntityId, "Entity", exists);
2786
+ assertComponentTypeValid(componentType);
2787
+ return {
2788
+ entityId: targetEntityId,
2789
+ componentType
2790
+ };
2315
2791
  }
2316
2792
  //#endregion
2317
2793
  //#region src/storage/serialization.ts
@@ -2479,51 +2955,63 @@ function deserializeWorld(ctx, snapshot) {
2479
2955
  * World class for ECS architecture
2480
2956
  * Manages entities and components
2481
2957
  */
2482
- var World = class {
2958
+ var World = class World {
2959
+ static DEPRECATED_SINGLETON_SET_SHORTHAND_WARNING = "world.set(componentId, value) for singleton components is deprecated; use world.singleton(componentId).set(value) or world.set(componentId, componentId, value) instead.";
2483
2960
  entityIdManager = new EntityIdManager();
2484
- archetypes = [];
2485
- archetypeBySignature = /* @__PURE__ */ new Map();
2486
- entityToArchetype = /* @__PURE__ */ new Map();
2487
- archetypesByComponent = /* @__PURE__ */ new Map();
2488
2961
  entityReferences = /* @__PURE__ */ new Map();
2489
- /** Reverse index: entity ID → set of archetypes whose componentTypes include that entity ID */
2490
- entityToReferencingArchetypes = /* @__PURE__ */ new Map();
2491
2962
  /** Sparse relation storage (for components created with `sparse: true`), shared with all Archetype instances */
2492
2963
  sparseStore = new SparseStoreImpl();
2493
2964
  /** Component entity (singleton) storage */
2494
2965
  componentEntities = new ComponentEntityStore();
2966
+ archetypeManager;
2967
+ get archetypes() {
2968
+ return this.archetypeManager.archetypes;
2969
+ }
2970
+ get entityToArchetype() {
2971
+ return this.archetypeManager.entityToArchetype;
2972
+ }
2973
+ get archetypesByComponent() {
2974
+ return this.archetypeManager.archetypesByComponent;
2975
+ }
2976
+ get entityToReferencingArchetypes() {
2977
+ return this.archetypeManager.entityToReferencingArchetypes;
2978
+ }
2495
2979
  queryRegistry = new QueryRegistry();
2496
2980
  hooks = /* @__PURE__ */ new Set();
2497
- _debugCollectors = /* @__PURE__ */ new Set();
2498
- _debugMigrations = 0;
2499
- _debugArchetypesCreated = 0;
2500
- _debugArchetypesRemoved = 0;
2501
- commandBuffer = new CommandBuffer((entityId, commands) => this.executeEntityCommands(entityId, commands));
2502
- _changeset = new ComponentChangeset();
2503
- _removeChangeset = new ComponentChangeset();
2504
- /** Cached command processor context to avoid per-entity object allocation */
2505
- _commandCtx = {
2506
- sparseStore: this.sparseStore,
2507
- ensureArchetype: (ct) => this.ensureArchetype(ct)
2508
- };
2509
- /** Cached hooks context to avoid per-entity object allocation */
2510
- _hooksCtx = {
2511
- multiHooks: this.hooks,
2512
- has: (eid, ct) => this.has(eid, ct),
2513
- get: (eid, ct) => this.get(eid, ct),
2514
- getOptional: (eid, ct) => this.getOptional(eid, ct)
2515
- };
2981
+ debugStats = new DebugStatsManager();
2982
+ commandBuffer;
2983
+ commandExecutor;
2516
2984
  constructor(snapshot) {
2985
+ this.archetypeManager = new ArchetypeManager({
2986
+ queryRegistry: this.queryRegistry,
2987
+ hooks: this.hooks,
2988
+ recordArchetypeCreated: () => this.debugStats.recordArchetypeCreated(),
2989
+ recordArchetypeRemoved: () => this.debugStats.recordArchetypeRemoved()
2990
+ }, this.sparseStore);
2517
2991
  if (snapshot && typeof snapshot === "object") deserializeWorld({
2518
2992
  entityIdManager: this.entityIdManager,
2519
2993
  componentEntities: this.componentEntities,
2520
2994
  entityReferences: this.entityReferences,
2521
2995
  ensureArchetype: (ct) => this.ensureArchetype(ct),
2522
- setEntityToArchetype: (eid, arch) => this.entityToArchetype.set(eid, arch)
2996
+ setEntityToArchetype: (eid, arch) => this.archetypeManager.entityToArchetype.set(eid, arch)
2523
2997
  }, snapshot);
2524
- }
2525
- createArchetypeSignature(componentTypes) {
2526
- return componentTypes.join(",");
2998
+ const execCtx = {
2999
+ componentEntities: this.componentEntities,
3000
+ entityReferences: this.entityReferences,
3001
+ hooks: this.hooks,
3002
+ entityToArchetype: this.entityToArchetype,
3003
+ ensureArchetype: (ct) => this.ensureArchetype(ct),
3004
+ sparseStore: this.sparseStore,
3005
+ has: (eid, ct) => this.has(eid, ct),
3006
+ get: (eid, ct) => this.get(eid, ct),
3007
+ getOptional: (eid, ct) => this.getOptional(eid, ct),
3008
+ destroyEntityImmediate: (eid) => this.destroyEntityImmediate(eid),
3009
+ incrementMigrations: () => this.debugStats.incrementMigrations(),
3010
+ triggerLifecycleHooks,
3011
+ triggerRemoveHooksForEntityDeletion
3012
+ };
3013
+ this.commandExecutor = new CommandExecutor(execCtx);
3014
+ this.commandBuffer = new CommandBuffer((entityId, commands) => this.commandExecutor.executeEntityCommands(entityId, commands));
2527
3015
  }
2528
3016
  /**
2529
3017
  * Creates a new entity.
@@ -2619,64 +3107,13 @@ var World = class {
2619
3107
  if (this.componentEntities.exists(entityId)) return true;
2620
3108
  return this.entityToArchetype.has(entityId);
2621
3109
  }
2622
- assertEntityExists(entityId, label) {
2623
- if (!this.exists(entityId)) throw new Error(`${label} ${entityId} does not exist`);
2624
- }
2625
- assertComponentTypeValid(componentType) {
2626
- if (getDetailedIdType(componentType).type === "invalid") throw new Error(`Invalid component type: ${componentType}`);
2627
- }
2628
- assertSetComponentTypeValid(componentType) {
2629
- const detailedType = getDetailedIdType(componentType);
2630
- if (detailedType.type === "invalid") throw new Error(`Invalid component type: ${componentType}`);
2631
- if (detailedType.type === "wildcard-relation") throw new Error(`Cannot directly add wildcard relation components: ${componentType}`);
2632
- }
2633
- resolveSetOperation(entityId, componentTypeOrComponent, maybeComponent) {
2634
- if (maybeComponent === void 0 && componentTypeOrComponent !== void 0) {
2635
- const detailedType = getDetailedIdType(entityId);
2636
- if (detailedType.type === "component" || detailedType.type === "component-relation") {
2637
- const componentId = entityId;
2638
- this.assertEntityExists(componentId, "Component entity");
2639
- this.assertSetComponentTypeValid(componentId);
2640
- return {
2641
- entityId: componentId,
2642
- componentType: componentId,
2643
- component: componentTypeOrComponent
2644
- };
2645
- }
2646
- }
2647
- const targetEntityId = entityId;
2648
- const componentType = componentTypeOrComponent;
2649
- this.assertEntityExists(targetEntityId, "Entity");
2650
- this.assertSetComponentTypeValid(componentType);
2651
- return {
2652
- entityId: targetEntityId,
2653
- componentType,
2654
- component: maybeComponent
2655
- };
2656
- }
2657
- resolveRemoveOperation(entityId, componentType) {
2658
- if (componentType === void 0) {
2659
- const componentId = entityId;
2660
- this.assertEntityExists(componentId, "Component entity");
2661
- return {
2662
- entityId: componentId,
2663
- componentType: componentId
2664
- };
2665
- }
2666
- const targetEntityId = entityId;
2667
- this.assertEntityExists(targetEntityId, "Entity");
2668
- this.assertComponentTypeValid(componentType);
2669
- return {
2670
- entityId: targetEntityId,
2671
- componentType
2672
- };
2673
- }
2674
3110
  set(entityId, componentTypeOrComponent, maybeComponent) {
2675
- const { entityId: targetEntityId, componentType, component } = this.resolveSetOperation(entityId, componentTypeOrComponent, maybeComponent);
3111
+ const { entityId: targetEntityId, componentType, component, deprecatedSingletonShorthand } = resolveSetOperation(entityId, componentTypeOrComponent, maybeComponent, arguments.length, (id) => this.exists(id));
3112
+ if (deprecatedSingletonShorthand) console.warn(World.DEPRECATED_SINGLETON_SET_SHORTHAND_WARNING);
2676
3113
  this.commandBuffer.set(targetEntityId, componentType, component);
2677
3114
  }
2678
3115
  remove(entityId, componentType) {
2679
- const { entityId: targetEntityId, componentType: targetComponentType } = this.resolveRemoveOperation(entityId, componentType);
3116
+ const { entityId: targetEntityId, componentType: targetComponentType } = resolveRemoveOperation(entityId, componentType, (id) => this.exists(id));
2680
3117
  this.commandBuffer.remove(targetEntityId, targetComponentType);
2681
3118
  }
2682
3119
  /**
@@ -2693,6 +3130,30 @@ var World = class {
2693
3130
  delete(entityId) {
2694
3131
  this.commandBuffer.delete(entityId);
2695
3132
  }
3133
+ /**
3134
+ * Returns an explicit handle for a singleton component (component-as-entity).
3135
+ *
3136
+ * This is the preferred API for singleton components.
3137
+ *
3138
+ * @example
3139
+ * const config = world.singleton(GlobalConfig);
3140
+ * config.set({ debug: true });
3141
+ * world.sync();
3142
+ * console.log(config.get());
3143
+ */
3144
+ singleton(componentId) {
3145
+ assertEntityExists(componentId, "Component entity", (id) => this.exists(id));
3146
+ assertSetComponentTypeValid(componentId);
3147
+ return new SingletonHandle(componentId, {
3148
+ has: () => this.componentEntities.hasSingleton(componentId),
3149
+ get: () => this.get(componentId),
3150
+ getOptional: () => this.getOptional(componentId),
3151
+ remove: () => this.commandBuffer.remove(componentId, componentId),
3152
+ set: (value) => {
3153
+ this.commandBuffer.set(componentId, componentId, value);
3154
+ }
3155
+ });
3156
+ }
2696
3157
  has(entityId, componentType) {
2697
3158
  if (componentType === void 0) {
2698
3159
  const componentId = entityId;
@@ -2763,7 +3224,7 @@ var World = class {
2763
3224
  * // world.getChildren(parent, ChildOf), world.getParent(child, ChildOf)
2764
3225
  */
2765
3226
  getRelationTargets(entityId, relationComp) {
2766
- this.assertEntityExists(entityId, "Entity");
3227
+ assertEntityExists(entityId, "Entity", (id) => this.exists(id));
2767
3228
  const wildcard = relation(relationComp, "*");
2768
3229
  if (this.componentEntities.exists(entityId)) return this.componentEntities.getWildcard(entityId, wildcard);
2769
3230
  return this.get(entityId, wildcard);
@@ -2793,7 +3254,7 @@ var World = class {
2793
3254
  * given base component.
2794
3255
  */
2795
3256
  hasRelation(entityId, relationComp, targetId) {
2796
- this.assertEntityExists(entityId, "Entity");
3257
+ assertEntityExists(entityId, "Entity", (id) => this.exists(id));
2797
3258
  if (targetId !== void 0) {
2798
3259
  const specific = relation(relationComp, targetId);
2799
3260
  return this.has(entityId, specific);
@@ -2804,7 +3265,7 @@ var World = class {
2804
3265
  * Returns the number of relations of the given base component held by the entity.
2805
3266
  */
2806
3267
  countRelations(entityId, relationComp) {
2807
- this.assertEntityExists(entityId, "Entity");
3268
+ assertEntityExists(entityId, "Entity", (id) => this.exists(id));
2808
3269
  return this.getRelationTargets(entityId, relationComp).length;
2809
3270
  }
2810
3271
  /**
@@ -2951,60 +3412,7 @@ var World = class {
2951
3412
  * This is intended for development/debugging and leak detection.
2952
3413
  */
2953
3414
  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 {}
3415
+ return this.debugStats.createCollector(callback);
3008
3416
  }
3009
3417
  /**
3010
3418
  * Synchronizes all buffered commands (set/remove/delete) to the world.
@@ -3017,19 +3425,39 @@ var World = class {
3017
3425
  * world.sync(); // Apply all buffered changes
3018
3426
  */
3019
3427
  sync() {
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;
3428
+ if (!this.debugStats.hasActiveCollectors()) {
3429
+ this.commandBuffer.execute();
3430
+ return;
3431
+ }
3432
+ const syncStart = performance.now();
3433
+ this.debugStats.resetActivity();
3434
+ const commandBufferStart = performance.now();
3024
3435
  const commandIterations = this.commandBuffer.execute();
3025
- const commandBufferEnd = hasCollectors ? performance.now() : 0;
3026
- const syncEnd = hasCollectors ? performance.now() : 0;
3027
- if (hasCollectors) this._deliverDebugStats({
3436
+ const commandBufferEnd = performance.now();
3437
+ const syncEnd = performance.now();
3438
+ const entityCount = this.entityToArchetype.size;
3439
+ let emptyArchetypes = 0;
3440
+ for (const arch of this.archetypes) if (arch.size === 0) emptyArchetypes++;
3441
+ let archetypesByComponentSize = 0;
3442
+ for (const set of this.archetypesByComponent.values()) archetypesByComponentSize += set.size;
3443
+ this.debugStats.deliver({
3028
3444
  syncStart,
3029
3445
  syncEnd,
3030
3446
  commandBufferStart,
3031
3447
  commandBufferEnd,
3032
3448
  commandIterations
3449
+ }, {
3450
+ entityCount,
3451
+ freelistSize: this.entityIdManager.getFreelistSize(),
3452
+ nextId: this.entityIdManager.getNextId(),
3453
+ archetypeCount: this.archetypes.length,
3454
+ emptyArchetypes,
3455
+ archetypesByComponentSize,
3456
+ cachedQueryCount: this.queryRegistry.cache?.size ?? 0,
3457
+ registeredQueryCount: this.queryRegistry.queries?.size ?? 0,
3458
+ hookCount: this.hooks.size,
3459
+ entityReferencesSize: this.entityReferences.size,
3460
+ entityToReferencingArchetypesSize: this.entityToReferencingArchetypes.size
3033
3461
  });
3034
3462
  }
3035
3463
  /**
@@ -3066,7 +3494,7 @@ var World = class {
3066
3494
  createQuery(componentTypes, filter = {}) {
3067
3495
  const sortedTypes = normalizeComponentTypes(componentTypes);
3068
3496
  const filterKey = serializeQueryFilter(filter);
3069
- const key = `${this.createArchetypeSignature(sortedTypes)}${filterKey ? `|${filterKey}` : ""}`;
3497
+ const key = `${sortedTypes.join(",")}${filterKey ? `|${filterKey}` : ""}`;
3070
3498
  return this.queryRegistry.getOrCreate(this, sortedTypes, key, filter);
3071
3499
  }
3072
3500
  /**
@@ -3132,44 +3560,7 @@ var World = class {
3132
3560
  * @internal
3133
3561
  */
3134
3562
  getMatchingArchetypes(componentTypes) {
3135
- if (componentTypes.length === 0) return [...this.archetypes];
3136
- const regularComponents = [];
3137
- const wildcardRelations = [];
3138
- for (const componentType of componentTypes) if (isWildcardRelationId(componentType)) {
3139
- const componentId = getComponentIdFromRelationId(componentType);
3140
- if (componentId !== void 0) wildcardRelations.push({
3141
- componentId,
3142
- relationId: componentType
3143
- });
3144
- } else regularComponents.push(componentType);
3145
- let matchingArchetypes = this.getArchetypesWithComponents(regularComponents);
3146
- for (const { componentId, relationId } of wildcardRelations) {
3147
- const markerSet = this.archetypesByComponent.get(relationId);
3148
- const archetypesWithMarker = markerSet ? Array.from(markerSet) : [];
3149
- matchingArchetypes = matchingArchetypes.length === 0 ? archetypesWithMarker : matchingArchetypes.filter((a) => markerSet?.has(a) || a.hasRelationWithComponentId(componentId));
3150
- }
3151
- return matchingArchetypes;
3152
- }
3153
- getArchetypesWithComponents(componentTypes) {
3154
- if (componentTypes.length === 0) return [...this.archetypes];
3155
- if (componentTypes.length === 1) {
3156
- const set = this.archetypesByComponent.get(componentTypes[0]);
3157
- return set ? Array.from(set) : [];
3158
- }
3159
- const sets = componentTypes.map((type) => this.archetypesByComponent.get(type)).filter((s) => s !== void 0 && s.size > 0).sort((a, b) => a.size - b.size);
3160
- if (sets.length === 0) return [];
3161
- if (sets.length < componentTypes.length) return [];
3162
- const smallest = sets[0];
3163
- if (sets.length === 2) {
3164
- const other = sets[1];
3165
- return Array.from(smallest).filter((a) => other.has(a));
3166
- }
3167
- let result = new Set(smallest);
3168
- for (let i = 1; i < sets.length; i++) {
3169
- for (const item of result) if (!sets[i].has(item)) result.delete(item);
3170
- if (result.size === 0) return [];
3171
- }
3172
- return Array.from(result);
3563
+ return this.archetypeManager.getMatchingArchetypes(componentTypes);
3173
3564
  }
3174
3565
  query(componentTypes, includeComponents) {
3175
3566
  const matchingArchetypes = this.getMatchingArchetypes(componentTypes);
@@ -3183,154 +3574,14 @@ var World = class {
3183
3574
  return result;
3184
3575
  }
3185
3576
  }
3186
- executeEntityCommands(entityId, commands) {
3187
- this._changeset.clear();
3188
- if (this.componentEntities.exists(entityId)) {
3189
- this.componentEntities.executeCommands(entityId, commands);
3190
- return;
3191
- }
3192
- if (commands.some((cmd) => cmd.type === "destroy")) {
3193
- this.destroyEntityImmediate(entityId);
3194
- return;
3195
- }
3196
- this.applyEntityCommands(entityId, commands);
3197
- }
3198
- applyEntityCommands(entityId, commands) {
3199
- const currentArchetype = this.entityToArchetype.get(entityId);
3200
- if (!currentArchetype) return;
3201
- const changeset = this._changeset;
3202
- processCommands(entityId, currentArchetype, commands, changeset, (eid, arch, compId) => {
3203
- if (isExclusiveComponent(compId)) removeMatchingRelations(eid, arch, compId, changeset);
3204
- });
3205
- const hasStructuralChange = changeset.removes.size > 0 || changeset.adds.size > 0;
3206
- if (this.hooks.size === 0) {
3207
- const newArchetype = applyChangeset(this._commandCtx, entityId, currentArchetype, changeset, this.entityToArchetype, null);
3208
- if (hasStructuralChange) this.updateEntityReferences(entityId, changeset);
3209
- if (this._debugCollectors.size > 0 && newArchetype !== currentArchetype) this._debugMigrations++;
3210
- return;
3211
- }
3212
- const removedComponents = /* @__PURE__ */ new Map();
3213
- const newArchetype = applyChangeset(this._commandCtx, entityId, currentArchetype, changeset, this.entityToArchetype, removedComponents);
3214
- if (hasStructuralChange) this.updateEntityReferences(entityId, changeset);
3215
- if (this._debugCollectors.size > 0 && newArchetype !== currentArchetype) this._debugMigrations++;
3216
- triggerLifecycleHooks(this.createHooksContext(), entityId, changeset.adds, removedComponents, currentArchetype, newArchetype);
3217
- }
3218
- createHooksContext() {
3219
- return this._hooksCtx;
3220
- }
3221
- removeComponentImmediate(entityId, componentType, targetEntityId) {
3222
- const sourceArchetype = this.entityToArchetype.get(entityId);
3223
- if (!sourceArchetype) return;
3224
- const changeset = this._removeChangeset;
3225
- changeset.clear();
3226
- changeset.delete(componentType);
3227
- maybeRemoveWildcardMarker(entityId, sourceArchetype, componentType, getComponentIdFromRelationId(componentType), changeset);
3228
- const removedComponent = sourceArchetype.get(entityId, componentType);
3229
- const newArchetype = applyChangeset(this._commandCtx, entityId, sourceArchetype, changeset, this.entityToArchetype, null);
3230
- untrackEntityReference(this.entityReferences, entityId, componentType, targetEntityId);
3231
- triggerLifecycleHooks(this.createHooksContext(), entityId, /* @__PURE__ */ new Map(), new Map([[componentType, removedComponent]]), sourceArchetype, newArchetype);
3232
- }
3233
- updateEntityReferences(entityId, changeset) {
3234
- for (const componentType of changeset.removes) if (isEntityRelation(componentType)) {
3235
- const targetId = getTargetIdFromRelationId(componentType);
3236
- untrackEntityReference(this.entityReferences, entityId, componentType, targetId);
3237
- } else if (componentType >= 1024) untrackEntityReference(this.entityReferences, entityId, componentType, componentType);
3238
- for (const [componentType] of changeset.adds) if (isEntityRelation(componentType)) {
3239
- const targetId = getTargetIdFromRelationId(componentType);
3240
- trackEntityReference(this.entityReferences, entityId, componentType, targetId);
3241
- } else if (componentType >= 1024) trackEntityReference(this.entityReferences, entityId, componentType, componentType);
3242
- }
3243
3577
  ensureArchetype(componentTypes) {
3244
- const sortedTypes = normalizeComponentTypes(filterRegularComponentTypes(componentTypes));
3245
- const hashKey = this.createArchetypeSignature(sortedTypes);
3246
- return getOrCompute(this.archetypeBySignature, hashKey, () => this.createNewArchetype(sortedTypes));
3247
- }
3248
- /** Add componentType to the reverse index if it contains an entity ID */
3249
- addToReferencingIndex(componentType, archetype) {
3250
- const detailedType = getDetailedIdType(componentType);
3251
- let entityId;
3252
- if (detailedType.type === "entity") entityId = componentType;
3253
- else if (detailedType.type === "entity-relation") entityId = detailedType.targetId;
3254
- if (entityId !== void 0) {
3255
- let refs = this.entityToReferencingArchetypes.get(entityId);
3256
- if (!refs) {
3257
- refs = /* @__PURE__ */ new Set();
3258
- this.entityToReferencingArchetypes.set(entityId, refs);
3259
- }
3260
- refs.add(archetype);
3261
- }
3262
- }
3263
- /** Remove componentType from the reverse index */
3264
- removeFromReferencingIndex(componentType, archetype) {
3265
- const detailedType = getDetailedIdType(componentType);
3266
- let entityId;
3267
- if (detailedType.type === "entity") entityId = componentType;
3268
- else if (detailedType.type === "entity-relation") entityId = detailedType.targetId;
3269
- if (entityId !== void 0) {
3270
- const refs = this.entityToReferencingArchetypes.get(entityId);
3271
- if (refs) {
3272
- refs.delete(archetype);
3273
- if (refs.size === 0) this.entityToReferencingArchetypes.delete(entityId);
3274
- }
3275
- }
3276
- }
3277
- createNewArchetype(componentTypes) {
3278
- const newArchetype = new Archetype(componentTypes, this.sparseStore);
3279
- this.archetypes.push(newArchetype);
3280
- if (this._debugCollectors.size > 0) this._debugArchetypesCreated++;
3281
- for (const componentType of componentTypes) {
3282
- let archetypes = this.archetypesByComponent.get(componentType);
3283
- if (!archetypes) {
3284
- archetypes = /* @__PURE__ */ new Set();
3285
- this.archetypesByComponent.set(componentType, archetypes);
3286
- }
3287
- archetypes.add(newArchetype);
3288
- this.addToReferencingIndex(componentType, newArchetype);
3289
- }
3290
- this.queryRegistry.onNewArchetype(newArchetype);
3291
- this.updateArchetypeHookMatches(newArchetype);
3292
- return newArchetype;
3293
- }
3294
- updateArchetypeHookMatches(archetype) {
3295
- for (const entry of this.hooks) if (this.archetypeMatchesHook(archetype, entry)) {
3296
- archetype.matchingMultiHooks.add(entry);
3297
- if (entry.matchedArchetypes) entry.matchedArchetypes.add(archetype);
3298
- }
3299
- }
3300
- archetypeMatchesHook(archetype, entry) {
3301
- return entry.requiredComponents.every((c) => {
3302
- if (isWildcardRelationId(c)) {
3303
- if (isSparseWildcard(c)) return true;
3304
- const componentId = getComponentIdFromRelationId(c);
3305
- return componentId !== void 0 && archetype.hasRelationWithComponentId(componentId);
3306
- }
3307
- return archetype.componentTypeSet.has(c) || isSparseRelation(c);
3308
- }) && matchesFilter(archetype, entry.filter);
3578
+ return this.archetypeManager.ensureArchetype(componentTypes);
3309
3579
  }
3310
3580
  cleanupArchetypesReferencingEntity(entityId) {
3311
- const refs = this.entityToReferencingArchetypes.get(entityId);
3312
- if (!refs) return;
3313
- for (const archetype of refs) if (archetype.getEntities().length === 0) this.removeArchetype(archetype);
3314
- this.entityToReferencingArchetypes.delete(entityId);
3581
+ this.archetypeManager.cleanupArchetypesReferencingEntity(entityId);
3315
3582
  }
3316
- removeArchetype(archetype) {
3317
- const index = this.archetypes.indexOf(archetype);
3318
- if (index !== -1) {
3319
- const last = this.archetypes[this.archetypes.length - 1];
3320
- this.archetypes[index] = last;
3321
- this.archetypes.pop();
3322
- }
3323
- if (this._debugCollectors.size > 0) this._debugArchetypesRemoved++;
3324
- this.archetypeBySignature.delete(this.createArchetypeSignature(archetype.componentTypes));
3325
- for (const componentType of archetype.componentTypes) {
3326
- const archetypes = this.archetypesByComponent.get(componentType);
3327
- if (archetypes) {
3328
- archetypes.delete(archetype);
3329
- if (archetypes.size === 0) this.archetypesByComponent.delete(componentType);
3330
- }
3331
- this.removeFromReferencingIndex(componentType, archetype);
3332
- }
3333
- this.queryRegistry.onArchetypeRemoved(archetype);
3583
+ archetypeMatchesHook(archetype, entry) {
3584
+ return this.archetypeManager.archetypeMatchesHook(archetype, entry);
3334
3585
  }
3335
3586
  /**
3336
3587
  * Serializes the entire world state to a plain JavaScript object.
@@ -3353,11 +3604,22 @@ var World = class {
3353
3604
  * const savedData = JSON.parse(localStorage.getItem('save'));
3354
3605
  * const newWorld = new World(savedData);
3355
3606
  */
3607
+ removeComponentImmediate(entityId, componentType, targetEntityId) {
3608
+ this.commandExecutor.removeComponentImmediate(entityId, componentType, targetEntityId);
3609
+ }
3610
+ createHooksContext() {
3611
+ return this.commandExecutor.getHooksContext?.() ?? {
3612
+ multiHooks: this.hooks,
3613
+ has: (eid, ct) => this.has(eid, ct),
3614
+ get: (eid, ct) => this.get(eid, ct),
3615
+ getOptional: (eid, ct) => this.getOptional(eid, ct)
3616
+ };
3617
+ }
3356
3618
  serialize() {
3357
- return serializeWorld(this.archetypes, this.componentEntities, this.entityIdManager);
3619
+ return serializeWorld(this.archetypeManager.archetypes, this.componentEntities, this.entityIdManager);
3358
3620
  }
3359
3621
  };
3360
3622
  //#endregion
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 };
3623
+ export { component as a, isSparseComponent as c, decodeRelationId as d, isWildcardRelationId as f, isRelationId as g, isEntityId as h, EntityBuilder as i, isSparseRelation as l, isComponentId as m, Query as n, getComponentIdByName as o, relation as p, SingletonHandle as r, getComponentNameById as s, World as t, isSparseWildcard as u };
3362
3624
 
3363
3625
  //# sourceMappingURL=world.mjs.map