UNPKG

@motion-core/motion-gpu

Version:

Framework-agnostic WebGPU runtime for fullscreen WGSL shaders with explicit Svelte, React, and Vue adapter entrypoints.

137 lines (136 loc) 4.09 kB
import { toMotionGPUErrorReport } from "../core/error-report.js"; import { createCurrentWritable } from "../core/current-value.js"; import { isAbortError, loadTexturesFromUrls } from "../core/texture-loader.js"; import { onDestroy, onMount } from "svelte"; //#region src/lib/svelte/use-texture.ts /** * Normalizes unknown thrown values to an `Error` instance. */ function toError(error) { if (error instanceof Error) return error; return /* @__PURE__ */ new Error("Unknown texture loading error"); } /** * Releases GPU-side resources for a list of loaded textures. */ function disposeTextures(list) { for (const texture of list ?? []) texture.dispose(); } function mergeAbortSignals(primary, secondary) { if (!secondary) return { signal: primary, dispose: () => {} }; if (typeof AbortSignal.any === "function") return { signal: AbortSignal.any([primary, secondary]), dispose: () => {} }; const fallback = new AbortController(); let disposed = false; const cleanup = () => { if (disposed) return; disposed = true; primary.removeEventListener("abort", abort); secondary.removeEventListener("abort", abort); }; const abort = () => fallback.abort(); primary.addEventListener("abort", abort, { once: true }); secondary.addEventListener("abort", abort, { once: true }); return { signal: fallback.signal, dispose: cleanup }; } /** * Loads textures from URLs and exposes reactive loading/error state. * * @param urlInput - URLs array or lazy URL provider. * @param optionsInput - Loader options object or lazy options provider. * @returns Reactive texture loading state with reload support. */ function useTexture(urlInput, optionsInput = {}) { const textures = createCurrentWritable(null); const loading = createCurrentWritable(true); const error = createCurrentWritable(null); const errorReport = createCurrentWritable(null); let disposed = false; let requestVersion = 0; let activeController = null; let runningLoad = null; let reloadQueued = false; const getUrls = typeof urlInput === "function" ? urlInput : () => urlInput; const getOptions = typeof optionsInput === "function" ? optionsInput : () => optionsInput; const executeLoad = async () => { if (disposed) return; const version = ++requestVersion; const controller = new AbortController(); activeController = controller; loading.set(true); error.set(null); errorReport.set(null); const previous = textures.current; const options = getOptions() ?? {}; const mergedSignal = mergeAbortSignals(controller.signal, options.signal); try { const loaded = await loadTexturesFromUrls(getUrls(), { ...options, signal: mergedSignal.signal }); if (disposed || version !== requestVersion) { disposeTextures(loaded); return; } textures.set(loaded); disposeTextures(previous); } catch (nextError) { if (disposed || version !== requestVersion) return; if (isAbortError(nextError)) return; disposeTextures(previous); textures.set(null); const normalizedError = toError(nextError); error.set(normalizedError); errorReport.set(toMotionGPUErrorReport(normalizedError, "initialization")); } finally { if (!disposed && version === requestVersion) loading.set(false); if (activeController === controller) activeController = null; mergedSignal.dispose(); } }; const runLoadLoop = async () => { do { reloadQueued = false; await executeLoad(); } while (reloadQueued && !disposed); }; const load = () => { activeController?.abort(); if (runningLoad) { reloadQueued = true; return runningLoad; } const trackedPending = runLoadLoop().finally(() => { if (runningLoad === trackedPending) runningLoad = null; }); runningLoad = trackedPending; return trackedPending; }; onMount(() => { load(); }); onDestroy(() => { disposed = true; requestVersion += 1; activeController?.abort(); disposeTextures(textures.current); }); return { textures, loading, error, errorReport, reload: load }; } //#endregion export { useTexture }; //# sourceMappingURL=use-texture.js.map