@codehz/ecs 0.1.2 → 0.1.4

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.
Files changed (4) hide show
  1. package/README.md +3 -0
  2. package/index.js +120 -124
  3. package/package.json +1 -1
  4. package/world.d.ts +40 -38
package/README.md CHANGED
@@ -171,6 +171,8 @@ bun run examples/simple/demo.ts
171
171
 
172
172
  - `new()`: 创建新实体
173
173
  - `set(entity, componentId, data)`: 向实体添加组件
174
+ - `get(entity, componentId)`: 获取实体的组件数据(注意:只能获取已设置的组件,使用前请先用 `has()` 检查组件是否存在)
175
+ - `has(entity, componentId)`: 检查实体是否拥有指定组件
174
176
  - `delete(entity, componentId)`: 从实体移除组件
175
177
  - `setExclusive(componentId)`: 将组件标记为独占关系
176
178
  - `createQuery(componentIds)`: 创建查询
@@ -251,6 +253,7 @@ const restored = World.deserialize(readySnapshot);
251
253
 
252
254
  注意事项
253
255
 
256
+ - **重要警告**:`get()` 方法只能获取实体已设置的组件。如果尝试获取不存在的组件,会抛出错误。由于 `undefined` 是组件的有效值,不能使用 `get()` 的返回值是否为 `undefined` 来判断组件是否存在。请在使用 `get()` 之前先用 `has()` 方法检查组件是否存在。
254
257
  - 快照只包含实体、组件、以及 `EntityIdManager` 的分配器状态(用于保留下一次分配的 ID);并不会自动恢复已注册的系统、查询缓存或生命周期钩子。恢复后应由应用负责重新注册系统与钩子。
255
258
  - 若需要跨版本兼容,建议在持久化格式中包含 `version` 字段,并在恢复时进行格式兼容性检查与迁移。
256
259
 
package/index.js CHANGED
@@ -660,64 +660,94 @@ class SystemScheduler {
660
660
  class World {
661
661
  entityIdManager = new EntityIdManager;
662
662
  archetypes = [];
663
- archetypeMap = new Map;
663
+ archetypeBySignature = new Map;
664
664
  entityToArchetype = new Map;
665
- systemScheduler = new SystemScheduler;
665
+ archetypesByComponent = new Map;
666
+ entityReferences = new Map;
666
667
  queries = [];
667
668
  queryCache = new Map;
668
- commandBuffer;
669
- componentToArchetypes = new Map;
669
+ systemScheduler = new SystemScheduler;
670
+ commandBuffer = new CommandBuffer((entityId, commands) => this.executeEntityCommands(entityId, commands));
670
671
  lifecycleHooks = new Map;
671
- entityReverseIndex = new Map;
672
672
  exclusiveComponents = new Set;
673
- constructor(entityIdManager) {
674
- this.entityIdManager = entityIdManager || new EntityIdManager;
675
- this.commandBuffer = new CommandBuffer((entityId, commands) => this.executeEntityCommands(entityId, commands));
673
+ constructor(snapshot) {
674
+ if (snapshot && typeof snapshot === "object") {
675
+ if (snapshot.entityManager) {
676
+ this.entityIdManager.deserializeState(snapshot.entityManager);
677
+ }
678
+ if (Array.isArray(snapshot.exclusiveComponents)) {
679
+ for (const id of snapshot.exclusiveComponents) {
680
+ this.exclusiveComponents.add(id);
681
+ }
682
+ }
683
+ if (Array.isArray(snapshot.entities)) {
684
+ for (const entry of snapshot.entities) {
685
+ const entityId = entry.id;
686
+ const componentsArray = entry.components || [];
687
+ const componentMap = new Map;
688
+ const componentTypes = [];
689
+ for (const componentEntry of componentsArray) {
690
+ componentMap.set(componentEntry.type, componentEntry.value);
691
+ componentTypes.push(componentEntry.type);
692
+ }
693
+ const archetype = this.ensureArchetype(componentTypes);
694
+ archetype.addEntity(entityId, componentMap);
695
+ this.entityToArchetype.set(entityId, archetype);
696
+ for (const compType of componentTypes) {
697
+ const detailedType = getDetailedIdType(compType);
698
+ if (detailedType.type === "entity-relation") {
699
+ const targetEntityId = detailedType.targetId;
700
+ this.trackEntityReference(entityId, compType, targetEntityId);
701
+ } else if (detailedType.type === "entity") {
702
+ this.trackEntityReference(entityId, compType, compType);
703
+ }
704
+ }
705
+ }
706
+ }
707
+ }
676
708
  }
677
- getComponentTypesHash(componentTypes) {
709
+ createArchetypeSignature(componentTypes) {
678
710
  return componentTypes.join(",");
679
711
  }
680
712
  new() {
681
713
  const entityId = this.entityIdManager.allocate();
682
- let emptyArchetype = this.getOrCreateArchetype([]);
714
+ let emptyArchetype = this.ensureArchetype([]);
683
715
  emptyArchetype.addEntity(entityId, new Map);
684
716
  this.entityToArchetype.set(entityId, emptyArchetype);
685
717
  return entityId;
686
718
  }
687
- _destroyEntity(entityId) {
719
+ destroyEntityImmediate(entityId) {
688
720
  const archetype = this.entityToArchetype.get(entityId);
689
721
  if (!archetype) {
690
722
  return;
691
723
  }
692
- const componentReferences = this.getComponentReferences(entityId);
724
+ const componentReferences = this.getEntityReferences(entityId);
693
725
  for (const { sourceEntityId, componentType } of componentReferences) {
694
726
  const sourceArchetype = this.entityToArchetype.get(sourceEntityId);
695
727
  if (sourceArchetype) {
696
728
  const currentComponents = new Map;
697
- for (const compType of sourceArchetype.componentTypes) {
698
- if (compType !== componentType) {
699
- const data = sourceArchetype.get(sourceEntityId, compType);
700
- if (data !== undefined) {
701
- currentComponents.set(compType, data);
702
- }
729
+ for (const archetypeComponentType of sourceArchetype.componentTypes) {
730
+ if (archetypeComponentType !== componentType) {
731
+ const componentData = sourceArchetype.get(sourceEntityId, archetypeComponentType);
732
+ currentComponents.set(archetypeComponentType, componentData);
703
733
  }
704
734
  }
705
735
  const newComponentTypes = Array.from(currentComponents.keys()).sort((a, b) => a - b);
706
- const newArchetype = this.getOrCreateArchetype(newComponentTypes);
736
+ const newArchetype = this.ensureArchetype(newComponentTypes);
707
737
  sourceArchetype.removeEntity(sourceEntityId);
708
738
  if (sourceArchetype.getEntities().length === 0) {
709
- this.removeEmptyArchetype(sourceArchetype);
739
+ this.cleanupEmptyArchetype(sourceArchetype);
710
740
  }
711
741
  newArchetype.addEntity(sourceEntityId, currentComponents);
712
742
  this.entityToArchetype.set(sourceEntityId, newArchetype);
713
- this.removeComponentReference(sourceEntityId, componentType, entityId);
714
- this.executeComponentLifecycleHooks(sourceEntityId, new Map, new Set([componentType]));
743
+ this.untrackEntityReference(sourceEntityId, componentType, entityId);
744
+ this.triggerLifecycleHooks(sourceEntityId, new Map, new Set([componentType]));
715
745
  }
716
746
  }
717
- this.entityReverseIndex.delete(entityId);
747
+ this.entityReferences.delete(entityId);
718
748
  archetype.removeEntity(entityId);
719
749
  if (archetype.getEntities().length === 0) {
720
- this.removeEmptyArchetype(archetype);
750
+ this.cleanupEmptyArchetype(archetype);
721
751
  }
722
752
  this.entityToArchetype.delete(entityId);
723
753
  this.entityIdManager.deallocate(entityId);
@@ -760,6 +790,12 @@ class World {
760
790
  if (!archetype) {
761
791
  throw new Error(`Entity ${entityId} does not exist`);
762
792
  }
793
+ const detailedType = getDetailedIdType(componentType);
794
+ if (detailedType.type !== "wildcard-relation") {
795
+ if (!archetype.componentTypes.includes(componentType)) {
796
+ throw new Error(`Entity ${entityId} does not have component ${componentType}. Use has() to check component existence before calling get().`);
797
+ }
798
+ }
763
799
  return archetype.get(entityId, componentType);
764
800
  }
765
801
  registerSystem(system) {
@@ -796,7 +832,7 @@ class World {
796
832
  createQuery(componentTypes, filter = {}) {
797
833
  const sortedTypes = [...componentTypes].sort((a, b) => a - b);
798
834
  const filterKey = serializeQueryFilter(filter);
799
- const key = `${this.getComponentTypesHash(sortedTypes)}${filterKey ? `|${filterKey}` : ""}`;
835
+ const key = `${this.createArchetypeSignature(sortedTypes)}${filterKey ? `|${filterKey}` : ""}`;
800
836
  const cached = this.queryCache.get(key);
801
837
  if (cached) {
802
838
  cached.refCount++;
@@ -844,15 +880,15 @@ class World {
844
880
  }
845
881
  const regularComponents = [];
846
882
  const wildcardRelations = [];
847
- for (const type of componentTypes) {
848
- const detailedType = getDetailedIdType(type);
883
+ for (const componentType of componentTypes) {
884
+ const detailedType = getDetailedIdType(componentType);
849
885
  if (detailedType.type === "wildcard-relation") {
850
886
  wildcardRelations.push({
851
887
  componentId: detailedType.componentId,
852
- relationId: type
888
+ relationId: componentType
853
889
  });
854
890
  } else {
855
- regularComponents.push(type);
891
+ regularComponents.push(componentType);
856
892
  }
857
893
  }
858
894
  let matchingArchetypes = [];
@@ -860,15 +896,15 @@ class World {
860
896
  const sortedRegularTypes = [...regularComponents].sort((a, b) => a - b);
861
897
  if (sortedRegularTypes.length === 1) {
862
898
  const componentType = sortedRegularTypes[0];
863
- matchingArchetypes = this.componentToArchetypes.get(componentType) || [];
899
+ matchingArchetypes = this.archetypesByComponent.get(componentType) || [];
864
900
  } else {
865
- const archetypeLists = sortedRegularTypes.map((type) => this.componentToArchetypes.get(type) || []);
901
+ const archetypeLists = sortedRegularTypes.map((type) => this.archetypesByComponent.get(type) || []);
866
902
  const firstList = archetypeLists[0] || [];
867
903
  const intersection = new Set;
868
904
  for (const archetype of firstList) {
869
905
  let hasAllComponents = true;
870
- for (let i = 1;i < archetypeLists.length; i++) {
871
- const otherList = archetypeLists[i];
906
+ for (let listIndex = 1;listIndex < archetypeLists.length; listIndex++) {
907
+ const otherList = archetypeLists[listIndex];
872
908
  if (!otherList.includes(archetype)) {
873
909
  hasAllComponents = false;
874
910
  break;
@@ -884,12 +920,12 @@ class World {
884
920
  matchingArchetypes = [...this.archetypes];
885
921
  }
886
922
  for (const wildcard of wildcardRelations) {
887
- const componentArchetypes = this.componentToArchetypes.get(wildcard.componentId) || [];
923
+ const componentArchetypes = this.archetypesByComponent.get(wildcard.componentId) || [];
888
924
  matchingArchetypes = matchingArchetypes.filter((archetype) => componentArchetypes.includes(archetype));
889
925
  }
890
926
  return matchingArchetypes;
891
927
  }
892
- queryEntities(componentTypes, includeComponents) {
928
+ query(componentTypes, includeComponents) {
893
929
  const matchingArchetypes = this.getMatchingArchetypes(componentTypes);
894
930
  if (includeComponents) {
895
931
  const result = [];
@@ -910,7 +946,7 @@ class World {
910
946
  const changeset = new ComponentChangeset;
911
947
  const hasDestroy = commands.some((cmd) => cmd.type === "destroy");
912
948
  if (hasDestroy) {
913
- this._destroyEntity(entityId);
949
+ this.destroyEntityImmediate(entityId);
914
950
  return changeset;
915
951
  }
916
952
  const currentArchetype = this.entityToArchetype.get(entityId);
@@ -919,14 +955,14 @@ class World {
919
955
  }
920
956
  const currentComponents = new Map;
921
957
  for (const componentType of currentArchetype.componentTypes) {
922
- const data = currentArchetype.get(entityId, componentType);
923
- currentComponents.set(componentType, data);
958
+ const componentData = currentArchetype.get(entityId, componentType);
959
+ currentComponents.set(componentType, componentData);
924
960
  }
925
- for (const cmd of commands) {
926
- switch (cmd.type) {
961
+ for (const command of commands) {
962
+ switch (command.type) {
927
963
  case "set":
928
- if (cmd.componentType) {
929
- const detailedType = getDetailedIdType(cmd.componentType);
964
+ if (command.componentType) {
965
+ const detailedType = getDetailedIdType(command.componentType);
930
966
  if ((detailedType.type === "entity-relation" || detailedType.type === "component-relation") && this.exclusiveComponents.has(detailedType.componentId)) {
931
967
  for (const componentType of currentArchetype.componentTypes) {
932
968
  const componentDetailedType = getDetailedIdType(componentType);
@@ -935,12 +971,12 @@ class World {
935
971
  }
936
972
  }
937
973
  }
938
- changeset.set(cmd.componentType, cmd.component);
974
+ changeset.set(command.componentType, command.component);
939
975
  }
940
976
  break;
941
977
  case "delete":
942
- if (cmd.componentType) {
943
- const detailedType = getDetailedIdType(cmd.componentType);
978
+ if (command.componentType) {
979
+ const detailedType = getDetailedIdType(command.componentType);
944
980
  if (detailedType.type === "wildcard-relation") {
945
981
  const baseComponentId = detailedType.componentId;
946
982
  for (const componentType of currentArchetype.componentTypes) {
@@ -952,7 +988,7 @@ class World {
952
988
  }
953
989
  }
954
990
  } else {
955
- changeset.delete(cmd.componentType);
991
+ changeset.delete(command.componentType);
956
992
  }
957
993
  }
958
994
  break;
@@ -963,7 +999,7 @@ class World {
963
999
  const currentComponentTypes = currentArchetype.componentTypes.sort((a, b) => a - b);
964
1000
  const needsArchetypeChange = finalComponentTypes.length !== currentComponentTypes.length || !finalComponentTypes.every((type, index) => type === currentComponentTypes[index]);
965
1001
  if (needsArchetypeChange) {
966
- const newArchetype = this.getOrCreateArchetype(finalComponentTypes);
1002
+ const newArchetype = this.ensureArchetype(finalComponentTypes);
967
1003
  currentArchetype.removeEntity(entityId);
968
1004
  newArchetype.addEntity(entityId, finalComponents);
969
1005
  this.entityToArchetype.set(entityId, newArchetype);
@@ -976,33 +1012,33 @@ class World {
976
1012
  const detailedType = getDetailedIdType(componentType);
977
1013
  if (detailedType.type === "entity-relation") {
978
1014
  const targetEntityId = detailedType.targetId;
979
- this.removeComponentReference(entityId, componentType, targetEntityId);
1015
+ this.untrackEntityReference(entityId, componentType, targetEntityId);
980
1016
  } else if (detailedType.type === "entity") {
981
- this.removeComponentReference(entityId, componentType, componentType);
1017
+ this.untrackEntityReference(entityId, componentType, componentType);
982
1018
  }
983
1019
  }
984
1020
  for (const [componentType, component2] of changeset.adds) {
985
1021
  const detailedType = getDetailedIdType(componentType);
986
1022
  if (detailedType.type === "entity-relation") {
987
1023
  const targetEntityId = detailedType.targetId;
988
- this.addComponentReference(entityId, componentType, targetEntityId);
1024
+ this.trackEntityReference(entityId, componentType, targetEntityId);
989
1025
  } else if (detailedType.type === "entity") {
990
- this.addComponentReference(entityId, componentType, componentType);
1026
+ this.trackEntityReference(entityId, componentType, componentType);
991
1027
  }
992
1028
  }
993
- this.executeComponentLifecycleHooks(entityId, changeset.adds, changeset.removes);
1029
+ this.triggerLifecycleHooks(entityId, changeset.adds, changeset.removes);
994
1030
  return changeset;
995
1031
  }
996
- getOrCreateArchetype(componentTypes) {
1032
+ ensureArchetype(componentTypes) {
997
1033
  const sortedTypes = [...componentTypes].sort((a, b) => a - b);
998
- const hashKey = this.getComponentTypesHash(sortedTypes);
999
- return getOrCreateWithSideEffect(this.archetypeMap, hashKey, () => {
1034
+ const hashKey = this.createArchetypeSignature(sortedTypes);
1035
+ return getOrCreateWithSideEffect(this.archetypeBySignature, hashKey, () => {
1000
1036
  const newArchetype = new Archetype(sortedTypes);
1001
1037
  this.archetypes.push(newArchetype);
1002
1038
  for (const componentType of sortedTypes) {
1003
- const archetypes = this.componentToArchetypes.get(componentType) || [];
1039
+ const archetypes = this.archetypesByComponent.get(componentType) || [];
1004
1040
  archetypes.push(newArchetype);
1005
- this.componentToArchetypes.set(componentType, archetypes);
1041
+ this.archetypesByComponent.set(componentType, archetypes);
1006
1042
  }
1007
1043
  for (const query of this.queries) {
1008
1044
  query.checkNewArchetype(newArchetype);
@@ -1010,30 +1046,30 @@ class World {
1010
1046
  return newArchetype;
1011
1047
  });
1012
1048
  }
1013
- addComponentReference(sourceEntityId, componentType, targetEntityId) {
1014
- if (!this.entityReverseIndex.has(targetEntityId)) {
1015
- this.entityReverseIndex.set(targetEntityId, new Set);
1049
+ trackEntityReference(sourceEntityId, componentType, targetEntityId) {
1050
+ if (!this.entityReferences.has(targetEntityId)) {
1051
+ this.entityReferences.set(targetEntityId, new Set);
1016
1052
  }
1017
- this.entityReverseIndex.get(targetEntityId).add({ sourceEntityId, componentType });
1053
+ this.entityReferences.get(targetEntityId).add({ sourceEntityId, componentType });
1018
1054
  }
1019
- removeComponentReference(sourceEntityId, componentType, targetEntityId) {
1020
- const references = this.entityReverseIndex.get(targetEntityId);
1055
+ untrackEntityReference(sourceEntityId, componentType, targetEntityId) {
1056
+ const references = this.entityReferences.get(targetEntityId);
1021
1057
  if (references) {
1022
- references.forEach((ref) => {
1023
- if (ref.sourceEntityId === sourceEntityId && ref.componentType === componentType) {
1024
- references.delete(ref);
1058
+ references.forEach((reference) => {
1059
+ if (reference.sourceEntityId === sourceEntityId && reference.componentType === componentType) {
1060
+ references.delete(reference);
1025
1061
  }
1026
1062
  });
1027
1063
  if (references.size === 0) {
1028
- this.entityReverseIndex.delete(targetEntityId);
1064
+ this.entityReferences.delete(targetEntityId);
1029
1065
  }
1030
1066
  }
1031
1067
  }
1032
- getComponentReferences(targetEntityId) {
1033
- const references = this.entityReverseIndex.get(targetEntityId);
1068
+ getEntityReferences(targetEntityId) {
1069
+ const references = this.entityReferences.get(targetEntityId);
1034
1070
  return references ? Array.from(references) : [];
1035
1071
  }
1036
- removeEmptyArchetype(archetype) {
1072
+ cleanupEmptyArchetype(archetype) {
1037
1073
  if (archetype.getEntities().length > 0) {
1038
1074
  return;
1039
1075
  }
@@ -1041,28 +1077,28 @@ class World {
1041
1077
  if (index !== -1) {
1042
1078
  this.archetypes.splice(index, 1);
1043
1079
  }
1044
- const hashKey = this.getComponentTypesHash(archetype.componentTypes);
1045
- this.archetypeMap.delete(hashKey);
1080
+ const hashKey = this.createArchetypeSignature(archetype.componentTypes);
1081
+ this.archetypeBySignature.delete(hashKey);
1046
1082
  for (const componentType of archetype.componentTypes) {
1047
- const archetypes = this.componentToArchetypes.get(componentType);
1083
+ const archetypes = this.archetypesByComponent.get(componentType);
1048
1084
  if (archetypes) {
1049
1085
  const compIndex = archetypes.indexOf(archetype);
1050
1086
  if (compIndex !== -1) {
1051
1087
  archetypes.splice(compIndex, 1);
1052
1088
  if (archetypes.length === 0) {
1053
- this.componentToArchetypes.delete(componentType);
1089
+ this.archetypesByComponent.delete(componentType);
1054
1090
  }
1055
1091
  }
1056
1092
  }
1057
1093
  }
1058
1094
  }
1059
- executeComponentLifecycleHooks(entityId, addedComponents, removedComponents) {
1095
+ triggerLifecycleHooks(entityId, addedComponents, removedComponents) {
1060
1096
  for (const [componentType, component2] of addedComponents) {
1061
1097
  const directHooks = this.lifecycleHooks.get(componentType);
1062
1098
  if (directHooks) {
1063
- for (const hook of directHooks) {
1064
- if (hook.onAdded) {
1065
- hook.onAdded(entityId, componentType, component2);
1099
+ for (const lifecycleHook of directHooks) {
1100
+ if (lifecycleHook.onAdded) {
1101
+ lifecycleHook.onAdded(entityId, componentType, component2);
1066
1102
  }
1067
1103
  }
1068
1104
  }
@@ -1071,9 +1107,9 @@ class World {
1071
1107
  const wildcardRelationId = relation(detailedType.componentId, "*");
1072
1108
  const wildcardHooks = this.lifecycleHooks.get(wildcardRelationId);
1073
1109
  if (wildcardHooks) {
1074
- for (const hook of wildcardHooks) {
1075
- if (hook.onAdded) {
1076
- hook.onAdded(entityId, componentType, component2);
1110
+ for (const lifecycleHook of wildcardHooks) {
1111
+ if (lifecycleHook.onAdded) {
1112
+ lifecycleHook.onAdded(entityId, componentType, component2);
1077
1113
  }
1078
1114
  }
1079
1115
  }
@@ -1082,9 +1118,9 @@ class World {
1082
1118
  for (const componentType of removedComponents) {
1083
1119
  const directHooks = this.lifecycleHooks.get(componentType);
1084
1120
  if (directHooks) {
1085
- for (const hook of directHooks) {
1086
- if (hook.onRemoved) {
1087
- hook.onRemoved(entityId, componentType);
1121
+ for (const lifecycleHook of directHooks) {
1122
+ if (lifecycleHook.onRemoved) {
1123
+ lifecycleHook.onRemoved(entityId, componentType);
1088
1124
  }
1089
1125
  }
1090
1126
  }
@@ -1119,46 +1155,6 @@ class World {
1119
1155
  entities
1120
1156
  };
1121
1157
  }
1122
- static deserialize(obj) {
1123
- if (!obj || typeof obj !== "object") {
1124
- throw new Error("World.deserialize expects a snapshot object (not a JSON string)");
1125
- }
1126
- const entityManager = new EntityIdManager;
1127
- if (obj && obj.entityManager) {
1128
- entityManager.deserializeState(obj.entityManager);
1129
- }
1130
- const world = new World(entityManager);
1131
- if (obj && Array.isArray(obj.exclusiveComponents)) {
1132
- for (const id of obj.exclusiveComponents) {
1133
- world.exclusiveComponents.add(id);
1134
- }
1135
- }
1136
- if (obj && Array.isArray(obj.entities)) {
1137
- for (const entry of obj.entities) {
1138
- const entityId = entry.id;
1139
- const componentsArray = entry.components || [];
1140
- const componentMap = new Map;
1141
- const componentTypes = [];
1142
- for (const c of componentsArray) {
1143
- componentMap.set(c.type, c.value);
1144
- componentTypes.push(c.type);
1145
- }
1146
- const archetype = world.getOrCreateArchetype(componentTypes);
1147
- archetype.addEntity(entityId, componentMap);
1148
- world.entityToArchetype.set(entityId, archetype);
1149
- for (const compType of componentTypes) {
1150
- const detailedType = getDetailedIdType(compType);
1151
- if (detailedType.type === "entity-relation") {
1152
- const targetEntityId = detailedType.targetId;
1153
- world.addComponentReference(entityId, compType, targetEntityId);
1154
- } else if (detailedType.type === "entity") {
1155
- world.addComponentReference(entityId, compType, compType);
1156
- }
1157
- }
1158
- }
1159
- }
1160
- return world;
1161
- }
1162
1158
  }
1163
1159
  export {
1164
1160
  relation,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codehz/ecs",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "main": "./index.js",
6
6
  "types": "./index.d.ts",
package/world.d.ts CHANGED
@@ -2,7 +2,6 @@ import { Archetype } from "./archetype";
2
2
  import { ComponentChangeset } from "./changeset";
3
3
  import { type Command } from "./command-buffer";
4
4
  import type { EntityId, WildcardRelationId } from "./entity";
5
- import { EntityIdManager } from "./entity";
6
5
  import { Query } from "./query";
7
6
  import { type QueryFilter } from "./query-filter";
8
7
  import type { System } from "./system";
@@ -12,43 +11,50 @@ import type { ComponentTuple, LifecycleHook } from "./types";
12
11
  * Manages entities, components, and systems
13
12
  */
14
13
  export declare class World<UpdateParams extends any[] = []> {
14
+ /** Manages allocation and deallocation of entity IDs */
15
15
  private entityIdManager;
16
+ /** Array of all archetypes in the world */
16
17
  private archetypes;
17
- private archetypeMap;
18
+ /** Maps archetype signatures (component type signatures) to archetype instances */
19
+ private archetypeBySignature;
20
+ /** Maps entity IDs to their current archetype */
18
21
  private entityToArchetype;
19
- private systemScheduler;
22
+ /** Maps component types to arrays of archetypes that contain them */
23
+ private archetypesByComponent;
24
+ /** Tracks which entities reference each entity as a component type */
25
+ private entityReferences;
26
+ /** Array of all active queries for archetype change notifications */
20
27
  private queries;
28
+ /** Cache for queries keyed by component types and filter signatures */
21
29
  private queryCache;
30
+ /** Schedules and executes systems in dependency order */
31
+ private systemScheduler;
32
+ /** Buffers structural changes for deferred execution */
22
33
  private commandBuffer;
23
- private componentToArchetypes;
24
- /**
25
- * Hook storage for component and wildcard relation lifecycle events
26
- */
34
+ /** Stores lifecycle hooks for component and relation events */
27
35
  private lifecycleHooks;
36
+ /** Set of component IDs marked as exclusive relations */
37
+ private exclusiveComponents;
28
38
  /**
29
- * Reverse index tracking which entities use each entity as a component type
30
- * Maps entity ID to set of {sourceEntityId, componentType} pairs where componentType uses this entity
31
- * This includes both relation components and direct usage of entities as component types
39
+ * Create a new World.
40
+ * If an optional snapshot object is provided (previously produced by `world.serialize()`),
41
+ * the world will be restored from that snapshot. The snapshot may contain non-JSON values.
32
42
  */
33
- private entityReverseIndex;
43
+ constructor(snapshot?: any);
34
44
  /**
35
- * Set of component IDs that are marked as exclusive relations
36
- * For exclusive relations, an entity can have at most one relation per base component
45
+ * Generate a signature string for component types array
46
+ * @returns A string signature for the component types
37
47
  */
38
- private exclusiveComponents;
39
- constructor(entityIdManager?: EntityIdManager);
40
- /**
41
- * Generate a hash key for component types array
42
- */
43
- private getComponentTypesHash;
48
+ private createArchetypeSignature;
44
49
  /**
45
50
  * Create a new entity
51
+ * @returns The ID of the newly created entity
46
52
  */
47
53
  new(): EntityId;
48
54
  /**
49
55
  * Destroy an entity and remove all its components (immediate execution)
50
56
  */
51
- private _destroyEntity;
57
+ private destroyEntityImmediate;
52
58
  /**
53
59
  * Check if an entity exists
54
60
  */
@@ -75,12 +81,14 @@ export declare class World<UpdateParams extends any[] = []> {
75
81
  * Returns an array of all matching relation instances
76
82
  * @param entityId The entity
77
83
  * @param componentType The wildcard relation type
84
+ * @returns Array of [targetEntityId, componentData] pairs for all matching relations
78
85
  */
79
86
  get<T>(entityId: EntityId, componentType: WildcardRelationId<T>): [EntityId<unknown>, T][];
80
87
  /**
81
88
  * Get component data for a specific entity and component type
82
89
  * @param entityId The entity
83
90
  * @param componentType The component type
91
+ * @returns The component data
84
92
  */
85
93
  get<T>(entityId: EntityId, componentType: EntityId<T>): T;
86
94
  /**
@@ -110,6 +118,7 @@ export declare class World<UpdateParams extends any[] = []> {
110
118
  sync(): void;
111
119
  /**
112
120
  * Create a cached query for efficient entity lookups
121
+ * @returns A Query object for the specified component types and filter
113
122
  */
114
123
  createQuery(componentTypes: EntityId<any>[], filter?: QueryFilter): Query;
115
124
  /**
@@ -131,62 +140,55 @@ export declare class World<UpdateParams extends any[] = []> {
131
140
  getMatchingArchetypes(componentTypes: EntityId<any>[]): Archetype[];
132
141
  /**
133
142
  * Query entities with specific components
143
+ * @returns Array of entity IDs that have all the specified components
134
144
  */
135
- queryEntities(componentTypes: EntityId<any>[]): EntityId[];
136
- queryEntities<const T extends readonly EntityId<any>[]>(componentTypes: T, includeComponents: true): Array<{
145
+ query(componentTypes: EntityId<any>[]): EntityId[];
146
+ query<const T extends readonly EntityId<any>[]>(componentTypes: T, includeComponents: true): Array<{
137
147
  entity: EntityId;
138
148
  components: ComponentTuple<T>;
139
149
  }>;
140
150
  /**
141
151
  * @internal Execute commands for a single entity (for internal use by CommandBuffer)
152
+ * @returns ComponentChangeset describing the changes made
142
153
  */
143
154
  executeEntityCommands(entityId: EntityId, commands: Command[]): ComponentChangeset;
144
155
  /**
145
156
  * Get or create an archetype for the given component types
157
+ * @returns The archetype for the given component types
146
158
  */
147
- private getOrCreateArchetype;
159
+ private ensureArchetype;
148
160
  /**
149
161
  * Add a component reference to the reverse index when an entity is used as a component type
150
162
  * @param sourceEntityId The entity that has the component
151
163
  * @param componentType The component type (which may be an entity ID used as component type)
152
164
  * @param targetEntityId The entity being used as component type
153
165
  */
154
- private addComponentReference;
166
+ private trackEntityReference;
155
167
  /**
156
168
  * Remove a component reference from the reverse index
157
169
  * @param sourceEntityId The entity that has the component
158
170
  * @param componentType The component type
159
171
  * @param targetEntityId The entity being used as component type
160
172
  */
161
- private removeComponentReference;
173
+ private untrackEntityReference;
162
174
  /**
163
175
  * Get all component references where a target entity is used as a component type
164
176
  * @param targetEntityId The target entity
165
177
  * @returns Array of {sourceEntityId, componentType} pairs
166
178
  */
167
- private getComponentReferences;
179
+ private getEntityReferences;
168
180
  /**
169
181
  * Remove an empty archetype from all internal data structures
170
182
  */
171
- private removeEmptyArchetype;
183
+ private cleanupEmptyArchetype;
172
184
  /**
173
185
  * Execute component lifecycle hooks for added and removed components
174
186
  */
175
- private executeComponentLifecycleHooks;
176
- /**
177
- * Convert the world into a plain JSON-serializable object.
178
- * Note: component values must be JSON-serializable by the caller.
179
- */
187
+ private triggerLifecycleHooks;
180
188
  /**
181
189
  * Convert the world into a plain snapshot object.
182
190
  * This returns an in-memory structure and does not perform JSON stringification.
183
191
  * Component values are stored as-is (they may be non-JSON-serializable).
184
192
  */
185
193
  serialize(): any;
186
- /**
187
- * Deserialize a world from a previously-created snapshot object.
188
- * The snapshot must have been produced by `world.serialize()` and may contain
189
- * non-JSON values (they will be copied by reference).
190
- */
191
- static deserialize<T extends any[] = []>(obj: any): World<T>;
192
194
  }