fix: enforce first-frame timeout and nav labels
This commit is contained in:
@@ -16,6 +16,7 @@ import type {
|
||||
} from '@/scene/types';
|
||||
|
||||
const RESTORE_CUTOFF_MS = 5_000;
|
||||
const FIRST_FRAME_CUTOFF_MS = 5_000;
|
||||
|
||||
const legalTransitions: Readonly<Record<SceneLifecycleState['kind'], readonly SceneLifecycleState['kind'][]>> = {
|
||||
idle: ['loading', 'paused', 'static', 'disposed'],
|
||||
@@ -70,6 +71,8 @@ export function createSceneController(options: SceneControllerOptions): SceneCon
|
||||
let contextLossCount = 0;
|
||||
let restorationSucceeded = false;
|
||||
let lostAtForRestore: number | null = null;
|
||||
let firstFrameTimer: number | null = null;
|
||||
let firstFrameGeneration: number | null = null;
|
||||
|
||||
const transition = (next: SceneLifecycleState): void => {
|
||||
machine.transition(next);
|
||||
@@ -78,9 +81,48 @@ export function createSceneController(options: SceneControllerOptions): SceneCon
|
||||
|
||||
const stopRaf = (): void => rootResources.cancelAnimationFrame();
|
||||
|
||||
const clearFirstFrameWatchdog = (generation?: number): void => {
|
||||
if (
|
||||
firstFrameTimer === null
|
||||
|| (generation !== undefined && firstFrameGeneration !== generation)
|
||||
) return;
|
||||
scheduler.clearTimeout(firstFrameTimer);
|
||||
rootResources.releaseTimer(firstFrameTimer);
|
||||
firstFrameTimer = null;
|
||||
firstFrameGeneration = null;
|
||||
};
|
||||
|
||||
const armFirstFrameWatchdog = (generation: number): void => {
|
||||
clearFirstFrameWatchdog();
|
||||
firstFrameGeneration = generation;
|
||||
const timer = scheduler.setTimeout(() => {
|
||||
rootResources.releaseTimer(timer);
|
||||
if (firstFrameTimer !== timer || firstFrameGeneration !== generation) return;
|
||||
firstFrameTimer = null;
|
||||
firstFrameGeneration = null;
|
||||
const lifecycle = machine.snapshot();
|
||||
const generationIsCurrent = (
|
||||
(lifecycle.kind === 'loading' && lifecycle.generation === generation)
|
||||
|| (lifecycle.kind === 'running' && lifecycle.generation === generation)
|
||||
|| (
|
||||
lifecycle.kind === 'paused'
|
||||
&& lifecycle.generation === generation
|
||||
&& !lifecycle.resumeNeedsLoad
|
||||
)
|
||||
);
|
||||
if (generationIsCurrent) enterStatic('first-frame-timeout');
|
||||
}, FIRST_FRAME_CUTOFF_MS);
|
||||
firstFrameTimer = rootResources.timer(timer, scheduler.clearTimeout);
|
||||
};
|
||||
|
||||
const confirmFirstFrame = (generation: number): void => {
|
||||
clearFirstFrameWatchdog(generation);
|
||||
};
|
||||
|
||||
const disposeGeneration = (): void => {
|
||||
stopRaf();
|
||||
if (current) {
|
||||
clearFirstFrameWatchdog(current.generation);
|
||||
current.resources.dispose();
|
||||
current = null;
|
||||
}
|
||||
@@ -91,6 +133,7 @@ export function createSceneController(options: SceneControllerOptions): SceneCon
|
||||
const invalidated = loading;
|
||||
loading = null;
|
||||
activeOperation = null;
|
||||
clearFirstFrameWatchdog(invalidated.generation);
|
||||
invalidated.abort.abort();
|
||||
invalidated.resources.dispose();
|
||||
};
|
||||
@@ -123,9 +166,13 @@ export function createSceneController(options: SceneControllerOptions): SceneCon
|
||||
if (overlay !== 'active' || machine.snapshot().kind !== 'running' || !current) return;
|
||||
try {
|
||||
const frameStartedAt = scheduler.now();
|
||||
current.runtime.render(latestJourney, now);
|
||||
const active = current;
|
||||
const complete = active.runtime.render(latestJourney, now) !== false;
|
||||
const frameEndedAt = scheduler.now();
|
||||
options.onFrameRendered?.(frameStartedAt, frameEndedAt);
|
||||
if (complete && current === active) {
|
||||
confirmFirstFrame(active.generation);
|
||||
options.onFrameRendered?.(frameStartedAt, frameEndedAt);
|
||||
}
|
||||
} catch {
|
||||
enterStatic('render-failure');
|
||||
return;
|
||||
@@ -148,6 +195,7 @@ export function createSceneController(options: SceneControllerOptions): SceneCon
|
||||
transition(mode === 'restoring'
|
||||
? { kind: 'restoring', generation, lostAt: restoreLostAt ?? scheduler.now() }
|
||||
: { kind: 'loading', generation });
|
||||
if (mode === 'loading') armFirstFrameWatchdog(generation);
|
||||
|
||||
const context: SceneBuildContext = {
|
||||
canvas: options.canvas,
|
||||
|
||||
Reference in New Issue
Block a user