@codehz/ecs 0.9.0 → 0.10.1

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/README.md CHANGED
@@ -222,7 +222,7 @@ bun run examples/inventory-system-relations.ts
222
222
  | `spawnMany(count, configure)` | 批量创建多个实体 |
223
223
  | `exists(entity)` | 检查实体是否存在 |
224
224
  | `set(entity, componentId, data?)` | 添加/更新组件(缓冲,`sync()` 后生效)。对 `void` 组件可不传 data |
225
- | `set(componentId, data)` | 单例组件简写:`world.set(GlobalConfig, { ... })` |
225
+ | `singleton(componentId)` | 获取单例组件句柄,推荐用 `world.singleton(Config).set(value)` |
226
226
  | `get(entity, componentId?)` | 获取组件数据。**若组件不存在会抛出异常**,请先用 `has()` 检查或使用 `getOptional()` |
227
227
  | `getOptional(entity, componentId?)` | 安全获取组件,返回 `{ value: T } \| undefined` |
228
228
  | `has(entity, componentId?)` | 检查组件是否存在 |
@@ -236,6 +236,18 @@ bun run examples/inventory-system-relations.ts
236
236
  | `serialize()` | 序列化世界状态为快照对象 |
237
237
  | `sync()` | 执行所有延迟命令 |
238
238
 
239
+ 单例组件推荐写法:
240
+
241
+ ```ts
242
+ const config = world.singleton(GlobalConfig);
243
+ config.set({ debug: true });
244
+ world.sync();
245
+
246
+ if (config.has()) {
247
+ console.log(config.get());
248
+ }
249
+ ```
250
+
239
251
  ### Query
240
252
 
241
253
  查询通过 `world.createQuery()` 创建,应**跨帧复用**以获得最佳性能。
@@ -1009,39 +1009,66 @@ declare class Query {
1009
1009
  get disposed(): boolean;
1010
1010
  }
1011
1011
  //#endregion
1012
+ //#region src/world/singleton.d.ts
1013
+ interface SingletonHandleOps<T> {
1014
+ has(): boolean;
1015
+ get(): T;
1016
+ getOptional(): {
1017
+ value: T;
1018
+ } | undefined;
1019
+ remove(): void;
1020
+ set(value: T | undefined): void;
1021
+ }
1022
+ /**
1023
+ * Explicit handle for a singleton component (component-as-entity).
1024
+ *
1025
+ * This is the preferred API for singleton components.
1026
+ * `world.set(componentId, value)` remains available only as a deprecated
1027
+ * compatibility shorthand.
1028
+ *
1029
+ * @example
1030
+ * const config = world.singleton(Config);
1031
+ * config.set({ debug: true });
1032
+ * world.sync();
1033
+ * console.log(config.get());
1034
+ */
1035
+ declare class SingletonHandle<T = void> {
1036
+ readonly componentId: ComponentId<T>;
1037
+ private readonly ops;
1038
+ constructor(componentId: ComponentId<T>, ops: SingletonHandleOps<T>);
1039
+ has(): boolean;
1040
+ get(): T;
1041
+ getOptional(): {
1042
+ value: T;
1043
+ } | undefined;
1044
+ remove(): void;
1045
+ set(...args: T extends void ? [] : [value: NoInfer<T>]): void;
1046
+ }
1047
+ //#endregion
1012
1048
  //#region src/world/world.d.ts
1013
1049
  /**
1014
1050
  * World class for ECS architecture
1015
1051
  * Manages entities and components
1016
1052
  */
1017
1053
  declare class World {
1054
+ private static readonly DEPRECATED_SINGLETON_SET_SHORTHAND_WARNING;
1018
1055
  private entityIdManager;
1019
- private archetypes;
1020
- private archetypeBySignature;
1021
- private entityToArchetype;
1022
- private archetypesByComponent;
1023
1056
  private entityReferences;
1024
- /** Reverse index: entity ID → set of archetypes whose componentTypes include that entity ID */
1025
- private entityToReferencingArchetypes;
1026
1057
  /** Sparse relation storage (for components created with `sparse: true`), shared with all Archetype instances */
1027
1058
  private readonly sparseStore;
1028
1059
  /** Component entity (singleton) storage */
1029
1060
  private readonly componentEntities;
1061
+ private archetypeManager;
1062
+ private get archetypes();
1063
+ private get entityToArchetype();
1064
+ private get archetypesByComponent();
1065
+ private get entityToReferencingArchetypes();
1030
1066
  private readonly queryRegistry;
1031
1067
  private hooks;
1032
- private readonly _debugCollectors;
1033
- private _debugMigrations;
1034
- private _debugArchetypesCreated;
1035
- private _debugArchetypesRemoved;
1068
+ private readonly debugStats;
1036
1069
  private commandBuffer;
1037
- private readonly _changeset;
1038
- private readonly _removeChangeset;
1039
- /** Cached command processor context to avoid per-entity object allocation */
1040
- private readonly _commandCtx;
1041
- /** Cached hooks context to avoid per-entity object allocation */
1042
- private readonly _hooksCtx;
1070
+ private commandExecutor;
1043
1071
  constructor(snapshot?: SerializedWorld);
1044
- private createArchetypeSignature;
1045
1072
  /**
1046
1073
  * Creates a new entity.
1047
1074
  * The entity is created with an empty component set and can be configured using `set()`.
@@ -1086,11 +1113,6 @@ declare class World {
1086
1113
  * if (world.has(entity, Position)) { ... }
1087
1114
  */
1088
1115
  exists(entityId: EntityId): boolean;
1089
- private assertEntityExists;
1090
- private assertComponentTypeValid;
1091
- private assertSetComponentTypeValid;
1092
- private resolveSetOperation;
1093
- private resolveRemoveOperation;
1094
1116
  /**
1095
1117
  * Adds or updates a component on an entity (or marks void component as present).
1096
1118
  * The change is buffered and takes effect after calling `world.sync()`.
@@ -1099,24 +1121,27 @@ declare class World {
1099
1121
  * @overload set(entityId: EntityId, componentType: EntityId<void>): void
1100
1122
  * Marks a void component as present on the entity
1101
1123
  *
1124
+ * @overload set<T>(componentId: ComponentId<T>, component: Exclude<NoInfer<T>, number>): void
1125
+ * @deprecated Use `world.singleton(componentId).set(value)` or `world.set(componentId, componentId, value)` instead.
1126
+ * Compatibility shorthand for singleton component data when the second argument is not a number
1127
+ *
1102
1128
  * @overload set<T>(entityId: EntityId, componentType: EntityId<T>, component: NoInfer<T>): void
1103
1129
  * Adds or updates a component with data on the entity
1104
1130
  *
1105
- * @overload set<T>(componentId: ComponentId<T>, component: NoInfer<T>): void
1106
- * Adds or updates a singleton component (shorthand for set(componentId, componentId, component))
1107
- *
1108
1131
  * @throws {Error} If the entity does not exist
1109
1132
  * @throws {Error} If the component type is invalid or is a wildcard relation
1110
1133
  *
1111
1134
  * @example
1112
1135
  * world.set(entity, Position, { x: 10, y: 20 });
1113
1136
  * world.set(entity, Marker); // void component
1114
- * world.set(GlobalConfig, { debug: true }); // singleton component
1137
+ * world.singleton(GlobalConfig).set({ debug: true }); // singleton component
1138
+ * world.set(GlobalConfig, { debug: true }); // deprecated singleton compatibility shorthand
1115
1139
  * world.sync(); // Apply changes
1116
1140
  */
1117
1141
  set(entityId: EntityId, componentType: EntityId<void>): void;
1142
+ /** @deprecated Use `world.singleton(componentId).set(value)` or `world.set(componentId, componentId, value)` instead. */
1143
+ set<T>(componentId: ComponentId<T>, component: Exclude<NoInfer<T>, number>): void;
1118
1144
  set<T>(entityId: EntityId, componentType: EntityId<T>, component: NoInfer<T>): void;
1119
- set<T>(componentId: ComponentId<T>, component: NoInfer<T>): void;
1120
1145
  /**
1121
1146
  * Removes a component from an entity.
1122
1147
  * The change is buffered and takes effect after calling `world.sync()`.
@@ -1154,6 +1179,18 @@ declare class World {
1154
1179
  * world.sync(); // Apply changes
1155
1180
  */
1156
1181
  delete(entityId: EntityId): void;
1182
+ /**
1183
+ * Returns an explicit handle for a singleton component (component-as-entity).
1184
+ *
1185
+ * This is the preferred API for singleton components.
1186
+ *
1187
+ * @example
1188
+ * const config = world.singleton(GlobalConfig);
1189
+ * config.set({ debug: true });
1190
+ * world.sync();
1191
+ * console.log(config.get());
1192
+ */
1193
+ singleton<T>(componentId: ComponentId<T>): SingletonHandle<T>;
1157
1194
  /**
1158
1195
  * Checks if a specific **component** is present on an entity.
1159
1196
  *
@@ -1401,8 +1438,6 @@ declare class World {
1401
1438
  * This is intended for development/debugging and leak detection.
1402
1439
  */
1403
1440
  createDebugStatsCollector(callback: (stats: SyncDebugStats) => void): DebugStatsCollector;
1404
- private _resetDebugActivityCounters;
1405
- private _deliverDebugStats;
1406
1441
  /**
1407
1442
  * Synchronizes all buffered commands (set/remove/delete) to the world.
1408
1443
  * This method must be called after making changes via `set()`, `remove()`, or `delete()` for them to take effect.
@@ -1498,7 +1533,6 @@ declare class World {
1498
1533
  * @internal
1499
1534
  */
1500
1535
  getMatchingArchetypes(componentTypes: EntityId<any>[]): Archetype[];
1501
- private getArchetypesWithComponents;
1502
1536
  /**
1503
1537
  * Queries entities with specific components.
1504
1538
  * For simpler use cases, prefer using `createQuery()` with `forEach()` which is cached and more efficient.
@@ -1531,21 +1565,9 @@ declare class World {
1531
1565
  entity: EntityId;
1532
1566
  components: ComponentTuple<T>;
1533
1567
  }>;
1534
- private executeEntityCommands;
1535
- private applyEntityCommands;
1536
- private createHooksContext;
1537
- private removeComponentImmediate;
1538
- private updateEntityReferences;
1539
1568
  private ensureArchetype;
1540
- /** Add componentType to the reverse index if it contains an entity ID */
1541
- private addToReferencingIndex;
1542
- /** Remove componentType from the reverse index */
1543
- private removeFromReferencingIndex;
1544
- private createNewArchetype;
1545
- private updateArchetypeHookMatches;
1546
- private archetypeMatchesHook;
1547
1569
  private cleanupArchetypesReferencingEntity;
1548
- private removeArchetype;
1570
+ private archetypeMatchesHook;
1549
1571
  /**
1550
1572
  * Serializes the entire world state to a plain JavaScript object.
1551
1573
  * This creates a "memory snapshot" that can be stored or transmitted.
@@ -1567,6 +1589,8 @@ declare class World {
1567
1589
  * const savedData = JSON.parse(localStorage.getItem('save'));
1568
1590
  * const newWorld = new World(savedData);
1569
1591
  */
1592
+ private removeComponentImmediate;
1593
+ private createHooksContext;
1570
1594
  serialize(): SerializedWorld;
1571
1595
  }
1572
1596
  //#endregion
@@ -1643,5 +1667,5 @@ declare class EntityBuilder {
1643
1667
  build(): EntityId;
1644
1668
  }
1645
1669
  //#endregion
1646
- export { WildcardRelationId as A, isWildcardRelationId as C, EntityId as D, ComponentRelationId as E, isEntityId as M, isRelationId as N, EntityRelationId as O, decodeRelationId as S, ComponentId as T, getComponentIdByName as _, ComponentTuple as a, isSparseRelation as b, LifecycleCallback as c, SerializedComponent as d, SerializedEntity as f, component as g, ComponentOptions as h, Query as i, isComponentId as j, RelationId as k, LifecycleHook as l, SerializedWorld as m, EntityBuilder as n, ComponentType as o, SerializedEntityId as p, World as r, DebugStatsCollector as s, ComponentDef as t, SyncDebugStats as u, getComponentNameById as v, relation as w, isSparseWildcard as x, isSparseComponent as y };
1670
+ export { EntityRelationId as A, isSparseWildcard as C, ComponentId as D, relation as E, isRelationId as F, WildcardRelationId as M, isComponentId as N, ComponentRelationId as O, isEntityId as P, isSparseRelation as S, isWildcardRelationId as T, ComponentOptions as _, SingletonHandleOps as a, getComponentNameById as b, ComponentType as c, LifecycleHook as d, SyncDebugStats as f, SerializedWorld as g, SerializedEntityId as h, SingletonHandle as i, RelationId as j, EntityId as k, DebugStatsCollector as l, SerializedEntity as m, EntityBuilder as n, Query as o, SerializedComponent as p, World as r, ComponentTuple as s, ComponentDef as t, LifecycleCallback as u, component as v, decodeRelationId as w, isSparseComponent as x, getComponentIdByName as y };
1647
1671
  //# sourceMappingURL=builder.d.mts.map
package/dist/index.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { A as WildcardRelationId, C as isWildcardRelationId, D as EntityId, E as ComponentRelationId, M as isEntityId, N as isRelationId, O as EntityRelationId, S as decodeRelationId, T as ComponentId, _ as getComponentIdByName, a as ComponentTuple, b as isSparseRelation, c as LifecycleCallback, d as SerializedComponent, f as SerializedEntity, g as component, h as ComponentOptions, i as Query, j as isComponentId, k as RelationId, l as LifecycleHook, m as SerializedWorld, n as EntityBuilder, o as ComponentType, p as SerializedEntityId, r as World, s as DebugStatsCollector, t as ComponentDef, u as SyncDebugStats, v as getComponentNameById, w as relation, x as isSparseWildcard, y as isSparseComponent } from "./builder.mjs";
2
- export { type ComponentDef, type ComponentId, type ComponentOptions, type ComponentRelationId, type ComponentTuple, type ComponentType, type DebugStatsCollector, EntityBuilder, type EntityId, type EntityRelationId, type LifecycleCallback, type LifecycleHook, Query, type RelationId, type SerializedComponent, type SerializedEntity, type SerializedEntityId, type SerializedWorld, type SyncDebugStats, type WildcardRelationId, World, component, decodeRelationId, getComponentIdByName, getComponentNameById, isComponentId, isSparseComponent as isDontFragmentComponent, isSparseComponent, isSparseRelation as isDontFragmentRelation, isSparseRelation, isSparseWildcard as isDontFragmentWildcard, isSparseWildcard, isEntityId, isRelationId, isWildcardRelationId, relation };
1
+ import { A as EntityRelationId, C as isSparseWildcard, D as ComponentId, E as relation, F as isRelationId, M as WildcardRelationId, N as isComponentId, O as ComponentRelationId, P as isEntityId, S as isSparseRelation, T as isWildcardRelationId, _ as ComponentOptions, a as SingletonHandleOps, b as getComponentNameById, c as ComponentType, d as LifecycleHook, f as SyncDebugStats, g as SerializedWorld, h as SerializedEntityId, i as SingletonHandle, j as RelationId, k as EntityId, l as DebugStatsCollector, m as SerializedEntity, n as EntityBuilder, o as Query, p as SerializedComponent, r as World, s as ComponentTuple, t as ComponentDef, u as LifecycleCallback, v as component, w as decodeRelationId, x as isSparseComponent, y as getComponentIdByName } from "./builder.mjs";
2
+ export { type ComponentDef, type ComponentId, type ComponentOptions, type ComponentRelationId, type ComponentTuple, type ComponentType, type DebugStatsCollector, EntityBuilder, type EntityId, type EntityRelationId, type LifecycleCallback, type LifecycleHook, Query, type RelationId, type SerializedComponent, type SerializedEntity, type SerializedEntityId, type SerializedWorld, SingletonHandle, type SingletonHandleOps, type SyncDebugStats, type WildcardRelationId, World, component, decodeRelationId, getComponentIdByName, getComponentNameById, isComponentId, isSparseComponent as isDontFragmentComponent, isSparseComponent, isSparseRelation as isDontFragmentRelation, isSparseRelation, isSparseWildcard as isDontFragmentWildcard, isSparseWildcard, isEntityId, isRelationId, isWildcardRelationId, relation };
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { a as getComponentIdByName, c as isSparseRelation, d as isWildcardRelationId, f as relation, h as isRelationId, i as component, l as isSparseWildcard, m as isEntityId, n as Query, o as getComponentNameById, p as isComponentId, r as EntityBuilder, s as isSparseComponent, t as World, u as decodeRelationId } from "./world.mjs";
2
- export { EntityBuilder, Query, World, component, decodeRelationId, getComponentIdByName, getComponentNameById, isComponentId, isSparseComponent as isDontFragmentComponent, isSparseComponent, isSparseRelation as isDontFragmentRelation, isSparseRelation, isSparseWildcard as isDontFragmentWildcard, isSparseWildcard, isEntityId, isRelationId, isWildcardRelationId, relation };
1
+ import { a as component, c as isSparseComponent, d as decodeRelationId, f as isWildcardRelationId, g as isRelationId, h as isEntityId, i as EntityBuilder, l as isSparseRelation, m as isComponentId, n as Query, o as getComponentIdByName, p as relation, r as SingletonHandle, s as getComponentNameById, t as World, u as isSparseWildcard } from "./world.mjs";
2
+ export { EntityBuilder, Query, SingletonHandle, World, component, decodeRelationId, getComponentIdByName, getComponentNameById, isComponentId, isSparseComponent as isDontFragmentComponent, isSparseComponent, isSparseRelation as isDontFragmentRelation, isSparseRelation, isSparseWildcard as isDontFragmentWildcard, isSparseWildcard, isEntityId, isRelationId, isWildcardRelationId, relation };
@@ -1,4 +1,4 @@
1
- import { A as WildcardRelationId, D as EntityId, T as ComponentId, c as LifecycleCallback, g as component, i as Query, k as RelationId, l as LifecycleHook, n as EntityBuilder, r as World, t as ComponentDef, w as relation } from "./builder.mjs";
1
+ import { D as ComponentId, E as relation, M as WildcardRelationId, d as LifecycleHook, j as RelationId, k as EntityId, n as EntityBuilder, o as Query, r as World, t as ComponentDef, u as LifecycleCallback, v as component } from "./builder.mjs";
2
2
 
3
3
  //#region src/testing/index.d.ts
4
4
  /**
package/dist/testing.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { d as isWildcardRelationId, f as relation, i as component, r as EntityBuilder, t as World } from "./world.mjs";
1
+ import { a as component, f as isWildcardRelationId, i as EntityBuilder, p as relation, t as World } from "./world.mjs";
2
2
  //#region src/testing/index.ts
3
3
  /**
4
4
  * A test fixture that manages a World instance and provides convenient