perf(core): 优化 EntitySystem 迭代性能,添加 CommandBuffer 延迟命令 (#281)

* perf(core): 优化 EntitySystem 迭代性能,添加 CommandBuffer 延迟命令

ReactiveQuery 快照优化:
- 添加快照机制,避免每帧拷贝数组
- 只在实体列表变化时创建新快照
- 静态场景下多个系统共享同一快照

CommandBuffer 延迟命令系统:
- 支持延迟添加/移除组件、销毁实体、设置实体激活状态
- 每个系统拥有独立的 commands 属性
- 命令在帧末统一执行,避免迭代过程中修改实体列表

Scene 更新:
- 在 lateUpdate 后自动刷新所有系统的命令缓冲区

文档:
- 更新系统文档,添加 CommandBuffer 使用说明

* fix(ci): upgrade first-interaction action to v1.3.0

Fix Docker build failure in welcome workflow.

* fix(ci): upgrade pnpm/action-setup to v4 and fix unused import

- Upgrade pnpm/action-setup@v2 to v4 in all workflow files
- Remove unused CommandType import in CommandBuffer.test.ts

* fix(ci): remove duplicate pnpm version specification
This commit is contained in:
YHH
2025-12-05 17:24:33 +08:00
committed by GitHub
parent dd130eacb0
commit 13a149c3a2
16 changed files with 1035 additions and 33 deletions

View File

@@ -334,6 +334,110 @@ class DamageSystem extends EntitySystem {
框架会在每次 `process`/`lateProcess` 调用前创建实体列表的快照,确保迭代过程中的组件变化不会导致跳过实体或重复处理。
## 命令缓冲区 (CommandBuffer)
> **v2.2.22+**
CommandBuffer 提供了一种延迟执行实体操作的机制。当你需要在迭代过程中销毁实体或进行其他可能影响迭代的操作时,使用 CommandBuffer 可以将这些操作推迟到帧末统一执行。
### 基本用法
每个 EntitySystem 都内置了 `commands` 属性:
```typescript
@ECSSystem('Damage')
class DamageSystem extends EntitySystem {
constructor() {
super(Matcher.all(Health, DamageReceiver));
}
protected process(entities: readonly Entity[]): void {
for (const entity of entities) {
const health = entity.getComponent(Health);
const damage = entity.getComponent(DamageReceiver);
if (health && damage) {
health.current -= damage.amount;
// 使用命令缓冲区延迟移除组件
this.commands.removeComponent(entity, DamageReceiver);
if (health.current <= 0) {
// 延迟添加死亡标记
this.commands.addComponent(entity, new Dead());
// 延迟销毁实体
this.commands.destroyEntity(entity);
}
}
}
}
}
```
### 支持的命令
| 方法 | 说明 |
|------|------|
| `addComponent(entity, component)` | 延迟添加组件 |
| `removeComponent(entity, ComponentType)` | 延迟移除组件 |
| `destroyEntity(entity)` | 延迟销毁实体 |
| `setEntityActive(entity, active)` | 延迟设置实体激活状态 |
### 执行时机
命令缓冲区中的命令会在每帧的 `lateUpdate` 阶段之后自动执行。执行顺序与命令入队顺序一致。
```
场景更新流程:
1. onBegin()
2. process()
3. lateProcess()
4. onEnd()
5. flushCommandBuffers() <-- 命令在这里执行
```
### 使用场景
CommandBuffer 适用于以下场景:
1. **在迭代中销毁实体**:避免修改正在遍历的集合
2. **批量延迟操作**:将多个操作合并到帧末执行
3. **跨系统协调**:一个系统标记,另一个系统响应
```typescript
// 示例:敌人死亡系统
@ECSSystem('EnemyDeath')
class EnemyDeathSystem extends EntitySystem {
constructor() {
super(Matcher.all(Enemy, Health));
}
protected process(entities: readonly Entity[]): void {
for (const entity of entities) {
const health = entity.getComponent(Health);
if (health && health.current <= 0) {
// 播放死亡动画、掉落物品等
this.spawnLoot(entity);
// 延迟销毁,不影响当前迭代
this.commands.destroyEntity(entity);
}
}
}
private spawnLoot(entity: Entity): void {
// 掉落物品逻辑
}
}
```
### 注意事项
- 命令会跳过已销毁的实体(安全检查)
- 单个命令执行失败不会影响其他命令
- 命令按入队顺序执行
- 每次 `flush()` 后命令队列会清空
## 系统属性和方法
### 重要属性