Advance M8-M11 parity workflows
This commit is contained in:
@@ -13,6 +13,7 @@ import type {
|
||||
} from "../../../protocol/web-engine";
|
||||
import type { LODGenerationRequest } from "../../../protocol/lod";
|
||||
import type { SceneSnapshotIR } from "../../../protocol/scene-ir";
|
||||
import type { ShaderCompileReport } from "../../../protocol/shader-compiler";
|
||||
import type { SceneDelta } from "../../../protocol/scene-delta";
|
||||
import type { NonMeshGeometryChunk } from "../../../protocol/nonmesh-binary";
|
||||
import type { SimplifyResult } from "../../../protocol/simplify";
|
||||
@@ -20,6 +21,15 @@ import type { DepsgraphEvaluationIR } from "../../../protocol/depsgraph";
|
||||
import type { RenderCapabilityRequest } from "../../../protocol/render-capabilities";
|
||||
import type { CapabilityGateResult } from "../../../protocol/capability-gates";
|
||||
import { applyMeshGeometryDelta } from "../../../protocol/mesh-geometry-delta";
|
||||
import { createWorkerFault, type WorkerFault } from "../../../protocol/worker-fault";
|
||||
import type {
|
||||
PaintStrokeSessionBeginIR,
|
||||
PaintStrokeSessionCancelIR,
|
||||
PaintStrokeSessionChunkIR,
|
||||
PaintStrokeSessionCommitIR,
|
||||
PaintStrokeSessionReceiptIR,
|
||||
} from "../../../protocol/paint-stroke-session";
|
||||
import type { PaintPBVHCapabilityRequest } from "../../../protocol/paint-pbvh-capability";
|
||||
|
||||
interface PendingRequest {
|
||||
resolve: (result: WebEngineResult) => void;
|
||||
@@ -27,11 +37,13 @@ interface PendingRequest {
|
||||
onProgress?: (progress: ProgressEvent) => void;
|
||||
timer: ReturnType<typeof setTimeout>;
|
||||
cleanup?: () => void;
|
||||
aborted?: boolean;
|
||||
}
|
||||
|
||||
export interface WebEngineClientOptions {
|
||||
timeoutMs?: number;
|
||||
workerFactory?: () => Worker;
|
||||
onWorkerFault?: (fault: WorkerFault) => void;
|
||||
}
|
||||
|
||||
const defaultWorkerFactory = () =>
|
||||
@@ -48,7 +60,10 @@ export interface BlendOpenResult {
|
||||
export class WebEngineClient {
|
||||
private readonly timeoutMs: number;
|
||||
private readonly workerFactory: () => Worker;
|
||||
private readonly onWorkerFault?: (fault: WorkerFault) => void;
|
||||
private worker: Worker | null = null;
|
||||
private workerFaulted = false;
|
||||
private lastWorkerFault: WorkerFault | null = null;
|
||||
private requestCounter = 0;
|
||||
private pending = new Map<string, PendingRequest>();
|
||||
private geometryBuffers: MeshGeometryBuffer[] = [];
|
||||
@@ -57,6 +72,7 @@ export class WebEngineClient {
|
||||
constructor(options: WebEngineClientOptions = {}) {
|
||||
this.timeoutMs = options.timeoutMs ?? 30_000;
|
||||
this.workerFactory = options.workerFactory ?? defaultWorkerFactory;
|
||||
this.onWorkerFault = options.onWorkerFault;
|
||||
}
|
||||
|
||||
async init(): Promise<WebEngineStatus> {
|
||||
@@ -88,7 +104,7 @@ export class WebEngineClient {
|
||||
return { status: result.status, delta: result.delta };
|
||||
}
|
||||
|
||||
async applyCommand(payload: WebEngineEditCommand): Promise<BlendOpenResult & { delta: SceneDelta }> {
|
||||
async applyCommand(payload: WebEngineEditCommand): Promise<BlendOpenResult & { delta: SceneDelta; shaderCompile?: ShaderCompileReport }> {
|
||||
const result = await this.request({ type: "applyCommand", payload });
|
||||
if (!result.snapshot || !result.delta) throw this.report("INVALID_ARGUMENT", "WebEngine 未返回命令结果", true);
|
||||
this.geometryBuffers = result.geometryDelta
|
||||
@@ -102,9 +118,53 @@ export class WebEngineClient {
|
||||
nonMeshGeometryBuffers: [...this.nonMeshGeometryBuffers],
|
||||
delta: result.delta,
|
||||
simplify: result.simplify,
|
||||
shaderCompile: result.shaderCompile,
|
||||
};
|
||||
}
|
||||
|
||||
async beginPaintStroke(session: PaintStrokeSessionBeginIR): Promise<PaintStrokeSessionReceiptIR> {
|
||||
const result = await this.request({ type: "beginPaintStroke", session });
|
||||
if (!result.paintStrokeSession) throw this.report("PAINT_SCHEMA_INVALID", "WebEngine did not open the paint pointer session", true);
|
||||
return result.paintStrokeSession;
|
||||
}
|
||||
|
||||
async appendPaintStrokeChunk(chunk: PaintStrokeSessionChunkIR): Promise<PaintStrokeSessionReceiptIR> {
|
||||
const result = await this.request({ type: "appendPaintStrokeChunk", chunk });
|
||||
if (!result.paintStrokeSession) throw this.report("PAINT_SCHEMA_INVALID", "WebEngine did not accept the paint stroke chunk", true);
|
||||
return result.paintStrokeSession;
|
||||
}
|
||||
|
||||
async commitPaintStroke(session: PaintStrokeSessionCommitIR): Promise<BlendOpenResult & { delta: SceneDelta; paintStrokeSession: PaintStrokeSessionReceiptIR }> {
|
||||
const result = await this.request({ type: "commitPaintStroke", session });
|
||||
if (!result.snapshot || !result.delta || !result.paintStrokeSession) {
|
||||
throw this.report("PAINT_SCHEMA_INVALID", "WebEngine did not return the committed paint stroke", true);
|
||||
}
|
||||
this.geometryBuffers = result.geometryDelta
|
||||
? applyMeshGeometryDelta(this.geometryBuffers, result.geometryDelta)
|
||||
: result.geometryBuffers ?? this.geometryBuffers;
|
||||
this.nonMeshGeometryBuffers = result.nonMeshGeometryBuffers ?? this.nonMeshGeometryBuffers;
|
||||
return {
|
||||
status: result.status,
|
||||
snapshot: result.snapshot,
|
||||
geometryBuffers: [...this.geometryBuffers],
|
||||
nonMeshGeometryBuffers: [...this.nonMeshGeometryBuffers],
|
||||
delta: result.delta,
|
||||
paintStrokeSession: result.paintStrokeSession,
|
||||
};
|
||||
}
|
||||
|
||||
async cancelPaintStroke(session: PaintStrokeSessionCancelIR): Promise<PaintStrokeSessionReceiptIR> {
|
||||
const result = await this.request({ type: "cancelPaintStroke", session });
|
||||
if (!result.paintStrokeSession) throw this.report("PAINT_SCHEMA_INVALID", "WebEngine did not cancel the paint pointer session", true);
|
||||
return result.paintStrokeSession;
|
||||
}
|
||||
|
||||
async queryPaintPBVHCapability(request: PaintPBVHCapabilityRequest): Promise<CapabilityGateResult> {
|
||||
const result = await this.request({ type: "queryPaintPBVHCapability", request });
|
||||
if (!result.capabilityGate) throw this.report("CAPABILITY_MISSING", "WebEngine did not return the PBVH paint capability gate", true);
|
||||
return result.capabilityGate;
|
||||
}
|
||||
|
||||
async previewCommand(payload: Extract<WebEngineEditCommand, { type: "previewDecimateMesh" }>): Promise<BlendOpenResult> {
|
||||
const result = await this.request({ type: "applyCommand", payload });
|
||||
if (!result.snapshot) throw this.report("INVALID_ARGUMENT", "WebEngine 未返回预览结果", true);
|
||||
@@ -154,21 +214,41 @@ export class WebEngineClient {
|
||||
return result.openResources;
|
||||
}
|
||||
|
||||
async restart(): Promise<WebEngineStatus> {
|
||||
this.worker?.terminate();
|
||||
this.worker = null;
|
||||
this.workerFaulted = false;
|
||||
this.lastWorkerFault = null;
|
||||
this.geometryBuffers = [];
|
||||
this.nonMeshGeometryBuffers = [];
|
||||
return this.init();
|
||||
}
|
||||
|
||||
crashForTest(): void {
|
||||
this.worker?.postMessage({
|
||||
requestId: `web-engine-crash-${++this.requestCounter}`,
|
||||
command: { type: "crashForTest" },
|
||||
} satisfies WebEngineRequest);
|
||||
}
|
||||
|
||||
terminate(): void {
|
||||
this.failPending(this.report("WORKER_TERMINATED", "WebEngineWorker 已关闭", true));
|
||||
this.worker?.terminate();
|
||||
this.worker = null;
|
||||
this.workerFaulted = true;
|
||||
this.geometryBuffers = [];
|
||||
this.nonMeshGeometryBuffers = [];
|
||||
}
|
||||
|
||||
private start(): Worker {
|
||||
if (this.workerFaulted) {
|
||||
throw this.lastWorkerFault?.error ?? this.report("WORKER_TERMINATED", "WebEngineWorker 需要重启", true);
|
||||
}
|
||||
if (this.worker) return this.worker;
|
||||
const worker = this.workerFactory();
|
||||
worker.onmessage = (event: MessageEvent<WebEngineResponse>) => this.handleResponse(event.data);
|
||||
worker.onerror = (event) => {
|
||||
this.failPending(this.report("WORKER_TERMINATED", event.message || "WebEngineWorker 发生异常", true));
|
||||
};
|
||||
worker.onerror = (event) => this.handleWorkerFault(event.message || "WebEngineWorker 发生异常");
|
||||
worker.onmessageerror = () => this.handleWorkerFault("WebEngineWorker 消息无法解析");
|
||||
this.worker = worker;
|
||||
return worker;
|
||||
}
|
||||
@@ -179,7 +259,13 @@ export class WebEngineClient {
|
||||
onProgress?: (progress: ProgressEvent) => void,
|
||||
signal?: AbortSignal,
|
||||
): Promise<WebEngineResult> {
|
||||
const worker = this.start();
|
||||
let worker: Worker;
|
||||
try {
|
||||
worker = this.start();
|
||||
}
|
||||
catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
const requestId = `web-engine-${++this.requestCounter}`;
|
||||
const request = { requestId, command } as WebEngineRequest;
|
||||
return new Promise<WebEngineResult>((resolve, reject) => {
|
||||
@@ -189,6 +275,8 @@ export class WebEngineClient {
|
||||
reject(this.report("WORKER_TERMINATED", `WebEngine 请求超时: ${command.type}`, true));
|
||||
}, this.timeoutMs);
|
||||
const abort = (): void => {
|
||||
const pending = this.pending.get(requestId);
|
||||
if (pending) pending.aborted = true;
|
||||
worker.postMessage({
|
||||
requestId: `web-engine-cancel-${++this.requestCounter}`,
|
||||
command: { type: "cancelOpen", targetRequestId: requestId },
|
||||
@@ -214,7 +302,10 @@ export class WebEngineClient {
|
||||
this.pending.delete(response.requestId);
|
||||
clearTimeout(pending.timer);
|
||||
pending.cleanup?.();
|
||||
if (response.ok) pending.resolve(response.result);
|
||||
if (response.ok) {
|
||||
if (pending.aborted) pending.reject(this.report("OPEN_CANCELLED", "WebEngine open cancelled", true));
|
||||
else pending.resolve(response.result);
|
||||
}
|
||||
else pending.reject(response.error);
|
||||
}
|
||||
|
||||
@@ -227,6 +318,18 @@ export class WebEngineClient {
|
||||
this.pending.clear();
|
||||
}
|
||||
|
||||
private handleWorkerFault(message: string): void {
|
||||
if (this.workerFaulted) return;
|
||||
const fault = createWorkerFault("engine", message);
|
||||
this.workerFaulted = true;
|
||||
this.lastWorkerFault = fault;
|
||||
const worker = this.worker;
|
||||
this.worker = null;
|
||||
this.failPending(fault.error);
|
||||
worker?.terminate();
|
||||
this.onWorkerFault?.(fault);
|
||||
}
|
||||
|
||||
private report(code: ErrorReport["code"], message: string, recoverable: boolean): ErrorReport {
|
||||
return { code, severity: "error", message, recoverable };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user