@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 +13 -1
- package/dist/builder.d.mts +68 -44
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/testing.d.mts +1 -1
- package/dist/testing.mjs +1 -1
- package/dist/world.mjs +1150 -888
- package/dist/world.mjs.map +1 -1
- package/examples/spatial-grid.ts +1 -1
- package/package.json +1 -1
- package/src/__tests__/component/singleton.test.ts +121 -34
- package/src/__tests__/serialization/bounds.test.ts +2 -3
- package/src/__tests__/world/component-management.test.ts +6 -5
- package/src/index.ts +2 -0
- package/src/world/archetype-manager.ts +283 -0
- package/src/world/command-executor.ts +258 -0
- package/src/world/debug-stats.ts +147 -0
- package/src/world/operations.ts +100 -0
- package/src/world/singleton.ts +52 -0
- package/src/world/world.ts +194 -573
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
|
-
| `
|
|
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()` 创建,应**跨帧复用**以获得最佳性能。
|
package/dist/builder.d.mts
CHANGED
|
@@ -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
|
|
1033
|
-
private _debugMigrations;
|
|
1034
|
-
private _debugArchetypesCreated;
|
|
1035
|
-
private _debugArchetypesRemoved;
|
|
1068
|
+
private readonly debugStats;
|
|
1036
1069
|
private commandBuffer;
|
|
1037
|
-
private
|
|
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(
|
|
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
|
|
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 {
|
|
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
|
|
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
|
|
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 };
|
package/dist/testing.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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 {
|
|
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
|