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 {
The framework creates a snapshot of the entity list before each `process`/`lateProcess` call, ensuring that component changes during iteration won't cause entities to be skipped or processed multiple times.
## Command Buffer (CommandBuffer)
> **v2.2.22+**
CommandBuffer provides a mechanism for deferred execution of entity operations. When you need to destroy entities or perform other operations that might affect iteration during processing, CommandBuffer allows you to defer these operations to the end of the frame.
### Basic Usage
Every EntitySystem has a built-in `commands` property:
```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;
// Use command buffer to defer component removal
this.commands.removeComponent(entity, DamageReceiver);
if (health.current <= 0) {
// Defer adding death marker
this.commands.addComponent(entity, new Dead());
// Defer entity destruction
this.commands.destroyEntity(entity);
}
}
}
}
}
```
### Supported Commands
| Method | Description |
|--------|-------------|
| `addComponent(entity, component)` | Defer adding component |
| `removeComponent(entity, ComponentType)` | Defer removing component |
| `destroyEntity(entity)` | Defer destroying entity |
| `setEntityActive(entity, active)` | Defer setting entity active state |
### Execution Timing
Commands in the buffer are automatically executed after the `lateUpdate` phase of each frame. Execution order matches the order commands were queued.
```
Scene Update Flow:
1. onBegin()
2. process()
3. lateProcess()
4. onEnd()
5. flushCommandBuffers() <-- Commands execute here
```
### Use Cases
CommandBuffer is suitable for:
1. **Destroying entities during iteration**: Avoid modifying collection being traversed
2. **Batch deferred operations**: Merge multiple operations to execute at end of frame
3. **Cross-system coordination**: One system marks, another system responds
```typescript
// Example: Enemy death system
@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) {
// Play death animation, spawn loot, etc.
this.spawnLoot(entity);
// Defer destruction, doesn't affect current iteration
this.commands.destroyEntity(entity);
}
}
}
private spawnLoot(entity: Entity): void {
// Loot spawning logic
}
}
```
### Notes
- Commands skip already destroyed entities (safety check)
- Single command failure doesn't affect other commands
- Commands execute in queue order
- Command queue clears after each `flush()`
## System Properties and Methods
### Important Properties