整合组件类型至统一的componentregistry中
This commit is contained in:
@@ -71,7 +71,7 @@
|
||||
ComponentRegistry • IdentifierPool
|
||||
</text>
|
||||
<text x="700" y="105" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="white">
|
||||
BitMaskOptimizer • ConfigManager
|
||||
EventBus • ConfigManager
|
||||
</text>
|
||||
|
||||
</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 {
|
||||
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 maxComponents = 64; // 支持最多64种组件类型
|
||||
|
||||
@@ -35,6 +38,8 @@ export class ComponentRegistry {
|
||||
|
||||
const bitIndex = this.nextBitIndex++;
|
||||
this.componentTypes.set(componentType, bitIndex);
|
||||
this.componentNameToType.set(componentType.name, componentType);
|
||||
this.componentNameToId.set(componentType.name, bitIndex);
|
||||
return bitIndex;
|
||||
}
|
||||
|
||||
@@ -73,6 +78,15 @@ export class ComponentRegistry {
|
||||
return this.componentTypes.has(componentType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过名称获取组件类型
|
||||
* @param componentName 组件名称
|
||||
* @returns 组件类型构造函数
|
||||
*/
|
||||
public static getComponentType(componentName: string): Function | null {
|
||||
return this.componentNameToType.get(componentName) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有已注册的组件类型
|
||||
* @returns 组件类型映射
|
||||
@@ -81,11 +95,104 @@ export class ComponentRegistry {
|
||||
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 {
|
||||
this.componentTypes.clear();
|
||||
this.componentNameToType.clear();
|
||||
this.componentNameToId.clear();
|
||||
this.maskCache.clear();
|
||||
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
|
||||
} 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 { ComponentPoolManager } from './ComponentPool';
|
||||
import { BitMaskOptimizer } from './BitMaskOptimizer';
|
||||
import { IndexUpdateBatcher } from './IndexUpdateBatcher';
|
||||
import { ComponentIndexManager, IndexType } from './ComponentIndex';
|
||||
import { ArchetypeSystem, Archetype, ArchetypeQueryResult } from './ArchetypeSystem';
|
||||
import { DirtyTrackingSystem, DirtyFlag } from './DirtyTrackingSystem';
|
||||
@@ -98,8 +96,6 @@ export class QuerySystem {
|
||||
|
||||
// 优化组件
|
||||
private componentPoolManager: ComponentPoolManager;
|
||||
private bitMaskOptimizer: BitMaskOptimizer;
|
||||
private indexUpdateBatcher: IndexUpdateBatcher;
|
||||
|
||||
// 新增性能优化系统
|
||||
private componentIndexManager: ComponentIndexManager;
|
||||
@@ -126,33 +122,10 @@ export class QuerySystem {
|
||||
|
||||
// 初始化优化组件
|
||||
this.componentPoolManager = ComponentPoolManager.getInstance();
|
||||
this.bitMaskOptimizer = BitMaskOptimizer.getInstance();
|
||||
this.indexUpdateBatcher = new IndexUpdateBatcher();
|
||||
|
||||
// 初始化新的性能优化系统
|
||||
this.componentIndexManager = new ComponentIndexManager(IndexType.HASH);
|
||||
this.archetypeSystem = new ArchetypeSystem();
|
||||
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,
|
||||
getProtoName
|
||||
} from './ProtobufDecorators';
|
||||
import { SerializedData } from './SerializationTypes';
|
||||
|
||||
/**
|
||||
* 序列化数据接口
|
||||
*/
|
||||
export interface SerializedData {
|
||||
/** 组件类型名称 */
|
||||
componentType: string;
|
||||
/** 序列化后的数据 */
|
||||
data: Uint8Array;
|
||||
/** 数据大小(字节) */
|
||||
size: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Protobuf序列化器
|
||||
@@ -143,6 +133,7 @@ export class ProtobufSerializer {
|
||||
const buffer = MessageType.encode(message).finish();
|
||||
|
||||
return {
|
||||
type: 'protobuf',
|
||||
componentType: componentType,
|
||||
data: buffer,
|
||||
size: buffer.length
|
||||
@@ -245,6 +236,7 @@ export class ProtobufSerializer {
|
||||
const buffer = MessageType.encode(message).finish();
|
||||
|
||||
results.push({
|
||||
type: 'protobuf',
|
||||
componentType: component.constructor.name,
|
||||
data: buffer,
|
||||
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 './ProtobufSerializer';
|
||||
export * from './StaticProtobufSerializer';
|
||||
export * from './ProtobufSerializer';
|
||||
@@ -1,8 +1,10 @@
|
||||
import { Entity } from '../../ECS/Entity';
|
||||
import { Component } from '../../ECS/Component';
|
||||
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 { ComponentRegistry } from '../../ECS/Core/ComponentStorage';
|
||||
|
||||
/**
|
||||
* 快照管理器
|
||||
@@ -41,6 +43,7 @@ export class SnapshotManager {
|
||||
this.protobufSerializer = ProtobufSerializer.getInstance();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 创建场景快照
|
||||
*
|
||||
@@ -183,9 +186,7 @@ export class SnapshotManager {
|
||||
* 获取组件类型
|
||||
*/
|
||||
private getComponentType(typeName: string): any {
|
||||
// 这里需要与组件注册系统集成
|
||||
// 暂时返回null,实际实现需要组件类型管理器
|
||||
return null;
|
||||
return ComponentRegistry.getComponentType(typeName);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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