diff --git a/docs/.vitepress/config.mjs b/docs/.vitepress/config.mjs index fe83f0cf..51c7fbbb 100644 --- a/docs/.vitepress/config.mjs +++ b/docs/.vitepress/config.mjs @@ -78,6 +78,16 @@ export default defineConfig({ { text: '时间和定时器 (Time)', link: '/guide/time-and-timers' }, { text: '日志系统 (Logger)', link: '/guide/logging' } ] + }, + { + text: '平台适配器', + link: '/guide/platform-adapter', + collapsed: false, + items: [ + { text: '浏览器适配器', link: '/guide/platform-adapter/browser' }, + { text: '微信小游戏适配器', link: '/guide/platform-adapter/wechat-minigame' }, + { text: 'Node.js适配器', link: '/guide/platform-adapter/nodejs' } + ] } ], '/examples/': [ diff --git a/docs/guide/index.md b/docs/guide/index.md index 78796a66..5df11794 100644 --- a/docs/guide/index.md +++ b/docs/guide/index.md @@ -23,4 +23,7 @@ 学习时间管理和定时器系统,实现游戏逻辑的精确时间控制。 ### [日志系统 (Logger)](./logging.md) -掌握分级日志系统,用于调试、监控和错误追踪。 \ No newline at end of file +掌握分级日志系统,用于调试、监控和错误追踪。 + +### [平台适配器 (Platform Adapter)](./platform-adapter.md) +了解如何为不同平台实现和注册平台适配器,支持浏览器、小游戏、Node.js等环境。 \ No newline at end of file diff --git a/docs/guide/platform-adapter.md b/docs/guide/platform-adapter.md new file mode 100644 index 00000000..26703cdf --- /dev/null +++ b/docs/guide/platform-adapter.md @@ -0,0 +1,289 @@ +# 平台适配器 + +## 概述 + +ECS框架提供了平台适配器接口,允许用户为不同的运行环境实现自定义的平台适配器。 + +**核心库只提供接口定义,平台适配器实现代码请从文档中复制使用。** + +## 为什么不提供单独的适配器包? + +1. **灵活性**: 不同项目对平台适配的需求可能不同,复制代码可以让用户根据需要自由修改 +2. **减少依赖**: 避免引入不必要的依赖包,保持核心框架的精简 +3. **定制化**: 用户可以根据具体的运行环境和需求进行定制 + +## 支持的平台 + +### 🌐 [浏览器适配器](./platform-adapter/browser.md) + +支持所有现代浏览器环境,包括 Chrome、Firefox、Safari、Edge 等。 + +**特性支持**: +- ✅ Worker (Web Worker) +- ✅ SharedArrayBuffer (需要COOP/COEP) +- ✅ Transferable Objects +- ✅ Module Worker (现代浏览器) + +**适用场景**: Web游戏、Web应用、PWA + +--- + +### 📱 [微信小游戏适配器](./platform-adapter/wechat-minigame.md) + +专为微信小游戏环境设计,处理微信小游戏的特殊限制和API。 + +**特性支持**: +- ✅ Worker (最多1个,需配置game.json) +- ❌ SharedArrayBuffer +- ❌ Transferable Objects +- ✅ 微信设备信息API + +**适用场景**: 微信小游戏开发 + +--- + +### 🖥️ [Node.js适配器](./platform-adapter/nodejs.md) + +为 Node.js 服务器环境提供支持,适用于游戏服务器和计算服务器。 + +**特性支持**: +- ✅ Worker Threads +- ✅ SharedArrayBuffer +- ✅ Transferable Objects +- ✅ 完整系统信息 + +**适用场景**: 游戏服务器、计算服务器、CLI工具 + +--- + +## 核心接口 + +### IPlatformAdapter + +```typescript +export interface IPlatformAdapter { + readonly name: string; + readonly version?: string; + + isWorkerSupported(): boolean; + isSharedArrayBufferSupported(): boolean; + getHardwareConcurrency(): number; + createWorker(script: string, options?: WorkerCreationOptions): PlatformWorker; + createSharedArrayBuffer(length: number): SharedArrayBuffer | null; + getHighResTimestamp(): number; + getPlatformConfig(): PlatformConfig; + getPlatformConfigAsync?(): Promise; +} +``` + +### PlatformWorker 接口 + +```typescript +export interface PlatformWorker { + postMessage(message: any, transfer?: Transferable[]): void; + onMessage(handler: (event: { data: any }) => void): void; + onError(handler: (error: ErrorEvent) => void): void; + terminate(): void; + readonly state: 'running' | 'terminated'; +} +``` + +## 使用方法 + +### 1. 选择合适的平台适配器 + +根据你的运行环境选择对应的适配器: + +```typescript +import { PlatformManager } from '@esengine/ecs-framework'; + +// 浏览器环境 +if (typeof window !== 'undefined') { + const { BrowserAdapter } = await import('./platform/BrowserAdapter'); + PlatformManager.getInstance().registerAdapter(new BrowserAdapter()); +} + +// 微信小游戏环境 +else if (typeof wx !== 'undefined') { + const { WeChatMiniGameAdapter } = await import('./platform/WeChatMiniGameAdapter'); + PlatformManager.getInstance().registerAdapter(new WeChatMiniGameAdapter()); +} + +// Node.js环境 +else if (typeof process !== 'undefined' && process.versions?.node) { + const { NodeAdapter } = await import('./platform/NodeAdapter'); + PlatformManager.getInstance().registerAdapter(new NodeAdapter()); +} +``` + +### 2. 检查适配器状态 + +```typescript +const manager = PlatformManager.getInstance(); + +// 检查是否已注册适配器 +if (manager.hasAdapter()) { + const adapter = manager.getAdapter(); + console.log('当前平台:', adapter.name); + console.log('平台版本:', adapter.version); + + // 检查功能支持 + console.log('Worker支持:', manager.supportsFeature('worker')); + console.log('SharedArrayBuffer支持:', manager.supportsFeature('shared-array-buffer')); +} +``` + +## 创建自定义适配器 + +如果现有的平台适配器不能满足你的需求,你可以创建自定义适配器: + +### 1. 实现接口 + +```typescript +import type { IPlatformAdapter, PlatformWorker, WorkerCreationOptions, PlatformConfig } from '@esengine/ecs-framework'; + +export class CustomAdapter implements IPlatformAdapter { + public readonly name = 'custom'; + public readonly version = '1.0.0'; + + public isWorkerSupported(): boolean { + // 实现你的 Worker 支持检查逻辑 + return false; + } + + public isSharedArrayBufferSupported(): boolean { + // 实现你的 SharedArrayBuffer 支持检查逻辑 + return false; + } + + public getHardwareConcurrency(): number { + // 返回你的平台的并发数 + return 1; + } + + public createWorker(script: string, options?: WorkerCreationOptions): PlatformWorker { + throw new Error('Worker not supported on this platform'); + } + + public createSharedArrayBuffer(length: number): SharedArrayBuffer | null { + return null; + } + + public getHighResTimestamp(): number { + return Date.now(); + } + + public getPlatformConfig(): PlatformConfig { + return { + maxWorkerCount: 1, + supportsModuleWorker: false, + supportsTransferableObjects: false, + limitations: { + workerNotSupported: true + } + }; + } +} +``` + +### 2. 注册自定义适配器 + +```typescript +import { PlatformManager } from '@esengine/ecs-framework'; +import { CustomAdapter } from './CustomAdapter'; + +const customAdapter = new CustomAdapter(); +PlatformManager.getInstance().registerAdapter(customAdapter); +``` + +## 最佳实践 + +### 1. 平台检测顺序 + +建议按照以下顺序检测和注册平台适配器: + +```typescript +async function initializePlatform() { + const manager = PlatformManager.getInstance(); + + try { + // 1. 微信小游戏 (优先级最高,环境特征最明显) + if (typeof wx !== 'undefined' && wx.getSystemInfoSync) { + const { WeChatMiniGameAdapter } = await import('./platform/WeChatMiniGameAdapter'); + manager.registerAdapter(new WeChatMiniGameAdapter()); + return; + } + + // 2. Node.js 环境 + if (typeof process !== 'undefined' && process.versions?.node) { + const { NodeAdapter } = await import('./platform/NodeAdapter'); + manager.registerAdapter(new NodeAdapter()); + return; + } + + // 3. 浏览器环境 (最后检测,覆盖面最广) + if (typeof window !== 'undefined' && typeof document !== 'undefined') { + const { BrowserAdapter } = await import('./platform/BrowserAdapter'); + manager.registerAdapter(new BrowserAdapter()); + return; + } + + // 4. 未知环境,使用默认适配器 + console.warn('未识别的平台环境,使用默认适配器'); + manager.registerAdapter(new CustomAdapter()); + + } catch (error) { + console.error('平台适配器初始化失败:', error); + throw error; + } +} +``` + +### 2. 功能降级处理 + +```typescript +function createWorkerSystem() { + const manager = PlatformManager.getInstance(); + + if (!manager.hasAdapter()) { + throw new Error('未注册平台适配器'); + } + + const config: WorkerSystemConfig = { + enableWorker: manager.supportsFeature('worker'), + workerCount: manager.supportsFeature('worker') ? + manager.getAdapter().getHardwareConcurrency() : 1, + useSharedArrayBuffer: manager.supportsFeature('shared-array-buffer') + }; + + // 如果不支持Worker,自动降级到同步处理 + if (!config.enableWorker) { + console.info('当前平台不支持Worker,使用同步处理模式'); + } + + return new PhysicsSystem(config); +} +``` + +### 3. 错误处理 + +```typescript +try { + await initializePlatform(); + + // 验证适配器功能 + const manager = PlatformManager.getInstance(); + const adapter = manager.getAdapter(); + + console.log(`平台适配器初始化成功: ${adapter.name} v${adapter.version}`); + +} catch (error) { + console.error('平台初始化失败:', error); + + // 提供降级方案 + const fallbackAdapter = new CustomAdapter(); + PlatformManager.getInstance().registerAdapter(fallbackAdapter); + + console.warn('使用降级适配器继续运行'); +} +``` \ No newline at end of file diff --git a/docs/guide/platform-adapter/browser.md b/docs/guide/platform-adapter/browser.md new file mode 100644 index 00000000..cb84deb4 --- /dev/null +++ b/docs/guide/platform-adapter/browser.md @@ -0,0 +1,475 @@ +# 浏览器适配器 + +## 概述 + +浏览器平台适配器为标准Web浏览器环境提供支持,包括 Chrome、Firefox、Safari、Edge 等现代浏览器。 + +## 特性支持 + +- ✅ **Worker**: 支持 Web Worker 和 Module Worker +- ✅ **SharedArrayBuffer**: 支持(需要COOP/COEP头部) +- ✅ **Transferable Objects**: 完全支持 +- ✅ **高精度时间**: 使用 `performance.now()` +- ✅ **设备信息**: 完整的浏览器和设备信息 + +## 完整实现 + +```typescript +import type { + IPlatformAdapter, + PlatformWorker, + WorkerCreationOptions, + PlatformConfig, + BrowserDeviceInfo +} from '@esengine/ecs-framework'; + +/** + * 浏览器平台适配器 + * 支持标准Web浏览器环境 + */ +export class BrowserAdapter implements IPlatformAdapter { + public readonly name = 'browser'; + public readonly version: string; + + constructor() { + this.version = this.getBrowserInfo(); + } + + /** + * 检查是否支持Worker + */ + public isWorkerSupported(): boolean { + return typeof Worker !== 'undefined'; + } + + /** + * 检查是否支持SharedArrayBuffer + */ + public isSharedArrayBufferSupported(): boolean { + return typeof SharedArrayBuffer !== 'undefined' && this.checkSharedArrayBufferEnabled(); + } + + /** + * 获取硬件并发数(CPU核心数) + */ + public getHardwareConcurrency(): number { + return navigator.hardwareConcurrency || 4; + } + + /** + * 创建Worker + */ + public createWorker(script: string, options: WorkerCreationOptions = {}): PlatformWorker { + if (!this.isWorkerSupported()) { + throw new Error('浏览器不支持Worker'); + } + + try { + return new BrowserWorker(script, options); + } catch (error) { + throw new Error(`创建浏览器Worker失败: ${(error as Error).message}`); + } + } + + /** + * 创建SharedArrayBuffer + */ + public createSharedArrayBuffer(length: number): SharedArrayBuffer | null { + if (!this.isSharedArrayBufferSupported()) { + return null; + } + + try { + return new SharedArrayBuffer(length); + } catch (error) { + console.warn('SharedArrayBuffer创建失败:', error); + return null; + } + } + + /** + * 获取高精度时间戳 + */ + public getHighResTimestamp(): number { + return performance.now(); + } + + /** + * 获取平台配置 + */ + public getPlatformConfig(): PlatformConfig { + return { + maxWorkerCount: this.getHardwareConcurrency(), + supportsModuleWorker: this.checkModuleWorkerSupport(), + supportsTransferableObjects: true, + maxSharedArrayBufferSize: this.getMaxSharedArrayBufferSize(), + workerScriptPrefix: '', + limitations: { + noEval: false, + requiresWorkerInit: false + }, + extensions: { + userAgent: navigator.userAgent, + vendor: navigator.vendor, + language: navigator.language, + cookieEnabled: navigator.cookieEnabled, + onLine: navigator.onLine + } + }; + } + + /** + * 获取浏览器设备信息 + */ + public getDeviceInfo(): BrowserDeviceInfo { + const deviceInfo: BrowserDeviceInfo = { + // 浏览器信息 + userAgent: navigator.userAgent, + vendor: navigator.vendor, + language: navigator.language, + languages: navigator.languages, + cookieEnabled: navigator.cookieEnabled, + onLine: navigator.onLine, + + // 硬件信息 + hardwareConcurrency: navigator.hardwareConcurrency, + deviceMemory: (navigator as any).deviceMemory, + maxTouchPoints: navigator.maxTouchPoints, + + // 屏幕信息 + screenWidth: screen.width, + screenHeight: screen.height, + availWidth: screen.availWidth, + availHeight: screen.availHeight, + colorDepth: screen.colorDepth, + pixelDepth: screen.pixelDepth, + + // 窗口信息 + innerWidth: window.innerWidth, + innerHeight: window.innerHeight, + outerWidth: window.outerWidth, + outerHeight: window.outerHeight, + devicePixelRatio: window.devicePixelRatio, + + // 平台信息 + platform: navigator.platform, + appVersion: navigator.appVersion, + appName: navigator.appName + }; + + // 连接信息(如果支持) + const connection = (navigator as any).connection; + if (connection) { + deviceInfo.connection = { + effectiveType: connection.effectiveType, + downlink: connection.downlink, + rtt: connection.rtt, + saveData: connection.saveData + }; + } + + return deviceInfo; + } + + /** + * 获取浏览器信息 + */ + private getBrowserInfo(): string { + const userAgent = navigator.userAgent; + let browserName = 'Unknown'; + let version = 'Unknown'; + + // 简单的浏览器检测 + if (userAgent.includes('Chrome')) { + browserName = 'Chrome'; + const match = userAgent.match(/Chrome\/([0-9.]+)/); + if (match) version = match[1]; + } else if (userAgent.includes('Firefox')) { + browserName = 'Firefox'; + const match = userAgent.match(/Firefox\/([0-9.]+)/); + if (match) version = match[1]; + } else if (userAgent.includes('Safari')) { + browserName = 'Safari'; + const match = userAgent.match(/Version\/([0-9.]+)/); + if (match) version = match[1]; + } else if (userAgent.includes('Edge')) { + browserName = 'Edge'; + const match = userAgent.match(/Edge\/([0-9.]+)/); + if (match) version = match[1]; + } + + return `${browserName} ${version}`; + } + + /** + * 检查SharedArrayBuffer是否真正可用 + */ + private checkSharedArrayBufferEnabled(): boolean { + try { + // 尝试创建一个小的SharedArrayBuffer来测试 + new SharedArrayBuffer(8); + return true; + } catch { + return false; + } + } + + /** + * 检查是否支持模块Worker + */ + private checkModuleWorkerSupport(): boolean { + try { + // 简单检测:Chrome 80+、Firefox 114+等支持 + return 'type' in Worker.prototype || false; + } catch { + return false; + } + } + + /** + * 获取SharedArrayBuffer最大大小限制 + */ + private getMaxSharedArrayBufferSize(): number { + // 浏览器通常限制为1GB或更少 + const deviceMemory = (navigator as any).deviceMemory; + if (deviceMemory) { + // 限制为设备内存的25% + return Math.min(deviceMemory * 0.25 * 1024 * 1024 * 1024, 1024 * 1024 * 1024); + } + + // 默认1GB + return 1024 * 1024 * 1024; + } +} + +/** + * 浏览器Worker封装 + */ +class BrowserWorker implements PlatformWorker { + private _state: 'running' | 'terminated' = 'running'; + private worker: Worker; + + constructor(script: string, options: WorkerCreationOptions = {}) { + this.worker = this.createBrowserWorker(script, options); + } + + public get state(): 'running' | 'terminated' { + return this._state; + } + + public postMessage(message: any, transfer?: Transferable[]): void { + if (this._state === 'terminated') { + throw new Error('Worker已被终止'); + } + + try { + if (transfer && transfer.length > 0) { + this.worker.postMessage(message, transfer); + } else { + this.worker.postMessage(message); + } + } catch (error) { + throw new Error(`发送消息到Worker失败: ${(error as Error).message}`); + } + } + + public onMessage(handler: (event: { data: any }) => void): void { + this.worker.onmessage = (event: MessageEvent) => { + handler({ data: event.data }); + }; + } + + public onError(handler: (error: ErrorEvent) => void): void { + this.worker.onerror = handler; + } + + public terminate(): void { + if (this._state === 'running') { + try { + this.worker.terminate(); + this._state = 'terminated'; + } catch (error) { + console.error('终止Worker失败:', error); + } + } + } + + /** + * 创建浏览器Worker + */ + private createBrowserWorker(script: string, options: WorkerCreationOptions): Worker { + try { + // 创建Blob URL + const blob = new Blob([script], { type: 'application/javascript' }); + const url = URL.createObjectURL(blob); + + // 创建Worker + const worker = new Worker(url, { + type: options.type || 'classic', + credentials: options.credentials, + name: options.name + }); + + // 清理Blob URL(延迟清理,确保Worker已加载) + setTimeout(() => { + URL.revokeObjectURL(url); + }, 1000); + + return worker; + } catch (error) { + throw new Error(`无法创建浏览器Worker: ${(error as Error).message}`); + } + } +} +``` + +## 使用方法 + +### 1. 复制代码 + +将上述代码复制到你的项目中,例如 `src/platform/BrowserAdapter.ts`。 + +### 2. 注册适配器 + +```typescript +import { PlatformManager } from '@esengine/ecs-framework'; +import { BrowserAdapter } from './platform/BrowserAdapter'; + +// 创建并注册浏览器适配器 +const browserAdapter = new BrowserAdapter(); +PlatformManager.registerAdapter(browserAdapter); + +// 框架会自动检测和使用合适的适配器 +``` + +### 3. 使用 WorkerEntitySystem + +浏览器适配器与 WorkerEntitySystem 配合使用,框架会自动处理 Worker 脚本的创建: + +```typescript +import { WorkerEntitySystem, Matcher } from '@esengine/ecs-framework'; + +class PhysicsSystem extends WorkerEntitySystem { + constructor() { + super(Matcher.all(Transform, Velocity), { + enableWorker: true, + workerCount: navigator.hardwareConcurrency || 4, + useSharedArrayBuffer: true, + systemConfig: { gravity: 9.8 } + }); + } + + protected getDefaultEntityDataSize(): number { + return 6; // x, y, vx, vy, mass, radius + } + + protected extractEntityData(entity: Entity): PhysicsData { + const transform = entity.getComponent(Transform); + const velocity = entity.getComponent(Velocity); + return { + x: transform.x, + y: transform.y, + vx: velocity.x, + vy: velocity.y, + mass: 1, + radius: 10 + }; + } + + // 这个函数会被自动序列化并在Worker中执行 + protected workerProcess(entities, deltaTime, config) { + return entities.map(entity => { + // 应用重力 + entity.vy += config.gravity * deltaTime; + + // 更新位置 + entity.x += entity.vx * deltaTime; + entity.y += entity.vy * deltaTime; + + return entity; + }); + } + + protected applyResult(entity: Entity, result: PhysicsData): void { + const transform = entity.getComponent(Transform); + const velocity = entity.getComponent(Velocity); + + transform.x = result.x; + transform.y = result.y; + velocity.x = result.vx; + velocity.y = result.vy; + } +} + +interface PhysicsData { + x: number; + y: number; + vx: number; + vy: number; + mass: number; + radius: number; +} +``` + +### 4. 检查适配器状态 + +```typescript +const manager = PlatformManager.getInstance(); + +// 检查是否已注册适配器 +if (manager.hasAdapter()) { + console.log('适配器信息:', manager.getAdapterInfo()); + + // 检查功能支持 + console.log('Worker支持:', manager.supportsFeature('worker')); + console.log('SharedArrayBuffer支持:', manager.supportsFeature('shared-array-buffer')); +} +``` + +## 重要注意事项 + +### SharedArrayBuffer 支持 + +SharedArrayBuffer 需要特殊的安全配置: + +1. **HTTPS**: 必须在安全上下文中使用 +2. **COOP/COEP 头部**: 需要设置正确的跨域隔离头部 + +```html + + + +``` + +或在服务器配置中设置: + +``` +Cross-Origin-Opener-Policy: same-origin +Cross-Origin-Embedder-Policy: require-corp +``` + +### 浏览器兼容性 + +- **Worker**: 所有现代浏览器支持 +- **Module Worker**: Chrome 80+, Firefox 114+ +- **SharedArrayBuffer**: Chrome 68+, Firefox 79+(需要COOP/COEP) +- **Transferable Objects**: 所有现代浏览器支持 + +## 性能优化建议 + +1. **Worker 池**: 复用 Worker 实例,避免频繁创建和销毁 +2. **数据传输**: 使用 Transferable Objects 减少数据拷贝 +3. **SharedArrayBuffer**: 对于大量数据共享,使用 SharedArrayBuffer +4. **模块 Worker**: 在支持的浏览器中使用模块 Worker 来更好地组织代码 + +## 调试技巧 + +```typescript +// 检查浏览器支持情况 +const adapter = new BrowserAdapter(); +console.log('Worker支持:', adapter.isWorkerSupported()); +console.log('SharedArrayBuffer支持:', adapter.isSharedArrayBufferSupported()); +console.log('硬件并发数:', adapter.getHardwareConcurrency()); +console.log('平台配置:', adapter.getPlatformConfig()); +console.log('设备信息:', adapter.getDeviceInfo()); +``` \ No newline at end of file diff --git a/docs/guide/platform-adapter/nodejs.md b/docs/guide/platform-adapter/nodejs.md new file mode 100644 index 00000000..574e2369 --- /dev/null +++ b/docs/guide/platform-adapter/nodejs.md @@ -0,0 +1,558 @@ +# Node.js 适配器 + +## 概述 + +Node.js 平台适配器为 Node.js 服务器环境提供支持,适用于游戏服务器、计算服务器或其他需要 ECS 架构的服务器应用。 + +## 特性支持 + +- ✅ **Worker**: 支持(通过 `worker_threads` 模块) +- ❌ **SharedArrayBuffer**: 支持(Node.js 16.17.0+) +- ✅ **Transferable Objects**: 完全支持 +- ✅ **高精度时间**: 使用 `process.hrtime.bigint()` +- ✅ **设备信息**: 完整的系统和进程信息 + +## 完整实现 + +```typescript +import { worker_threads, Worker, isMainThread, parentPort } from 'worker_threads'; +import * as os from 'os'; +import * as process from 'process'; +import * as fs from 'fs'; +import * as path from 'path'; +import type { + IPlatformAdapter, + PlatformWorker, + WorkerCreationOptions, + PlatformConfig, + NodeDeviceInfo +} from '@esengine/ecs-framework'; + +/** + * Node.js 平台适配器 + * 支持 Node.js 服务器环境 + */ +export class NodeAdapter implements IPlatformAdapter { + public readonly name = 'nodejs'; + public readonly version: string; + + constructor() { + this.version = process.version; + } + + /** + * 检查是否支持Worker + */ + public isWorkerSupported(): boolean { + try { + // 检查 worker_threads 模块是否可用 + return typeof worker_threads !== 'undefined' && typeof Worker !== 'undefined'; + } catch { + return false; + } + } + + /** + * 检查是否支持SharedArrayBuffer + */ + public isSharedArrayBufferSupported(): boolean { + // Node.js 支持 SharedArrayBuffer + return typeof SharedArrayBuffer !== 'undefined'; + } + + /** + * 获取硬件并发数(CPU核心数) + */ + public getHardwareConcurrency(): number { + return os.cpus().length; + } + + /** + * 创建Worker + */ + public createWorker(script: string, options: WorkerCreationOptions = {}): PlatformWorker { + if (!this.isWorkerSupported()) { + throw new Error('Node.js环境不支持Worker Threads'); + } + + try { + return new NodeWorker(script, options); + } catch (error) { + throw new Error(`创建Node.js Worker失败: ${(error as Error).message}`); + } + } + + /** + * 创建SharedArrayBuffer + */ + public createSharedArrayBuffer(length: number): SharedArrayBuffer | null { + if (!this.isSharedArrayBufferSupported()) { + return null; + } + + try { + return new SharedArrayBuffer(length); + } catch (error) { + console.warn('SharedArrayBuffer创建失败:', error); + return null; + } + } + + /** + * 获取高精度时间戳(纳秒) + */ + public getHighResTimestamp(): number { + // 返回毫秒,与浏览器 performance.now() 保持一致 + return Number(process.hrtime.bigint()) / 1000000; + } + + /** + * 获取平台配置 + */ + public getPlatformConfig(): PlatformConfig { + return { + maxWorkerCount: this.getHardwareConcurrency(), + supportsModuleWorker: true, // Node.js 支持 ES 模块 + supportsTransferableObjects: true, + maxSharedArrayBufferSize: this.getMaxSharedArrayBufferSize(), + workerScriptPrefix: '', + limitations: { + noEval: false, // Node.js 支持 eval + requiresWorkerInit: false + }, + extensions: { + platform: 'nodejs', + nodeVersion: process.version, + v8Version: process.versions.v8, + uvVersion: process.versions.uv, + zlibVersion: process.versions.zlib, + opensslVersion: process.versions.openssl, + architecture: process.arch, + endianness: os.endianness(), + pid: process.pid, + ppid: process.ppid + } + }; + } + + /** + * 获取Node.js设备信息 + */ + public getDeviceInfo(): NodeDeviceInfo { + const cpus = os.cpus(); + const networkInterfaces = os.networkInterfaces(); + const userInfo = os.userInfo(); + + return { + // 系统信息 + platform: os.platform(), + arch: os.arch(), + type: os.type(), + release: os.release(), + version: os.version(), + hostname: os.hostname(), + + // CPU信息 + cpus: cpus.map(cpu => ({ + model: cpu.model, + speed: cpu.speed, + times: cpu.times + })), + + // 内存信息 + totalMemory: os.totalmem(), + freeMemory: os.freemem(), + usedMemory: os.totalmem() - os.freemem(), + + // 负载信息 + loadAverage: os.loadavg(), + + // 网络接口 + networkInterfaces: Object.fromEntries( + Object.entries(networkInterfaces).map(([name, interfaces]) => [ + name, + (interfaces || []).map(iface => ({ + address: iface.address, + netmask: iface.netmask, + family: iface.family as 'IPv4' | 'IPv6', + mac: iface.mac, + internal: iface.internal, + cidr: iface.cidr, + scopeid: iface.scopeid + })) + ]) + ), + + // 进程信息 + process: { + pid: process.pid, + ppid: process.ppid, + version: process.version, + versions: process.versions, + uptime: process.uptime() + }, + + // 用户信息 + userInfo: { + uid: userInfo.uid, + gid: userInfo.gid, + username: userInfo.username, + homedir: userInfo.homedir, + shell: userInfo.shell + } + }; + } + + /** + * 获取SharedArrayBuffer最大大小限制 + */ + private getMaxSharedArrayBufferSize(): number { + const totalMemory = os.totalmem(); + // 限制为系统总内存的50% + return Math.floor(totalMemory * 0.5); + } +} + +/** + * Node.js Worker封装 + */ +class NodeWorker implements PlatformWorker { + private _state: 'running' | 'terminated' = 'running'; + private worker: Worker; + private isTemporaryFile: boolean = false; + private scriptPath: string; + + constructor(script: string, options: WorkerCreationOptions = {}) { + try { + // 判断 script 是文件路径还是脚本内容 + if (this.isFilePath(script)) { + // 直接使用文件路径 + this.scriptPath = script; + this.isTemporaryFile = false; + } else { + // 将脚本内容写入临时文件 + this.scriptPath = this.writeScriptToFile(script, options.name); + this.isTemporaryFile = true; + } + + // 创建Worker + this.worker = new Worker(this.scriptPath, { + // Node.js Worker options + workerData: options.name ? { name: options.name } : undefined + }); + + } catch (error) { + throw new Error(`创建Node.js Worker失败: ${(error as Error).message}`); + } + } + + /** + * 判断是否为文件路径 + */ + private isFilePath(script: string): boolean { + // 检查是否看起来像文件路径 + return (script.endsWith('.js') || script.endsWith('.mjs') || script.endsWith('.ts')) && + !script.includes('\n') && + !script.includes(';') && + script.length < 500; // 文件路径通常不会太长 + } + + /** + * 将脚本内容写入临时文件 + */ + private writeScriptToFile(script: string, name?: string): string { + const tmpDir = os.tmpdir(); + const fileName = name ? `worker-${name}-${Date.now()}.js` : `worker-${Date.now()}.js`; + const filePath = path.join(tmpDir, fileName); + + try { + fs.writeFileSync(filePath, script, 'utf8'); + return filePath; + } catch (error) { + throw new Error(`写入Worker脚本文件失败: ${(error as Error).message}`); + } + } + + public get state(): 'running' | 'terminated' { + return this._state; + } + + public postMessage(message: any, transfer?: Transferable[]): void { + if (this._state === 'terminated') { + throw new Error('Worker已被终止'); + } + + try { + if (transfer && transfer.length > 0) { + // Node.js Worker 支持 Transferable Objects + this.worker.postMessage(message, transfer); + } else { + this.worker.postMessage(message); + } + } catch (error) { + throw new Error(`发送消息到Node.js Worker失败: ${(error as Error).message}`); + } + } + + public onMessage(handler: (event: { data: any }) => void): void { + this.worker.on('message', (data: any) => { + handler({ data }); + }); + } + + public onError(handler: (error: ErrorEvent) => void): void { + this.worker.on('error', (error: Error) => { + // 将 Error 转换为 ErrorEvent 格式 + const errorEvent = { + message: error.message, + filename: '', + lineno: 0, + colno: 0, + error: error + } as ErrorEvent; + handler(errorEvent); + }); + } + + public terminate(): void { + if (this._state === 'running') { + try { + this.worker.terminate(); + this._state = 'terminated'; + + // 清理临时脚本文件 + this.cleanupScriptFile(); + } catch (error) { + console.error('终止Node.js Worker失败:', error); + } + } + } + + /** + * 清理临时脚本文件 + */ + private cleanupScriptFile(): void { + // 只清理临时创建的文件,不清理用户提供的文件路径 + if (this.scriptPath && this.isTemporaryFile) { + try { + fs.unlinkSync(this.scriptPath); + } catch (error) { + console.warn('清理Worker脚本文件失败:', error); + } + } + } +} +``` + +## 使用方法 + +### 1. 复制代码 + +将上述代码复制到你的项目中,例如 `src/platform/NodeAdapter.ts`。 + +### 2. 注册适配器 + +```typescript +import { PlatformManager } from '@esengine/ecs-framework'; +import { NodeAdapter } from './platform/NodeAdapter'; + +// 检查是否在Node.js环境 +if (typeof process !== 'undefined' && process.versions && process.versions.node) { + const nodeAdapter = new NodeAdapter(); + PlatformManager.getInstance().registerAdapter(nodeAdapter); +} +``` + +### 3. 使用 WorkerEntitySystem + +Node.js 适配器与 WorkerEntitySystem 配合使用,框架会自动处理 Worker 脚本的创建: + +```typescript +import { WorkerEntitySystem, Matcher } from '@esengine/ecs-framework'; +import * as os from 'os'; + +class PhysicsSystem extends WorkerEntitySystem { + constructor() { + super(Matcher.all(Transform, Velocity), { + enableWorker: true, + workerCount: os.cpus().length, // 使用所有CPU核心 + useSharedArrayBuffer: true, + systemConfig: { gravity: 9.8 } + }); + } + + protected getDefaultEntityDataSize(): number { + return 6; // x, y, vx, vy, mass, radius + } + + protected extractEntityData(entity: Entity): PhysicsData { + const transform = entity.getComponent(Transform); + const velocity = entity.getComponent(Velocity); + return { + x: transform.x, + y: transform.y, + vx: velocity.x, + vy: velocity.y, + mass: 1, + radius: 10 + }; + } + + // 这个函数会被自动序列化并在Worker中执行 + protected workerProcess(entities, deltaTime, config) { + return entities.map(entity => { + // 应用重力 + entity.vy += config.gravity * deltaTime; + + // 更新位置 + entity.x += entity.vx * deltaTime; + entity.y += entity.vy * deltaTime; + + return entity; + }); + } + + protected applyResult(entity: Entity, result: PhysicsData): void { + const transform = entity.getComponent(Transform); + const velocity = entity.getComponent(Velocity); + + transform.x = result.x; + transform.y = result.y; + velocity.x = result.vx; + velocity.y = result.vy; + } +} + +interface PhysicsData { + x: number; + y: number; + vx: number; + vy: number; + mass: number; + radius: number; +} +``` + +### 4. 获取系统信息 + +```typescript +const manager = PlatformManager.getInstance(); +if (manager.hasAdapter()) { + const adapter = manager.getAdapter(); + const deviceInfo = adapter.getDeviceInfo(); + + console.log('Node.js版本:', deviceInfo.process?.version); + console.log('CPU核心数:', deviceInfo.cpus?.length); + console.log('总内存:', Math.round(deviceInfo.totalMemory! / 1024 / 1024), 'MB'); + console.log('可用内存:', Math.round(deviceInfo.freeMemory! / 1024 / 1024), 'MB'); +} +``` + +## 官方文档参考 + +Node.js Worker Threads 相关官方文档: + +- [Worker Threads 官方文档](https://nodejs.org/api/worker_threads.html) +- [SharedArrayBuffer 支持](https://nodejs.org/api/globals.html#class-sharedarraybuffer) +- [OS 模块文档](https://nodejs.org/api/os.html) +- [Process 模块文档](https://nodejs.org/api/process.html) + +## 重要注意事项 + +### Worker Threads 要求 + +- **Node.js版本**: 需要 Node.js 10.5.0+ (建议 12+) +- **模块类型**: 支持 CommonJS 和 ES 模块 +- **线程限制**: 理论上无限制,但建议不超过 CPU 核心数的 2 倍 + +### 性能优化建议 + +#### 1. Worker 池管理 + +```typescript +class ServerPhysicsSystem extends WorkerEntitySystem { + constructor() { + const cpuCount = os.cpus().length; + super(Matcher.all(Transform, Velocity), { + enableWorker: true, + workerCount: Math.min(cpuCount * 2, 16), // 最多16个Worker + entitiesPerWorker: 1000, // 每个Worker处理1000个实体 + useSharedArrayBuffer: true, + systemConfig: { + gravity: 9.8, + timeStep: 1/60 + } + }); + } +} +``` + +#### 2. 内存管理 + +```typescript +class MemoryMonitor { + public static checkMemoryUsage(): void { + const used = process.memoryUsage(); + + console.log('内存使用情况:'); + console.log(` RSS: ${Math.round(used.rss / 1024 / 1024)} MB`); + console.log(` Heap Used: ${Math.round(used.heapUsed / 1024 / 1024)} MB`); + console.log(` Heap Total: ${Math.round(used.heapTotal / 1024 / 1024)} MB`); + console.log(` External: ${Math.round(used.external / 1024 / 1024)} MB`); + + // 内存使用率过高时触发警告 + if (used.heapUsed > used.heapTotal * 0.9) { + console.warn('内存使用率过高,建议优化或重启'); + } + } +} + +// 定期检查内存使用 +setInterval(() => { + MemoryMonitor.checkMemoryUsage(); +}, 30000); // 每30秒检查一次 +``` + +#### 3. 服务器环境优化 + +```typescript +// 设置进程标题 +process.title = 'ecs-game-server'; + +// 处理未捕获异常 +process.on('uncaughtException', (error) => { + console.error('未捕获异常:', error); + process.exit(1); +}); + +process.on('unhandledRejection', (reason, promise) => { + console.error('未处理的Promise拒绝:', reason); +}); + +// 优雅关闭 +process.on('SIGTERM', () => { + console.log('收到SIGTERM信号,正在关闭服务器...'); + // 清理资源 + process.exit(0); +}); +``` + +## 调试技巧 + +```typescript +// 检查Node.js环境支持情况 +const adapter = new NodeAdapter(); +console.log('Node.js版本:', adapter.version); +console.log('Worker支持:', adapter.isWorkerSupported()); +console.log('SharedArrayBuffer支持:', adapter.isSharedArrayBufferSupported()); +console.log('CPU核心数:', adapter.getHardwareConcurrency()); + +// 获取详细配置 +const config = adapter.getPlatformConfig(); +console.log('平台配置:', JSON.stringify(config, null, 2)); + +// 系统资源监控 +const deviceInfo = adapter.getDeviceInfo(); +console.log('系统负载:', deviceInfo.loadAverage); +console.log('网络接口:', Object.keys(deviceInfo.networkInterfaces!)); +``` \ No newline at end of file diff --git a/docs/guide/platform-adapter/wechat-minigame.md b/docs/guide/platform-adapter/wechat-minigame.md new file mode 100644 index 00000000..a280d45d --- /dev/null +++ b/docs/guide/platform-adapter/wechat-minigame.md @@ -0,0 +1,621 @@ +# 微信小游戏适配器 + +## 概述 + +微信小游戏平台适配器专为微信小游戏环境设计,处理微信小游戏的特殊限制和API。 + +## 特性支持 + +- ✅ **Worker**: 支持(通过 `wx.createWorker` 创建,需要配置 game.json) +- ❌ **SharedArrayBuffer**: 不支持 +- ❌ **Transferable Objects**: 不支持(只支持可序列化对象) +- ✅ **高精度时间**: 使用 `Date.now()` 或 `wx.getPerformance()` +- ✅ **设备信息**: 完整的微信小游戏设备信息 + +## 完整实现 + +```typescript +import type { + IPlatformAdapter, + PlatformWorker, + WorkerCreationOptions, + PlatformConfig, + WeChatDeviceInfo +} from '@esengine/ecs-framework'; + +/** + * 微信小游戏平台适配器 + * 支持微信小游戏环境 + */ +export class WeChatMiniGameAdapter implements IPlatformAdapter { + public readonly name = 'wechat-minigame'; + public readonly version: string; + private systemInfo: any; + + constructor() { + // 获取微信小游戏版本信息 + this.systemInfo = this.getSystemInfo(); + this.version = this.systemInfo.version || 'unknown'; + } + + /** + * 检查是否支持Worker + */ + public isWorkerSupported(): boolean { + // 微信小游戏支持Worker,通过wx.createWorker创建 + return typeof wx !== 'undefined' && typeof wx.createWorker === 'function'; + } + + /** + * 检查是否支持SharedArrayBuffer(不支持) + */ + public isSharedArrayBufferSupported(): boolean { + return false; // 微信小游戏不支持SharedArrayBuffer + } + + /** + * 获取硬件并发数 + */ + public getHardwareConcurrency(): number { + // 微信小游戏官方限制:最多只能创建 1 个 Worker + return 1; + } + + /** + * 创建Worker + * @param script 脚本内容或文件路径 + * @param options Worker创建选项 + */ + public createWorker(script: string, options: WorkerCreationOptions = {}): PlatformWorker { + if (!this.isWorkerSupported()) { + throw new Error('微信小游戏不支持Worker'); + } + + try { + return new WeChatWorker(script, options); + } catch (error) { + throw new Error(`创建微信Worker失败: ${(error as Error).message}`); + } + } + + /** + * 创建SharedArrayBuffer(不支持) + */ + public createSharedArrayBuffer(length: number): SharedArrayBuffer | null { + return null; // 微信小游戏不支持SharedArrayBuffer + } + + /** + * 获取高精度时间戳 + */ + public getHighResTimestamp(): number { + // 尝试使用微信的性能API,否则使用Date.now() + if (typeof wx !== 'undefined' && wx.getPerformance) { + const performance = wx.getPerformance(); + return performance.now(); + } + return Date.now(); + } + + /** + * 获取平台配置 + */ + public getPlatformConfig(): PlatformConfig { + return { + maxWorkerCount: 1, // 微信小游戏最多支持 1 个 Worker + supportsModuleWorker: false, // 不支持模块Worker + supportsTransferableObjects: this.checkTransferableObjectsSupport(), + maxSharedArrayBufferSize: 0, + workerScriptPrefix: '', + limitations: { + noEval: true, // 微信小游戏限制eval使用 + requiresWorkerInit: false, + memoryLimit: this.getMemoryLimit(), + workerNotSupported: false, + workerLimitations: [ + '最多只能创建 1 个 Worker', + '创建新Worker前必须先调用 Worker.terminate()', + 'Worker脚本必须为项目内相对路径', + '需要在 game.json 中配置 workers 路径', + '使用 worker.onMessage() 而不是 self.onmessage', + '需要基础库 1.9.90 及以上版本' + ] + }, + extensions: { + platform: 'wechat-minigame', + systemInfo: this.systemInfo, + appId: this.systemInfo.host?.appId || 'unknown' + } + }; + } + + /** + * 获取微信小游戏设备信息 + */ + public getDeviceInfo(): WeChatDeviceInfo { + return { + // 设备基础信息 + brand: this.systemInfo.brand, + model: this.systemInfo.model, + platform: this.systemInfo.platform, + system: this.systemInfo.system, + benchmarkLevel: this.systemInfo.benchmarkLevel, + cpuType: this.systemInfo.cpuType, + memorySize: this.systemInfo.memorySize, + deviceAbi: this.systemInfo.deviceAbi, + abi: this.systemInfo.abi, + + // 窗口信息 + screenWidth: this.systemInfo.screenWidth, + screenHeight: this.systemInfo.screenHeight, + screenTop: this.systemInfo.screenTop, + windowWidth: this.systemInfo.windowWidth, + windowHeight: this.systemInfo.windowHeight, + pixelRatio: this.systemInfo.pixelRatio, + statusBarHeight: this.systemInfo.statusBarHeight, + safeArea: this.systemInfo.safeArea, + + // 应用信息 + version: this.systemInfo.version, + language: this.systemInfo.language, + theme: this.systemInfo.theme, + SDKVersion: this.systemInfo.SDKVersion, + enableDebug: this.systemInfo.enableDebug, + fontSizeSetting: this.systemInfo.fontSizeSetting, + host: this.systemInfo.host + }; + } + + /** + * 异步获取完整的平台配置 + */ + public async getPlatformConfigAsync(): Promise { + // 可以在这里添加异步获取设备性能信息的逻辑 + const baseConfig = this.getPlatformConfig(); + + // 尝试获取设备性能信息 + try { + const benchmarkLevel = await this.getBenchmarkLevel(); + baseConfig.extensions = { + ...baseConfig.extensions, + benchmarkLevel + }; + } catch (error) { + console.warn('获取性能基准失败:', error); + } + + return baseConfig; + } + + /** + * 检查是否支持Transferable Objects + */ + private checkTransferableObjectsSupport(): boolean { + // 微信小游戏不支持 Transferable Objects + // 基础库 2.20.2 之前只支持可序列化的 key-value 对象 + // 2.20.2 之后支持任意类型数据,但仍然不支持 Transferable Objects + return false; + } + + /** + * 获取系统信息 + */ + private getSystemInfo(): any { + if (typeof wx !== 'undefined' && wx.getSystemInfoSync) { + try { + return wx.getSystemInfoSync(); + } catch (error) { + console.warn('获取微信系统信息失败:', error); + return {}; + } + } + return {}; + } + + /** + * 获取内存限制 + */ + private getMemoryLimit(): number { + // 微信小游戏通常有内存限制 + const memorySize = this.systemInfo.memorySize; + if (memorySize) { + // 解析内存大小字符串(如 "4GB") + const match = memorySize.match(/(\d+)([GM]B)?/i); + if (match) { + const value = parseInt(match[1], 10); + const unit = match[2]?.toUpperCase(); + + if (unit === 'GB') { + return value * 1024 * 1024 * 1024; + } else if (unit === 'MB') { + return value * 1024 * 1024; + } + } + } + + // 默认限制为512MB + return 512 * 1024 * 1024; + } + + /** + * 异步获取设备性能基准 + */ + private async getBenchmarkLevel(): Promise { + return new Promise((resolve) => { + if (typeof wx !== 'undefined' && wx.getDeviceInfo) { + wx.getDeviceInfo({ + success: (res: any) => { + resolve(res.benchmarkLevel || 0); + }, + fail: () => { + resolve(0); + } + }); + } else { + resolve(this.systemInfo.benchmarkLevel || 0); + } + }); + } +} + +/** + * 微信Worker封装 + */ +class WeChatWorker implements PlatformWorker { + private _state: 'running' | 'terminated' = 'running'; + private worker: any; + private scriptPath: string; + private isTemporaryFile: boolean = false; + + constructor(script: string, options: WorkerCreationOptions = {}) { + if (typeof wx === 'undefined' || typeof wx.createWorker !== 'function') { + throw new Error('微信小游戏不支持Worker'); + } + + try { + // 判断 script 是文件路径还是脚本内容 + if (this.isFilePath(script)) { + // 直接使用文件路径 + this.scriptPath = script; + this.isTemporaryFile = false; + this.worker = wx.createWorker(this.scriptPath); + } else { + // 将脚本内容写入文件系统 + this.scriptPath = this.writeScriptToFile(script, options.name); + this.isTemporaryFile = true; + this.worker = wx.createWorker(this.scriptPath); + } + } catch (error) { + throw new Error(`创建微信Worker失败: ${(error as Error).message}`); + } + } + + /** + * 判断是否为文件路径 + */ + private isFilePath(script: string): boolean { + // 简单判断:如果包含 .js 后缀且不包含换行符或分号,认为是文件路径 + return script.endsWith('.js') && + !script.includes('\n') && + !script.includes(';') && + script.length < 200; // 文件路径通常不会太长 + } + + /** + * 将脚本内容写入文件系统 + */ + private writeScriptToFile(script: string, name?: string): string { + const fs = wx.getFileSystemManager(); + const fileName = name ? `worker-${name}.js` : `worker-${Date.now()}.js`; + const filePath = `${wx.env.USER_DATA_PATH}/${fileName}`; + + try { + fs.writeFileSync(filePath, script, 'utf8'); + return filePath; + } catch (error) { + throw new Error(`写入Worker脚本文件失败: ${(error as Error).message}`); + } + } + + public get state(): 'running' | 'terminated' { + return this._state; + } + + public postMessage(message: any, transfer?: Transferable[]): void { + if (this._state === 'terminated') { + throw new Error('Worker已被终止'); + } + + try { + // 微信小游戏 Worker 只支持可序列化对象,忽略 transfer 参数 + this.worker.postMessage(message); + } catch (error) { + throw new Error(`发送消息到微信Worker失败: ${(error as Error).message}`); + } + } + + public onMessage(handler: (event: { data: any }) => void): void { + // 微信小游戏使用 onMessage 方法,不是 onmessage 属性 + this.worker.onMessage((res: any) => { + handler({ data: res }); + }); + } + + public onError(handler: (error: ErrorEvent) => void): void { + // 注意:微信小游戏 Worker 的错误处理可能与标准不同 + if (this.worker.onError) { + this.worker.onError(handler); + } + } + + public terminate(): void { + if (this._state === 'running') { + try { + this.worker.terminate(); + this._state = 'terminated'; + + // 清理临时脚本文件 + this.cleanupScriptFile(); + } catch (error) { + console.error('终止微信Worker失败:', error); + } + } + } + + /** + * 清理临时脚本文件 + */ + private cleanupScriptFile(): void { + // 只清理临时创建的文件,不清理用户提供的文件路径 + if (this.scriptPath && this.isTemporaryFile) { + try { + const fs = wx.getFileSystemManager(); + fs.unlinkSync(this.scriptPath); + } catch (error) { + console.warn('清理Worker脚本文件失败:', error); + } + } + } +} +``` + +## 使用方法 + +### 1. 复制代码 + +将上述代码复制到你的项目中,例如 `src/platform/WeChatMiniGameAdapter.ts`。 + +### 2. 注册适配器 + +```typescript +import { PlatformManager } from '@esengine/ecs-framework'; +import { WeChatMiniGameAdapter } from './platform/WeChatMiniGameAdapter'; + +// 检查是否在微信小游戏环境 +if (typeof wx !== 'undefined') { + const wechatAdapter = new WeChatMiniGameAdapter(); + PlatformManager.getInstance().registerAdapter(wechatAdapter); +} +``` + +### 3. Worker 使用方式 + +微信小游戏适配器支持两种 Worker 使用方式: + +#### 方式一:使用脚本文件路径(推荐) + +```typescript +// 1. 在 game.json 中配置 Worker 路径 +/* +{ + "workers": "workers" +} +*/ + +// 2. 创建 Worker 脚本文件 workers/physics.js +// workers/physics.js 内容: +/* +// 微信小游戏 Worker 中使用 worker 对象,不是 self +worker.onMessage(function(data) { + const { entities, deltaTime, systemConfig } = data; + + // 处理物理计算 + const results = entities.map(entity => { + entity.vy += systemConfig.gravity * deltaTime; + entity.x += entity.vx * deltaTime; + entity.y += entity.vy * deltaTime; + return entity; + }); + + worker.postMessage({ result: results }); +}); +*/ + +// 2. 在 WorkerEntitySystem 中使用 +class PhysicsSystem extends WorkerEntitySystem { + constructor() { + super(Matcher.all(Transform, Velocity), { + enableWorker: true, + workerCount: 1, // 微信小游戏限制 + systemConfig: { gravity: 100 } + }); + } + + // 重写创建 Worker 脚本的方法,返回文件路径 + private createWorkerScript(): string { + return 'workers/physics.js'; // 微信小游戏使用相对路径 + } +} +``` + +#### 方式二:动态脚本内容(自动处理) + +```typescript +// WorkerEntitySystem 会自动将用户的 workerProcess 函数序列化 +class PhysicsSystem extends WorkerEntitySystem { + protected workerProcess(entities, deltaTime, config) { + return entities.map(entity => { + entity.vy += config.gravity * deltaTime; + entity.x += entity.vx * deltaTime; + entity.y += entity.vy * deltaTime; + return entity; + }); + } +} +// 框架会自动将此函数内容写入临时文件并创建 Worker +``` + +### 4. 获取设备信息 + +```typescript +const manager = PlatformManager.getInstance(); +if (manager.hasAdapter()) { + const adapter = manager.getAdapter(); + console.log('微信设备信息:', adapter.getDeviceInfo()); +} +``` + +## 官方文档参考 + +在使用微信小游戏 Worker 之前,建议先阅读官方文档: + +- [wx.createWorker API](https://developers.weixin.qq.com/minigame/dev/api/worker/wx.createWorker.html) +- [Worker.postMessage API](https://developers.weixin.qq.com/minigame/dev/api/worker/Worker.postMessage.html) +- [Worker.onMessage API](https://developers.weixin.qq.com/minigame/dev/api/worker/Worker.onMessage.html) +- [Worker.terminate API](https://developers.weixin.qq.com/minigame/dev/api/worker/Worker.terminate.html) + +## 重要注意事项 + +### Worker 限制和配置 + +微信小游戏的 Worker 有以下限制: + +- **数量限制**: 最多只能创建 1 个 Worker +- **版本要求**: 需要基础库 1.9.90 及以上版本 +- **配置要求**: 必须在 `game.json` 中配置 workers 路径 +- **文件路径**: Worker 脚本必须为项目内的相对路径 +- **生命周期**: 创建新 Worker 前必须先调用 `Worker.terminate()` 终止当前 Worker +- **API 差异**: 使用 `worker.onMessage()` 而不是 `self.onmessage` + +#### 必要配置 + +在 `game.json` 中添加 workers 配置: + +```json +{ + "deviceOrientation": "portrait", + "showStatusBar": false, + "workers": "workers", + "subpackages": [] +} +``` + +### 内存限制 + +微信小游戏有严格的内存限制: + +- 通常限制在 256MB - 512MB +- 需要及时释放不用的资源 +- 避免内存泄漏 + +### API 限制 + +- 不支持 `eval()` 函数 +- 不支持 `Function` 构造器 +- DOM API 受限 +- 文件系统 API 受限 + +## 性能优化建议 + +### 1. 分帧处理 + +```typescript +class FramedProcessor { + private tasks: (() => void)[] = []; + private isProcessing = false; + + public addTask(task: () => void): void { + this.tasks.push(task); + if (!this.isProcessing) { + this.processNextFrame(); + } + } + + private processNextFrame(): void { + this.isProcessing = true; + const startTime = Date.now(); + const frameTime = 16; // 16ms per frame + + while (this.tasks.length > 0 && Date.now() - startTime < frameTime) { + const task = this.tasks.shift(); + if (task) task(); + } + + if (this.tasks.length > 0) { + setTimeout(() => this.processNextFrame(), 0); + } else { + this.isProcessing = false; + } + } +} +``` + +### 2. 内存管理 + +```typescript +class MemoryManager { + private static readonly MAX_MEMORY = 256 * 1024 * 1024; // 256MB + + public static checkMemoryUsage(): void { + if (typeof wx !== 'undefined' && wx.getPerformance) { + const performance = wx.getPerformance(); + const memoryInfo = performance.getEntries().find( + (entry: any) => entry.entryType === 'memory' + ); + + if (memoryInfo && memoryInfo.usedJSHeapSize > this.MAX_MEMORY * 0.8) { + console.warn('内存使用率过高,建议清理资源'); + // 触发垃圾回收或资源清理 + } + } + } +} +``` + +## 调试技巧 + +```typescript +// 检查微信小游戏环境 +if (typeof wx !== 'undefined') { + const adapter = new WeChatMiniGameAdapter(); + + console.log('微信版本:', adapter.version); + console.log('设备信息:', adapter.getDeviceInfo()); + console.log('平台配置:', adapter.getPlatformConfig()); + + // 检查功能支持 + console.log('Worker支持:', adapter.isWorkerSupported()); + console.log('SharedArrayBuffer支持:', adapter.isSharedArrayBufferSupported()); +} +``` + +## 微信小游戏特殊API + +```typescript +// 获取设备性能等级 +if (typeof wx !== 'undefined' && wx.getDeviceInfo) { + wx.getDeviceInfo({ + success: (res) => { + console.log('设备性能等级:', res.benchmarkLevel); + } + }); +} + +// 监听内存警告 +if (typeof wx !== 'undefined' && wx.onMemoryWarning) { + wx.onMemoryWarning(() => { + console.warn('收到内存警告,开始清理资源'); + // 清理不必要的资源 + }); +} +``` \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index b1664610..26790fd5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5229,9 +5229,9 @@ } }, "node_modules/@types/node": { - "version": "20.19.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.0.tgz", - "integrity": "sha512-hfrc+1tud1xcdVTABC2JiomZJEklMcXYNTVtZLAeqTVWD+qL5jkHKT+1lOtqDdGxt+mB53DTtiz673vfjU8D1Q==", + "version": "20.19.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.17.tgz", + "integrity": "sha512-gfehUI8N1z92kygssiuWvLiwcbOB3IRktR6hTDgJlXMYh5OvkPSRmgfoBUmfZt+vhwJtX7v1Yw4KvvAf7c5QKQ==", "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -15534,7 +15534,7 @@ "@rollup/plugin-node-resolve": "^16.0.1", "@rollup/plugin-terser": "^0.4.4", "@types/jest": "^29.5.14", - "@types/node": "^20.19.0", + "@types/node": "^20.19.17", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "rimraf": "^5.0.0", diff --git a/packages/core/package.json b/packages/core/package.json index a6218f60..81a3cd03 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -58,7 +58,7 @@ "@rollup/plugin-node-resolve": "^16.0.1", "@rollup/plugin-terser": "^0.4.4", "@types/jest": "^29.5.14", - "@types/node": "^20.19.0", + "@types/node": "^20.19.17", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "rimraf": "^5.0.0", diff --git a/packages/core/src/Platform/BrowserAdapter.ts b/packages/core/src/Platform/BrowserAdapter.ts deleted file mode 100644 index df321ce2..00000000 --- a/packages/core/src/Platform/BrowserAdapter.ts +++ /dev/null @@ -1,278 +0,0 @@ -import type { - IPlatformAdapter, - PlatformWorker, - WorkerCreationOptions, - PlatformConfig, - BrowserDeviceInfo -} from './IPlatformAdapter'; -import { createLogger, type ILogger } from '../Utils/Logger'; - -/** - * 浏览器平台适配器 - * 支持标准Web API的浏览器环境 - */ -export class BrowserAdapter implements IPlatformAdapter { - public readonly name = 'browser'; - public readonly version = this.getBrowserVersion(); - private readonly logger: ILogger; - - constructor() { - this.logger = createLogger('BrowserAdapter'); - } - - /** - * 检查是否支持Worker - */ - public isWorkerSupported(): boolean { - return typeof Worker !== 'undefined' && typeof Blob !== 'undefined' && typeof URL !== 'undefined'; - } - - /** - * 检查是否支持SharedArrayBuffer - */ - public isSharedArrayBufferSupported(): boolean { - return typeof SharedArrayBuffer !== 'undefined' && - typeof self !== 'undefined' && - self.crossOriginIsolated === true; - } - - /** - * 获取硬件并发数 - */ - public getHardwareConcurrency(): number { - if (typeof navigator !== 'undefined' && navigator.hardwareConcurrency) { - return navigator.hardwareConcurrency; - } - return 4; // 默认值 - } - - /** - * 创建Worker - */ - public createWorker(script: string, options: WorkerCreationOptions = {}): PlatformWorker { - const blob = new Blob([script], { type: 'application/javascript' }); - const scriptURL = URL.createObjectURL(blob); - - const worker = new Worker(scriptURL, { - type: options.type || 'classic', - credentials: options.credentials || 'same-origin', - name: options.name - }); - - return new BrowserWorker(worker, scriptURL); - } - - /** - * 创建SharedArrayBuffer - */ - public createSharedArrayBuffer(length: number): SharedArrayBuffer | null { - if (!this.isSharedArrayBufferSupported()) { - return null; - } - - try { - return new SharedArrayBuffer(length); - } catch (error) { - this.logger.warn('Failed to create SharedArrayBuffer:', error); - return null; - } - } - - /** - * 获取高精度时间戳 - */ - public getHighResTimestamp(): number { - if (typeof performance !== 'undefined' && performance.now) { - return performance.now(); - } - return Date.now(); - } - - /** - * 获取平台配置 - */ - public getPlatformConfig(): PlatformConfig { - return { - maxWorkerCount: this.getHardwareConcurrency(), - supportsModuleWorker: this.supportsModuleWorker(), - supportsTransferableObjects: this.supportsTransferableObjects(), - maxSharedArrayBufferSize: this.getMaxSharedArrayBufferSize(), - workerScriptPrefix: '', - limitations: { - noEval: false, - requiresWorkerInit: false - }, - extensions: { - supportsServiceWorker: typeof navigator !== 'undefined' && 'serviceWorker' in navigator, - supportsWebAssembly: typeof WebAssembly !== 'undefined' - } - }; - } - - - /** - * 获取浏览器设备信息 - */ - public getDeviceInfo(): BrowserDeviceInfo { - try { - const deviceInfo: BrowserDeviceInfo = {}; - - // 浏览器基础信息 - if (typeof navigator !== 'undefined') { - deviceInfo.userAgent = navigator.userAgent; - deviceInfo.vendor = navigator.vendor; - deviceInfo.language = navigator.language; - deviceInfo.languages = navigator.languages as string[]; - deviceInfo.cookieEnabled = navigator.cookieEnabled; - deviceInfo.onLine = navigator.onLine; - deviceInfo.doNotTrack = navigator.doNotTrack; - deviceInfo.platform = navigator.platform; - deviceInfo.appVersion = navigator.appVersion; - deviceInfo.appName = navigator.appName; - - // 硬件信息 - deviceInfo.hardwareConcurrency = navigator.hardwareConcurrency; - deviceInfo.maxTouchPoints = navigator.maxTouchPoints; - - // 设备内存(实验性API) - if ('deviceMemory' in navigator) { - deviceInfo.deviceMemory = (navigator as any).deviceMemory; - } - - // 网络连接信息(实验性API) - if ('connection' in navigator) { - const connection = (navigator as any).connection; - deviceInfo.connection = { - effectiveType: connection.effectiveType, - downlink: connection.downlink, - rtt: connection.rtt, - saveData: connection.saveData - }; - } - } - - // 屏幕信息 - if (typeof screen !== 'undefined') { - deviceInfo.screenWidth = screen.width; - deviceInfo.screenHeight = screen.height; - deviceInfo.availWidth = screen.availWidth; - deviceInfo.availHeight = screen.availHeight; - deviceInfo.colorDepth = screen.colorDepth; - deviceInfo.pixelDepth = screen.pixelDepth; - } - - // 窗口信息 - if (typeof window !== 'undefined') { - deviceInfo.innerWidth = window.innerWidth; - deviceInfo.innerHeight = window.innerHeight; - deviceInfo.outerWidth = window.outerWidth; - deviceInfo.outerHeight = window.outerHeight; - deviceInfo.devicePixelRatio = window.devicePixelRatio; - } - - return deviceInfo; - } catch (error) { - this.logger.warn('获取浏览器设备信息失败', error); - return {} as BrowserDeviceInfo; - } - } - - /** - * 获取浏览器版本信息 - */ - private getBrowserVersion(): string { - if (typeof navigator !== 'undefined') { - return navigator.userAgent; - } - return 'unknown'; - } - - /** - * 检查是否支持模块Worker - */ - private supportsModuleWorker(): boolean { - try { - // 尝试创建一个简单的模块Worker来测试支持 - const blob = new Blob(['export {};'], { type: 'application/javascript' }); - const url = URL.createObjectURL(blob); - const worker = new Worker(url, { type: 'module' }); - worker.terminate(); - URL.revokeObjectURL(url); - return true; - } catch { - return false; - } - } - - /** - * 检查是否支持Transferable Objects - */ - private supportsTransferableObjects(): boolean { - try { - const buffer = new ArrayBuffer(8); - const blob = new Blob(['self.postMessage(null);'], { type: 'application/javascript' }); - const url = URL.createObjectURL(blob); - const worker = new Worker(url); - worker.postMessage(buffer, [buffer]); - worker.terminate(); - URL.revokeObjectURL(url); - return buffer.byteLength === 0; // Buffer应该被transfer走 - } catch { - return false; - } - } - - /** - * 获取SharedArrayBuffer最大大小限制 - */ - private getMaxSharedArrayBufferSize(): number { - // 浏览器环境返回保守的默认值 - // 大多数现代浏览器都支持较大的SharedArrayBuffer - return 256 * 1024 * 1024; // 256MB,适合大多数ECS应用场景 - } -} - -/** - * 浏览器Worker封装 - */ -class BrowserWorker implements PlatformWorker { - private _state: 'running' | 'terminated' = 'running'; - - constructor( - private worker: Worker, - private scriptURL: string - ) {} - - public get state(): 'running' | 'terminated' { - return this._state; - } - - public postMessage(message: any, transfer?: Transferable[]): void { - if (this._state === 'terminated') { - throw new Error('Worker has been terminated'); - } - if (transfer && transfer.length > 0) { - this.worker.postMessage(message, transfer); - } else { - this.worker.postMessage(message); - } - } - - public onMessage(handler: (event: { data: any }) => void): void { - this.worker.onmessage = (event: MessageEvent) => { - handler({ data: event.data }); - }; - } - - public onError(handler: (error: ErrorEvent) => void): void { - this.worker.onerror = handler; - } - - public terminate(): void { - if (this._state === 'running') { - this.worker.terminate(); - URL.revokeObjectURL(this.scriptURL); - this._state = 'terminated'; - } - } -} \ No newline at end of file diff --git a/packages/core/src/Platform/IPlatformAdapter.ts b/packages/core/src/Platform/IPlatformAdapter.ts index 53f58a4b..45a86b82 100644 --- a/packages/core/src/Platform/IPlatformAdapter.ts +++ b/packages/core/src/Platform/IPlatformAdapter.ts @@ -179,7 +179,7 @@ export interface PlatformDetectionResult { /** * 平台类型 */ - platform: 'browser' | 'wechat-minigame' | 'bytedance-minigame' | 'alipay-minigame' | 'baidu-minigame' | 'unknown'; + platform: 'browser' | 'wechat-minigame' | 'bytedance-minigame' | 'alipay-minigame' | 'baidu-minigame' | 'nodejs' | 'unknown'; /** * 是否确定检测结果 @@ -197,93 +197,3 @@ export interface PlatformDetectionResult { adapterClass?: string; } -/** - * 微信小游戏设备信息接口 - */ -export interface WeChatDeviceInfo { - // 设备基础信息 - brand?: string; - model?: string; - platform?: string; - system?: string; - benchmarkLevel?: number; - cpuType?: string; - memorySize?: string; - deviceAbi?: string; - abi?: string; - - // 窗口信息 - screenWidth?: number; - screenHeight?: number; - screenTop?: number; - windowWidth?: number; - windowHeight?: number; - pixelRatio?: number; - statusBarHeight?: number; - safeArea?: { - top: number; - bottom: number; - left: number; - right: number; - width: number; - height: number; - }; - - // 应用信息 - version?: string; - language?: string; - theme?: string; - SDKVersion?: string; - enableDebug?: boolean; - fontSizeSetting?: number; - host?: { - appId: string; - }; -} - -/** - * 浏览器设备信息接口 - */ -export interface BrowserDeviceInfo { - // 浏览器信息 - userAgent?: string; - vendor?: string; - language?: string; - languages?: string[]; - cookieEnabled?: boolean; - onLine?: boolean; - doNotTrack?: string | null; - - // 硬件信息 - hardwareConcurrency?: number; - deviceMemory?: number; - maxTouchPoints?: number; - - // 屏幕信息 - screenWidth?: number; - screenHeight?: number; - availWidth?: number; - availHeight?: number; - colorDepth?: number; - pixelDepth?: number; - - // 窗口信息 - innerWidth?: number; - innerHeight?: number; - outerWidth?: number; - outerHeight?: number; - devicePixelRatio?: number; - - // 连接信息(如果支持) - connection?: { - effectiveType?: string; - downlink?: number; - rtt?: number; - saveData?: boolean; - }; - - // 平台信息 - platform?: string; - appVersion?: string; - appName?: string; -} \ No newline at end of file diff --git a/packages/core/src/Platform/PlatformDetector.ts b/packages/core/src/Platform/PlatformDetector.ts index 9ba02882..c34f0b14 100644 --- a/packages/core/src/Platform/PlatformDetector.ts +++ b/packages/core/src/Platform/PlatformDetector.ts @@ -27,8 +27,15 @@ export class PlatformDetector { features.push('self'); } + // 检测Node.js环境(优先级最高,因为Node.js可能包含全局对象模拟) + if (this.isNodeJS()) { + platform = 'nodejs'; + confident = true; + adapterClass = 'NodeAdapter'; + features.push('nodejs', 'process', 'require'); + } // 检测微信小游戏环境 - if (this.isWeChatMiniGame()) { + else if (this.isWeChatMiniGame()) { platform = 'wechat-minigame'; confident = true; adapterClass = 'WeChatMiniGameAdapter'; @@ -122,6 +129,28 @@ export class PlatformDetector { return false; } + /** + * 检测是否为Node.js环境 + */ + private static isNodeJS(): boolean { + try { + // 检查Node.js特有的全局对象和模块 + return !!( + typeof process !== 'undefined' && + process.versions && + process.versions.node && + typeof require !== 'undefined' && + typeof module !== 'undefined' && + typeof exports !== 'undefined' && + // 确保不是在浏览器环境中的Node.js模拟 + typeof window === 'undefined' && + typeof document === 'undefined' + ); + } catch { + return false; + } + } + /** * 检测是否为支付宝小游戏环境 */ diff --git a/packages/core/src/Platform/PlatformManager.ts b/packages/core/src/Platform/PlatformManager.ts index b6400ae7..63a3954d 100644 --- a/packages/core/src/Platform/PlatformManager.ts +++ b/packages/core/src/Platform/PlatformManager.ts @@ -1,22 +1,17 @@ import type { IPlatformAdapter } from './IPlatformAdapter'; -import { PlatformDetector } from './PlatformDetector'; -import { BrowserAdapter } from './BrowserAdapter'; -import { WeChatMiniGameAdapter } from './WeChatMiniGameAdapter'; import { createLogger, type ILogger } from '../Utils/Logger'; /** * 平台管理器 - * 负责自动检测平台并提供对应的适配器 + * 用户需要手动注册平台适配器 */ export class PlatformManager { private static instance: PlatformManager; private adapter: IPlatformAdapter | null = null; - private detectionResult: any = null; private readonly logger: ILogger; private constructor() { this.logger = createLogger('PlatformManager'); - this.detectAndInitialize(); } /** @@ -34,106 +29,42 @@ export class PlatformManager { */ public getAdapter(): IPlatformAdapter { if (!this.adapter) { - throw new Error('平台适配器未初始化,请检查平台环境'); + throw new Error('平台适配器未注册,请调用 registerAdapter() 注册适配器'); } return this.adapter; } /** - * 获取平台检测结果 + * 注册平台适配器 */ - public getDetectionResult() { - return this.detectionResult; - } - - /** - * 手动设置适配器(用于测试或特殊情况) - */ - public setAdapter(adapter: IPlatformAdapter): void { + public registerAdapter(adapter: IPlatformAdapter): void { this.adapter = adapter; - } - - /** - * 检测平台并初始化对应的适配器 - */ - private detectAndInitialize(): void { - this.detectionResult = PlatformDetector.detect(); - - try { - switch (this.detectionResult.platform) { - case 'wechat-minigame': - this.adapter = new WeChatMiniGameAdapter(); - break; - - case 'bytedance-minigame': - // TODO: 实现字节跳动小游戏适配器 - this.logger.warn('字节跳动小游戏适配器尚未实现,降级到浏览器适配器'); - this.adapter = new BrowserAdapter(); - break; - - case 'alipay-minigame': - // TODO: 实现支付宝小游戏适配器 - this.logger.warn('支付宝小游戏适配器尚未实现,降级到浏览器适配器'); - this.adapter = new BrowserAdapter(); - break; - - case 'baidu-minigame': - // TODO: 实现百度小游戏适配器 - this.logger.warn('百度小游戏适配器尚未实现,降级到浏览器适配器'); - this.adapter = new BrowserAdapter(); - break; - - case 'browser': - default: - this.adapter = new BrowserAdapter(); - break; - } - - // 输出初始化信息 - this.logInitializationInfo(); - - } catch (error) { - this.logger.error('平台适配器初始化失败:', error); - // 降级到浏览器适配器 - this.adapter = new BrowserAdapter(); - } - } - - /** - * 输出初始化信息 - */ - private logInitializationInfo(): void { - if (!this.adapter) return; - - const config = this.adapter.getPlatformConfig(); - - this.logger.info(`平台适配器初始化完成:`, { - platform: this.detectionResult.platform, - adapterName: this.adapter.name, - version: this.adapter.version, - features: this.detectionResult.features, - config: { - maxWorkerCount: config.maxWorkerCount, - supportsSharedArrayBuffer: this.adapter.isSharedArrayBufferSupported(), - supportsWorker: this.adapter.isWorkerSupported(), - hardwareConcurrency: this.adapter.getHardwareConcurrency() - } + this.logger.info(`平台适配器已注册: ${adapter.name}`, { + name: adapter.name, + version: adapter.version, + supportsWorker: adapter.isWorkerSupported(), + supportsSharedArrayBuffer: adapter.isSharedArrayBufferSupported(), + hardwareConcurrency: adapter.getHardwareConcurrency() }); } /** - * 获取详细的平台信息(用于调试) + * 检查是否已注册适配器 */ - public getDetailedInfo(): any { - return { - detectionResult: this.detectionResult, - detailedInfo: PlatformDetector.getDetailedInfo(), - adapterInfo: this.adapter ? { - name: this.adapter.name, - version: this.adapter.version, - config: this.adapter.getPlatformConfig() - } : null - }; + public hasAdapter(): boolean { + return this.adapter !== null; + } + + + /** + * 获取平台适配器信息(用于调试) + */ + public getAdapterInfo(): any { + return this.adapter ? { + name: this.adapter.name, + version: this.adapter.version, + config: this.adapter.getPlatformConfig() + } : null; } /** @@ -192,7 +123,7 @@ export class PlatformManager { */ public async getFullPlatformConfig(): Promise { if (!this.adapter) { - return null; + throw new Error('平台适配器未注册'); } // 如果适配器支持异步获取配置,使用异步方法 diff --git a/packages/core/src/Platform/WeChatMiniGameAdapter.ts b/packages/core/src/Platform/WeChatMiniGameAdapter.ts deleted file mode 100644 index b3a6e678..00000000 --- a/packages/core/src/Platform/WeChatMiniGameAdapter.ts +++ /dev/null @@ -1,451 +0,0 @@ -/// - -import type { - IPlatformAdapter, - PlatformWorker, - WorkerCreationOptions, - PlatformConfig, - WeChatDeviceInfo -} from './IPlatformAdapter'; -import { createLogger, type ILogger } from '../Utils/Logger'; - -/** - * 微信小游戏平台适配器 - * 适配微信小游戏环境的特殊限制和API - */ -export class WeChatMiniGameAdapter implements IPlatformAdapter { - public readonly name = 'wechat-minigame'; - public readonly version = this.getWeChatVersion(); - - private readonly wx: WechatMinigame.Wx; - private readonly logger: ILogger; - - constructor() { - this.logger = createLogger('WeChatMiniGameAdapter'); - this.wx = (globalThis as any).wx; - if (!this.wx) { - throw new Error('微信小游戏环境未检测到wx对象'); - } - } - - /** - * 检查是否支持Worker - * 微信小游戏虽然有Worker API,但限制太严格,对于动态ECS系统不实用 - */ - public isWorkerSupported(): boolean { - // 虽然微信小游戏有Worker API,但由于以下限制,实际上不适用于动态ECS系统: - // 1. 不能动态创建Worker脚本(必须预配置文件) - // 2. 禁止eval(不能动态执行用户的处理函数) - // 3. 需要用户手动配置game.json - // 因此对于WorkerEntitySystem来说,认为不支持Worker - return false; - } - - /** - * 检查是否支持SharedArrayBuffer - * 微信小游戏不支持SharedArrayBuffer,因为: - * 1. 不满足crossOriginIsolated要求 - * 2. 小游戏运行在受限沙箱环境中 - * 3. 出于安全考虑,微信限制了此API - */ - public isSharedArrayBufferSupported(): boolean { - // 微信小游戏明确不支持SharedArrayBuffer - // 即使有API存在,也无法在沙箱环境中正常使用 - return false; - } - - /** - * 获取硬件并发数 - * 微信小游戏没有直接的CPU核心数API,返回保守的默认值 - * 用户应该根据自己的业务需求和测试结果来设置Worker数量 - */ - public getHardwareConcurrency(): number { - // 微信小游戏平台返回保守的默认值 - // 用户可以通过getDevicePerformanceInfo()获取详细信息来自行判断 - return 4; - } - - /** - * 获取设备性能信息(供用户参考) - * 用户可以根据这些信息自行决定Worker配置 - */ - public getDevicePerformanceInfo(): Promise<{ - benchmarkLevel?: number; - modelLevel?: number; - memorySize?: string; - cpuType?: string; - platform?: string; - system?: string; - }> { - return new Promise((resolve) => { - const result: any = {}; - - // 获取设备基础信息 - if (typeof this.wx.getDeviceInfo === 'function') { - try { - const deviceInfo = this.wx.getDeviceInfo(); - Object.assign(result, { - memorySize: deviceInfo.memorySize, - cpuType: deviceInfo.cpuType, - platform: deviceInfo.platform, - system: deviceInfo.system, - benchmarkLevel: deviceInfo.benchmarkLevel - }); - } catch (error) { - this.logger.warn('获取设备信息失败', error); - } - } - - // 获取性能基准信息 - if (typeof this.wx.getDeviceBenchmarkInfo === 'function') { - this.wx.getDeviceBenchmarkInfo({ - success: (res) => { - result.benchmarkLevel = res.benchmarkLevel; - result.modelLevel = res.modelLevel; - resolve(result); - }, - fail: () => { - resolve(result); - } - }); - } else { - resolve(result); - } - }); - } - - /** - * 创建Worker - * 微信小游戏不支持WorkerEntitySystem所需的动态Worker创建 - */ - public createWorker(_script: string, _options: WorkerCreationOptions = {}): PlatformWorker { - throw new Error( - '微信小游戏不支持WorkerEntitySystem。' + - '原因:1) 不能动态创建Worker脚本 2) 禁止eval动态执行代码。' + - 'WorkerEntitySystem将自动降级到同步模式运行。' - ); - } - - /** - * 从预配置的文件路径创建Worker(仅供特殊用途) - * 注意:这不适用于WorkerEntitySystem,仅供其他特殊Worker需求使用 - */ - public createWorkerFromPath(scriptPath: string, options: { - useExperimentalWorker?: boolean; - name?: string; - } = {}): PlatformWorker { - // 即使支持原生Worker API,也明确说明不适用于ECS - this.logger.warn('微信小游戏Worker不适用于WorkerEntitySystem,建议使用同步模式'); - return new WeChatWorker(scriptPath, options, this.wx); - } - - /** - * 创建SharedArrayBuffer - * 微信小游戏通常不支持,返回null - */ - public createSharedArrayBuffer(length: number): SharedArrayBuffer | null { - if (!this.isSharedArrayBufferSupported()) { - this.logger.info('微信小游戏环境不支持SharedArrayBuffer,将使用Worker模式'); - return null; - } - - try { - return new SharedArrayBuffer(length); - } catch (error) { - this.logger.warn('SharedArrayBuffer创建失败', error); - return null; - } - } - - /** - * 获取高精度时间戳 - */ - public getHighResTimestamp(): number { - // 微信小游戏支持performance.now() - if (typeof performance !== 'undefined' && performance.now) { - return performance.now(); - } - - // 备选方案:使用Date.now() - return Date.now(); - } - - /** - * 获取平台配置(同步版本,不包含异步获取的性能信息) - */ - public getPlatformConfig(): PlatformConfig { - return { - maxWorkerCount: 0, // 不支持WorkerEntitySystem - supportsModuleWorker: false, - supportsTransferableObjects: false, // 对WorkerEntitySystem无意义 - maxSharedArrayBufferSize: 0, // 不支持SharedArrayBuffer - workerScriptPrefix: '', - limitations: { - noEval: true, // 微信小游戏禁止eval - requiresWorkerInit: true, - // 明确说明Worker限制 - workerNotSupported: true, - workerLimitations: [ - '不能动态创建Worker脚本', - '禁止eval动态执行代码', - '必须预配置Worker文件路径', - '不适用于动态ECS系统' - ] - }, - extensions: { - wechatVersion: this.version, - supportedAPIs: this.getSupportedAPIs(), - deviceInfo: this.getDeviceInfo() - } - }; - } - - /** - * 异步获取平台配置(包含性能信息) - */ - public async getPlatformConfigAsync(): Promise { - const baseConfig = this.getPlatformConfig(); - - try { - // 异步获取性能信息 - const performanceInfo = await this.getDevicePerformanceInfo(); - - return { - ...baseConfig, - extensions: { - ...baseConfig.extensions, - performanceInfo - } - }; - } catch (error) { - this.logger.warn('获取性能信息失败,返回基础配置', error); - return baseConfig; - } - } - - - - /** - * 获取微信版本信息 - */ - private getWeChatVersion(): string { - try { - const systemInfo = this.wx.getSystemInfoSync(); - return `WeChat ${systemInfo.version} (${systemInfo.platform})`; - } catch { - return 'WeChat Unknown'; - } - } - - - - /** - * 获取支持的API列表 - */ - private getSupportedAPIs(): string[] { - const apis: string[] = []; - - // 检查常用的微信小游戏API - const commonAPIs = [ - 'getSystemInfo', 'createCanvas', 'createImage', 'createAudio', - 'createWorker', 'downloadFile', 'request', 'connectSocket', - 'setStorage', 'getStorage', 'showToast', 'showModal' - ]; - - for (const api of commonAPIs) { - if (typeof (this.wx as any)[api] === 'function') { - apis.push(api); - } - } - - return apis; - } - - /** - * 获取设备信息 - */ - public getDeviceInfo(): WeChatDeviceInfo { - try { - const deviceInfo: any = {}; - - // 优先使用最新的设备信息API - if (typeof this.wx.getDeviceInfo === 'function') { - const info = this.wx.getDeviceInfo(); - Object.assign(deviceInfo, { - brand: info.brand, - model: info.model, - platform: info.platform, - system: info.system, - benchmarkLevel: info.benchmarkLevel, - cpuType: info.cpuType, - memorySize: info.memorySize, - deviceAbi: info.deviceAbi, - abi: info.abi - }); - } - - // 获取设备性能基准信息 - if (typeof this.wx.getDeviceBenchmarkInfo === 'function') { - this.wx.getDeviceBenchmarkInfo({ - success: (res) => { - deviceInfo.benchmarkLevel = res.benchmarkLevel; - deviceInfo.modelLevel = res.modelLevel; - } - }); - } - - // 获取窗口信息 - if (typeof this.wx.getWindowInfo === 'function') { - const windowInfo = this.wx.getWindowInfo(); - Object.assign(deviceInfo, { - screenWidth: windowInfo.screenWidth, - screenHeight: windowInfo.screenHeight, - screenTop: windowInfo.screenTop, - windowWidth: windowInfo.windowWidth, - windowHeight: windowInfo.windowHeight, - pixelRatio: windowInfo.pixelRatio, - statusBarHeight: windowInfo.statusBarHeight, - safeArea: windowInfo.safeArea - }); - } - - // 获取应用基础信息 - if (typeof this.wx.getAppBaseInfo === 'function') { - try { - const appInfo = this.wx.getAppBaseInfo(); - Object.assign(deviceInfo, { - version: appInfo.version, - language: appInfo.language, - theme: appInfo.theme, - SDKVersion: appInfo.SDKVersion, - enableDebug: appInfo.enableDebug, - fontSizeSetting: appInfo.fontSizeSetting, - // host是一个对象,不是hostName - host: appInfo.host - }); - } catch (error) { - this.logger.warn('获取应用基础信息失败', error); - } - } - - // 如果新API不完整,才使用废弃API作为兜底 - if (Object.keys(deviceInfo).length === 0 && typeof this.wx.getSystemInfoSync === 'function') { - this.logger.warn('新API不可用,使用废弃的getSystemInfoSync作为兜底'); - try { - const systemInfo = this.wx.getSystemInfoSync(); - return { - brand: systemInfo.brand, - model: systemInfo.model, - platform: systemInfo.platform, - system: systemInfo.system, - version: systemInfo.version, - benchmarkLevel: systemInfo.benchmarkLevel, - screenWidth: systemInfo.screenWidth, - screenHeight: systemInfo.screenHeight, - pixelRatio: systemInfo.pixelRatio - } as WeChatDeviceInfo; - } catch (error) { - this.logger.error('所有获取设备信息的方法都失败了', error); - return {} as WeChatDeviceInfo; - } - } - - return deviceInfo as WeChatDeviceInfo; - } catch (error) { - this.logger.warn('获取设备信息失败', error); - return {} as WeChatDeviceInfo; - } - } - -} - -/** - * 微信小游戏Worker封装 - */ -class WeChatWorker implements PlatformWorker { - private _state: 'running' | 'terminated' = 'running'; - private worker!: WechatMinigame.Worker; - private readonly logger: ILogger; - - constructor( - private scriptPath: string, - private options: { useExperimentalWorker?: boolean; name?: string }, - private wx: WechatMinigame.Wx - ) { - this.logger = createLogger('WeChatWorker'); - this.createWeChatWorker(); - } - - public get state(): 'running' | 'terminated' { - return this._state; - } - - public postMessage(message: any, _transfer?: Transferable[]): void { - if (this._state === 'terminated') { - throw new Error('Worker已被终止'); - } - - if (this.worker) { - this.worker.postMessage(message); - } - } - - public onMessage(handler: (event: { data: any }) => void): void { - if (this.worker) { - this.worker.onMessage((data: any) => { - handler({ data }); - }); - } - } - - public onError(handler: (error: ErrorEvent) => void): void { - if (this.worker) { - this.worker.onError((error: any) => { - // 转换微信错误格式为标准ErrorEvent格式 - const errorEvent = new ErrorEvent('error', { - message: error.message || '微信Worker错误', - filename: error.filename || '', - lineno: error.lineno || 0, - colno: error.colno || 0, - error: error - }); - handler(errorEvent); - }); - } - } - - public terminate(): void { - if (this._state === 'running' && this.worker) { - this.worker.terminate(); - this._state = 'terminated'; - } - } - - /** - * 创建微信Worker - * 使用预配置的Worker脚本文件路径 - */ - private createWeChatWorker(): void { - try { - // 使用微信小游戏的createWorker API,传入预配置的脚本路径 - this.worker = this.wx.createWorker(this.scriptPath, { - useExperimentalWorker: this.options.useExperimentalWorker || false - }); - - // 监听Worker被系统回收事件(实验性Worker特有) - if (this.options.useExperimentalWorker && this.worker.onProcessKilled) { - this.worker.onProcessKilled(() => { - this.logger.warn(`微信Worker ${this.scriptPath} 被系统回收`); - this._state = 'terminated'; - }); - } - - } catch (error) { - this.logger.error('创建微信Worker失败:', error); - throw new Error( - `无法创建微信Worker: ${error}。` + - `请确保已在game.json中配置workers字段,并且Worker文件 ${this.scriptPath} 存在。` - ); - } - } -} \ No newline at end of file diff --git a/packages/core/src/Platform/index.ts b/packages/core/src/Platform/index.ts index b45a91ae..5ac01e52 100644 --- a/packages/core/src/Platform/index.ts +++ b/packages/core/src/Platform/index.ts @@ -8,9 +8,7 @@ export type { PlatformWorker, WorkerCreationOptions, PlatformConfig, - PlatformDetectionResult, - WeChatDeviceInfo, - BrowserDeviceInfo + PlatformDetectionResult } from './IPlatformAdapter'; // 平台检测器 @@ -19,16 +17,13 @@ export { PlatformDetector } from './PlatformDetector'; // 平台管理器 export { PlatformManager } from './PlatformManager'; -// 平台适配器实现 -export { BrowserAdapter } from './BrowserAdapter'; -export { WeChatMiniGameAdapter } from './WeChatMiniGameAdapter'; - // 内部导入用于便利函数 import { PlatformManager } from './PlatformManager'; +import type { IPlatformAdapter } from './IPlatformAdapter'; // 便利函数 -export function getCurrentPlatform() { - return PlatformManager.getInstance().getDetectionResult(); +export function registerPlatformAdapter(adapter: IPlatformAdapter) { + return PlatformManager.getInstance().registerAdapter(adapter); } export function getCurrentAdapter() { @@ -45,4 +40,8 @@ export function getFullPlatformConfig() { export function supportsFeature(feature: 'worker' | 'shared-array-buffer' | 'transferable-objects' | 'module-worker') { return PlatformManager.getInstance().supportsFeature(feature); +} + +export function hasAdapter() { + return PlatformManager.getInstance().hasAdapter(); } \ No newline at end of file