UNPKG

@motion-core/motion-gpu

Version:

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

92 lines (91 loc) 3.4 kB
import { createCurrentWritable } from "../core/current-value.js"; import { useMotionGPU } from "./motiongpu-context.js"; import { createContext, useContext, useEffect, useRef } from "react"; //#region src/lib/react/frame-context.ts /** * Placeholder stage used before a frame task registration becomes available. */ var PENDING_STAGE_KEY = Symbol("motiongpu-react-pending-stage"); /** * React context container for the active frame registry. */ var FrameRegistryReactContext = createContext(null); /** * Registers a callback in the active frame registry and auto-unsubscribes on unmount. * * @param keyOrCallback - Task key or callback for auto-key registration. * @param callbackOrOptions - Callback (keyed overload) or options (auto-key overload). * @param maybeOptions - Optional registration options for keyed overload. * Registration key/options are frozen on first render; subsequent renders do not re-register. * @returns Registration control API with task, start/stop controls and started state. * @throws {Error} When called outside `<FragCanvas>`. * @throws {Error} When callback is missing in keyed overload. */ function useFrame(keyOrCallback, callbackOrOptions, maybeOptions) { const registry = useContext(FrameRegistryReactContext); if (!registry) throw new Error("useFrame must be used inside <FragCanvas>"); const motiongpu = useMotionGPU(); const resolved = typeof keyOrCallback === "function" ? { key: void 0, callback: keyOrCallback, options: callbackOrOptions } : { key: keyOrCallback, callback: callbackOrOptions, options: maybeOptions }; if (typeof resolved.callback !== "function") throw new Error("useFrame requires a callback"); const callbackRef = useRef(resolved.callback); callbackRef.current = resolved.callback; const registrationConfigRef = useRef(null); if (!registrationConfigRef.current) registrationConfigRef.current = { key: resolved.key, options: resolved.options }; const registrationConfig = registrationConfigRef.current; const registrationRef = useRef(null); const taskRef = useRef({ key: registrationConfig.key !== void 0 ? registrationConfig.key : Symbol("motiongpu-react-pending-task-key"), stage: PENDING_STAGE_KEY }); const startedStore = useRef(createCurrentWritable(false)).current; useEffect(() => { const wrappedCallback = (state) => { callbackRef.current(state); }; const registration = registrationConfig.key === void 0 ? registry.register(wrappedCallback, registrationConfig.options) : registry.register(registrationConfig.key, wrappedCallback, registrationConfig.options); registrationRef.current = registration; taskRef.current = registration.task; const unsubscribeStarted = registration.started.subscribe((value) => { startedStore.set(value); }); return () => { unsubscribeStarted(); registration.unsubscribe(); if (registrationRef.current === registration) registrationRef.current = null; startedStore.set(false); }; }, [ registrationConfig, registry, startedStore ]); useEffect(() => { motiongpu.invalidate(); }, [motiongpu, resolved.callback]); return { get task() { return taskRef.current; }, start: () => { registrationRef.current?.start(); }, stop: () => { registrationRef.current?.stop(); }, started: startedStore }; } //#endregion export { FrameRegistryReactContext, useFrame }; //# sourceMappingURL=frame-context.js.map