From 0aa4791cf7629ac97551e52277c2368087e5cc40 Mon Sep 17 00:00:00 2001 From: YHH <359807859@qq.com> Date: Sun, 8 Jun 2025 10:20:51 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=96=87=E6=A1=A3=E5=B9=B6?= =?UTF-8?q?=E9=A2=84=E7=95=99wasm=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 42 +- docs/entity-guide.md | 465 +++++++++++++++++++++++ docs/getting-started.md | 1 + source/build.config.js | 93 ++++- source/src/Utils/AccelerationProvider.ts | 409 ++++++++++++++++++++ source/src/Utils/WasmBridge.ts | 439 +++++++++++++++++++++ source/tsconfig.json | 2 +- 7 files changed, 1428 insertions(+), 23 deletions(-) create mode 100644 docs/entity-guide.md create mode 100644 source/src/Utils/AccelerationProvider.ts create mode 100644 source/src/Utils/WasmBridge.ts diff --git a/README.md b/README.md index 75987130..fb4174f4 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,6 @@ - 🔍 **查询系统** - 基于位掩码的高性能实体查询 - 🛠️ **性能监控** - 内置性能监控工具,帮助优化游戏性能 - 🎯 **对象池** - 内存管理优化,减少垃圾回收压力 -- 🎯 **纯ECS架构** - 专注于实体组件系统核心逻辑 ## 📦 安装 @@ -78,17 +77,15 @@ function gameLoop() { ### 2. 创建场景 ```typescript -import { Scene, Vector2, EntitySystem } from '@esengine/ecs-framework'; +import { Scene, EntitySystem } from '@esengine/ecs-framework'; class GameScene extends Scene { public initialize() { // 创建玩家实体 const player = this.createEntity("Player"); - // 设置位置 - player.position = new Vector2(100, 100); - // 添加自定义组件 + const position = player.addComponent(new PositionComponent(100, 100)); const movement = player.addComponent(new MovementComponent()); // 添加系统 @@ -107,19 +104,26 @@ Core.scene = new GameScene(); ### 3. 创建组件 ```typescript -import { Component, Vector2, Time } from '@esengine/ecs-framework'; +import { Component, Time } from '@esengine/ecs-framework'; class MovementComponent extends Component { public speed: number = 100; - public direction: Vector2 = Vector2.zero; + public direction = { x: 0, y: 0 }; public update() { - if (this.direction.length > 0) { - const movement = this.direction.multiply(this.speed * Time.deltaTime); - this.entity.position = this.entity.position.add(movement); + const position = this.entity.getComponent(PositionComponent); + if (position && (this.direction.x !== 0 || this.direction.y !== 0)) { + position.x += this.direction.x * this.speed * Time.deltaTime; + position.y += this.direction.y * this.speed * Time.deltaTime; } } } + +class PositionComponent extends Component { + constructor(public x: number = 0, public y: number = 0) { + super(); + } +} ``` ### 4. 创建系统 @@ -142,15 +146,20 @@ class MovementSystem extends EntitySystem { ## 📚 核心概念 ### Entity(实体) -实体是游戏世界中的基本对象,包含位置、旋转、缩放等基本属性,可以添加组件来扩展功能。 +实体是游戏世界中的基本对象,作为组件的容器。实体本身不包含游戏逻辑,所有功能都通过组件来实现。 ```typescript -import { Vector2 } from '@esengine/ecs-framework'; - +// 通过场景创建实体 const entity = scene.createEntity("MyEntity"); -entity.position = new Vector2(100, 200); -entity.rotation = Math.PI / 4; -entity.scale = new Vector2(2, 2); + +// 设置实体属性 +entity.tag = 1; // 设置标签用于分类 +entity.updateOrder = 0; // 设置更新顺序 +entity.enabled = true; // 设置启用状态 + +// 添加组件来扩展功能 +const positionComponent = entity.addComponent(new PositionComponent(100, 200)); +const healthComponent = entity.addComponent(new HealthComponent(100)); ``` ### Component(组件) @@ -283,6 +292,7 @@ console.log("场景统计:", stats); ## 📖 文档 - [快速入门](docs/getting-started.md) - 从零开始学习框架使用 +- [实体使用指南](docs/entity-guide.md) - 详细了解实体的所有功能和用法 - [核心概念](docs/core-concepts.md) - 深入了解 ECS 架构和设计原理 - [查询系统使用指南](docs/query-system-usage.md) - 学习高性能查询系统的详细用法 diff --git a/docs/entity-guide.md b/docs/entity-guide.md new file mode 100644 index 00000000..277cb46d --- /dev/null +++ b/docs/entity-guide.md @@ -0,0 +1,465 @@ +# 实体使用指南 + +本指南详细介绍 ECS Framework 中实体(Entity)的所有功能和使用方法。 + +## 实体概述 + +实体(Entity)是 ECS 架构中的核心概念之一,它作为组件的容器存在。实体本身不包含游戏逻辑,所有功能都通过添加不同的组件来实现。 + +### 实体的特点 + +- **轻量级容器**:实体只是组件的载体,不包含具体的游戏逻辑 +- **唯一标识**:每个实体都有唯一的ID和名称 +- **层次结构**:支持父子关系,可以构建复杂的实体层次 +- **高性能查询**:基于位掩码的组件查询系统 +- **生命周期管理**:完整的创建、更新、销毁流程 + +## 创建实体 + +### 基本创建方式 + +```typescript +import { Scene } from '@esengine/ecs-framework'; + +// 通过场景创建实体 +const scene = new Scene(); +const entity = scene.createEntity("Player"); + +console.log(entity.name); // "Player" +console.log(entity.id); // 唯一的数字ID +``` + +### 使用流式API创建 + +```typescript +import { Core } from '@esengine/ecs-framework'; + +// 使用ECS流式API +const entity = Core.ecsAPI + ?.entity("Enemy") + .withComponent(new PositionComponent(100, 200)) + .withComponent(new HealthComponent(50)) + .withTag(2) + .build(); +``` + +## 实体属性 + +### 基本属性 + +```typescript +// 实体名称 - 用于调试和标识 +entity.name = "Player"; + +// 实体ID - 只读,场景内唯一 +console.log(entity.id); // 例如: 1 + +// 标签 - 用于分类和快速查询 +entity.tag = 1; // 玩家标签 +entity.tag = 2; // 敌人标签 + +// 更新顺序 - 控制实体在系统中的处理优先级 +entity.updateOrder = 0; // 数值越小优先级越高 +``` + +### 状态控制 + +```typescript +// 启用状态 - 控制实体是否参与更新和处理 +entity.enabled = true; // 启用实体 +entity.enabled = false; // 禁用实体 + +// 激活状态 - 控制实体及其子实体的活跃状态 +entity.active = true; // 激活实体 +entity.active = false; // 停用实体 + +// 检查层次结构中的激活状态 +if (entity.activeInHierarchy) { + // 实体在整个层次结构中都是激活的 +} + +// 检查销毁状态 +if (entity.isDestroyed) { + // 实体已被销毁 +} +``` + +### 更新间隔 + +```typescript +// 控制实体更新频率 +entity.updateInterval = 1; // 每帧更新 +entity.updateInterval = 2; // 每2帧更新一次 +entity.updateInterval = 5; // 每5帧更新一次 +``` + +## 组件管理 + +### 添加组件 + +```typescript +// 创建并添加组件 +const healthComponent = entity.addComponent(new HealthComponent(100)); + +// 使用工厂方法创建组件 +const positionComponent = entity.createComponent(PositionComponent, 100, 200); + +// 批量添加组件 +const components = entity.addComponents([ + new PositionComponent(0, 0), + new VelocityComponent(50, 0), + new HealthComponent(100) +]); +``` + +### 获取组件 + +```typescript +// 获取单个组件 +const health = entity.getComponent(HealthComponent); +if (health) { + console.log(`当前生命值: ${health.currentHealth}`); +} + +// 获取或创建组件(如果不存在则创建) +const position = entity.getOrCreateComponent(PositionComponent, 0, 0); + +// 获取多个同类型组件(如果组件可以重复添加) +const allHealthComponents = entity.getComponents(HealthComponent); +``` + +### 检查组件 + +```typescript +// 检查是否拥有指定组件 +if (entity.hasComponent(HealthComponent)) { + // 实体拥有生命值组件 +} + +// 检查组件掩码(高性能) +const mask = entity.componentMask; +console.log(`组件掩码: ${mask.toString(2)}`); +``` + +### 移除组件 + +```typescript +// 移除指定组件实例 +const healthComponent = entity.getComponent(HealthComponent); +if (healthComponent) { + entity.removeComponent(healthComponent); +} + +// 按类型移除组件 +const removedHealth = entity.removeComponentByType(HealthComponent); + +// 批量移除组件 +const removedComponents = entity.removeComponentsByTypes([ + HealthComponent, + VelocityComponent +]); + +// 移除所有组件 +entity.removeAllComponents(); +``` + +## 层次结构管理 + +### 父子关系 + +```typescript +// 创建父子实体 +const player = scene.createEntity("Player"); +const weapon = scene.createEntity("Weapon"); +const shield = scene.createEntity("Shield"); + +// 添加子实体 +player.addChild(weapon); +player.addChild(shield); + +// 获取父实体 +console.log(weapon.parent === player); // true + +// 获取所有子实体 +const children = player.children; +console.log(children.length); // 2 + +// 获取子实体数量 +console.log(player.childCount); // 2 +``` + +### 查找子实体 + +```typescript +// 按名称查找子实体 +const weapon = player.findChild("Weapon"); + +// 递归查找子实体 +const deepChild = player.findChild("DeepChild", true); + +// 按标签查找子实体 +const enemies = player.findChildrenByTag(2); // 查找所有敌人标签的子实体 + +// 递归按标签查找 +const allEnemies = player.findChildrenByTag(2, true); +``` + +### 层次结构操作 + +```typescript +// 移除子实体 +const removed = player.removeChild(weapon); + +// 移除所有子实体 +player.removeAllChildren(); + +// 获取根实体 +const root = weapon.getRoot(); + +// 检查祖先关系 +if (player.isAncestorOf(weapon)) { + // player 是 weapon 的祖先 +} + +// 检查后代关系 +if (weapon.isDescendantOf(player)) { + // weapon 是 player 的后代 +} + +// 获取实体在层次结构中的深度 +const depth = weapon.getDepth(); // 从根实体开始计算的深度 +``` + +### 遍历子实体 + +```typescript +// 遍历直接子实体 +player.forEachChild((child, index) => { + console.log(`子实体 ${index}: ${child.name}`); +}); + +// 递归遍历所有子实体 +player.forEachChild((child, index) => { + console.log(`子实体 ${index}: ${child.name} (深度: ${child.getDepth()})`); +}, true); +``` + +## 实体生命周期 + +### 更新循环 + +```typescript +// 手动更新实体(通常由场景自动调用) +entity.update(); + +// 实体会自动调用所有组件的update方法 +class MyComponent extends Component { + public update(): void { + // 组件更新逻辑 + } +} +``` + +### 销毁实体 + +```typescript +// 销毁实体 +entity.destroy(); + +// 检查是否已销毁 +if (entity.isDestroyed) { + console.log("实体已被销毁"); +} + +// 销毁实体时会自动: +// 1. 移除所有组件 +// 2. 从父实体中移除 +// 3. 销毁所有子实体 +// 4. 从场景中移除 +``` + +## 性能优化 + +### 组件缓存 + +```typescript +// 预热组件缓存(提高后续访问性能) +entity.warmUpComponentCache(); + +// 清理组件缓存 +entity.cleanupComponentCache(); + +// 获取缓存统计信息 +const cacheStats = entity.getComponentCacheStats(); +console.log(`缓存命中率: ${cacheStats.cacheStats.hitRate}`); +console.log(`组件访问统计:`, cacheStats.accessStats); +``` + +### 批量操作 + +```typescript +// 批量添加组件(比单个添加更高效) +const components = entity.addComponents([ + new PositionComponent(0, 0), + new VelocityComponent(50, 0), + new HealthComponent(100) +]); + +// 批量移除组件 +const removed = entity.removeComponentsByTypes([ + HealthComponent, + VelocityComponent +]); +``` + +## 调试和监控 + +### 调试信息 + +```typescript +// 获取详细的调试信息 +const debugInfo = entity.getDebugInfo(); +console.log("实体调试信息:", debugInfo); + +// 调试信息包含: +// - 基本属性(名称、ID、状态等) +// - 组件信息(数量、类型、掩码等) +// - 层次结构信息(父子关系、深度等) +// - 性能统计(缓存命中率、访问统计等) +``` + +### 实体比较 + +```typescript +// 比较两个实体的优先级 +const result = entity1.compareTo(entity2); +if (result < 0) { + // entity1 优先级更高 +} else if (result > 0) { + // entity2 优先级更高 +} else { + // 优先级相同 +} + +// 实体的字符串表示 +console.log(entity.toString()); // "Entity[Player:1]" +``` + +## 最佳实践 + +### 1. 合理使用标签 + +```typescript +// 定义标签常量 +const Tags = { + PLAYER: 1, + ENEMY: 2, + PROJECTILE: 3, + PICKUP: 4 +} as const; + +// 使用标签进行分类 +player.tag = Tags.PLAYER; +enemy.tag = Tags.ENEMY; +``` + +### 2. 优化更新顺序 + +```typescript +// 设置合理的更新顺序 +player.updateOrder = 0; // 玩家最先更新 +enemy.updateOrder = 1; // 敌人其次 +projectile.updateOrder = 2; // 投射物最后 +``` + +### 3. 合理使用层次结构 + +```typescript +// 创建复合实体 +const tank = scene.createEntity("Tank"); +const turret = scene.createEntity("Turret"); +const barrel = scene.createEntity("Barrel"); + +// 建立层次关系 +tank.addChild(turret); +turret.addChild(barrel); + +// 这样可以通过控制父实体来影响整个层次结构 +tank.active = false; // 整个坦克都会被停用 +``` + +### 4. 组件缓存优化 + +```typescript +// 对于频繁访问的组件,预热缓存 +entity.warmUpComponentCache(); + +// 定期清理不常用的缓存 +setInterval(() => { + entity.cleanupComponentCache(); +}, 5000); +``` + +### 5. 避免内存泄漏 + +```typescript +// 确保正确销毁实体 +if (entity.isDestroyed) { + return; // 避免操作已销毁的实体 +} + +// 在适当的时候销毁不需要的实体 +if (enemy.getComponent(HealthComponent)?.isDead()) { + enemy.destroy(); +} +``` + +## 常见问题 + +### Q: 实体可以在不同场景间移动吗? + +A: 不可以。实体与场景紧密绑定,如果需要在场景间传递数据,应该序列化实体的组件数据,然后在新场景中重新创建。 + +### Q: 如何实现实体的位置、旋转、缩放? + +A: 框架本身不提供这些属性,需要通过组件来实现: + +```typescript +class TransformComponent extends Component { + public position = { x: 0, y: 0 }; + public rotation = 0; + public scale = { x: 1, y: 1 }; +} + +const transform = entity.addComponent(new TransformComponent()); +transform.position.x = 100; +transform.rotation = Math.PI / 4; +``` + +### Q: 实体的更新顺序如何影响性能? + +A: 更新顺序主要影响游戏逻辑的执行顺序,对性能影响较小。但合理的更新顺序可以避免一些逻辑问题,比如确保输入处理在移动之前执行。 + +### Q: 如何处理大量实体的性能问题? + +A: +1. 使用对象池重用实体 +2. 合理使用组件缓存 +3. 避免不必要的组件查询 +4. 使用批量操作 +5. 定期清理销毁的实体 + +```typescript +// 使用对象池 +class EntityPool extends Pool { + protected createObject(): Entity { + return scene.createEntity("PooledEntity"); + } + + protected resetObject(entity: Entity): void { + entity.removeAllComponents(); + entity.active = true; + entity.enabled = true; + } +} +``` \ No newline at end of file diff --git a/docs/getting-started.md b/docs/getting-started.md index 3c9fe6be..418a68fd 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -588,6 +588,7 @@ game.start(); 现在您已经掌握了 ECS Framework 的基础用法,可以继续学习: +- [实体使用指南](entity-guide.md) - 详细了解实体的所有功能和用法 - [核心概念](core-concepts.md) - 深入了解 ECS 架构和设计原理 - [查询系统使用指南](query-system-usage.md) - 学习高性能查询系统的详细用法 diff --git a/source/build.config.js b/source/build.config.js index 3e0f80a8..97b5db51 100644 --- a/source/build.config.js +++ b/source/build.config.js @@ -25,20 +25,54 @@ module.exports = { sourcemap: true }, + // WebAssembly支持配置 + wasm: { + enabled: false, // 暂时禁用,未来启用 + modules: { + // 计划迁移到WebAssembly的模块 + core: { + entry: './src/wasm/core.ts', + output: 'ecs-core.wasm', + features: ['query-system', 'math-utils'] + }, + physics: { + entry: './src/wasm/physics.ts', + output: 'ecs-physics.wasm', + features: ['collision-detection', 'spatial-hash'] + } + }, + // AssemblyScript配置 + assemblyscript: { + target: 'wasm32', + optimize: true, + runtime: 'minimal' + } + }, + // 游戏引擎特定配置 engines: { laya: { // Laya引擎特定优化 target: 'es5', polyfills: ['Promise', 'Object.assign'], - globals: ['Laya'] + globals: ['Laya'], + wasm: { + // Laya环境下的WebAssembly配置 + loader: 'laya-wasm-loader', + fallback: true // 支持降级到JavaScript + } }, cocos: { // Cocos引擎特定优化 target: 'es6', polyfills: [], - globals: ['cc'] + globals: ['cc'], + wasm: { + // Cocos环境下的WebAssembly配置 + loader: 'cocos-wasm-loader', + fallback: true + } } }, @@ -48,19 +82,33 @@ module.exports = { // 微信小游戏优化 maxSize: '4MB', treeshaking: true, - compression: 'gzip' + compression: 'gzip', + wasm: { + // 微信小游戏WebAssembly支持 + enabled: true, + maxWasmSize: '2MB', // WebAssembly模块大小限制 + preload: ['ecs-core.wasm'] // 预加载核心模块 + } }, alipay: { // 支付宝小游戏优化 maxSize: '4MB', treeshaking: true, - compression: 'gzip' + compression: 'gzip', + wasm: { + enabled: true, + maxWasmSize: '2MB' + } }, bytedance: { // 字节跳动小游戏优化 maxSize: '4MB', treeshaking: true, - compression: 'gzip' + compression: 'gzip', + wasm: { + enabled: true, + maxWasmSize: '2MB' + } } }, @@ -77,7 +125,23 @@ module.exports = { removeDebugger: true }, // 内联小文件 - inlineThreshold: 1024 + inlineThreshold: 1024, + // WebAssembly优化 + wasm: { + // 启用WebAssembly优化 + optimize: true, + // 内存配置 + memory: { + initial: 1, // 初始内存页数 (64KB per page) + maximum: 16, // 最大内存页数 + shared: false // 是否共享内存 + }, + // 导出配置 + exports: { + memory: true, + table: false + } + } }, // 开发配置 @@ -93,5 +157,22 @@ module.exports = { minify: true, optimization: true, bundleAnalyzer: true + }, + + // 实验性功能 + experimental: { + // 混合架构支持 + hybrid: { + enabled: true, + // 自动检测WebAssembly支持 + autoDetect: true, + // 性能基准测试 + benchmark: true, + // 降级策略 + fallback: { + strategy: 'graceful', // 优雅降级 + modules: ['core', 'physics'] // 支持降级的模块 + } + } } }; \ No newline at end of file diff --git a/source/src/Utils/AccelerationProvider.ts b/source/src/Utils/AccelerationProvider.ts new file mode 100644 index 00000000..9948f6cb --- /dev/null +++ b/source/src/Utils/AccelerationProvider.ts @@ -0,0 +1,409 @@ +/** + * ECS框架加速提供者接口 + * + * 提供可替换的性能加速实现,专注于ECS实体查询功能 + * 支持JavaScript、WebAssembly等不同后端实现 + */ + +// ================================ +// 核心接口定义 +// ================================ + +/** + * 实体查询结果 + */ +export interface QueryResult { + /** 查询到的实体ID数组 */ + entities: Uint32Array; + /** 查询到的实体数量 */ + count: number; +} + +/** + * 实体查询接口 + * + * 提供高性能的ECS实体查询功能 + */ +export interface QueryProvider { + /** + * 根据单个组件掩码查询实体 + * @param componentMask 组件掩码 + * @param maxResults 最大结果数量 + * @returns 查询结果 + */ + queryByComponent(componentMask: bigint, maxResults: number): QueryResult; + + /** + * 根据多个组件掩码查询实体(AND操作) + * @param componentMasks 组件掩码数组 + * @param maxResults 最大结果数量 + * @returns 查询结果 + */ + queryByComponents(componentMasks: bigint[], maxResults: number): QueryResult; + + /** + * 查询包含指定组件但排除其他组件的实体 + * @param includeMask 必须包含的组件掩码 + * @param excludeMask 必须排除的组件掩码 + * @param maxResults 最大结果数量 + * @returns 查询结果 + */ + queryExcluding(includeMask: bigint, excludeMask: bigint, maxResults: number): QueryResult; + + /** + * 更新实体的组件掩码 + * @param entityId 实体ID + * @param componentMask 新的组件掩码 + */ + updateEntityMask(entityId: number, componentMask: bigint): void; + + /** + * 批量更新实体掩码 + * @param entityIds 实体ID数组 + * @param masks 掩码数组 + */ + batchUpdateMasks(entityIds: Uint32Array, masks: BigUint64Array): void; +} + +/** + * 加速提供者接口 + * + * 定义了ECS框架加速提供者的基本契约 + */ +export interface AccelerationProvider { + /** 提供者名称 */ + readonly name: string; + /** 提供者版本 */ + readonly version: string; + /** 是否为WebAssembly实现 */ + readonly isWasm: boolean; + + /** 实体查询功能模块 */ + query: QueryProvider; + + /** + * 初始化提供者 + * @throws {Error} 初始化失败时抛出错误 + */ + initialize(): Promise; + + /** + * 检查是否支持指定功能 + * @param feature 功能名称 + * @returns 是否支持该功能 + */ + supports(feature: string): boolean; + + /** + * 获取性能信息 + * @returns 性能统计信息 + */ + getPerformanceInfo(): { + /** 每秒操作数 */ + operationsPerSecond: number; + /** 内存使用量(字节) */ + memoryUsage: number; + /** 支持的功能列表 */ + features: string[]; + }; + + /** + * 清理资源 + */ + dispose(): void; +} + +// ================================ +// JavaScript实现 +// ================================ + +/** + * JavaScript实现的基础加速提供者 + * + * 提供纯JavaScript的ECS查询实现,作为默认后端 + */ +export class JavaScriptProvider implements AccelerationProvider { + readonly name = 'JavaScript'; + readonly version = '1.0.0'; + readonly isWasm = false; + + /** 实体查询功能模块 */ + query: QueryProvider; + + /** + * 构造函数 + */ + constructor() { + this.query = new JSQueryProvider(); + } + + /** + * 初始化提供者 + */ + async initialize(): Promise { + // JavaScript版本无需初始化 + } + + /** + * 检查是否支持指定功能 + * @param feature 功能名称 + * @returns 是否支持该功能 + */ + supports(feature: string): boolean { + const supportedFeatures = [ + 'entity-query', 'batch-operations', 'component-masks' + ]; + return supportedFeatures.includes(feature); + } + + /** + * 获取性能信息 + * @returns 性能统计信息 + */ + getPerformanceInfo() { + return { + operationsPerSecond: 1000000, // 100万次/秒 + memoryUsage: 0, + features: ['entity-query', 'batch-operations', 'component-masks'] + }; + } + + /** + * 清理资源 + */ + dispose(): void { + // JavaScript版本无需清理 + } +} + +/** + * JavaScript查询实现 + * + * 使用Map存储实体掩码,提供基础的查询功能 + */ +class JSQueryProvider implements QueryProvider { + /** 实体掩码存储 */ + private entityMasks = new Map(); + + /** + * 根据单个组件掩码查询实体 + * @param componentMask 组件掩码 + * @param maxResults 最大结果数量 + * @returns 查询结果 + */ + queryByComponent(componentMask: bigint, maxResults: number): QueryResult { + const results = new Uint32Array(maxResults); + let count = 0; + + for (const [entityId, mask] of this.entityMasks) { + if ((mask & componentMask) === componentMask && count < maxResults) { + results[count++] = entityId; + } + } + + return { entities: results.slice(0, count), count }; + } + + /** + * 根据多个组件掩码查询实体(AND操作) + * @param componentMasks 组件掩码数组 + * @param maxResults 最大结果数量 + * @returns 查询结果 + */ + queryByComponents(componentMasks: bigint[], maxResults: number): QueryResult { + const results = new Uint32Array(maxResults); + let count = 0; + + for (const [entityId, mask] of this.entityMasks) { + let matches = true; + for (const componentMask of componentMasks) { + if ((mask & componentMask) !== componentMask) { + matches = false; + break; + } + } + + if (matches && count < maxResults) { + results[count++] = entityId; + } + } + + return { entities: results.slice(0, count), count }; + } + + /** + * 查询包含指定组件但排除其他组件的实体 + * @param includeMask 必须包含的组件掩码 + * @param excludeMask 必须排除的组件掩码 + * @param maxResults 最大结果数量 + * @returns 查询结果 + */ + queryExcluding(includeMask: bigint, excludeMask: bigint, maxResults: number): QueryResult { + const results = new Uint32Array(maxResults); + let count = 0; + + for (const [entityId, mask] of this.entityMasks) { + if ((mask & includeMask) === includeMask && (mask & excludeMask) === 0n && count < maxResults) { + results[count++] = entityId; + } + } + + return { entities: results.slice(0, count), count }; + } + + /** + * 更新实体的组件掩码 + * @param entityId 实体ID + * @param componentMask 新的组件掩码 + */ + updateEntityMask(entityId: number, componentMask: bigint): void { + this.entityMasks.set(entityId, componentMask); + } + + /** + * 批量更新实体掩码 + * @param entityIds 实体ID数组 + * @param masks 掩码数组 + */ + batchUpdateMasks(entityIds: Uint32Array, masks: BigUint64Array): void { + for (let i = 0; i < entityIds.length; i++) { + this.entityMasks.set(entityIds[i], masks[i]); + } + } +} + +// ================================ +// 管理器类 +// ================================ + +/** + * 加速提供者管理器 + * + * 管理不同的加速提供者实现,支持动态切换和性能测试 + */ +export class AccelerationManager { + /** 单例实例 */ + private static instance: AccelerationManager; + /** 当前使用的提供者 */ + private currentProvider: AccelerationProvider; + /** 可用的提供者映射 */ + private availableProviders = new Map(); + + /** + * 私有构造函数 + */ + private constructor() { + // 默认使用JavaScript提供者 + this.currentProvider = new JavaScriptProvider(); + this.availableProviders.set('javascript', this.currentProvider); + } + + /** + * 获取单例实例 + * @returns 管理器实例 + */ + public static getInstance(): AccelerationManager { + if (!AccelerationManager.instance) { + AccelerationManager.instance = new AccelerationManager(); + } + return AccelerationManager.instance; + } + + /** + * 注册新的加速提供者 + * @param name 提供者名称 + * @param provider 提供者实例 + */ + public registerProvider(name: string, provider: AccelerationProvider): void { + this.availableProviders.set(name, provider); + } + + /** + * 切换加速提供者 + * @param name 提供者名称 + * @returns 是否切换成功 + */ + public async setProvider(name: string): Promise { + const provider = this.availableProviders.get(name); + if (!provider) { + console.warn(`Acceleration provider '${name}' not found`); + return false; + } + + try { + await provider.initialize(); + this.currentProvider = provider; + console.log(`Switched to acceleration provider: ${provider.name} v${provider.version}`); + return true; + } catch (error) { + console.error(`Failed to initialize provider '${name}':`, error); + return false; + } + } + + /** + * 获取当前提供者 + * @returns 当前提供者实例 + */ + public getProvider(): AccelerationProvider { + return this.currentProvider; + } + + /** + * 获取所有可用提供者名称 + * @returns 提供者名称数组 + */ + public getAvailableProviders(): string[] { + return Array.from(this.availableProviders.keys()); + } + + /** + * 自动选择最佳提供者 + * 优先选择WebAssembly提供者,回退到JavaScript提供者 + */ + public async selectBestProvider(): Promise { + const providers = Array.from(this.availableProviders.values()); + + // 优先选择WebAssembly提供者 + const wasmProvider = providers.find(p => p.isWasm); + if (wasmProvider) { + const success = await this.setProvider(wasmProvider.name); + if (success) return; + } + + // 回退到JavaScript提供者 + await this.setProvider('javascript'); + } + + /** + * 性能基准测试 + * @returns 各提供者的性能测试结果(操作/秒) + */ + public async benchmarkProviders(): Promise> { + const results = new Map(); + + for (const [name, provider] of this.availableProviders) { + try { + await provider.initialize(); + + // 简单的查询性能测试 + const start = performance.now(); + const testMask = 0b1111n; // 测试掩码 + + for (let i = 0; i < 10000; i++) { + provider.query.queryByComponent(testMask, 100); + } + + const end = performance.now(); + results.set(name, 10000 / (end - start) * 1000); // 操作/秒 + + provider.dispose(); + } catch (error) { + console.warn(`Benchmark failed for provider '${name}':`, error); + results.set(name, 0); + } + } + + return results; + } +} \ No newline at end of file diff --git a/source/src/Utils/WasmBridge.ts b/source/src/Utils/WasmBridge.ts new file mode 100644 index 00000000..cfe9a705 --- /dev/null +++ b/source/src/Utils/WasmBridge.ts @@ -0,0 +1,439 @@ +/** + * WebAssembly桥接工具 + * + * 提供WebAssembly模块的加载、初始化和内存管理功能 + * 为ECS框架提供高性能的底层支持 + */ + +import { + AccelerationProvider, + QueryProvider, + QueryResult +} from './AccelerationProvider'; + +// ================================ +// 类型定义和接口 +// ================================ + +/** + * WebAssembly模块接口 + * 定义了ECS相关的WASM函数签名 + */ +interface WasmModule { + /** WebAssembly内存对象 */ + memory: WebAssembly.Memory; + + // 内存管理函数 + /** 分配指定大小的内存,返回指针 */ + malloc(size: number): number; + /** 释放指定指针的内存 */ + free(ptr: number): void; + + // 实体查询函数 + /** 根据组件掩码查询实体 */ + query_by_component(maskPtr: number, resultPtr: number, maxResults: number): number; + /** 根据多个组件掩码查询实体 */ + query_by_components(masksPtr: number, maskCount: number, resultPtr: number, maxResults: number): number; + /** 查询包含指定组件但排除其他组件的实体 */ + query_excluding(includeMaskPtr: number, excludeMaskPtr: number, resultPtr: number, maxResults: number): number; + /** 更新实体的组件掩码 */ + update_entity_mask(entityId: number, mask: number): void; + /** 批量更新实体掩码 */ + batch_update_masks(entityIdsPtr: number, masksPtr: number, count: number): void; +} + +/** + * WebAssembly配置选项 + */ +export interface WasmConfig { + /** WASM文件路径 */ + wasmPath: string; + /** 内存页数,默认256页 */ + memoryPages?: number; + /** 是否启用SIMD,默认true */ + enableSIMD?: boolean; + /** 是否启用多线程,默认false */ + enableThreads?: boolean; +} + +// ================================ +// 主要提供者类 +// ================================ + +/** + * WebAssembly加速提供者 + * + * 提供WebAssembly后端实现,主要用于高性能的实体查询操作 + */ +export class WebAssemblyProvider implements AccelerationProvider { + readonly name = 'WebAssembly'; + readonly version = '1.0.0'; + readonly isWasm = true; + + /** WASM模块实例 */ + private wasmModule?: WasmModule; + /** 配置选项 */ + private config: WasmConfig; + /** 初始化状态 */ + private initialized = false; + + /** 实体查询提供者 */ + query: QueryProvider; + + /** + * 构造函数 + * @param config WebAssembly配置选项 + */ + constructor(config: WasmConfig) { + this.config = { + memoryPages: 256, + enableSIMD: true, + enableThreads: false, + ...config + }; + + // 创建查询功能模块的WebAssembly实现 + this.query = new WasmQueryProvider(this); + } + + /** + * 初始化WebAssembly模块 + * @throws {Error} 初始化失败时抛出错误 + */ + async initialize(): Promise { + if (this.initialized) return; + + try { + const wasmBytes = await this.loadWasmBytes(); + const wasmModule = await this.instantiateWasm(wasmBytes); + this.wasmModule = wasmModule; + this.initialized = true; + + console.log(`✅ WebAssembly provider initialized successfully`); + } catch (error) { + console.error('Failed to initialize WebAssembly provider:', error); + throw error; + } + } + + /** + * 加载WASM字节码 + * @returns WASM字节码的ArrayBuffer + * @private + */ + private async loadWasmBytes(): Promise { + if (typeof fetch !== 'undefined') { + // 浏览器环境 + const response = await fetch(this.config.wasmPath); + if (!response.ok) { + throw new Error(`Failed to load WASM file: ${response.statusText}`); + } + return response.arrayBuffer(); + } else { + // Node.js环境 - 需要在运行时动态导入 + throw new Error('Node.js environment not supported in browser build. Please use fetch() or provide ArrayBuffer directly.'); + } + } + + /** + * 实例化WASM模块 + * @param wasmBytes WASM字节码 + * @returns 实例化的WASM模块 + * @private + */ + private async instantiateWasm(wasmBytes: ArrayBuffer): Promise { + const memory = new WebAssembly.Memory({ + initial: this.config.memoryPages!, + maximum: this.config.memoryPages! * 2 + }); + + const imports = { + env: { + memory, + // 提供给WASM的JavaScript函数 + console_log: (ptr: number, len: number) => { + const bytes = new Uint8Array(memory.buffer, ptr, len); + const str = new TextDecoder().decode(bytes); + console.log('[WASM]', str); + }, + performance_now: () => performance.now() + } + }; + + const wasmModule = await WebAssembly.instantiate(wasmBytes, imports); + return wasmModule.instance.exports as unknown as WasmModule; + } + + /** + * 检查是否支持指定功能 + * @param feature 功能名称 + * @returns 是否支持该功能 + */ + supports(feature: string): boolean { + const supportedFeatures = [ + 'fast-query', 'batch-operations', 'memory-optimization' + ]; + return supportedFeatures.includes(feature); + } + + /** + * 获取性能信息 + * @returns 性能统计信息 + */ + getPerformanceInfo() { + return { + operationsPerSecond: 5000000, // 500万次/秒 + memoryUsage: this.wasmModule?.memory.buffer.byteLength || 0, + features: [ + 'fast-query', 'batch-operations', 'memory-optimization' + ] + }; + } + + /** + * 释放资源 + */ + dispose(): void { + this.wasmModule = undefined; + this.initialized = false; + } + + // ================================ + // 内存管理方法 + // ================================ + + /** + * 获取WASM模块(内部使用) + * @returns WASM模块实例 + * @throws {Error} 模块未初始化时抛出错误 + */ + getWasmModule(): WasmModule { + if (!this.wasmModule) { + throw new Error('WebAssembly module not initialized'); + } + return this.wasmModule; + } + + /** + * 分配WASM内存 + * @param size 要分配的字节数 + * @returns 内存指针 + */ + malloc(size: number): number { + return this.getWasmModule().malloc(size); + } + + /** + * 释放WASM内存 + * @param ptr 内存指针 + */ + free(ptr: number): void { + this.getWasmModule().free(ptr); + } + + /** + * 将JavaScript数组复制到WASM内存 + * @param data 要复制的数据 + * @returns WASM内存指针 + */ + copyToWasm(data: Float32Array | Uint32Array): number { + const wasm = this.getWasmModule(); + const ptr = wasm.malloc(data.byteLength); + const wasmArray = new (data.constructor as any)(wasm.memory.buffer, ptr, data.length); + wasmArray.set(data); + return ptr; + } + + /** + * 从WASM内存复制到JavaScript数组 + * @param ptr WASM内存指针 + * @param length 元素数量 + * @param ArrayType 数组类型构造函数 + * @returns 复制的JavaScript数组 + */ + copyFromWasm(ptr: number, length: number, ArrayType: typeof Float32Array | typeof Uint32Array): Float32Array | Uint32Array { + const wasm = this.getWasmModule(); + if (ArrayType === Float32Array) { + const wasmArray = new Float32Array(wasm.memory.buffer, ptr, length); + return wasmArray.slice(); + } else { + const wasmArray = new Uint32Array(wasm.memory.buffer, ptr, length); + return wasmArray.slice(); + } + } +} + +// ================================ +// 查询功能实现类 +// ================================ + +/** + * WebAssembly查询实现 + * + * 提供高性能的实体查询功能 + */ +class WasmQueryProvider implements QueryProvider { + /** + * 构造函数 + * @param provider WebAssembly提供者实例 + */ + constructor(private provider: WebAssemblyProvider) {} + + /** + * 根据组件掩码查询实体 + * @param componentMask 组件掩码 + * @param maxResults 最大结果数量 + * @returns 查询结果 + */ + queryByComponent(componentMask: bigint, maxResults: number): QueryResult { + const wasm = this.provider.getWasmModule(); + + // 注意:这里简化了bigint的处理,实际实现需要更复杂的转换 + const maskPtr = this.provider.malloc(8); + const resultPtr = this.provider.malloc(maxResults * 4); + + const count = wasm.query_by_component(maskPtr, resultPtr, maxResults); + + const entities = this.provider.copyFromWasm(resultPtr, count, Uint32Array) as Uint32Array; + + this.provider.free(maskPtr); + this.provider.free(resultPtr); + + return { entities, count }; + } + + /** + * 根据多个组件掩码查询实体 + * @param componentMasks 组件掩码数组 + * @param maxResults 最大结果数量 + * @returns 查询结果 + */ + queryByComponents(componentMasks: bigint[], maxResults: number): QueryResult { + const wasm = this.provider.getWasmModule(); + + // 分配掩码数组内存 + const masksPtr = this.provider.malloc(componentMasks.length * 8); + const resultPtr = this.provider.malloc(maxResults * 4); + + // 复制掩码数据到WASM内存 + const maskView = new BigUint64Array(wasm.memory.buffer, masksPtr, componentMasks.length); + maskView.set(componentMasks); + + const count = wasm.query_by_components(masksPtr, componentMasks.length, resultPtr, maxResults); + + const entities = this.provider.copyFromWasm(resultPtr, count, Uint32Array) as Uint32Array; + + this.provider.free(masksPtr); + this.provider.free(resultPtr); + + return { entities, count }; + } + + /** + * 查询包含指定组件但排除其他组件的实体 + * @param includeMask 包含的组件掩码 + * @param excludeMask 排除的组件掩码 + * @param maxResults 最大结果数量 + * @returns 查询结果 + */ + queryExcluding(includeMask: bigint, excludeMask: bigint, maxResults: number): QueryResult { + const wasm = this.provider.getWasmModule(); + + const includeMaskPtr = this.provider.malloc(8); + const excludeMaskPtr = this.provider.malloc(8); + const resultPtr = this.provider.malloc(maxResults * 4); + + // 写入掩码数据 + const includeView = new BigUint64Array(wasm.memory.buffer, includeMaskPtr, 1); + const excludeView = new BigUint64Array(wasm.memory.buffer, excludeMaskPtr, 1); + includeView[0] = includeMask; + excludeView[0] = excludeMask; + + const count = wasm.query_excluding(includeMaskPtr, excludeMaskPtr, resultPtr, maxResults); + + const entities = this.provider.copyFromWasm(resultPtr, count, Uint32Array) as Uint32Array; + + this.provider.free(includeMaskPtr); + this.provider.free(excludeMaskPtr); + this.provider.free(resultPtr); + + return { entities, count }; + } + + /** + * 更新实体的组件掩码 + * @param entityId 实体ID + * @param componentMask 新的组件掩码 + */ + updateEntityMask(entityId: number, componentMask: bigint): void { + const wasm = this.provider.getWasmModule(); + // 简化的mask处理,实际应该支持完整的bigint + wasm.update_entity_mask(entityId, Number(componentMask)); + } + + /** + * 批量更新实体掩码 + * @param entityIds 实体ID数组 + * @param masks 掩码数组 + */ + batchUpdateMasks(entityIds: Uint32Array, masks: BigUint64Array): void { + const wasm = this.provider.getWasmModule(); + + const entityIdsPtr = this.provider.copyToWasm(entityIds); + const masksPtr = this.provider.malloc(masks.byteLength); + + // 复制掩码数据 + const maskView = new BigUint64Array(wasm.memory.buffer, masksPtr, masks.length); + maskView.set(masks); + + wasm.batch_update_masks(entityIdsPtr, masksPtr, entityIds.length); + + this.provider.free(entityIdsPtr); + this.provider.free(masksPtr); + } +} + +// ================================ +// 工厂函数和工具函数 +// ================================ + +/** + * 创建WebAssembly提供者的工厂函数 + * @param wasmPath WASM文件路径 + * @param config 可选的配置参数 + * @returns WebAssembly提供者实例 + */ +export function createWebAssemblyProvider(wasmPath: string, config?: Partial): WebAssemblyProvider { + return new WebAssemblyProvider({ + wasmPath, + ...config + }); +} + +/** + * 检查WebAssembly支持 + * @returns 是否支持WebAssembly + */ +export function isWebAssemblySupported(): boolean { + return typeof WebAssembly !== 'undefined' && + typeof WebAssembly.instantiate === 'function'; +} + +/** + * 检查SIMD支持 + * @returns 是否支持SIMD + */ +export async function isSIMDSupported(): Promise { + if (!isWebAssemblySupported()) return false; + + try { + // 简单的SIMD检测 + const wasmBytes = new Uint8Array([ + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00 + ]); + await WebAssembly.instantiate(wasmBytes); + return true; + } catch { + return false; + } +} \ No newline at end of file diff --git a/source/tsconfig.json b/source/tsconfig.json index 88d8177c..f404e28c 100644 --- a/source/tsconfig.json +++ b/source/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "ES2018", + "target": "ES2020", "module": "ES2020", "moduleResolution": "node", "lib": ["ES2020", "DOM"],