updated the dungeon loading so it doesnt fail first try

This commit is contained in:
phenom committed 2026-08-17 13:51:12 -04:00
1 parent e39aebf3a1
commit 7e60864e70
11 files changed
+383 -69

No files matched your search

+94 -3
View File
@@ -1,4 +1,4 @@
import { useCallback, useEffect, useMemo, useRef } from "react";
import { Suspense, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { RigidBody, TrimeshCollider } from "@react-three/rapier";
import {
AdditiveBlending,
@@ -15,6 +15,11 @@ import { useGameStore } from "../game/store";
import { collisionTrimeshes, prepareCollision } from "./productionDungeonCollision";
import { disposeObject3DResources } from "./threeResourceDisposal";
import { useGameGLTF } from "./useGameGLTF";
import {
DUNGEON_COLLISION_LOAD_BATCH_SIZE,
DUNGEON_VISUAL_LOAD_BATCH_SIZE,
advanceDungeonAssetCount,
} from "./dungeonLoadSequencing";
function matchesRole(name: string, patterns: readonly string[], names: readonly string[]): boolean {
const lower = name.toLowerCase();
@@ -90,6 +95,40 @@ function VisualChunk({ url, definition }: { readonly url: string; readonly defin
return <primitive object={visual} position={position} rotation={rotation} scale={scale} />;
}
function AssetProbe({ url }: { readonly url: string }) {
useGameGLTF(url);
return null;
}
function CommitLoadedBatch({ onReady }: { readonly onReady: () => void }) {
useEffect(() => {
const frame = window.requestAnimationFrame(onReady);
return () => window.cancelAnimationFrame(frame);
}, [onReady]);
return null;
}
/**
* Suspends only the small batch currently being fetched. Previously prepared
* visuals and colliders stay mounted while the next batch enters the GLTF
* cache, avoiding one cold-start burst across the entire dungeon package.
*/
function AssetBatchLoader({
urls,
onReady,
}: {
readonly urls: readonly string[];
readonly onReady: () => void;
}) {
if (!urls.length) return null;
return (
<Suspense fallback={null}>
{urls.map((url) => <AssetProbe key={url} url={url} />)}
<CommitLoadedBatch onReady={onReady} />
</Suspense>
);
}
function CollisionChunk({
url,
definition,
@@ -146,8 +185,53 @@ export function ProductionDungeon({
if (!assets.visual.length || !assets.collision.length) {
throw new Error(`${definition.title} mounted without its required visual and collision GLBs.`);
}
const [loadedVisualCount, setLoadedVisualCount] = useState(0);
const [loadedCollisionCount, setLoadedCollisionCount] = useState(0);
const preparedCollisions = useRef(new Set<string>());
const signalledFirstFrame = useRef(false);
const pendingVisuals = assets.visual.slice(
loadedVisualCount,
advanceDungeonAssetCount(
loadedVisualCount,
assets.visual.length,
DUNGEON_VISUAL_LOAD_BATCH_SIZE,
),
);
const visualsLoaded = loadedVisualCount >= assets.visual.length;
const pendingCollisions = visualsLoaded
? assets.collision.slice(
loadedCollisionCount,
advanceDungeonAssetCount(
loadedCollisionCount,
assets.collision.length,
DUNGEON_COLLISION_LOAD_BATCH_SIZE,
),
)
: [];
const pendingUrls = pendingVisuals.length ? pendingVisuals : pendingCollisions;
const commitLoadedBatch = useCallback(() => {
if (!visualsLoaded) {
setLoadedVisualCount((current) => advanceDungeonAssetCount(
current,
assets.visual.length,
DUNGEON_VISUAL_LOAD_BATCH_SIZE,
));
return;
}
setLoadedCollisionCount((current) => advanceDungeonAssetCount(
current,
assets.collision.length,
DUNGEON_COLLISION_LOAD_BATCH_SIZE,
));
}, [assets.collision.length, assets.visual.length, visualsLoaded]);
useEffect(() => {
useGameStore.getState().setAssetStatus(
"loading",
`Preparing ${loadedVisualCount}/${assets.visual.length} visual and ${loadedCollisionCount}/${assets.collision.length} collision chunks.`,
);
}, [assets.collision.length, assets.visual.length, loadedCollisionCount, loadedVisualCount]);
const registerPrepared = useCallback((url: string, ready: boolean) => {
if (!ready) {
preparedCollisions.current.delete(url);
@@ -163,8 +247,10 @@ export function ProductionDungeon({
return (
<group name={`${definition.id}-production`}>
{assets.visual.map((url) => <VisualChunk key={url} url={url} definition={definition} />)}
{assets.collision.map((url) => (
{assets.visual.slice(0, loadedVisualCount).map((url) => (
<VisualChunk key={url} url={url} definition={definition} />
))}
{assets.collision.slice(0, loadedCollisionCount).map((url) => (
<CollisionChunk
key={url}
url={url}
@@ -172,6 +258,11 @@ export function ProductionDungeon({
registerPrepared={registerPrepared}
/>
))}
<AssetBatchLoader
key={`${loadedVisualCount}:${loadedCollisionCount}:${pendingUrls.join("|")}`}
urls={pendingUrls}
onReady={commitLoadedBatch}
/>
</group>
);
}