抽象worker接口,避免污染项目

This commit is contained in:
YHH
2025-09-29 18:15:47 +08:00
parent 62bc6b547e
commit 90ad4b3ec4
14 changed files with 2027 additions and 931 deletions

View 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());
```

View 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!));
```

View 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('收到内存警告,开始清理资源');
// 清理不必要的资源
});
}
```