feat(ecs): 核心系统改进 - 句柄、调度、变更检测与查询编译 (#304)

新增功能:
- EntityHandle: 轻量级实体句柄 (28位索引 + 20位代数)
- SystemScheduler: 声明式系统调度,支持 @Stage/@Before/@After/@InSet 装饰器
- EpochManager: 帧级变更检测
- CompiledQuery: 预编译类型安全查询

API 改进:
- EntitySystem 添加 getBefore()/getAfter()/getSets() getter 方法
- Entity 添加 markDirty() 辅助方法
- IScene 添加 epochManager 属性
- CommandBuffer.pendingCount 修正为返回实际操作数

文档更新:
- 更新系统调度和查询相关文档
This commit is contained in:
YHH
2025-12-15 09:17:00 +08:00
committed by GitHub
parent b5158b6ac6
commit cd6ef222d1
27 changed files with 5233 additions and 43 deletions

View File

@@ -672,6 +672,243 @@ scene.addSystem(new SystemB()); // addOrder = 1, executes second
> **Note**: `addOrder` is automatically set by the framework when calling `addSystem`, no manual management needed. This ensures systems with the same `updateOrder` execute in their addition order, avoiding random behavior from unstable sorting. > **Note**: `addOrder` is automatically set by the framework when calling `addSystem`, no manual management needed. This ensures systems with the same `updateOrder` execute in their addition order, avoiding random behavior from unstable sorting.
## Declarative System Scheduling
> **v2.4.0+**
In addition to manually controlling execution order with `updateOrder`, the framework provides a declarative system scheduling mechanism that lets you define execution order through dependencies.
### Scheduling Decorators
```typescript
import { EntitySystem, ECSSystem, Stage, Before, After, InSet } from '@esengine/ecs-framework';
// Use decorators to declare system scheduling
@ECSSystem('Movement')
@Stage('update') // Execute in update stage
@After('InputSystem') // Execute after InputSystem
@Before('RenderSystem') // Execute before RenderSystem
class MovementSystem extends EntitySystem {
constructor() {
super(Matcher.all(Position, Velocity));
}
protected process(entities: readonly Entity[]): void {
// Movement logic
}
}
// Use system sets for grouping
@ECSSystem('Physics')
@Stage('update')
@InSet('CoreSystems') // Belongs to CoreSystems set
class PhysicsSystem extends EntitySystem {
// ...
}
@ECSSystem('Collision')
@Stage('update')
@After('set:CoreSystems') // Execute after all systems in CoreSystems set
class CollisionSystem extends EntitySystem {
// ...
}
```
### System Execution Stages
The framework defines the following system execution stages, executed in order:
| Stage | Description | Typical Usage |
|-------|-------------|---------------|
| `startup` | Startup stage | One-time initialization |
| `preUpdate` | Pre-update stage | Input handling, state preparation |
| `update` | Main update stage (default) | Core game logic |
| `postUpdate` | Post-update stage | Physics, collision detection |
| `cleanup` | Cleanup stage | Resource cleanup, state reset |
### Fluent API Configuration
If you prefer not to use decorators, you can configure scheduling at runtime using the Fluent API:
```typescript
@ECSSystem('Movement')
class MovementSystem extends EntitySystem {
constructor() {
super(Matcher.all(Position, Velocity));
// Configure scheduling using Fluent API
this.stage('update')
.after('InputSystem')
.before('RenderSystem')
.inSet('CoreSystems');
}
}
```
### Cycle Dependency Detection
The framework automatically detects cyclic dependencies and throws clear errors:
```typescript
// This will cause a cycle dependency error
@ECSSystem('SystemA')
@Before('SystemB')
class SystemA extends EntitySystem { }
@ECSSystem('SystemB')
@Before('SystemA') // Error: A -> B -> A forms a cycle
class SystemB extends EntitySystem { }
// Error message: Cyclic dependency detected: SystemA -> SystemB -> SystemA
```
## Frame-Level Change Detection
> **v2.4.0+**
The framework provides epoch-based frame-level change detection, allowing systems to process only entities that have changed, significantly improving performance.
### Core Concepts
- **Epoch**: Global frame counter, incremented each frame
- **lastWriteEpoch**: The epoch when a component was last modified
- **Change Detection**: Determine if component changed after a specific point by comparing epochs
### Marking Components as Modified
After modifying component data, you need to mark the component as changed. There are two approaches:
**Approach 1: Via Entity Helper Method (Recommended)**
```typescript
// Mark component dirty via entity.markDirty() after modification
const pos = entity.getComponent(Position)!;
pos.x = 100;
pos.y = 200;
entity.markDirty(pos);
// Can mark multiple components at once
const vel = entity.getComponent(Velocity)!;
vel.vx = 10;
entity.markDirty(pos, vel);
```
**Approach 2: Encapsulate in Component**
```typescript
class VelocityComponent extends Component {
private _vx: number = 0;
private _vy: number = 0;
// Provide modification method that accepts epoch parameter
public setVelocity(vx: number, vy: number, epoch: number): void {
this._vx = vx;
this._vy = vy;
this.markDirty(epoch);
}
public get vx(): number { return this._vx; }
public get vy(): number { return this._vy; }
}
// Usage in system
const vel = entity.getComponent(VelocityComponent)!;
vel.setVelocity(10, 20, this.currentEpoch);
```
### Using Change Detection in Systems
EntitySystem provides several change detection helper methods:
```typescript
@ECSSystem('Physics')
class PhysicsSystem extends EntitySystem {
constructor() {
super(Matcher.all(Position, Velocity));
}
protected process(entities: readonly Entity[]): void {
// Method 1: Use forEachChanged to process only changed entities
// Automatically saves epoch checkpoint
this.forEachChanged(entities, [Velocity], (entity) => {
const pos = this.requireComponent(entity, Position);
const vel = this.requireComponent(entity, Velocity);
// Only update position when Velocity changes
pos.x += vel.vx * Time.deltaTime;
pos.y += vel.vy * Time.deltaTime;
});
}
}
@ECSSystem('Transform')
class TransformSystem extends EntitySystem {
constructor() {
super(Matcher.all(Transform, RigidBody));
}
protected process(entities: readonly Entity[]): void {
// Method 2: Use filterChanged to get list of changed entities
const changedEntities = this.filterChanged(entities, [RigidBody]);
for (const entity of changedEntities) {
// Process entities with changed physics state
this.updatePhysics(entity);
}
// Manually save epoch checkpoint
this.saveEpoch();
}
protected updatePhysics(entity: Entity): void {
// Physics update logic
}
}
```
### Change Detection API Reference
| Method | Description |
|--------|-------------|
| `forEachChanged(entities, [Types], callback)` | Iterate entities with changed specified components, auto-saves checkpoint |
| `filterChanged(entities, [Types])` | Return array of entities with changed specified components |
| `hasChanged(entity, [Types])` | Check if single entity's specified components have changed |
| `saveEpoch()` | Manually save current epoch as checkpoint |
| `lastProcessEpoch` | Get last saved epoch checkpoint |
| `currentEpoch` | Get current scene epoch |
### Use Cases
Change detection is particularly suitable for:
1. **Dirty flag optimization**: Only update rendering when data changes
2. **Physics sync**: Only sync entities with changed position/velocity
3. **Network sync**: Only send changed component data
4. **Cache invalidation**: Only recalculate when dependent data changes
```typescript
@ECSSystem('NetworkSync')
class NetworkSyncSystem extends EntitySystem {
constructor() {
super(Matcher.all(NetworkComponent, Transform));
}
protected process(entities: readonly Entity[]): void {
// Only sync changed entities, greatly reducing network traffic
this.forEachChanged(entities, [Transform], (entity) => {
const transform = this.requireComponent(entity, Transform);
const network = this.requireComponent(entity, NetworkComponent);
this.sendTransformUpdate(network.id, transform);
});
}
private sendTransformUpdate(id: string, transform: Transform): void {
// Send network update
}
}
```
## Complex System Examples ## Complex System Examples
### Collision Detection System ### Collision Detection System

View File

@@ -430,6 +430,137 @@ class GameManager {
} }
``` ```
## 编译查询 (CompiledQuery)
> **v2.4.0+**
CompiledQuery 是一个轻量级的查询工具,提供类型安全的组件访问和变更检测支持。适合临时查询、工具开发和简单的迭代场景。
### 基本用法
```typescript
// 创建编译查询
const query = scene.querySystem.compile(Position, Velocity);
// 类型安全的遍历 - 组件参数自动推断类型
query.forEach((entity, pos, vel) => {
pos.x += vel.vx * deltaTime;
pos.y += vel.vy * deltaTime;
});
// 获取实体数量
console.log(`匹配实体数: ${query.count}`);
// 获取第一个匹配的实体
const first = query.first();
if (first) {
const [entity, pos, vel] = first;
console.log(`第一个实体: ${entity.name}`);
}
```
### 变更检测
CompiledQuery 支持基于 epoch 的变更检测:
```typescript
class RenderSystem extends EntitySystem {
private _query: CompiledQuery<[typeof Transform, typeof Sprite]>;
private _lastEpoch = 0;
protected onInitialize(): void {
this._query = this.scene!.querySystem.compile(Transform, Sprite);
}
protected process(entities: readonly Entity[]): void {
// 只处理 Transform 或 Sprite 发生变化的实体
this._query.forEachChanged(this._lastEpoch, (entity, transform, sprite) => {
this.updateRenderData(entity, transform, sprite);
});
// 保存当前 epoch 作为下次检查的起点
this._lastEpoch = this.scene!.epochManager.current;
}
private updateRenderData(entity: Entity, transform: Transform, sprite: Sprite): void {
// 更新渲染数据
}
}
```
### 函数式 API
CompiledQuery 提供了丰富的函数式 API
```typescript
const query = scene.querySystem.compile(Position, Health);
// map - 转换实体数据
const positions = query.map((entity, pos, health) => ({
x: pos.x,
y: pos.y,
healthPercent: health.current / health.max
}));
// filter - 过滤实体
const lowHealthEntities = query.filter((entity, pos, health) => {
return health.current < health.max * 0.2;
});
// find - 查找第一个匹配的实体
const target = query.find((entity, pos, health) => {
return health.current > 0 && pos.x > 100;
});
// toArray - 转换为数组
const allData = query.toArray();
for (const [entity, pos, health] of allData) {
console.log(`${entity.name}: ${pos.x}, ${pos.y}`);
}
// any/empty - 检查是否有匹配
if (query.any()) {
console.log('有匹配的实体');
}
if (query.empty()) {
console.log('没有匹配的实体');
}
```
### CompiledQuery vs EntitySystem
| 特性 | CompiledQuery | EntitySystem |
|------|---------------|--------------|
| **用途** | 轻量级查询工具 | 完整的系统逻辑 |
| **生命周期** | 无 | 完整 (onInitialize, onDestroy 等) |
| **调度集成** | 无 | 支持 @Stage, @Before, @After |
| **变更检测** | forEachChanged | forEachChanged |
| **事件监听** | 无 | addEventListener |
| **命令缓冲** | 无 | this.commands |
| **类型安全组件** | forEach 参数自动推断 | 需要手动 getComponent |
| **适用场景** | 临时查询、工具、原型 | 核心游戏逻辑 |
**选择建议**
- 使用 **EntitySystem** 处理核心游戏逻辑移动、战斗、AI 等)
- 使用 **CompiledQuery** 进行一次性查询、工具开发或简单迭代
### CompiledQuery API 参考
| 方法 | 说明 |
|------|------|
| `forEach(callback)` | 遍历所有匹配实体,类型安全的组件参数 |
| `forEachChanged(sinceEpoch, callback)` | 只遍历变更的实体 |
| `first()` | 获取第一个匹配的实体和组件 |
| `toArray()` | 转换为 [entity, ...components] 数组 |
| `map(callback)` | 映射转换 |
| `filter(predicate)` | 过滤实体 |
| `find(predicate)` | 查找第一个满足条件的实体 |
| `any()` | 是否有任何匹配 |
| `empty()` | 是否没有匹配 |
| `count` | 匹配的实体数量 |
| `entities` | 匹配的实体列表(只读) |
## 最佳实践 ## 最佳实践
### 1. 优先使用 EntitySystem ### 1. 优先使用 EntitySystem

View File

@@ -672,6 +672,243 @@ scene.addSystem(new SystemB()); // addOrder = 1后执行
> **注意**`addOrder` 由框架在 `addSystem` 时自动设置,无需手动管理。这确保了相同 `updateOrder` 的系统按照添加顺序执行,避免了排序不稳定导致的随机行为。 > **注意**`addOrder` 由框架在 `addSystem` 时自动设置,无需手动管理。这确保了相同 `updateOrder` 的系统按照添加顺序执行,避免了排序不稳定导致的随机行为。
## 声明式系统调度
> **v2.4.0+**
除了使用 `updateOrder` 手动控制执行顺序外,框架还提供了声明式的系统调度机制,让你可以通过依赖关系来定义系统的执行顺序。
### 调度装饰器
```typescript
import { EntitySystem, ECSSystem, Stage, Before, After, InSet } from '@esengine/ecs-framework';
// 使用装饰器声明系统调度
@ECSSystem('Movement')
@Stage('update') // 在 update 阶段执行
@After('InputSystem') // 在 InputSystem 之后执行
@Before('RenderSystem') // 在 RenderSystem 之前执行
class MovementSystem extends EntitySystem {
constructor() {
super(Matcher.all(Position, Velocity));
}
protected process(entities: readonly Entity[]): void {
// 移动逻辑
}
}
// 使用系统集合进行分组
@ECSSystem('Physics')
@Stage('update')
@InSet('CoreSystems') // 属于 CoreSystems 集合
class PhysicsSystem extends EntitySystem {
// ...
}
@ECSSystem('Collision')
@Stage('update')
@After('set:CoreSystems') // 在 CoreSystems 集合的所有系统之后执行
class CollisionSystem extends EntitySystem {
// ...
}
```
### 系统执行阶段
框架定义了以下系统执行阶段,按顺序执行:
| 阶段 | 说明 | 典型用途 |
|------|------|----------|
| `startup` | 启动阶段 | 一次性初始化 |
| `preUpdate` | 更新前阶段 | 输入处理、状态准备 |
| `update` | 主更新阶段(默认) | 核心游戏逻辑 |
| `postUpdate` | 更新后阶段 | 物理、碰撞检测 |
| `cleanup` | 清理阶段 | 资源清理、状态重置 |
### Fluent API 配置
如果不想使用装饰器,也可以使用 Fluent API 在运行时配置调度:
```typescript
@ECSSystem('Movement')
class MovementSystem extends EntitySystem {
constructor() {
super(Matcher.all(Position, Velocity));
// 使用 Fluent API 配置调度
this.stage('update')
.after('InputSystem')
.before('RenderSystem')
.inSet('CoreSystems');
}
}
```
### 循环依赖检测
框架会自动检测循环依赖并抛出明确的错误:
```typescript
// 这会导致循环依赖错误
@ECSSystem('SystemA')
@Before('SystemB')
class SystemA extends EntitySystem { }
@ECSSystem('SystemB')
@Before('SystemA') // 错误A -> B -> A 形成循环
class SystemB extends EntitySystem { }
// 错误信息Cyclic dependency detected: SystemA -> SystemB -> SystemA
```
## 帧级变更检测
> **v2.4.0+**
框架提供了基于 epoch 的帧级变更检测机制,让系统可以只处理发生变化的实体,大幅提升性能。
### 核心概念
- **Epoch**:全局帧计数器,每帧递增
- **lastWriteEpoch**:组件最后被修改时的 epoch
- **变更检测**:通过比较 epoch 判断组件是否在指定时间点后发生变化
### 标记组件为已修改
修改组件数据后,需要标记组件为已变更。有两种方式:
**方式 1通过 Entity 辅助方法(推荐)**
```typescript
// 修改组件后通过 entity.markDirty() 标记
const pos = entity.getComponent(Position)!;
pos.x = 100;
pos.y = 200;
entity.markDirty(pos);
// 可以同时标记多个组件
const vel = entity.getComponent(Velocity)!;
vel.vx = 10;
entity.markDirty(pos, vel);
```
**方式 2在组件内部封装**
```typescript
class VelocityComponent extends Component {
private _vx: number = 0;
private _vy: number = 0;
// 提供修改方法,接收 epoch 参数
public setVelocity(vx: number, vy: number, epoch: number): void {
this._vx = vx;
this._vy = vy;
this.markDirty(epoch);
}
public get vx(): number { return this._vx; }
public get vy(): number { return this._vy; }
}
// 在系统中使用
const vel = entity.getComponent(VelocityComponent)!;
vel.setVelocity(10, 20, this.currentEpoch);
```
### 在系统中使用变更检测
EntitySystem 提供了多个变更检测辅助方法:
```typescript
@ECSSystem('Physics')
class PhysicsSystem extends EntitySystem {
constructor() {
super(Matcher.all(Position, Velocity));
}
protected process(entities: readonly Entity[]): void {
// 方式1使用 forEachChanged 只处理变更的实体
// 自动保存 epoch 检查点
this.forEachChanged(entities, [Velocity], (entity) => {
const pos = this.requireComponent(entity, Position);
const vel = this.requireComponent(entity, Velocity);
// 只有 Velocity 变化时才更新位置
pos.x += vel.vx * Time.deltaTime;
pos.y += vel.vy * Time.deltaTime;
});
}
}
@ECSSystem('Transform')
class TransformSystem extends EntitySystem {
constructor() {
super(Matcher.all(Transform, RigidBody));
}
protected process(entities: readonly Entity[]): void {
// 方式2使用 filterChanged 获取变更的实体列表
const changedEntities = this.filterChanged(entities, [RigidBody]);
for (const entity of changedEntities) {
// 处理物理状态变化的实体
this.updatePhysics(entity);
}
// 手动保存 epoch 检查点
this.saveEpoch();
}
protected updatePhysics(entity: Entity): void {
// 物理更新逻辑
}
}
```
### 变更检测 API 参考
| 方法 | 说明 |
|------|------|
| `forEachChanged(entities, [Types], callback)` | 遍历指定组件发生变更的实体,自动保存检查点 |
| `filterChanged(entities, [Types])` | 返回指定组件发生变更的实体数组 |
| `hasChanged(entity, [Types])` | 检查单个实体的指定组件是否发生变更 |
| `saveEpoch()` | 手动保存当前 epoch 作为检查点 |
| `lastProcessEpoch` | 获取上次保存的 epoch 检查点 |
| `currentEpoch` | 获取当前场景的 epoch |
### 使用场景
变更检测特别适合以下场景:
1. **脏标记优化**:只在数据变化时更新渲染
2. **物理同步**:只同步位置/速度发生变化的实体
3. **网络同步**:只发送变化的组件数据
4. **缓存失效**:只在依赖数据变化时重新计算
```typescript
@ECSSystem('NetworkSync')
class NetworkSyncSystem extends EntitySystem {
constructor() {
super(Matcher.all(NetworkComponent, Transform));
}
protected process(entities: readonly Entity[]): void {
// 只同步变化的实体,大幅减少网络流量
this.forEachChanged(entities, [Transform], (entity) => {
const transform = this.requireComponent(entity, Transform);
const network = this.requireComponent(entity, NetworkComponent);
this.sendTransformUpdate(network.id, transform);
});
}
private sendTransformUpdate(id: string, transform: Transform): void {
// 发送网络更新
}
}
```
## 复杂系统示例 ## 复杂系统示例
### 碰撞检测系统 ### 碰撞检测系统

View File

@@ -57,6 +57,27 @@ export abstract class Component implements IComponent {
@Int32 @Int32
public entityId: number | null = null; public entityId: number | null = null;
/**
* 最后写入的 epoch
*
* 用于帧级变更检测,记录组件最后一次被修改时的 epoch。
* 0 表示从未被标记为已修改。
*
* Last write epoch.
* Used for frame-level change detection, records the epoch when component was last modified.
* 0 means never marked as modified.
*/
private _lastWriteEpoch: number = 0;
/**
* 获取最后写入的 epoch
*
* Get last write epoch.
*/
public get lastWriteEpoch(): number {
return this._lastWriteEpoch;
}
/** /**
* 创建组件实例 * 创建组件实例
* *
@@ -66,6 +87,29 @@ export abstract class Component implements IComponent {
this.id = Component.idGenerator++; this.id = Component.idGenerator++;
} }
/**
* 标记组件为已修改
*
* 调用此方法会更新组件的 lastWriteEpoch 为当前帧的 epoch。
* 系统可以通过比较 lastWriteEpoch 和上次检查的 epoch 来判断组件是否发生变更。
*
* Mark component as modified.
* Calling this method updates the component's lastWriteEpoch to the current frame's epoch.
* Systems can compare lastWriteEpoch with their last checked epoch to detect changes.
*
* @param epoch 当前帧的 epoch | Current frame's epoch
*
* @example
* ```typescript
* // 在修改组件数据后调用
* velocity.x = 10;
* velocity.markDirty(scene.epochManager.current);
* ```
*/
public markDirty(epoch: number): void {
this._lastWriteEpoch = epoch;
}
/** /**
* 组件添加到实体时的回调 * 组件添加到实体时的回调
* *

View File

@@ -1,6 +1,6 @@
import { Entity } from '../Entity'; import { Entity } from '../Entity';
import { Component } from '../Component'; import { Component } from '../Component';
import { ComponentType } from './ComponentStorage'; import { ComponentType, ComponentRegistry } from './ComponentStorage';
import { IScene } from '../IScene'; import { IScene } from '../IScene';
import { createLogger } from '../../Utils/Logger'; import { createLogger } from '../../Utils/Logger';
@@ -38,6 +38,24 @@ export interface DeferredCommand {
value?: boolean; value?: boolean;
} }
/**
* 每个实体的待处理操作
* Pending operations per entity
*
* 使用 last-write-wins 语义进行去重。
* Uses last-write-wins semantics for deduplication.
*/
interface PendingPerEntity {
/** 是否销毁实体(如果为 true忽略其他操作| Destroy entity (if true, ignores other operations) */
bDestroy?: boolean;
/** 最终的激活状态 | Final active state */
active?: boolean;
/** 要添加的组件typeId -> Component| Components to add (typeId -> Component) */
adds?: Map<number, Component>;
/** 要移除的组件类型 ID 集合 | Component type IDs to remove */
removes?: Set<number>;
}
/** /**
* 命令缓冲区 - 用于延迟执行实体操作 * 命令缓冲区 - 用于延迟执行实体操作
* Command Buffer - for deferred entity operations * Command Buffer - for deferred entity operations
@@ -45,8 +63,17 @@ export interface DeferredCommand {
* 在系统的 process() 方法中使用 CommandBuffer 可以避免迭代过程中修改实体列表, * 在系统的 process() 方法中使用 CommandBuffer 可以避免迭代过程中修改实体列表,
* 从而提高性能(无需每帧拷贝数组)并保证迭代安全。 * 从而提高性能(无需每帧拷贝数组)并保证迭代安全。
* *
* Using CommandBuffer in system's process() method avoids modifying entity list during iteration, * 特点:
* improving performance (no array copy per frame) and ensuring iteration safety. * - **去重**: 同一实体的多次同类操作会被合并last-write-wins
* - **有序执行**: removes -> adds -> active -> destroy
* - **冲突处理**: addComponent 会取消同类型的 removeComponent反之亦然
* - **销毁优先**: destroyEntity 会取消该实体的所有其他操作
*
* Features:
* - **Deduplication**: Multiple operations on same entity are merged (last-write-wins)
* - **Ordered execution**: removes -> adds -> active -> destroy
* - **Conflict handling**: addComponent cancels same-type removeComponent and vice versa
* - **Destroy priority**: destroyEntity cancels all other operations for that entity
* *
* @example * @example
* ```typescript * ```typescript
@@ -66,7 +93,10 @@ export interface DeferredCommand {
* ``` * ```
*/ */
export class CommandBuffer { export class CommandBuffer {
/** 命令队列 | Command queue */ /** 每个实体的待处理操作 | Pending operations per entity */
private _pending: Map<Entity, PendingPerEntity> = new Map();
/** 旧式命令队列(用于兼容)| Legacy command queue (for compatibility) */
private _commands: DeferredCommand[] = []; private _commands: DeferredCommand[] = [];
/** 关联的场景 | Associated scene */ /** 关联的场景 | Associated scene */
@@ -75,6 +105,9 @@ export class CommandBuffer {
/** 是否启用调试日志 | Enable debug logging */ /** 是否启用调试日志 | Enable debug logging */
private _debug: boolean = false; private _debug: boolean = false;
/** 是否使用去重模式 | Whether to use deduplication mode */
private _useDeduplication: boolean = true;
/** /**
* 创建命令缓冲区 * 创建命令缓冲区
* Create command buffer * Create command buffer
@@ -103,11 +136,34 @@ export class CommandBuffer {
return this._scene; return this._scene;
} }
/**
* 设置是否使用去重模式
* Set whether to use deduplication mode
*
* @param enabled - 是否启用 | Whether to enable
*/
public setDeduplication(enabled: boolean): void {
this._useDeduplication = enabled;
}
/** /**
* 获取待执行的命令数量 * 获取待执行的命令数量
* Get pending command count * Get pending command count
*
* 返回实际的操作数量,而不是实体数量。
* Returns actual operation count, not entity count.
*/ */
public get pendingCount(): number { public get pendingCount(): number {
if (this._useDeduplication) {
let count = 0;
for (const ops of this._pending.values()) {
if (ops.bDestroy) count++;
if (ops.active !== undefined) count++;
if (ops.adds) count += ops.adds.size;
if (ops.removes) count += ops.removes.size;
}
return count;
}
return this._commands.length; return this._commands.length;
} }
@@ -116,25 +172,82 @@ export class CommandBuffer {
* Check if there are pending commands * Check if there are pending commands
*/ */
public get hasPending(): boolean { public get hasPending(): boolean {
if (this._useDeduplication) {
return this._pending.size > 0;
}
return this._commands.length > 0; return this._commands.length > 0;
} }
/**
* 获取或创建实体的待处理操作
* Get or create pending operations for entity
*/
private getPending(entity: Entity): PendingPerEntity {
let pending = this._pending.get(entity);
if (!pending) {
pending = {};
this._pending.set(entity, pending);
}
return pending;
}
/**
* 获取组件类型 ID位索引
* Get component type ID (bit index)
*/
private getTypeId(componentOrType: Component | ComponentType): number {
if (typeof componentOrType === 'function') {
// ComponentType
return ComponentRegistry.getBitIndex(componentOrType);
} else {
// Component instance
return ComponentRegistry.getBitIndex(componentOrType.constructor as ComponentType);
}
}
/** /**
* 延迟添加组件 * 延迟添加组件
* Deferred add component * Deferred add component
* *
* 如果之前有同类型的 removeComponent会被取消。
* If there was a removeComponent for the same type, it will be canceled.
*
* @param entity - 目标实体 | Target entity * @param entity - 目标实体 | Target entity
* @param component - 要添加的组件 | Component to add * @param component - 要添加的组件 | Component to add
*/ */
public addComponent(entity: Entity, component: Component): void { public addComponent(entity: Entity, component: Component): void {
this._commands.push({ if (this._useDeduplication) {
type: CommandType.ADD_COMPONENT, const pending = this.getPending(entity);
entity,
component
});
if (this._debug) { // 如果实体已标记销毁,忽略
logger.debug(`CommandBuffer: 延迟添加组件 ${component.constructor.name} 到实体 ${entity.name}`); if (pending.bDestroy) {
if (this._debug) {
logger.debug(`CommandBuffer: 忽略添加组件,实体 ${entity.name} 已标记销毁`);
}
return;
}
const typeId = this.getTypeId(component);
// 取消同类型的 remove
pending.removes?.delete(typeId);
// 添加(覆盖同类型的之前的 add
if (!pending.adds) {
pending.adds = new Map();
}
pending.adds.set(typeId, component);
if (this._debug) {
logger.debug(`CommandBuffer: 延迟添加组件 ${component.constructor.name} 到实体 ${entity.name}`);
}
} else {
// 旧模式
this._commands.push({
type: CommandType.ADD_COMPONENT,
entity,
component
});
} }
} }
@@ -142,18 +255,45 @@ export class CommandBuffer {
* 延迟移除组件 * 延迟移除组件
* Deferred remove component * Deferred remove component
* *
* 如果之前有同类型的 addComponent会被取消。
* If there was an addComponent for the same type, it will be canceled.
*
* @param entity - 目标实体 | Target entity * @param entity - 目标实体 | Target entity
* @param componentType - 要移除的组件类型 | Component type to remove * @param componentType - 要移除的组件类型 | Component type to remove
*/ */
public removeComponent<T extends Component>(entity: Entity, componentType: ComponentType<T>): void { public removeComponent<T extends Component>(entity: Entity, componentType: ComponentType<T>): void {
this._commands.push({ if (this._useDeduplication) {
type: CommandType.REMOVE_COMPONENT, const pending = this.getPending(entity);
entity,
componentType
});
if (this._debug) { // 如果实体已标记销毁,忽略
logger.debug(`CommandBuffer: 延迟移除组件 ${componentType.name} 从实体 ${entity.name}`); if (pending.bDestroy) {
if (this._debug) {
logger.debug(`CommandBuffer: 忽略移除组件,实体 ${entity.name} 已标记销毁`);
}
return;
}
const typeId = this.getTypeId(componentType);
// 取消同类型的 add
pending.adds?.delete(typeId);
// 添加到移除列表
if (!pending.removes) {
pending.removes = new Set();
}
pending.removes.add(typeId);
if (this._debug) {
logger.debug(`CommandBuffer: 延迟移除组件 ${componentType.name} 从实体 ${entity.name}`);
}
} else {
// 旧模式
this._commands.push({
type: CommandType.REMOVE_COMPONENT,
entity,
componentType
});
} }
} }
@@ -161,16 +301,32 @@ export class CommandBuffer {
* 延迟销毁实体 * 延迟销毁实体
* Deferred destroy entity * Deferred destroy entity
* *
* 会取消该实体的所有其他待处理操作。
* Cancels all other pending operations for this entity.
*
* @param entity - 要销毁的实体 | Entity to destroy * @param entity - 要销毁的实体 | Entity to destroy
*/ */
public destroyEntity(entity: Entity): void { public destroyEntity(entity: Entity): void {
this._commands.push({ if (this._useDeduplication) {
type: CommandType.DESTROY_ENTITY, const pending = this.getPending(entity);
entity
});
if (this._debug) { // 清除所有其他操作
logger.debug(`CommandBuffer: 延迟销毁实体 ${entity.name}`); pending.adds?.clear();
pending.removes?.clear();
delete pending.active;
// 标记销毁
pending.bDestroy = true;
if (this._debug) {
logger.debug(`CommandBuffer: 延迟销毁实体 ${entity.name}`);
}
} else {
// 旧模式
this._commands.push({
type: CommandType.DESTROY_ENTITY,
entity
});
} }
} }
@@ -182,14 +338,30 @@ export class CommandBuffer {
* @param active - 激活状态 | Active state * @param active - 激活状态 | Active state
*/ */
public setEntityActive(entity: Entity, active: boolean): void { public setEntityActive(entity: Entity, active: boolean): void {
this._commands.push({ if (this._useDeduplication) {
type: CommandType.SET_ENTITY_ACTIVE, const pending = this.getPending(entity);
entity,
value: active
});
if (this._debug) { // 如果实体已标记销毁,忽略
logger.debug(`CommandBuffer: 延迟设置实体 ${entity.name} 激活状态为 ${active}`); if (pending.bDestroy) {
if (this._debug) {
logger.debug(`CommandBuffer: 忽略设置激活状态,实体 ${entity.name} 已标记销毁`);
}
return;
}
// 设置(覆盖之前的设置)
pending.active = active;
if (this._debug) {
logger.debug(`CommandBuffer: 延迟设置实体 ${entity.name} 激活状态为 ${active}`);
}
} else {
// 旧模式
this._commands.push({
type: CommandType.SET_ENTITY_ACTIVE,
entity,
value: active
});
} }
} }
@@ -197,12 +369,120 @@ export class CommandBuffer {
* 执行所有待处理的命令 * 执行所有待处理的命令
* Execute all pending commands * Execute all pending commands
* *
* 执行顺序removes -> adds -> active -> destroy
* Execution order: removes -> adds -> active -> destroy
*
* 通常在帧末由 Scene 自动调用。 * 通常在帧末由 Scene 自动调用。
* Usually called automatically by Scene at end of frame. * Usually called automatically by Scene at end of frame.
* *
* @returns 执行的命令数量 | Number of commands executed * @returns 执行的命令数量 | Number of commands executed
*/ */
public flush(): number { public flush(): number {
if (this._useDeduplication) {
return this.flushDeduplication();
} else {
return this.flushLegacy();
}
}
/**
* 使用去重模式刷新
* Flush using deduplication mode
*/
private flushDeduplication(): number {
if (this._pending.size === 0) {
return 0;
}
const entityCount = this._pending.size;
let commandCount = 0;
if (this._debug) {
logger.debug(`CommandBuffer: 开始执行 ${entityCount} 个实体的延迟命令`);
}
// 复制并清空,防止执行过程中有新命令加入
const pending = this._pending;
this._pending = new Map();
// 分阶段执行
// Phase 1: Removes
for (const [entity, ops] of pending) {
if (ops.bDestroy || !entity.scene) continue;
if (ops.removes && ops.removes.size > 0) {
for (const typeId of ops.removes) {
try {
const componentType = ComponentRegistry.getTypeByBitIndex(typeId);
if (componentType) {
entity.removeComponentByType(componentType);
commandCount++;
}
} catch (error) {
logger.error(`CommandBuffer: 移除组件失败`, { entity: entity.name, typeId, error });
}
}
}
}
// Phase 2: Adds
for (const [entity, ops] of pending) {
if (ops.bDestroy || !entity.scene) continue;
if (ops.adds && ops.adds.size > 0) {
for (const component of ops.adds.values()) {
try {
entity.addComponent(component);
commandCount++;
} catch (error) {
logger.error(`CommandBuffer: 添加组件失败`, {
entity: entity.name,
component: component.constructor.name,
error
});
}
}
}
}
// Phase 3: Active state
for (const [entity, ops] of pending) {
if (ops.bDestroy || !entity.scene) continue;
if (ops.active !== undefined) {
try {
entity.active = ops.active;
commandCount++;
} catch (error) {
logger.error(`CommandBuffer: 设置激活状态失败`, { entity: entity.name, error });
}
}
}
// Phase 4: Destroy
for (const [entity, ops] of pending) {
if (!ops.bDestroy || !entity.scene) continue;
try {
entity.destroy();
commandCount++;
} catch (error) {
logger.error(`CommandBuffer: 销毁实体失败`, { entity: entity.name, error });
}
}
if (this._debug) {
logger.debug(`CommandBuffer: 完成执行 ${commandCount} 个延迟命令`);
}
return commandCount;
}
/**
* 使用旧模式刷新
* Flush using legacy mode
*/
private flushLegacy(): number {
if (this._commands.length === 0) { if (this._commands.length === 0) {
return 0; return 0;
} }
@@ -214,7 +494,6 @@ export class CommandBuffer {
} }
// 复制命令列表并清空,防止执行过程中有新命令加入 // 复制命令列表并清空,防止执行过程中有新命令加入
// Copy and clear command list to prevent new commands during execution
const commands = this._commands; const commands = this._commands;
this._commands = []; this._commands = [];
@@ -230,12 +509,11 @@ export class CommandBuffer {
} }
/** /**
* 执行单个命令 * 执行单个命令(旧模式)
* Execute single command * Execute single command (legacy mode)
*/ */
private executeCommand(cmd: DeferredCommand): void { private executeCommand(cmd: DeferredCommand): void {
// 检查实体是否仍然有效 // 检查实体是否仍然有效
// Check if entity is still valid
if (!cmd.entity.scene) { if (!cmd.entity.scene) {
if (this._debug) { if (this._debug) {
logger.debug(`CommandBuffer: 跳过命令,实体 ${cmd.entity.name} 已无效`); logger.debug(`CommandBuffer: 跳过命令,实体 ${cmd.entity.name} 已无效`);
@@ -277,9 +555,13 @@ export class CommandBuffer {
* Clear all pending commands (without executing) * Clear all pending commands (without executing)
*/ */
public clear(): void { public clear(): void {
if (this._debug && this._commands.length > 0) { if (this._debug) {
logger.debug(`CommandBuffer: 清空 ${this._commands.length} 个未执行的命令`); const count = this._useDeduplication ? this._pending.size : this._commands.length;
if (count > 0) {
logger.debug(`CommandBuffer: 清空 ${count} 个未执行的命令`);
}
} }
this._pending.clear();
this._commands.length = 0; this._commands.length = 0;
} }

View File

@@ -0,0 +1,136 @@
/**
* 轻量级实体句柄
*
* 使用数值表示实体,包含索引和代数信息。
* 28位索引 + 20位代数 = 48位在 JavaScript 安全整数范围内。
*
* Lightweight entity handle.
* Uses numeric value to represent entity with index and generation.
* 28-bit index + 20-bit generation = 48-bit, within JavaScript safe integer range.
*
* @example
* ```typescript
* const handle = makeHandle(42, 1);
* console.log(indexOf(handle)); // 42
* console.log(genOf(handle)); // 1
* console.log(isValidHandle(handle)); // true
* ```
*/
/**
* 实体句柄类型
*
* 使用 branded type 提供类型安全。
*
* Entity handle type.
* Uses branded type for type safety.
*/
export type EntityHandle = number & { readonly __brand: 'EntityHandle' };
/**
* 索引位数 | Index bits
*/
export const INDEX_BITS = 28;
/**
* 代数位数 | Generation bits
*/
export const GEN_BITS = 20;
/**
* 索引掩码 | Index mask
*/
export const INDEX_MASK = (1 << INDEX_BITS) - 1; // 0x0FFFFFFF
/**
* 代数掩码 | Generation mask
*/
export const GEN_MASK = (1 << GEN_BITS) - 1; // 0x000FFFFF
/**
* 最大实体数量 | Maximum entity count
*/
export const MAX_ENTITIES = 1 << INDEX_BITS; // 268,435,456
/**
* 最大代数值 | Maximum generation value
*/
export const MAX_GENERATION = 1 << GEN_BITS; // 1,048,576
/**
* 空句柄常量 | Null handle constant
*/
export const NULL_HANDLE = 0 as EntityHandle;
/**
* 创建实体句柄
* Create entity handle
*
* @param index 实体索引 | Entity index
* @param generation 实体代数 | Entity generation
* @returns 实体句柄 | Entity handle
*/
export function makeHandle(index: number, generation: number): EntityHandle {
// handle = generation * 2^28 + index
// 使用乘法而不是位移,因为位移只支持 32 位
return ((generation & GEN_MASK) * MAX_ENTITIES + (index & INDEX_MASK)) as EntityHandle;
}
/**
* 从句柄提取索引
* Extract index from handle
*
* @param handle 实体句柄 | Entity handle
* @returns 实体索引 | Entity index
*/
export function indexOf(handle: EntityHandle): number {
return handle & INDEX_MASK;
}
/**
* 从句柄提取代数
* Extract generation from handle
*
* @param handle 实体句柄 | Entity handle
* @returns 实体代数 | Entity generation
*/
export function genOf(handle: EntityHandle): number {
return Math.floor(handle / MAX_ENTITIES) & GEN_MASK;
}
/**
* 检查句柄是否有效(非空)
* Check if handle is valid (non-null)
*
* @param handle 实体句柄 | Entity handle
* @returns 是否有效 | Whether valid
*/
export function isValidHandle(handle: EntityHandle): boolean {
return handle !== NULL_HANDLE;
}
/**
* 比较两个句柄是否相等
* Compare two handles for equality
*
* @param a 第一个句柄 | First handle
* @param b 第二个句柄 | Second handle
* @returns 是否相等 | Whether equal
*/
export function handleEquals(a: EntityHandle, b: EntityHandle): boolean {
return a === b;
}
/**
* 将句柄转换为字符串(用于调试)
* Convert handle to string (for debugging)
*
* @param handle 实体句柄 | Entity handle
* @returns 字符串表示 | String representation
*/
export function handleToString(handle: EntityHandle): string {
if (handle === NULL_HANDLE) {
return 'Entity(NULL)';
}
return `Entity(idx=${indexOf(handle)}, gen=${genOf(handle)})`;
}

View File

@@ -0,0 +1,331 @@
/**
* 实体句柄管理器
*
* 管理轻量级实体句柄的生命周期,包括创建、销毁和状态查询。
* 使用 TypedArray 实现高效的内存布局。
*
* Entity handle manager.
* Manages lifecycle of lightweight entity handles including creation, destruction and state queries.
* Uses TypedArray for efficient memory layout.
*
* @example
* ```typescript
* const manager = new EntityHandleManager();
* const handle = manager.create();
* console.log(manager.isAlive(handle)); // true
*
* manager.destroy(handle);
* console.log(manager.isAlive(handle)); // false
*
* // 索引会被复用,但代数会增加
* const newHandle = manager.create();
* console.log(indexOf(handle) === indexOf(newHandle)); // true
* console.log(genOf(newHandle) > genOf(handle)); // true
* ```
*/
import {
EntityHandle,
makeHandle,
indexOf,
genOf,
MAX_ENTITIES,
MAX_GENERATION
} from './EntityHandle';
/**
* 初始容量 | Initial capacity
*/
const INITIAL_CAPACITY = 1024;
/**
* 实体句柄管理器
*
* Entity handle manager.
*/
export class EntityHandleManager {
/**
* 代数数组(每个索引的当前代数)
* Generation array (current generation for each index)
*/
private _generations: Uint32Array;
/**
* 存活状态数组0=死亡1=存活)
* Alive state array (0=dead, 1=alive)
*/
private _alive: Uint8Array;
/**
* 启用状态数组0=禁用1=启用)
* Enabled state array (0=disabled, 1=enabled)
*/
private _enabled: Uint8Array;
/**
* 空闲索引列表(用于复用已销毁的索引)
* Free index list (for reusing destroyed indices)
*/
private _freeList: number[] = [];
/**
* 下一个新索引
* Next new index
*/
private _nextIndex: number = 1; // 从 1 开始0 保留给 NULL_HANDLE
/**
* 当前存活实体数量
* Current alive entity count
*/
private _aliveCount: number = 0;
/**
* 当前容量
* Current capacity
*/
private _capacity: number;
/**
* 创建实体句柄管理器
* Create entity handle manager
*
* @param initialCapacity 初始容量 | Initial capacity
*/
constructor(initialCapacity: number = INITIAL_CAPACITY) {
this._capacity = initialCapacity;
this._generations = new Uint32Array(initialCapacity);
this._alive = new Uint8Array(initialCapacity);
this._enabled = new Uint8Array(initialCapacity);
// 索引 0 保留给 NULL_HANDLE
this._alive[0] = 0;
this._enabled[0] = 0;
}
/**
* 获取存活实体数量
* Get alive entity count
*/
public get aliveCount(): number {
return this._aliveCount;
}
/**
* 获取当前容量
* Get current capacity
*/
public get capacity(): number {
return this._capacity;
}
/**
* 创建新实体句柄
* Create new entity handle
*
* @returns 新实体句柄 | New entity handle
*/
public create(): EntityHandle {
let index: number;
// 优先从空闲列表中获取索引
if (this._freeList.length > 0) {
index = this._freeList.pop()!;
} else {
// 分配新索引
index = this._nextIndex++;
// 检查是否需要扩容
if (index >= this._capacity) {
this.grow(index);
}
}
// 获取当前代数(可能因之前销毁而增加)
const generation = this._generations[index]!;
// 标记为存活和启用
this._alive[index] = 1;
this._enabled[index] = 1;
this._aliveCount++;
return makeHandle(index, generation);
}
/**
* 销毁实体句柄
* Destroy entity handle
*
* @param handle 要销毁的句柄 | Handle to destroy
* @returns 是否成功销毁 | Whether destruction succeeded
*/
public destroy(handle: EntityHandle): boolean {
const index = indexOf(handle);
const generation = genOf(handle);
// 验证句柄有效性
if (index >= this._capacity || index === 0) {
return false;
}
if (this._generations[index] !== generation) {
return false;
}
if (this._alive[index] !== 1) {
return false;
}
// 标记为死亡
this._alive[index] = 0;
this._enabled[index] = 0;
this._aliveCount--;
// 增加代数(防止 ABA 问题)
const newGen = (generation + 1) % MAX_GENERATION;
this._generations[index] = newGen;
// 将索引加入空闲列表
this._freeList.push(index);
return true;
}
/**
* 检查句柄是否存活
* Check if handle is alive
*
* @param handle 实体句柄 | Entity handle
* @returns 是否存活 | Whether alive
*/
public isAlive(handle: EntityHandle): boolean {
const index = indexOf(handle);
const generation = genOf(handle);
if (index >= this._capacity || index === 0) {
return false;
}
return this._alive[index] === 1 && this._generations[index] === generation;
}
/**
* 检查句柄是否启用
* Check if handle is enabled
*
* @param handle 实体句柄 | Entity handle
* @returns 是否启用 | Whether enabled
*/
public isEnabled(handle: EntityHandle): boolean {
if (!this.isAlive(handle)) {
return false;
}
const index = indexOf(handle);
return this._enabled[index] === 1;
}
/**
* 设置句柄启用状态
* Set handle enabled state
*
* @param handle 实体句柄 | Entity handle
* @param enabled 启用状态 | Enabled state
* @returns 是否成功设置 | Whether setting succeeded
*/
public setEnabled(handle: EntityHandle, enabled: boolean): boolean {
if (!this.isAlive(handle)) {
return false;
}
const index = indexOf(handle);
this._enabled[index] = enabled ? 1 : 0;
return true;
}
/**
* 验证句柄是否有效(存活且代数匹配)
* Validate if handle is valid (alive and generation matches)
*
* @param handle 实体句柄 | Entity handle
* @returns 是否有效 | Whether valid
*/
public validate(handle: EntityHandle): boolean {
return this.isAlive(handle);
}
/**
* 扩容数组
* Grow arrays
*/
private grow(minIndex: number): void {
let newSize = this._capacity;
// 按 2 倍扩容
while (newSize <= minIndex) {
newSize <<= 1;
}
// 检查是否超过最大容量
if (newSize > MAX_ENTITIES) {
newSize = MAX_ENTITIES;
if (minIndex >= newSize) {
throw new Error(`EntityHandleManager: 超过最大实体数量 ${MAX_ENTITIES}`);
}
}
// 创建新数组并复制数据
const newGenerations = new Uint32Array(newSize);
const newAlive = new Uint8Array(newSize);
const newEnabled = new Uint8Array(newSize);
newGenerations.set(this._generations);
newAlive.set(this._alive);
newEnabled.set(this._enabled);
this._generations = newGenerations;
this._alive = newAlive;
this._enabled = newEnabled;
this._capacity = newSize;
}
/**
* 重置管理器状态
* Reset manager state
*/
public reset(): void {
this._generations.fill(0);
this._alive.fill(0);
this._enabled.fill(0);
this._freeList.length = 0;
this._nextIndex = 1;
this._aliveCount = 0;
}
/**
* 遍历所有存活的句柄
* Iterate all alive handles
*
* @param callback 回调函数 | Callback function
*/
public forEach(callback: (handle: EntityHandle) => void): void {
for (let i = 1; i < this._nextIndex; i++) {
if (this._alive[i] === 1) {
const handle = makeHandle(i, this._generations[i]!);
callback(handle);
}
}
}
/**
* 获取所有存活的句柄
* Get all alive handles
*
* @returns 存活句柄数组 | Alive handles array
*/
public getAllAlive(): EntityHandle[] {
const result: EntityHandle[] = [];
this.forEach((handle) => result.push(handle));
return result;
}
}

View File

@@ -0,0 +1,103 @@
/**
* 帧级变更检测管理器
*
* 基于 epoch帧计数的组件变更追踪系统。
* 每帧递增 epoch组件写入时记录当前 epoch
* 系统可以查询自上次检查以来发生变更的组件。
*
* Frame-level change detection manager.
* Epoch-based component change tracking system.
* Epoch increments each frame, components record current epoch on write,
* systems can query components changed since last check.
*
* @example
* ```typescript
* // 在 System 中使用
* class MovementSystem extends EntitySystem {
* private _lastEpoch = 0;
*
* process(): void {
* const epoch = this.scene.epochManager;
*
* // 只处理 Velocity 变化的实体
* for (const entity of this.entities) {
* const vel = entity.getComponent(Velocity);
* if (vel && vel.lastWriteEpoch > this._lastEpoch) {
* // 处理变更
* }
* }
*
* this._lastEpoch = epoch.current;
* }
* }
* ```
*/
/**
* Epoch 管理器
*
* Epoch manager.
*/
export class EpochManager {
/**
* 当前 epoch 值
*
* 从 1 开始0 表示"从未写入"。
*
* Current epoch value.
* Starts from 1, 0 means "never written".
*/
private _current: number = 1;
/**
* 获取当前 epoch
*
* Get current epoch.
*/
public get current(): number {
return this._current;
}
/**
* 递增 epoch
*
* 应在每帧开始时调用。
*
* Increment epoch.
* Should be called at the start of each frame.
*/
public increment(): void {
this._current++;
// 处理溢出(虽然 2^53 帧基本不可能达到)
// Handle overflow (though 2^53 frames is practically unreachable)
if (this._current >= Number.MAX_SAFE_INTEGER) {
this._current = 1;
}
}
/**
* 重置 epoch
*
* 在场景重置时调用。
*
* Reset epoch.
* Called when scene is reset.
*/
public reset(): void {
this._current = 1;
}
/**
* 检查给定 epoch 是否在指定 epoch 之后发生变更
*
* Check if given epoch is after the specified epoch (changed since).
*
* @param writeEpoch 写入时的 epoch | Epoch when written
* @param sinceEpoch 检查点 epoch | Checkpoint epoch
* @returns 是否在检查点之后发生变更 | Whether changed after checkpoint
*/
public isChangedSince(writeEpoch: number, sinceEpoch: number): boolean {
return writeEpoch > sinceEpoch;
}
}

View File

@@ -0,0 +1,366 @@
/**
* 编译查询
*
* 预编译的查询执行计划,避免每次查询时重复构建匹配条件。
* 提供便捷的迭代方法和变更检测支持。
*
* Compiled Query.
* Pre-compiled query execution plan that avoids repeated condition building.
* Provides convenient iteration methods and change detection support.
*
* @example
* ```typescript
* class MovementSystem extends EntitySystem {
* private _query: CompiledQuery<[typeof Position, typeof Velocity]>;
*
* onInitialize(): void {
* this._query = this.scene.querySystem.compile(Position, Velocity);
* }
*
* update(): void {
* this._query.forEach((entity, pos, vel) => {
* pos.x += vel.x * this.deltaTime;
* pos.y += vel.y * this.deltaTime;
* });
* }
* }
* ```
*/
import { Entity } from '../../Entity';
import { Component } from '../../Component';
import { ComponentType } from '../ComponentStorage';
import { QuerySystem } from '../QuerySystem';
/**
* 组件实例类型提取
*
* Extract component instance types from component type tuple.
*/
export type InstanceTypes<T extends ComponentType[]> = {
[K in keyof T]: T[K] extends ComponentType<infer C> ? C : never;
};
/**
* 编译查询类
*
* Compiled query class.
*/
export class CompiledQuery<T extends ComponentType[] = ComponentType[]> {
/**
* 查询的组件类型列表
*
* Component types for this query.
*/
private readonly _componentTypes: T;
/**
* 查询系统引用
*
* Reference to query system.
*/
private readonly _querySystem: QuerySystem;
/**
* 上次查询的版本号(用于缓存失效检测)
*
* Last query version (for cache invalidation detection).
*/
private _lastVersion: number = -1;
/**
* 缓存的实体列表
*
* Cached entity list.
*/
private _cachedEntities: readonly Entity[] = [];
/**
* 创建编译查询
*
* Create compiled query.
*
* @param querySystem 查询系统引用 | Query system reference
* @param componentTypes 组件类型列表 | Component type list
*/
constructor(querySystem: QuerySystem, ...componentTypes: T) {
this._querySystem = querySystem;
this._componentTypes = componentTypes;
}
/**
* 获取组件类型列表
*
* Get component type list.
*/
public get componentTypes(): readonly ComponentType[] {
return this._componentTypes;
}
/**
* 获取匹配的实体列表
*
* Get matching entity list.
*/
public get entities(): readonly Entity[] {
this._refreshCache();
return this._cachedEntities;
}
/**
* 获取匹配的实体数量
*
* Get matching entity count.
*/
public get count(): number {
return this.entities.length;
}
/**
* 刷新缓存
*
* Refresh cache if needed.
*/
private _refreshCache(): void {
const currentVersion = this._querySystem.version;
if (this._lastVersion !== currentVersion) {
const result = this._querySystem.queryAll(...this._componentTypes);
this._cachedEntities = result.entities;
this._lastVersion = currentVersion;
}
}
/**
* 遍历所有匹配的实体及其组件
*
* Iterate all matching entities with their components.
*
* @param callback 回调函数,接收实体和组件实例 | Callback receiving entity and component instances
*
* @example
* ```typescript
* query.forEach((entity, position, velocity) => {
* position.x += velocity.x * deltaTime;
* });
* ```
*/
public forEach(
callback: (entity: Entity, ...components: InstanceTypes<T>) => void
): void {
const entities = this.entities;
const componentTypes = this._componentTypes;
const typeCount = componentTypes.length;
for (let i = 0, len = entities.length; i < len; i++) {
const entity = entities[i]!;
const components: Component[] = new Array(typeCount);
// 获取所有组件
for (let j = 0; j < typeCount; j++) {
const component = entity.getComponent(componentTypes[j]!);
if (!component) {
// 组件不存在,跳过这个实体
continue;
}
components[j] = component;
}
// 调用回调
callback(entity, ...(components as InstanceTypes<T>));
}
}
/**
* 遍历自指定 epoch 以来发生变更的实体
*
* Iterate entities with components changed since specified epoch.
*
* @param sinceEpoch 检查点 epoch | Checkpoint epoch
* @param callback 回调函数 | Callback function
*
* @example
* ```typescript
* class PhysicsSystem extends EntitySystem {
* private _lastEpoch = 0;
*
* update(): void {
* this._query.forEachChanged(this._lastEpoch, (entity, pos, vel) => {
* // 只处理变更的实体
* });
* this._lastEpoch = this.scene.epochManager.current;
* }
* }
* ```
*/
public forEachChanged(
sinceEpoch: number,
callback: (entity: Entity, ...components: InstanceTypes<T>) => void
): void {
const entities = this.entities;
const componentTypes = this._componentTypes;
const typeCount = componentTypes.length;
for (let i = 0, len = entities.length; i < len; i++) {
const entity = entities[i]!;
const components: Component[] = new Array(typeCount);
let hasChanged = false;
// 获取所有组件并检查变更
for (let j = 0; j < typeCount; j++) {
const component = entity.getComponent(componentTypes[j]!);
if (!component) {
continue;
}
components[j] = component;
if (component.lastWriteEpoch > sinceEpoch) {
hasChanged = true;
}
}
// 只在有变更时调用回调
if (hasChanged) {
callback(entity, ...(components as InstanceTypes<T>));
}
}
}
/**
* 获取第一个匹配的实体及其组件
*
* Get first matching entity with its components.
*
* @returns 实体和组件元组,或 null | Entity and components tuple, or null
*/
public first(): [Entity, ...InstanceTypes<T>] | null {
const entities = this.entities;
if (entities.length === 0) {
return null;
}
const entity = entities[0]!;
const components: Component[] = [];
for (const type of this._componentTypes) {
const component = entity.getComponent(type);
if (!component) {
return null;
}
components.push(component);
}
return [entity, ...(components as InstanceTypes<T>)];
}
/**
* 转换为数组
*
* Convert to array.
*
* @returns 实体和组件元组数组 | Array of entity and components tuples
*/
public toArray(): Array<[Entity, ...InstanceTypes<T>]> {
const result: Array<[Entity, ...InstanceTypes<T>]> = [];
this.forEach((entity, ...components) => {
result.push([entity, ...components]);
});
return result;
}
/**
* 映射转换
*
* Map transformation.
*
* @param callback 转换函数 | Transform function
* @returns 转换结果数组 | Array of transform results
*/
public map<R>(
callback: (entity: Entity, ...components: InstanceTypes<T>) => R
): R[] {
const result: R[] = [];
this.forEach((entity, ...components) => {
result.push(callback(entity, ...components));
});
return result;
}
/**
* 过滤实体
*
* Filter entities.
*
* @param predicate 过滤条件 | Filter predicate
* @returns 过滤后的实体数组 | Filtered entity array
*/
public filter(
predicate: (entity: Entity, ...components: InstanceTypes<T>) => boolean
): Entity[] {
const result: Entity[] = [];
this.forEach((entity, ...components) => {
if (predicate(entity, ...components)) {
result.push(entity);
}
});
return result;
}
/**
* 查找满足条件的实体
*
* Find entity matching predicate.
*
* @param predicate 查找条件 | Find predicate
* @returns 找到的实体或 undefined | Found entity or undefined
*/
public find(
predicate: (entity: Entity, ...components: InstanceTypes<T>) => boolean
): Entity | undefined {
const entities = this.entities;
const componentTypes = this._componentTypes;
const typeCount = componentTypes.length;
for (let i = 0, len = entities.length; i < len; i++) {
const entity = entities[i]!;
const components: Component[] = new Array(typeCount);
for (let j = 0; j < typeCount; j++) {
const component = entity.getComponent(componentTypes[j]!);
if (!component) {
continue;
}
components[j] = component;
}
if (predicate(entity, ...(components as InstanceTypes<T>))) {
return entity;
}
}
return undefined;
}
/**
* 检查是否有任何实体匹配
*
* Check if any entity matches.
*/
public any(): boolean {
return this.count > 0;
}
/**
* 检查是否没有实体匹配
*
* Check if no entity matches.
*/
public empty(): boolean {
return this.count === 0;
}
}

View File

@@ -7,8 +7,10 @@ import { getComponentTypeName } from '../Decorators';
import { Archetype, ArchetypeSystem } from './ArchetypeSystem'; import { Archetype, ArchetypeSystem } from './ArchetypeSystem';
import { ReactiveQuery, ReactiveQueryConfig } from './ReactiveQuery'; import { ReactiveQuery, ReactiveQueryConfig } from './ReactiveQuery';
import { QueryCondition, QueryConditionType, QueryResult } from './QueryTypes'; import { QueryCondition, QueryConditionType, QueryResult } from './QueryTypes';
import { CompiledQuery } from './Query/CompiledQuery';
export { QueryCondition, QueryConditionType, QueryResult }; export { QueryCondition, QueryConditionType, QueryResult };
export { CompiledQuery };
/** /**
* 实体索引结构 * 实体索引结构
@@ -530,6 +532,76 @@ export class QuerySystem {
}; };
} }
/**
* 查询自指定 epoch 以来发生变更的实体
*
* 返回拥有指定组件且该组件在 sinceEpoch 之后被修改过的实体。
* 用于实现增量更新,只处理发生变化的实体。
*
* Query entities with components changed since the specified epoch.
* Returns entities that have the specified component type(s) and that component
* was modified after sinceEpoch.
* Used for incremental updates, only processing entities that have changed.
*
* @param sinceEpoch 上次检查的 epoch | Last checked epoch
* @param componentTypes 要检查的组件类型 | Component types to check
* @returns 查询结果,包含变更的实体 | Query result with changed entities
*
* @example
* ```typescript
* class MovementSystem extends EntitySystem {
* private _lastEpoch = 0;
*
* update(): void {
* // 只处理 Velocity 组件发生变化的实体
* const result = this.scene.querySystem.queryChangedSince(
* this._lastEpoch,
* Velocity
* );
*
* for (const entity of result.entities) {
* // 处理变更的实体
* }
*
* this._lastEpoch = this.scene.epochManager.current;
* }
* }
* ```
*/
public queryChangedSince(sinceEpoch: number, ...componentTypes: ComponentType[]): QueryResult {
const startTime = performance.now();
this._queryStats.totalQueries++;
this._queryStats.dirtyChecks++;
// 先获取所有拥有这些组件的实体
const baseResult = this.queryAll(...componentTypes);
const changedEntities: Entity[] = [];
// 过滤出组件发生变更的实体
for (const entity of baseResult.entities) {
let hasChanged = false;
for (const componentType of componentTypes) {
const component = entity.getComponent(componentType);
if (component && component.lastWriteEpoch > sinceEpoch) {
hasChanged = true;
break;
}
}
if (hasChanged) {
changedEntities.push(entity);
}
}
return {
entities: changedEntities,
count: changedEntities.length,
executionTime: performance.now() - startTime,
fromCache: false
};
}
/** /**
* 按单个组件类型查询实体 * 按单个组件类型查询实体
* *
@@ -704,6 +776,42 @@ export class QuerySystem {
this.clearReactiveQueries(); this.clearReactiveQueries();
} }
/**
* 编译查询
*
* 创建预编译的查询对象,避免每次查询时重复构建匹配条件。
* 适用于在 System 中频繁执行的查询,提供便捷的迭代方法。
*
* Compile query.
* Creates a pre-compiled query object that avoids repeated condition building.
* Suitable for frequently executed queries in Systems, provides convenient iteration.
*
* @param componentTypes 要查询的组件类型 | Component types to query
* @returns 编译查询实例 | Compiled query instance
*
* @example
* ```typescript
* class MovementSystem extends EntitySystem {
* private _query: CompiledQuery<[typeof Position, typeof Velocity]>;
*
* onInitialize(): void {
* this._query = this.scene.querySystem.compile(Position, Velocity);
* }
*
* update(): void {
* this._query.forEach((entity, pos, vel) => {
* pos.x += vel.x * this.deltaTime;
* pos.y += vel.y * this.deltaTime;
* pos.markDirty(this.scene.epochManager.current);
* });
* }
* }
* ```
*/
public compile<T extends ComponentType[]>(...componentTypes: T): CompiledQuery<T> {
return new CompiledQuery<T>(this, ...componentTypes);
}
/** /**
* 创建响应式查询 * 创建响应式查询
* *

View File

@@ -0,0 +1,263 @@
/**
* 系统依赖图
*
* 用于构建系统间的依赖关系并进行拓扑排序。
* 支持 before/after 依赖声明和 set 分组。
*
* System dependency graph.
* Used to build dependency relationships between systems and perform topological sorting.
* Supports before/after dependencies and set grouping.
*/
/**
* 循环依赖错误
* Cycle dependency error
*/
export class CycleDependencyError extends Error {
/**
* 参与循环的节点 ID
* Node IDs involved in the cycle
*/
public readonly involvedNodes: string[];
constructor(involvedNodes: string[]) {
const message = `[SystemDependencyGraph] 检测到循环依赖 | Cycle dependency detected: ${involvedNodes.join(' -> ')}`;
super(message);
this.name = 'CycleDependencyError';
this.involvedNodes = involvedNodes;
Object.setPrototypeOf(this, new.target.prototype);
}
}
/**
* 依赖图节点
* Dependency graph node
*/
interface GraphNode {
/** 节点 ID | Node ID */
id: string;
/** 是否为虚拟集合节点 | Whether this is a virtual set node */
bIsVirtual: boolean;
/** 入边(依赖于此节点的节点) | Incoming edges (nodes that depend on this node) */
inEdges: Set<string>;
/** 出边(此节点依赖的节点) | Outgoing edges (nodes this node depends on) */
outEdges: Set<string>;
}
/**
* 系统依赖信息
* System dependency info
*/
export interface SystemDependencyInfo {
/** 系统名称 | System name */
name: string;
/** 在这些系统之前执行 | Execute before these systems */
before: string[];
/** 在这些系统之后执行 | Execute after these systems */
after: string[];
/** 所属集合 | Sets this system belongs to */
sets: string[];
}
/** 集合前缀 | Set prefix */
const SET_PREFIX = 'set:';
/**
* 系统依赖图
*
* 使用 Kahn 算法进行拓扑排序,检测循环依赖。
*
* System dependency graph.
* Uses Kahn's algorithm for topological sorting and cycle detection.
*/
export class SystemDependencyGraph {
/** 节点映射 | Node map */
private _nodes: Map<string, GraphNode> = new Map();
/**
* 添加系统节点
* Add system node
*
* @param name 系统名称 | System name
*/
public addSystemNode(name: string): void {
this.getOrCreateNode(name, false);
}
/**
* 添加集合节点(虚拟节点)
* Add set node (virtual node)
*
* @param setName 集合名称 | Set name
*/
public addSetNode(setName: string): void {
const nodeId = SET_PREFIX + setName;
this.getOrCreateNode(nodeId, true);
}
/**
* 添加依赖边
* Add dependency edge
*
* @param from 起始节点 | Source node
* @param to 目标节点 | Target node
*/
public addEdge(from: string, to: string): void {
if (from === to) return;
const fromNode = this.getOrCreateNode(from, from.startsWith(SET_PREFIX));
const toNode = this.getOrCreateNode(to, to.startsWith(SET_PREFIX));
fromNode.outEdges.add(to);
toNode.inEdges.add(from);
}
/**
* 从系统依赖信息构建图
* Build graph from system dependency info
*
* @param systems 系统依赖信息列表 | System dependency info list
*/
public buildFromSystems(systems: SystemDependencyInfo[]): void {
this.clear();
// 1. 添加所有系统节点
for (const sys of systems) {
this.addSystemNode(sys.name);
// 添加集合节点
for (const setName of sys.sets) {
this.addSetNode(setName);
}
}
// 2. 添加边
for (const sys of systems) {
// 集合 -> 系统(系统属于集合,集合执行后系统执行)
for (const setName of sys.sets) {
const setId = SET_PREFIX + setName;
this.addEdge(setId, sys.name);
}
// before: 此系统 -> 目标(此系统先执行)
for (const target of sys.before) {
const targetId = this.resolveTargetId(target);
this.addEdge(sys.name, targetId);
}
// after: 目标 -> 此系统(目标先执行)
for (const target of sys.after) {
const targetId = this.resolveTargetId(target);
this.addEdge(targetId, sys.name);
}
}
}
/**
* 执行拓扑排序Kahn 算法)
* Perform topological sort (Kahn's algorithm)
*
* @returns 排序后的系统名称列表(不包含虚拟节点) | Sorted system names (excluding virtual nodes)
* @throws {CycleDependencyError} 如果存在循环依赖 | If cycle dependency detected
*/
public topologicalSort(): string[] {
// 复制入边计数(避免修改原始数据)
const inDegree = new Map<string, number>();
for (const [id, node] of this._nodes) {
inDegree.set(id, node.inEdges.size);
}
// 初始化队列:入度为 0 的节点
const queue: string[] = [];
for (const [id, degree] of inDegree) {
if (degree === 0) {
queue.push(id);
}
}
const result: string[] = [];
let processedCount = 0;
while (queue.length > 0) {
const nodeId = queue.shift()!;
processedCount++;
const node = this._nodes.get(nodeId);
if (!node) continue;
// 只添加非虚拟节点到结果
if (!node.bIsVirtual) {
result.push(nodeId);
}
// 减少所有出边目标的入度
for (const outId of node.outEdges) {
const newDegree = (inDegree.get(outId) ?? 0) - 1;
inDegree.set(outId, newDegree);
if (newDegree === 0) {
queue.push(outId);
}
}
}
// 检测循环:如果处理的节点数少于总节点数,说明存在循环
if (processedCount < this._nodes.size) {
const cycleNodes: string[] = [];
for (const [id, degree] of inDegree) {
if (degree > 0) {
cycleNodes.push(id);
}
}
throw new CycleDependencyError(cycleNodes);
}
return result;
}
/**
* 清空图
* Clear graph
*/
public clear(): void {
this._nodes.clear();
}
/**
* 获取节点数量
* Get node count
*/
public get size(): number {
return this._nodes.size;
}
/**
* 获取或创建节点
* Get or create node
*/
private getOrCreateNode(id: string, bIsVirtual: boolean): GraphNode {
let node = this._nodes.get(id);
if (!node) {
node = {
id,
bIsVirtual,
inEdges: new Set(),
outEdges: new Set()
};
this._nodes.set(id, node);
}
return node;
}
/**
* 解析目标 ID支持 set: 前缀)
* Resolve target ID (supports set: prefix)
*/
private resolveTargetId(nameOrSet: string): string {
// 如果已经有 set: 前缀,直接返回
if (nameOrSet.startsWith(SET_PREFIX)) {
return nameOrSet;
}
return nameOrSet;
}
}

View File

@@ -0,0 +1,338 @@
/**
* 系统调度器
*
* 负责管理系统的执行阶段和依赖关系。
* 支持声明式的 before/after 依赖和拓扑排序。
*
* System scheduler.
* Manages system execution stages and dependencies.
* Supports declarative before/after dependencies and topological sorting.
*
* @example
* ```typescript
* // 使用装饰器声明依赖
* @Stage('update')
* @After('PhysicsSystem')
* @Before('RenderSystem')
* class MovementSystem extends EntitySystem { }
*
* // 或者使用方法链
* class MovementSystem extends EntitySystem {
* constructor() {
* super();
* this.stage('update').after('PhysicsSystem').before('RenderSystem');
* }
* }
* ```
*/
import { SystemDependencyGraph, CycleDependencyError, type SystemDependencyInfo } from './SystemDependencyGraph';
import type { EntitySystem } from '../Systems/EntitySystem';
export { CycleDependencyError };
/**
* 系统执行阶段
* System execution stage
*/
export type SystemStage = 'startup' | 'preUpdate' | 'update' | 'postUpdate' | 'cleanup';
/**
* 默认阶段执行顺序
* Default stage execution order
*/
export const DEFAULT_STAGE_ORDER: readonly SystemStage[] = [
'startup',
'preUpdate',
'update',
'postUpdate',
'cleanup'
];
/**
* 系统调度元数据
* System scheduling metadata
*/
export interface SystemSchedulingMetadata {
/** 执行阶段 | Execution stage */
stage: SystemStage;
/** 在这些系统之前执行 | Execute before these systems */
before: string[];
/** 在这些系统之后执行 | Execute after these systems */
after: string[];
/** 所属集合 | Sets this system belongs to */
sets: string[];
}
/**
* 默认调度元数据
* Default scheduling metadata
*/
export const DEFAULT_SCHEDULING_METADATA: Readonly<SystemSchedulingMetadata> = {
stage: 'update',
before: [],
after: [],
sets: []
};
/**
* 系统调度器
*
* 管理系统的执行顺序,支持:
* - 阶段分组startup, preUpdate, update, postUpdate, cleanup
* - before/after 依赖声明
* - 拓扑排序和循环检测
* - 与现有 updateOrder 的兼容
*
* System scheduler.
* Manages system execution order, supports:
* - Stage grouping (startup, preUpdate, update, postUpdate, cleanup)
* - before/after dependency declarations
* - Topological sorting and cycle detection
* - Compatibility with existing updateOrder
*/
export class SystemScheduler {
/** 按阶段分组的排序结果 | Sorted results grouped by stage */
private _sortedByStage: Map<SystemStage, EntitySystem[]> = new Map();
/** 是否需要重新构建 | Whether rebuild is needed */
private _dirty: boolean = true;
/** 依赖图 | Dependency graph */
private _graph: SystemDependencyGraph = new SystemDependencyGraph();
/** 是否启用依赖排序 | Whether dependency sorting is enabled */
private _useDependencySort: boolean = true;
/**
* 设置是否使用依赖排序
* Set whether to use dependency sorting
*
* @param enabled 是否启用 | Whether to enable
*/
public setUseDependencySort(enabled: boolean): void {
if (this._useDependencySort !== enabled) {
this._useDependencySort = enabled;
this._dirty = true;
}
}
/**
* 标记需要重新构建
* Mark as needing rebuild
*/
public markDirty(): void {
this._dirty = true;
}
/**
* 获取指定阶段的排序后系统列表
* Get sorted system list for specified stage
*
* @param systems 所有系统 | All systems
* @param stage 阶段 | Stage
* @returns 排序后的系统列表 | Sorted system list
*/
public getSortedSystems(systems: EntitySystem[], stage?: SystemStage): EntitySystem[] {
this.ensureBuilt(systems);
if (stage) {
return this._sortedByStage.get(stage) ?? [];
}
// 返回所有阶段按顺序合并的结果
const result: EntitySystem[] = [];
for (const s of DEFAULT_STAGE_ORDER) {
const stageSystems = this._sortedByStage.get(s);
if (stageSystems) {
result.push(...stageSystems);
}
}
return result;
}
/**
* 获取所有排序后的系统(按默认阶段顺序)
* Get all sorted systems (by default stage order)
*
* @param systems 所有系统 | All systems
* @returns 排序后的系统列表 | Sorted system list
*/
public getAllSortedSystems(systems: EntitySystem[]): EntitySystem[] {
return this.getSortedSystems(systems);
}
/**
* 确保已构建排序结果
* Ensure sorted results are built
*/
private ensureBuilt(systems: EntitySystem[]): void {
if (!this._dirty) return;
this._sortedByStage.clear();
if (!this._useDependencySort || !this.hasDependencies(systems)) {
// 使用简单的 updateOrder 排序
this.buildWithUpdateOrder(systems);
} else {
// 使用依赖图拓扑排序
this.buildWithDependencyGraph(systems);
}
this._dirty = false;
}
/**
* 检查系统是否有依赖声明
* Check if systems have dependency declarations
*/
private hasDependencies(systems: EntitySystem[]): boolean {
for (const sys of systems) {
const meta = this.getSchedulingMetadata(sys);
if (meta.before.length > 0 || meta.after.length > 0 || meta.sets.length > 0) {
return true;
}
// 检查 stage 是否非默认值
if (meta.stage !== 'update') {
return true;
}
}
return false;
}
/**
* 使用 updateOrder 构建排序
* Build sorting with updateOrder
*/
private buildWithUpdateOrder(systems: EntitySystem[]): void {
// 先按 updateOrder 和 addOrder 排序
const sorted = [...systems].sort((a, b) => {
const orderDiff = a.updateOrder - b.updateOrder;
if (orderDiff !== 0) return orderDiff;
return a.addOrder - b.addOrder;
});
// 所有系统放入 update 阶段
this._sortedByStage.set('update', sorted);
}
/**
* 使用依赖图构建排序
* Build sorting with dependency graph
*/
private buildWithDependencyGraph(systems: EntitySystem[]): void {
// 按阶段分组
const byStage = new Map<SystemStage, EntitySystem[]>();
for (const stage of DEFAULT_STAGE_ORDER) {
byStage.set(stage, []);
}
for (const sys of systems) {
const meta = this.getSchedulingMetadata(sys);
const stage = meta.stage;
const stageList = byStage.get(stage);
if (stageList) {
stageList.push(sys);
} else {
// 未知阶段放入 update
byStage.get('update')!.push(sys);
}
}
// 对每个阶段内的系统进行拓扑排序
for (const [stage, stageSystems] of byStage) {
if (stageSystems.length === 0) {
this._sortedByStage.set(stage, []);
continue;
}
const sorted = this.sortSystemsInStage(stageSystems);
this._sortedByStage.set(stage, sorted);
}
}
/**
* 在阶段内对系统进行拓扑排序
* Sort systems within a stage using topological sort
*/
private sortSystemsInStage(systems: EntitySystem[]): EntitySystem[] {
// 构建名称到系统的映射
const nameToSystem = new Map<string, EntitySystem>();
const depInfos: SystemDependencyInfo[] = [];
for (const sys of systems) {
const name = sys.systemName;
nameToSystem.set(name, sys);
const meta = this.getSchedulingMetadata(sys);
depInfos.push({
name,
before: meta.before,
after: meta.after,
sets: meta.sets
});
}
// 构建依赖图并排序
this._graph.buildFromSystems(depInfos);
try {
const sortedNames = this._graph.topologicalSort();
// 将排序结果映射回系统实例
const result: EntitySystem[] = [];
for (const name of sortedNames) {
const sys = nameToSystem.get(name);
if (sys) {
result.push(sys);
}
}
// 对于没有依赖的系统,保持 updateOrder 作为次要排序键
return this.stableSortByUpdateOrder(result);
} catch (error) {
if (error instanceof CycleDependencyError) {
// 重新抛出循环错误,让用户知道
throw error;
}
// 其他错误回退到 updateOrder 排序
console.warn('[SystemScheduler] 拓扑排序失败,回退到 updateOrder 排序', error);
return this.fallbackSort(systems);
}
}
/**
* 稳定排序:在拓扑顺序的基础上,用 updateOrder 作为次要排序键
* Stable sort: use updateOrder as secondary key after topological order
*/
private stableSortByUpdateOrder(systems: EntitySystem[]): EntitySystem[] {
// 拓扑排序已经保证了依赖顺序
// 对于没有直接依赖关系的系统,按 updateOrder 排序
// 这里简单处理:假设拓扑排序已经正确,只需保持结果
return systems;
}
/**
* 回退排序:使用 updateOrder
* Fallback sort: use updateOrder
*/
private fallbackSort(systems: EntitySystem[]): EntitySystem[] {
return [...systems].sort((a, b) => {
const orderDiff = a.updateOrder - b.updateOrder;
if (orderDiff !== 0) return orderDiff;
return a.addOrder - b.addOrder;
});
}
/**
* 获取系统的调度元数据
* Get system scheduling metadata
*/
private getSchedulingMetadata(system: EntitySystem): SystemSchedulingMetadata {
// 使用公共 getter 方法获取调度信息
// Use public getter methods to access scheduling info
return {
stage: system.getStage(),
before: [...system.getBefore()],
after: [...system.getAfter()],
sets: [...system.getSets()]
};
}
}

View File

@@ -0,0 +1,177 @@
/**
* 系统调度装饰器
*
* 提供声明式的系统调度配置,包括执行阶段和依赖关系。
*
* System scheduling decorators.
* Provides declarative system scheduling configuration including execution stage and dependencies.
*
* @example
* ```typescript
* @Stage('update')
* @After('PhysicsSystem')
* @Before('RenderSystem')
* @InSet('GameplaySystems')
* class MovementSystem extends EntitySystem {
* // ...
* }
* ```
*/
import type { SystemStage, SystemSchedulingMetadata } from '../Core/SystemScheduler';
/**
* 调度元数据 Symbol
* Scheduling metadata symbol
*/
export const SCHEDULING_METADATA = Symbol('schedulingMetadata');
/**
* 获取或创建调度元数据
* Get or create scheduling metadata
*/
function getOrCreateMetadata(target: object): SystemSchedulingMetadata {
const proto = target as Record<symbol, SystemSchedulingMetadata | undefined>;
if (!proto[SCHEDULING_METADATA]) {
proto[SCHEDULING_METADATA] = {
stage: 'update',
before: [],
after: [],
sets: []
};
}
return proto[SCHEDULING_METADATA]!;
}
/**
* 设置系统执行阶段
* Set system execution stage
*
* @param stage 执行阶段 | Execution stage
* - 'startup': 只在首帧执行一次 | Execute once on first frame
* - 'preUpdate': 在 update 之前执行 | Execute before update
* - 'update': 主更新阶段(默认)| Main update stage (default)
* - 'postUpdate': 在 update 之后执行 | Execute after update
* - 'cleanup': 帧末清理阶段 | End of frame cleanup stage
*
* @example
* ```typescript
* @Stage('preUpdate')
* class InputSystem extends EntitySystem { }
*
* @Stage('postUpdate')
* class RenderSystem extends EntitySystem { }
* ```
*/
export function Stage(stage: SystemStage): ClassDecorator {
return function (target) {
const metadata = getOrCreateMetadata(target.prototype);
metadata.stage = stage;
return target;
};
}
/**
* 声明此系统在指定系统之前执行
* Declare this system executes before specified systems
*
* @param systems 系统名称列表 | System names
*
* @example
* ```typescript
* @Before('RenderSystem', 'UISystem')
* class TransformSystem extends EntitySystem { }
* ```
*/
export function Before(...systems: string[]): ClassDecorator {
return function (target) {
const metadata = getOrCreateMetadata(target.prototype);
metadata.before.push(...systems);
return target;
};
}
/**
* 声明此系统在指定系统之后执行
* Declare this system executes after specified systems
*
* @param systems 系统名称列表(可使用 'set:' 前缀指定集合)| System names (use 'set:' prefix for sets)
*
* @example
* ```typescript
* @After('PhysicsSystem')
* class CollisionResponseSystem extends EntitySystem { }
*
* // 使用集合
* @After('set:PhysicsSystems')
* class GameLogicSystem extends EntitySystem { }
* ```
*/
export function After(...systems: string[]): ClassDecorator {
return function (target) {
const metadata = getOrCreateMetadata(target.prototype);
metadata.after.push(...systems);
return target;
};
}
/**
* 将系统加入指定集合
* Add system to specified sets
*
* 集合是虚拟分组,可用于批量依赖声明。
* Sets are virtual groups that can be used for batch dependency declarations.
*
* @param sets 集合名称列表 | Set names
*
* @example
* ```typescript
* @InSet('PhysicsSystems')
* class RigidBodySystem extends EntitySystem { }
*
* @InSet('PhysicsSystems')
* class CollisionSystem extends EntitySystem { }
*
* // 其他系统可以依赖整个集合
* @After('set:PhysicsSystems')
* class GameLogicSystem extends EntitySystem { }
* ```
*/
export function InSet(...sets: string[]): ClassDecorator {
return function (target) {
const metadata = getOrCreateMetadata(target.prototype);
metadata.sets.push(...sets);
return target;
};
}
/**
* 获取系统的调度元数据
* Get system scheduling metadata
*
* @param target 系统类或实例 | System class or instance
* @returns 调度元数据或 undefined | Scheduling metadata or undefined
*/
export function getSchedulingMetadata(target: object): SystemSchedulingMetadata | undefined {
// 从原型链查找
let proto = Object.getPrototypeOf(target);
while (proto) {
const metadata = (proto as Record<symbol, SystemSchedulingMetadata | undefined>)[SCHEDULING_METADATA];
if (metadata) {
return metadata;
}
proto = Object.getPrototypeOf(proto);
}
return undefined;
}
/**
* 检查系统是否有调度元数据
* Check if system has scheduling metadata
*
* @param target 系统类或实例 | System class or instance
* @returns 是否有元数据 | Whether has metadata
*/
export function hasSchedulingMetadata(target: object): boolean {
return getSchedulingMetadata(target) !== undefined;
}

View File

@@ -67,3 +67,17 @@ export type {
PropertyAssetType, PropertyAssetType,
EnumOption EnumOption
} from './PropertyDecorator'; } from './PropertyDecorator';
// ============================================================================
// System Scheduling Decorators
// 系统调度装饰器
// ============================================================================
export {
Stage,
Before,
After,
InSet,
getSchedulingMetadata,
hasSchedulingMetadata,
SCHEDULING_METADATA
} from './SystemScheduling';

View File

@@ -6,6 +6,7 @@ import { createLogger } from '../Utils/Logger';
import { getComponentInstanceTypeName, getComponentTypeName } from './Decorators'; import { getComponentInstanceTypeName, getComponentTypeName } from './Decorators';
import { generateGUID } from '../Utils/GUID'; import { generateGUID } from '../Utils/GUID';
import type { IScene } from './IScene'; import type { IScene } from './IScene';
import { EntityHandle, NULL_HANDLE } from './Core/EntityHandle';
/** /**
* 组件活跃状态变化接口 * 组件活跃状态变化接口
@@ -93,6 +94,19 @@ export class Entity {
*/ */
public readonly persistentId: string; public readonly persistentId: string;
/**
* 轻量级实体句柄
*
* 数值类型的实体标识符,包含索引和代数信息。
* 用于高性能场景下替代对象引用,支持 Archetype 存储等优化。
*
* Lightweight entity handle.
* Numeric identifier containing index and generation.
* Used for high-performance scenarios instead of object references,
* supports Archetype storage optimizations.
*/
private _handle: EntityHandle = NULL_HANDLE;
/** /**
* 所属场景引用 * 所属场景引用
*/ */
@@ -171,6 +185,34 @@ export class Entity {
return this._lifecyclePolicy === EEntityLifecyclePolicy.Persistent; return this._lifecyclePolicy === EEntityLifecyclePolicy.Persistent;
} }
/**
* 获取实体句柄
*
* 返回轻量级数值句柄,用于高性能场景。
* 如果实体尚未分配句柄,返回 NULL_HANDLE。
*
* Get entity handle.
* Returns lightweight numeric handle for high-performance scenarios.
* Returns NULL_HANDLE if entity has no handle assigned.
*/
public get handle(): EntityHandle {
return this._handle;
}
/**
* 设置实体句柄(内部使用)
*
* 此方法供 Scene 在创建实体时调用。
*
* Set entity handle (internal use).
* Called by Scene when creating entities.
*
* @internal
*/
public setHandle(handle: EntityHandle): void {
this._handle = handle;
}
/** /**
* 设置实体为持久化(跨场景保留) * 设置实体为持久化(跨场景保留)
* *
@@ -559,6 +601,39 @@ export class Entity {
return component; return component;
} }
/**
* 标记组件为已修改
*
* 便捷方法,自动从场景获取当前 epoch 并标记组件。
* 用于帧级变更检测系统。
*
* Mark component(s) as modified.
* Convenience method that auto-gets epoch from scene and marks components.
* Used for frame-level change detection system.
*
* @param components 要标记的组件 | Components to mark
*
* @example
* ```typescript
* const pos = entity.getComponent(Position)!;
* pos.x = 100;
* entity.markDirty(pos);
*
* // 或者标记多个组件
* entity.markDirty(pos, vel);
* ```
*/
public markDirty(...components: Component[]): void {
if (!this.scene) {
return;
}
const epoch = this.scene.epochManager.current;
for (const component of components) {
component.markDirty(epoch);
}
}
/** /**
* 移除指定的组件 * 移除指定的组件
* *

View File

@@ -5,6 +5,7 @@ import { EntitySystem } from './Systems/EntitySystem';
import { ComponentStorageManager, ComponentType } from './Core/ComponentStorage'; import { ComponentStorageManager, ComponentType } from './Core/ComponentStorage';
import { QuerySystem } from './Core/QuerySystem'; import { QuerySystem } from './Core/QuerySystem';
import { TypeSafeEventSystem } from './Core/EventSystem'; import { TypeSafeEventSystem } from './Core/EventSystem';
import { EpochManager } from './Core/EpochManager';
import type { ReferenceTracker } from './Core/ReferenceTracker'; import type { ReferenceTracker } from './Core/ReferenceTracker';
import type { ServiceContainer, ServiceType } from '../Core/ServiceContainer'; import type { ServiceContainer, ServiceType } from '../Core/ServiceContainer';
import type { TypedQueryBuilder } from './Core/Query/TypedQuery'; import type { TypedQueryBuilder } from './Core/Query/TypedQuery';
@@ -71,6 +72,16 @@ export interface IScene {
*/ */
readonly referenceTracker: ReferenceTracker; readonly referenceTracker: ReferenceTracker;
/**
* Epoch 管理器
*
* 用于帧级变更检测,追踪组件修改。
*
* Epoch manager.
* Used for frame-level change detection, tracking component modifications.
*/
readonly epochManager: EpochManager;
/** /**
* 服务容器 * 服务容器
* *

View File

@@ -27,6 +27,10 @@ import { AutoProfiler } from '../Utils/Profiler/AutoProfiler';
import { ServiceContainer, type ServiceType, type IService } from '../Core/ServiceContainer'; import { ServiceContainer, type ServiceType, type IService } from '../Core/ServiceContainer';
import { createInstance, isInjectable, injectProperties } from '../Core/DI'; import { createInstance, isInjectable, injectProperties } from '../Core/DI';
import { createLogger } from '../Utils/Logger'; import { createLogger } from '../Utils/Logger';
import { SystemScheduler, CycleDependencyError } from './Core/SystemScheduler';
import { EntityHandleManager } from './Core/EntityHandleManager';
import { EntityHandle, isValidHandle } from './Core/EntityHandle';
import { EpochManager } from './Core/EpochManager';
/** /**
* 游戏场景默认实现类 * 游戏场景默认实现类
@@ -92,6 +96,38 @@ export class Scene implements IScene {
*/ */
public readonly referenceTracker: ReferenceTracker; public readonly referenceTracker: ReferenceTracker;
/**
* 实体句柄管理器
*
* 管理轻量级实体句柄的生命周期,包括创建、销毁和状态查询。
* 用于高性能场景下替代对象引用。
*
* Entity handle manager.
* Manages lifecycle of lightweight entity handles.
* Used for high-performance scenarios instead of object references.
*/
public readonly handleManager: EntityHandleManager;
/**
* 句柄到实体的映射
*
* 用于 O(1) 时间复杂度的句柄查找。
*
* Handle to entity mapping.
* Used for O(1) lookup by handle.
*/
private readonly _handleToEntity: Map<EntityHandle, Entity> = new Map();
/**
* Epoch 管理器
*
* 用于帧级变更检测,追踪组件的修改时间。
*
* Epoch manager.
* Used for frame-level change detection, tracking component modification time.
*/
public readonly epochManager: EpochManager = new EpochManager();
/** /**
* 服务容器 * 服务容器
* *
@@ -175,6 +211,18 @@ export class Scene implements IScene {
*/ */
private _systemAddCounter: number = 0; private _systemAddCounter: number = 0;
/**
* 系统调度器
*
* 用于管理系统的执行阶段和依赖关系。
* 支持 before/after 依赖声明和拓扑排序。
*
* System scheduler.
* Manages system execution stages and dependencies.
* Supports before/after dependency declarations and topological sorting.
*/
private readonly _systemScheduler: SystemScheduler = new SystemScheduler();
/** /**
* 组件ID到系统的索引映射 * 组件ID到系统的索引映射
* *
@@ -220,12 +268,34 @@ export class Scene implements IScene {
/** /**
* 重新构建系统缓存 * 重新构建系统缓存
* *
* 从服务容器中提取所有EntitySystem并排序 * 从服务容器中提取所有EntitySystem并排序
* 使用 SystemScheduler 进行依赖排序,支持 before/after 声明。
*
* Rebuild system cache.
* Extract all EntitySystems from service container and sort them.
* Uses SystemScheduler for dependency sorting, supports before/after declarations.
*/ */
private _rebuildSystemsCache(): EntitySystem[] { private _rebuildSystemsCache(): EntitySystem[] {
const allServices = this._services.getAll(); const allServices = this._services.getAll();
const systems = this._filterEntitySystems(allServices); const systems = this._filterEntitySystems(allServices);
return this._sortSystemsByUpdateOrder(systems);
try {
// 使用 SystemScheduler 进行依赖排序
this._systemScheduler.markDirty();
return this._systemScheduler.getAllSortedSystems(systems);
} catch (error) {
if (error instanceof CycleDependencyError) {
// 循环依赖错误,记录警告并回退到 updateOrder 排序
this.logger.error(
`[Scene] 系统存在循环依赖,回退到 updateOrder 排序 | Cycle dependency detected, falling back to updateOrder sort`,
error.involvedNodes
);
} else {
this.logger.error(`[Scene] 系统排序失败 | System sorting failed`, error);
}
// 回退到简单的 updateOrder 排序
return this._sortSystemsByUpdateOrder(systems);
}
} }
/** /**
@@ -302,6 +372,7 @@ export class Scene implements IScene {
this.querySystem = new QuerySystem(); this.querySystem = new QuerySystem();
this.eventSystem = new TypeSafeEventSystem(); this.eventSystem = new TypeSafeEventSystem();
this.referenceTracker = new ReferenceTracker(); this.referenceTracker = new ReferenceTracker();
this.handleManager = new EntityHandleManager();
this._services = new ServiceContainer(); this._services = new ServiceContainer();
this.logger = createLogger('Scene'); this.logger = createLogger('Scene');
@@ -429,12 +500,22 @@ export class Scene implements IScene {
// 清空组件索引 | Clear component indices // 清空组件索引 | Clear component indices
this._componentIdToSystems.clear(); this._componentIdToSystems.clear();
this._globalNotifySystems.clear(); this._globalNotifySystems.clear();
// 清空句柄映射并重置句柄管理器 | Clear handle mapping and reset handle manager
this._handleToEntity.clear();
this.handleManager.reset();
// 重置 epoch 管理器 | Reset epoch manager
this.epochManager.reset();
} }
/** /**
* 更新场景 * 更新场景
*/ */
public update() { public update() {
// 递增帧计数(用于变更检测) | Increment epoch (for change detection)
this.epochManager.increment();
// 开始性能采样帧 // 开始性能采样帧
ProfilerSDK.beginFrame(); ProfilerSDK.beginFrame();
const frameHandle = ProfilerSDK.beginSample('Scene.update', ProfileCategory.ECS); const frameHandle = ProfilerSDK.beginSample('Scene.update', ProfileCategory.ECS);
@@ -549,6 +630,13 @@ export class Scene implements IScene {
public createEntity(name: string) { public createEntity(name: string) {
const entity = new Entity(name, this.identifierPool.checkOut()); const entity = new Entity(name, this.identifierPool.checkOut());
// 分配轻量级句柄 | Assign lightweight handle
const handle = this.handleManager.create();
entity.setHandle(handle);
// 添加到句柄映射 | Add to handle mapping
this._handleToEntity.set(handle, entity);
this.eventSystem.emitSync('entity:created', { entityName: name, entity, scene: this }); this.eventSystem.emitSync('entity:created', { entityName: name, entity, scene: this });
return this.addEntity(entity); return this.addEntity(entity);
@@ -736,6 +824,14 @@ export class Scene implements IScene {
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
const entity = new Entity(`${namePrefix}_${i}`, this.identifierPool.checkOut()); const entity = new Entity(`${namePrefix}_${i}`, this.identifierPool.checkOut());
entity.scene = this; entity.scene = this;
// 分配轻量级句柄 | Assign lightweight handle
const handle = this.handleManager.create();
entity.setHandle(handle);
// 添加到句柄映射 | Add to handle mapping
this._handleToEntity.set(handle, entity);
entities.push(entity); entities.push(entity);
} }
@@ -770,6 +866,12 @@ export class Scene implements IScene {
for (const entity of entities) { for (const entity of entities) {
this.entities.remove(entity); this.entities.remove(entity);
this.querySystem.removeEntity(entity); this.querySystem.removeEntity(entity);
// 销毁句柄并从映射中移除 | Destroy handle and remove from mapping
if (isValidHandle(entity.handle)) {
this._handleToEntity.delete(entity.handle);
this.handleManager.destroy(entity.handle);
}
} }
this.querySystem.clearCache(); this.querySystem.clearCache();
@@ -801,6 +903,26 @@ export class Scene implements IScene {
return this.entities.findEntityById(id); return this.entities.findEntityById(id);
} }
/**
* 根据句柄查找实体
*
* 通过轻量级句柄查找对应的实体O(1) 时间复杂度)。
* 如果句柄无效或实体已被销毁,返回 null。
*
* Find entity by handle (O(1) time complexity).
* Returns null if handle is invalid or entity has been destroyed.
*
* @param handle 实体句柄 | Entity handle
* @returns 实体实例或 null | Entity instance or null
*/
public findEntityByHandle(handle: EntityHandle): Entity | null {
if (!isValidHandle(handle) || !this.handleManager.isAlive(handle)) {
return null;
}
return this._handleToEntity.get(handle) ?? null;
}
/** /**
* 根据标签查找实体 * 根据标签查找实体
* @param tag 实体标签 * @param tag 实体标签

View File

@@ -11,6 +11,8 @@ import type { ComponentConstructor, ComponentInstance } from '../../Types/TypeHe
import type { IService } from '../../Core/ServiceContainer'; import type { IService } from '../../Core/ServiceContainer';
import { EntityCache } from './EntityCache'; import { EntityCache } from './EntityCache';
import { CommandBuffer } from '../Core/CommandBuffer'; import { CommandBuffer } from '../Core/CommandBuffer';
import type { SystemStage, SystemSchedulingMetadata } from '../Core/SystemScheduler';
import { getSchedulingMetadata } from '../Decorators/SystemScheduling';
/** /**
* 事件监听器记录 * 事件监听器记录
@@ -83,6 +85,12 @@ export abstract class EntitySystem implements ISystemBase, IService {
private _destroyed: boolean; private _destroyed: boolean;
protected logger: ReturnType<typeof createLogger>; protected logger: ReturnType<typeof createLogger>;
/**
* 调度元数据
* Scheduling metadata
*/
protected _schedulingMetadata: SystemSchedulingMetadata;
/** /**
* 实体ID映射缓存 * 实体ID映射缓存
*/ */
@@ -118,6 +126,15 @@ export abstract class EntitySystem implements ISystemBase, IService {
*/ */
protected readonly commands: CommandBuffer = new CommandBuffer(); protected readonly commands: CommandBuffer = new CommandBuffer();
/**
* 上次处理的 epoch 检查点
* Last processed epoch checkpoint
*
* 用于变更检测,记录上次 forEachChanged 完成时的 epoch。
* Used for change detection, records epoch when forEachChanged last completed.
*/
private _lastProcessEpoch: number = 0;
/** /**
* 获取系统处理的实体列表 * 获取系统处理的实体列表
*/ */
@@ -205,6 +222,18 @@ export abstract class EntitySystem implements ISystemBase, IService {
this.logger = createLogger(this.getLoggerName()); this.logger = createLogger(this.getLoggerName());
this._entityCache = new EntityCache(); this._entityCache = new EntityCache();
// 初始化调度元数据(优先使用装饰器元数据)
// Initialize scheduling metadata (prefer decorator metadata)
const decoratorMeta = getSchedulingMetadata(this);
this._schedulingMetadata = decoratorMeta
? { ...decoratorMeta }
: {
stage: 'update',
before: [],
after: [],
sets: []
};
} }
/** /**
@@ -257,6 +286,135 @@ export abstract class EntitySystem implements ISystemBase, IService {
this._scene?.markSystemsOrderDirty(); this._scene?.markSystemsOrderDirty();
} }
// ========================================================================
// 调度配置方法 (Fluent API)
// Scheduling configuration methods (Fluent API)
// ========================================================================
/**
* 设置系统执行阶段
* Set system execution stage
*
* @param stage 执行阶段 | Execution stage
* @returns this (用于链式调用 | for chaining)
*
* @example
* ```typescript
* class MySystem extends EntitySystem {
* constructor() {
* super();
* this.stage('preUpdate');
* }
* }
* ```
*/
public stage(stage: SystemStage): this {
this._schedulingMetadata.stage = stage;
this._scene?.markSystemsOrderDirty();
return this;
}
/**
* 声明此系统在指定系统之前执行
* Declare this system executes before specified systems
*
* @param systems 系统名称列表 | System names
* @returns this (用于链式调用 | for chaining)
*
* @example
* ```typescript
* class MySystem extends EntitySystem {
* constructor() {
* super();
* this.before('RenderSystem');
* }
* }
* ```
*/
public before(...systems: string[]): this {
this._schedulingMetadata.before.push(...systems);
this._scene?.markSystemsOrderDirty();
return this;
}
/**
* 声明此系统在指定系统之后执行
* Declare this system executes after specified systems
*
* @param systems 系统名称列表(可使用 'set:' 前缀指定集合)| System names (use 'set:' prefix for sets)
* @returns this (用于链式调用 | for chaining)
*
* @example
* ```typescript
* class MySystem extends EntitySystem {
* constructor() {
* super();
* this.after('PhysicsSystem', 'set:CoreSystems');
* }
* }
* ```
*/
public after(...systems: string[]): this {
this._schedulingMetadata.after.push(...systems);
this._scene?.markSystemsOrderDirty();
return this;
}
/**
* 将系统加入指定集合
* Add system to specified sets
*
* @param sets 集合名称列表 | Set names
* @returns this (用于链式调用 | for chaining)
*
* @example
* ```typescript
* class MySystem extends EntitySystem {
* constructor() {
* super();
* this.inSet('PhysicsSystems');
* }
* }
* ```
*/
public inSet(...sets: string[]): this {
this._schedulingMetadata.sets.push(...sets);
this._scene?.markSystemsOrderDirty();
return this;
}
/**
* 获取系统的执行阶段
* Get system execution stage
*/
public getStage(): SystemStage {
return this._schedulingMetadata.stage;
}
/**
* 获取系统应该在哪些系统之前执行
* Get systems that this system should execute before
*/
public getBefore(): readonly string[] {
return this._schedulingMetadata.before;
}
/**
* 获取系统应该在哪些系统之后执行
* Get systems that this system should execute after
*/
public getAfter(): readonly string[] {
return this._schedulingMetadata.after;
}
/**
* 获取系统所属的集合
* Get sets that this system belongs to
*/
public getSets(): readonly string[] {
return this._schedulingMetadata.sets;
}
/** /**
* 系统初始化(框架调用) * 系统初始化(框架调用)
* *
@@ -1302,4 +1460,167 @@ export abstract class EntitySystem implements ISystemBase, IService {
} }
return true; return true;
} }
// ========================================================================
// 变更检测方法 (Change Detection Methods)
// ========================================================================
/**
* 获取上次处理的 epoch 检查点
* Get the last processed epoch checkpoint
*
* @returns 上次处理完成时的 epoch | Epoch when last processing completed
*/
protected get lastProcessEpoch(): number {
return this._lastProcessEpoch;
}
/**
* 获取当前 epoch
* Get the current epoch
*
* @returns 当前的 epoch 值,如果 scene 不可用则返回 0 | Current epoch value, or 0 if scene unavailable
*/
protected get currentEpoch(): number {
return this._scene?.epochManager?.current ?? 0;
}
/**
* 保存当前 epoch 作为检查点
* Save current epoch as checkpoint
*
* 调用此方法后,下次 forEachChanged 将只处理此时间点之后变更的组件。
* After calling this, next forEachChanged will only process components changed after this point.
*/
protected saveEpoch(): void {
this._lastProcessEpoch = this.currentEpoch;
}
/**
* 遍历有变更组件的实体
* Iterate entities with changed components
*
* 只处理自上次 saveEpoch() 或指定 epoch 以来组件发生变更的实体。
* 处理完成后自动更新 lastProcessEpoch。
*
* Only processes entities whose components changed since last saveEpoch() or specified epoch.
* Automatically updates lastProcessEpoch after processing.
*
* @param entities 实体列表 | Entity list
* @param componentTypes 要检查变更的组件类型 | Component types to check for changes
* @param processor 处理函数 | Processor function
* @param sinceEpoch 可选的起始 epoch默认使用 lastProcessEpoch | Optional starting epoch, defaults to lastProcessEpoch
*
* @example
* ```typescript
* protected process(entities: readonly Entity[]): void {
* // 只处理 Velocity 组件发生变更的实体
* this.forEachChanged(entities, [Velocity], (entity) => {
* const vel = this.requireComponent(entity, Velocity);
* // 只有 velocity 改变时才重新计算
* this.updatePhysics(entity, vel);
* });
* }
* ```
*/
protected forEachChanged<T extends ComponentConstructor[]>(
entities: readonly Entity[],
componentTypes: T,
processor: (entity: Entity, index: number) => void,
sinceEpoch?: number
): void {
const checkEpoch = sinceEpoch ?? this._lastProcessEpoch;
for (let i = 0; i < entities.length; i++) {
const entity = entities[i]!;
let hasChanged = false;
// 检查任意指定组件是否发生变更
// Check if any specified component has changed
for (const componentType of componentTypes) {
const component = entity.getComponent(componentType);
if (component && component.lastWriteEpoch > checkEpoch) {
hasChanged = true;
break;
}
}
if (hasChanged) {
processor(entity, i);
}
}
// 自动更新检查点
// Automatically update checkpoint
this._lastProcessEpoch = this.currentEpoch;
}
/**
* 过滤出有变更组件的实体
* Filter entities with changed components
*
* @param entities 实体列表 | Entity list
* @param componentTypes 要检查变更的组件类型 | Component types to check for changes
* @param sinceEpoch 可选的起始 epoch | Optional starting epoch
* @returns 有变更的实体数组 | Array of entities with changes
*
* @example
* ```typescript
* protected process(entities: readonly Entity[]): void {
* const changedEntities = this.filterChanged(entities, [Position, Velocity]);
* for (const entity of changedEntities) {
* // 处理位置或速度变化的实体
* }
* this.saveEpoch();
* }
* ```
*/
protected filterChanged<T extends ComponentConstructor[]>(
entities: readonly Entity[],
componentTypes: T,
sinceEpoch?: number
): Entity[] {
const checkEpoch = sinceEpoch ?? this._lastProcessEpoch;
const result: Entity[] = [];
for (let i = 0; i < entities.length; i++) {
const entity = entities[i]!;
for (const componentType of componentTypes) {
const component = entity.getComponent(componentType);
if (component && component.lastWriteEpoch > checkEpoch) {
result.push(entity);
break;
}
}
}
return result;
}
/**
* 检查实体的指定组件是否发生变更
* Check if entity's specified components have changed
*
* @param entity 实体 | Entity
* @param componentTypes 要检查的组件类型 | Component types to check
* @param sinceEpoch 可选的起始 epoch | Optional starting epoch
* @returns 是否有变更 | Whether there are changes
*/
protected hasChanged<T extends ComponentConstructor[]>(
entity: Entity,
componentTypes: T,
sinceEpoch?: number
): boolean {
const checkEpoch = sinceEpoch ?? this._lastProcessEpoch;
for (const componentType of componentTypes) {
const component = entity.getComponent(componentType);
if (component && component.lastWriteEpoch > checkEpoch) {
return true;
}
}
return false;
}
} }

View File

@@ -23,3 +23,35 @@ export type { ReactiveQueryChange, ReactiveQueryListener, ReactiveQueryConfig }
export { CommandBuffer, CommandType } from './Core/CommandBuffer'; export { CommandBuffer, CommandType } from './Core/CommandBuffer';
export type { DeferredCommand } from './Core/CommandBuffer'; export type { DeferredCommand } from './Core/CommandBuffer';
export * from './EntityTags'; export * from './EntityTags';
// System Scheduling
export { SystemScheduler, CycleDependencyError, DEFAULT_STAGE_ORDER } from './Core/SystemScheduler';
export type { SystemStage, SystemSchedulingMetadata } from './Core/SystemScheduler';
export { SystemDependencyGraph } from './Core/SystemDependencyGraph';
export type { SystemDependencyInfo } from './Core/SystemDependencyGraph';
// Entity Handle
export {
makeHandle,
indexOf,
genOf,
isValidHandle,
handleEquals,
handleToString,
NULL_HANDLE,
INDEX_BITS,
GEN_BITS,
INDEX_MASK,
GEN_MASK,
MAX_ENTITIES,
MAX_GENERATION
} from './Core/EntityHandle';
export type { EntityHandle } from './Core/EntityHandle';
export { EntityHandleManager } from './Core/EntityHandleManager';
// Change Detection
export { EpochManager } from './Core/EpochManager';
// Compiled Query
export { CompiledQuery } from './Core/Query/CompiledQuery';
export type { InstanceTypes } from './Core/Query/CompiledQuery';

View File

@@ -16,9 +16,10 @@ export type ComponentInstance<T> = T extends new (...args: any[]) => infer R ? R
/** /**
* 组件构造函数类型 * 组件构造函数类型
* *
* 与 ComponentType 保持一致,避免类型转换 * 使用 Component 作为默认类型,与 ComponentType 保持一致
* 这确保类型兼容性,因为所有实际组件都继承自 Component 类。
*/ */
export type ComponentConstructor<T extends IComponent = IComponent> = new (...args: any[]) => T; export type ComponentConstructor<T extends Component = Component> = new (...args: any[]) => T;
/** /**
* 组件类型的通用约束 * 组件类型的通用约束
@@ -237,7 +238,7 @@ export interface TypedQueryCondition<
/** /**
* 组件类型守卫 * 组件类型守卫
*/ */
export function isComponentType<T extends IComponent>( export function isComponentType<T extends Component>(
value: any value: any
): value is ComponentConstructor<T> { ): value is ComponentConstructor<T> {
return typeof value === 'function' && value.prototype instanceof Component; return typeof value === 'function' && value.prototype instanceof Component;

View File

@@ -20,6 +20,22 @@ export interface IComponent {
/** 组件所属的实体ID */ /** 组件所属的实体ID */
entityId: number | null; entityId: number | null;
/**
* 最后写入的 epoch
*
* 用于帧级变更检测,记录组件最后一次被修改时的 epoch。
*
* Last write epoch for frame-level change detection.
*/
readonly lastWriteEpoch: number;
/**
* 标记组件为已修改
*
* Mark component as modified with current epoch.
*/
markDirty(epoch: number): void;
/** 组件添加到实体时的回调 */ /** 组件添加到实体时的回调 */
onAddedToEntity(): void; onAddedToEntity(): void;
/** 组件从实体移除时的回调 */ /** 组件从实体移除时的回调 */

View File

@@ -282,7 +282,9 @@ describe('CommandBuffer', () => {
commandBuffer.addComponent(entity, new MarkerComponent()); commandBuffer.addComponent(entity, new MarkerComponent());
commandBuffer.destroyEntity(entity); commandBuffer.destroyEntity(entity);
expect(commandBuffer.pendingCount).toBe(2); // 由于去重逻辑destroyEntity 会清除同一实体的其他操作
// Due to deduplication, destroyEntity clears other operations for the same entity
expect(commandBuffer.pendingCount).toBe(1);
commandBuffer.clear(); commandBuffer.clear();

View File

@@ -0,0 +1,373 @@
import { CompiledQuery } from '../../../src/ECS/Core/Query/CompiledQuery';
import { Scene } from '../../../src/ECS/Scene';
import { Entity } from '../../../src/ECS/Entity';
import { Component } from '../../../src/ECS/Component';
import { ECSComponent } from '../../../src/ECS/Decorators';
// 测试组件
@ECSComponent('CompiledQuery_Position')
class PositionComponent extends Component {
public x: number;
public y: number;
constructor(...args: unknown[]) {
super();
const [x = 0, y = 0] = args as [number?, number?];
this.x = x;
this.y = y;
}
}
@ECSComponent('CompiledQuery_Velocity')
class VelocityComponent extends Component {
public vx: number;
public vy: number;
constructor(...args: unknown[]) {
super();
const [vx = 0, vy = 0] = args as [number?, number?];
this.vx = vx;
this.vy = vy;
}
}
@ECSComponent('CompiledQuery_Health')
class HealthComponent extends Component {
public current: number;
public max: number;
constructor(...args: unknown[]) {
super();
const [current = 100, max = 100] = args as [number?, number?];
this.current = current;
this.max = max;
}
}
describe('CompiledQuery', () => {
let scene: Scene;
beforeEach(() => {
scene = new Scene();
});
afterEach(() => {
scene.end();
});
describe('创建和基本属性', () => {
it('应该能通过 QuerySystem.compile 创建', () => {
const query = scene.querySystem.compile(PositionComponent);
expect(query).toBeInstanceOf(CompiledQuery);
});
it('应该保存组件类型列表', () => {
const query = scene.querySystem.compile(PositionComponent, VelocityComponent);
expect(query.componentTypes).toContain(PositionComponent);
expect(query.componentTypes).toContain(VelocityComponent);
});
});
describe('entities 属性', () => {
it('初始应该返回空数组', () => {
const query = scene.querySystem.compile(PositionComponent);
expect(query.entities).toHaveLength(0);
});
it('应该返回匹配的实体', () => {
const entity = scene.createEntity('test');
entity.addComponent(new PositionComponent(10, 20));
const query = scene.querySystem.compile(PositionComponent);
expect(query.entities).toHaveLength(1);
expect(query.entities[0]).toBe(entity);
});
it('应该只返回拥有所有组件的实体', () => {
const entity1 = scene.createEntity('entity1');
entity1.addComponent(new PositionComponent());
entity1.addComponent(new VelocityComponent());
const entity2 = scene.createEntity('entity2');
entity2.addComponent(new PositionComponent());
// entity2 没有 VelocityComponent
const query = scene.querySystem.compile(PositionComponent, VelocityComponent);
expect(query.entities).toHaveLength(1);
expect(query.entities[0]).toBe(entity1);
});
});
describe('count 属性', () => {
it('应该返回匹配实体的数量', () => {
const query = scene.querySystem.compile(PositionComponent);
expect(query.count).toBe(0);
const entity = scene.createEntity('test');
entity.addComponent(new PositionComponent());
expect(query.count).toBe(1);
});
});
describe('forEach', () => {
it('应该遍历所有匹配的实体', () => {
const entity1 = scene.createEntity('entity1');
entity1.addComponent(new PositionComponent(10, 20));
const entity2 = scene.createEntity('entity2');
entity2.addComponent(new PositionComponent(30, 40));
const query = scene.querySystem.compile(PositionComponent);
const visited: Entity[] = [];
query.forEach((entity, pos) => {
visited.push(entity);
});
expect(visited).toHaveLength(2);
expect(visited).toContain(entity1);
expect(visited).toContain(entity2);
});
it('应该提供类型安全的组件参数', () => {
const entity = scene.createEntity('test');
entity.addComponent(new PositionComponent(10, 20));
entity.addComponent(new VelocityComponent(1, 2));
const query = scene.querySystem.compile(PositionComponent, VelocityComponent);
query.forEach((entity, pos, vel) => {
expect(pos.x).toBe(10);
expect(pos.y).toBe(20);
expect(vel.vx).toBe(1);
expect(vel.vy).toBe(2);
});
});
});
describe('forEachChanged', () => {
it('应该只遍历变更的实体', () => {
const entity1 = scene.createEntity('entity1');
const pos1 = entity1.addComponent(new PositionComponent(10, 20));
const entity2 = scene.createEntity('entity2');
const pos2 = entity2.addComponent(new PositionComponent(30, 40));
const query = scene.querySystem.compile(PositionComponent);
// 获取当前 epoch
const epoch = scene.epochManager.current;
// 递增 epoch
scene.epochManager.increment();
// 只标记 entity1 的组件为已修改
pos1.markDirty(scene.epochManager.current);
const changed: Entity[] = [];
query.forEachChanged(epoch, (entity, pos) => {
changed.push(entity);
});
expect(changed).toHaveLength(1);
expect(changed[0]).toBe(entity1);
});
it('当所有组件都未变更时应该不遍历任何实体', () => {
const entity = scene.createEntity('test');
entity.addComponent(new PositionComponent(10, 20));
const query = scene.querySystem.compile(PositionComponent);
// 使用当前 epoch 检查 - 组件的 epoch 应该小于等于当前
const futureEpoch = scene.epochManager.current + 100;
const changed: Entity[] = [];
query.forEachChanged(futureEpoch, (entity, pos) => {
changed.push(entity);
});
expect(changed).toHaveLength(0);
});
});
describe('first', () => {
it('应该返回第一个匹配的实体和组件', () => {
const entity = scene.createEntity('test');
entity.addComponent(new PositionComponent(10, 20));
const query = scene.querySystem.compile(PositionComponent);
const result = query.first();
expect(result).not.toBeNull();
expect(result![0]).toBe(entity);
expect(result![1].x).toBe(10);
expect(result![1].y).toBe(20);
});
it('没有匹配实体时应该返回 null', () => {
const query = scene.querySystem.compile(PositionComponent);
const result = query.first();
expect(result).toBeNull();
});
});
describe('toArray', () => {
it('应该返回实体和组件的数组', () => {
const entity1 = scene.createEntity('entity1');
entity1.addComponent(new PositionComponent(10, 20));
const entity2 = scene.createEntity('entity2');
entity2.addComponent(new PositionComponent(30, 40));
const query = scene.querySystem.compile(PositionComponent);
const result = query.toArray();
expect(result).toHaveLength(2);
expect(result[0]![0]).toBe(entity1);
expect(result[0]![1].x).toBe(10);
});
});
describe('map', () => {
it('应该映射转换实体数据', () => {
const entity1 = scene.createEntity('entity1');
entity1.addComponent(new PositionComponent(10, 20));
const entity2 = scene.createEntity('entity2');
entity2.addComponent(new PositionComponent(30, 40));
const query = scene.querySystem.compile(PositionComponent);
const result = query.map((entity, pos) => pos.x + pos.y);
expect(result).toHaveLength(2);
expect(result).toContain(30); // 10 + 20
expect(result).toContain(70); // 30 + 40
});
});
describe('filter', () => {
it('应该过滤实体', () => {
const entity1 = scene.createEntity('entity1');
entity1.addComponent(new PositionComponent(10, 20));
const entity2 = scene.createEntity('entity2');
entity2.addComponent(new PositionComponent(30, 40));
const query = scene.querySystem.compile(PositionComponent);
const result = query.filter((entity, pos) => pos.x > 20);
expect(result).toHaveLength(1);
expect(result[0]).toBe(entity2);
});
});
describe('find', () => {
it('应该找到第一个满足条件的实体', () => {
const entity1 = scene.createEntity('entity1');
entity1.addComponent(new PositionComponent(10, 20));
const entity2 = scene.createEntity('entity2');
entity2.addComponent(new PositionComponent(30, 40));
const query = scene.querySystem.compile(PositionComponent);
const result = query.find((entity, pos) => pos.x > 20);
expect(result).toBe(entity2);
});
it('找不到时应该返回 undefined', () => {
const entity = scene.createEntity('test');
entity.addComponent(new PositionComponent(10, 20));
const query = scene.querySystem.compile(PositionComponent);
const result = query.find((entity, pos) => pos.x > 100);
expect(result).toBeUndefined();
});
});
describe('any', () => {
it('有匹配实体时应该返回 true', () => {
const entity = scene.createEntity('test');
entity.addComponent(new PositionComponent());
const query = scene.querySystem.compile(PositionComponent);
expect(query.any()).toBe(true);
});
it('没有匹配实体时应该返回 false', () => {
const query = scene.querySystem.compile(PositionComponent);
expect(query.any()).toBe(false);
});
});
describe('empty', () => {
it('没有匹配实体时应该返回 true', () => {
const query = scene.querySystem.compile(PositionComponent);
expect(query.empty()).toBe(true);
});
it('有匹配实体时应该返回 false', () => {
const entity = scene.createEntity('test');
entity.addComponent(new PositionComponent());
const query = scene.querySystem.compile(PositionComponent);
expect(query.empty()).toBe(false);
});
});
describe('缓存机制', () => {
it('应该缓存查询结果', () => {
const entity = scene.createEntity('test');
entity.addComponent(new PositionComponent());
const query = scene.querySystem.compile(PositionComponent);
// 第一次访问
const entities1 = query.entities;
// 第二次访问
const entities2 = query.entities;
// 应该返回相同的缓存数组
expect(entities1).toBe(entities2);
});
it('当实体变化时应该刷新缓存', () => {
const query = scene.querySystem.compile(PositionComponent);
// 初始为空
expect(query.count).toBe(0);
// 添加实体
const entity = scene.createEntity('test');
entity.addComponent(new PositionComponent());
// 应该检测到变化
expect(query.count).toBe(1);
});
});
describe('多组件查询', () => {
it('应该支持多组件查询', () => {
const entity = scene.createEntity('test');
entity.addComponent(new PositionComponent(10, 20));
entity.addComponent(new VelocityComponent(1, 2));
entity.addComponent(new HealthComponent(80, 100));
const query = scene.querySystem.compile(
PositionComponent,
VelocityComponent,
HealthComponent
);
query.forEach((entity, pos, vel, health) => {
expect(pos.x).toBe(10);
expect(vel.vx).toBe(1);
expect(health.current).toBe(80);
});
});
});
});

View File

@@ -0,0 +1,380 @@
import {
makeHandle,
indexOf,
genOf,
isValidHandle,
handleEquals,
handleToString,
NULL_HANDLE,
INDEX_BITS,
GEN_BITS,
INDEX_MASK,
GEN_MASK,
MAX_ENTITIES,
MAX_GENERATION,
EntityHandle
} from '../../../src/ECS/Core/EntityHandle';
import { EntityHandleManager } from '../../../src/ECS/Core/EntityHandleManager';
describe('EntityHandle', () => {
describe('常量定义', () => {
it('INDEX_BITS 应该是 28', () => {
expect(INDEX_BITS).toBe(28);
});
it('GEN_BITS 应该是 20', () => {
expect(GEN_BITS).toBe(20);
});
it('INDEX_MASK 应该正确', () => {
expect(INDEX_MASK).toBe((1 << INDEX_BITS) - 1);
});
it('GEN_MASK 应该正确', () => {
expect(GEN_MASK).toBe((1 << GEN_BITS) - 1);
});
it('MAX_ENTITIES 应该是 2^28', () => {
expect(MAX_ENTITIES).toBe(1 << INDEX_BITS);
});
it('MAX_GENERATION 应该是 2^20', () => {
expect(MAX_GENERATION).toBe(1 << GEN_BITS);
});
it('NULL_HANDLE 应该是 0', () => {
expect(NULL_HANDLE).toBe(0);
});
});
describe('makeHandle', () => {
it('应该正确组合 index 和 generation', () => {
const handle = makeHandle(100, 5);
expect(indexOf(handle)).toBe(100);
expect(genOf(handle)).toBe(5);
});
it('应该处理边界值', () => {
// 最小值
const minHandle = makeHandle(0, 0);
expect(indexOf(minHandle)).toBe(0);
expect(genOf(minHandle)).toBe(0);
// 最大 index
const maxIndexHandle = makeHandle(INDEX_MASK, 0);
expect(indexOf(maxIndexHandle)).toBe(INDEX_MASK);
// 最大 generation
const maxGenHandle = makeHandle(0, GEN_MASK);
expect(genOf(maxGenHandle)).toBe(GEN_MASK);
});
it('应该正确处理大数值', () => {
const largeIndex = 1000000;
const largeGen = 500;
const handle = makeHandle(largeIndex, largeGen);
expect(indexOf(handle)).toBe(largeIndex);
expect(genOf(handle)).toBe(largeGen);
});
});
describe('indexOf', () => {
it('应该正确提取 index', () => {
const handle = makeHandle(12345, 67);
expect(indexOf(handle)).toBe(12345);
});
it('应该对 NULL_HANDLE 返回 0', () => {
expect(indexOf(NULL_HANDLE)).toBe(0);
});
});
describe('genOf', () => {
it('应该正确提取 generation', () => {
const handle = makeHandle(12345, 67);
expect(genOf(handle)).toBe(67);
});
it('应该对 NULL_HANDLE 返回 0', () => {
expect(genOf(NULL_HANDLE)).toBe(0);
});
});
describe('isValidHandle', () => {
it('应该判断 NULL_HANDLE 为无效', () => {
expect(isValidHandle(NULL_HANDLE)).toBe(false);
});
it('应该判断非零句柄为有效', () => {
const handle = makeHandle(1, 0);
expect(isValidHandle(handle)).toBe(true);
});
it('应该判断带 generation 的句柄为有效', () => {
const handle = makeHandle(0, 1);
expect(isValidHandle(handle)).toBe(true);
});
});
describe('handleEquals', () => {
it('应该正确比较相同句柄', () => {
const handle1 = makeHandle(100, 5);
const handle2 = makeHandle(100, 5);
expect(handleEquals(handle1, handle2)).toBe(true);
});
it('应该正确比较不同句柄', () => {
const handle1 = makeHandle(100, 5);
const handle2 = makeHandle(100, 6);
expect(handleEquals(handle1, handle2)).toBe(false);
});
it('应该区分 index 不同的句柄', () => {
const handle1 = makeHandle(100, 5);
const handle2 = makeHandle(101, 5);
expect(handleEquals(handle1, handle2)).toBe(false);
});
});
describe('handleToString', () => {
it('应该返回可读的字符串格式', () => {
const handle = makeHandle(100, 5);
const str = handleToString(handle);
expect(str).toContain('100');
expect(str).toContain('5');
});
it('应该对 NULL_HANDLE 返回特殊标记', () => {
const str = handleToString(NULL_HANDLE);
expect(str.toLowerCase()).toContain('null');
});
});
});
describe('EntityHandleManager', () => {
let manager: EntityHandleManager;
beforeEach(() => {
manager = new EntityHandleManager();
});
describe('create', () => {
it('应该创建有效的句柄', () => {
const handle = manager.create();
expect(isValidHandle(handle)).toBe(true);
});
it('应该创建不同的句柄', () => {
const handle1 = manager.create();
const handle2 = manager.create();
expect(handleEquals(handle1, handle2)).toBe(false);
});
it('应该递增 index', () => {
const handle1 = manager.create();
const handle2 = manager.create();
expect(indexOf(handle2)).toBe(indexOf(handle1) + 1);
});
it('创建的句柄应该是存活的', () => {
const handle = manager.create();
expect(manager.isAlive(handle)).toBe(true);
});
it('创建的句柄默认应该是启用的', () => {
const handle = manager.create();
expect(manager.isEnabled(handle)).toBe(true);
});
});
describe('destroy', () => {
it('应该销毁句柄', () => {
const handle = manager.create();
expect(manager.isAlive(handle)).toBe(true);
manager.destroy(handle);
expect(manager.isAlive(handle)).toBe(false);
});
it('销毁后使用相同 index 应该增加 generation', () => {
const handle1 = manager.create();
const index1 = indexOf(handle1);
manager.destroy(handle1);
const handle2 = manager.create();
const index2 = indexOf(handle2);
// 应该复用同一个 index
expect(index2).toBe(index1);
// 但 generation 应该不同
expect(genOf(handle2)).toBe(genOf(handle1) + 1);
});
it('销毁已销毁的句柄不应该报错', () => {
const handle = manager.create();
manager.destroy(handle);
expect(() => {
manager.destroy(handle);
}).not.toThrow();
});
it('销毁 NULL_HANDLE 不应该报错', () => {
expect(() => {
manager.destroy(NULL_HANDLE);
}).not.toThrow();
});
});
describe('isAlive', () => {
it('应该对存活句柄返回 true', () => {
const handle = manager.create();
expect(manager.isAlive(handle)).toBe(true);
});
it('应该对已销毁句柄返回 false', () => {
const handle = manager.create();
manager.destroy(handle);
expect(manager.isAlive(handle)).toBe(false);
});
it('应该对 NULL_HANDLE 返回 false', () => {
expect(manager.isAlive(NULL_HANDLE)).toBe(false);
});
it('应该对过期 generation 返回 false', () => {
const handle1 = manager.create();
manager.destroy(handle1);
const handle2 = manager.create();
// handle1 的 generation 已过期
expect(manager.isAlive(handle1)).toBe(false);
expect(manager.isAlive(handle2)).toBe(true);
});
});
describe('isEnabled/setEnabled', () => {
it('新创建的句柄默认启用', () => {
const handle = manager.create();
expect(manager.isEnabled(handle)).toBe(true);
});
it('应该能够禁用句柄', () => {
const handle = manager.create();
manager.setEnabled(handle, false);
expect(manager.isEnabled(handle)).toBe(false);
});
it('应该能够重新启用句柄', () => {
const handle = manager.create();
manager.setEnabled(handle, false);
manager.setEnabled(handle, true);
expect(manager.isEnabled(handle)).toBe(true);
});
it('对已销毁句柄设置启用状态不应该报错', () => {
const handle = manager.create();
manager.destroy(handle);
expect(() => {
manager.setEnabled(handle, true);
}).not.toThrow();
});
it('已销毁句柄应该返回未启用', () => {
const handle = manager.create();
manager.destroy(handle);
expect(manager.isEnabled(handle)).toBe(false);
});
});
describe('aliveCount', () => {
it('初始时应该为 0', () => {
expect(manager.aliveCount).toBe(0);
});
it('创建句柄后应该增加', () => {
manager.create();
expect(manager.aliveCount).toBe(1);
manager.create();
expect(manager.aliveCount).toBe(2);
});
it('销毁句柄后应该减少', () => {
const handle1 = manager.create();
const handle2 = manager.create();
expect(manager.aliveCount).toBe(2);
manager.destroy(handle1);
expect(manager.aliveCount).toBe(1);
manager.destroy(handle2);
expect(manager.aliveCount).toBe(0);
});
});
describe('reset', () => {
it('应该重置所有状态', () => {
const handle1 = manager.create();
const handle2 = manager.create();
manager.destroy(handle1);
manager.reset();
expect(manager.aliveCount).toBe(0);
expect(manager.isAlive(handle2)).toBe(false);
});
it('重置后应该能重新创建句柄', () => {
manager.create();
manager.create();
manager.reset();
const newHandle = manager.create();
expect(isValidHandle(newHandle)).toBe(true);
expect(manager.isAlive(newHandle)).toBe(true);
});
});
describe('大规模测试', () => {
it('应该能处理大量句柄', () => {
const handles: EntityHandle[] = [];
const count = 10000;
// 创建大量句柄
for (let i = 0; i < count; i++) {
handles.push(manager.create());
}
expect(manager.aliveCount).toBe(count);
// 验证所有句柄都是存活的
for (const handle of handles) {
expect(manager.isAlive(handle)).toBe(true);
}
// 销毁一半
for (let i = 0; i < count / 2; i++) {
manager.destroy(handles[i]!);
}
expect(manager.aliveCount).toBe(count / 2);
});
it('应该正确复用已销毁的 index', () => {
// 创建并销毁
const handle1 = manager.create();
manager.destroy(handle1);
// 再次创建
const handle2 = manager.create();
// 应该复用 index
expect(indexOf(handle2)).toBe(indexOf(handle1));
// 但 generation 增加
expect(genOf(handle2)).toBe(genOf(handle1) + 1);
});
});
});

View File

@@ -0,0 +1,72 @@
import { EpochManager } from '../../../src/ECS/Core/EpochManager';
describe('EpochManager', () => {
let epochManager: EpochManager;
beforeEach(() => {
epochManager = new EpochManager();
});
describe('初始状态', () => {
it('初始 epoch 应该是 1', () => {
expect(epochManager.current).toBe(1);
});
});
describe('increment', () => {
it('应该递增 epoch', () => {
const initial = epochManager.current;
epochManager.increment();
expect(epochManager.current).toBe(initial + 1);
});
it('应该正确递增多次', () => {
const initial = epochManager.current;
epochManager.increment();
epochManager.increment();
epochManager.increment();
expect(epochManager.current).toBe(initial + 3);
});
});
describe('reset', () => {
it('应该重置 epoch 到 1', () => {
epochManager.increment();
epochManager.increment();
expect(epochManager.current).toBeGreaterThan(1);
epochManager.reset();
expect(epochManager.current).toBe(1);
});
});
describe('current getter', () => {
it('应该返回当前 epoch', () => {
expect(epochManager.current).toBe(1);
epochManager.increment();
expect(epochManager.current).toBe(2);
});
});
describe('使用场景', () => {
it('可以用于追踪帧数', () => {
// 模拟 10 帧
for (let i = 0; i < 10; i++) {
epochManager.increment();
}
expect(epochManager.current).toBe(11); // 初始 1 + 10 帧
});
it('可以用于变更检测', () => {
// 保存检查点
const checkpoint = epochManager.current;
// 模拟几帧
epochManager.increment();
epochManager.increment();
// 当前 epoch 应该大于检查点
expect(epochManager.current).toBeGreaterThan(checkpoint);
});
});
});

View File

@@ -0,0 +1,555 @@
import {
SystemScheduler,
CycleDependencyError,
DEFAULT_STAGE_ORDER,
SystemStage
} from '../../../src/ECS/Core/SystemScheduler';
import { SystemDependencyGraph } from '../../../src/ECS/Core/SystemDependencyGraph';
import { EntitySystem } from '../../../src/ECS/Systems/EntitySystem';
import { Scene } from '../../../src/ECS/Scene';
import { Matcher } from '../../../src/ECS/Utils/Matcher';
import { Entity } from '../../../src/ECS/Entity';
import { ECSSystem, Stage, Before, After, InSet } from '../../../src/ECS/Decorators';
// 测试系统
@ECSSystem('TestSystemA')
class TestSystemA extends EntitySystem {
public executionOrder: number = 0;
public static executionCounter = 0;
constructor() {
super(Matcher.nothing());
}
protected override process(entities: readonly Entity[]): void {
this.executionOrder = ++TestSystemA.executionCounter;
}
}
@ECSSystem('TestSystemB')
class TestSystemB extends EntitySystem {
public executionOrder: number = 0;
constructor() {
super(Matcher.nothing());
}
protected override process(entities: readonly Entity[]): void {
this.executionOrder = ++TestSystemA.executionCounter;
}
}
@ECSSystem('TestSystemC')
class TestSystemC extends EntitySystem {
public executionOrder: number = 0;
constructor() {
super(Matcher.nothing());
}
protected override process(entities: readonly Entity[]): void {
this.executionOrder = ++TestSystemA.executionCounter;
}
}
describe('SystemDependencyGraph', () => {
let graph: SystemDependencyGraph;
beforeEach(() => {
graph = new SystemDependencyGraph();
});
describe('addSystemNode', () => {
it('应该添加节点', () => {
graph.addSystemNode('SystemA');
expect(graph.size).toBe(1);
});
it('应该支持添加多个节点', () => {
graph.addSystemNode('SystemA');
graph.addSystemNode('SystemB');
expect(graph.size).toBe(2);
});
});
describe('addSetNode', () => {
it('应该添加虚拟集合节点', () => {
graph.addSetNode('CoreSystems');
expect(graph.size).toBe(1);
});
});
describe('addEdge', () => {
it('应该添加边', () => {
graph.addSystemNode('SystemA');
graph.addSystemNode('SystemB');
graph.addEdge('SystemA', 'SystemB');
// 拓扑排序后 A 应该在 B 之前
const sorted = graph.topologicalSort();
expect(sorted.indexOf('SystemA')).toBeLessThan(sorted.indexOf('SystemB'));
});
it('应该忽略自引用边', () => {
graph.addSystemNode('SystemA');
graph.addEdge('SystemA', 'SystemA');
// 不应该产生循环
const sorted = graph.topologicalSort();
expect(sorted).toContain('SystemA');
});
});
describe('topologicalSort', () => {
it('应该返回正确的拓扑顺序', () => {
graph.addSystemNode('SystemA');
graph.addSystemNode('SystemB');
graph.addSystemNode('SystemC');
// A -> B -> C
graph.addEdge('SystemA', 'SystemB');
graph.addEdge('SystemB', 'SystemC');
const sorted = graph.topologicalSort();
expect(sorted.indexOf('SystemA')).toBeLessThan(sorted.indexOf('SystemB'));
expect(sorted.indexOf('SystemB')).toBeLessThan(sorted.indexOf('SystemC'));
});
it('应该检测循环依赖', () => {
graph.addSystemNode('SystemA');
graph.addSystemNode('SystemB');
// A -> B -> A (循环)
graph.addEdge('SystemA', 'SystemB');
graph.addEdge('SystemB', 'SystemA');
expect(() => {
graph.topologicalSort();
}).toThrow(CycleDependencyError);
});
it('应该检测多节点循环依赖', () => {
graph.addSystemNode('SystemA');
graph.addSystemNode('SystemB');
graph.addSystemNode('SystemC');
// A -> B -> C -> A (循环)
graph.addEdge('SystemA', 'SystemB');
graph.addEdge('SystemB', 'SystemC');
graph.addEdge('SystemC', 'SystemA');
expect(() => {
graph.topologicalSort();
}).toThrow(CycleDependencyError);
});
it('应该处理没有依赖的系统', () => {
graph.addSystemNode('SystemA');
graph.addSystemNode('SystemB');
// 没有边,两个独立系统
const sorted = graph.topologicalSort();
expect(sorted).toHaveLength(2);
expect(sorted).toContain('SystemA');
expect(sorted).toContain('SystemB');
});
it('不应该包含虚拟节点在结果中', () => {
graph.addSystemNode('SystemA');
graph.addSetNode('CoreSystems');
const sorted = graph.topologicalSort();
expect(sorted).toContain('SystemA');
expect(sorted).not.toContain('set:CoreSystems');
});
});
describe('buildFromSystems', () => {
it('应该从系统依赖信息构建图', () => {
graph.buildFromSystems([
{ name: 'SystemA', before: ['SystemB'], after: [], sets: [] },
{ name: 'SystemB', before: [], after: [], sets: [] }
]);
const sorted = graph.topologicalSort();
expect(sorted.indexOf('SystemA')).toBeLessThan(sorted.indexOf('SystemB'));
});
it('应该处理 after 依赖', () => {
graph.buildFromSystems([
{ name: 'SystemA', before: [], after: [], sets: [] },
{ name: 'SystemB', before: [], after: ['SystemA'], sets: [] }
]);
const sorted = graph.topologicalSort();
expect(sorted.indexOf('SystemA')).toBeLessThan(sorted.indexOf('SystemB'));
});
it('应该处理 set 依赖', () => {
graph.buildFromSystems([
{ name: 'SystemA', before: [], after: [], sets: ['CoreSystems'] },
{ name: 'SystemB', before: [], after: [], sets: ['CoreSystems'] }
]);
const sorted = graph.topologicalSort();
expect(sorted).toHaveLength(2);
expect(sorted).toContain('SystemA');
expect(sorted).toContain('SystemB');
});
});
describe('clear', () => {
it('应该清除所有节点和边', () => {
graph.addSystemNode('SystemA');
graph.addSystemNode('SystemB');
graph.addEdge('SystemA', 'SystemB');
graph.clear();
expect(graph.size).toBe(0);
});
});
});
describe('SystemScheduler', () => {
let scheduler: SystemScheduler;
beforeEach(() => {
scheduler = new SystemScheduler();
TestSystemA.executionCounter = 0;
});
describe('DEFAULT_STAGE_ORDER', () => {
it('应该包含所有阶段', () => {
expect(DEFAULT_STAGE_ORDER).toContain('startup');
expect(DEFAULT_STAGE_ORDER).toContain('preUpdate');
expect(DEFAULT_STAGE_ORDER).toContain('update');
expect(DEFAULT_STAGE_ORDER).toContain('postUpdate');
expect(DEFAULT_STAGE_ORDER).toContain('cleanup');
});
it('阶段顺序应该正确', () => {
expect(DEFAULT_STAGE_ORDER.indexOf('startup')).toBeLessThan(
DEFAULT_STAGE_ORDER.indexOf('preUpdate')
);
expect(DEFAULT_STAGE_ORDER.indexOf('preUpdate')).toBeLessThan(
DEFAULT_STAGE_ORDER.indexOf('update')
);
expect(DEFAULT_STAGE_ORDER.indexOf('update')).toBeLessThan(
DEFAULT_STAGE_ORDER.indexOf('postUpdate')
);
expect(DEFAULT_STAGE_ORDER.indexOf('postUpdate')).toBeLessThan(
DEFAULT_STAGE_ORDER.indexOf('cleanup')
);
});
});
describe('getSortedSystems', () => {
it('应该返回排序后的系统', () => {
const systemA = new TestSystemA();
const systemB = new TestSystemB();
const systems = scheduler.getSortedSystems([systemA, systemB], 'update');
expect(systems.length).toBeGreaterThanOrEqual(2);
});
it('应该按 updateOrder 排序', () => {
const systemA = new TestSystemA();
const systemB = new TestSystemB();
systemA.updateOrder = 10;
systemB.updateOrder = 5;
const systems = scheduler.getSortedSystems([systemA, systemB], 'update');
// B 的 updateOrder 更小,应该在 A 之前
expect(systems.indexOf(systemB)).toBeLessThan(systems.indexOf(systemA));
});
it('应该按依赖关系排序', () => {
// 创建带有依赖关系的系统
@ECSSystem('DepSystemA')
@Stage('update')
class DepSystemA extends EntitySystem {
constructor() {
super(Matcher.nothing());
}
}
@ECSSystem('DepSystemB')
@Stage('update')
@After('DepSystemA')
class DepSystemB extends EntitySystem {
constructor() {
super(Matcher.nothing());
}
}
const systemA = new DepSystemA();
const systemB = new DepSystemB();
const systems = scheduler.getSortedSystems([systemA, systemB], 'update');
const indexA = systems.indexOf(systemA);
const indexB = systems.indexOf(systemB);
expect(indexA).toBeLessThan(indexB);
});
it('应该返回指定阶段的系统', () => {
@ECSSystem('PreUpdateSystem')
@Stage('preUpdate')
class PreUpdateSystem extends EntitySystem {
constructor() {
super(Matcher.nothing());
}
}
@ECSSystem('UpdateSystem')
@Stage('update')
class UpdateSystem extends EntitySystem {
constructor() {
super(Matcher.nothing());
}
}
const preSystem = new PreUpdateSystem();
const updateSystem = new UpdateSystem();
const preSystems = scheduler.getSortedSystems([preSystem, updateSystem], 'preUpdate');
const updateSystems = scheduler.getSortedSystems([preSystem, updateSystem], 'update');
expect(preSystems).toContain(preSystem);
expect(preSystems).not.toContain(updateSystem);
expect(updateSystems).toContain(updateSystem);
expect(updateSystems).not.toContain(preSystem);
});
});
describe('getAllSortedSystems', () => {
it('应该返回所有阶段的系统', () => {
@ECSSystem('AllPreSystem')
@Stage('preUpdate')
class AllPreSystem extends EntitySystem {
constructor() {
super(Matcher.nothing());
}
}
@ECSSystem('AllUpdateSystem')
@Stage('update')
class AllUpdateSystem extends EntitySystem {
constructor() {
super(Matcher.nothing());
}
}
const preSystem = new AllPreSystem();
const updateSystem = new AllUpdateSystem();
const allSystems = scheduler.getAllSortedSystems([preSystem, updateSystem]);
expect(allSystems).toContain(preSystem);
expect(allSystems).toContain(updateSystem);
// preUpdate 阶段在 update 之前
expect(allSystems.indexOf(preSystem)).toBeLessThan(allSystems.indexOf(updateSystem));
});
});
describe('markDirty', () => {
it('调用 markDirty 后应该重新排序', () => {
const systemA = new TestSystemA();
const systemB = new TestSystemB();
// 第一次排序
scheduler.getSortedSystems([systemA, systemB], 'update');
// 标记脏
scheduler.markDirty();
// 应该重新排序而不出错
const systems = scheduler.getSortedSystems([systemA, systemB], 'update');
expect(systems).toHaveLength(2);
});
});
describe('setUseDependencySort', () => {
it('禁用依赖排序后应该只使用 updateOrder', () => {
@ECSSystem('DisabledDepA')
@Stage('update')
class DisabledDepA extends EntitySystem {
constructor() {
super(Matcher.nothing());
}
}
@ECSSystem('DisabledDepB')
@Stage('update')
@Before('DisabledDepA')
class DisabledDepB extends EntitySystem {
constructor() {
super(Matcher.nothing());
}
}
const systemA = new DisabledDepA();
const systemB = new DisabledDepB();
systemA.updateOrder = 1;
systemB.updateOrder = 2;
scheduler.setUseDependencySort(false);
const systems = scheduler.getSortedSystems([systemA, systemB], 'update');
// 禁用依赖排序后,按 updateOrder 排序
expect(systems.indexOf(systemA)).toBeLessThan(systems.indexOf(systemB));
});
});
});
describe('调度装饰器', () => {
describe('@Stage', () => {
it('应该设置系统的执行阶段', () => {
@ECSSystem('StageTestSystem')
@Stage('postUpdate')
class StageTestSystem extends EntitySystem {
constructor() {
super(Matcher.nothing());
}
}
const system = new StageTestSystem();
expect(system.getStage()).toBe('postUpdate');
});
});
describe('@Before', () => {
it('应该设置系统的前置依赖', () => {
@ECSSystem('BeforeTestSystem')
@Before('OtherSystem')
class BeforeTestSystem extends EntitySystem {
constructor() {
super(Matcher.nothing());
}
}
const system = new BeforeTestSystem();
expect(system.getBefore()).toContain('OtherSystem');
});
it('应该支持多个前置依赖', () => {
@ECSSystem('MultiBeforeSystem')
@Before('SystemA', 'SystemB')
class MultiBeforeSystem extends EntitySystem {
constructor() {
super(Matcher.nothing());
}
}
const system = new MultiBeforeSystem();
expect(system.getBefore()).toContain('SystemA');
expect(system.getBefore()).toContain('SystemB');
});
});
describe('@After', () => {
it('应该设置系统的后置依赖', () => {
@ECSSystem('AfterTestSystem')
@After('OtherSystem')
class AfterTestSystem extends EntitySystem {
constructor() {
super(Matcher.nothing());
}
}
const system = new AfterTestSystem();
expect(system.getAfter()).toContain('OtherSystem');
});
});
describe('@InSet', () => {
it('应该设置系统所属的集合', () => {
@ECSSystem('InSetTestSystem')
@InSet('CoreSystems')
class InSetTestSystem extends EntitySystem {
constructor() {
super(Matcher.nothing());
}
}
const system = new InSetTestSystem();
expect(system.getSets()).toContain('CoreSystems');
});
});
describe('组合使用', () => {
it('应该支持组合多个装饰器', () => {
@ECSSystem('CombinedSystem')
@Stage('update')
@After('InputSystem')
@Before('RenderSystem')
@InSet('CoreSystems')
class CombinedSystem extends EntitySystem {
constructor() {
super(Matcher.nothing());
}
}
const system = new CombinedSystem();
expect(system.getStage()).toBe('update');
expect(system.getAfter()).toContain('InputSystem');
expect(system.getBefore()).toContain('RenderSystem');
expect(system.getSets()).toContain('CoreSystems');
});
});
});
describe('Fluent API 调度配置', () => {
it('应该支持 stage() 方法', () => {
@ECSSystem('FluentStageSystem')
class FluentStageSystem extends EntitySystem {
constructor() {
super(Matcher.nothing());
this.stage('postUpdate');
}
}
const system = new FluentStageSystem();
expect(system.getStage()).toBe('postUpdate');
});
it('应该支持链式调用', () => {
@ECSSystem('FluentChainSystem')
class FluentChainSystem extends EntitySystem {
constructor() {
super(Matcher.nothing());
this.stage('update')
.after('SystemA')
.before('SystemB')
.inSet('CoreSystems');
}
}
const system = new FluentChainSystem();
expect(system.getStage()).toBe('update');
expect(system.getAfter()).toContain('SystemA');
expect(system.getBefore()).toContain('SystemB');
expect(system.getSets()).toContain('CoreSystems');
});
});
describe('CycleDependencyError', () => {
it('应该包含循环节点信息', () => {
const error = new CycleDependencyError(['SystemA', 'SystemB', 'SystemA']);
expect(error.message).toContain('SystemA');
expect(error.message).toContain('SystemB');
expect(error.involvedNodes).toEqual(['SystemA', 'SystemB', 'SystemA']);
});
it('应该是 Error 的实例', () => {
const error = new CycleDependencyError(['SystemA']);
expect(error).toBeInstanceOf(Error);
expect(error.name).toBe('CycleDependencyError');
});
});

View File

@@ -0,0 +1,463 @@
import { EntitySystem } from '../../../src/ECS/Systems/EntitySystem';
import { Entity } from '../../../src/ECS/Entity';
import { Component } from '../../../src/ECS/Component';
import { Scene } from '../../../src/ECS/Scene';
import { Matcher } from '../../../src/ECS/Utils/Matcher';
import { ECSComponent, ECSSystem } from '../../../src/ECS/Decorators';
// 测试组件
@ECSComponent('ChangeDetect_Position')
class PositionComponent extends Component {
private _x: number;
private _y: number;
constructor(...args: unknown[]) {
super();
const [x = 0, y = 0] = args as [number?, number?];
this._x = x;
this._y = y;
}
public get x(): number {
return this._x;
}
public set x(value: number) {
this._x = value;
// 实际使用中需要通过 entity.scene.epochManager.current 获取
// 这里简化测试,手动调用 markDirty
}
public get y(): number {
return this._y;
}
public set y(value: number) {
this._y = value;
}
public setPosition(x: number, y: number, epoch: number): void {
this._x = x;
this._y = y;
this.markDirty(epoch);
}
}
@ECSComponent('ChangeDetect_Velocity')
class VelocityComponent extends Component {
private _vx: number;
private _vy: number;
constructor(...args: unknown[]) {
super();
const [vx = 0, vy = 0] = args as [number?, number?];
this._vx = vx;
this._vy = vy;
}
public get vx(): number {
return this._vx;
}
public get vy(): number {
return this._vy;
}
public setVelocity(vx: number, vy: number, epoch: number): void {
this._vx = vx;
this._vy = vy;
this.markDirty(epoch);
}
}
@ECSComponent('ChangeDetect_Health')
class HealthComponent extends Component {
public current: number;
public max: number;
constructor(...args: unknown[]) {
super();
const [current = 100, max = 100] = args as [number?, number?];
this.current = current;
this.max = max;
}
}
// 测试系统 - 暴露 protected 方法供测试
@ECSSystem('ChangeDetectionTestSystem')
class ChangeDetectionTestSystem extends EntitySystem {
public processedEntities: Entity[] = [];
public changedEntities: Entity[] = [];
constructor() {
super(Matcher.all(PositionComponent, VelocityComponent));
}
protected override process(entities: readonly Entity[]): void {
this.processedEntities = [...entities];
}
// 暴露 protected 方法供测试
public testForEachChanged(
entities: readonly Entity[],
componentTypes: any[],
processor: (entity: Entity, index: number) => void,
sinceEpoch?: number
): void {
this.forEachChanged(entities, componentTypes, processor, sinceEpoch);
}
public testFilterChanged(
entities: readonly Entity[],
componentTypes: any[],
sinceEpoch?: number
): Entity[] {
return this.filterChanged(entities, componentTypes, sinceEpoch);
}
public testHasChanged(
entity: Entity,
componentTypes: any[],
sinceEpoch?: number
): boolean {
return this.hasChanged(entity, componentTypes, sinceEpoch);
}
public testSaveEpoch(): void {
this.saveEpoch();
}
public testGetLastProcessEpoch(): number {
return this.lastProcessEpoch;
}
public testGetCurrentEpoch(): number {
return this.currentEpoch;
}
}
describe('EntitySystem 变更检测', () => {
let scene: Scene;
let system: ChangeDetectionTestSystem;
let entity1: Entity;
let entity2: Entity;
let entity3: Entity;
beforeEach(() => {
scene = new Scene();
system = new ChangeDetectionTestSystem();
scene.addSystem(system);
// 创建测试实体
entity1 = scene.createEntity('entity1');
entity1.addComponent(new PositionComponent(10, 20));
entity1.addComponent(new VelocityComponent(1, 2));
entity2 = scene.createEntity('entity2');
entity2.addComponent(new PositionComponent(30, 40));
entity2.addComponent(new VelocityComponent(3, 4));
entity3 = scene.createEntity('entity3');
entity3.addComponent(new PositionComponent(50, 60));
entity3.addComponent(new VelocityComponent(5, 6));
});
afterEach(() => {
scene.removeSystem(system);
scene.end();
});
describe('lastProcessEpoch', () => {
it('初始值应该是 0', () => {
expect(system.testGetLastProcessEpoch()).toBe(0);
});
});
describe('currentEpoch', () => {
it('应该返回场景的当前 epoch', () => {
expect(system.testGetCurrentEpoch()).toBe(scene.epochManager.current);
});
});
describe('saveEpoch', () => {
it('应该保存当前 epoch', () => {
const currentEpoch = scene.epochManager.current;
system.testSaveEpoch();
expect(system.testGetLastProcessEpoch()).toBe(currentEpoch);
});
it('递增 epoch 后 saveEpoch 应该保存新值', () => {
scene.epochManager.increment();
scene.epochManager.increment();
const currentEpoch = scene.epochManager.current;
system.testSaveEpoch();
expect(system.testGetLastProcessEpoch()).toBe(currentEpoch);
});
});
describe('hasChanged', () => {
it('新组件应该被检测为已变更', () => {
// 组件的 lastWriteEpoch 默认是 0如果检查点也是 0则不算变更
// 需要先保存 epoch然后修改组件
system.testSaveEpoch();
scene.epochManager.increment();
const pos = entity1.getComponent(PositionComponent)!;
pos.setPosition(100, 200, scene.epochManager.current);
expect(system.testHasChanged(entity1, [PositionComponent])).toBe(true);
});
it('未修改的组件应该不被检测为变更', () => {
system.testSaveEpoch();
scene.epochManager.increment();
// entity2 的组件未被修改
expect(system.testHasChanged(entity2, [PositionComponent])).toBe(false);
});
it('应该检查多个组件类型', () => {
system.testSaveEpoch();
scene.epochManager.increment();
// 只修改 Velocity
const vel = entity1.getComponent(VelocityComponent)!;
vel.setVelocity(10, 20, scene.epochManager.current);
// 检查 Position 和 Velocity应该检测到变更
expect(system.testHasChanged(entity1, [PositionComponent, VelocityComponent])).toBe(true);
});
it('指定 sinceEpoch 参数应该使用该值作为检查点', () => {
const pos = entity1.getComponent(PositionComponent)!;
// 在 epoch 5 修改组件
scene.epochManager.increment(); // 2
scene.epochManager.increment(); // 3
scene.epochManager.increment(); // 4
scene.epochManager.increment(); // 5
pos.setPosition(100, 200, 5);
// 检查 epoch 4 之后的变更 - 应该检测到
expect(system.testHasChanged(entity1, [PositionComponent], 4)).toBe(true);
// 检查 epoch 5 之后的变更 - 不应该检测到
expect(system.testHasChanged(entity1, [PositionComponent], 5)).toBe(false);
});
});
describe('filterChanged', () => {
it('应该返回有变更的实体', () => {
system.testSaveEpoch();
scene.epochManager.increment();
// 只修改 entity1
const pos1 = entity1.getComponent(PositionComponent)!;
pos1.setPosition(100, 200, scene.epochManager.current);
const entities = [entity1, entity2, entity3];
const changed = system.testFilterChanged(entities, [PositionComponent]);
expect(changed).toHaveLength(1);
expect(changed[0]).toBe(entity1);
});
it('应该返回空数组当没有变更时', () => {
system.testSaveEpoch();
scene.epochManager.increment();
// 没有修改任何组件
const entities = [entity1, entity2, entity3];
const changed = system.testFilterChanged(entities, [PositionComponent]);
expect(changed).toHaveLength(0);
});
it('应该返回所有变更的实体', () => {
system.testSaveEpoch();
scene.epochManager.increment();
// 修改 entity1 和 entity3
const pos1 = entity1.getComponent(PositionComponent)!;
pos1.setPosition(100, 200, scene.epochManager.current);
const pos3 = entity3.getComponent(PositionComponent)!;
pos3.setPosition(500, 600, scene.epochManager.current);
const entities = [entity1, entity2, entity3];
const changed = system.testFilterChanged(entities, [PositionComponent]);
expect(changed).toHaveLength(2);
expect(changed).toContain(entity1);
expect(changed).toContain(entity3);
});
});
describe('forEachChanged', () => {
it('应该只遍历有变更的实体', () => {
system.testSaveEpoch();
scene.epochManager.increment();
// 只修改 entity2
const pos2 = entity2.getComponent(PositionComponent)!;
pos2.setPosition(300, 400, scene.epochManager.current);
const entities = [entity1, entity2, entity3];
const processed: Entity[] = [];
system.testForEachChanged(entities, [PositionComponent], (entity) => {
processed.push(entity);
});
expect(processed).toHaveLength(1);
expect(processed[0]).toBe(entity2);
});
it('应该自动更新 lastProcessEpoch', () => {
system.testSaveEpoch();
const savedEpoch = system.testGetLastProcessEpoch();
scene.epochManager.increment();
const currentEpoch = scene.epochManager.current;
const entities = [entity1, entity2];
system.testForEachChanged(entities, [PositionComponent], () => {});
// forEachChanged 应该更新 lastProcessEpoch
expect(system.testGetLastProcessEpoch()).toBe(currentEpoch);
expect(system.testGetLastProcessEpoch()).toBeGreaterThan(savedEpoch);
});
it('指定 sinceEpoch 时不应该影响自动更新', () => {
scene.epochManager.increment();
scene.epochManager.increment();
const entities = [entity1];
system.testForEachChanged(entities, [PositionComponent], () => {}, 0);
// 应该更新到当前 epoch
expect(system.testGetLastProcessEpoch()).toBe(scene.epochManager.current);
});
});
describe('实际使用场景', () => {
it('应该支持增量更新模式', () => {
// 模拟第一帧
scene.update();
// 保存检查点
system.testSaveEpoch();
const checkpoint = system.testGetLastProcessEpoch();
// 模拟第二帧 - 修改一个实体
scene.epochManager.increment();
const pos1 = entity1.getComponent(PositionComponent)!;
pos1.setPosition(100, 200, scene.epochManager.current);
// 只处理变更的实体
const changed = system.testFilterChanged(system.entities, [PositionComponent]);
expect(changed).toHaveLength(1);
expect(changed[0]).toBe(entity1);
// 更新检查点
system.testSaveEpoch();
// 模拟第三帧 - 没有修改
scene.epochManager.increment();
// 不应该有变更
const noChanges = system.testFilterChanged(system.entities, [PositionComponent]);
expect(noChanges).toHaveLength(0);
});
it('应该正确处理多次变更', () => {
system.testSaveEpoch();
// 第一次变更
scene.epochManager.increment();
const pos1 = entity1.getComponent(PositionComponent)!;
pos1.setPosition(100, 200, scene.epochManager.current);
let changed = system.testFilterChanged(system.entities, [PositionComponent]);
expect(changed).toContain(entity1);
// 更新检查点
system.testSaveEpoch();
// 第二次变更 - 不同实体
scene.epochManager.increment();
const pos2 = entity2.getComponent(PositionComponent)!;
pos2.setPosition(300, 400, scene.epochManager.current);
changed = system.testFilterChanged(system.entities, [PositionComponent]);
expect(changed).not.toContain(entity1);
expect(changed).toContain(entity2);
});
});
describe('边界情况', () => {
it('空实体列表应该正常处理', () => {
const processed: Entity[] = [];
system.testForEachChanged([], [PositionComponent], (entity) => {
processed.push(entity);
});
expect(processed).toHaveLength(0);
});
it('空组件类型列表应该不检测到任何变更', () => {
system.testSaveEpoch();
scene.epochManager.increment();
const pos1 = entity1.getComponent(PositionComponent)!;
pos1.setPosition(100, 200, scene.epochManager.current);
// 空组件类型列表
const changed = system.testFilterChanged(system.entities, []);
expect(changed).toHaveLength(0);
});
it('实体缺少指定组件时应该跳过', () => {
// 创建一个只有 Position 的实体
const entityWithoutVelocity = scene.createEntity('noVel');
entityWithoutVelocity.addComponent(new PositionComponent(70, 80));
system.testSaveEpoch();
scene.epochManager.increment();
// 修改该实体的 Position
const pos = entityWithoutVelocity.getComponent(PositionComponent)!;
pos.setPosition(100, 200, scene.epochManager.current);
// 检查 Velocity 变更 - 该实体没有 Velocity应该不被检测
const entities = [entityWithoutVelocity];
const changed = system.testFilterChanged(entities, [VelocityComponent]);
expect(changed).toHaveLength(0);
});
});
});
describe('Component.markDirty', () => {
let scene: Scene;
beforeEach(() => {
scene = new Scene();
});
afterEach(() => {
scene.end();
});
it('应该更新 lastWriteEpoch', () => {
const entity = scene.createEntity('test');
const pos = entity.addComponent(new PositionComponent(10, 20));
expect(pos.lastWriteEpoch).toBe(0);
pos.markDirty(5);
expect(pos.lastWriteEpoch).toBe(5);
pos.markDirty(10);
expect(pos.lastWriteEpoch).toBe(10);
});
});