/** * Worker 文件生成器 * Worker file generator */ import * as fs from 'fs'; import * as path from 'path'; import type { WorkerSystemInfo, GeneratorConfig, GenerationResult, WorkerScriptMapping } from './types'; /** * 生成 Worker 文件 * Generate Worker files */ export function generateWorkerFiles( systems: WorkerSystemInfo[], config: GeneratorConfig ): GenerationResult { const result: GenerationResult = { success: [], errors: [], skipped: [], }; for (const system of systems) { try { // 优先使用用户配置的 workerScriptPath // Prefer user-configured workerScriptPath let outputPath: string; if (system.workerScriptPath) { // 用户已配置路径,使用该路径(相对于项目根目录) // User has configured path, use it (relative to project root) outputPath = path.resolve(process.cwd(), system.workerScriptPath); if (config.verbose) { console.log(` Using configured workerScriptPath: ${system.workerScriptPath}`); } } else { // 未配置,使用默认输出目录 // Not configured, use default output directory // 确保输出目录存在 if (!fs.existsSync(config.outDir)) { fs.mkdirSync(config.outDir, { recursive: true }); } const outputFileName = `${toKebabCase(system.className)}-worker.js`; outputPath = path.join(config.outDir, outputFileName); // 提示用户需要配置 workerScriptPath // Remind user to configure workerScriptPath result.skipped.push({ className: system.className, suggestedPath: path.relative(process.cwd(), outputPath).replace(/\\/g, '/'), reason: 'No workerScriptPath configured', }); } // 确保输出目录存在 // Ensure output directory exists const outputDir = path.dirname(outputPath); if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }); } const workerCode = config.wechat ? generateWeChatWorkerCode(system) : generateStandardWorkerCode(system); fs.writeFileSync(outputPath, workerCode, 'utf8'); result.success.push({ className: system.className, outputPath: outputPath, configuredPath: system.workerScriptPath, }); if (config.verbose) { console.log(` Generated: ${outputPath}`); } } catch (error) { result.errors.push({ className: system.className, filePath: system.filePath, error: error instanceof Error ? error.message : String(error), }); } } // 生成映射文件 // Generate mapping file if (config.generateMapping && result.success.length > 0) { generateMappingFile(result.success, config); } return result; } /** * 生成微信小游戏 Worker 代码 * Generate WeChat Mini Game Worker code */ function generateWeChatWorkerCode(system: WorkerSystemInfo): string { const { workerProcessBody, workerProcessParams, sharedBufferProcessBody, entityDataSize } = system; return `/** * Auto-generated Worker file for ${system.className} * 自动生成的 Worker 文件 * * Source: ${system.filePath} * Generated by @esengine/worker-generator * * 使用方式 | Usage: * 1. 将此文件放入 workers/ 目录 * 2. 在 game.json 中配置 "workers": "workers" * 3. 在 System 中配置 workerScriptPath: 'workers/${toKebabCase(system.className)}-worker.js' */ // 微信小游戏 Worker 环境 // WeChat Mini Game Worker environment let sharedFloatArray = null; const ENTITY_DATA_SIZE = ${entityDataSize || 8}; worker.onMessage(function(res) { // 微信小游戏 Worker 消息直接传递数据,不需要 .data // WeChat Mini Game Worker passes data directly, no .data wrapper var type = res.type; var id = res.id; var entities = res.entities; var deltaTime = res.deltaTime; var systemConfig = res.systemConfig; var startIndex = res.startIndex; var endIndex = res.endIndex; var sharedBuffer = res.sharedBuffer; try { // 处理 SharedArrayBuffer 初始化 // Handle SharedArrayBuffer initialization if (type === 'init' && sharedBuffer) { sharedFloatArray = new Float32Array(sharedBuffer); worker.postMessage({ type: 'init', success: true }); return; } // 处理 SharedArrayBuffer 数据 // Handle SharedArrayBuffer data if (type === 'shared' && sharedFloatArray) { processSharedArrayBuffer(startIndex, endIndex, deltaTime, systemConfig); worker.postMessage({ id: id, result: null }); return; } // 传统处理方式 // Traditional processing if (entities) { var result = workerProcess(entities, deltaTime, systemConfig); // 处理 Promise 返回值 // Handle Promise return value if (result && typeof result.then === 'function') { result.then(function(finalResult) { worker.postMessage({ id: id, result: finalResult }); }).catch(function(error) { worker.postMessage({ id: id, error: error.message }); }); } else { worker.postMessage({ id: id, result: result }); } } } catch (error) { worker.postMessage({ id: id, error: error.message }); } }); /** * 实体处理函数 - 从 ${system.className}.workerProcess 提取 * Entity processing function - extracted from ${system.className}.workerProcess */ function workerProcess(${workerProcessParams.entities}, ${workerProcessParams.deltaTime}, ${workerProcessParams.config}) { ${convertToES5(workerProcessBody)} } /** * SharedArrayBuffer 处理函数 * SharedArrayBuffer processing function */ function processSharedArrayBuffer(startIndex, endIndex, deltaTime, systemConfig) { if (!sharedFloatArray) return; ${sharedBufferProcessBody ? convertToES5(sharedBufferProcessBody) : '// No SharedArrayBuffer processing defined'} } `; } /** * 生成标准 Worker 代码(用于浏览器等环境) * Generate standard Worker code (for browsers, etc.) */ function generateStandardWorkerCode(system: WorkerSystemInfo): string { const { workerProcessBody, workerProcessParams, sharedBufferProcessBody, entityDataSize } = system; return `/** * Auto-generated Worker file for ${system.className} * 自动生成的 Worker 文件 * * Source: ${system.filePath} * Generated by @esengine/worker-generator */ let sharedFloatArray = null; const ENTITY_DATA_SIZE = ${entityDataSize || 8}; self.onmessage = function(e) { const { type, id, entities, deltaTime, systemConfig, startIndex, endIndex, sharedBuffer } = e.data; try { // 处理 SharedArrayBuffer 初始化 if (type === 'init' && sharedBuffer) { sharedFloatArray = new Float32Array(sharedBuffer); self.postMessage({ type: 'init', success: true }); return; } // 处理 SharedArrayBuffer 数据 if (type === 'shared' && sharedFloatArray) { processSharedArrayBuffer(startIndex, endIndex, deltaTime, systemConfig); self.postMessage({ id, result: null }); return; } // 传统处理方式 if (entities) { const result = workerProcess(entities, deltaTime, systemConfig); if (result && typeof result.then === 'function') { result.then(finalResult => { self.postMessage({ id, result: finalResult }); }).catch(error => { self.postMessage({ id, error: error.message }); }); } else { self.postMessage({ id, result }); } } } catch (error) { self.postMessage({ id, error: error.message }); } }; /** * Entity processing function - extracted from ${system.className}.workerProcess */ function workerProcess(${workerProcessParams.entities}, ${workerProcessParams.deltaTime}, ${workerProcessParams.config}) { ${workerProcessBody} } /** * SharedArrayBuffer processing function */ function processSharedArrayBuffer(startIndex, endIndex, deltaTime, systemConfig) { if (!sharedFloatArray) return; ${sharedBufferProcessBody || '// No SharedArrayBuffer processing defined'} } `; } /** * 生成映射文件 * Generate mapping file */ function generateMappingFile( success: Array<{ className: string; outputPath: string }>, config: GeneratorConfig ): void { const mapping: WorkerScriptMapping = { generatedAt: new Date().toISOString(), mappings: {}, }; for (const item of success) { // 使用相对于输出目录的路径 // Use path relative to output directory const relativePath = path.relative(config.outDir, item.outputPath).replace(/\\/g, '/'); mapping.mappings[item.className] = relativePath; } const mappingPath = path.join(config.outDir, 'worker-mapping.json'); fs.writeFileSync(mappingPath, JSON.stringify(mapping, null, 2), 'utf8'); if (config.verbose) { console.log(` Generated mapping: ${mappingPath}`); } } /** * 将 camelCase/PascalCase 转换为 kebab-case * Convert camelCase/PascalCase to kebab-case */ function toKebabCase(str: string): string { return str .replace(/([a-z])([A-Z])/g, '$1-$2') .replace(/([A-Z])([A-Z][a-z])/g, '$1-$2') .toLowerCase(); } /** * ES6+ 到 ES5 转换(用于微信小游戏兼容性) * ES6+ to ES5 conversion (for WeChat Mini Game compatibility) * * 使用 TypeScript 编译器进行安全的代码转换 * Uses TypeScript compiler for safe code transformation */ function convertToES5(code: string): string { // 使用 ts-morph 已有的 TypeScript 依赖进行转换 // Use ts-morph's TypeScript dependency for transformation const ts = require('typescript'); const result = ts.transpileModule(code, { compilerOptions: { target: ts.ScriptTarget.ES5, module: ts.ModuleKind.None, removeComments: false, // 不生成严格模式声明 noImplicitUseStrict: true, } }); return result.outputText; }