feat(web): unify worker wasm calls behind json abi
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled

This commit is contained in:
mes123456
2026-08-30 06:45:31 +08:00
parent 34801d655e
commit d101ff2d1f
34 changed files with 1775 additions and 96 deletions

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@@ -45,16 +45,11 @@ type WasmModule = {
_web_engine_get_live_handles: () => number;
_web_engine_get_allocated_bytes: () => number;
_web_engine_open_blend: (handle: number, data: number, length: number) => number;
_web_engine_apply_command: (handle: number, data: number, length: number) => number;
_web_engine_call_json: (handle: number, data: number, length: number, resultData: number, resultLength: number) => number;
_web_engine_append_library_object: (handle: number, source: number, sourceLength: number, metadata: number, metadataLength: number) => number;
_web_engine_undo: (handle: number) => number;
_web_engine_redo: (handle: number) => number;
_web_engine_get_scene_snapshot: (handle: number, data: number, length: number) => number;
_web_engine_get_scene_metadata?: (handle: number, data: number, length: number) => number;
_web_engine_get_scene_geometry?: (handle: number, data: number, length: number) => number;
_web_engine_get_scene_delta: (handle: number, data: number, length: number) => number;
_web_engine_get_packed_asset: (handle: number, assetId: number, assetIdLength: number, data: number, length: number) => number;
_web_engine_evaluate_depsgraph: (handle: number, data: number, length: number) => number;
_web_engine_save_blend: (handle: number, data: number, length: number) => number;
_web_engine_decimate_apply: (
positions: number,
@@ -948,57 +943,45 @@ function copyCommand(command: unknown): { pointer: number; length: number } {
return copyIntoWasm(encoded.buffer);
}
function readSnapshot(): ReturnType<typeof parseSceneSnapshotIR> {
if (!module || !handle) throw new Error("WebEngine is not initialized");
const dataOut = module._malloc(4);
const lengthOut = module._malloc(4);
function callUnifiedJson<T>(cmd: string, params: Record<string, unknown> = {}, targetModule: WasmModule | null = module, targetHandle = handle): T {
if (!targetModule || !targetHandle) throw new Error("WebEngine is not initialized");
if (typeof targetModule._web_engine_call_json !== "function") throw report("WASM_INIT_FAILED", "Unified WebEngine JSON ABI is unavailable", true);
const input = copyIntoWasm(new TextEncoder().encode(JSON.stringify({ cmd, params })).buffer);
const resultData = targetModule._malloc(4);
const resultLength = targetModule._malloc(4);
if (!resultData || !resultLength) {
targetModule._free(input.pointer);
if (resultData) targetModule._free(resultData);
if (resultLength) targetModule._free(resultLength);
throw report("WASM_INIT_FAILED", "Unified WebEngine JSON output allocation failed", true);
}
let resultPointer = 0;
try {
const result = module._web_engine_get_scene_snapshot(handle, dataOut, lengthOut);
if (result !== 0) throw nativeError("INVALID_ARGUMENT");
const pointer = module.HEAPU32[dataOut >>> 2];
const length = module.HEAPU32[lengthOut >>> 2];
const bytes = module.HEAPU8.slice(pointer, pointer + length);
return parseSceneSnapshotIR(JSON.parse(new TextDecoder().decode(bytes)) as unknown);
const nativeCode = targetModule._web_engine_call_json(targetHandle, input.pointer, input.length, resultData, resultLength);
resultPointer = targetModule.HEAPU32[resultData >>> 2];
const resultBytes = targetModule.HEAPU8.slice(resultPointer, resultPointer + targetModule.HEAPU32[resultLength >>> 2]);
const envelope = JSON.parse(new TextDecoder().decode(resultBytes)) as { ok?: boolean; result?: T; error?: string };
if (nativeCode !== 0 || envelope.ok !== true) throw nativeErrorFor(targetModule, "INVALID_ARGUMENT");
return envelope.result as T;
}
finally {
module._free(dataOut);
module._free(lengthOut);
if (resultPointer) targetModule._web_engine_free_buffer(resultPointer);
targetModule._free(input.pointer);
targetModule._free(resultData);
targetModule._free(resultLength);
}
}
function readSnapshot(): ReturnType<typeof parseSceneSnapshotIR> {
return parseSceneSnapshotIR(callUnifiedJson<unknown>("snapshot"));
}
function readDelta(): SceneDelta {
if (!module || !handle) throw new Error("WebEngine is not initialized");
const dataOut = module._malloc(4);
const lengthOut = module._malloc(4);
try {
const result = module._web_engine_get_scene_delta(handle, dataOut, lengthOut);
if (result !== 0) throw nativeError("INVALID_ARGUMENT");
const pointer = module.HEAPU32[dataOut >>> 2];
const length = module.HEAPU32[lengthOut >>> 2];
return parseSceneDelta(JSON.parse(new TextDecoder().decode(module.HEAPU8.slice(pointer, pointer + length))) as unknown);
}
finally {
module._free(dataOut);
module._free(lengthOut);
}
return parseSceneDelta(callUnifiedJson<unknown>("delta"));
}
function readDepsgraph(): ReturnType<typeof parseDepsgraphEvaluation> {
if (!module || !handle) throw new Error("WebEngine is not initialized");
const dataOut = module._malloc(4);
const lengthOut = module._malloc(4);
try {
const result = module._web_engine_evaluate_depsgraph(handle, dataOut, lengthOut);
if (result !== 0) throw nativeError("CAPABILITY_MISSING");
const pointer = module.HEAPU32[dataOut >>> 2];
const length = module.HEAPU32[lengthOut >>> 2];
if (!pointer || !length) throw report("CAPABILITY_MISSING", "Blender Depsgraph returned no report");
return parseDepsgraphEvaluation(JSON.parse(new TextDecoder().decode(module.HEAPU8.slice(pointer, pointer + length))) as unknown);
}
finally {
module._free(dataOut);
module._free(lengthOut);
}
return parseDepsgraphEvaluation(callUnifiedJson<unknown>("depsgraph"));
}
function copyFloatBuffer(values: number[]): ArrayBuffer {
@@ -1340,13 +1323,7 @@ async function evaluateIsolatedDecimate(
isolatedModule._free(blendInput.pointer);
}
const sourceSnapshot = readSnapshot();
const commandInput = copyCommand(payload);
try {
if (isolatedModule._web_engine_apply_command(isolatedHandle, commandInput.pointer, commandInput.length) !== 0) throw nativeError("INVALID_ARGUMENT");
}
finally {
isolatedModule._free(commandInput.pointer);
}
callUnifiedJson("applyCommand", { command: payload }, isolatedModule, isolatedHandle);
const scene = await readSnapshotResult();
return { sourceSnapshot, scene, simplify: simplifyReport(sourceSnapshot, scene, payload) };
}
@@ -1497,13 +1474,7 @@ async function handleRequest(event: MessageEvent<WebEngineRequest>): Promise<voi
if (!beforeSnapshot) throw report("PAINT_SCHEMA_INVALID", "Paint pointer sessions require an open Main scene");
const staged = paintStrokeSessions.commit(request.command.session, beforeSnapshot.revision);
assertFutureCapability(staged.command);
const input = copyCommand(staged.command);
try {
if (!module || module._web_engine_apply_command(handle, input.pointer, input.length) !== 0) throw nativeError("INVALID_ARGUMENT");
}
finally {
module?._free(input.pointer);
}
callUnifiedJson("applyCommand", { command: staged.command });
const scene = await readSnapshotResult();
currentSnapshot = scene.snapshot;
result = {
@@ -1568,13 +1539,7 @@ async function handleRequest(event: MessageEvent<WebEngineRequest>): Promise<voi
module._free(blendInput.pointer);
}
const nativePayload = { ...payload, type: "decimateMesh" as const };
const commandInput = copyCommand(nativePayload);
try {
if (module._web_engine_apply_command(previewHandle, commandInput.pointer, commandInput.length) !== 0) throw nativeError("INVALID_ARGUMENT");
}
finally {
module._free(commandInput.pointer);
}
callUnifiedJson("applyCommand", { command: nativePayload }, module, previewHandle);
const scene = await readSnapshotResult();
scene.snapshot = parseSceneSnapshotIR({ ...scene.snapshot, revision: beforeSnapshot.revision });
result = { status: status(), ...scene, simplify: simplifyReport(beforeSnapshot, scene, payload) };
@@ -1588,25 +1553,13 @@ async function handleRequest(event: MessageEvent<WebEngineRequest>): Promise<voi
break;
}
if (payload.type === "undo" || payload.type === "redo") {
if (typeof module?._web_engine_undo !== "function" || typeof module?._web_engine_redo !== "function") {
throw report("WASM_INIT_FAILED", `history ABI unavailable: ${Object.keys(module ?? {}).filter((key) => key.includes("undo") || key.includes("redo")).join(",")}`, true);
}
const undoResult = payload.type === "undo" ? module?._web_engine_undo(handle) : module?._web_engine_redo(handle);
if (!module || undoResult !== 0) throw nativeError("INVALID_ARGUMENT");
callUnifiedJson(payload.type);
const scene = await readSnapshotResult();
currentSnapshot = scene.snapshot;
result = { status: status(), ...publishIncrementalScene(scene), delta: readDelta() };
break;
}
const input = copyCommand(nativePayload);
try {
if (!module || module._web_engine_apply_command(handle, input.pointer, input.length) !== 0) {
throw nativeError("INVALID_ARGUMENT");
}
}
finally {
module?._free(input.pointer);
}
callUnifiedJson("applyCommand", { command: nativePayload });
const scene = await readSnapshotResult();
currentSnapshot = scene.snapshot;
const simplify = simplifyReport(beforeSnapshot, scene, payload);