UNPKG

@motion-core/motion-gpu

Version:

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

146 lines (129 loc) 4.02 kB
import type { CurrentReadable } from '../core/current-value.js'; import { useMotionGPU, type MotionGPUUserNamespace } from './motiongpu-context.js'; /** * Internal shape of the user context store. */ type UserContextStore = Record<MotionGPUUserNamespace, unknown>; /** * Object-like context payload used by merge semantics. */ type UserContextEntry = Record<string, unknown>; /** * Controls how a namespaced user context value behaves when already present. */ export interface SetMotionGPUUserContextOptions { /** * Conflict strategy when namespace already exists: * - `skip`: keep current value * - `replace`: replace current value * - `merge`: shallow merge object values, fallback to replace otherwise * * @default 'skip' */ existing?: 'merge' | 'replace' | 'skip'; /** * How function inputs should be interpreted: * - `factory`: call function and store its return value * - `value`: store function itself * * @default 'factory' */ functionValue?: 'factory' | 'value'; } /** * Checks whether a value is a non-array object suitable for shallow merge. */ function isObjectEntry(value: unknown): value is UserContextEntry { return typeof value === 'object' && value !== null && !Array.isArray(value); } /** * Returns a read-only view of the entire motiongpu user context store. */ export function useMotionGPUUserContext< UC extends UserContextStore = UserContextStore >(): CurrentReadable<UC>; /** * Reads a namespaced user context value as a reactive readable store. */ export function useMotionGPUUserContext< UC extends UserContextStore = UserContextStore, K extends keyof UC & MotionGPUUserNamespace = keyof UC & MotionGPUUserNamespace >(namespace: K): CurrentReadable<UC[K] | undefined>; /** * Read-only user context hook: * - no args: returns full user context store * - namespace: returns namespaced store view * * @param namespace - Optional namespace key. */ export function useMotionGPUUserContext< UC extends UserContextStore = UserContextStore, K extends keyof UC & MotionGPUUserNamespace = keyof UC & MotionGPUUserNamespace >(namespace?: K): CurrentReadable<UC> | CurrentReadable<UC[K] | undefined> { const userStore = useMotionGPU().user; if (namespace === undefined) { const allStore: CurrentReadable<UC> = { get current() { return userStore.current as UC; }, subscribe(run) { return userStore.subscribe((context) => run(context as UC)); } }; return allStore; } const scopedStore: CurrentReadable<UC[K] | undefined> = { get current() { return userStore.current[namespace] as UC[K] | undefined; }, subscribe(run) { return userStore.subscribe((context) => run(context[namespace] as UC[K] | undefined)); } }; return scopedStore; } /** * Sets a namespaced user context value with explicit write semantics. * * Returns the effective value stored under the namespace. */ export function setMotionGPUUserContext<UCT = unknown>( namespace: MotionGPUUserNamespace, value: UCT | (() => UCT), options?: SetMotionGPUUserContextOptions ): UCT | undefined { const userStore = useMotionGPU().user; const mode = options?.existing ?? 'skip'; const functionValueMode = options?.functionValue ?? 'factory'; let resolvedValue: UCT | undefined; userStore.update((context) => { const hasExisting = namespace in context; if (hasExisting && mode === 'skip') { resolvedValue = context[namespace] as UCT | undefined; return context; } const nextValue = typeof value === 'function' && functionValueMode === 'factory' ? (value as () => UCT)() : (value as UCT); if (hasExisting && mode === 'merge') { const currentValue = context[namespace]; if (isObjectEntry(currentValue) && isObjectEntry(nextValue)) { resolvedValue = { ...currentValue, ...nextValue } as UCT; return { ...context, [namespace]: resolvedValue }; } } resolvedValue = nextValue; return { ...context, [namespace]: nextValue }; }); return resolvedValue; }