56 lines
2.2 KiB
TypeScript
56 lines
2.2 KiB
TypeScript
import type { DungeonId } from "../game/dungeonRegistry";
|
|
import type { Frameloop } from "@react-three/fiber";
|
|
import type { GameplayFrameRateLimit } from "../game/frameRateSettings";
|
|
|
|
/**
|
|
* Encounter transitions must never remount the Canvas: R3F intentionally loses
|
|
* the old WebGL context during Canvas disposal. Only explicit graphics recovery
|
|
* is allowed to replace the renderer.
|
|
*/
|
|
export function rendererCanvasKey(canvasRevision: number): string {
|
|
return `renderer:${Math.max(0, Math.trunc(canvasRevision))}`;
|
|
}
|
|
|
|
/** The Rapier world and all encounter actors are rebuilt for every session. */
|
|
export function encounterWorldKey(dungeonId: DungeonId, sessionRevision: number): string {
|
|
return `${dungeonId}:encounter:${Math.max(0, Math.trunc(sessionRevision))}`;
|
|
}
|
|
|
|
/**
|
|
* Readiness belongs to one encounter world. Comparing revisions avoids a
|
|
* child collision-ready effect being overwritten by a later parent reset in
|
|
* the same React effect flush, and prevents stale worlds from unblocking the
|
|
* current session.
|
|
*/
|
|
export function encounterWorldIsReady(
|
|
readySessionRevision: number | null,
|
|
sessionRevision: number,
|
|
): boolean {
|
|
return readySessionRevision !== null
|
|
&& readySessionRevision === Math.max(0, Math.trunc(sessionRevision));
|
|
}
|
|
|
|
/**
|
|
* A paused local world only needs a frame when React or R3F invalidates it.
|
|
* Loading, recovery, and shared-online worlds keep the continuous loop so
|
|
* asynchronous scene setup and authoritative remote simulation can progress.
|
|
*/
|
|
export function sceneFrameLoop(options: {
|
|
readonly worldReady: boolean;
|
|
readonly graphicsRecovering: boolean;
|
|
readonly sharedOnline: boolean;
|
|
readonly paused: boolean;
|
|
readonly mapOpen: boolean;
|
|
readonly companionOpen: boolean;
|
|
readonly documentVisible: boolean;
|
|
readonly frameRateLimit: GameplayFrameRateLimit;
|
|
}): Frameloop {
|
|
if (!options.documentVisible && !options.sharedOnline) return "demand";
|
|
const locallyPaused = options.worldReady
|
|
&& !options.graphicsRecovering
|
|
&& !options.sharedOnline
|
|
&& (options.paused || options.mapOpen || options.companionOpen);
|
|
if (locallyPaused) return "demand";
|
|
return options.frameRateLimit === "display" ? "always" : "never";
|
|
}
|