import type { EngineCapabilities, EngineRequest, EngineResponse, EngineCommand, } from "../../../protocol/engine"; import type { ErrorReport } from "../../../protocol/error"; import type { SceneSnapshotIR } from "../../../protocol/scene-ir"; interface PendingRequest { resolve: (response: EngineResponse) => void; reject: (reason: ErrorReport) => void; timer: ReturnType; } export interface EngineClientOptions { timeoutMs?: number; workerFactory?: () => Worker; } const defaultWorkerFactory = () => new Worker(new URL("../workers/engine.worker.ts", import.meta.url), { type: "module" }); export class EngineClient { private readonly timeoutMs: number; private readonly workerFactory: () => Worker; private worker: Worker | null = null; private requestCounter = 0; private pending = new Map(); private _revision = 0; constructor(options: EngineClientOptions = {}) { this.timeoutMs = options.timeoutMs ?? 10_000; this.workerFactory = options.workerFactory ?? defaultWorkerFactory; } get revision(): number { return this._revision; } start(): void { if (this.worker) return; const worker = this.workerFactory(); worker.onmessage = (event: MessageEvent) => this.handleResponse(event.data); worker.onerror = () => this.failPending(this.report("WORKER_TERMINATED", "EngineWorker 发生异常", true)); this.worker = worker; } async init(protocolVersion = 1): Promise { const response = await this.request({ type: "init", protocolVersion }); if (!response.capabilities) throw this.report("WASM_INIT_FAILED", "EngineWorker 未返回能力信息", false); return response.capabilities; } async getSceneSnapshot(): Promise { const response = await this.request({ type: "getSceneSnapshot", sinceRevision: this._revision }); if (!response.snapshot) throw this.report("INVALID_ARGUMENT", "EngineWorker 未返回 SceneIR", false); return response.snapshot; } async setFrame(frame: number): Promise { await this.request({ type: "setFrame", frame }); } async setObjectVisibility(objectId: string, visible: boolean): Promise { await this.request({ type: "setObjectVisibility", objectId, visible }); } restart(): void { this.failPending(this.report("WORKER_TERMINATED", "EngineWorker 已重启", true)); this.worker?.terminate(); this.worker = null; this._revision = 0; this.start(); } terminate(): void { this.failPending(this.report("WORKER_TERMINATED", "EngineWorker 已关闭", true)); this.worker?.terminate(); this.worker = null; } private request(command: EngineCommand): Promise { this.start(); const worker = this.worker; if (!worker) return Promise.reject(this.report("WASM_INIT_FAILED", "无法创建 EngineWorker", false)); const requestId = `engine-${++this.requestCounter}`; const message: EngineRequest = { requestId, expectedRevision: this._revision, command }; return new Promise((resolve, reject) => { const timer = setTimeout(() => { this.pending.delete(requestId); reject(this.report("WORKER_TERMINATED", `请求超时: ${command.type}`, true)); }, this.timeoutMs); this.pending.set(requestId, { resolve, reject, timer }); worker.postMessage(message); }); } private handleResponse(response: EngineResponse): void { const pending = this.pending.get(response.requestId); if (!pending) return; this.pending.delete(response.requestId); clearTimeout(pending.timer); this._revision = response.revision; if (response.ok) pending.resolve(response); else pending.reject(response.reports?.[0] ?? this.report("INVALID_ARGUMENT", "EngineWorker 请求失败", true)); } private failPending(report: ErrorReport): void { for (const pending of this.pending.values()) { clearTimeout(pending.timer); pending.reject(report); } this.pending.clear(); } private report(code: ErrorReport["code"], message: string, recoverable: boolean): ErrorReport { return { code, severity: "error", message, recoverable }; } }