抽象worker接口,避免污染项目
This commit is contained in:
@@ -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/': [
|
||||
|
||||
@@ -23,4 +23,7 @@
|
||||
学习时间管理和定时器系统,实现游戏逻辑的精确时间控制。
|
||||
|
||||
### [日志系统 (Logger)](./logging.md)
|
||||
掌握分级日志系统,用于调试、监控和错误追踪。
|
||||
掌握分级日志系统,用于调试、监控和错误追踪。
|
||||
|
||||
### [平台适配器 (Platform Adapter)](./platform-adapter.md)
|
||||
了解如何为不同平台实现和注册平台适配器,支持浏览器、小游戏、Node.js等环境。
|
||||
289
docs/guide/platform-adapter.md
Normal file
289
docs/guide/platform-adapter.md
Normal file
@@ -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<PlatformConfig>;
|
||||
}
|
||||
```
|
||||
|
||||
### 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('使用降级适配器继续运行');
|
||||
}
|
||||
```
|
||||
475
docs/guide/platform-adapter/browser.md
Normal file
475
docs/guide/platform-adapter/browser.md
Normal file
@@ -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
|
||||
<!-- 在 HTML 中设置 -->
|
||||
<meta http-equiv="Cross-Origin-Opener-Policy" content="same-origin">
|
||||
<meta http-equiv="Cross-Origin-Embedder-Policy" content="require-corp">
|
||||
```
|
||||
|
||||
或在服务器配置中设置:
|
||||
|
||||
```
|
||||
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());
|
||||
```
|
||||
558
docs/guide/platform-adapter/nodejs.md
Normal file
558
docs/guide/platform-adapter/nodejs.md
Normal file
@@ -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!));
|
||||
```
|
||||
621
docs/guide/platform-adapter/wechat-minigame.md
Normal file
621
docs/guide/platform-adapter/wechat-minigame.md
Normal file
@@ -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<PlatformConfig> {
|
||||
// 可以在这里添加异步获取设备性能信息的逻辑
|
||||
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<number> {
|
||||
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('收到内存警告,开始清理资源');
|
||||
// 清理不必要的资源
|
||||
});
|
||||
}
|
||||
```
|
||||
8
package-lock.json
generated
8
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测是否为支付宝小游戏环境
|
||||
*/
|
||||
|
||||
@@ -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<any> {
|
||||
if (!this.adapter) {
|
||||
return null;
|
||||
throw new Error('平台适配器未注册');
|
||||
}
|
||||
|
||||
// 如果适配器支持异步获取配置,使用异步方法
|
||||
|
||||
@@ -1,451 +0,0 @@
|
||||
/// <reference types="minigame-api-typings" />
|
||||
|
||||
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<PlatformConfig> {
|
||||
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} 存在。`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
Reference in New Issue
Block a user