From 1ffb7e9d41ee1d71547b31cc306a614894360e60 Mon Sep 17 00:00:00 2001 From: Hiren Kangad Date: Tue, 10 Mar 2026 11:09:17 +0530 Subject: [PATCH] =?UTF-8?q?fix(refboard):=20remove=20LRU=20eviction=20?= =?UTF-8?q?=E2=80=94=20let=20culling=20system=20manage=20textures?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LRU eviction was destroying texture sources while sprites still referenced them, causing PixiJS v8 'alphaMode of null' crash. The viewport culling system (MAX_LOADED_TEXTURES=60) already controls what's loaded/unloaded, so TextureManager doesn't need its own eviction. Simplified to a pure load/unload cache with no budget tracking. --- frontend/src/canvas/TextureManager.ts | 56 +++++---------------------- 1 file changed, 9 insertions(+), 47 deletions(-) diff --git a/frontend/src/canvas/TextureManager.ts b/frontend/src/canvas/TextureManager.ts index c1b147b..09be85a 100644 --- a/frontend/src/canvas/TextureManager.ts +++ b/frontend/src/canvas/TextureManager.ts @@ -3,18 +3,17 @@ import { Texture, Assets } from "pixi.js"; interface TextureEntry { texture: Texture; url: string; // needed for Assets.unload() - lastUsed: number; - memoryEstimate: number; } /** - * TextureManager — GPU texture cache with LRU eviction and memory budget. - * Uses Assets.unload() instead of texture.destroy() for proper cleanup. + * TextureManager — GPU texture cache. + * + * No internal LRU eviction — the viewport culling system (PixiCanvas) + * manages loading/unloading based on proximity. This avoids destroying + * textures that sprites still reference (which crashes PixiJS v8). */ export class TextureManager { private cache = new Map(); - private budget = 256 * 1024 * 1024; // 256 MB — keeps headroom for GPU ops - private currentUsage = 0; /** Build the URL for a given asset. */ urlForAsset(assetKey: string): string { @@ -31,7 +30,6 @@ export class TextureManager { async load(assetKey: string): Promise { const existing = this.cache.get(assetKey); if (existing) { - existing.lastUsed = performance.now(); return existing.texture; } @@ -45,23 +43,7 @@ export class TextureManager { return Texture.EMPTY; } - const w = texture.source.width ?? 256; - const h = texture.source.height ?? 256; - const memoryEstimate = w * h * 4; // RGBA - - this.currentUsage += memoryEstimate; - - this.cache.set(assetKey, { - texture, - url, - lastUsed: performance.now(), - memoryEstimate, - }); - - while (this.currentUsage > this.budget && this.cache.size > 1) { - this.evictLRU(); - } - + this.cache.set(assetKey, { texture, url }); return texture; } @@ -70,39 +52,19 @@ export class TextureManager { const entry = this.cache.get(assetKey); if (!entry) return; - this.currentUsage -= entry.memoryEstimate; + this.cache.delete(assetKey); try { Assets.unload(entry.url); } catch { - // Fallback if Assets doesn't know about it - entry.texture.destroy(true); - } - this.cache.delete(assetKey); - } - - /** Evict the least-recently-used cache entry. */ - private evictLRU(): void { - let oldestKey: string | null = null; - let oldestTime = Infinity; - - for (const [key, entry] of this.cache) { - if (entry.lastUsed < oldestTime) { - oldestTime = entry.lastUsed; - oldestKey = key; - } - } - - if (oldestKey) { - this.unload(oldestKey); + // ignore — texture may already be gone } } - /** Unload all cached textures and reset memory tracking. */ + /** Unload all cached textures. */ clear(): void { for (const [key] of this.cache) { this.unload(key); } this.cache.clear(); - this.currentUsage = 0; } }