From da8bbb7641c2ce20abfbc1b170e9fc4fc492fb88 Mon Sep 17 00:00:00 2001 From: tuyenio Date: Thu, 25 Jun 2026 09:12:01 +0700 Subject: [PATCH] =?UTF-8?q?perf:=20gi=E1=BB=9Bi=20h=E1=BA=A1n=20cache=20pa?= =?UTF-8?q?norama=20(LRU)=20+=20ghim=20c=E1=BA=A3nh=20hi=E1=BB=87n=20t?= =?UTF-8?q?=E1=BA=A1i/l=C3=A2n=20c=E1=BA=ADn,=20tr=C3=A1nh=20tr=C3=A0n=20V?= =?UTF-8?q?RAM?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/components/VirtualTour.tsx | 40 ++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) 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))