整合组件类型至统一的componentregistry中
This commit is contained in:
@@ -71,7 +71,7 @@
|
|||||||
ComponentRegistry • IdentifierPool
|
ComponentRegistry • IdentifierPool
|
||||||
</text>
|
</text>
|
||||||
<text x="700" y="105" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="white">
|
<text x="700" y="105" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="white">
|
||||||
BitMaskOptimizer • ConfigManager
|
EventBus • ConfigManager
|
||||||
</text>
|
</text>
|
||||||
|
|
||||||
</g>
|
</g>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
@@ -1,431 +0,0 @@
|
|||||||
# 位掩码使用指南
|
|
||||||
|
|
||||||
本文档详细解释ECS框架中位掩码的概念、原理和使用方法。
|
|
||||||
|
|
||||||
## 目录
|
|
||||||
|
|
||||||
1. [什么是位掩码](#什么是位掩码)
|
|
||||||
2. [位掩码的优势](#位掩码的优势)
|
|
||||||
3. [基础使用方法](#基础使用方法)
|
|
||||||
4. [高级位掩码操作](#高级位掩码操作)
|
|
||||||
5. [实际应用场景](#实际应用场景)
|
|
||||||
6. [性能优化技巧](#性能优化技巧)
|
|
||||||
|
|
||||||
## 什么是位掩码
|
|
||||||
|
|
||||||
### 基本概念
|
|
||||||
|
|
||||||
位掩码(BitMask)是一种使用二进制位来表示状态或属性的技术。在ECS框架中,每个组件类型对应一个二进制位,实体的组件组合可以用一个数字来表示。
|
|
||||||
|
|
||||||
### 简单例子
|
|
||||||
|
|
||||||
假设我们有以下组件:
|
|
||||||
- PositionComponent → 位置 0 (二进制: 001)
|
|
||||||
- VelocityComponent → 位置 1 (二进制: 010)
|
|
||||||
- HealthComponent → 位置 2 (二进制: 100)
|
|
||||||
|
|
||||||
那么一个同时拥有Position和Health组件的实体,其位掩码就是:
|
|
||||||
```
|
|
||||||
001 (Position) + 100 (Health) = 101 (二进制) = 5 (十进制)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 可视化理解
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 组件类型对应的位位置
|
|
||||||
PositionComponent → 位置0 → 2^0 = 1 → 二进制: 001
|
|
||||||
VelocityComponent → 位置1 → 2^1 = 2 → 二进制: 010
|
|
||||||
HealthComponent → 位置2 → 2^2 = 4 → 二进制: 100
|
|
||||||
RenderComponent → 位置3 → 2^3 = 8 → 二进制: 1000
|
|
||||||
|
|
||||||
// 实体的组件组合示例
|
|
||||||
实体A: Position + Velocity → 001 + 010 = 011 (二进制) = 3 (十进制)
|
|
||||||
实体B: Position + Health → 001 + 100 = 101 (二进制) = 5 (十进制)
|
|
||||||
实体C: Position + Velocity + Health → 001 + 010 + 100 = 111 (二进制) = 7 (十进制)
|
|
||||||
```
|
|
||||||
|
|
||||||
## 位掩码的优势
|
|
||||||
|
|
||||||
### 1. 极快的查询速度
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 传统方式:需要遍历组件列表
|
|
||||||
function hasComponents(entity, componentTypes) {
|
|
||||||
for (const type of componentTypes) {
|
|
||||||
if (!entity.hasComponent(type)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 位掩码方式:一次位运算即可
|
|
||||||
function hasComponentsMask(entityMask, requiredMask) {
|
|
||||||
return (entityMask & requiredMask) === requiredMask;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 内存效率
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 一个bigint可以表示64个组件的组合状态
|
|
||||||
// 相比存储组件列表,内存使用量大大减少
|
|
||||||
|
|
||||||
const entity = scene.createEntity("Player");
|
|
||||||
entity.addComponent(new PositionComponent());
|
|
||||||
entity.addComponent(new HealthComponent());
|
|
||||||
|
|
||||||
// 获取位掩码(只是一个数字)
|
|
||||||
const mask = entity.componentMask; // bigint类型
|
|
||||||
console.log(`位掩码: ${mask}`); // 输出: 5 (二进制: 101)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 批量操作优化
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 可以快速筛选大量实体
|
|
||||||
const entities = scene.getAllEntities();
|
|
||||||
const requiredMask = BigInt(0b101); // Position + Health
|
|
||||||
|
|
||||||
const filteredEntities = entities.filter(entity =>
|
|
||||||
(entity.componentMask & requiredMask) === requiredMask
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
## 基础使用方法
|
|
||||||
|
|
||||||
### 获取实体的位掩码
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { Scene, Entity, Component } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
// 创建组件
|
|
||||||
class PositionComponent extends Component {
|
|
||||||
constructor(public x: number = 0, public y: number = 0) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class HealthComponent extends Component {
|
|
||||||
constructor(public maxHealth: number = 100) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建实体并添加组件
|
|
||||||
const scene = new Scene();
|
|
||||||
const entity = scene.createEntity("Player");
|
|
||||||
|
|
||||||
console.log(`初始位掩码: ${entity.componentMask}`); // 0
|
|
||||||
|
|
||||||
entity.addComponent(new PositionComponent(100, 200));
|
|
||||||
console.log(`添加Position后: ${entity.componentMask}`); // 可能是 1
|
|
||||||
|
|
||||||
entity.addComponent(new HealthComponent(100));
|
|
||||||
console.log(`添加Health后: ${entity.componentMask}`); // 可能是 5
|
|
||||||
|
|
||||||
// 查看二进制表示
|
|
||||||
console.log(`二进制表示: ${entity.componentMask.toString(2)}`);
|
|
||||||
```
|
|
||||||
|
|
||||||
### 手动检查位掩码
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 检查实体是否拥有特定组件组合
|
|
||||||
function checkEntityComponents(entity: Entity) {
|
|
||||||
const mask = entity.componentMask;
|
|
||||||
|
|
||||||
// 将位掩码转换为二进制字符串查看
|
|
||||||
const binaryString = mask.toString(2).padStart(8, '0');
|
|
||||||
console.log(`实体组件状态: ${binaryString}`);
|
|
||||||
|
|
||||||
// 分析每一位
|
|
||||||
console.log(`位0 (Position): ${(mask & 1n) !== 0n ? '有' : '无'}`);
|
|
||||||
console.log(`位1 (Velocity): ${(mask & 2n) !== 0n ? '有' : '无'}`);
|
|
||||||
console.log(`位2 (Health): ${(mask & 4n) !== 0n ? '有' : '无'}`);
|
|
||||||
console.log(`位3 (Render): ${(mask & 8n) !== 0n ? '有' : '无'}`);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 高级位掩码操作
|
|
||||||
|
|
||||||
### 使用BitMaskOptimizer
|
|
||||||
|
|
||||||
框架提供了BitMaskOptimizer类来简化位掩码操作:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { BitMaskOptimizer } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
// 获取优化器实例
|
|
||||||
const optimizer = BitMaskOptimizer.getInstance();
|
|
||||||
|
|
||||||
// 注册组件类型(建议在游戏初始化时进行)
|
|
||||||
optimizer.registerComponentType('PositionComponent');
|
|
||||||
optimizer.registerComponentType('VelocityComponent');
|
|
||||||
optimizer.registerComponentType('HealthComponent');
|
|
||||||
optimizer.registerComponentType('RenderComponent');
|
|
||||||
|
|
||||||
// 创建单个组件的掩码
|
|
||||||
const positionMask = optimizer.createSingleComponentMask('PositionComponent');
|
|
||||||
console.log(`Position掩码: ${positionMask} (二进制: ${positionMask.toString(2)})`);
|
|
||||||
|
|
||||||
// 创建组合掩码
|
|
||||||
const movementMask = optimizer.createCombinedMask(['PositionComponent', 'VelocityComponent']);
|
|
||||||
console.log(`Movement掩码: ${movementMask} (二进制: ${movementMask.toString(2)})`);
|
|
||||||
|
|
||||||
// 检查实体是否匹配掩码
|
|
||||||
const entity = scene.createEntity("TestEntity");
|
|
||||||
entity.addComponent(new PositionComponent());
|
|
||||||
entity.addComponent(new VelocityComponent());
|
|
||||||
|
|
||||||
const hasMovementComponents = optimizer.maskContainsAllComponents(
|
|
||||||
entity.componentMask,
|
|
||||||
['PositionComponent', 'VelocityComponent']
|
|
||||||
);
|
|
||||||
console.log(`实体拥有移动组件: ${hasMovementComponents}`);
|
|
||||||
```
|
|
||||||
|
|
||||||
### 位掩码分析工具
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 分析位掩码的实用函数
|
|
||||||
class MaskAnalyzer {
|
|
||||||
private optimizer = BitMaskOptimizer.getInstance();
|
|
||||||
|
|
||||||
// 分析实体的组件组合
|
|
||||||
analyzeEntity(entity: Entity): void {
|
|
||||||
const mask = entity.componentMask;
|
|
||||||
const componentNames = this.optimizer.maskToComponentNames(mask);
|
|
||||||
const componentCount = this.optimizer.getComponentCount(mask);
|
|
||||||
|
|
||||||
console.log(`实体 ${entity.name} 分析:`);
|
|
||||||
console.log(`- 位掩码: ${mask} (二进制: ${mask.toString(2)})`);
|
|
||||||
console.log(`- 组件数量: ${componentCount}`);
|
|
||||||
console.log(`- 组件列表: ${componentNames.join(', ')}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 比较两个实体的组件差异
|
|
||||||
compareEntities(entityA: Entity, entityB: Entity): void {
|
|
||||||
const maskA = entityA.componentMask;
|
|
||||||
const maskB = entityB.componentMask;
|
|
||||||
|
|
||||||
const commonMask = maskA & maskB;
|
|
||||||
const onlyInA = maskA & ~maskB;
|
|
||||||
const onlyInB = maskB & ~maskA;
|
|
||||||
|
|
||||||
console.log(`实体比较:`);
|
|
||||||
console.log(`- 共同组件: ${this.optimizer.maskToComponentNames(commonMask).join(', ')}`);
|
|
||||||
console.log(`- 仅在A中: ${this.optimizer.maskToComponentNames(onlyInA).join(', ')}`);
|
|
||||||
console.log(`- 仅在B中: ${this.optimizer.maskToComponentNames(onlyInB).join(', ')}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查找具有特定组件组合的实体
|
|
||||||
findEntitiesWithMask(entities: Entity[], requiredComponents: string[]): Entity[] {
|
|
||||||
const requiredMask = this.optimizer.createCombinedMask(requiredComponents);
|
|
||||||
|
|
||||||
return entities.filter(entity =>
|
|
||||||
(entity.componentMask & requiredMask) === requiredMask
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用示例
|
|
||||||
const analyzer = new MaskAnalyzer();
|
|
||||||
analyzer.analyzeEntity(entity);
|
|
||||||
```
|
|
||||||
|
|
||||||
## 实际应用场景
|
|
||||||
|
|
||||||
### 1. 高性能实体查询
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
class GameSystem {
|
|
||||||
private optimizer = BitMaskOptimizer.getInstance();
|
|
||||||
private movementMask: bigint;
|
|
||||||
private combatMask: bigint;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
// 预计算常用掩码
|
|
||||||
this.movementMask = this.optimizer.createCombinedMask([
|
|
||||||
'PositionComponent', 'VelocityComponent'
|
|
||||||
]);
|
|
||||||
|
|
||||||
this.combatMask = this.optimizer.createCombinedMask([
|
|
||||||
'PositionComponent', 'HealthComponent', 'WeaponComponent'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 快速查找移动实体
|
|
||||||
findMovingEntities(entities: Entity[]): Entity[] {
|
|
||||||
return entities.filter(entity =>
|
|
||||||
(entity.componentMask & this.movementMask) === this.movementMask
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 快速查找战斗单位
|
|
||||||
findCombatUnits(entities: Entity[]): Entity[] {
|
|
||||||
return entities.filter(entity =>
|
|
||||||
(entity.componentMask & this.combatMask) === this.combatMask
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 实体分类和管理
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
class EntityClassifier {
|
|
||||||
private optimizer = BitMaskOptimizer.getInstance();
|
|
||||||
|
|
||||||
// 定义实体类型掩码
|
|
||||||
private readonly ENTITY_TYPES = {
|
|
||||||
PLAYER: this.optimizer.createCombinedMask([
|
|
||||||
'PositionComponent', 'HealthComponent', 'InputComponent'
|
|
||||||
]),
|
|
||||||
ENEMY: this.optimizer.createCombinedMask([
|
|
||||||
'PositionComponent', 'HealthComponent', 'AIComponent'
|
|
||||||
]),
|
|
||||||
PROJECTILE: this.optimizer.createCombinedMask([
|
|
||||||
'PositionComponent', 'VelocityComponent', 'DamageComponent'
|
|
||||||
]),
|
|
||||||
PICKUP: this.optimizer.createCombinedMask([
|
|
||||||
'PositionComponent', 'PickupComponent'
|
|
||||||
])
|
|
||||||
};
|
|
||||||
|
|
||||||
// 根据组件组合判断实体类型
|
|
||||||
classifyEntity(entity: Entity): string {
|
|
||||||
const mask = entity.componentMask;
|
|
||||||
|
|
||||||
for (const [type, typeMask] of Object.entries(this.ENTITY_TYPES)) {
|
|
||||||
if ((mask & typeMask) === typeMask) {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'UNKNOWN';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 批量分类实体
|
|
||||||
classifyEntities(entities: Entity[]): Map<string, Entity[]> {
|
|
||||||
const classified = new Map<string, Entity[]>();
|
|
||||||
|
|
||||||
for (const entity of entities) {
|
|
||||||
const type = this.classifyEntity(entity);
|
|
||||||
if (!classified.has(type)) {
|
|
||||||
classified.set(type, []);
|
|
||||||
}
|
|
||||||
classified.get(type)!.push(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
return classified;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 性能优化技巧
|
|
||||||
|
|
||||||
### 1. 预计算常用掩码
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
class MaskCache {
|
|
||||||
private optimizer = BitMaskOptimizer.getInstance();
|
|
||||||
|
|
||||||
// 预计算游戏中常用的组件组合
|
|
||||||
public readonly COMMON_MASKS = {
|
|
||||||
RENDERABLE: this.optimizer.createCombinedMask([
|
|
||||||
'PositionComponent', 'RenderComponent'
|
|
||||||
]),
|
|
||||||
MOVABLE: this.optimizer.createCombinedMask([
|
|
||||||
'PositionComponent', 'VelocityComponent'
|
|
||||||
]),
|
|
||||||
LIVING: this.optimizer.createCombinedMask([
|
|
||||||
'HealthComponent'
|
|
||||||
]),
|
|
||||||
INTERACTIVE: this.optimizer.createCombinedMask([
|
|
||||||
'PositionComponent', 'ColliderComponent'
|
|
||||||
])
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
// 预计算常用组合以提升性能
|
|
||||||
this.optimizer.precomputeCommonMasks([
|
|
||||||
['PositionComponent', 'RenderComponent'],
|
|
||||||
['PositionComponent', 'VelocityComponent'],
|
|
||||||
['PositionComponent', 'HealthComponent', 'WeaponComponent']
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 位掩码调试工具
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 位掩码调试工具
|
|
||||||
class MaskDebugger {
|
|
||||||
private optimizer = BitMaskOptimizer.getInstance();
|
|
||||||
|
|
||||||
// 可视化位掩码
|
|
||||||
visualizeMask(mask: bigint, maxBits: number = 16): string {
|
|
||||||
const binary = mask.toString(2).padStart(maxBits, '0');
|
|
||||||
const components = this.optimizer.maskToComponentNames(mask);
|
|
||||||
|
|
||||||
let visualization = `位掩码: ${mask} (二进制: ${binary})\n`;
|
|
||||||
visualization += `组件: ${components.join(', ')}\n`;
|
|
||||||
visualization += `可视化: `;
|
|
||||||
|
|
||||||
for (let i = maxBits - 1; i >= 0; i--) {
|
|
||||||
const bit = (mask & (1n << BigInt(i))) !== 0n ? '1' : '0';
|
|
||||||
visualization += bit;
|
|
||||||
if (i % 4 === 0 && i > 0) visualization += ' ';
|
|
||||||
}
|
|
||||||
|
|
||||||
return visualization;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 最佳实践
|
|
||||||
|
|
||||||
### 1. 组件注册
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 在游戏初始化时注册所有组件类型
|
|
||||||
function initializeComponentTypes() {
|
|
||||||
const optimizer = BitMaskOptimizer.getInstance();
|
|
||||||
|
|
||||||
// 按使用频率注册(常用的组件分配较小的位位置)
|
|
||||||
optimizer.registerComponentType('PositionComponent'); // 位置0
|
|
||||||
optimizer.registerComponentType('VelocityComponent'); // 位置1
|
|
||||||
optimizer.registerComponentType('HealthComponent'); // 位置2
|
|
||||||
optimizer.registerComponentType('RenderComponent'); // 位置3
|
|
||||||
// ... 其他组件
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 掩码缓存管理
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 定期清理掩码缓存以避免内存泄漏
|
|
||||||
setInterval(() => {
|
|
||||||
const optimizer = BitMaskOptimizer.getInstance();
|
|
||||||
const stats = optimizer.getCacheStats();
|
|
||||||
|
|
||||||
// 如果缓存过大,清理一部分
|
|
||||||
if (stats.size > 1000) {
|
|
||||||
optimizer.clearCache();
|
|
||||||
console.log('位掩码缓存已清理');
|
|
||||||
}
|
|
||||||
}, 60000); // 每分钟检查一次
|
|
||||||
```
|
|
||||||
|
|
||||||
## 总结
|
|
||||||
|
|
||||||
位掩码是ECS框架中的核心优化技术,它提供了:
|
|
||||||
|
|
||||||
1. **极快的查询速度** - 位运算比遍历快数百倍
|
|
||||||
2. **内存效率** - 用一个数字表示复杂的组件组合
|
|
||||||
3. **批量操作优化** - 可以快速处理大量实体
|
|
||||||
4. **灵活的查询构建** - 支持复杂的组件组合查询
|
|
||||||
|
|
||||||
通过理解和正确使用位掩码,可以显著提升游戏的性能表现。记住要在游戏初始化时注册组件类型,预计算常用掩码,并合理管理缓存。
|
|
||||||
@@ -1,214 +0,0 @@
|
|||||||
import { IBigIntLike, BigIntFactory } from '../Utils/BigIntCompatibility';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 位掩码优化器,用于预计算和缓存常用的组件掩码
|
|
||||||
*
|
|
||||||
* 使用BigInt兼容层确保在所有平台上的正常运行。
|
|
||||||
*/
|
|
||||||
export class BitMaskOptimizer {
|
|
||||||
private static instance: BitMaskOptimizer;
|
|
||||||
private maskCache = new Map<string, IBigIntLike>();
|
|
||||||
private componentTypeMap = new Map<string, number>();
|
|
||||||
private nextComponentId = 0;
|
|
||||||
|
|
||||||
private constructor() {}
|
|
||||||
|
|
||||||
static getInstance(): BitMaskOptimizer {
|
|
||||||
if (!BitMaskOptimizer.instance) {
|
|
||||||
BitMaskOptimizer.instance = new BitMaskOptimizer();
|
|
||||||
}
|
|
||||||
return BitMaskOptimizer.instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 注册组件类型
|
|
||||||
*/
|
|
||||||
registerComponentType(componentName: string): number {
|
|
||||||
if (!this.componentTypeMap.has(componentName)) {
|
|
||||||
this.componentTypeMap.set(componentName, this.nextComponentId++);
|
|
||||||
}
|
|
||||||
return this.componentTypeMap.get(componentName)!;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取组件类型ID
|
|
||||||
*/
|
|
||||||
getComponentTypeId(componentName: string): number | undefined {
|
|
||||||
return this.componentTypeMap.get(componentName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建单个组件的掩码
|
|
||||||
* @param componentName 组件名称
|
|
||||||
* @returns 组件掩码
|
|
||||||
*/
|
|
||||||
createSingleComponentMask(componentName: string): IBigIntLike {
|
|
||||||
const cacheKey = `single:${componentName}`;
|
|
||||||
|
|
||||||
if (this.maskCache.has(cacheKey)) {
|
|
||||||
return this.maskCache.get(cacheKey)!;
|
|
||||||
}
|
|
||||||
|
|
||||||
const componentId = this.getComponentTypeId(componentName);
|
|
||||||
if (componentId === undefined) {
|
|
||||||
throw new Error(`Component type not registered: ${componentName}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const mask = BigIntFactory.one().shiftLeft(componentId);
|
|
||||||
this.maskCache.set(cacheKey, mask);
|
|
||||||
return mask;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建多个组件的组合掩码
|
|
||||||
* @param componentNames 组件名称数组
|
|
||||||
* @returns 组合掩码
|
|
||||||
*/
|
|
||||||
createCombinedMask(componentNames: string[]): IBigIntLike {
|
|
||||||
const sortedNames = [...componentNames].sort();
|
|
||||||
const cacheKey = `combined:${sortedNames.join(',')}`;
|
|
||||||
|
|
||||||
if (this.maskCache.has(cacheKey)) {
|
|
||||||
return this.maskCache.get(cacheKey)!;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mask = BigIntFactory.zero();
|
|
||||||
for (const componentName of componentNames) {
|
|
||||||
const componentId = this.getComponentTypeId(componentName);
|
|
||||||
if (componentId === undefined) {
|
|
||||||
throw new Error(`Component type not registered: ${componentName}`);
|
|
||||||
}
|
|
||||||
const componentMask = BigIntFactory.one().shiftLeft(componentId);
|
|
||||||
mask = mask.or(componentMask);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.maskCache.set(cacheKey, mask);
|
|
||||||
return mask;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查掩码是否包含指定组件
|
|
||||||
* @param mask 要检查的掩码
|
|
||||||
* @param componentName 组件名称
|
|
||||||
* @returns 是否包含指定组件
|
|
||||||
*/
|
|
||||||
maskContainsComponent(mask: IBigIntLike, componentName: string): boolean {
|
|
||||||
const componentMask = this.createSingleComponentMask(componentName);
|
|
||||||
return !mask.and(componentMask).isZero();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查掩码是否包含所有指定组件
|
|
||||||
* @param mask 要检查的掩码
|
|
||||||
* @param componentNames 组件名称数组
|
|
||||||
* @returns 是否包含所有指定组件
|
|
||||||
*/
|
|
||||||
maskContainsAllComponents(mask: IBigIntLike, componentNames: string[]): boolean {
|
|
||||||
const requiredMask = this.createCombinedMask(componentNames);
|
|
||||||
const intersection = mask.and(requiredMask);
|
|
||||||
return intersection.equals(requiredMask);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查掩码是否包含任一指定组件
|
|
||||||
* @param mask 要检查的掩码
|
|
||||||
* @param componentNames 组件名称数组
|
|
||||||
* @returns 是否包含任一指定组件
|
|
||||||
*/
|
|
||||||
maskContainsAnyComponent(mask: IBigIntLike, componentNames: string[]): boolean {
|
|
||||||
const anyMask = this.createCombinedMask(componentNames);
|
|
||||||
return !mask.and(anyMask).isZero();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 添加组件到掩码
|
|
||||||
* @param mask 原始掩码
|
|
||||||
* @param componentName 要添加的组件名称
|
|
||||||
* @returns 新的掩码
|
|
||||||
*/
|
|
||||||
addComponentToMask(mask: IBigIntLike, componentName: string): IBigIntLike {
|
|
||||||
const componentMask = this.createSingleComponentMask(componentName);
|
|
||||||
return mask.or(componentMask);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从掩码中移除组件
|
|
||||||
* @param mask 原始掩码
|
|
||||||
* @param componentName 要移除的组件名称
|
|
||||||
* @returns 新的掩码
|
|
||||||
*/
|
|
||||||
removeComponentFromMask(mask: IBigIntLike, componentName: string): IBigIntLike {
|
|
||||||
const componentMask = this.createSingleComponentMask(componentName);
|
|
||||||
const notComponentMask = componentMask.not();
|
|
||||||
return mask.and(notComponentMask);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 预计算常用掩码组合
|
|
||||||
*/
|
|
||||||
precomputeCommonMasks(commonCombinations: string[][]): void {
|
|
||||||
for (const combination of commonCombinations) {
|
|
||||||
this.createCombinedMask(combination);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取掩码缓存统计信息
|
|
||||||
*/
|
|
||||||
getCacheStats(): { size: number; componentTypes: number } {
|
|
||||||
return {
|
|
||||||
size: this.maskCache.size,
|
|
||||||
componentTypes: this.componentTypeMap.size
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清空缓存
|
|
||||||
*/
|
|
||||||
clearCache(): void {
|
|
||||||
this.maskCache.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 重置优化器
|
|
||||||
*/
|
|
||||||
reset(): void {
|
|
||||||
this.maskCache.clear();
|
|
||||||
this.componentTypeMap.clear();
|
|
||||||
this.nextComponentId = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将掩码转换为组件名称数组
|
|
||||||
*/
|
|
||||||
maskToComponentNames(mask: IBigIntLike): string[] {
|
|
||||||
const componentNames: string[] = [];
|
|
||||||
|
|
||||||
for (const [componentName, componentId] of this.componentTypeMap) {
|
|
||||||
const componentMask = BigIntFactory.one().shiftLeft(componentId);
|
|
||||||
if (!mask.and(componentMask).isZero()) {
|
|
||||||
componentNames.push(componentName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return componentNames;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取掩码中组件的数量
|
|
||||||
*/
|
|
||||||
getComponentCount(mask: IBigIntLike): number {
|
|
||||||
let count = 0;
|
|
||||||
let tempMask = mask.clone();
|
|
||||||
const one = BigIntFactory.one();
|
|
||||||
|
|
||||||
while (!tempMask.isZero()) {
|
|
||||||
if (!tempMask.and(one).isZero()) {
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
tempMask = tempMask.shiftRight(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -16,6 +16,9 @@ export type ComponentType<T extends Component = Component> = new (...args: unkno
|
|||||||
*/
|
*/
|
||||||
export class ComponentRegistry {
|
export class ComponentRegistry {
|
||||||
private static componentTypes = new Map<Function, number>();
|
private static componentTypes = new Map<Function, number>();
|
||||||
|
private static componentNameToType = new Map<string, Function>();
|
||||||
|
private static componentNameToId = new Map<string, number>();
|
||||||
|
private static maskCache = new Map<string, IBigIntLike>();
|
||||||
private static nextBitIndex = 0;
|
private static nextBitIndex = 0;
|
||||||
private static maxComponents = 64; // 支持最多64种组件类型
|
private static maxComponents = 64; // 支持最多64种组件类型
|
||||||
|
|
||||||
@@ -35,6 +38,8 @@ export class ComponentRegistry {
|
|||||||
|
|
||||||
const bitIndex = this.nextBitIndex++;
|
const bitIndex = this.nextBitIndex++;
|
||||||
this.componentTypes.set(componentType, bitIndex);
|
this.componentTypes.set(componentType, bitIndex);
|
||||||
|
this.componentNameToType.set(componentType.name, componentType);
|
||||||
|
this.componentNameToId.set(componentType.name, bitIndex);
|
||||||
return bitIndex;
|
return bitIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,6 +78,15 @@ export class ComponentRegistry {
|
|||||||
return this.componentTypes.has(componentType);
|
return this.componentTypes.has(componentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过名称获取组件类型
|
||||||
|
* @param componentName 组件名称
|
||||||
|
* @returns 组件类型构造函数
|
||||||
|
*/
|
||||||
|
public static getComponentType(componentName: string): Function | null {
|
||||||
|
return this.componentNameToType.get(componentName) || null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取所有已注册的组件类型
|
* 获取所有已注册的组件类型
|
||||||
* @returns 组件类型映射
|
* @returns 组件类型映射
|
||||||
@@ -81,11 +95,104 @@ export class ComponentRegistry {
|
|||||||
return new Map(this.componentTypes);
|
return new Map(this.componentTypes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有组件名称到类型的映射
|
||||||
|
* @returns 名称到类型的映射
|
||||||
|
*/
|
||||||
|
public static getAllComponentNames(): Map<string, Function> {
|
||||||
|
return new Map(this.componentNameToType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过名称获取组件类型ID
|
||||||
|
* @param componentName 组件名称
|
||||||
|
* @returns 组件类型ID
|
||||||
|
*/
|
||||||
|
public static getComponentId(componentName: string): number | undefined {
|
||||||
|
return this.componentNameToId.get(componentName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册组件类型(通过名称)
|
||||||
|
* @param componentName 组件名称
|
||||||
|
* @returns 分配的组件ID
|
||||||
|
*/
|
||||||
|
public static registerComponentByName(componentName: string): number {
|
||||||
|
if (this.componentNameToId.has(componentName)) {
|
||||||
|
return this.componentNameToId.get(componentName)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.nextBitIndex >= this.maxComponents) {
|
||||||
|
throw new Error(`Maximum number of component types (${this.maxComponents}) exceeded`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bitIndex = this.nextBitIndex++;
|
||||||
|
this.componentNameToId.set(componentName, bitIndex);
|
||||||
|
return bitIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建单个组件的掩码
|
||||||
|
* @param componentName 组件名称
|
||||||
|
* @returns 组件掩码
|
||||||
|
*/
|
||||||
|
public static createSingleComponentMask(componentName: string): IBigIntLike {
|
||||||
|
const cacheKey = `single:${componentName}`;
|
||||||
|
|
||||||
|
if (this.maskCache.has(cacheKey)) {
|
||||||
|
return this.maskCache.get(cacheKey)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
const componentId = this.getComponentId(componentName);
|
||||||
|
if (componentId === undefined) {
|
||||||
|
throw new Error(`Component type ${componentName} is not registered`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mask = BigIntFactory.one().shiftLeft(componentId);
|
||||||
|
this.maskCache.set(cacheKey, mask);
|
||||||
|
return mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建多个组件的掩码
|
||||||
|
* @param componentNames 组件名称数组
|
||||||
|
* @returns 组合掩码
|
||||||
|
*/
|
||||||
|
public static createComponentMask(componentNames: string[]): IBigIntLike {
|
||||||
|
const sortedNames = [...componentNames].sort();
|
||||||
|
const cacheKey = `multi:${sortedNames.join(',')}`;
|
||||||
|
|
||||||
|
if (this.maskCache.has(cacheKey)) {
|
||||||
|
return this.maskCache.get(cacheKey)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mask = BigIntFactory.zero();
|
||||||
|
for (const name of componentNames) {
|
||||||
|
const componentId = this.getComponentId(name);
|
||||||
|
if (componentId !== undefined) {
|
||||||
|
mask = mask.or(BigIntFactory.one().shiftLeft(componentId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.maskCache.set(cacheKey, mask);
|
||||||
|
return mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除掩码缓存
|
||||||
|
*/
|
||||||
|
public static clearMaskCache(): void {
|
||||||
|
this.maskCache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重置注册表(用于测试)
|
* 重置注册表(用于测试)
|
||||||
*/
|
*/
|
||||||
public static reset(): void {
|
public static reset(): void {
|
||||||
this.componentTypes.clear();
|
this.componentTypes.clear();
|
||||||
|
this.componentNameToType.clear();
|
||||||
|
this.componentNameToId.clear();
|
||||||
|
this.maskCache.clear();
|
||||||
this.nextBitIndex = 0;
|
this.nextBitIndex = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,256 +0,0 @@
|
|||||||
import { Entity } from '../Entity';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 索引更新操作类型
|
|
||||||
*/
|
|
||||||
export enum IndexUpdateType {
|
|
||||||
ADD_ENTITY = 'add_entity',
|
|
||||||
REMOVE_ENTITY = 'remove_entity',
|
|
||||||
UPDATE_ENTITY = 'update_entity'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 索引更新操作
|
|
||||||
*/
|
|
||||||
export interface IndexUpdateOperation {
|
|
||||||
type: IndexUpdateType;
|
|
||||||
entity: Entity;
|
|
||||||
oldMask?: bigint;
|
|
||||||
newMask?: bigint;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 延迟索引更新器,用于批量更新查询索引以提高性能
|
|
||||||
*/
|
|
||||||
export class IndexUpdateBatcher {
|
|
||||||
private pendingOperations: IndexUpdateOperation[] = [];
|
|
||||||
private isProcessing = false;
|
|
||||||
private batchSize = 1000;
|
|
||||||
private flushTimeout: NodeJS.Timeout | null = null;
|
|
||||||
private flushDelay = 16; // 16ms,约60fps
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 添加索引更新操作
|
|
||||||
*/
|
|
||||||
addOperation(operation: IndexUpdateOperation): void {
|
|
||||||
this.pendingOperations.push(operation);
|
|
||||||
|
|
||||||
// 如果达到批量大小,立即处理
|
|
||||||
if (this.pendingOperations.length >= this.batchSize) {
|
|
||||||
this.flush();
|
|
||||||
} else {
|
|
||||||
// 否则延迟处理
|
|
||||||
this.scheduleFlush();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 批量添加实体
|
|
||||||
*/
|
|
||||||
addEntities(entities: Entity[]): void {
|
|
||||||
for (const entity of entities) {
|
|
||||||
this.pendingOperations.push({
|
|
||||||
type: IndexUpdateType.ADD_ENTITY,
|
|
||||||
entity
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.pendingOperations.length >= this.batchSize) {
|
|
||||||
this.flush();
|
|
||||||
} else {
|
|
||||||
this.scheduleFlush();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 批量移除实体
|
|
||||||
*/
|
|
||||||
removeEntities(entities: Entity[]): void {
|
|
||||||
for (const entity of entities) {
|
|
||||||
this.pendingOperations.push({
|
|
||||||
type: IndexUpdateType.REMOVE_ENTITY,
|
|
||||||
entity
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.pendingOperations.length >= this.batchSize) {
|
|
||||||
this.flush();
|
|
||||||
} else {
|
|
||||||
this.scheduleFlush();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 批量更新实体
|
|
||||||
*/
|
|
||||||
updateEntities(updates: Array<{ entity: Entity; oldMask: bigint; newMask: bigint }>): void {
|
|
||||||
for (const update of updates) {
|
|
||||||
this.pendingOperations.push({
|
|
||||||
type: IndexUpdateType.UPDATE_ENTITY,
|
|
||||||
entity: update.entity,
|
|
||||||
oldMask: update.oldMask,
|
|
||||||
newMask: update.newMask
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.pendingOperations.length >= this.batchSize) {
|
|
||||||
this.flush();
|
|
||||||
} else {
|
|
||||||
this.scheduleFlush();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 安排延迟刷新
|
|
||||||
*/
|
|
||||||
private scheduleFlush(): void {
|
|
||||||
if (this.flushTimeout) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.flushTimeout = setTimeout(() => {
|
|
||||||
this.flush();
|
|
||||||
}, this.flushDelay);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 立即处理所有待处理的操作
|
|
||||||
*/
|
|
||||||
flush(): void {
|
|
||||||
if (this.isProcessing || this.pendingOperations.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isProcessing = true;
|
|
||||||
|
|
||||||
if (this.flushTimeout) {
|
|
||||||
clearTimeout(this.flushTimeout);
|
|
||||||
this.flushTimeout = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.processBatch();
|
|
||||||
} finally {
|
|
||||||
this.isProcessing = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理批量操作
|
|
||||||
*/
|
|
||||||
private processBatch(): void {
|
|
||||||
const operations = this.pendingOperations;
|
|
||||||
this.pendingOperations = [];
|
|
||||||
|
|
||||||
// 按操作类型分组以优化处理
|
|
||||||
const addOperations: Entity[] = [];
|
|
||||||
const removeOperations: Entity[] = [];
|
|
||||||
const updateOperations: Array<{ entity: Entity; oldMask: bigint; newMask: bigint }> = [];
|
|
||||||
|
|
||||||
for (const operation of operations) {
|
|
||||||
switch (operation.type) {
|
|
||||||
case IndexUpdateType.ADD_ENTITY:
|
|
||||||
addOperations.push(operation.entity);
|
|
||||||
break;
|
|
||||||
case IndexUpdateType.REMOVE_ENTITY:
|
|
||||||
removeOperations.push(operation.entity);
|
|
||||||
break;
|
|
||||||
case IndexUpdateType.UPDATE_ENTITY:
|
|
||||||
if (operation.oldMask !== undefined && operation.newMask !== undefined) {
|
|
||||||
updateOperations.push({
|
|
||||||
entity: operation.entity,
|
|
||||||
oldMask: operation.oldMask,
|
|
||||||
newMask: operation.newMask
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 批量处理每种类型的操作
|
|
||||||
if (addOperations.length > 0) {
|
|
||||||
this.processBatchAdd(addOperations);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (removeOperations.length > 0) {
|
|
||||||
this.processBatchRemove(removeOperations);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (updateOperations.length > 0) {
|
|
||||||
this.processBatchUpdate(updateOperations);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 批量处理添加操作
|
|
||||||
*/
|
|
||||||
private processBatchAdd(entities: Entity[]): void {
|
|
||||||
// 这里应该调用QuerySystem的批量添加方法
|
|
||||||
// 由于需要访问QuerySystem,这个方法应该由外部注入处理函数
|
|
||||||
if (this.onBatchAdd) {
|
|
||||||
this.onBatchAdd(entities);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 批量处理移除操作
|
|
||||||
*/
|
|
||||||
private processBatchRemove(entities: Entity[]): void {
|
|
||||||
if (this.onBatchRemove) {
|
|
||||||
this.onBatchRemove(entities);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 批量处理更新操作
|
|
||||||
*/
|
|
||||||
private processBatchUpdate(updates: Array<{ entity: Entity; oldMask: bigint; newMask: bigint }>): void {
|
|
||||||
if (this.onBatchUpdate) {
|
|
||||||
this.onBatchUpdate(updates);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置批量大小
|
|
||||||
*/
|
|
||||||
setBatchSize(size: number): void {
|
|
||||||
this.batchSize = Math.max(1, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置刷新延迟
|
|
||||||
*/
|
|
||||||
setFlushDelay(delay: number): void {
|
|
||||||
this.flushDelay = Math.max(0, delay);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取待处理操作数量
|
|
||||||
*/
|
|
||||||
getPendingCount(): number {
|
|
||||||
return this.pendingOperations.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清空所有待处理操作
|
|
||||||
*/
|
|
||||||
clear(): void {
|
|
||||||
this.pendingOperations.length = 0;
|
|
||||||
if (this.flushTimeout) {
|
|
||||||
clearTimeout(this.flushTimeout);
|
|
||||||
this.flushTimeout = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查是否有待处理操作
|
|
||||||
*/
|
|
||||||
hasPendingOperations(): boolean {
|
|
||||||
return this.pendingOperations.length > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 回调函数,由外部设置
|
|
||||||
public onBatchAdd?: (entities: Entity[]) => void;
|
|
||||||
public onBatchRemove?: (entities: Entity[]) => void;
|
|
||||||
public onBatchUpdate?: (updates: Array<{ entity: Entity; oldMask: bigint; newMask: bigint }>) => void;
|
|
||||||
}
|
|
||||||
@@ -18,5 +18,4 @@ export {
|
|||||||
DirtyListener
|
DirtyListener
|
||||||
} from '../DirtyTrackingSystem';
|
} from '../DirtyTrackingSystem';
|
||||||
|
|
||||||
export { IndexUpdateBatcher } from '../IndexUpdateBatcher';
|
|
||||||
export { BitMaskOptimizer } from '../BitMaskOptimizer';
|
|
||||||
@@ -4,8 +4,6 @@ import { ComponentRegistry, ComponentType } from './ComponentStorage';
|
|||||||
import { IBigIntLike, BigIntFactory } from '../Utils/BigIntCompatibility';
|
import { IBigIntLike, BigIntFactory } from '../Utils/BigIntCompatibility';
|
||||||
|
|
||||||
import { ComponentPoolManager } from './ComponentPool';
|
import { ComponentPoolManager } from './ComponentPool';
|
||||||
import { BitMaskOptimizer } from './BitMaskOptimizer';
|
|
||||||
import { IndexUpdateBatcher } from './IndexUpdateBatcher';
|
|
||||||
import { ComponentIndexManager, IndexType } from './ComponentIndex';
|
import { ComponentIndexManager, IndexType } from './ComponentIndex';
|
||||||
import { ArchetypeSystem, Archetype, ArchetypeQueryResult } from './ArchetypeSystem';
|
import { ArchetypeSystem, Archetype, ArchetypeQueryResult } from './ArchetypeSystem';
|
||||||
import { DirtyTrackingSystem, DirtyFlag } from './DirtyTrackingSystem';
|
import { DirtyTrackingSystem, DirtyFlag } from './DirtyTrackingSystem';
|
||||||
@@ -98,8 +96,6 @@ export class QuerySystem {
|
|||||||
|
|
||||||
// 优化组件
|
// 优化组件
|
||||||
private componentPoolManager: ComponentPoolManager;
|
private componentPoolManager: ComponentPoolManager;
|
||||||
private bitMaskOptimizer: BitMaskOptimizer;
|
|
||||||
private indexUpdateBatcher: IndexUpdateBatcher;
|
|
||||||
|
|
||||||
// 新增性能优化系统
|
// 新增性能优化系统
|
||||||
private componentIndexManager: ComponentIndexManager;
|
private componentIndexManager: ComponentIndexManager;
|
||||||
@@ -126,33 +122,10 @@ export class QuerySystem {
|
|||||||
|
|
||||||
// 初始化优化组件
|
// 初始化优化组件
|
||||||
this.componentPoolManager = ComponentPoolManager.getInstance();
|
this.componentPoolManager = ComponentPoolManager.getInstance();
|
||||||
this.bitMaskOptimizer = BitMaskOptimizer.getInstance();
|
|
||||||
this.indexUpdateBatcher = new IndexUpdateBatcher();
|
|
||||||
|
|
||||||
// 初始化新的性能优化系统
|
// 初始化新的性能优化系统
|
||||||
this.componentIndexManager = new ComponentIndexManager(IndexType.HASH);
|
this.componentIndexManager = new ComponentIndexManager(IndexType.HASH);
|
||||||
this.archetypeSystem = new ArchetypeSystem();
|
this.archetypeSystem = new ArchetypeSystem();
|
||||||
this.dirtyTrackingSystem = new DirtyTrackingSystem();
|
this.dirtyTrackingSystem = new DirtyTrackingSystem();
|
||||||
|
|
||||||
// 设置索引更新批处理器的回调
|
|
||||||
this.indexUpdateBatcher.onBatchAdd = (entities) => {
|
|
||||||
for (const entity of entities) {
|
|
||||||
this.addEntityToIndexes(entity);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.indexUpdateBatcher.onBatchRemove = (entities) => {
|
|
||||||
for (const entity of entities) {
|
|
||||||
this.removeEntityFromIndexes(entity);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.indexUpdateBatcher.onBatchUpdate = (updates) => {
|
|
||||||
for (const update of updates) {
|
|
||||||
this.removeEntityFromIndexes(update.entity);
|
|
||||||
this.addEntityToIndexes(update.entity);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -13,18 +13,8 @@ import {
|
|||||||
isProtoSerializable,
|
isProtoSerializable,
|
||||||
getProtoName
|
getProtoName
|
||||||
} from './ProtobufDecorators';
|
} from './ProtobufDecorators';
|
||||||
|
import { SerializedData } from './SerializationTypes';
|
||||||
|
|
||||||
/**
|
|
||||||
* 序列化数据接口
|
|
||||||
*/
|
|
||||||
export interface SerializedData {
|
|
||||||
/** 组件类型名称 */
|
|
||||||
componentType: string;
|
|
||||||
/** 序列化后的数据 */
|
|
||||||
data: Uint8Array;
|
|
||||||
/** 数据大小(字节) */
|
|
||||||
size: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Protobuf序列化器
|
* Protobuf序列化器
|
||||||
@@ -143,6 +133,7 @@ export class ProtobufSerializer {
|
|||||||
const buffer = MessageType.encode(message).finish();
|
const buffer = MessageType.encode(message).finish();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
type: 'protobuf',
|
||||||
componentType: componentType,
|
componentType: componentType,
|
||||||
data: buffer,
|
data: buffer,
|
||||||
size: buffer.length
|
size: buffer.length
|
||||||
@@ -245,6 +236,7 @@ export class ProtobufSerializer {
|
|||||||
const buffer = MessageType.encode(message).finish();
|
const buffer = MessageType.encode(message).finish();
|
||||||
|
|
||||||
results.push({
|
results.push({
|
||||||
|
type: 'protobuf',
|
||||||
componentType: component.constructor.name,
|
componentType: component.constructor.name,
|
||||||
data: buffer,
|
data: buffer,
|
||||||
size: buffer.length
|
size: buffer.length
|
||||||
|
|||||||
17
src/Utils/Serialization/SerializationTypes.ts
Normal file
17
src/Utils/Serialization/SerializationTypes.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* 序列化类型定义
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 序列化数据接口
|
||||||
|
*/
|
||||||
|
export interface SerializedData {
|
||||||
|
/** 序列化类型 */
|
||||||
|
type: 'protobuf';
|
||||||
|
/** 组件类型名称 */
|
||||||
|
componentType: string;
|
||||||
|
/** 序列化后的数据 */
|
||||||
|
data: Uint8Array;
|
||||||
|
/** 数据大小(字节) */
|
||||||
|
size: number;
|
||||||
|
}
|
||||||
@@ -1,371 +0,0 @@
|
|||||||
/**
|
|
||||||
* 静态Protobuf序列化器
|
|
||||||
*
|
|
||||||
* 使用预生成的protobuf静态模块进行序列化
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Component } from '../../ECS/Component';
|
|
||||||
import {
|
|
||||||
ProtobufRegistry,
|
|
||||||
ProtoComponentDefinition,
|
|
||||||
isProtoSerializable,
|
|
||||||
getProtoName
|
|
||||||
} from './ProtobufDecorators';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 序列化数据接口
|
|
||||||
*/
|
|
||||||
export interface SerializedData {
|
|
||||||
/** 序列化类型 */
|
|
||||||
type: 'protobuf' | 'json';
|
|
||||||
/** 组件类型名称 */
|
|
||||||
componentType: string;
|
|
||||||
/** 序列化后的数据 */
|
|
||||||
data: Uint8Array | any;
|
|
||||||
/** 数据大小(字节) */
|
|
||||||
size: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 静态Protobuf序列化器
|
|
||||||
*
|
|
||||||
* 使用CLI预生成的protobuf静态模块
|
|
||||||
*/
|
|
||||||
export class StaticProtobufSerializer {
|
|
||||||
private registry: ProtobufRegistry;
|
|
||||||
private static instance: StaticProtobufSerializer;
|
|
||||||
|
|
||||||
/** 预生成的protobuf根对象 */
|
|
||||||
private protobufRoot: any = null;
|
|
||||||
private isInitialized: boolean = false;
|
|
||||||
|
|
||||||
private constructor() {
|
|
||||||
this.registry = ProtobufRegistry.getInstance();
|
|
||||||
this.initializeStaticProtobuf();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static getInstance(): StaticProtobufSerializer {
|
|
||||||
if (!StaticProtobufSerializer.instance) {
|
|
||||||
StaticProtobufSerializer.instance = new StaticProtobufSerializer();
|
|
||||||
}
|
|
||||||
return StaticProtobufSerializer.instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化静态protobuf模块
|
|
||||||
*/
|
|
||||||
private async initializeStaticProtobuf(): Promise<void> {
|
|
||||||
try {
|
|
||||||
// 尝试加载预生成的protobuf模块
|
|
||||||
const ecsProto = await this.loadGeneratedProtobuf();
|
|
||||||
if (ecsProto && ecsProto.ecs) {
|
|
||||||
this.protobufRoot = ecsProto.ecs;
|
|
||||||
this.isInitialized = true;
|
|
||||||
console.log('[StaticProtobufSerializer] 预生成的Protobuf模块已加载');
|
|
||||||
} else {
|
|
||||||
console.warn('[StaticProtobufSerializer] 未找到预生成的protobuf模块,将使用JSON序列化');
|
|
||||||
console.log('💡 请运行: npm run proto:build');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('[StaticProtobufSerializer] 初始化失败,将使用JSON序列化:', error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 加载预生成的protobuf模块
|
|
||||||
*/
|
|
||||||
private async loadGeneratedProtobuf(): Promise<any> {
|
|
||||||
const possiblePaths = [
|
|
||||||
// 项目中的生成路径
|
|
||||||
'./generated/ecs-components',
|
|
||||||
'../generated/ecs-components',
|
|
||||||
'../../generated/ecs-components',
|
|
||||||
// 相对于当前文件的路径
|
|
||||||
'../../../generated/ecs-components'
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const path of possiblePaths) {
|
|
||||||
try {
|
|
||||||
const module = await import(path);
|
|
||||||
return module;
|
|
||||||
} catch (error) {
|
|
||||||
// 继续尝试下一个路径
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果所有路径都失败,尝试require方式
|
|
||||||
for (const path of possiblePaths) {
|
|
||||||
try {
|
|
||||||
const module = require(path);
|
|
||||||
return module;
|
|
||||||
} catch (error) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 序列化组件
|
|
||||||
*/
|
|
||||||
public serialize(component: Component): SerializedData {
|
|
||||||
const componentType = component.constructor.name;
|
|
||||||
|
|
||||||
// 检查是否支持protobuf序列化
|
|
||||||
if (!isProtoSerializable(component) || !this.isInitialized) {
|
|
||||||
return this.fallbackToJSON(component);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const protoName = getProtoName(component);
|
|
||||||
if (!protoName) {
|
|
||||||
return this.fallbackToJSON(component);
|
|
||||||
}
|
|
||||||
|
|
||||||
const definition = this.registry.getComponentDefinition(protoName);
|
|
||||||
if (!definition) {
|
|
||||||
console.warn(`[StaticProtobufSerializer] 未找到组件定义: ${protoName}`);
|
|
||||||
return this.fallbackToJSON(component);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取对应的protobuf消息类型
|
|
||||||
const MessageType = this.protobufRoot[protoName];
|
|
||||||
if (!MessageType) {
|
|
||||||
console.warn(`[StaticProtobufSerializer] 未找到protobuf消息类型: ${protoName}`);
|
|
||||||
return this.fallbackToJSON(component);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 构建protobuf数据对象
|
|
||||||
const protoData = this.buildProtoData(component, definition);
|
|
||||||
|
|
||||||
// 验证数据
|
|
||||||
const error = MessageType.verify(protoData);
|
|
||||||
if (error) {
|
|
||||||
console.warn(`[StaticProtobufSerializer] 数据验证失败: ${error}`);
|
|
||||||
return this.fallbackToJSON(component);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建消息并编码
|
|
||||||
const message = MessageType.create(protoData);
|
|
||||||
const buffer = MessageType.encode(message).finish();
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: 'protobuf',
|
|
||||||
componentType: componentType,
|
|
||||||
data: buffer,
|
|
||||||
size: buffer.length
|
|
||||||
};
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.warn(`[StaticProtobufSerializer] 序列化失败,回退到JSON: ${componentType}`, error);
|
|
||||||
return this.fallbackToJSON(component);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 反序列化组件
|
|
||||||
*/
|
|
||||||
public deserialize(component: Component, serializedData: SerializedData): void {
|
|
||||||
if (serializedData.type === 'json') {
|
|
||||||
this.deserializeFromJSON(component, serializedData.data);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.isInitialized) {
|
|
||||||
console.warn('[StaticProtobufSerializer] Protobuf未初始化,无法反序列化');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const protoName = getProtoName(component);
|
|
||||||
if (!protoName) {
|
|
||||||
this.deserializeFromJSON(component, serializedData.data);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const MessageType = this.protobufRoot[protoName];
|
|
||||||
if (!MessageType) {
|
|
||||||
console.warn(`[StaticProtobufSerializer] 反序列化时未找到消息类型: ${protoName}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解码消息
|
|
||||||
const message = MessageType.decode(serializedData.data as Uint8Array);
|
|
||||||
const data = MessageType.toObject(message);
|
|
||||||
|
|
||||||
// 应用数据到组件
|
|
||||||
this.applyDataToComponent(component, data);
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.warn(`[StaticProtobufSerializer] 反序列化失败: ${component.constructor.name}`, error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查组件是否支持protobuf序列化
|
|
||||||
*/
|
|
||||||
public canSerialize(component: Component): boolean {
|
|
||||||
return this.isInitialized && isProtoSerializable(component);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取序列化统计信息
|
|
||||||
*/
|
|
||||||
public getStats(): {
|
|
||||||
registeredComponents: number;
|
|
||||||
protobufAvailable: boolean;
|
|
||||||
initialized: boolean;
|
|
||||||
} {
|
|
||||||
return {
|
|
||||||
registeredComponents: this.registry.getAllComponents().size,
|
|
||||||
protobufAvailable: !!this.protobufRoot,
|
|
||||||
initialized: this.isInitialized
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 手动设置protobuf根对象(用于测试)
|
|
||||||
*/
|
|
||||||
public setProtobufRoot(root: any): void {
|
|
||||||
this.protobufRoot = root;
|
|
||||||
this.isInitialized = !!root;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 构建protobuf数据对象
|
|
||||||
*/
|
|
||||||
private buildProtoData(component: Component, definition: ProtoComponentDefinition): any {
|
|
||||||
const data: any = {};
|
|
||||||
|
|
||||||
for (const [propertyName, fieldDef] of definition.fields) {
|
|
||||||
const value = (component as any)[propertyName];
|
|
||||||
|
|
||||||
if (value !== undefined && value !== null) {
|
|
||||||
data[fieldDef.name] = this.convertValueToProtoType(value, fieldDef.type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 转换值到protobuf类型
|
|
||||||
*/
|
|
||||||
private convertValueToProtoType(value: any, type: string): any {
|
|
||||||
switch (type) {
|
|
||||||
case 'int32':
|
|
||||||
case 'uint32':
|
|
||||||
case 'sint32':
|
|
||||||
case 'fixed32':
|
|
||||||
case 'sfixed32':
|
|
||||||
return parseInt(value) || 0;
|
|
||||||
|
|
||||||
case 'float':
|
|
||||||
case 'double':
|
|
||||||
return parseFloat(value) || 0;
|
|
||||||
|
|
||||||
case 'bool':
|
|
||||||
return Boolean(value);
|
|
||||||
|
|
||||||
case 'string':
|
|
||||||
return String(value);
|
|
||||||
|
|
||||||
default:
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 应用数据到组件
|
|
||||||
*/
|
|
||||||
private applyDataToComponent(component: Component, data: any): void {
|
|
||||||
const protoName = getProtoName(component);
|
|
||||||
if (!protoName) return;
|
|
||||||
|
|
||||||
const definition = this.registry.getComponentDefinition(protoName);
|
|
||||||
if (!definition) return;
|
|
||||||
|
|
||||||
for (const [propertyName, fieldDef] of definition.fields) {
|
|
||||||
const value = data[fieldDef.name];
|
|
||||||
if (value !== undefined) {
|
|
||||||
(component as any)[propertyName] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 回退到JSON序列化
|
|
||||||
*/
|
|
||||||
private fallbackToJSON(component: Component): SerializedData {
|
|
||||||
const data = this.defaultJSONSerialize(component);
|
|
||||||
const jsonString = JSON.stringify(data);
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: 'json',
|
|
||||||
componentType: component.constructor.name,
|
|
||||||
data: data,
|
|
||||||
size: new Blob([jsonString]).size
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 默认JSON序列化
|
|
||||||
*/
|
|
||||||
private defaultJSONSerialize(component: Component): any {
|
|
||||||
const data: any = {};
|
|
||||||
|
|
||||||
for (const key in component) {
|
|
||||||
if (component.hasOwnProperty(key) &&
|
|
||||||
typeof (component as any)[key] !== 'function' &&
|
|
||||||
key !== 'id' &&
|
|
||||||
key !== 'entity' &&
|
|
||||||
key !== '_enabled' &&
|
|
||||||
key !== '_updateOrder') {
|
|
||||||
|
|
||||||
const value = (component as any)[key];
|
|
||||||
if (this.isSerializableValue(value)) {
|
|
||||||
data[key] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JSON反序列化
|
|
||||||
*/
|
|
||||||
private deserializeFromJSON(component: Component, data: any): void {
|
|
||||||
for (const key in data) {
|
|
||||||
if (component.hasOwnProperty(key) &&
|
|
||||||
typeof (component as any)[key] !== 'function' &&
|
|
||||||
key !== 'id' &&
|
|
||||||
key !== 'entity' &&
|
|
||||||
key !== '_enabled' &&
|
|
||||||
key !== '_updateOrder') {
|
|
||||||
|
|
||||||
(component as any)[key] = data[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查值是否可序列化
|
|
||||||
*/
|
|
||||||
private isSerializableValue(value: any): boolean {
|
|
||||||
if (value === null || value === undefined) return true;
|
|
||||||
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') return true;
|
|
||||||
if (Array.isArray(value)) return value.every(v => this.isSerializableValue(v));
|
|
||||||
if (typeof value === 'object') {
|
|
||||||
try {
|
|
||||||
JSON.stringify(value);
|
|
||||||
return true;
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,6 +2,6 @@
|
|||||||
* 序列化模块导出
|
* 序列化模块导出
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
export * from './SerializationTypes';
|
||||||
export * from './ProtobufDecorators';
|
export * from './ProtobufDecorators';
|
||||||
export * from './ProtobufSerializer';
|
export * from './ProtobufSerializer';
|
||||||
export * from './StaticProtobufSerializer';
|
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
import { Entity } from '../../ECS/Entity';
|
import { Entity } from '../../ECS/Entity';
|
||||||
import { Component } from '../../ECS/Component';
|
import { Component } from '../../ECS/Component';
|
||||||
import { ISnapshotable, SceneSnapshot, EntitySnapshot, ComponentSnapshot, SnapshotConfig } from './ISnapshotable';
|
import { ISnapshotable, SceneSnapshot, EntitySnapshot, ComponentSnapshot, SnapshotConfig } from './ISnapshotable';
|
||||||
import { ProtobufSerializer, SerializedData } from '../Serialization/ProtobufSerializer';
|
import { ProtobufSerializer } from '../Serialization/ProtobufSerializer';
|
||||||
|
import { SerializedData } from '../Serialization/SerializationTypes';
|
||||||
import { isProtoSerializable } from '../Serialization/ProtobufDecorators';
|
import { isProtoSerializable } from '../Serialization/ProtobufDecorators';
|
||||||
|
import { ComponentRegistry } from '../../ECS/Core/ComponentStorage';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 快照管理器
|
* 快照管理器
|
||||||
@@ -41,6 +43,7 @@ export class SnapshotManager {
|
|||||||
this.protobufSerializer = ProtobufSerializer.getInstance();
|
this.protobufSerializer = ProtobufSerializer.getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建场景快照
|
* 创建场景快照
|
||||||
*
|
*
|
||||||
@@ -183,9 +186,7 @@ export class SnapshotManager {
|
|||||||
* 获取组件类型
|
* 获取组件类型
|
||||||
*/
|
*/
|
||||||
private getComponentType(typeName: string): any {
|
private getComponentType(typeName: string): any {
|
||||||
// 这里需要与组件注册系统集成
|
return ComponentRegistry.getComponentType(typeName);
|
||||||
// 暂时返回null,实际实现需要组件类型管理器
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,333 +0,0 @@
|
|||||||
import { BitMaskOptimizer } from '../../../src/ECS/Core/BitMaskOptimizer';
|
|
||||||
import { BigIntFactory } from '../../../src/ECS/Utils/BigIntCompatibility';
|
|
||||||
|
|
||||||
describe('BitMaskOptimizer - 位掩码优化器测试', () => {
|
|
||||||
let optimizer: BitMaskOptimizer;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
optimizer = BitMaskOptimizer.getInstance();
|
|
||||||
optimizer.reset(); // 确保每个测试开始时都是干净的状态
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('单例模式测试', () => {
|
|
||||||
it('应该返回同一个实例', () => {
|
|
||||||
const instance1 = BitMaskOptimizer.getInstance();
|
|
||||||
const instance2 = BitMaskOptimizer.getInstance();
|
|
||||||
expect(instance1).toBe(instance2);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('组件类型注册', () => {
|
|
||||||
it('应该能够注册组件类型', () => {
|
|
||||||
const id = optimizer.registerComponentType('Transform');
|
|
||||||
expect(id).toBe(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('重复注册相同组件应该返回相同ID', () => {
|
|
||||||
const id1 = optimizer.registerComponentType('Transform');
|
|
||||||
const id2 = optimizer.registerComponentType('Transform');
|
|
||||||
expect(id1).toBe(id2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('应该能够注册多个不同组件类型', () => {
|
|
||||||
const id1 = optimizer.registerComponentType('Transform');
|
|
||||||
const id2 = optimizer.registerComponentType('Velocity');
|
|
||||||
const id3 = optimizer.registerComponentType('Health');
|
|
||||||
|
|
||||||
expect(id1).toBe(0);
|
|
||||||
expect(id2).toBe(1);
|
|
||||||
expect(id3).toBe(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('应该能够获取已注册组件的类型ID', () => {
|
|
||||||
optimizer.registerComponentType('Transform');
|
|
||||||
const id = optimizer.getComponentTypeId('Transform');
|
|
||||||
expect(id).toBe(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('获取未注册组件的类型ID应该返回undefined', () => {
|
|
||||||
const id = optimizer.getComponentTypeId('UnknownComponent');
|
|
||||||
expect(id).toBeUndefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('单组件掩码创建', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
optimizer.registerComponentType('Transform');
|
|
||||||
optimizer.registerComponentType('Velocity');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('应该能够创建单个组件的掩码', () => {
|
|
||||||
const mask = optimizer.createSingleComponentMask('Transform');
|
|
||||||
expect(mask.toString()).toBe('1');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('不同组件应该有不同的掩码', () => {
|
|
||||||
const transformMask = optimizer.createSingleComponentMask('Transform');
|
|
||||||
const velocityMask = optimizer.createSingleComponentMask('Velocity');
|
|
||||||
|
|
||||||
expect(transformMask.toString()).toBe('1');
|
|
||||||
expect(velocityMask.toString()).toBe('2');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('创建未注册组件的掩码应该抛出错误', () => {
|
|
||||||
expect(() => {
|
|
||||||
optimizer.createSingleComponentMask('UnknownComponent');
|
|
||||||
}).toThrow('Component type not registered: UnknownComponent');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('相同组件的掩码应该使用缓存', () => {
|
|
||||||
const mask1 = optimizer.createSingleComponentMask('Transform');
|
|
||||||
const mask2 = optimizer.createSingleComponentMask('Transform');
|
|
||||||
expect(mask1).toBe(mask2);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('组合掩码创建', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
optimizer.registerComponentType('Transform');
|
|
||||||
optimizer.registerComponentType('Velocity');
|
|
||||||
optimizer.registerComponentType('Health');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('应该能够创建多个组件的组合掩码', () => {
|
|
||||||
const mask = optimizer.createCombinedMask(['Transform', 'Velocity']);
|
|
||||||
expect(mask.toString()).toBe('3'); // 1 | 2 = 3
|
|
||||||
});
|
|
||||||
|
|
||||||
it('组件顺序不应该影响掩码结果', () => {
|
|
||||||
const mask1 = optimizer.createCombinedMask(['Transform', 'Velocity']);
|
|
||||||
const mask2 = optimizer.createCombinedMask(['Velocity', 'Transform']);
|
|
||||||
expect(mask1).toBe(mask2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('三个组件的组合掩码应该正确', () => {
|
|
||||||
const mask = optimizer.createCombinedMask(['Transform', 'Velocity', 'Health']);
|
|
||||||
expect(mask.toString()).toBe('7'); // 1 | 2 | 4 = 7
|
|
||||||
});
|
|
||||||
|
|
||||||
it('空数组应该返回0掩码', () => {
|
|
||||||
const mask = optimizer.createCombinedMask([]);
|
|
||||||
expect(mask.isZero()).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('包含未注册组件应该抛出错误', () => {
|
|
||||||
expect(() => {
|
|
||||||
optimizer.createCombinedMask(['Transform', 'UnknownComponent']);
|
|
||||||
}).toThrow('Component type not registered: UnknownComponent');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('掩码检查功能', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
optimizer.registerComponentType('Transform');
|
|
||||||
optimizer.registerComponentType('Velocity');
|
|
||||||
optimizer.registerComponentType('Health');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('应该能够检查掩码是否包含指定组件', () => {
|
|
||||||
const mask = optimizer.createCombinedMask(['Transform', 'Velocity']);
|
|
||||||
|
|
||||||
expect(optimizer.maskContainsComponent(mask, 'Transform')).toBe(true);
|
|
||||||
expect(optimizer.maskContainsComponent(mask, 'Velocity')).toBe(true);
|
|
||||||
expect(optimizer.maskContainsComponent(mask, 'Health')).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('应该能够检查掩码是否包含所有指定组件', () => {
|
|
||||||
const mask = optimizer.createCombinedMask(['Transform', 'Velocity', 'Health']);
|
|
||||||
|
|
||||||
expect(optimizer.maskContainsAllComponents(mask, ['Transform'])).toBe(true);
|
|
||||||
expect(optimizer.maskContainsAllComponents(mask, ['Transform', 'Velocity'])).toBe(true);
|
|
||||||
expect(optimizer.maskContainsAllComponents(mask, ['Transform', 'Velocity', 'Health'])).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('不完全包含时maskContainsAllComponents应该返回false', () => {
|
|
||||||
const mask = optimizer.createCombinedMask(['Transform']);
|
|
||||||
|
|
||||||
expect(optimizer.maskContainsAllComponents(mask, ['Transform', 'Velocity'])).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('应该能够检查掩码是否包含任一指定组件', () => {
|
|
||||||
const mask = optimizer.createCombinedMask(['Transform']);
|
|
||||||
|
|
||||||
expect(optimizer.maskContainsAnyComponent(mask, ['Transform', 'Velocity'])).toBe(true);
|
|
||||||
expect(optimizer.maskContainsAnyComponent(mask, ['Velocity', 'Health'])).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('掩码操作功能', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
optimizer.registerComponentType('Transform');
|
|
||||||
optimizer.registerComponentType('Velocity');
|
|
||||||
optimizer.registerComponentType('Health');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('应该能够向掩码添加组件', () => {
|
|
||||||
let mask = optimizer.createSingleComponentMask('Transform');
|
|
||||||
mask = optimizer.addComponentToMask(mask, 'Velocity');
|
|
||||||
|
|
||||||
expect(optimizer.maskContainsComponent(mask, 'Transform')).toBe(true);
|
|
||||||
expect(optimizer.maskContainsComponent(mask, 'Velocity')).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('应该能够从掩码移除组件', () => {
|
|
||||||
let mask = optimizer.createCombinedMask(['Transform', 'Velocity']);
|
|
||||||
mask = optimizer.removeComponentFromMask(mask, 'Velocity');
|
|
||||||
|
|
||||||
expect(optimizer.maskContainsComponent(mask, 'Transform')).toBe(true);
|
|
||||||
expect(optimizer.maskContainsComponent(mask, 'Velocity')).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('移除不存在的组件不应该影响掩码', () => {
|
|
||||||
const originalMask = optimizer.createSingleComponentMask('Transform');
|
|
||||||
const newMask = optimizer.removeComponentFromMask(originalMask, 'Velocity');
|
|
||||||
|
|
||||||
expect(newMask.equals(originalMask)).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('工具功能', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
optimizer.registerComponentType('Transform');
|
|
||||||
optimizer.registerComponentType('Velocity');
|
|
||||||
optimizer.registerComponentType('Health');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('应该能够将掩码转换为组件名称数组', () => {
|
|
||||||
const mask = optimizer.createCombinedMask(['Transform', 'Health']);
|
|
||||||
const componentNames = optimizer.maskToComponentNames(mask);
|
|
||||||
|
|
||||||
expect(componentNames).toContain('Transform');
|
|
||||||
expect(componentNames).toContain('Health');
|
|
||||||
expect(componentNames).not.toContain('Velocity');
|
|
||||||
expect(componentNames.length).toBe(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('空掩码应该返回空数组', () => {
|
|
||||||
const componentNames = optimizer.maskToComponentNames(BigIntFactory.zero());
|
|
||||||
expect(componentNames).toEqual([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('应该能够获取掩码中组件的数量', () => {
|
|
||||||
const mask1 = optimizer.createSingleComponentMask('Transform');
|
|
||||||
const mask2 = optimizer.createCombinedMask(['Transform', 'Velocity']);
|
|
||||||
const mask3 = optimizer.createCombinedMask(['Transform', 'Velocity', 'Health']);
|
|
||||||
|
|
||||||
expect(optimizer.getComponentCount(mask1)).toBe(1);
|
|
||||||
expect(optimizer.getComponentCount(mask2)).toBe(2);
|
|
||||||
expect(optimizer.getComponentCount(mask3)).toBe(3);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('空掩码的组件数量应该为0', () => {
|
|
||||||
expect(optimizer.getComponentCount(BigIntFactory.zero())).toBe(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('缓存和性能优化', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
optimizer.registerComponentType('Transform');
|
|
||||||
optimizer.registerComponentType('Velocity');
|
|
||||||
optimizer.registerComponentType('Health');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('应该能够预计算常用掩码组合', () => {
|
|
||||||
const commonCombinations = [
|
|
||||||
['Transform', 'Velocity'],
|
|
||||||
['Transform', 'Health'],
|
|
||||||
['Velocity', 'Health']
|
|
||||||
];
|
|
||||||
|
|
||||||
optimizer.precomputeCommonMasks(commonCombinations);
|
|
||||||
|
|
||||||
// 验证掩码已被缓存
|
|
||||||
const stats = optimizer.getCacheStats();
|
|
||||||
expect(stats.size).toBeGreaterThan(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('应该能够获取缓存统计信息', () => {
|
|
||||||
optimizer.createSingleComponentMask('Transform');
|
|
||||||
optimizer.createCombinedMask(['Transform', 'Velocity']);
|
|
||||||
|
|
||||||
const stats = optimizer.getCacheStats();
|
|
||||||
expect(stats.size).toBeGreaterThan(0);
|
|
||||||
expect(stats.componentTypes).toBe(3);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('应该能够清空缓存', () => {
|
|
||||||
optimizer.createSingleComponentMask('Transform');
|
|
||||||
optimizer.clearCache();
|
|
||||||
|
|
||||||
const stats = optimizer.getCacheStats();
|
|
||||||
expect(stats.size).toBe(0);
|
|
||||||
expect(stats.componentTypes).toBe(3); // 组件类型不会被清除
|
|
||||||
});
|
|
||||||
|
|
||||||
it('应该能够重置优化器', () => {
|
|
||||||
optimizer.registerComponentType('NewComponent');
|
|
||||||
optimizer.createSingleComponentMask('Transform');
|
|
||||||
|
|
||||||
optimizer.reset();
|
|
||||||
|
|
||||||
const stats = optimizer.getCacheStats();
|
|
||||||
expect(stats.size).toBe(0);
|
|
||||||
expect(stats.componentTypes).toBe(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('边界情况和错误处理', () => {
|
|
||||||
it('处理大量组件类型注册', () => {
|
|
||||||
for (let i = 0; i < 100; i++) {
|
|
||||||
const id = optimizer.registerComponentType(`Component${i}`);
|
|
||||||
expect(id).toBe(i);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('处理大掩码值', () => {
|
|
||||||
// 注册64个组件类型
|
|
||||||
for (let i = 0; i < 64; i++) {
|
|
||||||
optimizer.registerComponentType(`Component${i}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const mask = optimizer.createSingleComponentMask('Component63');
|
|
||||||
expect(mask.toString()).toBe(BigIntFactory.one().shiftLeft(63).toString());
|
|
||||||
});
|
|
||||||
|
|
||||||
it('空组件名称数组的组合掩码', () => {
|
|
||||||
const mask = optimizer.createCombinedMask([]);
|
|
||||||
expect(mask.isZero()).toBe(true);
|
|
||||||
expect(optimizer.getComponentCount(mask)).toBe(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('性能测试', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
// 注册一些组件类型
|
|
||||||
for (let i = 0; i < 10; i++) {
|
|
||||||
optimizer.registerComponentType(`Component${i}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('大量掩码创建应该高效', () => {
|
|
||||||
const startTime = performance.now();
|
|
||||||
|
|
||||||
for (let i = 0; i < 1000; i++) {
|
|
||||||
optimizer.createSingleComponentMask('Component0');
|
|
||||||
}
|
|
||||||
|
|
||||||
const endTime = performance.now();
|
|
||||||
expect(endTime - startTime).toBeLessThan(100); // 应该在100ms内完成
|
|
||||||
});
|
|
||||||
|
|
||||||
it('大量掩码检查应该高效', () => {
|
|
||||||
const mask = optimizer.createCombinedMask(['Component0', 'Component1', 'Component2']);
|
|
||||||
const startTime = performance.now();
|
|
||||||
|
|
||||||
for (let i = 0; i < 10000; i++) {
|
|
||||||
optimizer.maskContainsComponent(mask, 'Component1');
|
|
||||||
}
|
|
||||||
|
|
||||||
const endTime = performance.now();
|
|
||||||
expect(endTime - startTime).toBeLessThan(100); // 应该在100ms内完成
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Reference in New Issue
Block a user