diff --git a/src/app/components/VirtualTour.tsx b/src/app/components/VirtualTour.tsx index fd7e55a..6042e0b 100644 --- a/src/app/components/VirtualTour.tsx +++ b/src/app/components/VirtualTour.tsx @@ -216,6 +216,38 @@ const panoramaTexturePromises = new Map>(); const panoramaTextureProgress = new Map(); const panoramaTextureProgressListeners = new Map 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(); + +// 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) { panoramaTextureProgress.set(image, 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); if (cachedTexture) { + touchPanoramaCache(image, cachedTexture); onProgress?.(100); return Promise.resolve(cachedTexture); } @@ -263,6 +296,7 @@ function loadPanoramaTexture(image: string, onProgress?: (progress: number) => v texture.colorSpace = THREE.SRGBColorSpace; texture.anisotropy = 8; panoramaTextureCache.set(image, texture); + evictPanoramaTextures(); panoramaTexturePromises.delete(image); notifyPanoramaProgress(image, 100); resolve(texture); @@ -1311,6 +1345,12 @@ function TourExperience({ const preloadTourScene = useCallback( (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) => { loadPanoramaTexture(image) .then((texture) => warmPanoramaTexture(image, texture))