diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2603459..65bfb78 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -615,67 +615,56 @@ packages: resolution: {integrity: sha512-46OzWeqEVQyX3N2/QdiU/CMXYDH/lSHpgfBkuhl3igpZiaB3ZIfSjKuOnybFVBQzjsLwkus2mjaESy8H41SzvA==} cpu: [arm] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.41.0': resolution: {integrity: sha512-lfgW3KtQP4YauqdPpcUZHPcqQXmTmH4nYU0cplNeW583CMkAGjtImw4PKli09NFi2iQgChk4e9erkwlfYem6Lg==} cpu: [arm] os: [linux] - libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.41.0': resolution: {integrity: sha512-nn8mEyzMbdEJzT7cwxgObuwviMx6kPRxzYiOl6o/o+ChQq23gfdlZcUNnt89lPhhz3BYsZ72rp0rxNqBSfqlqw==} cpu: [arm64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.41.0': resolution: {integrity: sha512-l+QK99je2zUKGd31Gh+45c4pGDAqZSuWQiuRFCdHYC2CSiO47qUWsCcenrI6p22hvHZrDje9QjwSMAFL3iwXwQ==} cpu: [arm64] os: [linux] - libc: [musl] '@rollup/rollup-linux-loongarch64-gnu@4.41.0': resolution: {integrity: sha512-WbnJaxPv1gPIm6S8O/Wg+wfE/OzGSXlBMbOe4ie+zMyykMOeqmgD1BhPxZQuDqwUN+0T/xOFtL2RUWBspnZj3w==} cpu: [loong64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-powerpc64le-gnu@4.41.0': resolution: {integrity: sha512-eRDWR5t67/b2g8Q/S8XPi0YdbKcCs4WQ8vklNnUYLaSWF+Cbv2axZsp4jni6/j7eKvMLYCYdcsv8dcU+a6QNFg==} cpu: [ppc64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.41.0': resolution: {integrity: sha512-TWrZb6GF5jsEKG7T1IHwlLMDRy2f3DPqYldmIhnA2DVqvvhY2Ai184vZGgahRrg8k9UBWoSlHv+suRfTN7Ua4A==} cpu: [riscv64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.41.0': resolution: {integrity: sha512-ieQljaZKuJpmWvd8gW87ZmSFwid6AxMDk5bhONJ57U8zT77zpZ/TPKkU9HpnnFrM4zsgr4kiGuzbIbZTGi7u9A==} cpu: [riscv64] os: [linux] - libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.41.0': resolution: {integrity: sha512-/L3pW48SxrWAlVsKCN0dGLB2bi8Nv8pr5S5ocSM+S0XCn5RCVCXqi8GVtHFsOBBCSeR+u9brV2zno5+mg3S4Aw==} cpu: [s390x] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.41.0': resolution: {integrity: sha512-XMLeKjyH8NsEDCRptf6LO8lJk23o9wvB+dJwcXMaH6ZQbbkHu2dbGIUindbMtRN6ux1xKi16iXWu6q9mu7gDhQ==} cpu: [x64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-musl@4.41.0': resolution: {integrity: sha512-m/P7LycHZTvSQeXhFmgmdqEiTqSV80zn6xHaQ1JSqwCtD1YGtwEK515Qmy9DcB2HK4dOUVypQxvhVSy06cJPEg==} cpu: [x64] os: [linux] - libc: [musl] '@rollup/rollup-win32-arm64-msvc@4.41.0': resolution: {integrity: sha512-4yodtcOrFHpbomJGVEqZ8fzD4kfBeCbpsUy5Pqk4RluXOdsWdjLnjhiKy2w3qzcASWd04fp52Xz7JKarVJ5BTg==} diff --git a/src/core/script/LCCScript.ts b/src/core/script/LCCScript.ts index b6ddb54..d4e9f79 100644 --- a/src/core/script/LCCScript.ts +++ b/src/core/script/LCCScript.ts @@ -6,6 +6,26 @@ import { Request } from '@ease-forge/shared' * LCC 物流控制中心客户端 API 实现 */ export default class LCCScript implements LCC { + + log(from: string, message: string, ...args: any[]): void { + // 插入时分秒 HH:mm:ss.sss + const now = new Date().toISOString().replace('T', ' ').replace('Z', '').split(' ')[1] + // 打成彩色 + // console.log(now + ' [LCC-' + from + '] ' + message, ...args) + console.log(`%c${now} [${from}] ${message}`, 'color: #00f', ...args) + } + + /** + * 延时函数 - 使用普通函数确保正确绑定 + */ + async sleep(timeOfMs: number = 1000): Promise { + return new Promise((resolve) => { + setTimeout(() => { + resolve() // 确保调用 resolve + }, timeOfMs) + }) + } + async getAllProjects(): Promise> { return Request.request.post('/api/workbench/LccController@getAllProjects', {}) } diff --git a/src/core/script/ModelManager.ts b/src/core/script/ModelManager.ts index e567b82..55c1160 100644 --- a/src/core/script/ModelManager.ts +++ b/src/core/script/ModelManager.ts @@ -1,5 +1,5 @@ import Viewport from '@/core/engine/Viewport.ts' -import { executeTypeScript } from '@/core/script/ModelScript.ts' +import { compileTypeScript, executeTypeScript } from '@/core/script/ModelScript.ts' import * as THREE from 'three' import { getMatrixFromTf } from '@/core/ModelUtils.ts' import type { Object3DLike } from '@/types/ModelTypes.ts' @@ -7,6 +7,8 @@ import TaskManager from '../manager/TaskManager.ts' import Cl2Entity from '@/modules/amr/ptr/cl2/Cl2Entity.ts' import ClxEntity from '@/modules/amr/ptr/clx/ClxEntity.ts' import { getRenderer } from '@/core/manager/ModuleManager.ts' +import { markRaw } from 'vue' +import ToolProxyManager from '@/runtime/ToolProxyManager.ts' export default class ModelManager implements IControls, Model { private viewport: Viewport @@ -177,4 +179,249 @@ this.viewport.stateManager.update(({ getEntity, putEntity, addEntity }) => { system.showInfoDialog(content) } } + + // 脚本与 Worker 的映射,用于快速查找 + private scriptWorkers = new WeakMap() + private toolProxyManager = new ToolProxyManager() + + /** + * 开启另外的线程,运行脚本 + */ + async runScript(script: CustomScript) { + // 检查脚本是否已在运行 + if (script.isRunning) { + console.warn(`脚本 "${script.name}" 已在运行中`) + return + } + + try { + // 创建 Blob URL 作为 Worker 执行内容 + const jsCode = await compileTypeScript(script.content) + + const wrappedContent = ` +// Worker 端工具代理 +// 创建代理对象 +const createToolProxy = (port) => { + return new Proxy({}, { + get(_, toolName) { + return new Proxy({}, { + get(_, methodName) { + return (...args) => { + return new Promise((resolve, reject) => { + const callId = Math.random().toString(36).substr(2, 10); + + // 发送调用请求 + port.postMessage({ + type: 'call', + tool: toolName, + method: methodName, + callId, + args + }); + + // 监听响应 + const responseHandler = (event) => { + const { data } = event; + if (data.callId === callId) { + console.log('[Worker] 收到响应: ' + toolName + '.' + methodName, data.type); + port.removeEventListener('message', responseHandler); + + if (data.type === 'result') { + resolve(data.result); + } else if (data.type === 'error') { + const err = new Error(data.error.message); + err.name = data.error.name; + err.stack = data.error.stack; + reject(err); + } + } + }; + + port.addEventListener('message', responseHandler); + }); + }; + } + }); + } + }); +}; + +// 创建代理对象 +const tools = new Proxy({}, toolProxyHandler); + +self.onmessage = function(__initMessage) { + if (__initMessage.data.type === 'init') { + // 设置通信端口 + self.port = __initMessage.data.port; + self.port.onmessage = function(event) { + const { type, callId, result, error } = event.data; + + if (!self.pendingCalls || !self.pendingCalls[callId]) return; + + const { resolve, reject } = self.pendingCalls[callId]; + delete self.pendingCalls[callId]; + + if (type === 'result') { + resolve(result); + } else if (type === 'error') { + const err = new Error(error.message); + err.name = error.name; + err.stack = error.stack; + reject(err); + } + }; + self.port.start(); + + // 解构代理工具 + const { LCC, RCS } = tools; + + try { + (async function() { + // ---------------------custom script start---------------------[ +${jsCode} + // ]---------------------custom script end--------------------- + })().then(() => { + self.postMessage({ type: 'completed' }) + }).catch(error => { + // 捕获异步错误 + self.postMessage({ + type: 'error', + error: { + name: error.name, + message: error.message, + stack: error.stack + } + }) + }) + } catch (error) { + // 捕获脚本错误 + self.postMessage({ + type: 'error', + error: { + name: error.name, + message: error.message, + stack: error.stack + } + }) + } finally { + // 确保关闭 worker + self.close() + } + } +} + ` + // console.log(wrappedContent) + + const blob = new Blob([wrappedContent], { type: 'application/javascript' }) + const blobUrl = URL.createObjectURL(blob) + const worker = new Worker(blobUrl) + + // 创建代理并初始化 Worker + const port = this.toolProxyManager.createToolProxyForWorker(worker) + + // 存储 Worker 引用并更新状态 + this.scriptWorkers.set(script, worker) + script.worker = markRaw(worker) + script.isRunning = true + URL.revokeObjectURL(blobUrl) // 清理不再需要的 Blob URL + + + // 设置 Worker 事件监听器 + worker.onmessage = (e) => this.handleWorkerMessage(script, e) + worker.onerror = (e) => this.handleWorkerError(script, e) + worker.onmessageerror = (e) => this.handleMessageError(script, e) + + console.log(`[ScriptRunner] 创建 Worker 并启动脚本 "${script.name}"`) + + worker.postMessage({ + type: 'init', + port: port + }, [port]) + + console.log(`[ScriptRunner] 初始化消息已发送给 Worker`) + + } catch (error) { + console.error(`启动脚本 "${script.name}" 失败:`, error) + script.isRunning = false + } + } + + /** + * 停止脚本 + */ + stopScript(script: CustomScript) { + this.cleanupScript(script, '手动停止') + } + + /** + * 处理来自 Worker 的消息 + */ + private handleWorkerMessage(script: CustomScript, event: MessageEvent): void { + const { data } = event + + if (data?.type === 'call') { + // 处理来自 Worker 的工具调用请求 + this.toolProxyManager.handleWorkerCall(script.worker, event) + return + } + + if (data?.type === 'completed') { + // 脚本自然完成 + console.log(`脚本 "${script.name}" 执行完成`) + this.cleanupScript(script, '执行完成') + return + } + + if (data?.type === 'error') { + // 脚本内部错误 + const error = new Error(data.error.message) + error.name = data.error.name + error.stack = data.error.stack + + console.error(`[${script.name}] 执行错误:`, error) + this.cleanupScript(script, '执行错误') + return + } + + // 常规消息处理 + console.log(`[${script.name}] 收到消息:`, data) + debugger + } + + /** + * 处理 Worker 错误 + */ + private handleWorkerError(script: CustomScript, event: ErrorEvent): void { + console.error(`[${script.name}] 发生错误:`, event.message, `@${event.filename}:${event.lineno}`) + this.cleanupScript(script, '运行时错误') + } + + /** + * 处理消息错误 + */ + private handleMessageError(script: CustomScript, event: MessageEvent): void { + console.error(`[${script.name}] 消息解析错误:`, event) + this.cleanupScript(script, '消息错误') + } + + /** + * 清理脚本资源 + */ + private cleanupScript(script: CustomScript, reason: string): void { + if (!script.isRunning || !script.worker) return + + try { + // 清理代理管理器中的资源 + this.toolProxyManager.cleanupWorker(script.worker) + + // 终止 Worker + script.worker.terminate() + this.scriptWorkers.delete(script) + script.worker = undefined + script.isRunning = false + console.log(`脚本 "${script.name}" 已停止 (原因: ${reason})`) + } catch (error) { + console.error(`清理脚本 "${script.name}" 资源失败:`, error) + } + } } diff --git a/src/core/script/ModelScript.ts b/src/core/script/ModelScript.ts index 5385512..db42b53 100644 --- a/src/core/script/ModelScript.ts +++ b/src/core/script/ModelScript.ts @@ -6,7 +6,7 @@ import LCCDts from '@/types/LCC.d.ts?raw' import * as ts from 'typescript' import { createDefaultMapFromCDN, createSystem, createVirtualCompilerHost } from '@typescript/vfs' -export async function executeTypeScript(code: string, contextExtend: any = {}) { +export async function compileTypeScript(code: string) { const fsMap = await createDefaultMapFromCDN( { target: ts.ScriptTarget.ESNext }, 'latest', @@ -31,7 +31,11 @@ export async function executeTypeScript(code: string, contextExtend: any = {}) { program.emit() // 4. 获取编译后的JS代码 - const jsCode = fsMap.get('input.js') || '' + return fsMap.get('input.js') || '' +} + +export async function executeTypeScript(code: string, contextExtend: any = {}) { + const jsCode = await compileTypeScript(code) // 5. 安全执行(使用沙箱) return await executeInSandbox(jsCode, contextExtend) diff --git a/src/editor/widgets/script/ScriptView.vue b/src/editor/widgets/script/ScriptView.vue index 5cb54c8..70492f7 100644 --- a/src/editor/widgets/script/ScriptView.vue +++ b/src/editor/widgets/script/ScriptView.vue @@ -18,9 +18,12 @@ 运行 + v-if="scriptList[scriptIndex] && !scriptList[scriptIndex].isRunning" + @click="()=>runScript(scriptList[scriptIndex])">运行脚本 + + 停止运行 + LocalRun + @@ -49,6 +55,7 @@ import YvSrcEditor from '@/components/YvSrcEditor.vue' import IWidgets from '../IWidgets.js' import CodeDropper from '@/core/manager/CodeDropper.js' import { escByKeyboard } from '@/core/ModelUtils.js' +import scriptRunner from '@/runtime/ScriptRunner.js' export default { name: 'ScriptView', @@ -56,12 +63,22 @@ export default { YvSrcEditor }, mixins: [IWidgets], + mounted() { + window['ScriptView'] = this + }, + unmounted() { + window['ScriptView'] = null + }, data() { const me = this return { scriptIsRunning: false, scriptIndex: 0, + /** + * 脚本列表 + * @type {Array} + */ scriptList: [], searchKeyword: '', scriptMenu: [ @@ -151,15 +168,27 @@ export default { system.clearLoading() } }, - runScript() { - this.scriptIsRunning = true - this.viewport.modelManager.executestring(this.currentScript) - .catch(err => { - system.msg('脚本执行失败: ' + err.message, 'error') - }) + /** + * 运行脚本 + * @type {(script: CustomScript) => void} + */ + runScript(script) { + system.showLoading() + scriptRunner.runScript(script) .finally(() => { - this.scriptIsRunning = false + system.clearLoading() }) + // this.viewport.modelManager.runScript(script) + }, + localRun(script) { + this.viewport.modelManager.executestring(script.content) + }, + /** + * 停止脚本 + * @type {(script: CustomScript) => void} + */ + stopScript(script) { + this.viewport.modelManager.stopScript(script) }, setCodeDropper() { CodeDropper.start(this.viewport, (item) => { @@ -182,7 +211,12 @@ export default { }, saveScriptsToLocal() { localforage.setItem('-yvan-lcc-scriptlist-', JSON.stringify({ - scriptList: this.scriptList, + scriptList: _.map(this.scriptList, item => { + return { + name: item.name, + content: item.content + } + }), scriptIndex: this.scriptIndex })).catch(err => { @@ -211,7 +245,8 @@ export default { addScript() { this.scriptList.push({ name: 'Script' + (this.scriptList.length + 1), - content: '' + content: '', + isRunning: false }) this.scriptIndex = this.scriptList.length - 1 }, @@ -235,7 +270,8 @@ export default { if (!this.scriptList[this.scriptIndex]) { this.scriptList[this.scriptIndex] = { name: 'Script' + (this.scriptList.length + 1), - content: '' + content: '', + isRunning: false } } this.scriptList[this.scriptIndex].content = value diff --git a/src/runtime/ScriptRunner.ts b/src/runtime/ScriptRunner.ts new file mode 100644 index 0000000..e4ff1da --- /dev/null +++ b/src/runtime/ScriptRunner.ts @@ -0,0 +1,246 @@ +import ToolProxyManager from '@/runtime/ToolProxyManager.ts' +import { compileTypeScript } from '@/core/script/ModelScript.ts' +import { markRaw } from 'vue' + +class ScriptRunner { + private scriptWorkers = new WeakMap() + private toolProxyManager = new ToolProxyManager() + + async runScript(script: CustomScript) { + if (script.isRunning) { + console.warn(`脚本 "${script.name}" 已在运行中`) + return + } + + try { + const jsCode = await compileTypeScript(script.content) + + // 使用更安全的 Worker 包装代码 + const wrappedContent = ` +const pendingCalls = new Map(); + +// 端口消息处理函数 +const handlePortMessage = (event) => { + const { data } = event; + + // 只处理有 callId 的消息 + if (!data.callId) return; + + const resolver = pendingCalls.get(data.callId); + if (!resolver) { + console.warn(\`[Worker] 收到未知 callId 的响应: \${data.callId}\`); + return; + } + + pendingCalls.delete(data.callId); + + if (data.type === 'result') { + resolver.resolve(data.result); + } else if (data.type === 'error') { + const err = new Error(data.error.message); + err.name = data.error.name; + err.stack = data.error.stack; + resolver.reject(err); + } +}; + +// 创建代理对象 +const createToolProxy = (port) => { + port.addEventListener('message', handlePortMessage); + + return new Proxy({}, { + get(_, toolName) { + return new Proxy({}, { + get(_, methodName) { + return (...args) => { + return new Promise((resolve, reject) => { + const callId = Math.random().toString(36).substr(2, 10); + + // 保存解析器 + pendingCalls.set(callId, { resolve, reject }); + + // 发送调用请求 + port.postMessage({ + type: 'call', tool: toolName, method: methodName, callId, args + }); + }); + }; + } + }); + } + }); +}; + +self.onmessage = async (initMessage) => { + if (initMessage.data.type === 'init') { + // 设置通信端口 + const port = initMessage.data.port; + port.start(); + + // 创建工具代理 + const tools = createToolProxy(port); + const { LCC, RCS } = tools; + + try { + // 添加全局错误处理 + self.addEventListener('error', (event) => { + console.error('Worker 全局错误:', event.error); + self.postMessage({ + type: 'error', + error: { + name: event.error.name, + message: event.error.message, + stack: event.error.stack + } + }); + }); + + self.addEventListener('unhandledrejection', (event) => { + console.error('未处理的 Promise 拒绝:', event.reason); + self.postMessage({ + type: 'error', + error: { + name: 'UnhandledRejection', + message: event.reason.message, + stack: event.reason.stack + } + }); + }); + + // 执行用户脚本 + (async () => { + // ---------------------custom script start--------------------- +${jsCode} + // ---------------------custom script end--------------------- + })().then(() => { + self.postMessage({ type: 'completed' }); + }).catch(error => { + console.error('脚本执行出错:', error); + self.postMessage({ + type: 'error', + error: { + name: error.name, + message: error.message, + stack: error.stack + } + }); + }); + } catch (error) { + console.error('脚本同步错误:', error); + self.postMessage({ + type: 'error', + error: { + name: error.name, + message: error.message, + stack: error.stack + } + }); + } + } +}; +` + + const blob = new Blob([wrappedContent], { type: 'application/javascript' }) + const blobUrl = URL.createObjectURL(blob) + const worker = new Worker(blobUrl) + worker['name'] = script.name // 设置 Worker 名称,便于调试 + + // 创建代理并初始化 Worker + const port = this.toolProxyManager.createToolProxyForWorker(worker) + + // 存储 Worker 引用 + this.scriptWorkers.set(script, worker) + script.worker = markRaw(worker) + script.isRunning = true + URL.revokeObjectURL(blobUrl) + + // 设置 Worker 事件监听器 + worker.onmessage = (e) => this.handleWorkerMessage(script, e) + worker.onerror = (e) => this.handleWorkerError(script, e) + worker.onmessageerror = (e) => this.handleMessageError(script, e) + + // 发送初始化消息(仅传递可转移的端口对象) + worker.postMessage({ + type: 'init', + port: port + }, [port]) // 将端口作为可转移对象传递 + + console.log(`脚本 "${script.name}" 已启动`) + + } catch (error) { + console.error(`启动脚本 "${script.name}" 失败:`, error) + script.isRunning = false + } + } + + /** + * 停止脚本 + */ + stopScript(script: CustomScript) { + const worker = this.scriptWorkers.get(script) + if (!worker || !script.isRunning) { + console.warn(`脚本 "${script.name}" 未运行`) + return + } + + try { + // 清理代理管理器中的资源 + this.toolProxyManager.cleanupWorker(worker) + + // 终止 Worker + worker.terminate() + this.scriptWorkers.delete(script) + script.worker = undefined + script.isRunning = false + + console.log(`脚本 "${script.name}" 已停止`) + } catch (error) { + console.error(`停止脚本 "${script.name}" 时出错:`, error) + } + } + + /** + * 处理来自 Worker 的消息 + */ + private handleWorkerMessage(script: CustomScript, event: MessageEvent): void { + const { data } = event + + switch (data?.type) { + case 'completed': + console.log(`脚本 "${script.name}" 执行完成`) + this.stopScript(script) + break + + case 'error': + const error = new Error(data.error.message) + error.name = data.error.name + error.stack = data.error.stack + console.error(`[${script.name}] 执行错误:`, error) + this.stopScript(script) + break + + default: + console.log(`[${script.name}] 收到消息:`, data) + } + } + + /** + * 处理 Worker 错误 + */ + private handleWorkerError(script: CustomScript, event: ErrorEvent): void { + console.error(`[${script.name}] 运行时错误:`, event.message, `@${event.filename}:${event.lineno}`) + this.stopScript(script) + } + + /** + * 处理消息错误 + */ + private handleMessageError(script: CustomScript, event: MessageEvent): void { + console.error(`[${script.name}] 消息解析错误:`, event) + this.stopScript(script) + } +} + +const scriptRunner = new ScriptRunner() +window['ScriptRunner'] = scriptRunner +export default scriptRunner diff --git a/src/runtime/ToolProxyManager.ts b/src/runtime/ToolProxyManager.ts new file mode 100644 index 0000000..197ce75 --- /dev/null +++ b/src/runtime/ToolProxyManager.ts @@ -0,0 +1,130 @@ +// 工具类代理管理器 +import LCCScript from '@/core/script/LCCScript.ts' + +export default class ToolProxyManager { + // 存储所有活跃的 MessageChannel 端口 + private activePorts = new WeakMap() + + /** + * 为 Worker 创建工具代理 + */ + createToolProxyForWorker(worker: Worker): MessagePort { + // 创建 MessageChannel 用于通信 + const channel = new MessageChannel() + + // 存储 port2 用于后续通信 + this.activePorts.set(worker, channel.port2) + + // 设置 port2 的消息监听器 + channel.port2.onmessage = (event) => { + this.handleWorkerCall(worker, event) + } + + channel.port2.start() + + // 返回 port1 供 Worker 使用 + return channel.port1 + } + + /** + * 处理来自 Worker 的调用请求 + */ + handleWorkerCall(worker: Worker, event: MessageEvent): void { + const { data } = event + if (data?.type !== 'call') return + + const { tool, method, callId, args } = data + const port = this.activePorts.get(worker) + if (!port) { + console.error(`[ToolProxy] 找不到 worker 的端口`, worker) + return + } + + // 获取主线程中的工具实例 + const toolInstance = (window as any)[tool] + if (!toolInstance) { + console.error(`[ToolProxy] 工具未找到: ${tool}`) + port.postMessage({ + type: 'error', + tool, + method, + callId, + error: { + name: 'ToolNotFound', + message: `${tool} is not available` + } + }) + return + } + + if (typeof toolInstance[method] !== 'function') { + console.error(`[ToolProxy] 方法未找到: ${tool}.${method}`) + port.postMessage({ + type: 'error', tool, method, callId, + error: { + name: 'MethodNotFound', + message: `${tool}.${method} is not a function` + } + }) + return + } + + try { + // 执行工具方法 + let result + if (toolInstance instanceof LCCScript && method === 'log') { + // 特殊处理 LCCScript 的 log 方法 + result = toolInstance[method]('Script-' + worker['name'], ...args) + } else { + result = toolInstance[method](...args) + } + + // 处理异步函数 + if (result instanceof Promise) { + result.then( + resolved => { + port.postMessage({ + type: 'result', tool, method, callId, result: resolved + }) + }, + error => { + port.postMessage({ + type: 'error', tool, method, callId, + error: { + name: error.name || 'PromiseRejection', + message: error.message, + stack: error.stack + } + }) + } + ) + } + // 处理同步函数 + else { + port.postMessage({ type: 'result', tool, method, callId, result }) + } + + } catch (error) { + console.error(`[ToolProxy] 执行错误: ${tool}.${method}`, error) + port.postMessage({ + type: 'error', tool, method, callId, + error: { + name: error.name || 'ExecutionError', + message: error.message, + stack: error.stack + } + }) + } + } + + /** + * 清理 Worker 资源 + */ + cleanupWorker(worker: Worker) { + const port = this.activePorts.get(worker) + if (port) { + port.close() + this.activePorts.delete(worker) + } + } +} diff --git a/src/types/LCC.d.ts b/src/types/LCC.d.ts index 21d1ea1..53fadf1 100644 --- a/src/types/LCC.d.ts +++ b/src/types/LCC.d.ts @@ -2,6 +2,17 @@ * LCC 物流控制中心客户端 API 声明 */ declare interface LCC { + + /** + * 打印日志 + */ + log(from: string, message: string, ...args: any[]): void + + /** + * 延时函数 + */ + sleep(timeOfMs: number = 1000): Promise + /** * 获取所有项目 */ diff --git a/src/types/Model.d.ts b/src/types/Model.d.ts index 05ce769..a0ff7a7 100644 --- a/src/types/Model.d.ts +++ b/src/types/Model.d.ts @@ -386,3 +386,25 @@ interface Cl2If extends EntityIf, Carry, Walk, ForkArm, LiftingArm { */ interface ClxIf extends EntityIf, Carry, Walk, ForkArm, LiftingArm { } + +interface CustomScript { + /** + * 脚本名称 + */ + name: string + + /** + * 脚本内容 + */ + content: string + + /** + * 脚本是否正在运行 + */ + isRunning: boolean + + /** + * Worker 实例 + */ + worker?: Worker; +}