fix: prevent world 3d viewer loading forever
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

This commit is contained in:
Your Name 2026-05-22 22:15:37 +08:00
parent ccb57c095a
commit fc73057935

View File

@ -178,6 +178,7 @@ const SPARK_MINIMAL_FRAME_LOD_SPLAT_SCALE = 0.82;
const SPARK_MINIMAL_FRAME_LOD_RENDER_SCALE = 1.6;
const SPARK_MIN_SORT_INTERVAL_MS = 24;
const SPARK_MINIMAL_FRAME_SORT_INTERVAL_MS = 40;
const SPARK_SPLAT_LOAD_TIMEOUT_MS = 45_000;
const KEY_TO_MOVEMENT: Partial<Record<string, MovementCommand>> = {
KeyW: "forward",
@ -507,12 +508,14 @@ function WorldEmptyState({
description,
fillFrame = false,
previewImageUrl,
loading = true,
}: {
icon: React.ComponentType<{ className?: string }>;
title: string;
description: string;
fillFrame?: boolean;
previewImageUrl?: string | null;
loading?: boolean;
}) {
if (previewImageUrl && fillFrame) {
return (
@ -523,7 +526,13 @@ function WorldEmptyState({
src={previewImageUrl}
/>
<div className="absolute inset-0 flex items-center justify-center bg-black/10">
<Loader2 className="h-7 w-7 animate-spin text-white/55 drop-shadow" />
{loading ? (
<Loader2 className="h-7 w-7 animate-spin text-white/55 drop-shadow" />
) : (
<div className="flex h-9 w-9 items-center justify-center rounded-full border border-white/15 bg-black/55 text-white/80 shadow-lg backdrop-blur-sm">
<Icon className="h-4 w-4" />
</div>
)}
</div>
</div>
);
@ -549,7 +558,13 @@ function WorldEmptyState({
src={previewImageUrl}
/>
<div className="absolute inset-0 flex items-center justify-center bg-black/30 backdrop-blur-[1px]">
<Loader2 className="h-8 w-8 animate-spin text-white/40" />
{loading ? (
<Loader2 className="h-8 w-8 animate-spin text-white/40" />
) : (
<div className="flex h-12 w-12 items-center justify-center rounded-2xl border border-white/10 bg-black/55 text-white/80 shadow-xl">
<Icon className="h-5 w-5" />
</div>
)}
</div>
</div>
) : (
@ -1173,40 +1188,63 @@ function WorldSplatScene({
let cancelled = false;
let idleCallbackId: number | null = null;
let timeoutId: ReturnType<typeof globalThis.setTimeout> | null = null;
const loadTimeoutId = globalThis.setTimeout(() => {
if (cancelled) return;
onLoadStateChange?.("error");
}, SPARK_SPLAT_LOAD_TIMEOUT_MS);
const markReady = () => {
if (cancelled || hasAppliedInitialViewRef.current) return;
try {
lastCameraPoseRef.current = positionCameraInsideWorld({
camera,
controls: orbitControlsRef.current,
firstPerson,
groundPlaneOffset,
splatMesh,
});
} catch (error) {
console.warn("[WorldSplatScene] Failed to position camera for SPZ", error);
lastCameraPoseRef.current = null;
}
hasAppliedInitialViewRef.current = true;
onLoadStateChange?.("ready");
const buildParticleCloud = () => {
if (cancelled || revealPhaseRef.current === "hidden") return;
try {
setParticleCloudData(buildSplatParticleCloudData(splatMesh));
} catch (error) {
console.warn("[WorldSplatScene] Failed to build SPZ particle cloud", error);
}
};
if ("requestIdleCallback" in window) {
idleCallbackId = window.requestIdleCallback(buildParticleCloud, {
timeout: 250,
});
} else {
timeoutId = globalThis.setTimeout(buildParticleCloud, 160);
}
};
splatMesh.initialized
.then(() => {
if (cancelled) return;
window.requestAnimationFrame(() => {
if (cancelled || hasAppliedInitialViewRef.current) return;
lastCameraPoseRef.current = positionCameraInsideWorld({
camera,
controls: orbitControlsRef.current,
firstPerson,
groundPlaneOffset,
splatMesh,
});
hasAppliedInitialViewRef.current = true;
onLoadStateChange?.("ready");
const buildParticleCloud = () => {
if (cancelled || revealPhaseRef.current === "hidden") return;
setParticleCloudData(buildSplatParticleCloudData(splatMesh));
};
if ("requestIdleCallback" in window) {
idleCallbackId = window.requestIdleCallback(buildParticleCloud, {
timeout: 250,
});
} else {
timeoutId = globalThis.setTimeout(buildParticleCloud, 160);
}
});
globalThis.clearTimeout(loadTimeoutId);
window.requestAnimationFrame(markReady);
})
.catch(() => {
if (!cancelled) onLoadStateChange?.("error");
.catch((error) => {
globalThis.clearTimeout(loadTimeoutId);
if (!cancelled) {
console.warn("[WorldSplatScene] Failed to load SPZ", error);
onLoadStateChange?.("error");
}
});
return () => {
cancelled = true;
globalThis.clearTimeout(loadTimeoutId);
if (idleCallbackId !== null && "cancelIdleCallback" in window) {
window.cancelIdleCallback(idleCallbackId);
}
@ -1551,6 +1589,7 @@ function WorldSplatViewer({
}
fillFrame={minimalFrame}
icon={AlertCircle}
loading={false}
previewImageUrl={previewImageUrl}
title="World 视图加载失败"
/>