@codehz/ecs 0.6.5 → 0.6.7
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/builder.d.mts +306 -1
- package/package.json +1 -1
- package/world.mjs +311 -3
- package/world.mjs.map +1 -1
package/world.mjs
CHANGED
|
@@ -1766,6 +1766,8 @@ var World = class {
|
|
|
1766
1766
|
archetypesByComponent = /* @__PURE__ */ new Map();
|
|
1767
1767
|
entityReferences = /* @__PURE__ */ new Map();
|
|
1768
1768
|
dontFragmentRelations = /* @__PURE__ */ new Map();
|
|
1769
|
+
componentEntityComponents = /* @__PURE__ */ new Map();
|
|
1770
|
+
relationEntityIdsByTarget = /* @__PURE__ */ new Map();
|
|
1769
1771
|
queries = [];
|
|
1770
1772
|
queryCache = /* @__PURE__ */ new Map();
|
|
1771
1773
|
commandBuffer = new CommandBuffer((entityId, commands) => this.executeEntityCommands(entityId, commands));
|
|
@@ -1776,6 +1778,18 @@ var World = class {
|
|
|
1776
1778
|
}
|
|
1777
1779
|
deserializeSnapshot(snapshot) {
|
|
1778
1780
|
if (snapshot.entityManager) this.entityIdManager.deserializeState(snapshot.entityManager);
|
|
1781
|
+
if (Array.isArray(snapshot.componentEntities)) for (const entry of snapshot.componentEntities) {
|
|
1782
|
+
const entityId = decodeSerializedId(entry.id);
|
|
1783
|
+
if (!this.isComponentEntityId(entityId)) continue;
|
|
1784
|
+
const componentsArray = entry.components || [];
|
|
1785
|
+
const componentMap = /* @__PURE__ */ new Map();
|
|
1786
|
+
for (const componentEntry of componentsArray) {
|
|
1787
|
+
const componentType = decodeSerializedId(componentEntry.type);
|
|
1788
|
+
componentMap.set(componentType, componentEntry.value);
|
|
1789
|
+
}
|
|
1790
|
+
this.componentEntityComponents.set(entityId, componentMap);
|
|
1791
|
+
this.registerRelationEntityId(entityId);
|
|
1792
|
+
}
|
|
1779
1793
|
if (Array.isArray(snapshot.entities)) for (const entry of snapshot.entities) {
|
|
1780
1794
|
const entityId = decodeSerializedId(entry.id);
|
|
1781
1795
|
const componentsArray = entry.components || [];
|
|
@@ -1799,6 +1813,18 @@ var World = class {
|
|
|
1799
1813
|
createArchetypeSignature(componentTypes) {
|
|
1800
1814
|
return componentTypes.join(",");
|
|
1801
1815
|
}
|
|
1816
|
+
/**
|
|
1817
|
+
* Creates a new entity.
|
|
1818
|
+
* The entity is created with an empty component set and can be configured using `set()`.
|
|
1819
|
+
*
|
|
1820
|
+
* @template T - The initial component type (defaults to void if not specified)
|
|
1821
|
+
* @returns A unique identifier for the new entity
|
|
1822
|
+
*
|
|
1823
|
+
* @example
|
|
1824
|
+
* const entity = world.new<MyComponent>();
|
|
1825
|
+
* world.set(entity, MyComponent, { value: 42 });
|
|
1826
|
+
* world.sync();
|
|
1827
|
+
*/
|
|
1802
1828
|
new() {
|
|
1803
1829
|
const entityId = this.entityIdManager.allocate();
|
|
1804
1830
|
let emptyArchetype = this.ensureArchetype([]);
|
|
@@ -1806,6 +1832,50 @@ var World = class {
|
|
|
1806
1832
|
this.entityToArchetype.set(entityId, emptyArchetype);
|
|
1807
1833
|
return entityId;
|
|
1808
1834
|
}
|
|
1835
|
+
isComponentEntityId(entityId) {
|
|
1836
|
+
const detailed = getDetailedIdType(entityId);
|
|
1837
|
+
return detailed.type !== "entity" && detailed.type !== "invalid";
|
|
1838
|
+
}
|
|
1839
|
+
registerRelationEntityId(entityId) {
|
|
1840
|
+
const detailed = getDetailedIdType(entityId);
|
|
1841
|
+
if (detailed.type !== "entity-relation") return;
|
|
1842
|
+
const targetId = detailed.targetId;
|
|
1843
|
+
if (targetId === void 0) return;
|
|
1844
|
+
const existing = this.relationEntityIdsByTarget.get(targetId);
|
|
1845
|
+
if (existing) {
|
|
1846
|
+
existing.add(entityId);
|
|
1847
|
+
return;
|
|
1848
|
+
}
|
|
1849
|
+
this.relationEntityIdsByTarget.set(targetId, new Set([entityId]));
|
|
1850
|
+
}
|
|
1851
|
+
unregisterRelationEntityId(entityId) {
|
|
1852
|
+
const detailed = getDetailedIdType(entityId);
|
|
1853
|
+
if (detailed.type !== "entity-relation") return;
|
|
1854
|
+
const targetId = detailed.targetId;
|
|
1855
|
+
if (targetId === void 0) return;
|
|
1856
|
+
const existing = this.relationEntityIdsByTarget.get(targetId);
|
|
1857
|
+
if (!existing) return;
|
|
1858
|
+
existing.delete(entityId);
|
|
1859
|
+
if (existing.size === 0) this.relationEntityIdsByTarget.delete(targetId);
|
|
1860
|
+
}
|
|
1861
|
+
getComponentEntityComponents(entityId, create) {
|
|
1862
|
+
let data = this.componentEntityComponents.get(entityId);
|
|
1863
|
+
if (!data && create) {
|
|
1864
|
+
data = /* @__PURE__ */ new Map();
|
|
1865
|
+
this.componentEntityComponents.set(entityId, data);
|
|
1866
|
+
this.registerRelationEntityId(entityId);
|
|
1867
|
+
}
|
|
1868
|
+
return data;
|
|
1869
|
+
}
|
|
1870
|
+
clearComponentEntityComponents(entityId) {
|
|
1871
|
+
if (this.componentEntityComponents.delete(entityId)) this.unregisterRelationEntityId(entityId);
|
|
1872
|
+
}
|
|
1873
|
+
cleanupComponentEntitiesReferencingEntity(targetId) {
|
|
1874
|
+
const relationEntities = this.relationEntityIdsByTarget.get(targetId);
|
|
1875
|
+
if (!relationEntities) return;
|
|
1876
|
+
for (const relationEntityId of relationEntities) this.componentEntityComponents.delete(relationEntityId);
|
|
1877
|
+
this.relationEntityIdsByTarget.delete(targetId);
|
|
1878
|
+
}
|
|
1809
1879
|
destroyEntityImmediate(entityId) {
|
|
1810
1880
|
const queue = [entityId];
|
|
1811
1881
|
const visited = /* @__PURE__ */ new Set();
|
|
@@ -1827,9 +1897,22 @@ var World = class {
|
|
|
1827
1897
|
triggerRemoveHooksForEntityDeletion(this.createHooksContext(), cur, removedComponents, archetype);
|
|
1828
1898
|
this.cleanupArchetypesReferencingEntity(cur);
|
|
1829
1899
|
this.entityIdManager.deallocate(cur);
|
|
1900
|
+
this.cleanupComponentEntitiesReferencingEntity(cur);
|
|
1830
1901
|
}
|
|
1831
1902
|
}
|
|
1903
|
+
/**
|
|
1904
|
+
* Checks if an entity exists in the world.
|
|
1905
|
+
*
|
|
1906
|
+
* @param entityId - The entity identifier to check
|
|
1907
|
+
* @returns `true` if the entity exists, `false` otherwise
|
|
1908
|
+
*
|
|
1909
|
+
* @example
|
|
1910
|
+
* if (world.exists(entityId)) {
|
|
1911
|
+
* console.log("Entity exists");
|
|
1912
|
+
* }
|
|
1913
|
+
*/
|
|
1832
1914
|
exists(entityId) {
|
|
1915
|
+
if (this.isComponentEntityId(entityId)) return true;
|
|
1833
1916
|
return this.entityToArchetype.has(entityId);
|
|
1834
1917
|
}
|
|
1835
1918
|
set(entityId, componentType, component$1) {
|
|
@@ -1839,22 +1922,91 @@ var World = class {
|
|
|
1839
1922
|
if (detailedType.type === "wildcard-relation") throw new Error(`Cannot directly add wildcard relation components: ${componentType}`);
|
|
1840
1923
|
this.commandBuffer.set(entityId, componentType, component$1);
|
|
1841
1924
|
}
|
|
1925
|
+
/**
|
|
1926
|
+
* Removes a component from an entity.
|
|
1927
|
+
* The change is buffered and takes effect after calling `world.sync()`.
|
|
1928
|
+
* If the entity does not exist, throws an error.
|
|
1929
|
+
*
|
|
1930
|
+
* @template T - The component data type
|
|
1931
|
+
* @param entityId - The entity identifier
|
|
1932
|
+
* @param componentType - The component type to remove
|
|
1933
|
+
*
|
|
1934
|
+
* @throws {Error} If the entity does not exist
|
|
1935
|
+
* @throws {Error} If the component type is invalid
|
|
1936
|
+
*
|
|
1937
|
+
* @example
|
|
1938
|
+
* world.remove(entity, Position);
|
|
1939
|
+
* world.sync(); // Apply changes
|
|
1940
|
+
*/
|
|
1842
1941
|
remove(entityId, componentType) {
|
|
1843
1942
|
if (!this.exists(entityId)) throw new Error(`Entity ${entityId} does not exist`);
|
|
1844
1943
|
if (getDetailedIdType(componentType).type === "invalid") throw new Error(`Invalid component type: ${componentType}`);
|
|
1845
1944
|
this.commandBuffer.remove(entityId, componentType);
|
|
1846
1945
|
}
|
|
1946
|
+
/**
|
|
1947
|
+
* Deletes an entity and all its components from the world.
|
|
1948
|
+
* The change is buffered and takes effect after calling `world.sync()`.
|
|
1949
|
+
* Related entities may trigger cascade delete hooks if configured.
|
|
1950
|
+
*
|
|
1951
|
+
* @param entityId - The entity identifier to delete
|
|
1952
|
+
*
|
|
1953
|
+
* @example
|
|
1954
|
+
* world.delete(entity);
|
|
1955
|
+
* world.sync(); // Apply changes
|
|
1956
|
+
*/
|
|
1847
1957
|
delete(entityId) {
|
|
1848
1958
|
this.commandBuffer.delete(entityId);
|
|
1849
1959
|
}
|
|
1960
|
+
/**
|
|
1961
|
+
* Checks if an entity has a specific component.
|
|
1962
|
+
* Immediately reflects the current state without waiting for `sync()`.
|
|
1963
|
+
*
|
|
1964
|
+
* @template T - The component data type
|
|
1965
|
+
* @param entityId - The entity identifier
|
|
1966
|
+
* @param componentType - The component type to check
|
|
1967
|
+
* @returns `true` if the entity has the component, `false` otherwise
|
|
1968
|
+
*
|
|
1969
|
+
* @example
|
|
1970
|
+
* if (world.has(entity, Position)) {
|
|
1971
|
+
* const pos = world.get(entity, Position);
|
|
1972
|
+
* }
|
|
1973
|
+
*/
|
|
1850
1974
|
has(entityId, componentType) {
|
|
1975
|
+
if (this.isComponentEntityId(entityId)) {
|
|
1976
|
+
if (isWildcardRelationId(componentType)) {
|
|
1977
|
+
const componentId = getComponentIdFromRelationId(componentType);
|
|
1978
|
+
if (componentId === void 0) return false;
|
|
1979
|
+
const data = this.componentEntityComponents.get(entityId);
|
|
1980
|
+
if (!data) return false;
|
|
1981
|
+
for (const key of data.keys()) if (getComponentIdFromRelationId(key) === componentId) return true;
|
|
1982
|
+
return false;
|
|
1983
|
+
}
|
|
1984
|
+
return this.componentEntityComponents.get(entityId)?.has(componentType) ?? false;
|
|
1985
|
+
}
|
|
1851
1986
|
const archetype = this.entityToArchetype.get(entityId);
|
|
1852
1987
|
if (!archetype) return false;
|
|
1853
1988
|
if (archetype.componentTypes.includes(componentType)) return true;
|
|
1854
1989
|
if (isDontFragmentRelation(componentType)) return this.dontFragmentRelations.get(entityId)?.has(componentType) ?? false;
|
|
1855
1990
|
return false;
|
|
1856
1991
|
}
|
|
1857
|
-
get(entityId, componentType) {
|
|
1992
|
+
get(entityId, componentType = entityId) {
|
|
1993
|
+
if (this.isComponentEntityId(entityId)) {
|
|
1994
|
+
if (isWildcardRelationId(componentType)) {
|
|
1995
|
+
const componentId = getComponentIdFromRelationId(componentType);
|
|
1996
|
+
const data$1 = this.componentEntityComponents.get(entityId);
|
|
1997
|
+
const relations = [];
|
|
1998
|
+
if (componentId !== void 0 && data$1) {
|
|
1999
|
+
for (const [key, value] of data$1.entries()) if (getComponentIdFromRelationId(key) === componentId) {
|
|
2000
|
+
const detailed = getDetailedIdType(key);
|
|
2001
|
+
if (detailed.type === "entity-relation" || detailed.type === "component-relation") relations.push([detailed.targetId, value]);
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
return relations;
|
|
2005
|
+
}
|
|
2006
|
+
const data = this.componentEntityComponents.get(entityId);
|
|
2007
|
+
if (!data || !data.has(componentType)) throw new Error(`Entity ${entityId} does not have component ${componentType}. Use has() to check component existence before calling get().`);
|
|
2008
|
+
return data.get(componentType);
|
|
2009
|
+
}
|
|
1858
2010
|
const archetype = this.entityToArchetype.get(entityId);
|
|
1859
2011
|
if (!archetype) throw new Error(`Entity ${entityId} does not exist`);
|
|
1860
2012
|
if (componentType >= 0 || componentType % 2 ** 42 !== 0) {
|
|
@@ -1864,7 +2016,25 @@ var World = class {
|
|
|
1864
2016
|
}
|
|
1865
2017
|
return archetype.get(entityId, componentType);
|
|
1866
2018
|
}
|
|
1867
|
-
getOptional(entityId, componentType) {
|
|
2019
|
+
getOptional(entityId, componentType = entityId) {
|
|
2020
|
+
if (this.isComponentEntityId(entityId)) {
|
|
2021
|
+
if (isWildcardRelationId(componentType)) {
|
|
2022
|
+
const componentId = getComponentIdFromRelationId(componentType);
|
|
2023
|
+
if (componentId === void 0) return void 0;
|
|
2024
|
+
const data$1 = this.componentEntityComponents.get(entityId);
|
|
2025
|
+
if (!data$1) return void 0;
|
|
2026
|
+
const relations = [];
|
|
2027
|
+
for (const [key, value] of data$1.entries()) if (getComponentIdFromRelationId(key) === componentId) {
|
|
2028
|
+
const detailed = getDetailedIdType(key);
|
|
2029
|
+
if (detailed.type === "entity-relation" || detailed.type === "component-relation") relations.push([detailed.targetId, value]);
|
|
2030
|
+
}
|
|
2031
|
+
if (relations.length === 0) return void 0;
|
|
2032
|
+
return { value: relations };
|
|
2033
|
+
}
|
|
2034
|
+
const data = this.componentEntityComponents.get(entityId);
|
|
2035
|
+
if (!data || !data.has(componentType)) return void 0;
|
|
2036
|
+
return { value: data.get(componentType) };
|
|
2037
|
+
}
|
|
1868
2038
|
const archetype = this.entityToArchetype.get(entityId);
|
|
1869
2039
|
if (!archetype) throw new Error(`Entity ${entityId} does not exist`);
|
|
1870
2040
|
if (isWildcardRelationId(componentType)) {
|
|
@@ -1957,9 +2127,47 @@ var World = class {
|
|
|
1957
2127
|
}
|
|
1958
2128
|
}
|
|
1959
2129
|
}
|
|
2130
|
+
/**
|
|
2131
|
+
* Synchronizes all buffered commands (set/remove/delete) to the world.
|
|
2132
|
+
* This method must be called after making changes via `set()`, `remove()`, or `delete()` for them to take effect.
|
|
2133
|
+
* Typically called once per frame at the end of your game loop.
|
|
2134
|
+
*
|
|
2135
|
+
* @example
|
|
2136
|
+
* world.set(entity, Position, { x: 10, y: 20 });
|
|
2137
|
+
* world.remove(entity, OldComponent);
|
|
2138
|
+
* world.sync(); // Apply all buffered changes
|
|
2139
|
+
*/
|
|
1960
2140
|
sync() {
|
|
1961
2141
|
this.commandBuffer.execute();
|
|
1962
2142
|
}
|
|
2143
|
+
/**
|
|
2144
|
+
* Creates a cached query for efficiently iterating entities with specific components.
|
|
2145
|
+
* The query is cached internally and reused across calls with the same component types and filter.
|
|
2146
|
+
*
|
|
2147
|
+
* **Important:** Store the query reference and reuse it across frames for optimal performance.
|
|
2148
|
+
* Creating a new query each frame defeats the caching mechanism.
|
|
2149
|
+
*
|
|
2150
|
+
* @param componentTypes - Array of component types to match
|
|
2151
|
+
* @param filter - Optional filter for additional constraints (e.g., without specific components)
|
|
2152
|
+
* @returns A Query instance that can be used to iterate matching entities
|
|
2153
|
+
*
|
|
2154
|
+
* @example
|
|
2155
|
+
* // Create once, reuse many times
|
|
2156
|
+
* const movementQuery = world.createQuery([Position, Velocity]);
|
|
2157
|
+
*
|
|
2158
|
+
* // In game loop
|
|
2159
|
+
* movementQuery.forEach((entity) => {
|
|
2160
|
+
* const pos = world.get(entity, Position);
|
|
2161
|
+
* const vel = world.get(entity, Velocity);
|
|
2162
|
+
* pos.x += vel.x;
|
|
2163
|
+
* pos.y += vel.y;
|
|
2164
|
+
* });
|
|
2165
|
+
*
|
|
2166
|
+
* // With filter
|
|
2167
|
+
* const activeQuery = world.createQuery([Position], {
|
|
2168
|
+
* without: [Disabled]
|
|
2169
|
+
* });
|
|
2170
|
+
*/
|
|
1963
2171
|
createQuery(componentTypes, filter = {}) {
|
|
1964
2172
|
const sortedTypes = [...componentTypes].sort((a, b) => a - b);
|
|
1965
2173
|
const filterKey = serializeQueryFilter(filter);
|
|
@@ -1976,9 +2184,38 @@ var World = class {
|
|
|
1976
2184
|
});
|
|
1977
2185
|
return query;
|
|
1978
2186
|
}
|
|
2187
|
+
/**
|
|
2188
|
+
* Creates a new entity builder for fluent entity configuration.
|
|
2189
|
+
* Useful for building entities with multiple components in a single expression.
|
|
2190
|
+
*
|
|
2191
|
+
* @returns An EntityBuilder instance
|
|
2192
|
+
*
|
|
2193
|
+
* @example
|
|
2194
|
+
* const entity = world.spawn()
|
|
2195
|
+
* .with(Position, { x: 0, y: 0 })
|
|
2196
|
+
* .with(Velocity, { x: 1, y: 1 })
|
|
2197
|
+
* .build();
|
|
2198
|
+
* world.sync(); // Apply changes
|
|
2199
|
+
*/
|
|
1979
2200
|
spawn() {
|
|
1980
2201
|
return new EntityBuilder(this);
|
|
1981
2202
|
}
|
|
2203
|
+
/**
|
|
2204
|
+
* Spawns multiple entities with a configuration callback.
|
|
2205
|
+
* More efficient than calling `spawn()` multiple times when creating many entities.
|
|
2206
|
+
*
|
|
2207
|
+
* @param count - Number of entities to spawn
|
|
2208
|
+
* @param configure - Callback that receives an EntityBuilder and index; must return the configured builder
|
|
2209
|
+
* @returns Array of created entity IDs
|
|
2210
|
+
*
|
|
2211
|
+
* @example
|
|
2212
|
+
* const entities = world.spawnMany(100, (builder, index) => {
|
|
2213
|
+
* return builder
|
|
2214
|
+
* .with(Position, { x: index * 10, y: 0 })
|
|
2215
|
+
* .with(Velocity, { x: 0, y: 1 });
|
|
2216
|
+
* });
|
|
2217
|
+
* world.sync();
|
|
2218
|
+
*/
|
|
1982
2219
|
spawnMany(count, configure) {
|
|
1983
2220
|
const entities = [];
|
|
1984
2221
|
for (let i = 0; i < count; i++) {
|
|
@@ -1994,6 +2231,17 @@ var World = class {
|
|
|
1994
2231
|
const index = this.queries.indexOf(query);
|
|
1995
2232
|
if (index !== -1) this.queries.splice(index, 1);
|
|
1996
2233
|
}
|
|
2234
|
+
/**
|
|
2235
|
+
* Releases a cached query and frees its resources if no longer needed.
|
|
2236
|
+
* Call this when you're done using a query to allow the world to clean up its cache entry.
|
|
2237
|
+
*
|
|
2238
|
+
* @param query - The query to release
|
|
2239
|
+
*
|
|
2240
|
+
* @example
|
|
2241
|
+
* const query = world.createQuery([Position]);
|
|
2242
|
+
* // ... use query ...
|
|
2243
|
+
* world.releaseQuery(query); // Optional cleanup
|
|
2244
|
+
*/
|
|
1997
2245
|
releaseQuery(query) {
|
|
1998
2246
|
for (const [k, v] of this.queryCache.entries()) if (v.query === query) {
|
|
1999
2247
|
v.refCount--;
|
|
@@ -2005,6 +2253,14 @@ var World = class {
|
|
|
2005
2253
|
return;
|
|
2006
2254
|
}
|
|
2007
2255
|
}
|
|
2256
|
+
/**
|
|
2257
|
+
* Returns all archetypes that contain entities with the specified components.
|
|
2258
|
+
* Used internally for query optimization but can be useful for debugging.
|
|
2259
|
+
*
|
|
2260
|
+
* @param componentTypes - Array of component types to match
|
|
2261
|
+
* @returns Array of Archetype objects containing matching components
|
|
2262
|
+
* @internal
|
|
2263
|
+
*/
|
|
2008
2264
|
getMatchingArchetypes(componentTypes) {
|
|
2009
2265
|
if (componentTypes.length === 0) return [...this.archetypes];
|
|
2010
2266
|
const regularComponents = [];
|
|
@@ -2043,6 +2299,10 @@ var World = class {
|
|
|
2043
2299
|
}
|
|
2044
2300
|
executeEntityCommands(entityId, commands) {
|
|
2045
2301
|
const changeset = new ComponentChangeset();
|
|
2302
|
+
if (this.isComponentEntityId(entityId)) {
|
|
2303
|
+
this.executeComponentEntityCommands(entityId, commands);
|
|
2304
|
+
return changeset;
|
|
2305
|
+
}
|
|
2046
2306
|
if (commands.some((cmd) => cmd.type === "destroy")) {
|
|
2047
2307
|
this.destroyEntityImmediate(entityId);
|
|
2048
2308
|
return changeset;
|
|
@@ -2060,6 +2320,24 @@ var World = class {
|
|
|
2060
2320
|
triggerLifecycleHooks(this.createHooksContext(), entityId, changeset.adds, removedComponents, currentArchetype, newArchetype);
|
|
2061
2321
|
return changeset;
|
|
2062
2322
|
}
|
|
2323
|
+
executeComponentEntityCommands(entityId, commands) {
|
|
2324
|
+
if (commands.some((cmd) => cmd.type === "destroy")) {
|
|
2325
|
+
this.clearComponentEntityComponents(entityId);
|
|
2326
|
+
return;
|
|
2327
|
+
}
|
|
2328
|
+
for (const command of commands) if (command.type === "set" && command.componentType) this.getComponentEntityComponents(entityId, true).set(command.componentType, command.component);
|
|
2329
|
+
else if (command.type === "delete" && command.componentType) {
|
|
2330
|
+
const data = this.componentEntityComponents.get(entityId);
|
|
2331
|
+
if (!data) continue;
|
|
2332
|
+
if (isWildcardRelationId(command.componentType)) {
|
|
2333
|
+
const componentId = getComponentIdFromRelationId(command.componentType);
|
|
2334
|
+
if (componentId !== void 0) {
|
|
2335
|
+
for (const key of Array.from(data.keys())) if (getComponentIdFromRelationId(key) === componentId) data.delete(key);
|
|
2336
|
+
}
|
|
2337
|
+
} else data.delete(command.componentType);
|
|
2338
|
+
if (data.size === 0) this.clearComponentEntityComponents(entityId);
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2063
2341
|
createHooksContext() {
|
|
2064
2342
|
return {
|
|
2065
2343
|
hooks: this.legacyHooks,
|
|
@@ -2148,6 +2426,27 @@ var World = class {
|
|
|
2148
2426
|
}
|
|
2149
2427
|
for (const query of this.queries) query.removeArchetype(archetype);
|
|
2150
2428
|
}
|
|
2429
|
+
/**
|
|
2430
|
+
* Serializes the entire world state to a plain JavaScript object.
|
|
2431
|
+
* This creates a "memory snapshot" that can be stored or transmitted.
|
|
2432
|
+
* The snapshot can be restored using `new World(snapshot)`.
|
|
2433
|
+
*
|
|
2434
|
+
* **Note:** This is NOT automatically persistent storage. To persist data,
|
|
2435
|
+
* you must serialize the returned object to JSON or another format yourself.
|
|
2436
|
+
*
|
|
2437
|
+
* @returns A serializable object representing the world state
|
|
2438
|
+
*
|
|
2439
|
+
* @example
|
|
2440
|
+
* // Create snapshot
|
|
2441
|
+
* const snapshot = world.serialize();
|
|
2442
|
+
*
|
|
2443
|
+
* // Save to storage (example)
|
|
2444
|
+
* localStorage.setItem('save', JSON.stringify(snapshot));
|
|
2445
|
+
*
|
|
2446
|
+
* // Later, restore from snapshot
|
|
2447
|
+
* const savedData = JSON.parse(localStorage.getItem('save'));
|
|
2448
|
+
* const newWorld = new World(savedData);
|
|
2449
|
+
*/
|
|
2151
2450
|
serialize() {
|
|
2152
2451
|
const entities = [];
|
|
2153
2452
|
for (const archetype of this.archetypes) {
|
|
@@ -2160,10 +2459,19 @@ var World = class {
|
|
|
2160
2459
|
}))
|
|
2161
2460
|
});
|
|
2162
2461
|
}
|
|
2462
|
+
const componentEntities = [];
|
|
2463
|
+
for (const [entityId, components] of this.componentEntityComponents.entries()) componentEntities.push({
|
|
2464
|
+
id: encodeEntityId(entityId),
|
|
2465
|
+
components: Array.from(components.entries()).map(([rawType, value]) => ({
|
|
2466
|
+
type: encodeEntityId(rawType),
|
|
2467
|
+
value: value === MISSING_COMPONENT ? void 0 : value
|
|
2468
|
+
}))
|
|
2469
|
+
});
|
|
2163
2470
|
return {
|
|
2164
2471
|
version: 1,
|
|
2165
2472
|
entityManager: this.entityIdManager.serializeState(),
|
|
2166
|
-
entities
|
|
2473
|
+
entities,
|
|
2474
|
+
componentEntities
|
|
2167
2475
|
};
|
|
2168
2476
|
}
|
|
2169
2477
|
};
|