@codehz/ecs 0.6.11 → 0.7.0

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
@@ -1,6 +1,8 @@
1
1
  # @codehz/ecs
2
2
 
3
- 一个高性能的Entity Component System (ECS) 库,使用 TypeScript 和 Bun 运行时构建。
3
+ > **English version:** [README.en.md](./README.en.md)
4
+
5
+ 一个高性能的 Entity Component System (ECS) 库,使用 TypeScript 和 Bun 运行时构建。
4
6
 
5
7
  ## 特性
6
8
 
@@ -9,7 +11,7 @@
9
11
  - 🏗️ 模块化:清晰的架构,支持自定义组件
10
12
  - 📦 轻量级:零依赖,易于集成
11
13
  - ⚡ 内存高效:连续内存布局,优化的迭代性能
12
- - 🎣 生命周期钩子:支持组件和通配符关系的事件监听
14
+ - 🎣 生命周期钩子:支持多组件和通配符关系的事件监听
13
15
 
14
16
  ## 安装
15
17
 
@@ -22,283 +24,307 @@ bun install
22
24
  ### 基本示例
23
25
 
24
26
  ```typescript
25
- import { World } from "@codehz/ecs";
26
- import { component } from "@codehz/ecs";
27
+ import { World, component } from "@codehz/ecs";
27
28
 
28
29
  // 定义组件类型
29
30
  type Position = { x: number; y: number };
30
31
  type Velocity = { x: number; y: number };
31
32
 
32
- // 定义组件ID
33
- const PositionId = component<Position>(1);
34
- const VelocityId = component<Velocity>(2);
33
+ // 定义组件 ID(自动分配)
34
+ const PositionId = component<Position>();
35
+ const VelocityId = component<Velocity>();
35
36
 
36
37
  // 创建世界
37
38
  const world = new World();
38
39
 
39
- // 创建实体
40
+ // 创建实体并设置组件(所有更改缓冲到 sync() 时应用)
40
41
  const entity = world.new();
41
42
  world.set(entity, PositionId, { x: 0, y: 0 });
42
43
  world.set(entity, VelocityId, { x: 1, y: 0.5 });
43
-
44
- // 应用更改
45
44
  world.sync();
46
45
 
47
- // 创建查询并更新
46
+ // 创建可重用的查询
48
47
  const query = world.createQuery([PositionId, VelocityId]);
49
- const deltaTime = 1.0 / 60.0; // 假设60FPS
48
+
49
+ // 更新循环
50
+ const deltaTime = 1.0 / 60.0;
50
51
  query.forEach([PositionId, VelocityId], (entity, position, velocity) => {
51
52
  position.x += velocity.x * deltaTime;
52
53
  position.y += velocity.y * deltaTime;
53
54
  });
54
55
  ```
55
56
 
56
- ### 组件生命周期钩子
57
+ ### 定义组件(ID 自动分配)
57
58
 
58
- ECS 支持监听组件的生命周期事件。可以监听单个组件或多个组件同时存在于实体时的事件。
59
+ `component()` 自动从全局分配器中分配一个唯一 ID,也可以指定名称或选项:
59
60
 
60
61
  ```typescript
61
- // 定义组件类型
62
- type Position = { x: number; y: number };
63
- type Velocity = { x: number; y: number };
62
+ import { component } from "@codehz/ecs";
64
63
 
65
- // 定义组件ID
66
- const PositionId = component<Position>();
67
- const VelocityId = component<Velocity>();
64
+ // 无参自动分配 ID
65
+ const Position = component<Position>();
66
+
67
+ // 指定名称(序列化时可读)
68
+ const Velocity = component<Velocity>("Velocity");
69
+
70
+ // 带选项的组件(关系专用)
71
+ const ChildOf = component({ exclusive: true, name: "ChildOf" });
72
+ ```
73
+
74
+ **`ComponentOptions` 选项:**
75
+
76
+ | 选项 | 类型 | 说明 |
77
+ | --------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
78
+ | `name` | `string` | 组件名称,用于序列化/调试 |
79
+ | `exclusive` | `boolean` | 仅关系组件:同一实体对同一基础组件最多只能有一个关系 |
80
+ | `cascadeDelete` | `boolean` | 仅实体关系:删除目标实体时,持有该关系的**整个实体**也会被删除。区别于默认行为(默认仅清理关系组件,实体保留)。支持传递级联。 |
81
+ | `dontFragment` | `boolean` | 仅关系组件:不同目标实体的关系存放在同一 Archetype,防止因目标不同而过度碎片化 |
82
+ | `merge` | `(prev, next) => T` | 在同一 sync 批次中对同一组件反复 `set()` 时的合并策略 |
83
+
84
+ ### 生命周期钩子
85
+
86
+ `world.hook()` 使用组件数组注册多组件生命周期钩子:
68
87
 
69
- // 注册生命周期钩子,返回卸载函数
88
+ ```typescript
89
+ // 返回卸载函数
70
90
  const unhook = world.hook([PositionId, VelocityId], {
71
91
  on_init: (entityId, position, velocity) => {
72
- // 当钩子注册时,为已同时拥有 Position 和 Velocity 组件的实体调用
73
- console.log(`实体 ${entityId} 同时拥有 Position 和 Velocity 组件`);
92
+ // 钩子注册时,为每个已同时满足条件的实体调用
74
93
  },
75
94
  on_set: (entityId, position, velocity) => {
76
- // 当实体同时拥有 Position 和 Velocity 组件时调用
77
- console.log(
78
- `实体 ${entityId} 现在同时拥有 Position (${position.x}, ${position.y}) 和 Velocity (${velocity.x}, ${velocity.y})`,
79
- );
95
+ // 当实体「进入」匹配集合时调用(添加/更新组件后)
80
96
  },
81
97
  on_remove: (entityId, position, velocity) => {
82
- // 当实体失去 Position 或 Velocity 组件之一时调用(如果之前同时拥有两者)
83
- console.log(`实体 ${entityId} 失去了 Position 或 Velocity 组件`);
98
+ // 当实体「退出」匹配集合时调用(移除组件或删除实体后)
84
99
  },
85
100
  });
86
-
87
- // 添加组件
88
- const entity = world.new();
89
- world.set(entity, PositionId, { x: 0, y: 0 });
90
- world.set(entity, VelocityId, { x: 1, y: 0.5 });
91
- world.sync(); // 钩子在这里被调用
92
-
93
- // 不再需要时,调用卸载函数移除钩子
101
+ // 卸载钩子
94
102
  unhook();
95
103
  ```
96
104
 
97
- `hook()` 也支持只监听单个组件:
105
+ 也支持回调简写形式:
98
106
 
99
107
  ```typescript
100
- // 监听单个组件
101
- const unhook = world.hook([PositionId], {
102
- on_set: (entityId, position) => {
103
- console.log(`组件 Position 被添加到实体 ${entityId}`);
104
- },
105
- on_remove: (entityId, position) => {
106
- console.log(`组件 Position 被从实体 ${entityId} 移除`);
107
- },
108
+ const unhook = world.hook([PositionId, VelocityId], (type, entityId, position, velocity) => {
109
+ if (type === "init") console.log("初始化");
110
+ if (type === "set") console.log("设置");
111
+ if (type === "remove") console.log("移除");
108
112
  });
109
113
  ```
110
114
 
111
- 还可以使用可选组件,这样即使某些组件不存在也会触发钩子:
115
+ 可选组件与过滤器:
112
116
 
113
117
  ```typescript
114
- // 注册包含可选组件的生命周期钩子
115
- const unhook = world.hook([PositionId, { optional: VelocityId }], {
118
+ // 可选组件:即使 Velocity 不存在也会触发钩子
119
+ world.hook([PositionId, { optional: VelocityId }], {
116
120
  on_set: (entityId, position, velocity) => {
117
- // 当实体拥有 Position 组件时调用,Velocity 组件可选
118
121
  if (velocity !== undefined) {
119
- console.log(`实体 ${entityId} 拥有 Position 和 Velocity 组件`);
122
+ console.log("拥有速度和位置");
120
123
  } else {
121
- console.log(`实体 ${entityId} 仅拥有 Position 组件`);
124
+ console.log("仅拥有位置");
122
125
  }
123
126
  },
124
127
  });
125
- ```
126
-
127
- 多组件 `hook()` 还支持第三个可选参数 `filter`(与 `createQuery()` 的过滤语义一致),可用于排除带有某些负面组件的实体:
128
128
 
129
- ```typescript
129
+ // 过滤器:排除带有指定负面组件的实体
130
130
  const DisabledId = component<void>();
131
-
132
- const unhook = world.hook(
131
+ world.hook(
133
132
  [PositionId, VelocityId],
134
133
  {
135
- on_set: (entityId, position, velocity) => {
136
- // 实体进入匹配集合时触发(包括移除 Disabled 后重新进入)
137
- console.log("active", entityId, position, velocity);
138
- },
139
- on_remove: (entityId, position, velocity) => {
140
- // 实体退出匹配集合时触发(包括新增 Disabled 后退出)
141
- console.log("inactive", entityId, position, velocity);
142
- },
143
- },
144
- {
145
- negativeComponentTypes: [DisabledId],
134
+ on_set: (entityId, position, velocity) => console.log("进入匹配集合"),
135
+ on_remove: (entityId, position, velocity) => console.log("退出匹配集合"),
146
136
  },
137
+ { negativeComponentTypes: [DisabledId] },
147
138
  );
148
139
  ```
149
140
 
150
- ### 通配符关系钩子
151
-
152
- ECS 支持通配符关系钩子,可以监听特定组件的所有关系变化:
141
+ ### 关系组件
153
142
 
154
143
  ```typescript
155
144
  import { World, component, relation } from "@codehz/ecs";
156
145
 
157
- // 定义组件类型
158
- type Position = { x: number; y: number };
146
+ const ChildOf = component<void>({ exclusive: true });
147
+ const world = new World();
148
+ const child = world.new();
149
+ const parent1 = world.new();
150
+ const parent2 = world.new();
159
151
 
160
- // 定义组件ID
161
- const PositionId = component<Position>(1);
152
+ // 添加关系
153
+ world.set(child, relation(ChildOf, parent1));
154
+ world.sync();
162
155
 
163
- // 创建世界
164
- const world = new World();
156
+ // 独占关系:添加新关系时自动移除旧关系
157
+ world.set(child, relation(ChildOf, parent2));
158
+ world.sync();
159
+ console.log(world.has(child, relation(ChildOf, parent1))); // false
160
+ console.log(world.has(child, relation(ChildOf, parent2))); // true
161
+ ```
165
162
 
166
- // 创建实体
167
- const entity = world.new();
163
+ ### 通配符关系钩子
164
+
165
+ ```typescript
166
+ import { World, component, relation } from "@codehz/ecs";
167
+ const PositionId = component<Position>();
168
168
 
169
- // 创建通配符关系ID,用于监听所有 Position 相关的关系
170
- const wildcardPositionRelation = relation(PositionId, "*");
169
+ const world = new World();
170
+ const wildcardPos = relation(PositionId, "*");
171
171
 
172
- // 注册通配符关系钩子,返回卸载函数
173
- const unhook = world.hook([wildcardPositionRelation], {
172
+ // 监听所有该类型关系的变动
173
+ world.hook([wildcardPos], {
174
174
  on_set: (entityId, relations) => {
175
- console.log(`实体 ${entityId} 添加了 Position 关系`);
176
175
  for (const [targetId, position] of relations) {
177
- console.log(` -> 目标实体 ${targetId}:`, position);
176
+ console.log(`实体 ${entityId} -> 目标 ${targetId}:`, position);
178
177
  }
179
178
  },
180
179
  on_remove: (entityId, relations) => {
181
- console.log(`实体 ${entityId} 移除了 Position 关系`);
180
+ console.log(`实体 ${entityId} 移除了所有 Position 关系`);
182
181
  },
183
182
  });
184
-
185
- // 创建实体间的关系
186
- const entity2 = world.new();
187
- const positionRelation = relation(PositionId, entity2);
188
- world.set(entity, positionRelation, { x: 10, y: 20 });
189
- world.sync(); // 通配符钩子会被触发
190
-
191
- // 不再需要时移除钩子
192
- unhook();
193
183
  ```
194
184
 
195
- ### Exclusive Relations
196
-
197
- ECS 支持 Exclusive Relations,确保实体对于指定的组件类型最多只能有一个关系。当添加新的关系时,会自动移除之前的所有同类型关系:
185
+ ### EntityBuilder 流式创建
198
186
 
199
187
  ```typescript
200
- import { World, component, relation } from "@codehz/ecs";
201
-
202
- // 定义组件ID,设置为独占关系
203
- const ChildOf = component({ exclusive: true }); // 空组件,用于关系
204
-
205
- // 创建世界
206
- const world = new World();
207
-
208
- // 创建实体
209
- const child = world.new();
210
- const parent1 = world.new();
211
- const parent2 = world.new();
188
+ const entity = world
189
+ .spawn()
190
+ .with(Position, { x: 0, y: 0 })
191
+ .with(Marker) // void 组件无需传值
192
+ .withRelation(ChildOf, parentEntity)
193
+ .build();
194
+ world.sync(); // 统一应用
195
+ ```
212
196
 
213
- // 添加第一个关系
214
- world.set(child, relation(ChildOf, parent1));
215
- world.sync();
216
- console.log(world.has(child, relation(ChildOf, parent1))); // true
197
+ ### 批量创建
217
198
 
218
- // 添加第二个关系 - 会自动移除第一个
219
- world.set(child, relation(ChildOf, parent2));
199
+ ```typescript
200
+ const entities = world.spawnMany(100, (builder, index) => builder.with(Position, { x: index * 10, y: 0 }));
220
201
  world.sync();
221
- console.log(world.has(child, relation(ChildOf, parent1))); // false
222
- console.log(world.has(child, relation(ChildOf, parent2))); // true
223
202
  ```
224
203
 
225
204
  ### 运行示例
226
205
 
227
- ```bash
228
- bun run demo
229
- ```
230
-
231
- 或者直接运行:
232
-
233
206
  ```bash
234
207
  bun run examples/simple/demo.ts
208
+ bun run examples/advanced-scheduling/demo.ts
235
209
  ```
236
210
 
237
211
  ## API 概述
238
212
 
239
213
  ### World
240
214
 
241
- - `new()`: 创建新实体
242
- - `spawn()`: 创建 EntityBuilder 用于流式实体创建
243
- - `spawnMany(count, configure)`: 批量创建多个实体
244
- - `exists(entity)`: 检查实体是否存在
245
- - `set(entity, componentId, data)`: 向实体添加组件
246
- - `get(entity, componentId)`: 获取实体的组件数据(注意:只能获取已设置的组件,使用前请先用 `has()` 检查组件是否存在)
247
- - `has(entity, componentId)`: 检查实体是否拥有指定组件
248
- - `remove(entity, componentId)`: 从实体移除组件
249
- - `delete(entity)`: 销毁实体及其所有组件
250
- - `query(componentIds)`: 快速查询具有指定组件的实体
251
- - `createQuery(componentIds)`: 创建可重用的查询对象
252
- - `hook(componentIds, hook, filter?)`: 注册生命周期钩子,返回卸载函数(数组形式支持可选 filter)
253
- - `serialize()`: 序列化世界状态为快照对象
254
- - `sync()`: 执行所有延迟命令
215
+ | 方法 | 说明 |
216
+ | ------------------------------------- | ----------------------------------------------------------------------------------- |
217
+ | `new<T>()` | 创建新实体,返回 `EntityId<T>` |
218
+ | `create<T>()` | `new()` 的语义别名 |
219
+ | `spawn()` | 返回 `EntityBuilder` 用于流式创建 |
220
+ | `spawnMany(count, configure)` | 批量创建多个实体 |
221
+ | `exists(entity)` | 检查实体是否存在 |
222
+ | `set(entity, componentId, data?)` | 添加/更新组件(缓冲,`sync()` 后生效)。对 `void` 组件可不传 data |
223
+ | `set(componentId, data)` | 单例组件简写:`world.set(GlobalConfig, { ... })` |
224
+ | `get(entity, componentId?)` | 获取组件数据。**若组件不存在会抛出异常**,请先用 `has()` 检查或使用 `getOptional()` |
225
+ | `getOptional(entity, componentId?)` | 安全获取组件,返回 `{ value: T } \| undefined` |
226
+ | `has(entity, componentId?)` | 检查组件是否存在 |
227
+ | `remove(entity, componentId?)` | 移除组件(缓冲),也有单例简写 |
228
+ | `delete(entity)` | 销毁实体及其所有组件(缓冲) |
229
+ | `query(componentIds)` | 快速查询(不缓存) |
230
+ | `query(componentIds, true)` | 快速查询并返回实体及组件数据 |
231
+ | `createQuery(componentIds, filter?)` | 创建可重用的缓存查询 |
232
+ | `releaseQuery(query)` | 释放查询(可选清理) |
233
+ | `hook(componentTypes, hook, filter?)` | 注册生命周期钩子,返回卸载函数 |
234
+ | `serialize()` | 序列化世界状态为快照对象 |
235
+ | `sync()` | 执行所有延迟命令 |
236
+
237
+ ### Query
238
+
239
+ 查询通过 `world.createQuery()` 创建,应**跨帧复用**以获得最佳性能。
240
+
241
+ | 方法 | 说明 |
242
+ | ----------------------------------- | ---------------------------------------- |
243
+ | `forEach(componentTypes, callback)` | 遍历匹配实体 |
244
+ | `getEntities()` | 获取所有匹配实体的 ID 列表 |
245
+ | `getEntitiesWithComponents(types)` | 获取实体及组件数据的对象数组 |
246
+ | `iterate(types)` | 返回生成器,用于 `for...of` 遍历 |
247
+ | `getComponentData(type)` | 获取所有匹配实体的单组件数据数组 |
248
+ | `dispose()` | 释放查询(引用计数减一,归零时完全释放) |
249
+ | `get disposed()` | 检查查询是否已释放 |
250
+
251
+ ### QueryFilter
252
+
253
+ ```typescript
254
+ interface QueryFilter {
255
+ negativeComponentTypes?: EntityId<any>[]; // 排除的组件
256
+ }
257
+ ```
258
+
259
+ ### EntityBuilder
260
+
261
+ | 方法 | 说明 |
262
+ | -------------------------------------------- | -------------------------------------------- |
263
+ | `with(componentId, ...args)` | 添加普通组件。`void` 类型不传值 |
264
+ | `withRelation(componentId, target, ...args)` | 添加关系组件。`void` 类型不传值 |
265
+ | `build()` | 创建实体并返回 `EntityId`(仍需要 `sync()`) |
266
+
267
+ ### component()
268
+
269
+ ```typescript
270
+ // 自动分配 ID
271
+ component<T>();
272
+ // 指定名称
273
+ component<T>("Name");
274
+ // 带选项
275
+ component<T>({ name?: string, exclusive?: boolean, cascadeDelete?: boolean, dontFragment?: boolean, merge?: (prev, next) => T });
276
+ ```
277
+
278
+ ### relation()
255
279
 
256
- ### 序列化(快照)
280
+ ```typescript
281
+ // 创建关系 ID
282
+ relation(componentId, targetEntity);
283
+ // 通配符(查询所有目标)
284
+ relation(componentId, "*");
285
+ // 单例目标(关联到另一个组件)
286
+ relation(componentId, otherComponentId);
287
+ ```
257
288
 
258
- 库提供了对世界状态的「内存快照」序列化接口,用于保存/恢复实体与组件的数据。注意关键点:
289
+ ### 组件 / 实体 ID 规则
259
290
 
260
- - `world.serialize()` 返回一个内存中的快照对象(snapshot),快照会按引用保存组件的实际值;它不会对数据做 JSON.stringify 操作,也不会尝试把组件值转换为可序列化格式。
261
- - `new World(snapshot)` 通过构造函数接受由 `world.serialize()` 生成的快照对象并重建世界状态。它期望一个内存对象(非 JSON 字符串)。
291
+ - 组件 ID:`1` ~ `1023`
292
+ - 实体 ID:`1024+`
293
+ - 关系 ID:负数编码 `-(componentId * 2^42 + targetId)`
262
294
 
263
- 为什么采用这种设计?很多情况下组件值可能包含函数、类实例、循环引用或其他无法用 JSON 表示的值。库不对组件值强行进行序列化/字符串化,以避免数据丢失或不可信的自动转换。
295
+ ## 序列化(快照)
264
296
 
265
- 示例:内存回环(component 值可为任意对象)
297
+ 库提供对世界状态的「内存快照」序列化接口,用于保存/恢复实体与组件数据。
266
298
 
267
- ```ts
268
- // 获取快照(内存对象)
299
+ ```typescript
300
+ // 创建快照(内存对象)
269
301
  const snapshot = world.serialize();
270
302
 
271
303
  // 在同一进程内直接恢复
272
304
  const restored = new World(snapshot);
273
305
  ```
274
306
 
275
- 持久化到磁盘或跨进程传输
307
+ **设计要点:**
276
308
 
277
- 如果你需要把世界保存到文件或通过网络传输,需要自己实现组件值的编码/解码策略:
309
+ - `world.serialize()` 返回内存快照对象,**不会**对组件值执行 `JSON.stringify`,也不会尝试将组件值转换为可序列化格式。
310
+ - `new World(snapshot)` 是反序列化的唯一入口(没有 `World.deserialize()` 静态方法)。
311
+ - 快照包含实体、组件以及 `EntityIdManager` 分配器状态(保留下一次分配的 ID);**不会**自动恢复查询缓存或生命周期钩子。
278
312
 
279
- 1. 使用 `World.serialize()` 得到 snapshot。
280
- 2. 对 snapshot 中的组件值逐项进行可自定义的编码(例如将类实例转成纯数据、把函数替换为标识符,或使用自定义二进制编码)。
281
- 3. 将编码后的对象字符串化并持久化。恢复时执行相反的解码步骤,得到与 `World.serialize()` 兼容的快照对象,然后调用 `World.deserialize(decodedSnapshot)`。
313
+ **持久化示例(组件值为 JSON 友好时):**
282
314
 
283
- 简单示例:当组件值都是 JSON-友好时
284
-
285
- ```ts
315
+ ```typescript
286
316
  const snapshot = world.serialize();
287
- // 如果组件值都可 JSON 化,可以直接 stringify
288
- const text = JSON.stringify(snapshot);
289
- // 写入文件或发送到网络
317
+ const json = JSON.stringify(snapshot);
318
+ // 写入文件或发送到网络 ...
290
319
 
291
- // 恢复:parse -> deserialize
292
- const parsed = JSON.parse(text);
320
+ const parsed = JSON.parse(json);
293
321
  const restored = new World(parsed);
294
322
  ```
295
323
 
296
- 示例:带自定义编码的持久化(伪代码)
324
+ **自定义编码示例:**
297
325
 
298
- ```ts
326
+ ```typescript
299
327
  const snapshot = world.serialize();
300
-
301
- // 将组件值编码为可持久化格式
302
328
  const encoded = {
303
329
  ...snapshot,
304
330
  entities: snapshot.entities.map((e) => ({
@@ -306,89 +332,29 @@ const encoded = {
306
332
  components: e.components.map((c) => ({ type: c.type, value: myEncode(c.value) })),
307
333
  })),
308
334
  };
335
+ // 持久化 encoded ...
309
336
 
310
- // 持久化 encoded(JSON.stringify / 二进制写入等)
311
-
312
- // 恢复时解码回原始组件值
313
- const decoded = /* parse file and decode */ encoded;
314
- const readySnapshot = {
337
+ // 恢复时反向解码
338
+ const decodedSnapshot = {
315
339
  ...decoded,
316
340
  entities: decoded.entities.map((e) => ({
317
341
  id: e.id,
318
342
  components: e.components.map((c) => ({ type: c.type, value: myDecode(c.value) })),
319
343
  })),
320
344
  };
321
-
322
- const restored = new World(readySnapshot);
345
+ const restored = new World(decodedSnapshot);
323
346
  ```
324
347
 
325
- 注意事项
326
-
327
- - **重要警告**:`get()` 方法只能获取实体已设置的组件。如果尝试获取不存在的组件,会抛出错误。由于 `undefined` 是组件的有效值,不能使用 `get()` 的返回值是否为 `undefined` 来判断组件是否存在。请在使用 `get()` 之前先用 `has()` 方法检查组件是否存在。
328
- - 快照只包含实体、组件、以及 `EntityIdManager` 的分配器状态(用于保留下一次分配的 ID);并不会自动恢复查询缓存或生命周期钩子。恢复后应由应用负责重新注册钩子。
329
- - 若需要跨版本兼容,建议在持久化格式中包含 `version` 字段,并在恢复时进行格式兼容性检查与迁移。
330
-
331
- ### Entity
332
-
333
- - `component<T>(id)`: 分配类型安全的组件ID(上限:1022个)
334
-
335
- ### Query
336
-
337
- - `forEach(componentIds, callback)`: 遍历匹配的实体,为每个实体调用回调函数
338
- - `getEntities()`: 获取所有匹配实体的ID列表
339
- - `getEntitiesWithComponents(componentIds)`: 获取实体及其组件数据的对象数组
340
- - `iterate(componentIds)`: 返回一个生成器,用于遍历匹配的实体及其组件数据
341
- - `getComponentData(componentType)`: 获取指定组件类型的所有匹配实体的数据数组
342
- - `dispose()`: 释放查询资源,停止接收世界更新通知
343
-
344
- ### EntityBuilder
345
-
346
- EntityBuilder 提供流式 API 用于便捷的实体创建:
347
-
348
- - `with(componentId, value?)`: 添加组件到构建器(对于 `void` 类型组件,value 参数可省略)
349
- - `withRelation(componentId, targetEntity, value?)`: 添加关系组件到构建器(对于 `void` 类型关系,value 参数可省略)
350
- - `build()`: 创建实体并应用所有组件(需要手动调用 `world.sync()`)
351
-
352
- ### World
353
-
354
- 从 v0.4.0 开始,本库移除了内置的 `System` 和 `SystemScheduler` 功能。推荐使用 `@codehz/pipeline` 作为替代方案来组织游戏循环逻辑。
355
-
356
- ### 为什么移除 System?
357
-
358
- - **简化库的维护**:System 调度器增加了代码复杂度,但其功能可以通过更通用的 pipeline 模式实现
359
- - **更灵活的执行控制**:Pipeline 模式允许更细粒度的控制,支持异步操作和条件执行
360
- - **更好的关注点分离**:ECS 库专注于实体和组件管理,系统调度由外部库处理
361
-
362
- ### 迁移示例
363
-
364
- **旧代码(使用 System)**:
365
-
366
- ```typescript
367
- import { World, component } from "@codehz/ecs";
368
- import type { System } from "@codehz/ecs";
369
-
370
- class MovementSystem implements System<[deltaTime: number]> {
371
- private query: Query;
348
+ **重要:** `get()` 在组件不存在时会抛出异常。由于 `undefined` 是组件的有效值,不能用 `get()` 的返回值是否为 `undefined` 来判断组件是否存在。请使用 `has()` 或 `getOptional()`。
372
349
 
373
- constructor(world: World<[deltaTime: number]>) {
374
- this.query = world.createQuery([PositionId, VelocityId]);
375
- }
350
+ ## System / Pipeline 集成
376
351
 
377
- update(deltaTime: number): void {
378
- this.query.forEach([PositionId, VelocityId], (entity, position, velocity) => {
379
- position.x += velocity.x * deltaTime;
380
- position.y += velocity.y * deltaTime;
381
- });
382
- }
383
- }
352
+ v0.4.0 开始,库移除了内置的 `System` 和 `SystemScheduler`。推荐使用 `@codehz/pipeline` 来组织游戏循环,**务必在最后一个 pass 调用 `world.sync()`**。
384
353
 
385
- const world = new World<[deltaTime: number]>();
386
- world.registerSystem(new MovementSystem(world));
387
- world.update(0.016); // 自动调用 sync()
354
+ ```bash
355
+ bun add @codehz/pipeline
388
356
  ```
389
357
 
390
- **新代码(使用 Pipeline)**:
391
-
392
358
  ```typescript
393
359
  import { pipeline } from "@codehz/pipeline";
394
360
  import { World, component } from "@codehz/ecs";
@@ -403,76 +369,66 @@ const gameLoop = pipeline<{ deltaTime: number }>()
403
369
  position.y += velocity.y * env.deltaTime;
404
370
  });
405
371
  })
406
- // 重要:world.sync() 必须作为最后一个 pass 调用,以还原之前 world.update() 的自动提交行为
407
372
  .addPass(() => {
408
- world.sync();
373
+ world.sync(); // 必须作为最后一个 pass
409
374
  })
410
375
  .build();
411
376
 
412
377
  gameLoop({ deltaTime: 0.016 });
413
378
  ```
414
379
 
415
- ### 关键变化
416
-
417
- 1. **移除泛型参数**:`World` 不再需要 `UpdateParams` 泛型参数
418
- 2. **移除的方法**:`registerSystem()` 和 `update()` 方法已移除
419
- 3. **手动调用 sync()**:之前 `world.update()` 会自动调用 `sync()`,现在需要在 pipeline 末尾显式调用
420
- 4. **执行顺序**:Pass 的执行顺序由添加顺序决定,无需手动声明依赖关系
421
-
422
- ### 安装 Pipeline
423
-
424
- ```bash
425
- bun add @codehz/pipeline
426
- ```
427
-
428
- ## 性能特点
429
-
430
- - **Archetype 系统**:实体按组件组合分组,实现连续内存访问
431
- - **缓存查询**:查询结果自动缓存,减少重复计算
432
- - **命令缓冲区**:延迟执行组件添加/移除,提高批处理效率
433
- - **类型安全**:编译时类型检查,无运行时开销
434
-
435
- ## 开发
436
-
437
- ### 运行测试
438
-
439
- ```bash
440
- bun test
441
- ```
442
-
443
- ### 类型检查
444
-
445
- ```bash
446
- bunx tsc --noEmit
447
- ```
448
-
449
380
  ## 项目结构
450
381
 
451
382
  ```
452
383
  src/
453
- ├── index.ts # 入口文件
454
- ├── entity.ts # 实体和组件管理
455
- ├── world.ts # 世界管理
456
- ├── archetype.ts # Archetype 系统(高效组件存储)
457
- ├── query.ts # 查询系统
458
- ├── query-filter.ts # 查询过滤器
459
- ├── command-buffer.ts # 命令缓冲区
460
- ├── types.ts # 类型定义
461
- ├── utils.ts # 工具函数
462
- ├── *.test.ts # 单元测试
463
- ├── query.example.ts # 查询示例
464
- └── *.perf.test.ts # 性能测试
384
+ ├── index.ts # 入口文件(统一导出)
385
+ ├── core/ # 核心实现
386
+ ├── world.ts # 世界管理
387
+ ├── archetype.ts # Archetype 系统(高效组件存储)
388
+ ├── builder.ts # EntityBuilder 流式创建
389
+ ├── component-registry.ts # 组件注册表
390
+ ├── component-entity-store.ts # 单例组件存储
391
+ ├── component-type-utils.ts # 组件类型工具
392
+ ├── dont-fragment-store.ts # DontFragment 存储
393
+ ├── entity.ts # 实体/组件/关系类型导出(聚合)
394
+ ├── entity-types.ts # 实体 ID 类型定义与常量
395
+ │ ├── entity-relation.ts # 关系 ID 编码/解码
396
+ │ ├── entity-manager.ts # ID 分配器
397
+ │ ├── query-registry.ts # 查询注册表
398
+ │ ├── serialization.ts # 序列化 ID 编解码
399
+ │ ├── world-serialization.ts # 世界序列化/反序列化
400
+ │ ├── world-commands.ts # 世界命令
401
+ │ ├── world-hooks.ts # 钩子执行逻辑
402
+ │ ├── world-references.ts # 实体引用追踪
403
+ │ └── types.ts # 类型定义
404
+ ├── query/ # 查询系统
405
+ │ ├── query.ts # Query 类
406
+ │ └── filter.ts # 查询过滤器
407
+ ├── commands/ # 命令缓冲区
408
+ ├── utils/ # 工具函数
409
+ ├── testing/ # 测试工具
410
+ └── __tests__/ # 单元测试 & 性能测试
465
411
 
466
412
  examples/
467
413
  ├── simple/
468
- │ ├── demo.ts # 基本示例
469
- │ └── README.md # 示例说明
414
+ │ ├── demo.ts # 基本示例
415
+ │ └── README.md # 示例说明
470
416
  └── advanced-scheduling/
471
- └── demo.ts # Pipeline 调度示例
417
+ └── demo.ts # Pipeline 调度示例
472
418
 
473
419
  scripts/
474
- ├── build.ts # 构建脚本
475
- └── release.ts # 发布脚本
420
+ ├── build.ts # 构建脚本
421
+ └── release.ts # 发布脚本
422
+ ```
423
+
424
+ ## 开发
425
+
426
+ ```bash
427
+ bun install
428
+ bun test # 运行测试
429
+ bunx tsc --noEmit # 类型检查
430
+ bun run examples/simple/demo.ts # 运行示例
431
+ bun run scripts/build.ts # 构建
476
432
  ```
477
433
 
478
434
  ## 许可证