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.

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);