Skip to content

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 要求

  • 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!));

Released under the MIT License.