@codehz/ecs 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/world.mjs CHANGED
@@ -622,124 +622,326 @@ var EntityBuilder = class {
622
622
  }
623
623
  };
624
624
  //#endregion
625
- //#region src/component/type-utils.ts
625
+ //#region src/world/singleton.ts
626
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
640
- /**
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 provides an explicit and concise API for singleton components without
630
+ * overloading `world.set()` semantics.
631
+ *
632
+ * @example
633
+ * const config = world.singleton(Config);
634
+ * config.set({ debug: true });
635
+ * world.sync();
636
+ * console.log(config.get());
649
637
  */
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);
638
+ var SingletonHandle = class {
639
+ componentId;
640
+ ops;
641
+ constructor(componentId, ops) {
642
+ this.componentId = componentId;
643
+ this.ops = ops;
655
644
  }
656
- return value;
657
- }
645
+ has() {
646
+ return this.ops.has();
647
+ }
648
+ get() {
649
+ return this.ops.get();
650
+ }
651
+ getOptional() {
652
+ return this.ops.getOptional();
653
+ }
654
+ remove() {
655
+ this.ops.remove();
656
+ }
657
+ set(...args) {
658
+ this.ops.set(args[0]);
659
+ }
660
+ };
658
661
  //#endregion
659
- //#region src/archetype/helpers.ts
662
+ //#region src/archetype/store.ts
660
663
  /**
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
664
+ * Production implementation of SparseStore.
665
+ *
666
+ * Internal layout (optimized):
667
+ * - byComponent: baseComponentId (entityId RelationEntry)
668
+ * RelationEntry uses a single-value form for the common exclusive case (1 target),
669
+ * avoiding Map allocation for the vast majority of usage.
670
+ * - entityIndex: entityId → Set<baseComponentId>
671
+ * Lightweight reverse index.
665
672
  */
666
- function hasWildcardRelation(components, wildcardComponentId) {
667
- for (const relId of components.keys()) if (isRelationId(relId)) {
668
- if (getComponentIdFromRelationId(relId) === wildcardComponentId) return true;
673
+ var SparseStoreImpl = class {
674
+ /**
675
+ * Primary storage, keyed by the base relation component ID.
676
+ */
677
+ byComponent = /* @__PURE__ */ new Map();
678
+ /**
679
+ * Reverse index: which base component kinds an entity participates in.
680
+ * Only required to support getAllForEntity and deleteEntity efficiently.
681
+ * The primary storage (byComponent) is deliberately not optimized for these operations.
682
+ */
683
+ entityIndex = /* @__PURE__ */ new Map();
684
+ getValue(entityId, relationType) {
685
+ const componentId = getComponentIdFromRelationId(relationType);
686
+ if (componentId === void 0) return void 0;
687
+ const entities = this.byComponent.get(componentId);
688
+ if (!entities) return void 0;
689
+ const entry = entities.get(entityId);
690
+ if (!entry) return void 0;
691
+ const targetId = getTargetIdFromRelationId(relationType);
692
+ if (entry.type === "single") return entry.target === targetId ? entry.data : void 0;
693
+ else {
694
+ const item = entry.targets.get(targetId);
695
+ return item ? item.data : void 0;
696
+ }
669
697
  }
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
- }
698
+ setValue(entityId, relationType, data) {
699
+ const componentId = getComponentIdFromRelationId(relationType);
700
+ if (componentId === void 0) throw new Error("setValue called with a non-relation type on SparseStore");
701
+ let entities = this.byComponent.get(componentId);
702
+ if (!entities) {
703
+ entities = /* @__PURE__ */ new Map();
704
+ this.byComponent.set(componentId, entities);
705
+ }
706
+ const targetId = getTargetIdFromRelationId(relationType);
707
+ let entry = entities.get(entityId);
708
+ if (!entry) {
709
+ entry = {
710
+ type: "single",
711
+ relationType,
712
+ target: targetId,
713
+ data
714
+ };
715
+ entities.set(entityId, entry);
716
+ } else if (entry.type === "single") if (entry.target === targetId) {
717
+ entry.data = data;
718
+ entry.relationType = relationType;
719
+ } else {
720
+ const targets = /* @__PURE__ */ new Map();
721
+ targets.set(entry.target, {
722
+ relationType: entry.relationType,
723
+ data: entry.data
724
+ });
725
+ targets.set(targetId, {
726
+ relationType,
727
+ data
728
+ });
729
+ entities.set(entityId, {
730
+ type: "multi",
731
+ targets
732
+ });
733
+ }
734
+ else entry.targets.set(targetId, {
735
+ relationType,
736
+ data
737
+ });
738
+ let components = this.entityIndex.get(entityId);
739
+ if (!components) {
740
+ components = /* @__PURE__ */ new Set();
741
+ this.entityIndex.set(entityId, components);
742
+ }
743
+ components.add(componentId);
744
+ }
745
+ deleteValue(entityId, relationType) {
746
+ const componentId = getComponentIdFromRelationId(relationType);
747
+ if (componentId === void 0) return false;
748
+ const entities = this.byComponent.get(componentId);
749
+ if (!entities) return false;
750
+ const entry = entities.get(entityId);
751
+ if (!entry) return false;
752
+ const targetId = getTargetIdFromRelationId(relationType);
753
+ let existed = false;
754
+ if (entry.type === "single") {
755
+ if (entry.target === targetId) {
756
+ existed = true;
757
+ entities.delete(entityId);
758
+ }
759
+ } else {
760
+ existed = entry.targets.delete(targetId);
761
+ if (entry.targets.size === 0) entities.delete(entityId);
762
+ else if (entry.targets.size === 1) {
763
+ const [first] = entry.targets.entries();
764
+ const [t, item] = first;
765
+ entities.set(entityId, {
766
+ type: "single",
767
+ relationType: item.relationType,
768
+ target: t,
769
+ data: item.data
770
+ });
771
+ }
772
+ }
773
+ if (!entities.has(entityId) && entities.size === 0) this.byComponent.delete(componentId);
774
+ const components = this.entityIndex.get(entityId);
775
+ if (components && !entities.has(entityId)) {
776
+ components.delete(componentId);
777
+ if (components.size === 0) this.entityIndex.delete(entityId);
778
+ }
779
+ return existed;
780
+ }
781
+ hasAnyForComponent(componentId) {
782
+ const entities = this.byComponent.get(componentId);
783
+ return entities !== void 0 && entities.size > 0;
784
+ }
785
+ getRelationsForComponent(entityId, componentId) {
786
+ const result = [];
787
+ const entities = this.byComponent.get(componentId);
788
+ if (!entities) return result;
789
+ const entry = entities.get(entityId);
790
+ if (!entry) return result;
791
+ if (entry.type === "single") result.push([entry.target, entry.data]);
792
+ else for (const [target, item] of entry.targets) result.push([target, item.data]);
793
+ return result;
794
+ }
795
+ getAllForEntity(entityId) {
796
+ const components = this.entityIndex.get(entityId);
797
+ if (!components || components.size === 0) return [];
798
+ const result = [];
799
+ for (const componentId of components) {
800
+ const entry = this.byComponent.get(componentId)?.get(entityId);
801
+ if (entry) if (entry.type === "single") result.push([entry.relationType, entry.data]);
802
+ else for (const item of entry.targets.values()) result.push([item.relationType, item.data]);
803
+ }
804
+ return result;
805
+ }
806
+ deleteEntity(entityId) {
807
+ const components = this.entityIndex.get(entityId);
808
+ if (!components) return;
809
+ for (const componentId of components) {
810
+ const entities = this.byComponent.get(componentId);
811
+ if (entities) {
812
+ entities.delete(entityId);
813
+ if (entities.size === 0) this.byComponent.delete(componentId);
814
+ }
815
+ }
816
+ this.entityIndex.delete(entityId);
817
+ }
818
+ getAllForEntities(entityIds) {
819
+ const result = /* @__PURE__ */ new Map();
820
+ for (const eid of entityIds) {
821
+ const data = this.getAllForEntity(eid);
822
+ if (data.length > 0) result.set(eid, data);
823
+ }
824
+ return result;
825
+ }
826
+ };
827
+ //#endregion
828
+ //#region src/commands/buffer.ts
691
829
  /**
692
- * Get data source for wildcard relations from component types
830
+ * Maximum number of command buffer execution iterations to prevent infinite loops
693
831
  */
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
- }
832
+ const MAX_COMMAND_ITERATIONS = 100;
698
833
  /**
699
- * Build wildcard relation value from matching relations.
700
- * Receives the SparseStore directly for efficient per-component lookups.
834
+ * Command buffer for deferred structural changes
701
835
  */
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]);
836
+ var CommandBuffer = class {
837
+ commands = [];
838
+ swapBuffer = [];
839
+ /** Reusable map to group commands by entity, avoids per-sync allocations */
840
+ entityCommands = /* @__PURE__ */ new Map();
841
+ executeEntityCommands;
842
+ /**
843
+ * Create a command buffer with an executor function
844
+ */
845
+ constructor(executeEntityCommands) {
846
+ this.executeEntityCommands = executeEntityCommands;
709
847
  }
710
- if (targetComponentId !== void 0) {
711
- const dfMatches = sparseStore.getRelationsForComponent(entityId, targetComponentId);
712
- for (const m of dfMatches) relations.push(m);
848
+ set(entityId, componentType, component) {
849
+ this.commands.push({
850
+ type: "set",
851
+ entityId,
852
+ componentType,
853
+ component
854
+ });
713
855
  }
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}`);
856
+ /**
857
+ * Remove a component from an entity (deferred)
858
+ */
859
+ remove(entityId, componentType) {
860
+ this.commands.push({
861
+ type: "delete",
862
+ entityId,
863
+ componentType
864
+ });
865
+ }
866
+ /**
867
+ * Destroy an entity (deferred)
868
+ */
869
+ delete(entityId) {
870
+ this.commands.push({
871
+ type: "destroy",
872
+ entityId
873
+ });
874
+ }
875
+ /**
876
+ * Execute all commands and clear the buffer.
877
+ * Returns the number of iterations performed (for debug stats).
878
+ */
879
+ execute() {
880
+ let iterations = 0;
881
+ while (this.commands.length > 0) {
882
+ if (iterations >= MAX_COMMAND_ITERATIONS) throw new Error("Command execution exceeded maximum iterations, possible infinite loop");
883
+ iterations++;
884
+ const currentCommands = this.commands;
885
+ this.commands = this.swapBuffer;
886
+ const entityCommands = this.entityCommands;
887
+ for (const cmd of currentCommands) {
888
+ const existing = entityCommands.get(cmd.entityId);
889
+ if (existing !== void 0) existing.push(cmd);
890
+ else entityCommands.set(cmd.entityId, [cmd]);
891
+ }
892
+ currentCommands.length = 0;
893
+ this.swapBuffer = currentCommands;
894
+ for (const [entityId, commands] of entityCommands) this.executeEntityCommands(entityId, commands);
895
+ entityCommands.clear();
718
896
  }
719
- return;
897
+ return iterations;
720
898
  }
721
- return optional ? { value: relations } : relations;
899
+ /**
900
+ * Get current commands (for testing)
901
+ */
902
+ getCommands() {
903
+ return [...this.commands];
904
+ }
905
+ /**
906
+ * Clear all commands
907
+ */
908
+ clear() {
909
+ this.commands = [];
910
+ }
911
+ };
912
+ //#endregion
913
+ //#region src/types/index.ts
914
+ function isOptionalEntityId(type) {
915
+ return typeof type === "object" && type !== null && "optional" in type;
722
916
  }
917
+ //#endregion
918
+ //#region src/component/type-utils.ts
723
919
  /**
724
- * Build regular component value from data source
920
+ * Normalize component type collections into a stable ascending order.
921
+ * This keeps cache keys and archetype signatures deterministic.
725
922
  */
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;
923
+ function normalizeComponentTypes(componentTypes) {
924
+ return [...componentTypes].sort((a, b) => a - b);
734
925
  }
926
+ //#endregion
927
+ //#region src/utils/utils.ts
735
928
  /**
736
- * Build a single component value based on its type
929
+ * Utility functions for ECS library
737
930
  */
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);
931
+ /**
932
+ * Get a value from cache or compute and cache it if not present
933
+ * @param cache The cache map
934
+ * @param key The cache key
935
+ * @param compute Function to compute the value if not cached (may have side effects)
936
+ * @returns The cached or computed value
937
+ */
938
+ function getOrCompute(cache, key, compute) {
939
+ let value = cache.get(key);
940
+ if (value === void 0) {
941
+ value = compute();
942
+ cache.set(key, value);
943
+ }
944
+ return value;
743
945
  }
744
946
  //#endregion
745
947
  //#region src/archetype/archetype.ts
@@ -961,420 +1163,198 @@ var Archetype = class {
961
1163
  }
962
1164
  if (this.sparseRelations.getValue(entityId, componentType) !== void 0 || this.sparseRelations.getAllForEntity(entityId).some(([t]) => t === componentType)) return this.sparseRelations.getValue(entityId, componentType);
963
1165
  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
- }
1166
+ }
1167
+ getOptional(entityId, componentType) {
1168
+ const index = this.entityToIndex.get(entityId);
1169
+ if (index === void 0) throw new Error(`Entity ${entityId} is not in this archetype`);
1170
+ if (this.componentTypeSet.has(componentType)) {
1171
+ const data = this.getComponentData(componentType)[index];
1172
+ if (data === MISSING_COMPONENT) return void 0;
1173
+ return { value: data };
1181
1174
  }
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);
1175
+ const value = this.sparseRelations.getValue(entityId, componentType);
1176
+ if (value !== void 0) return { value };
1177
+ if (this.sparseRelations.getAllForEntity(entityId).some(([t]) => t === componentType)) return { value: this.sparseRelations.getValue(entityId, componentType) };
1178
+ }
1179
+ set(entityId, componentType, data) {
1180
+ const index = this.entityToIndex.get(entityId);
1181
+ if (index === void 0) throw new Error(`Entity ${entityId} is not in this archetype`);
1182
+ if (this.componentData.has(componentType)) {
1183
+ this.getComponentData(componentType)[index] = data;
1184
+ return;
1187
1185
  }
1188
- return existed;
1186
+ const detailedType = getDetailedIdType(componentType);
1187
+ if (isRelationType(detailedType) && isSparseComponent(detailedType.componentId)) {
1188
+ this.sparseRelations.setValue(entityId, componentType, data);
1189
+ return;
1190
+ }
1191
+ throw new Error(`Component type ${componentType} is not in this archetype`);
1189
1192
  }
1190
- hasAnyForComponent(componentId) {
1191
- const entities = this.byComponent.get(componentId);
1192
- return entities !== void 0 && entities.size > 0;
1193
+ getEntities() {
1194
+ return this.entities;
1193
1195
  }
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;
1196
+ getEntityToIndexMap() {
1197
+ return this.entityToIndex;
1203
1198
  }
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;
1199
+ getComponentData(componentType) {
1200
+ const data = this.componentData.get(componentType);
1201
+ if (!data) throw new Error(`Component type ${componentType} is not in this archetype`);
1202
+ return data;
1214
1203
  }
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);
1204
+ getOptionalComponentData(componentType) {
1205
+ return this.componentData.get(componentType);
1226
1206
  }
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);
1207
+ getCachedComponentDataSources(componentTypes) {
1208
+ const cacheKey = buildCacheKey(componentTypes);
1209
+ return getOrCompute(this.componentDataSourcesCache, cacheKey, () => componentTypes.map((compType) => this.getComponentDataSource(compType)));
1210
+ }
1211
+ getComponentDataSource(compType) {
1212
+ const optional = isOptionalEntityId(compType);
1213
+ const actualType = optional ? compType.optional : compType;
1214
+ if (getIdType(actualType) === "wildcard-relation") {
1215
+ const componentId = getComponentIdFromRelationId(actualType);
1216
+ return getWildcardRelationDataSource(this.componentTypes, componentId, optional);
1232
1217
  }
1233
- return result;
1218
+ return optional ? this.getOptionalComponentData(actualType) : this.getComponentData(actualType);
1234
1219
  }
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;
1220
+ buildComponentsForIndex(componentTypes, componentDataSources, entityIndex, entityId) {
1221
+ return componentDataSources.map((dataSource, i) => buildSingleComponent(componentTypes[i], dataSource, entityIndex, entityId, (type) => this.getComponentData(type), this.sparseRelations));
1256
1222
  }
1257
- set(entityId, componentType, component) {
1258
- this.commands.push({
1259
- type: "set",
1260
- entityId,
1261
- componentType,
1262
- component
1263
- });
1223
+ getEntitiesWithComponents(componentTypes) {
1224
+ const result = [];
1225
+ this.appendEntitiesWithComponents(componentTypes, result);
1226
+ return result;
1264
1227
  }
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
1228
+ appendEntitiesWithComponents(componentTypes, result) {
1229
+ this.forEachWithComponents(componentTypes, (entity, ...components) => {
1230
+ result.push({
1231
+ entity,
1232
+ components
1233
+ });
1273
1234
  });
1274
1235
  }
1275
- /**
1276
- * Destroy an entity (deferred)
1277
- */
1278
- delete(entityId) {
1279
- this.commands.push({
1280
- type: "destroy",
1281
- entityId
1282
- });
1236
+ *iterateWithComponents(componentTypes) {
1237
+ const componentDataSources = this.getCachedComponentDataSources(componentTypes);
1238
+ for (let entityIndex = 0; entityIndex < this.entities.length; entityIndex++) {
1239
+ const entity = this.entities[entityIndex];
1240
+ yield [entity, ...this.buildComponentsForIndex(componentTypes, componentDataSources, entityIndex, entity)];
1241
+ }
1283
1242
  }
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();
1243
+ forEachWithComponents(componentTypes, callback) {
1244
+ const componentDataSources = this.getCachedComponentDataSources(componentTypes);
1245
+ for (let entityIndex = 0; entityIndex < this.entities.length; entityIndex++) {
1246
+ const entity = this.entities[entityIndex];
1247
+ callback(entity, ...this.buildComponentsForIndex(componentTypes, componentDataSources, entityIndex, entity));
1305
1248
  }
1306
- return iterations;
1307
1249
  }
1308
- /**
1309
- * Get current commands (for testing)
1310
- */
1311
- getCommands() {
1312
- return [...this.commands];
1250
+ forEach(callback) {
1251
+ for (let i = 0; i < this.entities.length; i++) {
1252
+ const entity = this.entities[i];
1253
+ const components = /* @__PURE__ */ new Map();
1254
+ for (const componentType of this.componentTypes) {
1255
+ const data = this.getComponentData(componentType)[i];
1256
+ components.set(componentType, data === MISSING_COMPONENT ? void 0 : data);
1257
+ }
1258
+ const sparseTuples = this.sparseRelations.getAllForEntity(entity);
1259
+ for (const [componentType, data] of sparseTuples) components.set(componentType, data);
1260
+ callback(entity, components);
1261
+ }
1313
1262
  }
1314
- /**
1315
- * Clear all commands
1316
- */
1317
- clear() {
1318
- this.commands = [];
1263
+ hasRelationWithComponentId(componentId) {
1264
+ for (const componentType of this.componentTypes) {
1265
+ const detailedType = getDetailedIdType(componentType);
1266
+ if (isRelationType(detailedType) && detailedType.componentId === componentId) return true;
1267
+ }
1268
+ for (const entityId of this.entities) if (this.sparseRelations.getRelationsForComponent(entityId, componentId).length > 0) return true;
1269
+ return false;
1319
1270
  }
1320
1271
  };
1321
1272
  //#endregion
1322
- //#region src/commands/changeset.ts
1273
+ //#region src/archetype/helpers.ts
1323
1274
  /**
1324
- * @internal Represents a set of component changes to be applied to an entity
1275
+ * Check if a components map has any wildcard relations matching a component ID
1276
+ * @param components - Component entity's components map
1277
+ * @param wildcardComponentId - The component ID to match
1278
+ * @returns True if at least one matching relation exists
1325
1279
  */
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);
1280
+ function hasWildcardRelation(components, wildcardComponentId) {
1281
+ for (const relId of components.keys()) if (isRelationId(relId)) {
1282
+ if (getComponentIdFromRelationId(relId) === wildcardComponentId) return true;
1342
1283
  }
1343
- /**
1344
- * Check if the changeset has any changes
1345
- */
1346
- hasChanges() {
1347
- return this.adds.size > 0 || this.removes.size > 0;
1284
+ return false;
1285
+ }
1286
+ /**
1287
+ * Check if a detailed type represents a relation (entity or component)
1288
+ */
1289
+ function isRelationType(detailedType) {
1290
+ return detailedType.type === "entity-relation" || detailedType.type === "component-relation";
1291
+ }
1292
+ /**
1293
+ * Check if a component type matches a given component ID for relations
1294
+ */
1295
+ function matchesRelationComponentId(componentType, componentId) {
1296
+ const detailedType = getDetailedIdType(componentType);
1297
+ return isRelationType(detailedType) && detailedType.componentId === componentId;
1298
+ }
1299
+ /**
1300
+ * Build cache key for component types
1301
+ */
1302
+ function buildCacheKey(componentTypes) {
1303
+ return componentTypes.map((id) => isOptionalEntityId(id) ? `opt(${id.optional})` : `${id}`).join(",");
1304
+ }
1305
+ /**
1306
+ * Get data source for wildcard relations from component types
1307
+ */
1308
+ function getWildcardRelationDataSource(componentTypes, componentId, optional) {
1309
+ const matchingRelations = componentTypes.filter((ct) => matchesRelationComponentId(ct, componentId));
1310
+ return optional ? matchingRelations.length > 0 ? matchingRelations : void 0 : matchingRelations;
1311
+ }
1312
+ /**
1313
+ * Build wildcard relation value from matching relations.
1314
+ * Receives the SparseStore directly for efficient per-component lookups.
1315
+ */
1316
+ function buildWildcardRelationValue(wildcardRelationType, matchingRelations, getDataAtIndex, sparseStore, entityId, optional) {
1317
+ const relations = [];
1318
+ const targetComponentId = getComponentIdFromRelationId(wildcardRelationType);
1319
+ for (const relType of matchingRelations || []) {
1320
+ const data = getDataAtIndex(relType);
1321
+ const targetId = getTargetIdFromRelationId(relType);
1322
+ relations.push([targetId, data === MISSING_COMPONENT ? void 0 : data]);
1348
1323
  }
1349
- /**
1350
- * Clear all changes
1351
- */
1352
- clear() {
1353
- this.adds.clear();
1354
- this.removes.clear();
1324
+ if (targetComponentId !== void 0) {
1325
+ const dfMatches = sparseStore.getRelationsForComponent(entityId, targetComponentId);
1326
+ for (const m of dfMatches) relations.push(m);
1355
1327
  }
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);
1328
+ if (relations.length === 0) {
1329
+ if (!optional) {
1330
+ const componentId = getComponentIdFromRelationId(wildcardRelationType);
1331
+ throw new Error(`No matching relations found for mandatory wildcard relation component ${componentId} on entity ${entityId}`);
1367
1332
  }
1333
+ return;
1368
1334
  }
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;
1335
+ return optional ? { value: relations } : relations;
1336
+ }
1337
+ /**
1338
+ * Build regular component value from data source
1339
+ */
1340
+ function buildRegularComponentValue(dataSource, entityIndex, optional) {
1341
+ if (dataSource === void 0) {
1342
+ if (optional) return void 0;
1343
+ throw new Error(`Component data not found for mandatory component type`);
1376
1344
  }
1377
- };
1345
+ const data = dataSource[entityIndex];
1346
+ const result = data === MISSING_COMPONENT ? void 0 : data;
1347
+ return optional ? { value: result } : result;
1348
+ }
1349
+ /**
1350
+ * Build a single component value based on its type
1351
+ */
1352
+ function buildSingleComponent(compType, dataSource, entityIndex, entityId, getComponentData, sparseRelations) {
1353
+ const optional = isOptionalEntityId(compType);
1354
+ const actualType = optional ? compType.optional : compType;
1355
+ if (getIdType(actualType) === "wildcard-relation") return buildWildcardRelationValue(actualType, dataSource, (relType) => getComponentData(relType)[entityIndex], sparseRelations, entityId, optional);
1356
+ else return buildRegularComponentValue(dataSource, entityIndex, optional);
1357
+ }
1378
1358
  //#endregion
1379
1359
  //#region src/component/entity-store.ts
1380
1360
  /**
@@ -1928,84 +1908,305 @@ function maybeRemoveWildcardMarker(entityId, archetype, removedComponentType, co
1928
1908
  if (changeset.removes.has(otherComponentType)) continue;
1929
1909
  if (getComponentIdFromRelationId(otherComponentType) === componentId) return;
1930
1910
  }
1931
- const sparseData = archetype.getEntitySparseRelations(entityId);
1932
- if (sparseData) for (const otherComponentType of sparseData.keys()) {
1933
- if (otherComponentType === removedComponentType) continue;
1934
- if (changeset.removes.has(otherComponentType)) continue;
1935
- if (getComponentIdFromRelationId(otherComponentType) === componentId) return;
1911
+ const sparseData = archetype.getEntitySparseRelations(entityId);
1912
+ if (sparseData) for (const otherComponentType of sparseData.keys()) {
1913
+ if (otherComponentType === removedComponentType) continue;
1914
+ if (changeset.removes.has(otherComponentType)) continue;
1915
+ if (getComponentIdFromRelationId(otherComponentType) === componentId) return;
1916
+ }
1917
+ for (const addedType of changeset.adds.keys()) {
1918
+ if (addedType === removedComponentType) continue;
1919
+ if (getComponentIdFromRelationId(addedType) === componentId) return;
1920
+ }
1921
+ changeset.delete(wildcardMarker);
1922
+ }
1923
+ function hasEntityComponent(archetype, entityId, componentType) {
1924
+ if (archetype.componentTypeSet.has(componentType)) return true;
1925
+ return archetype.getEntitySparseRelations(entityId)?.has(componentType) ?? false;
1926
+ }
1927
+ function pruneMissingRemovals(changeset, archetype, entityId) {
1928
+ let toPrune;
1929
+ for (const componentType of changeset.removes) if (!hasEntityComponent(archetype, entityId, componentType)) {
1930
+ if (toPrune === void 0) toPrune = [];
1931
+ toPrune.push(componentType);
1932
+ }
1933
+ if (toPrune !== void 0) for (const componentType of toPrune) changeset.removes.delete(componentType);
1934
+ }
1935
+ function hasArchetypeStructuralChange(changeset, currentArchetype) {
1936
+ for (const componentType of changeset.removes) if (!isSparseRelation(componentType) && currentArchetype.componentTypeSet.has(componentType)) return true;
1937
+ for (const componentType of changeset.adds.keys()) if (!isSparseRelation(componentType) && !currentArchetype.componentTypeSet.has(componentType)) return true;
1938
+ return false;
1939
+ }
1940
+ function buildFinalRegularComponentTypes(currentArchetype, changeset) {
1941
+ const finalRegularTypes = new Set(currentArchetype.componentTypes);
1942
+ for (const componentType of changeset.removes) if (!isSparseRelation(componentType)) finalRegularTypes.delete(componentType);
1943
+ for (const [componentType] of changeset.adds) if (!isSparseRelation(componentType)) finalRegularTypes.add(componentType);
1944
+ return Array.from(finalRegularTypes);
1945
+ }
1946
+ function applyChangeset(ctx, entityId, currentArchetype, changeset, entityToArchetype, removedComponents) {
1947
+ pruneMissingRemovals(changeset, currentArchetype, entityId);
1948
+ if (hasArchetypeStructuralChange(changeset, currentArchetype)) {
1949
+ const finalRegularTypes = buildFinalRegularComponentTypes(currentArchetype, changeset);
1950
+ const newArchetype = ctx.ensureArchetype(finalRegularTypes);
1951
+ const currentComponents = currentArchetype.removeEntity(entityId);
1952
+ if (removedComponents !== null) for (const componentType of changeset.removes) removedComponents.set(componentType, currentComponents.get(componentType));
1953
+ newArchetype.addEntity(entityId, changeset.applyTo(currentComponents));
1954
+ entityToArchetype.set(entityId, newArchetype);
1955
+ return newArchetype;
1956
+ }
1957
+ if (removedComponents !== null) applySparseChanges(ctx.sparseStore, entityId, changeset, removedComponents);
1958
+ else applySparseChangesNoHooks(ctx.sparseStore, entityId, changeset);
1959
+ for (const [componentType, component] of changeset.adds) {
1960
+ if (isSparseRelation(componentType)) continue;
1961
+ currentArchetype.set(entityId, componentType, component);
1962
+ }
1963
+ return currentArchetype;
1964
+ }
1965
+ function applySparseChanges(sparseStore, entityId, changeset, removedComponents) {
1966
+ for (const componentType of changeset.removes) if (isSparseRelation(componentType)) {
1967
+ const removedValue = sparseStore.getValue(entityId, componentType);
1968
+ if (removedValue !== void 0 || sparseStore.getAllForEntity(entityId).some(([t]) => t === componentType)) removedComponents.set(componentType, removedValue);
1969
+ sparseStore.deleteValue(entityId, componentType);
1970
+ }
1971
+ for (const [componentType, component] of changeset.adds) if (isSparseRelation(componentType)) sparseStore.setValue(entityId, componentType, component);
1972
+ }
1973
+ function applySparseChangesNoHooks(sparseStore, entityId, changeset) {
1974
+ for (const componentType of changeset.removes) if (isSparseRelation(componentType)) sparseStore.deleteValue(entityId, componentType);
1975
+ for (const [componentType, component] of changeset.adds) if (isSparseRelation(componentType)) sparseStore.setValue(entityId, componentType, component);
1976
+ }
1977
+ function filterRegularComponentTypes(componentTypes) {
1978
+ const regularTypes = [];
1979
+ for (const componentType of componentTypes) {
1980
+ if (isSparseWildcard(componentType)) {
1981
+ regularTypes.push(componentType);
1982
+ continue;
1983
+ }
1984
+ if (isSparseRelation(componentType)) continue;
1985
+ regularTypes.push(componentType);
1986
+ }
1987
+ return regularTypes;
1988
+ }
1989
+ //#endregion
1990
+ //#region src/world/archetype-manager.ts
1991
+ /**
1992
+ * Encapsulates all archetype storage, indexing, creation, removal, and reverse
1993
+ * referencing logic that was previously scattered as private methods + maps
1994
+ * directly on the World class.
1995
+ *
1996
+ * Responsibilities:
1997
+ * - Archetype memoization by signature
1998
+ * - Component-type reverse index (archetypesByComponent)
1999
+ * - Entity → current Archetype map
2000
+ * - Reverse "who references this entity via component/relation" index
2001
+ * - Creation + removal with proper notifications to QueryRegistry + hook matching
2002
+ * - Cleanup of empty archetypes after entity cascades
2003
+ *
2004
+ * This extraction shrinks World while keeping the same behavior and hot-path characteristics.
2005
+ */
2006
+ var ArchetypeManager = class {
2007
+ archetypes = [];
2008
+ archetypeBySignature = /* @__PURE__ */ new Map();
2009
+ entityToArchetype = /* @__PURE__ */ new Map();
2010
+ archetypesByComponent = /* @__PURE__ */ new Map();
2011
+ entityToReferencingArchetypes = /* @__PURE__ */ new Map();
2012
+ sparseStore;
2013
+ ctx;
2014
+ constructor(ctx, sparseStore) {
2015
+ this.ctx = ctx;
2016
+ this.sparseStore = sparseStore;
2017
+ }
2018
+ /** Primary entry point — memoized archetype creation/lookup. */
2019
+ ensureArchetype(componentTypes) {
2020
+ const sortedTypes = normalizeComponentTypes(filterRegularComponentTypes(componentTypes));
2021
+ const hashKey = this.createArchetypeSignature(sortedTypes);
2022
+ return getOrCompute(this.archetypeBySignature, hashKey, () => this.createNewArchetype(sortedTypes));
2023
+ }
2024
+ getArchetypeForEntity(entityId) {
2025
+ return this.entityToArchetype.get(entityId);
2026
+ }
2027
+ setEntityToArchetype(entityId, archetype) {
2028
+ this.entityToArchetype.set(entityId, archetype);
2029
+ }
2030
+ getMatchingArchetypes(componentTypes) {
2031
+ if (componentTypes.length === 0) return [...this.archetypes];
2032
+ const regularComponents = [];
2033
+ const wildcardRelations = [];
2034
+ for (const componentType of componentTypes) if (isWildcardRelationId(componentType)) {
2035
+ const componentId = getComponentIdFromRelationId(componentType);
2036
+ if (componentId !== void 0) wildcardRelations.push({
2037
+ componentId,
2038
+ relationId: componentType
2039
+ });
2040
+ } else regularComponents.push(componentType);
2041
+ let matchingArchetypes = this.getArchetypesWithComponents(regularComponents);
2042
+ for (const { componentId, relationId } of wildcardRelations) {
2043
+ const markerSet = this.archetypesByComponent.get(relationId);
2044
+ const archetypesWithMarker = markerSet ? Array.from(markerSet) : [];
2045
+ matchingArchetypes = matchingArchetypes.length === 0 ? archetypesWithMarker : matchingArchetypes.filter((a) => markerSet?.has(a) || a.hasRelationWithComponentId(componentId));
2046
+ }
2047
+ return matchingArchetypes;
2048
+ }
2049
+ getArchetypesWithComponents(componentTypes) {
2050
+ if (componentTypes.length === 0) return [...this.archetypes];
2051
+ if (componentTypes.length === 1) {
2052
+ const set = this.archetypesByComponent.get(componentTypes[0]);
2053
+ return set ? Array.from(set) : [];
2054
+ }
2055
+ const sets = componentTypes.map((type) => this.archetypesByComponent.get(type)).filter((s) => s !== void 0 && s.size > 0).sort((a, b) => a.size - b.size);
2056
+ if (sets.length === 0) return [];
2057
+ if (sets.length < componentTypes.length) return [];
2058
+ const smallest = sets[0];
2059
+ if (sets.length === 2) {
2060
+ const other = sets[1];
2061
+ return Array.from(smallest).filter((a) => other.has(a));
2062
+ }
2063
+ let result = new Set(smallest);
2064
+ for (let i = 1; i < sets.length; i++) {
2065
+ for (const item of result) if (!sets[i].has(item)) result.delete(item);
2066
+ if (result.size === 0) return [];
2067
+ }
2068
+ return Array.from(result);
2069
+ }
2070
+ createArchetypeSignature(componentTypes) {
2071
+ return componentTypes.join(",");
2072
+ }
2073
+ /** Deduplicated version of the original pair of methods. */
2074
+ updateReferencingIndex(componentType, archetype, isAdd) {
2075
+ const detailedType = getDetailedIdType(componentType);
2076
+ let entityId;
2077
+ if (detailedType.type === "entity") entityId = componentType;
2078
+ else if (detailedType.type === "entity-relation") entityId = detailedType.targetId;
2079
+ if (entityId !== void 0) {
2080
+ let refs = this.entityToReferencingArchetypes.get(entityId);
2081
+ if (isAdd) {
2082
+ if (!refs) {
2083
+ refs = /* @__PURE__ */ new Set();
2084
+ this.entityToReferencingArchetypes.set(entityId, refs);
2085
+ }
2086
+ refs.add(archetype);
2087
+ } else if (refs) {
2088
+ refs.delete(archetype);
2089
+ if (refs.size === 0) this.entityToReferencingArchetypes.delete(entityId);
2090
+ }
2091
+ }
2092
+ }
2093
+ createNewArchetype(componentTypes) {
2094
+ const newArchetype = new Archetype(componentTypes, this.sparseStore);
2095
+ this.archetypes.push(newArchetype);
2096
+ this.ctx.recordArchetypeCreated?.();
2097
+ for (const componentType of componentTypes) {
2098
+ let archetypes = this.archetypesByComponent.get(componentType);
2099
+ if (!archetypes) {
2100
+ archetypes = /* @__PURE__ */ new Set();
2101
+ this.archetypesByComponent.set(componentType, archetypes);
2102
+ }
2103
+ archetypes.add(newArchetype);
2104
+ this.updateReferencingIndex(componentType, newArchetype, true);
2105
+ }
2106
+ this.ctx.queryRegistry.onNewArchetype(newArchetype);
2107
+ this.updateArchetypeHookMatches(newArchetype);
2108
+ return newArchetype;
2109
+ }
2110
+ updateArchetypeHookMatches(archetype) {
2111
+ for (const entry of this.ctx.hooks) if (this.archetypeMatchesHook(archetype, entry)) {
2112
+ archetype.matchingMultiHooks.add(entry);
2113
+ if (entry.matchedArchetypes) entry.matchedArchetypes.add(archetype);
2114
+ }
2115
+ }
2116
+ archetypeMatchesHook(archetype, entry) {
2117
+ return entry.requiredComponents.every((c) => {
2118
+ if (isWildcardRelationId(c)) {
2119
+ if (isSparseWildcard(c)) return true;
2120
+ const componentId = getComponentIdFromRelationId(c);
2121
+ return componentId !== void 0 && archetype.hasRelationWithComponentId(componentId);
2122
+ }
2123
+ return archetype.componentTypeSet.has(c) || isSparseRelation(c);
2124
+ }) && matchesFilter(archetype, entry.filter);
2125
+ }
2126
+ /** Called during cascade deletion cleanup. */
2127
+ cleanupArchetypesReferencingEntity(entityId) {
2128
+ const refs = this.entityToReferencingArchetypes.get(entityId);
2129
+ if (!refs) return;
2130
+ for (const archetype of refs) if (archetype.getEntities().length === 0) this.removeArchetype(archetype);
2131
+ this.entityToReferencingArchetypes.delete(entityId);
1936
2132
  }
1937
- for (const addedType of changeset.adds.keys()) {
1938
- if (addedType === removedComponentType) continue;
1939
- if (getComponentIdFromRelationId(addedType) === componentId) return;
2133
+ removeArchetype(archetype) {
2134
+ const index = this.archetypes.indexOf(archetype);
2135
+ if (index !== -1) {
2136
+ const last = this.archetypes[this.archetypes.length - 1];
2137
+ this.archetypes[index] = last;
2138
+ this.archetypes.pop();
2139
+ }
2140
+ this.ctx.recordArchetypeRemoved?.();
2141
+ this.archetypeBySignature.delete(this.createArchetypeSignature(archetype.componentTypes));
2142
+ for (const componentType of archetype.componentTypes) {
2143
+ const archetypes = this.archetypesByComponent.get(componentType);
2144
+ if (archetypes) {
2145
+ archetypes.delete(archetype);
2146
+ if (archetypes.size === 0) this.archetypesByComponent.delete(componentType);
2147
+ }
2148
+ this.updateReferencingIndex(componentType, archetype, false);
2149
+ }
2150
+ this.ctx.queryRegistry.onArchetypeRemoved(archetype);
1940
2151
  }
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);
2152
+ };
2153
+ //#endregion
2154
+ //#region src/commands/changeset.ts
2155
+ /**
2156
+ * @internal Represents a set of component changes to be applied to an entity
2157
+ */
2158
+ var ComponentChangeset = class {
2159
+ adds = /* @__PURE__ */ new Map();
2160
+ removes = /* @__PURE__ */ new Set();
2161
+ /**
2162
+ * Add a component to the changeset
2163
+ */
2164
+ set(componentType, component) {
2165
+ this.adds.set(componentType, component);
2166
+ this.removes.delete(componentType);
1952
2167
  }
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;
2168
+ /**
2169
+ * Remove a component from the changeset
2170
+ */
2171
+ delete(componentType) {
2172
+ this.removes.add(componentType);
2173
+ this.adds.delete(componentType);
1976
2174
  }
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);
2175
+ /**
2176
+ * Check if the changeset has any changes
2177
+ */
2178
+ hasChanges() {
2179
+ return this.adds.size > 0 || this.removes.size > 0;
1982
2180
  }
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);
2181
+ /**
2182
+ * Clear all changes
2183
+ */
2184
+ clear() {
2185
+ this.adds.clear();
2186
+ this.removes.clear();
1990
2187
  }
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;
2188
+ /**
2189
+ * Merge another changeset into this one
2190
+ */
2191
+ merge(other) {
2192
+ for (const [componentType, component] of other.adds) {
2193
+ this.adds.set(componentType, component);
2194
+ this.removes.delete(componentType);
2195
+ }
2196
+ for (const componentType of other.removes) {
2197
+ this.removes.add(componentType);
2198
+ this.adds.delete(componentType);
2003
2199
  }
2004
- if (isSparseRelation(componentType)) continue;
2005
- regularTypes.push(componentType);
2006
2200
  }
2007
- return regularTypes;
2008
- }
2201
+ /**
2202
+ * Apply the changeset to existing components and return the final state
2203
+ */
2204
+ applyTo(existingComponents) {
2205
+ for (const componentType of this.removes) existingComponents.delete(componentType);
2206
+ for (const [componentType, component] of this.adds) existingComponents.set(componentType, component);
2207
+ return existingComponents;
2208
+ }
2209
+ };
2009
2210
  //#endregion
2010
2211
  //#region src/world/hooks.ts
2011
2212
  /**
@@ -2310,8 +2511,272 @@ function untrackEntityReference(entityReferences, sourceEntityId, componentType,
2310
2511
  *
2311
2512
  * @internal
2312
2513
  */
2313
- function getEntityReferences(entityReferences, targetEntityId) {
2314
- return entityReferences.get(targetEntityId) ?? new MultiMap();
2514
+ function getEntityReferences(entityReferences, targetEntityId) {
2515
+ return entityReferences.get(targetEntityId) ?? new MultiMap();
2516
+ }
2517
+ //#endregion
2518
+ //#region src/world/command-executor.ts
2519
+ /**
2520
+ * Encapsulates the command execution pipeline, reusable changesets,
2521
+ * and related orchestration that was previously private methods + fields on World.
2522
+ *
2523
+ * Responsibilities:
2524
+ * - executeEntityCommands (routing for singletons / destroy / structural changes)
2525
+ * - applyEntityCommands (changeset processing + exclusive relations + apply + refs + hooks)
2526
+ * - removeComponentImmediate (used by cascade deletion)
2527
+ * - updateEntityReferences (keeps the reverse index in sync)
2528
+ *
2529
+ * This extraction significantly reduces World line count while preserving
2530
+ * every fast-path branch and allocation-avoidance characteristic.
2531
+ */
2532
+ var CommandExecutor = class {
2533
+ ctx;
2534
+ _changeset = new ComponentChangeset();
2535
+ _removeChangeset = new ComponentChangeset();
2536
+ _commandCtx;
2537
+ _hooksCtx;
2538
+ constructor(ctx) {
2539
+ this.ctx = ctx;
2540
+ this._commandCtx = {
2541
+ sparseStore: ctx.sparseStore,
2542
+ ensureArchetype: ctx.ensureArchetype
2543
+ };
2544
+ this._hooksCtx = {
2545
+ multiHooks: ctx.hooks,
2546
+ has: ctx.has,
2547
+ get: ctx.get,
2548
+ getOptional: ctx.getOptional
2549
+ };
2550
+ }
2551
+ /**
2552
+ * Entry point used by the CommandBuffer.
2553
+ * Routes to singleton handling, destroy fast path, or structural apply.
2554
+ */
2555
+ executeEntityCommands(entityId, commands) {
2556
+ this._changeset.clear();
2557
+ if (this.ctx.componentEntities.exists(entityId)) {
2558
+ this.ctx.componentEntities.executeCommands(entityId, commands);
2559
+ return;
2560
+ }
2561
+ if (commands.some((cmd) => cmd.type === "destroy")) {
2562
+ this.ctx.destroyEntityImmediate(entityId);
2563
+ return;
2564
+ }
2565
+ this.applyEntityCommands(entityId, commands);
2566
+ }
2567
+ applyEntityCommands(entityId, commands) {
2568
+ const currentArchetype = this.ctx.entityToArchetype.get(entityId);
2569
+ if (!currentArchetype) return;
2570
+ const changeset = this._changeset;
2571
+ processCommands(entityId, currentArchetype, commands, changeset, (eid, arch, compId) => {
2572
+ if (isExclusiveComponent(compId)) removeMatchingRelations(eid, arch, compId, changeset);
2573
+ });
2574
+ const hasStructuralChange = changeset.removes.size > 0 || changeset.adds.size > 0;
2575
+ if (this.ctx.hooks.size === 0) {
2576
+ const newArchetype = applyChangeset(this._commandCtx, entityId, currentArchetype, changeset, this.ctx.entityToArchetype, null);
2577
+ if (hasStructuralChange) this.updateEntityReferences(entityId, changeset);
2578
+ if (newArchetype !== currentArchetype) this.ctx.incrementMigrations();
2579
+ return;
2580
+ }
2581
+ const removedComponents = /* @__PURE__ */ new Map();
2582
+ const newArchetype = applyChangeset(this._commandCtx, entityId, currentArchetype, changeset, this.ctx.entityToArchetype, removedComponents);
2583
+ if (hasStructuralChange) this.updateEntityReferences(entityId, changeset);
2584
+ if (newArchetype !== currentArchetype) this.ctx.incrementMigrations();
2585
+ this.ctx.triggerLifecycleHooks(this._hooksCtx, entityId, changeset.adds, removedComponents, currentArchetype, newArchetype);
2586
+ }
2587
+ /**
2588
+ * Immediate (non-buffered) component removal used during cascade deletion.
2589
+ * Called from destroy* paths (which remain in World).
2590
+ */
2591
+ removeComponentImmediate(entityId, componentType, targetEntityId) {
2592
+ const sourceArchetype = this.ctx.entityToArchetype.get(entityId);
2593
+ if (!sourceArchetype) return;
2594
+ const changeset = this._removeChangeset;
2595
+ changeset.clear();
2596
+ changeset.delete(componentType);
2597
+ maybeRemoveWildcardMarker(entityId, sourceArchetype, componentType, getComponentIdFromRelationId(componentType), changeset);
2598
+ const removedComponent = sourceArchetype.get(entityId, componentType);
2599
+ const newArchetype = applyChangeset(this._commandCtx, entityId, sourceArchetype, changeset, this.ctx.entityToArchetype, null);
2600
+ untrackEntityReference(this.ctx.entityReferences, entityId, componentType, targetEntityId);
2601
+ this.ctx.triggerLifecycleHooks(this._hooksCtx, entityId, /* @__PURE__ */ new Map(), new Map([[componentType, removedComponent]]), sourceArchetype, newArchetype);
2602
+ }
2603
+ /**
2604
+ * Keeps the entity reference reverse index in sync after structural changes.
2605
+ * Called from apply paths.
2606
+ */
2607
+ updateEntityReferences(entityId, changeset) {
2608
+ for (const componentType of changeset.removes) if (isEntityRelation(componentType)) {
2609
+ const targetId = getTargetIdFromRelationId(componentType);
2610
+ untrackEntityReference(this.ctx.entityReferences, entityId, componentType, targetId);
2611
+ } else if (componentType >= 1024) untrackEntityReference(this.ctx.entityReferences, entityId, componentType, componentType);
2612
+ for (const [componentType] of changeset.adds) if (isEntityRelation(componentType)) {
2613
+ const targetId = getTargetIdFromRelationId(componentType);
2614
+ trackEntityReference(this.ctx.entityReferences, entityId, componentType, targetId);
2615
+ } else if (componentType >= 1024) trackEntityReference(this.ctx.entityReferences, entityId, componentType, componentType);
2616
+ }
2617
+ /**
2618
+ * Exposed for any future direct needs (currently not required outside the executor).
2619
+ */
2620
+ getHooksContext() {
2621
+ return this._hooksCtx;
2622
+ }
2623
+ };
2624
+ //#endregion
2625
+ //#region src/world/debug-stats.ts
2626
+ /**
2627
+ * Manages debug stats collectors and transient activity counters for World#sync().
2628
+ *
2629
+ * Extracted from World to shrink the main class while keeping the entire debug/observability
2630
+ * path isolated, zero-cost when no collectors are active, and easy to test/maintain.
2631
+ *
2632
+ * Follows the same context/callback injection style as ArchetypeManager, CommandProcessorContext,
2633
+ * and HooksContext to avoid tight coupling.
2634
+ *
2635
+ * All collectors receive the *exact same* stats object for a given sync (as before).
2636
+ * Exceptions in user callbacks are swallowed (as before).
2637
+ */
2638
+ var DebugStatsManager = class {
2639
+ collectors = /* @__PURE__ */ new Set();
2640
+ migrations = 0;
2641
+ archetypesCreated = 0;
2642
+ archetypesRemoved = 0;
2643
+ /** Fast check used to arm timing + reset + counting in hot paths. */
2644
+ hasActiveCollectors() {
2645
+ return this.collectors.size > 0;
2646
+ }
2647
+ /**
2648
+ * Registers a collector. Returns a disposable handle (supports `using`).
2649
+ * Collection stops when the handle is disposed.
2650
+ */
2651
+ createCollector(callback) {
2652
+ this.collectors.add(callback);
2653
+ return { [Symbol.dispose]: () => {
2654
+ this.collectors.delete(callback);
2655
+ } };
2656
+ }
2657
+ recordArchetypeCreated() {
2658
+ if (this.hasActiveCollectors()) this.archetypesCreated++;
2659
+ }
2660
+ recordArchetypeRemoved() {
2661
+ if (this.hasActiveCollectors()) this.archetypesRemoved++;
2662
+ }
2663
+ incrementMigrations() {
2664
+ if (this.hasActiveCollectors()) this.migrations++;
2665
+ }
2666
+ /** Reset all activity counters + the shared hook execution counter. Called at start of an armed sync. */
2667
+ resetActivity() {
2668
+ this.migrations = 0;
2669
+ this.archetypesCreated = 0;
2670
+ this.archetypesRemoved = 0;
2671
+ debugHookExecutionCounter.value = 0;
2672
+ }
2673
+ /**
2674
+ * Build and deliver a SyncDebugStats payload to every active collector.
2675
+ * World supplies the pre-computed snapshot numbers (keeps debug manager decoupled from
2676
+ * internal World maps/registries while preserving exact original stats shape and values).
2677
+ */
2678
+ deliver(timings, data) {
2679
+ const stats = {
2680
+ timestamps: {
2681
+ syncStart: timings.syncStart,
2682
+ syncEnd: timings.syncEnd,
2683
+ commandBufferStart: timings.commandBufferStart,
2684
+ commandBufferEnd: timings.commandBufferEnd
2685
+ },
2686
+ commandIterations: timings.commandIterations,
2687
+ entities: {
2688
+ total: data.entityCount,
2689
+ freelistSize: data.freelistSize,
2690
+ nextId: data.nextId
2691
+ },
2692
+ archetypes: {
2693
+ total: data.archetypeCount,
2694
+ empty: data.emptyArchetypes
2695
+ },
2696
+ queries: {
2697
+ cached: data.cachedQueryCount,
2698
+ registered: data.registeredQueryCount
2699
+ },
2700
+ hooks: { total: data.hookCount },
2701
+ indices: {
2702
+ entityReferences: data.entityReferencesSize,
2703
+ entityToReferencingArchetypes: data.entityToReferencingArchetypesSize,
2704
+ archetypesByComponent: data.archetypesByComponentSize
2705
+ },
2706
+ activity: {
2707
+ migrations: this.migrations,
2708
+ hooksExecuted: debugHookExecutionCounter.value,
2709
+ archetypesCreated: this.archetypesCreated,
2710
+ archetypesRemoved: this.archetypesRemoved
2711
+ }
2712
+ };
2713
+ for (const cb of this.collectors) try {
2714
+ cb(stats);
2715
+ } catch {}
2716
+ }
2717
+ };
2718
+ //#endregion
2719
+ //#region src/world/operations.ts
2720
+ /**
2721
+ * Validation and overload-resolution helpers extracted from World.
2722
+ *
2723
+ * These were previously private methods on World. Moving them reduces line count
2724
+ * in the core class with almost zero coupling (the only dep is a liveness predicate
2725
+ * for assertEntityExists, supplied by the caller).
2726
+ *
2727
+ * Pure type checks (assert*TypeValid) and the resolve* helpers for set/remove
2728
+ * overloads live here.
2729
+ */
2730
+ /**
2731
+ * Assert that an entity (or component-entity) is alive in the world.
2732
+ * The caller supplies the liveness check (World.exists or equivalent) to keep
2733
+ * this module free of direct references to stores.
2734
+ */
2735
+ function assertEntityExists(entityId, label, exists) {
2736
+ if (!exists(entityId)) throw new Error(`${label} ${entityId} does not exist`);
2737
+ }
2738
+ function assertComponentTypeValid(componentType) {
2739
+ if (getDetailedIdType(componentType).type === "invalid") throw new Error(`Invalid component type: ${componentType}`);
2740
+ }
2741
+ function assertSetComponentTypeValid(componentType) {
2742
+ const detailedType = getDetailedIdType(componentType);
2743
+ if (detailedType.type === "invalid") throw new Error(`Invalid component type: ${componentType}`);
2744
+ if (detailedType.type === "wildcard-relation") throw new Error(`Cannot directly add wildcard relation components: ${componentType}`);
2745
+ }
2746
+ /**
2747
+ * Resolve the (entity, componentType, value) for a set() call.
2748
+ */
2749
+ function resolveSetOperation(entityId, componentTypeOrComponent, maybeComponent, exists = () => true) {
2750
+ const targetEntityId = entityId;
2751
+ const componentType = componentTypeOrComponent;
2752
+ assertEntityExists(targetEntityId, "Entity", exists);
2753
+ assertSetComponentTypeValid(componentType);
2754
+ return {
2755
+ entityId: targetEntityId,
2756
+ componentType,
2757
+ component: maybeComponent
2758
+ };
2759
+ }
2760
+ /**
2761
+ * Resolve the (entity, componentType) for a remove() call, handling the
2762
+ * singleton component overload (remove(componentId)).
2763
+ */
2764
+ function resolveRemoveOperation(entityId, componentType, exists = () => true) {
2765
+ if (componentType === void 0) {
2766
+ const componentId = entityId;
2767
+ assertEntityExists(componentId, "Component entity", exists);
2768
+ return {
2769
+ entityId: componentId,
2770
+ componentType: componentId
2771
+ };
2772
+ }
2773
+ const targetEntityId = entityId;
2774
+ assertEntityExists(targetEntityId, "Entity", exists);
2775
+ assertComponentTypeValid(componentType);
2776
+ return {
2777
+ entityId: targetEntityId,
2778
+ componentType
2779
+ };
2315
2780
  }
2316
2781
  //#endregion
2317
2782
  //#region src/storage/serialization.ts
@@ -2481,49 +2946,60 @@ function deserializeWorld(ctx, snapshot) {
2481
2946
  */
2482
2947
  var World = class {
2483
2948
  entityIdManager = new EntityIdManager();
2484
- archetypes = [];
2485
- archetypeBySignature = /* @__PURE__ */ new Map();
2486
- entityToArchetype = /* @__PURE__ */ new Map();
2487
- archetypesByComponent = /* @__PURE__ */ new Map();
2488
2949
  entityReferences = /* @__PURE__ */ new Map();
2489
- /** Reverse index: entity ID → set of archetypes whose componentTypes include that entity ID */
2490
- entityToReferencingArchetypes = /* @__PURE__ */ new Map();
2491
2950
  /** Sparse relation storage (for components created with `sparse: true`), shared with all Archetype instances */
2492
2951
  sparseStore = new SparseStoreImpl();
2493
2952
  /** Component entity (singleton) storage */
2494
2953
  componentEntities = new ComponentEntityStore();
2954
+ archetypeManager;
2955
+ get archetypes() {
2956
+ return this.archetypeManager.archetypes;
2957
+ }
2958
+ get entityToArchetype() {
2959
+ return this.archetypeManager.entityToArchetype;
2960
+ }
2961
+ get archetypesByComponent() {
2962
+ return this.archetypeManager.archetypesByComponent;
2963
+ }
2964
+ get entityToReferencingArchetypes() {
2965
+ return this.archetypeManager.entityToReferencingArchetypes;
2966
+ }
2495
2967
  queryRegistry = new QueryRegistry();
2496
2968
  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
- };
2969
+ debugStats = new DebugStatsManager();
2970
+ commandBuffer;
2971
+ commandExecutor;
2516
2972
  constructor(snapshot) {
2973
+ this.archetypeManager = new ArchetypeManager({
2974
+ queryRegistry: this.queryRegistry,
2975
+ hooks: this.hooks,
2976
+ recordArchetypeCreated: () => this.debugStats.recordArchetypeCreated(),
2977
+ recordArchetypeRemoved: () => this.debugStats.recordArchetypeRemoved()
2978
+ }, this.sparseStore);
2517
2979
  if (snapshot && typeof snapshot === "object") deserializeWorld({
2518
2980
  entityIdManager: this.entityIdManager,
2519
2981
  componentEntities: this.componentEntities,
2520
2982
  entityReferences: this.entityReferences,
2521
2983
  ensureArchetype: (ct) => this.ensureArchetype(ct),
2522
- setEntityToArchetype: (eid, arch) => this.entityToArchetype.set(eid, arch)
2984
+ setEntityToArchetype: (eid, arch) => this.archetypeManager.entityToArchetype.set(eid, arch)
2523
2985
  }, snapshot);
2524
- }
2525
- createArchetypeSignature(componentTypes) {
2526
- return componentTypes.join(",");
2986
+ const execCtx = {
2987
+ componentEntities: this.componentEntities,
2988
+ entityReferences: this.entityReferences,
2989
+ hooks: this.hooks,
2990
+ entityToArchetype: this.entityToArchetype,
2991
+ ensureArchetype: (ct) => this.ensureArchetype(ct),
2992
+ sparseStore: this.sparseStore,
2993
+ has: (eid, ct) => this.has(eid, ct),
2994
+ get: (eid, ct) => this.get(eid, ct),
2995
+ getOptional: (eid, ct) => this.getOptional(eid, ct),
2996
+ destroyEntityImmediate: (eid) => this.destroyEntityImmediate(eid),
2997
+ incrementMigrations: () => this.debugStats.incrementMigrations(),
2998
+ triggerLifecycleHooks,
2999
+ triggerRemoveHooksForEntityDeletion
3000
+ };
3001
+ this.commandExecutor = new CommandExecutor(execCtx);
3002
+ this.commandBuffer = new CommandBuffer((entityId, commands) => this.commandExecutor.executeEntityCommands(entityId, commands));
2527
3003
  }
2528
3004
  /**
2529
3005
  * Creates a new entity.
@@ -2619,64 +3095,12 @@ var World = class {
2619
3095
  if (this.componentEntities.exists(entityId)) return true;
2620
3096
  return this.entityToArchetype.has(entityId);
2621
3097
  }
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
3098
  set(entityId, componentTypeOrComponent, maybeComponent) {
2675
- const { entityId: targetEntityId, componentType, component } = this.resolveSetOperation(entityId, componentTypeOrComponent, maybeComponent);
3099
+ const { entityId: targetEntityId, componentType, component } = resolveSetOperation(entityId, componentTypeOrComponent, maybeComponent, (id) => this.exists(id));
2676
3100
  this.commandBuffer.set(targetEntityId, componentType, component);
2677
3101
  }
2678
3102
  remove(entityId, componentType) {
2679
- const { entityId: targetEntityId, componentType: targetComponentType } = this.resolveRemoveOperation(entityId, componentType);
3103
+ const { entityId: targetEntityId, componentType: targetComponentType } = resolveRemoveOperation(entityId, componentType, (id) => this.exists(id));
2680
3104
  this.commandBuffer.remove(targetEntityId, targetComponentType);
2681
3105
  }
2682
3106
  /**
@@ -2693,6 +3117,30 @@ var World = class {
2693
3117
  delete(entityId) {
2694
3118
  this.commandBuffer.delete(entityId);
2695
3119
  }
3120
+ /**
3121
+ * Returns an explicit handle for a singleton component (component-as-entity).
3122
+ *
3123
+ * This is the preferred API for singleton components.
3124
+ *
3125
+ * @example
3126
+ * const config = world.singleton(GlobalConfig);
3127
+ * config.set({ debug: true });
3128
+ * world.sync();
3129
+ * console.log(config.get());
3130
+ */
3131
+ singleton(componentId) {
3132
+ assertEntityExists(componentId, "Component entity", (id) => this.exists(id));
3133
+ assertSetComponentTypeValid(componentId);
3134
+ return new SingletonHandle(componentId, {
3135
+ has: () => this.componentEntities.hasSingleton(componentId),
3136
+ get: () => this.get(componentId),
3137
+ getOptional: () => this.getOptional(componentId),
3138
+ remove: () => this.commandBuffer.remove(componentId, componentId),
3139
+ set: (value) => {
3140
+ this.commandBuffer.set(componentId, componentId, value);
3141
+ }
3142
+ });
3143
+ }
2696
3144
  has(entityId, componentType) {
2697
3145
  if (componentType === void 0) {
2698
3146
  const componentId = entityId;
@@ -2763,7 +3211,7 @@ var World = class {
2763
3211
  * // world.getChildren(parent, ChildOf), world.getParent(child, ChildOf)
2764
3212
  */
2765
3213
  getRelationTargets(entityId, relationComp) {
2766
- this.assertEntityExists(entityId, "Entity");
3214
+ assertEntityExists(entityId, "Entity", (id) => this.exists(id));
2767
3215
  const wildcard = relation(relationComp, "*");
2768
3216
  if (this.componentEntities.exists(entityId)) return this.componentEntities.getWildcard(entityId, wildcard);
2769
3217
  return this.get(entityId, wildcard);
@@ -2793,7 +3241,7 @@ var World = class {
2793
3241
  * given base component.
2794
3242
  */
2795
3243
  hasRelation(entityId, relationComp, targetId) {
2796
- this.assertEntityExists(entityId, "Entity");
3244
+ assertEntityExists(entityId, "Entity", (id) => this.exists(id));
2797
3245
  if (targetId !== void 0) {
2798
3246
  const specific = relation(relationComp, targetId);
2799
3247
  return this.has(entityId, specific);
@@ -2804,7 +3252,7 @@ var World = class {
2804
3252
  * Returns the number of relations of the given base component held by the entity.
2805
3253
  */
2806
3254
  countRelations(entityId, relationComp) {
2807
- this.assertEntityExists(entityId, "Entity");
3255
+ assertEntityExists(entityId, "Entity", (id) => this.exists(id));
2808
3256
  return this.getRelationTargets(entityId, relationComp).length;
2809
3257
  }
2810
3258
  /**
@@ -2951,60 +3399,7 @@ var World = class {
2951
3399
  * This is intended for development/debugging and leak detection.
2952
3400
  */
2953
3401
  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 {}
3402
+ return this.debugStats.createCollector(callback);
3008
3403
  }
3009
3404
  /**
3010
3405
  * Synchronizes all buffered commands (set/remove/delete) to the world.
@@ -3017,19 +3412,39 @@ var World = class {
3017
3412
  * world.sync(); // Apply all buffered changes
3018
3413
  */
3019
3414
  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;
3415
+ if (!this.debugStats.hasActiveCollectors()) {
3416
+ this.commandBuffer.execute();
3417
+ return;
3418
+ }
3419
+ const syncStart = performance.now();
3420
+ this.debugStats.resetActivity();
3421
+ const commandBufferStart = performance.now();
3024
3422
  const commandIterations = this.commandBuffer.execute();
3025
- const commandBufferEnd = hasCollectors ? performance.now() : 0;
3026
- const syncEnd = hasCollectors ? performance.now() : 0;
3027
- if (hasCollectors) this._deliverDebugStats({
3423
+ const commandBufferEnd = performance.now();
3424
+ const syncEnd = performance.now();
3425
+ const entityCount = this.entityToArchetype.size;
3426
+ let emptyArchetypes = 0;
3427
+ for (const arch of this.archetypes) if (arch.size === 0) emptyArchetypes++;
3428
+ let archetypesByComponentSize = 0;
3429
+ for (const set of this.archetypesByComponent.values()) archetypesByComponentSize += set.size;
3430
+ this.debugStats.deliver({
3028
3431
  syncStart,
3029
3432
  syncEnd,
3030
3433
  commandBufferStart,
3031
3434
  commandBufferEnd,
3032
3435
  commandIterations
3436
+ }, {
3437
+ entityCount,
3438
+ freelistSize: this.entityIdManager.getFreelistSize(),
3439
+ nextId: this.entityIdManager.getNextId(),
3440
+ archetypeCount: this.archetypes.length,
3441
+ emptyArchetypes,
3442
+ archetypesByComponentSize,
3443
+ cachedQueryCount: this.queryRegistry.cache?.size ?? 0,
3444
+ registeredQueryCount: this.queryRegistry.queries?.size ?? 0,
3445
+ hookCount: this.hooks.size,
3446
+ entityReferencesSize: this.entityReferences.size,
3447
+ entityToReferencingArchetypesSize: this.entityToReferencingArchetypes.size
3033
3448
  });
3034
3449
  }
3035
3450
  /**
@@ -3066,7 +3481,7 @@ var World = class {
3066
3481
  createQuery(componentTypes, filter = {}) {
3067
3482
  const sortedTypes = normalizeComponentTypes(componentTypes);
3068
3483
  const filterKey = serializeQueryFilter(filter);
3069
- const key = `${this.createArchetypeSignature(sortedTypes)}${filterKey ? `|${filterKey}` : ""}`;
3484
+ const key = `${sortedTypes.join(",")}${filterKey ? `|${filterKey}` : ""}`;
3070
3485
  return this.queryRegistry.getOrCreate(this, sortedTypes, key, filter);
3071
3486
  }
3072
3487
  /**
@@ -3132,44 +3547,7 @@ var World = class {
3132
3547
  * @internal
3133
3548
  */
3134
3549
  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);
3550
+ return this.archetypeManager.getMatchingArchetypes(componentTypes);
3173
3551
  }
3174
3552
  query(componentTypes, includeComponents) {
3175
3553
  const matchingArchetypes = this.getMatchingArchetypes(componentTypes);
@@ -3183,154 +3561,14 @@ var World = class {
3183
3561
  return result;
3184
3562
  }
3185
3563
  }
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
3564
  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);
3565
+ return this.archetypeManager.ensureArchetype(componentTypes);
3309
3566
  }
3310
3567
  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);
3568
+ this.archetypeManager.cleanupArchetypesReferencingEntity(entityId);
3315
3569
  }
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);
3570
+ archetypeMatchesHook(archetype, entry) {
3571
+ return this.archetypeManager.archetypeMatchesHook(archetype, entry);
3334
3572
  }
3335
3573
  /**
3336
3574
  * Serializes the entire world state to a plain JavaScript object.
@@ -3353,11 +3591,22 @@ var World = class {
3353
3591
  * const savedData = JSON.parse(localStorage.getItem('save'));
3354
3592
  * const newWorld = new World(savedData);
3355
3593
  */
3594
+ removeComponentImmediate(entityId, componentType, targetEntityId) {
3595
+ this.commandExecutor.removeComponentImmediate(entityId, componentType, targetEntityId);
3596
+ }
3597
+ createHooksContext() {
3598
+ return this.commandExecutor.getHooksContext?.() ?? {
3599
+ multiHooks: this.hooks,
3600
+ has: (eid, ct) => this.has(eid, ct),
3601
+ get: (eid, ct) => this.get(eid, ct),
3602
+ getOptional: (eid, ct) => this.getOptional(eid, ct)
3603
+ };
3604
+ }
3356
3605
  serialize() {
3357
- return serializeWorld(this.archetypes, this.componentEntities, this.entityIdManager);
3606
+ return serializeWorld(this.archetypeManager.archetypes, this.componentEntities, this.entityIdManager);
3358
3607
  }
3359
3608
  };
3360
3609
  //#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 };
3610
+ 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
3611
 
3363
3612
  //# sourceMappingURL=world.mjs.map