perf: giới hạn cache panorama (LRU) + ghim cảnh hiện tại/lân cận, tránh tràn VRAM

This commit is contained in:
tuyenio
2026-06-25 09:12:01 +07:00
parent dc14bc5197
commit da8bbb7641
+40
View File
@@ -216,6 +216,38 @@ const panoramaTexturePromises = new Map<string, Promise<THREE.Texture>>();
const panoramaTextureProgress = new Map<string, number>(); const panoramaTextureProgress = new Map<string, number>();
const panoramaTextureProgressListeners = new Map<string, Set<(progress: number) => void>>(); const panoramaTextureProgressListeners = new Map<string, Set<(progress: number) => void>>();
const MAX_CACHED_PANORAMAS = 8; // đủ cho cảnh hiện tại + các cảnh lân cận
const pinnedPanoramaUrls = new Set<string>();
// Ghim các ảnh không được phép dispose (cảnh hiện tại + lân cận).
function setPinnedPanoramas(urls: string[]) {
pinnedPanoramaUrls.clear();
urls.forEach((url) => pinnedPanoramaUrls.add(url));
}
// Đánh dấu ảnh là mới-dùng-nhất (Map giữ thứ tự chèn) rồi dọn nếu vượt giới hạn.
function touchPanoramaCache(image: string, texture: THREE.Texture) {
panoramaTextureCache.delete(image);
panoramaTextureCache.set(image, texture);
evictPanoramaTextures();
}
function evictPanoramaTextures() {
if (panoramaTextureCache.size <= MAX_CACHED_PANORAMAS) {
return;
}
for (const [url, texture] of panoramaTextureCache) {
if (panoramaTextureCache.size <= MAX_CACHED_PANORAMAS) {
break;
}
if (pinnedPanoramaUrls.has(url)) {
continue; // không dispose ảnh đang ghim (đang hiển thị/lân cận)
}
panoramaTextureCache.delete(url);
texture.dispose();
}
}
function notifyPanoramaProgress(image: string, progress: number) { function notifyPanoramaProgress(image: string, progress: number) {
panoramaTextureProgress.set(image, progress); panoramaTextureProgress.set(image, progress);
panoramaTextureProgressListeners.get(image)?.forEach((listener) => listener(progress)); panoramaTextureProgressListeners.get(image)?.forEach((listener) => listener(progress));
@@ -243,6 +275,7 @@ function loadPanoramaTexture(image: string, onProgress?: (progress: number) => v
const cachedTexture = panoramaTextureCache.get(image); const cachedTexture = panoramaTextureCache.get(image);
if (cachedTexture) { if (cachedTexture) {
touchPanoramaCache(image, cachedTexture);
onProgress?.(100); onProgress?.(100);
return Promise.resolve(cachedTexture); return Promise.resolve(cachedTexture);
} }
@@ -263,6 +296,7 @@ function loadPanoramaTexture(image: string, onProgress?: (progress: number) => v
texture.colorSpace = THREE.SRGBColorSpace; texture.colorSpace = THREE.SRGBColorSpace;
texture.anisotropy = 8; texture.anisotropy = 8;
panoramaTextureCache.set(image, texture); panoramaTextureCache.set(image, texture);
evictPanoramaTextures();
panoramaTexturePromises.delete(image); panoramaTexturePromises.delete(image);
notifyPanoramaProgress(image, 100); notifyPanoramaProgress(image, 100);
resolve(texture); resolve(texture);
@@ -1311,6 +1345,12 @@ function TourExperience({
const preloadTourScene = useCallback( const preloadTourScene = useCallback(
(scene: TourScene) => { (scene: TourScene) => {
// Ghim ảnh đang dùng + các cảnh lân cận để LRU không dispose nhầm.
const neighborImages = scene.hotspots
.map((hotspot) => tourConfig.sceneById.get(hotspot.targetId)?.image)
.filter((image): image is string => Boolean(image));
setPinnedPanoramas([scene.image, ...neighborImages]);
const preloadImage = (image: string) => { const preloadImage = (image: string) => {
loadPanoramaTexture(image) loadPanoramaTexture(image)
.then((texture) => warmPanoramaTexture(image, texture)) .then((texture) => warmPanoramaTexture(image, texture))