diff --git a/assets/svg/ecs-architecture.svg b/assets/svg/ecs-architecture.svg index 014aa430..09c8aef6 100644 --- a/assets/svg/ecs-architecture.svg +++ b/assets/svg/ecs-architecture.svg @@ -71,7 +71,7 @@ ComponentRegistry • IdentifierPool - BitMaskOptimizer • ConfigManager + EventBus • ConfigManager diff --git a/docs/bitmask-guide.md b/docs/bitmask-guide.md deleted file mode 100644 index eb647986..00000000 --- a/docs/bitmask-guide.md +++ /dev/null @@ -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 { - const classified = new Map(); - - 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. **灵活的查询构建** - 支持复杂的组件组合查询 - -通过理解和正确使用位掩码,可以显著提升游戏的性能表现。记住要在游戏初始化时注册组件类型,预计算常用掩码,并合理管理缓存。 \ No newline at end of file diff --git a/src/ECS/Core/BitMaskOptimizer.ts b/src/ECS/Core/BitMaskOptimizer.ts deleted file mode 100644 index c7465375..00000000 --- a/src/ECS/Core/BitMaskOptimizer.ts +++ /dev/null @@ -1,214 +0,0 @@ -import { IBigIntLike, BigIntFactory } from '../Utils/BigIntCompatibility'; - -/** - * 位掩码优化器,用于预计算和缓存常用的组件掩码 - * - * 使用BigInt兼容层确保在所有平台上的正常运行。 - */ -export class BitMaskOptimizer { - private static instance: BitMaskOptimizer; - private maskCache = new Map(); - private componentTypeMap = new Map(); - 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; - } -} \ No newline at end of file diff --git a/src/ECS/Core/ComponentStorage.ts b/src/ECS/Core/ComponentStorage.ts index c8157892..68f72926 100644 --- a/src/ECS/Core/ComponentStorage.ts +++ b/src/ECS/Core/ComponentStorage.ts @@ -16,6 +16,9 @@ export type ComponentType = new (...args: unkno */ export class ComponentRegistry { private static componentTypes = new Map(); + private static componentNameToType = new Map(); + private static componentNameToId = new Map(); + private static maskCache = new Map(); 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 { + 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; } } diff --git a/src/ECS/Core/IndexUpdateBatcher.ts b/src/ECS/Core/IndexUpdateBatcher.ts deleted file mode 100644 index 0032405f..00000000 --- a/src/ECS/Core/IndexUpdateBatcher.ts +++ /dev/null @@ -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; -} \ No newline at end of file diff --git a/src/ECS/Core/Performance/index.ts b/src/ECS/Core/Performance/index.ts index fb39c584..8c06a738 100644 --- a/src/ECS/Core/Performance/index.ts +++ b/src/ECS/Core/Performance/index.ts @@ -18,5 +18,4 @@ export { DirtyListener } from '../DirtyTrackingSystem'; -export { IndexUpdateBatcher } from '../IndexUpdateBatcher'; -export { BitMaskOptimizer } from '../BitMaskOptimizer'; \ No newline at end of file + \ No newline at end of file diff --git a/src/ECS/Core/QuerySystem.ts b/src/ECS/Core/QuerySystem.ts index 09154fb7..acf48b84 100644 --- a/src/ECS/Core/QuerySystem.ts +++ b/src/ECS/Core/QuerySystem.ts @@ -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); - } - }; } diff --git a/src/Utils/Serialization/ProtobufSerializer.ts b/src/Utils/Serialization/ProtobufSerializer.ts index 1712257c..f0a40612 100644 --- a/src/Utils/Serialization/ProtobufSerializer.ts +++ b/src/Utils/Serialization/ProtobufSerializer.ts @@ -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 diff --git a/src/Utils/Serialization/SerializationTypes.ts b/src/Utils/Serialization/SerializationTypes.ts new file mode 100644 index 00000000..6acf54a9 --- /dev/null +++ b/src/Utils/Serialization/SerializationTypes.ts @@ -0,0 +1,17 @@ +/** + * 序列化类型定义 + */ + +/** + * 序列化数据接口 + */ +export interface SerializedData { + /** 序列化类型 */ + type: 'protobuf'; + /** 组件类型名称 */ + componentType: string; + /** 序列化后的数据 */ + data: Uint8Array; + /** 数据大小(字节) */ + size: number; +} \ No newline at end of file diff --git a/src/Utils/Serialization/StaticProtobufSerializer.ts b/src/Utils/Serialization/StaticProtobufSerializer.ts deleted file mode 100644 index 16c44dff..00000000 --- a/src/Utils/Serialization/StaticProtobufSerializer.ts +++ /dev/null @@ -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 { - 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 { - 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; - } -} \ No newline at end of file diff --git a/src/Utils/Serialization/index.ts b/src/Utils/Serialization/index.ts index ef5a7a3e..b66d983c 100644 --- a/src/Utils/Serialization/index.ts +++ b/src/Utils/Serialization/index.ts @@ -2,6 +2,6 @@ * 序列化模块导出 */ +export * from './SerializationTypes'; export * from './ProtobufDecorators'; -export * from './ProtobufSerializer'; -export * from './StaticProtobufSerializer'; \ No newline at end of file +export * from './ProtobufSerializer'; \ No newline at end of file diff --git a/src/Utils/Snapshot/SnapshotManager.ts b/src/Utils/Snapshot/SnapshotManager.ts index 8b4cf58d..8219fbe0 100644 --- a/src/Utils/Snapshot/SnapshotManager.ts +++ b/src/Utils/Snapshot/SnapshotManager.ts @@ -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); } /** diff --git a/tests/ECS/Core/BitMaskOptimizer.test.ts b/tests/ECS/Core/BitMaskOptimizer.test.ts deleted file mode 100644 index 08ae141d..00000000 --- a/tests/ECS/Core/BitMaskOptimizer.test.ts +++ /dev/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内完成 - }); - }); -}); \ No newline at end of file