fix(refboard): remove LRU eviction — let culling system manage textures
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.
This commit is contained in:
@@ -3,18 +3,17 @@ import { Texture, Assets } from "pixi.js";
|
|||||||
interface TextureEntry {
|
interface TextureEntry {
|
||||||
texture: Texture;
|
texture: Texture;
|
||||||
url: string; // needed for Assets.unload()
|
url: string; // needed for Assets.unload()
|
||||||
lastUsed: number;
|
|
||||||
memoryEstimate: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TextureManager — GPU texture cache with LRU eviction and memory budget.
|
* TextureManager — GPU texture cache.
|
||||||
* Uses Assets.unload() instead of texture.destroy() for proper cleanup.
|
*
|
||||||
|
* 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 {
|
export class TextureManager {
|
||||||
private cache = new Map<string, TextureEntry>();
|
private cache = new Map<string, TextureEntry>();
|
||||||
private budget = 256 * 1024 * 1024; // 256 MB — keeps headroom for GPU ops
|
|
||||||
private currentUsage = 0;
|
|
||||||
|
|
||||||
/** Build the URL for a given asset. */
|
/** Build the URL for a given asset. */
|
||||||
urlForAsset(assetKey: string): string {
|
urlForAsset(assetKey: string): string {
|
||||||
@@ -31,7 +30,6 @@ export class TextureManager {
|
|||||||
async load(assetKey: string): Promise<Texture> {
|
async load(assetKey: string): Promise<Texture> {
|
||||||
const existing = this.cache.get(assetKey);
|
const existing = this.cache.get(assetKey);
|
||||||
if (existing) {
|
if (existing) {
|
||||||
existing.lastUsed = performance.now();
|
|
||||||
return existing.texture;
|
return existing.texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,23 +43,7 @@ export class TextureManager {
|
|||||||
return Texture.EMPTY;
|
return Texture.EMPTY;
|
||||||
}
|
}
|
||||||
|
|
||||||
const w = texture.source.width ?? 256;
|
this.cache.set(assetKey, { texture, url });
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
return texture;
|
return texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,39 +52,19 @@ export class TextureManager {
|
|||||||
const entry = this.cache.get(assetKey);
|
const entry = this.cache.get(assetKey);
|
||||||
if (!entry) return;
|
if (!entry) return;
|
||||||
|
|
||||||
this.currentUsage -= entry.memoryEstimate;
|
this.cache.delete(assetKey);
|
||||||
try {
|
try {
|
||||||
Assets.unload(entry.url);
|
Assets.unload(entry.url);
|
||||||
} catch {
|
} catch {
|
||||||
// Fallback if Assets doesn't know about it
|
// ignore — texture may already be gone
|
||||||
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) {
|
/** Unload all cached textures. */
|
||||||
this.unload(oldestKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Unload all cached textures and reset memory tracking. */
|
|
||||||
clear(): void {
|
clear(): void {
|
||||||
for (const [key] of this.cache) {
|
for (const [key] of this.cache) {
|
||||||
this.unload(key);
|
this.unload(key);
|
||||||
}
|
}
|
||||||
this.cache.clear();
|
this.cache.clear();
|
||||||
this.currentUsage = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user