@codehz/ecs 0.1.2 → 0.1.3
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/index.js +114 -124
- package/package.json +1 -1
- package/world.d.ts +40 -38
package/index.js
CHANGED
|
@@ -660,64 +660,94 @@ class SystemScheduler {
|
|
|
660
660
|
class World {
|
|
661
661
|
entityIdManager = new EntityIdManager;
|
|
662
662
|
archetypes = [];
|
|
663
|
-
|
|
663
|
+
archetypeBySignature = new Map;
|
|
664
664
|
entityToArchetype = new Map;
|
|
665
|
-
|
|
665
|
+
archetypesByComponent = new Map;
|
|
666
|
+
entityReferences = new Map;
|
|
666
667
|
queries = [];
|
|
667
668
|
queryCache = new Map;
|
|
668
|
-
|
|
669
|
-
|
|
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(
|
|
674
|
-
|
|
675
|
-
|
|
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
|
-
|
|
709
|
+
createArchetypeSignature(componentTypes) {
|
|
678
710
|
return componentTypes.join(",");
|
|
679
711
|
}
|
|
680
712
|
new() {
|
|
681
713
|
const entityId = this.entityIdManager.allocate();
|
|
682
|
-
let emptyArchetype = this.
|
|
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
|
-
|
|
719
|
+
destroyEntityImmediate(entityId) {
|
|
688
720
|
const archetype = this.entityToArchetype.get(entityId);
|
|
689
721
|
if (!archetype) {
|
|
690
722
|
return;
|
|
691
723
|
}
|
|
692
|
-
const componentReferences = this.
|
|
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
|
|
698
|
-
if (
|
|
699
|
-
const
|
|
700
|
-
|
|
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.
|
|
736
|
+
const newArchetype = this.ensureArchetype(newComponentTypes);
|
|
707
737
|
sourceArchetype.removeEntity(sourceEntityId);
|
|
708
738
|
if (sourceArchetype.getEntities().length === 0) {
|
|
709
|
-
this.
|
|
739
|
+
this.cleanupEmptyArchetype(sourceArchetype);
|
|
710
740
|
}
|
|
711
741
|
newArchetype.addEntity(sourceEntityId, currentComponents);
|
|
712
742
|
this.entityToArchetype.set(sourceEntityId, newArchetype);
|
|
713
|
-
this.
|
|
714
|
-
this.
|
|
743
|
+
this.untrackEntityReference(sourceEntityId, componentType, entityId);
|
|
744
|
+
this.triggerLifecycleHooks(sourceEntityId, new Map, new Set([componentType]));
|
|
715
745
|
}
|
|
716
746
|
}
|
|
717
|
-
this.
|
|
747
|
+
this.entityReferences.delete(entityId);
|
|
718
748
|
archetype.removeEntity(entityId);
|
|
719
749
|
if (archetype.getEntities().length === 0) {
|
|
720
|
-
this.
|
|
750
|
+
this.cleanupEmptyArchetype(archetype);
|
|
721
751
|
}
|
|
722
752
|
this.entityToArchetype.delete(entityId);
|
|
723
753
|
this.entityIdManager.deallocate(entityId);
|
|
@@ -796,7 +826,7 @@ class World {
|
|
|
796
826
|
createQuery(componentTypes, filter = {}) {
|
|
797
827
|
const sortedTypes = [...componentTypes].sort((a, b) => a - b);
|
|
798
828
|
const filterKey = serializeQueryFilter(filter);
|
|
799
|
-
const key = `${this.
|
|
829
|
+
const key = `${this.createArchetypeSignature(sortedTypes)}${filterKey ? `|${filterKey}` : ""}`;
|
|
800
830
|
const cached = this.queryCache.get(key);
|
|
801
831
|
if (cached) {
|
|
802
832
|
cached.refCount++;
|
|
@@ -844,15 +874,15 @@ class World {
|
|
|
844
874
|
}
|
|
845
875
|
const regularComponents = [];
|
|
846
876
|
const wildcardRelations = [];
|
|
847
|
-
for (const
|
|
848
|
-
const detailedType = getDetailedIdType(
|
|
877
|
+
for (const componentType of componentTypes) {
|
|
878
|
+
const detailedType = getDetailedIdType(componentType);
|
|
849
879
|
if (detailedType.type === "wildcard-relation") {
|
|
850
880
|
wildcardRelations.push({
|
|
851
881
|
componentId: detailedType.componentId,
|
|
852
|
-
relationId:
|
|
882
|
+
relationId: componentType
|
|
853
883
|
});
|
|
854
884
|
} else {
|
|
855
|
-
regularComponents.push(
|
|
885
|
+
regularComponents.push(componentType);
|
|
856
886
|
}
|
|
857
887
|
}
|
|
858
888
|
let matchingArchetypes = [];
|
|
@@ -860,15 +890,15 @@ class World {
|
|
|
860
890
|
const sortedRegularTypes = [...regularComponents].sort((a, b) => a - b);
|
|
861
891
|
if (sortedRegularTypes.length === 1) {
|
|
862
892
|
const componentType = sortedRegularTypes[0];
|
|
863
|
-
matchingArchetypes = this.
|
|
893
|
+
matchingArchetypes = this.archetypesByComponent.get(componentType) || [];
|
|
864
894
|
} else {
|
|
865
|
-
const archetypeLists = sortedRegularTypes.map((type) => this.
|
|
895
|
+
const archetypeLists = sortedRegularTypes.map((type) => this.archetypesByComponent.get(type) || []);
|
|
866
896
|
const firstList = archetypeLists[0] || [];
|
|
867
897
|
const intersection = new Set;
|
|
868
898
|
for (const archetype of firstList) {
|
|
869
899
|
let hasAllComponents = true;
|
|
870
|
-
for (let
|
|
871
|
-
const otherList = archetypeLists[
|
|
900
|
+
for (let listIndex = 1;listIndex < archetypeLists.length; listIndex++) {
|
|
901
|
+
const otherList = archetypeLists[listIndex];
|
|
872
902
|
if (!otherList.includes(archetype)) {
|
|
873
903
|
hasAllComponents = false;
|
|
874
904
|
break;
|
|
@@ -884,12 +914,12 @@ class World {
|
|
|
884
914
|
matchingArchetypes = [...this.archetypes];
|
|
885
915
|
}
|
|
886
916
|
for (const wildcard of wildcardRelations) {
|
|
887
|
-
const componentArchetypes = this.
|
|
917
|
+
const componentArchetypes = this.archetypesByComponent.get(wildcard.componentId) || [];
|
|
888
918
|
matchingArchetypes = matchingArchetypes.filter((archetype) => componentArchetypes.includes(archetype));
|
|
889
919
|
}
|
|
890
920
|
return matchingArchetypes;
|
|
891
921
|
}
|
|
892
|
-
|
|
922
|
+
query(componentTypes, includeComponents) {
|
|
893
923
|
const matchingArchetypes = this.getMatchingArchetypes(componentTypes);
|
|
894
924
|
if (includeComponents) {
|
|
895
925
|
const result = [];
|
|
@@ -910,7 +940,7 @@ class World {
|
|
|
910
940
|
const changeset = new ComponentChangeset;
|
|
911
941
|
const hasDestroy = commands.some((cmd) => cmd.type === "destroy");
|
|
912
942
|
if (hasDestroy) {
|
|
913
|
-
this.
|
|
943
|
+
this.destroyEntityImmediate(entityId);
|
|
914
944
|
return changeset;
|
|
915
945
|
}
|
|
916
946
|
const currentArchetype = this.entityToArchetype.get(entityId);
|
|
@@ -919,14 +949,14 @@ class World {
|
|
|
919
949
|
}
|
|
920
950
|
const currentComponents = new Map;
|
|
921
951
|
for (const componentType of currentArchetype.componentTypes) {
|
|
922
|
-
const
|
|
923
|
-
currentComponents.set(componentType,
|
|
952
|
+
const componentData = currentArchetype.get(entityId, componentType);
|
|
953
|
+
currentComponents.set(componentType, componentData);
|
|
924
954
|
}
|
|
925
|
-
for (const
|
|
926
|
-
switch (
|
|
955
|
+
for (const command of commands) {
|
|
956
|
+
switch (command.type) {
|
|
927
957
|
case "set":
|
|
928
|
-
if (
|
|
929
|
-
const detailedType = getDetailedIdType(
|
|
958
|
+
if (command.componentType) {
|
|
959
|
+
const detailedType = getDetailedIdType(command.componentType);
|
|
930
960
|
if ((detailedType.type === "entity-relation" || detailedType.type === "component-relation") && this.exclusiveComponents.has(detailedType.componentId)) {
|
|
931
961
|
for (const componentType of currentArchetype.componentTypes) {
|
|
932
962
|
const componentDetailedType = getDetailedIdType(componentType);
|
|
@@ -935,12 +965,12 @@ class World {
|
|
|
935
965
|
}
|
|
936
966
|
}
|
|
937
967
|
}
|
|
938
|
-
changeset.set(
|
|
968
|
+
changeset.set(command.componentType, command.component);
|
|
939
969
|
}
|
|
940
970
|
break;
|
|
941
971
|
case "delete":
|
|
942
|
-
if (
|
|
943
|
-
const detailedType = getDetailedIdType(
|
|
972
|
+
if (command.componentType) {
|
|
973
|
+
const detailedType = getDetailedIdType(command.componentType);
|
|
944
974
|
if (detailedType.type === "wildcard-relation") {
|
|
945
975
|
const baseComponentId = detailedType.componentId;
|
|
946
976
|
for (const componentType of currentArchetype.componentTypes) {
|
|
@@ -952,7 +982,7 @@ class World {
|
|
|
952
982
|
}
|
|
953
983
|
}
|
|
954
984
|
} else {
|
|
955
|
-
changeset.delete(
|
|
985
|
+
changeset.delete(command.componentType);
|
|
956
986
|
}
|
|
957
987
|
}
|
|
958
988
|
break;
|
|
@@ -963,7 +993,7 @@ class World {
|
|
|
963
993
|
const currentComponentTypes = currentArchetype.componentTypes.sort((a, b) => a - b);
|
|
964
994
|
const needsArchetypeChange = finalComponentTypes.length !== currentComponentTypes.length || !finalComponentTypes.every((type, index) => type === currentComponentTypes[index]);
|
|
965
995
|
if (needsArchetypeChange) {
|
|
966
|
-
const newArchetype = this.
|
|
996
|
+
const newArchetype = this.ensureArchetype(finalComponentTypes);
|
|
967
997
|
currentArchetype.removeEntity(entityId);
|
|
968
998
|
newArchetype.addEntity(entityId, finalComponents);
|
|
969
999
|
this.entityToArchetype.set(entityId, newArchetype);
|
|
@@ -976,33 +1006,33 @@ class World {
|
|
|
976
1006
|
const detailedType = getDetailedIdType(componentType);
|
|
977
1007
|
if (detailedType.type === "entity-relation") {
|
|
978
1008
|
const targetEntityId = detailedType.targetId;
|
|
979
|
-
this.
|
|
1009
|
+
this.untrackEntityReference(entityId, componentType, targetEntityId);
|
|
980
1010
|
} else if (detailedType.type === "entity") {
|
|
981
|
-
this.
|
|
1011
|
+
this.untrackEntityReference(entityId, componentType, componentType);
|
|
982
1012
|
}
|
|
983
1013
|
}
|
|
984
1014
|
for (const [componentType, component2] of changeset.adds) {
|
|
985
1015
|
const detailedType = getDetailedIdType(componentType);
|
|
986
1016
|
if (detailedType.type === "entity-relation") {
|
|
987
1017
|
const targetEntityId = detailedType.targetId;
|
|
988
|
-
this.
|
|
1018
|
+
this.trackEntityReference(entityId, componentType, targetEntityId);
|
|
989
1019
|
} else if (detailedType.type === "entity") {
|
|
990
|
-
this.
|
|
1020
|
+
this.trackEntityReference(entityId, componentType, componentType);
|
|
991
1021
|
}
|
|
992
1022
|
}
|
|
993
|
-
this.
|
|
1023
|
+
this.triggerLifecycleHooks(entityId, changeset.adds, changeset.removes);
|
|
994
1024
|
return changeset;
|
|
995
1025
|
}
|
|
996
|
-
|
|
1026
|
+
ensureArchetype(componentTypes) {
|
|
997
1027
|
const sortedTypes = [...componentTypes].sort((a, b) => a - b);
|
|
998
|
-
const hashKey = this.
|
|
999
|
-
return getOrCreateWithSideEffect(this.
|
|
1028
|
+
const hashKey = this.createArchetypeSignature(sortedTypes);
|
|
1029
|
+
return getOrCreateWithSideEffect(this.archetypeBySignature, hashKey, () => {
|
|
1000
1030
|
const newArchetype = new Archetype(sortedTypes);
|
|
1001
1031
|
this.archetypes.push(newArchetype);
|
|
1002
1032
|
for (const componentType of sortedTypes) {
|
|
1003
|
-
const archetypes = this.
|
|
1033
|
+
const archetypes = this.archetypesByComponent.get(componentType) || [];
|
|
1004
1034
|
archetypes.push(newArchetype);
|
|
1005
|
-
this.
|
|
1035
|
+
this.archetypesByComponent.set(componentType, archetypes);
|
|
1006
1036
|
}
|
|
1007
1037
|
for (const query of this.queries) {
|
|
1008
1038
|
query.checkNewArchetype(newArchetype);
|
|
@@ -1010,30 +1040,30 @@ class World {
|
|
|
1010
1040
|
return newArchetype;
|
|
1011
1041
|
});
|
|
1012
1042
|
}
|
|
1013
|
-
|
|
1014
|
-
if (!this.
|
|
1015
|
-
this.
|
|
1043
|
+
trackEntityReference(sourceEntityId, componentType, targetEntityId) {
|
|
1044
|
+
if (!this.entityReferences.has(targetEntityId)) {
|
|
1045
|
+
this.entityReferences.set(targetEntityId, new Set);
|
|
1016
1046
|
}
|
|
1017
|
-
this.
|
|
1047
|
+
this.entityReferences.get(targetEntityId).add({ sourceEntityId, componentType });
|
|
1018
1048
|
}
|
|
1019
|
-
|
|
1020
|
-
const references = this.
|
|
1049
|
+
untrackEntityReference(sourceEntityId, componentType, targetEntityId) {
|
|
1050
|
+
const references = this.entityReferences.get(targetEntityId);
|
|
1021
1051
|
if (references) {
|
|
1022
|
-
references.forEach((
|
|
1023
|
-
if (
|
|
1024
|
-
references.delete(
|
|
1052
|
+
references.forEach((reference) => {
|
|
1053
|
+
if (reference.sourceEntityId === sourceEntityId && reference.componentType === componentType) {
|
|
1054
|
+
references.delete(reference);
|
|
1025
1055
|
}
|
|
1026
1056
|
});
|
|
1027
1057
|
if (references.size === 0) {
|
|
1028
|
-
this.
|
|
1058
|
+
this.entityReferences.delete(targetEntityId);
|
|
1029
1059
|
}
|
|
1030
1060
|
}
|
|
1031
1061
|
}
|
|
1032
|
-
|
|
1033
|
-
const references = this.
|
|
1062
|
+
getEntityReferences(targetEntityId) {
|
|
1063
|
+
const references = this.entityReferences.get(targetEntityId);
|
|
1034
1064
|
return references ? Array.from(references) : [];
|
|
1035
1065
|
}
|
|
1036
|
-
|
|
1066
|
+
cleanupEmptyArchetype(archetype) {
|
|
1037
1067
|
if (archetype.getEntities().length > 0) {
|
|
1038
1068
|
return;
|
|
1039
1069
|
}
|
|
@@ -1041,28 +1071,28 @@ class World {
|
|
|
1041
1071
|
if (index !== -1) {
|
|
1042
1072
|
this.archetypes.splice(index, 1);
|
|
1043
1073
|
}
|
|
1044
|
-
const hashKey = this.
|
|
1045
|
-
this.
|
|
1074
|
+
const hashKey = this.createArchetypeSignature(archetype.componentTypes);
|
|
1075
|
+
this.archetypeBySignature.delete(hashKey);
|
|
1046
1076
|
for (const componentType of archetype.componentTypes) {
|
|
1047
|
-
const archetypes = this.
|
|
1077
|
+
const archetypes = this.archetypesByComponent.get(componentType);
|
|
1048
1078
|
if (archetypes) {
|
|
1049
1079
|
const compIndex = archetypes.indexOf(archetype);
|
|
1050
1080
|
if (compIndex !== -1) {
|
|
1051
1081
|
archetypes.splice(compIndex, 1);
|
|
1052
1082
|
if (archetypes.length === 0) {
|
|
1053
|
-
this.
|
|
1083
|
+
this.archetypesByComponent.delete(componentType);
|
|
1054
1084
|
}
|
|
1055
1085
|
}
|
|
1056
1086
|
}
|
|
1057
1087
|
}
|
|
1058
1088
|
}
|
|
1059
|
-
|
|
1089
|
+
triggerLifecycleHooks(entityId, addedComponents, removedComponents) {
|
|
1060
1090
|
for (const [componentType, component2] of addedComponents) {
|
|
1061
1091
|
const directHooks = this.lifecycleHooks.get(componentType);
|
|
1062
1092
|
if (directHooks) {
|
|
1063
|
-
for (const
|
|
1064
|
-
if (
|
|
1065
|
-
|
|
1093
|
+
for (const lifecycleHook of directHooks) {
|
|
1094
|
+
if (lifecycleHook.onAdded) {
|
|
1095
|
+
lifecycleHook.onAdded(entityId, componentType, component2);
|
|
1066
1096
|
}
|
|
1067
1097
|
}
|
|
1068
1098
|
}
|
|
@@ -1071,9 +1101,9 @@ class World {
|
|
|
1071
1101
|
const wildcardRelationId = relation(detailedType.componentId, "*");
|
|
1072
1102
|
const wildcardHooks = this.lifecycleHooks.get(wildcardRelationId);
|
|
1073
1103
|
if (wildcardHooks) {
|
|
1074
|
-
for (const
|
|
1075
|
-
if (
|
|
1076
|
-
|
|
1104
|
+
for (const lifecycleHook of wildcardHooks) {
|
|
1105
|
+
if (lifecycleHook.onAdded) {
|
|
1106
|
+
lifecycleHook.onAdded(entityId, componentType, component2);
|
|
1077
1107
|
}
|
|
1078
1108
|
}
|
|
1079
1109
|
}
|
|
@@ -1082,9 +1112,9 @@ class World {
|
|
|
1082
1112
|
for (const componentType of removedComponents) {
|
|
1083
1113
|
const directHooks = this.lifecycleHooks.get(componentType);
|
|
1084
1114
|
if (directHooks) {
|
|
1085
|
-
for (const
|
|
1086
|
-
if (
|
|
1087
|
-
|
|
1115
|
+
for (const lifecycleHook of directHooks) {
|
|
1116
|
+
if (lifecycleHook.onRemoved) {
|
|
1117
|
+
lifecycleHook.onRemoved(entityId, componentType);
|
|
1088
1118
|
}
|
|
1089
1119
|
}
|
|
1090
1120
|
}
|
|
@@ -1119,46 +1149,6 @@ class World {
|
|
|
1119
1149
|
entities
|
|
1120
1150
|
};
|
|
1121
1151
|
}
|
|
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
1152
|
}
|
|
1163
1153
|
export {
|
|
1164
1154
|
relation,
|
package/package.json
CHANGED
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
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
|
-
|
|
43
|
+
constructor(snapshot?: any);
|
|
34
44
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
45
|
+
* Generate a signature string for component types array
|
|
46
|
+
* @returns A string signature for the component types
|
|
37
47
|
*/
|
|
38
|
-
private
|
|
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
|
|
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
|
-
|
|
136
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
179
|
+
private getEntityReferences;
|
|
168
180
|
/**
|
|
169
181
|
* Remove an empty archetype from all internal data structures
|
|
170
182
|
*/
|
|
171
|
-
private
|
|
183
|
+
private cleanupEmptyArchetype;
|
|
172
184
|
/**
|
|
173
185
|
* Execute component lifecycle hooks for added and removed components
|
|
174
186
|
*/
|
|
175
|
-
private
|
|
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
|
}
|