fix: harden GIF support — ref-counted sources, defensive detection, play budget

1. Move GIF loading behind TextureManager.loadGif/releaseGif with
   ref-counting (fixes shared-source invalidation on duplicate GIFs)
2. Strip query strings/fragments before .gif extension check
3. GIFs now load paused — separate MAX_PLAYING_GIFS=8 budget in
   culling system, only nearest N animate (others show static frame)
This commit is contained in:
Hiren Kangad
2026-03-10 19:41:50 +05:30
parent 5a64708bd4
commit e45fce3e67
4 changed files with 122 additions and 15 deletions
+52 -1
View File
@@ -1,4 +1,5 @@
import { Texture, Assets } from "pixi.js";
import type { GifSource } from "pixi.js/gif";
interface TextureEntry {
texture: Texture;
@@ -6,6 +7,12 @@ interface TextureEntry {
refCount: number;
}
interface GifEntry {
source: GifSource;
url: string;
refCount: number;
}
/**
* TextureManager — GPU texture cache with reference counting.
*
@@ -19,6 +26,7 @@ interface TextureEntry {
*/
export class TextureManager {
private cache = new Map<string, TextureEntry>();
private gifCache = new Map<string, GifEntry>();
/** Build the URL for a given asset. */
urlForAsset(assetKey: string): string {
@@ -84,11 +92,54 @@ export class TextureManager {
}
}
/** Unload all cached textures. */
// -- GIF source management (ref-counted, same pattern as textures) --
/**
* Load a GifSource for the given asset. Increments ref count.
* Returns cached source if available, otherwise fetches and caches.
*/
async loadGif(assetKey: string): Promise<GifSource> {
const existing = this.gifCache.get(assetKey);
if (existing) {
existing.refCount++;
return existing.source;
}
const url = this.urlForAsset(assetKey);
const source: GifSource = await Assets.load(url);
this.gifCache.set(assetKey, { source, url, refCount: 1 });
return source;
}
/**
* Release a reference to a GifSource. Only actually unloads
* when the last reference is released.
*/
releaseGif(assetKey: string): void {
const entry = this.gifCache.get(assetKey);
if (!entry) return;
entry.refCount--;
if (entry.refCount > 0) return;
this.gifCache.delete(assetKey);
try {
Assets.unload(entry.url);
} catch {
// ignore
}
}
/** Unload all cached textures and GIF sources. */
clear(): void {
for (const [, entry] of this.cache) {
try { Assets.unload(entry.url); } catch { /* ignore */ }
}
this.cache.clear();
for (const [, entry] of this.gifCache) {
try { Assets.unload(entry.url); } catch { /* ignore */ }
}
this.gifCache.clear();
}
}