fix(web): make first wasm load work on remote deployment
- Correct engine-manifest single wasm SHA and reuse cache for verification. - Remove duplicate main-thread wasm fetch; raise init timeout to 300s. - Record production deployment details and verification evidence. Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -43,7 +43,7 @@ import { useCallback, useEffect, useMemo, useReducer, useRef, useState } from "r
|
||||
import { createPortal, flushSync } from "react-dom";
|
||||
import { WebEngineClient, type BlendOpenResult } from "../engine-client/WebEngineClient";
|
||||
import {runEmbeddedRender} from '../engine-client/embedded-render';
|
||||
import { loadWebEngineManifest, verifyWasmResource } from "../../../protocol/manifest";
|
||||
import { loadWebEngineManifest } from "../../../protocol/manifest";
|
||||
import type { ProgressEvent } from "../../../protocol/progress";
|
||||
import type { MaterialIR, SceneSnapshotIR } from "../../../protocol/scene-ir";
|
||||
import { sequencerCommands } from "../../../protocol/sequencer";
|
||||
@@ -6050,11 +6050,10 @@ export function App({localOnly=false,course}:{localOnly?:boolean;course?:Availab
|
||||
useEffect(() => {
|
||||
let mounted = true;
|
||||
void loadWebEngineManifest()
|
||||
.then(async (manifest) => {
|
||||
const requiredResource = "variants" in manifest
|
||||
? manifest.variants.find((variant) => variant.id === "single")?.resources.wasm
|
||||
: manifest.wasm.find((resource) => resource.required);
|
||||
if (requiredResource) await verifyWasmResource(requiredResource);
|
||||
.then((manifest) => {
|
||||
// The engine worker verifies the wasm bytes when it loads the module.
|
||||
// Re-fetching the multi-hundred-MB wasm here doubles the first-load
|
||||
// transfer and can exceed the remote init budget.
|
||||
if (mounted) setManifestStatus(`Manifest: verified r${manifest.protocolVersion}`);
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
|
||||
@@ -326,7 +326,9 @@ export class WebEngineClient {
|
||||
}
|
||||
|
||||
async init(): Promise<WebEngineStatus> {
|
||||
return (await this.request({ type: "init" })).status;
|
||||
// The first load fetches and instantiates the multi-hundred-MB engine wasm.
|
||||
// A 30 s budget only fits loopback; remote deployments need a longer first-run window.
|
||||
return (await this.request({ type: "init" }, [], undefined, undefined, undefined, 300_000)).status;
|
||||
}
|
||||
|
||||
async gpuUICapabilities(): Promise<NonNullable<WebEngineResult["gpuUI"]>> {
|
||||
@@ -1249,6 +1251,7 @@ export class WebEngineClient {
|
||||
onProgress?: (progress: ProgressEvent) => void,
|
||||
signal?: AbortSignal,
|
||||
nativeInterrupt?: Int32Array,
|
||||
timeoutMs = this.timeoutMs,
|
||||
): Promise<WebEngineResult> {
|
||||
if (this.shutdownTask && command.type !== "shutdown" && command.type !== "cancelOpen") return Promise.reject(this.report("INVALID_ARGUMENT", "SESSION_CLOSE_PENDING: native session persistence is in progress", true));
|
||||
if (command.type === "openBlend" && signal?.aborted) return Promise.reject(this.report("OPEN_CANCELLED", "Blend open cancelled before dispatch", true));
|
||||
@@ -1270,7 +1273,7 @@ export class WebEngineClient {
|
||||
if (nativeInterrupt && Atomics.load(nativeInterrupt, 0) === 1 && !interruptGraceUsed) {
|
||||
interruptGraceUsed = true;
|
||||
const pending = this.pending.get(requestId);
|
||||
if (pending) pending.timer = setTimeout(onTimeout, Math.min(this.timeoutMs, 5000));
|
||||
if (pending) pending.timer = setTimeout(onTimeout, Math.min(timeoutMs, 5000));
|
||||
return;
|
||||
}
|
||||
const pending = this.pending.get(requestId);
|
||||
@@ -1279,9 +1282,9 @@ export class WebEngineClient {
|
||||
pending.timedOut = true;
|
||||
pending.timeoutError = this.report("WORKER_TERMINATED", `WebEngine 请求超时: ${command.type}`, true);
|
||||
pending.cleanup?.(); pending.cleanup = undefined; pending.reject(pending.timeoutError!);
|
||||
pending.timer = setTimeout(() => { if (this.pending.get(requestId) !== pending) return; this.pending.delete(requestId); this.retiredPending.set(requestId, pending); }, this.timeoutMs);
|
||||
pending.timer = setTimeout(() => { if (this.pending.get(requestId) !== pending) return; this.pending.delete(requestId); this.retiredPending.set(requestId, pending); }, timeoutMs);
|
||||
};
|
||||
const timer = setTimeout(onTimeout, Math.max(this.timeoutMs,this.renderDeadline-Date.now()));
|
||||
const timer = setTimeout(onTimeout, Math.max(timeoutMs,this.renderDeadline-Date.now()));
|
||||
const abort = (): void => {
|
||||
void this.request({ type: "cancelOpen", targetRequestId: requestId })
|
||||
.catch(() => undefined);
|
||||
|
||||
Reference in New Issue
Block a user