@codehz/ecs 0.6.6 → 0.6.8

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 CHANGED
@@ -405,6 +405,7 @@ type SerializedWorld = {
405
405
  version: number;
406
406
  entityManager: any;
407
407
  entities: SerializedEntity[];
408
+ componentEntities?: SerializedEntity[];
408
409
  };
409
410
  type SerializedEntity = {
410
411
  id: SerializedEntityId;
@@ -428,6 +429,8 @@ declare class World {
428
429
  private archetypesByComponent;
429
430
  private entityReferences;
430
431
  private dontFragmentRelations;
432
+ private componentEntityComponents;
433
+ private relationEntityIdsByTarget;
431
434
  private queries;
432
435
  private queryCache;
433
436
  private commandBuffer;
@@ -449,6 +452,12 @@ declare class World {
449
452
  * world.sync();
450
453
  */
451
454
  new<T = void>(): EntityId<T>;
455
+ private isComponentEntityId;
456
+ private registerRelationEntityId;
457
+ private unregisterRelationEntityId;
458
+ private getComponentEntityComponents;
459
+ private clearComponentEntityComponents;
460
+ private cleanupComponentEntitiesReferencingEntity;
452
461
  private destroyEntityImmediate;
453
462
  /**
454
463
  * Checks if an entity exists in the world.
@@ -473,21 +482,32 @@ declare class World {
473
482
  * @overload set<T>(entityId: EntityId, componentType: EntityId<T>, component: NoInfer<T>): void
474
483
  * Adds or updates a component with data on the entity
475
484
  *
485
+ * @overload set<T>(componentId: ComponentId<T>, component: NoInfer<T>): void
486
+ * Adds or updates a singleton component (shorthand for set(componentId, componentId, component))
487
+ *
476
488
  * @throws {Error} If the entity does not exist
477
489
  * @throws {Error} If the component type is invalid or is a wildcard relation
478
490
  *
479
491
  * @example
480
492
  * world.set(entity, Position, { x: 10, y: 20 });
481
493
  * world.set(entity, Marker); // void component
494
+ * world.set(GlobalConfig, { debug: true }); // singleton component
482
495
  * world.sync(); // Apply changes
483
496
  */
484
497
  set(entityId: EntityId, componentType: EntityId<void>): void;
485
498
  set<T>(entityId: EntityId, componentType: EntityId<T>, component: NoInfer<T>): void;
499
+ set<T>(componentId: ComponentId<T>, component: NoInfer<T>): void;
486
500
  /**
487
501
  * Removes a component from an entity.
488
502
  * The change is buffered and takes effect after calling `world.sync()`.
489
503
  * If the entity does not exist, throws an error.
490
504
  *
505
+ * @overload remove<T>(entityId: EntityId, componentType: EntityId<T>): void
506
+ * Removes a component from an entity.
507
+ *
508
+ * @overload remove<T>(componentId: ComponentId<T>): void
509
+ * Removes a singleton component (shorthand for remove(componentId, componentId)).
510
+ *
491
511
  * @template T - The component data type
492
512
  * @param entityId - The entity identifier
493
513
  * @param componentType - The component type to remove
@@ -497,8 +517,10 @@ declare class World {
497
517
  *
498
518
  * @example
499
519
  * world.remove(entity, Position);
520
+ * world.remove(GlobalConfig); // Remove singleton component
500
521
  * world.sync(); // Apply changes
501
522
  */
523
+ remove<T>(componentId: ComponentId<T>): void;
502
524
  remove<T>(entityId: EntityId, componentType: EntityId<T>): void;
503
525
  /**
504
526
  * Deletes an entity and all its components from the world.
@@ -516,6 +538,12 @@ declare class World {
516
538
  * Checks if an entity has a specific component.
517
539
  * Immediately reflects the current state without waiting for `sync()`.
518
540
  *
541
+ * @overload has<T>(entityId: EntityId, componentType: EntityId<T>): boolean
542
+ * Checks if a specific component type is present on the entity.
543
+ *
544
+ * @overload has<T>(componentId: ComponentId<T>): boolean
545
+ * Checks if a singleton component has data (shorthand for has(componentId, componentId)).
546
+ *
519
547
  * @template T - The component data type
520
548
  * @param entityId - The entity identifier
521
549
  * @param componentType - The component type to check
@@ -525,7 +553,11 @@ declare class World {
525
553
  * if (world.has(entity, Position)) {
526
554
  * const pos = world.get(entity, Position);
527
555
  * }
556
+ * if (world.has(GlobalConfig)) {
557
+ * const config = world.get(GlobalConfig);
558
+ * }
528
559
  */
560
+ has<T>(componentId: ComponentId<T>): boolean;
529
561
  has<T>(entityId: EntityId, componentType: EntityId<T>): boolean;
530
562
  /**
531
563
  * Retrieves a component from an entity.
@@ -747,6 +779,7 @@ declare class World {
747
779
  components: ComponentTuple<T>;
748
780
  }>;
749
781
  executeEntityCommands(entityId: EntityId, commands: Command[]): ComponentChangeset;
782
+ private executeComponentEntityCommands;
750
783
  private createHooksContext;
751
784
  private removeComponentImmediate;
752
785
  private updateEntityReferences;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codehz/ecs",
3
- "version": "0.6.6",
3
+ "version": "0.6.8",
4
4
  "repository": {
5
5
  "url": "https://github.com/codehz/ecs"
6
6
  },
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 || [];
@@ -1818,6 +1832,50 @@ var World = class {
1818
1832
  this.entityToArchetype.set(entityId, emptyArchetype);
1819
1833
  return entityId;
1820
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
+ }
1821
1879
  destroyEntityImmediate(entityId) {
1822
1880
  const queue = [entityId];
1823
1881
  const visited = /* @__PURE__ */ new Set();
@@ -1839,6 +1897,7 @@ var World = class {
1839
1897
  triggerRemoveHooksForEntityDeletion(this.createHooksContext(), cur, removedComponents, archetype);
1840
1898
  this.cleanupArchetypesReferencingEntity(cur);
1841
1899
  this.entityIdManager.deallocate(cur);
1900
+ this.cleanupComponentEntitiesReferencingEntity(cur);
1842
1901
  }
1843
1902
  }
1844
1903
  /**
@@ -1853,35 +1912,43 @@ var World = class {
1853
1912
  * }
1854
1913
  */
1855
1914
  exists(entityId) {
1915
+ if (this.isComponentEntityId(entityId)) return true;
1856
1916
  return this.entityToArchetype.has(entityId);
1857
1917
  }
1858
- set(entityId, componentType, component$1) {
1859
- if (!this.exists(entityId)) throw new Error(`Entity ${entityId} does not exist`);
1918
+ set(entityId, componentTypeOrComponent, maybeComponent) {
1919
+ if (maybeComponent === void 0 && componentTypeOrComponent !== void 0) {
1920
+ const detailedType$1 = getDetailedIdType(entityId);
1921
+ if (detailedType$1.type === "component" || detailedType$1.type === "component-relation") {
1922
+ const componentId = entityId;
1923
+ const component$2 = componentTypeOrComponent;
1924
+ if (!this.exists(componentId)) throw new Error(`Component entity ${componentId} does not exist`);
1925
+ const detailedComponentType = getDetailedIdType(componentId);
1926
+ if (detailedComponentType.type === "invalid") throw new Error(`Invalid component type: ${componentId}`);
1927
+ if (detailedComponentType.type === "wildcard-relation") throw new Error(`Cannot directly add wildcard relation components: ${componentId}`);
1928
+ this.commandBuffer.set(componentId, componentId, component$2);
1929
+ return;
1930
+ }
1931
+ }
1932
+ const entityIdArg = entityId;
1933
+ const componentType = componentTypeOrComponent;
1934
+ const component$1 = maybeComponent;
1935
+ if (!this.exists(entityIdArg)) throw new Error(`Entity ${entityIdArg} does not exist`);
1860
1936
  const detailedType = getDetailedIdType(componentType);
1861
1937
  if (detailedType.type === "invalid") throw new Error(`Invalid component type: ${componentType}`);
1862
1938
  if (detailedType.type === "wildcard-relation") throw new Error(`Cannot directly add wildcard relation components: ${componentType}`);
1863
- this.commandBuffer.set(entityId, componentType, component$1);
1939
+ this.commandBuffer.set(entityIdArg, componentType, component$1);
1864
1940
  }
1865
- /**
1866
- * Removes a component from an entity.
1867
- * The change is buffered and takes effect after calling `world.sync()`.
1868
- * If the entity does not exist, throws an error.
1869
- *
1870
- * @template T - The component data type
1871
- * @param entityId - The entity identifier
1872
- * @param componentType - The component type to remove
1873
- *
1874
- * @throws {Error} If the entity does not exist
1875
- * @throws {Error} If the component type is invalid
1876
- *
1877
- * @example
1878
- * world.remove(entity, Position);
1879
- * world.sync(); // Apply changes
1880
- */
1881
1941
  remove(entityId, componentType) {
1882
- if (!this.exists(entityId)) throw new Error(`Entity ${entityId} does not exist`);
1942
+ if (componentType === void 0) {
1943
+ const componentId = entityId;
1944
+ if (!this.exists(componentId)) throw new Error(`Component entity ${componentId} does not exist`);
1945
+ this.commandBuffer.remove(componentId, componentId);
1946
+ return;
1947
+ }
1948
+ const entityIdArg = entityId;
1949
+ if (!this.exists(entityIdArg)) throw new Error(`Entity ${entityIdArg} does not exist`);
1883
1950
  if (getDetailedIdType(componentType).type === "invalid") throw new Error(`Invalid component type: ${componentType}`);
1884
- this.commandBuffer.remove(entityId, componentType);
1951
+ this.commandBuffer.remove(entityIdArg, componentType);
1885
1952
  }
1886
1953
  /**
1887
1954
  * Deletes an entity and all its components from the world.
@@ -1897,21 +1964,22 @@ var World = class {
1897
1964
  delete(entityId) {
1898
1965
  this.commandBuffer.delete(entityId);
1899
1966
  }
1900
- /**
1901
- * Checks if an entity has a specific component.
1902
- * Immediately reflects the current state without waiting for `sync()`.
1903
- *
1904
- * @template T - The component data type
1905
- * @param entityId - The entity identifier
1906
- * @param componentType - The component type to check
1907
- * @returns `true` if the entity has the component, `false` otherwise
1908
- *
1909
- * @example
1910
- * if (world.has(entity, Position)) {
1911
- * const pos = world.get(entity, Position);
1912
- * }
1913
- */
1914
1967
  has(entityId, componentType) {
1968
+ if (componentType === void 0) {
1969
+ const componentId = entityId;
1970
+ return this.componentEntityComponents.get(componentId)?.has(componentId) ?? false;
1971
+ }
1972
+ if (this.isComponentEntityId(entityId)) {
1973
+ if (isWildcardRelationId(componentType)) {
1974
+ const componentId = getComponentIdFromRelationId(componentType);
1975
+ if (componentId === void 0) return false;
1976
+ const data = this.componentEntityComponents.get(entityId);
1977
+ if (!data) return false;
1978
+ for (const key of data.keys()) if (getComponentIdFromRelationId(key) === componentId) return true;
1979
+ return false;
1980
+ }
1981
+ return this.componentEntityComponents.get(entityId)?.has(componentType) ?? false;
1982
+ }
1915
1983
  const archetype = this.entityToArchetype.get(entityId);
1916
1984
  if (!archetype) return false;
1917
1985
  if (archetype.componentTypes.includes(componentType)) return true;
@@ -1919,6 +1987,23 @@ var World = class {
1919
1987
  return false;
1920
1988
  }
1921
1989
  get(entityId, componentType = entityId) {
1990
+ if (this.isComponentEntityId(entityId)) {
1991
+ if (isWildcardRelationId(componentType)) {
1992
+ const componentId = getComponentIdFromRelationId(componentType);
1993
+ const data$1 = this.componentEntityComponents.get(entityId);
1994
+ const relations = [];
1995
+ if (componentId !== void 0 && data$1) {
1996
+ for (const [key, value] of data$1.entries()) if (getComponentIdFromRelationId(key) === componentId) {
1997
+ const detailed = getDetailedIdType(key);
1998
+ if (detailed.type === "entity-relation" || detailed.type === "component-relation") relations.push([detailed.targetId, value]);
1999
+ }
2000
+ }
2001
+ return relations;
2002
+ }
2003
+ const data = this.componentEntityComponents.get(entityId);
2004
+ if (!data || !data.has(componentType)) throw new Error(`Entity ${entityId} does not have component ${componentType}. Use has() to check component existence before calling get().`);
2005
+ return data.get(componentType);
2006
+ }
1922
2007
  const archetype = this.entityToArchetype.get(entityId);
1923
2008
  if (!archetype) throw new Error(`Entity ${entityId} does not exist`);
1924
2009
  if (componentType >= 0 || componentType % 2 ** 42 !== 0) {
@@ -1929,6 +2014,24 @@ var World = class {
1929
2014
  return archetype.get(entityId, componentType);
1930
2015
  }
1931
2016
  getOptional(entityId, componentType = entityId) {
2017
+ if (this.isComponentEntityId(entityId)) {
2018
+ if (isWildcardRelationId(componentType)) {
2019
+ const componentId = getComponentIdFromRelationId(componentType);
2020
+ if (componentId === void 0) return void 0;
2021
+ const data$1 = this.componentEntityComponents.get(entityId);
2022
+ if (!data$1) return void 0;
2023
+ const relations = [];
2024
+ for (const [key, value] of data$1.entries()) if (getComponentIdFromRelationId(key) === componentId) {
2025
+ const detailed = getDetailedIdType(key);
2026
+ if (detailed.type === "entity-relation" || detailed.type === "component-relation") relations.push([detailed.targetId, value]);
2027
+ }
2028
+ if (relations.length === 0) return void 0;
2029
+ return { value: relations };
2030
+ }
2031
+ const data = this.componentEntityComponents.get(entityId);
2032
+ if (!data || !data.has(componentType)) return void 0;
2033
+ return { value: data.get(componentType) };
2034
+ }
1932
2035
  const archetype = this.entityToArchetype.get(entityId);
1933
2036
  if (!archetype) throw new Error(`Entity ${entityId} does not exist`);
1934
2037
  if (isWildcardRelationId(componentType)) {
@@ -2193,6 +2296,10 @@ var World = class {
2193
2296
  }
2194
2297
  executeEntityCommands(entityId, commands) {
2195
2298
  const changeset = new ComponentChangeset();
2299
+ if (this.isComponentEntityId(entityId)) {
2300
+ this.executeComponentEntityCommands(entityId, commands);
2301
+ return changeset;
2302
+ }
2196
2303
  if (commands.some((cmd) => cmd.type === "destroy")) {
2197
2304
  this.destroyEntityImmediate(entityId);
2198
2305
  return changeset;
@@ -2210,6 +2317,24 @@ var World = class {
2210
2317
  triggerLifecycleHooks(this.createHooksContext(), entityId, changeset.adds, removedComponents, currentArchetype, newArchetype);
2211
2318
  return changeset;
2212
2319
  }
2320
+ executeComponentEntityCommands(entityId, commands) {
2321
+ if (commands.some((cmd) => cmd.type === "destroy")) {
2322
+ this.clearComponentEntityComponents(entityId);
2323
+ return;
2324
+ }
2325
+ for (const command of commands) if (command.type === "set" && command.componentType) this.getComponentEntityComponents(entityId, true).set(command.componentType, command.component);
2326
+ else if (command.type === "delete" && command.componentType) {
2327
+ const data = this.componentEntityComponents.get(entityId);
2328
+ if (!data) continue;
2329
+ if (isWildcardRelationId(command.componentType)) {
2330
+ const componentId = getComponentIdFromRelationId(command.componentType);
2331
+ if (componentId !== void 0) {
2332
+ for (const key of Array.from(data.keys())) if (getComponentIdFromRelationId(key) === componentId) data.delete(key);
2333
+ }
2334
+ } else data.delete(command.componentType);
2335
+ if (data.size === 0) this.clearComponentEntityComponents(entityId);
2336
+ }
2337
+ }
2213
2338
  createHooksContext() {
2214
2339
  return {
2215
2340
  hooks: this.legacyHooks,
@@ -2331,10 +2456,19 @@ var World = class {
2331
2456
  }))
2332
2457
  });
2333
2458
  }
2459
+ const componentEntities = [];
2460
+ for (const [entityId, components] of this.componentEntityComponents.entries()) componentEntities.push({
2461
+ id: encodeEntityId(entityId),
2462
+ components: Array.from(components.entries()).map(([rawType, value]) => ({
2463
+ type: encodeEntityId(rawType),
2464
+ value: value === MISSING_COMPONENT ? void 0 : value
2465
+ }))
2466
+ });
2334
2467
  return {
2335
2468
  version: 1,
2336
2469
  entityManager: this.entityIdManager.serializeState(),
2337
- entities
2470
+ entities,
2471
+ componentEntities
2338
2472
  };
2339
2473
  }
2340
2474
  };