import { useEffect, useRef, useState } from 'react' /** Cached data URLs keyed by the original URL */ const dataUrlCache = new Map() /** URLs that failed to load (404 / network error) */ const failedUrls = new Set() /** In-flight fetch promises */ const inflight = new Map>() async function fetchAsDataUrl(url: string): Promise { try { const res = await fetch(url) if (!res.ok) return null const blob = await res.blob() return await new Promise((resolve, reject) => { const reader = new FileReader() reader.onload = () => resolve(reader.result as string) reader.onerror = () => reject(new Error('FileReader error')) reader.readAsDataURL(blob) }) } catch { return null } } function scheduleLoad(url: string): Promise { if (inflight.has(url)) return inflight.get(url)! const p = fetchAsDataUrl(url).then((dataUrl) => { inflight.delete(url) if (dataUrl) { dataUrlCache.set(url, dataUrl) } else { failedUrls.add(url) } }) inflight.set(url, p) return p } export function getCachedDataUrl(url: string): string | undefined { return dataUrlCache.get(url) } export function isTextureFailed(url: string): boolean { return failedUrls.has(url) } /** * Clear all cached texture data URLs and failed state. * Call after setOverride in bundledTexturesConfig so slots re-request textures. */ export function clearTextureCache(): void { dataUrlCache.clear() failedUrls.clear() inflight.clear() } /** * Hook that resolves a URL to a cached base64 data URL. * Returns: * - `string` — loaded (data URL) * - `null` — permanently failed (404 / network error) * - `undefined` — still loading */ export function useDataUrl(url: string | null): string | null | undefined { const urlRef = useRef(url) const [state, setState] = useState(() => { if (!url) return undefined const cached = dataUrlCache.get(url) if (cached !== undefined) return cached if (failedUrls.has(url)) return null return undefined }) useEffect(() => { urlRef.current = url if (!url) { setState(undefined) return } // Synchronous fast-path: already in cache const cached = dataUrlCache.get(url) if (cached !== undefined) { setState(cached) return } if (failedUrls.has(url)) { setState(null) return } // Fetch asynchronously setState(undefined) // mark loading scheduleLoad(url).then(() => { if (urlRef.current !== url) return // stale, ignore const result = dataUrlCache.get(url) setState(result !== undefined ? result : null) }) }, [url]) return state }