diff --git a/apps/typegpu-docs/astro.config.mjs b/apps/typegpu-docs/astro.config.mjs index 34d9785799..3fd3ed189f 100644 --- a/apps/typegpu-docs/astro.config.mjs +++ b/apps/typegpu-docs/astro.config.mjs @@ -243,6 +243,11 @@ export default defineConfig({ label: 'React Native', slug: 'integration/react-native', }, + { + label: 'React Native Worklets', + slug: 'integration/react-native/worklets', + badge: { text: 'experimental', variant: 'caution' }, + }, { label: 'WESL Interoperability', slug: 'integration/wesl-interoperability', diff --git a/apps/typegpu-docs/src/content/docs/integration/react-native/worklets.mdx b/apps/typegpu-docs/src/content/docs/integration/react-native/worklets.mdx new file mode 100644 index 0000000000..90fa936976 --- /dev/null +++ b/apps/typegpu-docs/src/content/docs/integration/react-native/worklets.mdx @@ -0,0 +1,121 @@ +--- +title: React Native Worklets +description: A guide on running TypeGPU render loops on the UI thread with react-native-worklets. +--- + +With [react-native-worklets](https://docs.swmansion.com/react-native-worklets/), per-frame GPU work can be scheduled on the UI thread, unaffected by tasks running on the default React Native thread (or RN thread). +TypeGPU resources created on the RN thread can be captured by worklets directly, they are transferred between runtimes automatically. + +## Setup + +Follow the [React Native guide](/TypeGPU/integration/react-native/) first, then install `react-native-worklets` and enable its babel plugin with [Bundle Mode](https://docs.swmansion.com/react-native-worklets/docs/bundleMode/): + +```diff lang=js title="babel.config.js" ++const workletsPluginOptions = { ++ bundleMode: true, ++ importForwarding: { ++ moduleNames: ['typegpu'], ++ // Directories with your module-scope shader definitions ++ relativePaths: ['my-app/components'], ++ }, ++}; + +module.exports = (api) => { + api.cache(true); + return { + presets: ['babel-preset-expo'], + plugins: [ + 'unplugin-typegpu/babel', ++ ['react-native-worklets/plugin', workletsPluginOptions], + ], + }; +}; +``` + +No extra imports are needed - `@typegpu/react` detects `react-native-worklets` at runtime and registers the transfer support for TypeGPU resources automatically. +`useFrame` runs its callback on the UI thread whenever the callback is marked with the `'worklet'` directive; plain callbacks keep running on the RN thread. + +After changing the babel config, clear the Metro cache with `npx expo start --clear`. + +## Example + +Create resources on the RN thread, then use them freely inside a `useFrame` worklet: + +```tsx +import { useMemo } from 'react'; +import { Canvas } from 'react-native-webgpu'; +import tgpu, { common, d } from 'typegpu'; +import { useConfigureContext, useFrame, useRoot, useUniform } from '@typegpu/react'; + +export function Pulse() { + const root = useRoot(); + const color = useUniform(d.vec3f, { initial: d.vec3f(0.114, 0.447, 0.941) }); + + const pipeline = useMemo( + () => + root.createRenderPipeline({ + vertex: common.fullScreenTriangle, + fragment: () => { + 'use gpu'; + return d.vec4f(color.$, 1); + }, + }), + [root, color], + ); + + const { ref, ctxRef } = useConfigureContext({ alphaMode: 'premultiplied' }); + + // Runs each frame on the UI thread + useFrame(({ elapsedSeconds }) => { + 'worklet'; + const ctx = ctxRef.current; + if (!ctx) return; + + color.write(d.vec3f(0.5 + Math.sin(elapsedSeconds) * 0.5, 0.447, 0.941)); + pipeline.withColorAttachment({ view: ctx }).draw(3); + ctx.present?.(); + }); + + return ; +} +``` + +The `color` uniform and `pipeline` captured by the worklet are transferred to the UI runtime on first use. +Both runtimes share the same underlying GPU objects, and transferring the same resource again yields the same object back. + +This works for buffers (including `createUniform`/`createMutable`/`createReadonly`), textures, samplers, bind groups and their layouts, vertex layouts, query sets, pipelines, roots, slots, accessors, consts, data schemas, and vector/matrix instances. + +## Opting out + +To keep everything on the RN thread even with `react-native-worklets` installed, pass `disableWorklets` to the `Root` provider: + +```tsx + + + +``` + +`useFrame` then runs its callbacks on the RN thread, and `useConfigureContext` returns a plain object of the same shape, so the code above keeps working unchanged. + +## Rules of transfer + +**Definitions are runtime-local.** +Shader functions (`tgpu.fn`, entry functions) and `tgpu.comptime` cannot be serialized. +Make sure to create them on the runtime they will be used on, or keep them at module scope in files covered by `importForwarding`; worklets then re-import them natively on the UI runtime instead of transferring them. + +**Functions crossing runtimes must be worklets.** +Any plain function reachable from a transferred resource (e.g. a `withPerformanceCallback` callback) has to be marked with the `'worklet'` directive. + +**Attachments and passes stay on their runtime.** +A pipeline carrying a color attachment, a depth-stencil attachment, or a bound pass or command encoder throws when transferred, since those hold views and encoders local to the runtime that made them. +Transfer the bare pipeline and apply them on the other side, as the example above does with `withColorAttachment`. + +:::note +Definitions created dynamically (inside components or worklets) cannot cross runtimes and throw. +If you create pipelines on the UI thread, memoize them - resolving a pipeline every frame is wasted work. +::: + +:::caution +Render pipelines created on the UI runtime must specify an explicit target format (e.g. `targets: { format: 'bgra8unorm' }`), the default relies on +`navigator.gpu.getPreferredCanvasFormat()`, and `navigator` is not available on worklet runtimes by default. +::: diff --git a/packages/typegpu-react/README.md b/packages/typegpu-react/README.md index bb7eb5d3ea..9ec734cdd2 100644 --- a/packages/typegpu-react/README.md +++ b/packages/typegpu-react/README.md @@ -46,6 +46,12 @@ const App = (props: Props) => { }; ``` +# React Native + +When `react-native-worklets` is installed, per-frame GPU work can run on the UI thread - `useFrame` picks it up automatically for worklet callbacks. +TypeGPU resources captured by worklets are transferred between runtimes automatically. +See the [React Native Worklets guide](https://typegpu.com/integration/react-native/worklets). + ## TypeGPU is created by Software Mansion [![swm](https://logo.swmansion.com/logo?color=white&variant=desktop&width=150&tag=typegpu-github 'Software Mansion')](https://swmansion.com) diff --git a/packages/typegpu-react/package.json b/packages/typegpu-react/package.json index 46ed2a9291..383aaf7ad4 100644 --- a/packages/typegpu-react/package.json +++ b/packages/typegpu-react/package.json @@ -17,7 +17,11 @@ "sideEffects": false, "types": "./src/browser/index.ts", "exports": { - ".": "./src/browser/index.ts", + ".": { + "react-native": "./src/react-native/index.ts", + "browser": "./src/browser/index.ts", + "default": "./src/browser/index.ts" + }, "./package.json": "./package.json" }, "publishConfig": { @@ -53,6 +57,7 @@ "react": "catalog:", "react-dom": "catalog:", "react-native": "0.84.1", + "react-native-worklets": "0.10.2", "tsdown": "catalog:build", "typegpu": "workspace:*", "typegpu-testing-utility": "workspace:^", @@ -63,6 +68,7 @@ "react": "^19.0.0", "react-native": "*", "react-native-webgpu": "*", + "react-native-worklets": "*", "typegpu": "workspace:^" }, "peerDependenciesMeta": { @@ -71,6 +77,9 @@ }, "react-native-webgpu": { "optional": true + }, + "react-native-worklets": { + "optional": true } } } diff --git a/packages/typegpu-react/src/core/root-context.tsx b/packages/typegpu-react/src/core/root-context.tsx index 790638a6f8..cf802342fd 100644 --- a/packages/typegpu-react/src/core/root-context.tsx +++ b/packages/typegpu-react/src/core/root-context.tsx @@ -9,7 +9,7 @@ import React, { useRef, useState, } from 'react'; -import { tgpu, type TgpuRoot } from 'typegpu'; +import { tgpu, type InitOptions, type TgpuRoot } from 'typegpu'; import { useDeferredCleanup } from './helper-hooks.ts'; import { useBailOnServer } from './use-bail-on-server.ts'; @@ -83,6 +83,11 @@ interface RootContext { class OwnRootContext implements RootContext { #result: RootContextResult | undefined; #destroyed: boolean = false; + readonly #options: InitOptions | undefined; + + constructor(options?: InitOptions) { + this.#options = options; + } initOrGetRoot(): RootContextResult { if (this.#destroyed) { @@ -91,7 +96,7 @@ class OwnRootContext implements RootContext { } if (!this.#result) { - const promise = tgpu.init().then( + const promise = tgpu.init(this.#options).then( (root) => { if (this.#destroyed) { root.destroy(); @@ -156,7 +161,16 @@ const globalRootContextValue = new OwnRootContext(); const rootContext = createContext(null); +const workletsDisabledContext = createContext(false); + +/** @internal Reads the `disableWorklets` flag from the nearest provider */ +export function useWorkletsDisabled(): boolean { + return useContext(workletsDisabledContext); +} + export interface RootProps { + /** Options used when this provider creates its own root, ignored when `root` is provided */ + options?: InitOptions | undefined; /** * An existing root to provide. If undefined (default), a new root will be initialized for * this provider's children. @@ -164,6 +178,13 @@ export interface RootProps { * @default undefined */ root?: TgpuRoot | undefined; + /** + * (React Native only) When true, `useFrame` runs on the RN thread even if + * `react-native-worklets` is installed. Ignored on the web + * + * @default false + */ + disableWorklets?: boolean | undefined; children?: ReactNode | undefined; } @@ -183,8 +204,8 @@ function WarnSuspense() { return null; } -export const Root = ({ children, root }: RootProps) => { - const [ownCtx] = useState(() => new OwnRootContext()); +export const Root = ({ children, options, root, disableWorklets = false }: RootProps) => { + const [ownCtx] = useState(() => new OwnRootContext(options)); const existingRootCtx = useMemo(() => { if (root) { return new ExistingRootContext(root); @@ -198,7 +219,9 @@ export const Root = ({ children, root }: RootProps) => { return ( - }>{children} + + }>{children} + ); }; diff --git a/packages/typegpu-react/src/core/use-frame.ts b/packages/typegpu-react/src/core/use-frame.ts index c47498c729..d69e93871f 100644 --- a/packages/typegpu-react/src/core/use-frame.ts +++ b/packages/typegpu-react/src/core/use-frame.ts @@ -1,6 +1,6 @@ import { useEffect, useRef } from 'react'; -interface FrameCtx { +export interface FrameCtx { /** * Time elapsed since the last frame */ @@ -11,6 +11,35 @@ interface FrameCtx { readonly elapsedSeconds: number; } +export function startFrameLoop(cb: (ctx: FrameCtx) => void): () => void { + let frameId: number | undefined; + let startTime: number | undefined; + let lastTime: number | undefined; + + const loop = () => { + frameId = requestAnimationFrame(loop); + + const now = performance.now(); + if (lastTime === undefined || startTime === undefined) { + startTime = now; + lastTime = now; + } + cb({ + deltaSeconds: (now - lastTime) / 1000, + elapsedSeconds: (now - startTime) / 1000, + }); + lastTime = now; + }; + + loop(); + + return () => { + if (frameId !== undefined) { + cancelAnimationFrame(frameId); + } + }; +} + export function useFrame(cb: (ctx: FrameCtx) => void) { const latestCb = useRef(cb); @@ -18,32 +47,5 @@ export function useFrame(cb: (ctx: FrameCtx) => void) { latestCb.current = cb; }, [cb]); - useEffect(() => { - let frameId: number | undefined; - let startTime: number | undefined; - let lastTime: number | undefined; - - const loop = () => { - frameId = requestAnimationFrame(loop); - - const now = performance.now(); - if (lastTime === undefined || startTime === undefined) { - startTime = now; - lastTime = now; - } - latestCb.current({ - deltaSeconds: (now - lastTime) / 1000, - elapsedSeconds: (now - startTime) / 1000, - }); - lastTime = now; - }; - - loop(); - - return () => { - if (frameId !== undefined) { - cancelAnimationFrame(frameId); - } - }; - }, []); + useEffect(() => startFrameLoop((ctx) => latestCb.current(ctx)), []); } diff --git a/packages/typegpu-react/src/react-native/index.ts b/packages/typegpu-react/src/react-native/index.ts index bfb514da65..2e6c3ce100 100644 --- a/packages/typegpu-react/src/react-native/index.ts +++ b/packages/typegpu-react/src/react-native/index.ts @@ -1,8 +1,14 @@ import { WebGPUModule } from 'react-native-webgpu'; +import { registerTypegpuReactSerializables } from './serialization/register-serializables.ts'; + // Making sure the WebGPU module is installed before navigator.gpu is accessed WebGPUModule.install(); +// No-ops when react-native-worklets is not installed +registerTypegpuReactSerializables(); export * from '../shared-exports.ts'; -export { useConfigureContext } from './use-configure-context.ts'; +// Intentionally shadows the browser `useFrame`, this one can run the frame loop on the UI runtime +export { useFrame } from './use-frame.ts'; +export { useConfigureContext } from './use-configure-worklet-context.ts'; diff --git a/packages/typegpu-react/src/react-native/serialization/register-serializables.ts b/packages/typegpu-react/src/react-native/serialization/register-serializables.ts new file mode 100644 index 0000000000..5c184e6e12 --- /dev/null +++ b/packages/typegpu-react/src/react-native/serialization/register-serializables.ts @@ -0,0 +1,91 @@ +import { installWebGPU } from 'react-native-webgpu'; +import { + isNonTransferableResource, + isSnapshotableResource, + restoreResource, + snapshotResource, + type TgpuResourceSnapshot, +} from 'typegpu/~internal'; +import { + cacheTransferredResource, + getCachedTransferredResource, + getOrCreateTransferId, + getTransferredRoot, +} from './transfer-cache.ts'; +import { getWorkletsModule } from '../worklets-integration.ts'; + +export type PackedTgpuResource = { + id: number; + snapshot: TgpuResourceSnapshot; +}; + +let registered = false; + +export function registerTypegpuReactSerializables(): void { + if (registered) { + return; + } + const worklets = getWorkletsModule(); + if (!worklets) { + return; + } + registered = true; + + worklets.registerCustomSerializable({ + name: 'TypeGPU', + determine(value: object): value is object { + 'worklet'; + // Non-transferable TypeGPU objects are claimed too, so pack() fails loudly + return isSnapshotableResource(value) || isNonTransferableResource(value); + }, + pack(value: object): PackedTgpuResource { + 'worklet'; + const snapshot = snapshotResource(value); + if (!snapshot) { + const resourceType = (value as { resourceType?: string }).resourceType ?? 'unknown'; + throw new Error( + `[typegpu-react] TypeGPU object '${resourceType}' cannot be transferred to a worklet. ` + + 'Definitions (functions, comptime, derived) are runtime-local: import them from a module ' + + 'covered by importForwarding, or build pipelines on the RN thread and transfer the result.', + ); + } + for (const [key, field] of Object.entries(snapshot)) { + // Inlined isWorkletFunction, the lazily resolved module cannot be captured in a worklet + if ( + typeof field === 'function' && + !(field as { __workletHash?: unknown }).__workletHash && + !(field as { __bundleData?: unknown }).__bundleData + ) { + throw new Error( + `[typegpu-react] Cannot transfer '${snapshot.type}': its '${key}' is a plain function. ` + + "Only worklets can cross runtimes - mark it with 'worklet'. If it is a schema or " + + 'TypeGPU definition, it cannot be transferred yet.', + ); + } + } + return { id: getOrCreateTransferId(value), snapshot }; + }, + unpack(payload: PackedTgpuResource): object { + 'worklet'; + try { + const cached = getCachedTransferredResource(payload.id); + if (cached) { + return cached; + } + + installWebGPU(); + const resource = restoreResource(payload.snapshot, { + getRoot: getTransferredRoot, + }) as object; + cacheTransferredResource(payload.id, resource); + return resource; + } catch (err) { + const details = err instanceof Error ? (err.stack ?? err.message) : String(err); + throw new Error( + `[typegpu-react] Failed to restore '${payload?.snapshot?.type}' (id ${payload?.id}). Cause: ${details}`, + { cause: err }, + ); + } + }, + }); +} diff --git a/packages/typegpu-react/src/react-native/serialization/transfer-cache.ts b/packages/typegpu-react/src/react-native/serialization/transfer-cache.ts new file mode 100644 index 0000000000..1cb03c8551 --- /dev/null +++ b/packages/typegpu-react/src/react-native/serialization/transfer-cache.ts @@ -0,0 +1,117 @@ +import { tgpu, type TgpuRoot } from 'typegpu'; + +export type TransferredResourceRef = { + deref(): object | undefined; +}; + +type ResourceWeakRefConstructor = new ( + target: T, +) => { + deref(): T | undefined; +}; + +type WeakRefGlobals = { + WeakRef?: ResourceWeakRefConstructor; +}; + +type ResourceFinalizationRegistry = { + register(target: object, heldValue: number): void; +}; + +type ResourceFinalizationRegistryConstructor = new ( + cleanup: (heldValue: number) => void, +) => ResourceFinalizationRegistry; + +type TypegpuReactTransferGlobals = typeof globalThis & { + __TYPEGPU_REACT_NEXT_TRANSFER_ID__?: number; + __TYPEGPU_REACT_TRANSFER_IDS__?: WeakMap; + __TYPEGPU_REACT_TRANSFERRED_RESOURCES__?: Map; + __TYPEGPU_REACT_TRANSFER_CACHE_CLEANUP__?: ResourceFinalizationRegistry; + __TYPEGPU_REACT_STRONG_TRANSFER_CACHE_WARNING_SHOWN__?: boolean; + __TYPEGPU_REACT_ROOTS__?: WeakMap; +}; + +export function getTransferredRoot(device: GPUDevice): TgpuRoot { + 'worklet'; + const global = globalThis as TypegpuReactTransferGlobals; + const roots = (global.__TYPEGPU_REACT_ROOTS__ ??= new WeakMap()); + let root = roots.get(device); + if (!root) { + root = tgpu.initFromDevice({ device }); + roots.set(device, root); + } + return root; +} + +export function getOrCreateTransferId(value: object): number { + 'worklet'; + const global = globalThis as TypegpuReactTransferGlobals; + const ids = (global.__TYPEGPU_REACT_TRANSFER_IDS__ ??= new WeakMap()); + let id = ids.get(value); + if (id === undefined) { + id = global.__TYPEGPU_REACT_NEXT_TRANSFER_ID__ ?? 0; + global.__TYPEGPU_REACT_NEXT_TRANSFER_ID__ = id + 1; + ids.set(value, id); + } + return id; +} + +export function getTransferredResourceCache(): Map { + 'worklet'; + const global = globalThis as TypegpuReactTransferGlobals; + return (global.__TYPEGPU_REACT_TRANSFERRED_RESOURCES__ ??= new Map()); +} + +export function getCachedTransferredResource(id: number): object | undefined { + 'worklet'; + return getTransferredResourceCache().get(id)?.deref(); +} + +function getWeakRef(): ResourceWeakRefConstructor | undefined { + 'worklet'; + return (globalThis as unknown as WeakRefGlobals).WeakRef; +} + +export function createTransferredResourceRef(resource: object): TransferredResourceRef { + 'worklet'; + const WeakRefCtor = getWeakRef(); + if (WeakRefCtor) { + return new WeakRefCtor(resource); + } + + const global = globalThis as TypegpuReactTransferGlobals; + if (!global.__TYPEGPU_REACT_STRONG_TRANSFER_CACHE_WARNING_SHOWN__) { + global.__TYPEGPU_REACT_STRONG_TRANSFER_CACHE_WARNING_SHOWN__ = true; + console.warn( + 'WeakRef is not available in this worklet runtime. TypeGPU transferred resources will use a strong identity cache.', + ); + } + + return { deref: () => resource }; +} + +function getCacheCleanupRegistry(): ResourceFinalizationRegistry | undefined { + 'worklet'; + const FinalizationRegistryCtor = ( + globalThis as { FinalizationRegistry?: ResourceFinalizationRegistryConstructor } + ).FinalizationRegistry; + if (!FinalizationRegistryCtor) { + return undefined; + } + const global = globalThis as TypegpuReactTransferGlobals; + return (global.__TYPEGPU_REACT_TRANSFER_CACHE_CLEANUP__ ??= new FinalizationRegistryCtor((id) => { + const cache = getTransferredResourceCache(); + // The id may have been repopulated with a live resource in the meantime + if (cache.get(id)?.deref() === undefined) { + cache.delete(id); + } + })); +} + +// The worklets babel plugin turns workletized declarations into `const`s initialized in source +// order, so functions captured by worklets here must be declared above their dependents +export function cacheTransferredResource(id: number, resource: object): void { + 'worklet'; + getTransferredResourceCache().set(id, createTransferredResourceRef(resource)); + getCacheCleanupRegistry()?.register(resource, id); +} diff --git a/packages/typegpu-react/src/react-native/use-configure-worklet-context.ts b/packages/typegpu-react/src/react-native/use-configure-worklet-context.ts new file mode 100644 index 0000000000..6c2fbb8dbf --- /dev/null +++ b/packages/typegpu-react/src/react-native/use-configure-worklet-context.ts @@ -0,0 +1,91 @@ +import { useEffect, useRef, type RefObject } from 'react'; +import type { Shareable } from 'react-native-worklets'; + +import { useWorkletsDisabled } from '../core/root-context.tsx'; +import type { + UseConfigureContextOptions, + UseConfigureContextResult, +} from '../core/use-configure-context.ts'; +import { useConfigureContext as useRNConfigureContext } from './use-configure-context.ts'; +import { getWorkletsModule } from './worklets-integration.ts'; + +type CanvasContext = GPUCanvasContext & { present?: () => void }; + +type ShareableContext = Shareable< + CanvasContext | null, + RefObject, + RefObject +>; + +function createShareableCtx(workletsDisabled: boolean): ShareableContext { + const worklets = workletsDisabled ? null : getWorkletsModule(); + + if (!worklets) { + return { + value: null as CanvasContext | null, + get current() { + return this.value as CanvasContext | null; + }, + setSync(value: CanvasContext | null) { + this.value = value; + }, + } as ShareableContext; + } + + return worklets.createShareable< + CanvasContext | null, + RefObject, + RefObject + >(worklets.UIRuntimeId, null, { + initSynchronously: true, + hostDecorator(shareable) { + 'worklet'; + Object.defineProperty(shareable, 'current', { + get() { + return shareable.value; + }, + enumerable: true, + }); + return shareable; + }, + guestDecorator(shareable) { + 'worklet'; + Object.defineProperty(shareable, 'current', { + get() { + throw new Error( + `Result of useConfigureContext() is only available on the UI thread. If you'd like to disable worklet support, wrap your component in ...`, + ); + }, + enumerable: true, + }); + return shareable; + }, + }); +} + +/** + * Same as `useConfigureContext`, but exposes the canvas context through a ref readable + * on the UI runtime, or a same-shape JS-thread object when worklets are unavailable or + * disabled. The choice is fixed on mount, flipping `disableWorklets` requires a remount + */ +export function useConfigureContext( + options?: UseConfigureContextOptions, +): UseConfigureContextResult { + const result = useRNConfigureContext(options); + const workletsDisabled = useWorkletsDisabled(); + const shareableCtxRef = useRef(undefined); + + const shareableCtx = (shareableCtxRef.current ??= createShareableCtx(workletsDisabled)); + + useEffect(() => { + shareableCtx.setSync?.(result.ctxRef.current); + }); + + useEffect(() => { + return () => { + shareableCtx.setSync?.(null); + }; + }, [shareableCtx]); + + return { ref: result.ref, ctxRef: shareableCtx }; +} diff --git a/packages/typegpu-react/src/react-native/use-frame.ts b/packages/typegpu-react/src/react-native/use-frame.ts new file mode 100644 index 0000000000..0caaa0901c --- /dev/null +++ b/packages/typegpu-react/src/react-native/use-frame.ts @@ -0,0 +1,78 @@ +import { useEffect, useRef } from 'react'; + +import { useWorkletsDisabled } from '../core/root-context.tsx'; +import { type FrameCtx, startFrameLoop } from '../core/use-frame.ts'; +import { getWorkletsModule } from './worklets-integration.ts'; + +type FrameCallback = (ctx: FrameCtx) => void; +type FrameCallbackRef = { current: FrameCallback }; +type UiValue = { + value: T; + setSync(value: T | ((prev: T) => T)): void; +}; + +/** + * Runs the frame loop on the UI runtime when the callback is a worklet and + * `react-native-worklets` is available, on the RN thread otherwise + */ +export function useFrame(cb: FrameCallback) { + const workletsDisabled = useWorkletsDisabled(); + const worklets = workletsDisabled ? null : getWorkletsModule(); + const runOnUI = worklets !== null && worklets.isWorkletFunction(cb); + + const latestCb = useRef(cb); + const uiCbRef = useRef | undefined>(undefined); + + useEffect(() => { + latestCb.current = cb; + uiCbRef.current?.setSync({ current: cb }); + }, [cb]); + + useEffect(() => { + if (!runOnUI || !worklets) { + return startFrameLoop((ctx) => latestCb.current(ctx)); + } + + const { runOnUISync, createShareable, UIRuntimeId } = worklets; + const cbRef = createShareable( + UIRuntimeId, + { current: latestCb.current }, + { initSynchronously: true }, + ) as UiValue; + const frameId = createShareable(UIRuntimeId, undefined) as UiValue; + uiCbRef.current = cbRef; + + runOnUISync(() => { + 'worklet'; + let startTime: number | undefined; + let lastTime: number | undefined; + + function loop(timestamp?: number) { + frameId.value = requestAnimationFrame(loop); + + const now = timestamp ?? performance.now(); + if (lastTime === undefined || startTime === undefined) { + startTime = now; + lastTime = now; + } + cbRef.value.current({ + deltaSeconds: (now - lastTime) / 1000, + elapsedSeconds: (now - startTime) / 1000, + }); + lastTime = now; + } + + loop(); + }); + + return () => { + uiCbRef.current = undefined; + runOnUISync(() => { + 'worklet'; + if (frameId.value !== undefined) { + cancelAnimationFrame(frameId.value); + } + }); + }; + }, [runOnUI, worklets]); +} diff --git a/packages/typegpu-react/src/react-native/worklets-integration.ts b/packages/typegpu-react/src/react-native/worklets-integration.ts new file mode 100644 index 0000000000..e77b52f39e --- /dev/null +++ b/packages/typegpu-react/src/react-native/worklets-integration.ts @@ -0,0 +1,25 @@ +type WorkletsModule = typeof import('react-native-worklets'); + +declare const require: (id: string) => unknown; + +let cached: WorkletsModule | null | undefined; + +/** Returns `react-native-worklets` when installed and recent enough, null otherwise */ +export function getWorkletsModule(): WorkletsModule | null { + if (cached === undefined) { + try { + // Metro treats a require inside `try` as optional, apps without the package still bundle + const worklets = require('react-native-worklets') as WorkletsModule; + cached = + typeof worklets?.registerCustomSerializable === 'function' && + typeof worklets.isWorkletFunction === 'function' && + typeof worklets.runOnUISync === 'function' && + typeof worklets.createShareable === 'function' + ? worklets + : null; + } catch { + cached = null; + } + } + return cached; +} diff --git a/packages/typegpu-react/tests/react-native/register-serializables.test.ts b/packages/typegpu-react/tests/react-native/register-serializables.test.ts new file mode 100644 index 0000000000..9bc1d1fd26 --- /dev/null +++ b/packages/typegpu-react/tests/react-native/register-serializables.test.ts @@ -0,0 +1,85 @@ +import { it } from 'typegpu-testing-utility'; +import { tgpu, d } from 'typegpu'; +import { describe, expect, vi } from 'vitest'; +import { registerTypegpuReactSerializables } from '../../src/react-native/serialization/register-serializables.ts'; + +const { registerCustomSerializable } = vi.hoisted(() => ({ + registerCustomSerializable: vi.fn(), +})); + +vi.mock('react-native-webgpu', () => ({ installWebGPU: vi.fn() })); +vi.mock('../../src/react-native/worklets-integration.ts', () => ({ + getWorkletsModule: () => ({ registerCustomSerializable }), +})); + +type Serializer = { + determine(value: object): boolean; + pack(value: object): object; + unpack(value: object): object; +}; + +function getSerializer(): Serializer { + registerTypegpuReactSerializables(); + + const serializer = vi.mocked(registerCustomSerializable).mock.calls[0]?.[0] as + | Serializer + | undefined; + if (!serializer) { + throw new Error('TypeGPU serializer was not registered.'); + } + return serializer; +} + +describe('react-native serializable registration', () => { + it('round-trips buffers end to end', ({ root }) => { + const serializer = getSerializer(); + const buffer = root.createBuffer(d.arrayOf(d.u32, 3)).$usage('storage'); + const rawBuffer = root.unwrap(buffer); + + expect(serializer.determine(buffer)).toBe(true); + + const restored = serializer.unpack(serializer.pack(buffer)) as typeof buffer; + expect(restored.usableAsStorage).toBe(true); + expect(root.unwrap(restored)).toBe(rawBuffer); + }); + + it('round-trips roots by device identity', ({ root }) => { + const serializer = getSerializer(); + + expect(serializer.determine(root)).toBe(true); + + const restored = serializer.unpack(serializer.pack(root)) as typeof root; + expect(restored.resourceType).toBe('root'); + expect(restored.device).toBe(root.device); + expect(serializer.unpack(serializer.pack(root))).toBe(restored); + }); + + it('fails loudly for non-transferable TypeGPU objects', ({ root }) => { + const serializer = getSerializer(); + const view = root + .createTexture({ size: [2, 2], format: 'rgba8unorm' }) + .$usage('sampled') + .createView(); + + expect(serializer.determine(view)).toBe(true); + expect(() => serializer.pack(view)).toThrowErrorMatchingInlineSnapshot( + `[Error: [typegpu-react] TypeGPU object 'texture-view' cannot be transferred to a worklet. Definitions (functions, comptime, derived) are runtime-local: import them from a module covered by importForwarding, or build pipelines on the RN thread and transfer the result.]`, + ); + }); + + it('rejects plain-function performance callbacks', ({ root }) => { + const serializer = getSerializer(); + const pipeline = root + .createComputePipeline({ + compute: tgpu.computeFn({ workgroupSize: [1] })(() => { + 'use gpu'; + }), + }) + .withTimestampWrites({ querySet: root.createQuerySet('timestamp', 2) }) + .withPerformanceCallback(vi.fn()); + + expect(() => serializer.pack(pipeline)).toThrowErrorMatchingInlineSnapshot( + `[Error: [typegpu-react] Cannot transfer 'compute-pipeline': its 'performanceCallback' is a plain function. Only worklets can cross runtimes - mark it with 'worklet'. If it is a schema or TypeGPU definition, it cannot be transferred yet.]`, + ); + }); +}); diff --git a/packages/typegpu-react/tests/react-native/use-frame.test.tsx b/packages/typegpu-react/tests/react-native/use-frame.test.tsx new file mode 100644 index 0000000000..9f9c197e5e --- /dev/null +++ b/packages/typegpu-react/tests/react-native/use-frame.test.tsx @@ -0,0 +1,58 @@ +import { render } from '@testing-library/react'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { useFrame } from '../../src/react-native/use-frame.ts'; + +const holder = vi.hoisted(() => ({ + worklets: null as object | null, +})); + +vi.mock('../../src/react-native/worklets-integration.ts', () => ({ + getWorkletsModule: () => holder.worklets, +})); + +function FrameUser({ cb }: { cb: (ctx: unknown) => void }) { + useFrame(cb); + return null; +} + +describe('react-native useFrame dispatch', () => { + beforeEach(() => { + vi.stubGlobal( + 'requestAnimationFrame', + vi.fn(() => 1), + ); + vi.stubGlobal('cancelAnimationFrame', vi.fn()); + }); + + afterEach(() => { + holder.worklets = null; + vi.unstubAllGlobals(); + }); + + it('runs on the JS thread when worklets are unavailable', () => { + const cb = vi.fn(); + + const { unmount } = render(); + + expect(cb).toHaveBeenCalledWith({ deltaSeconds: 0, elapsedSeconds: 0 }); + unmount(); + expect(cancelAnimationFrame).toHaveBeenCalled(); + }); + + it('never dispatches plain callbacks to the UI runtime even with worklets installed', () => { + const runOnUISync = vi.fn(); + holder.worklets = { + isWorkletFunction: (value: unknown) => + typeof value === 'function' && !!(value as { __workletHash?: unknown }).__workletHash, + runOnUISync, + createShareable: vi.fn(), + UIRuntimeId: 1, + }; + const cb = vi.fn(); + + render(); + + expect(runOnUISync).not.toHaveBeenCalled(); + expect(cb).toHaveBeenCalledWith({ deltaSeconds: 0, elapsedSeconds: 0 }); + }); +}); diff --git a/packages/typegpu-react/tests/root-context.test.tsx b/packages/typegpu-react/tests/root-context.test.tsx index d5db80a6d4..4c8a2ce682 100644 --- a/packages/typegpu-react/tests/root-context.test.tsx +++ b/packages/typegpu-react/tests/root-context.test.tsx @@ -54,6 +54,35 @@ describe('Root unmount cleanup', () => { expect(() => unmount()).not.toThrow(); }); + it('should pass options to owned root init', async ({ adapter }) => { + function TestConsumer() { + useRootWithStatus(); + return null; + } + + render( + + + , + ); + + await act(async () => { + await Promise.resolve(); + }); + + expect(adapter.requestDevice.mock.calls).toMatchInlineSnapshot(` + [ + [ + { + "requiredFeatures": [ + "timestamp-query", + ], + }, + ], + ] + `); + }); + it('should destroy root when init promise resolves after unmount', async ({ stallDeviceRequest, }) => { diff --git a/packages/typegpu-react/tsdown.config.ts b/packages/typegpu-react/tsdown.config.ts index 24110e68b5..87f067b1df 100644 --- a/packages/typegpu-react/tsdown.config.ts +++ b/packages/typegpu-react/tsdown.config.ts @@ -1,5 +1,19 @@ import { defineConfig } from 'tsdown'; +// Rolldown rewrites `require` into a helper Metro cannot statically analyze, +// restoring the literal call keeps react-native-worklets an optional dependency +const preserveOptionalRequire = { + name: 'preserve-optional-require', + renderChunk(code: string) { + if (!code.includes('__require("react-native-worklets")')) { + return null; + } + return code + .replace('__require("react-native-worklets")', 'require("react-native-worklets")') + .replace(/import \{ __require \} from "[^"]+";\n/, ''); + }, +}; + export default defineConfig({ entry: ['src/browser/index.ts', 'src/react-native/index.ts'], outDir: 'dist', @@ -9,4 +23,5 @@ export default defineConfig({ unbundle: true, sourcemap: false, target: false, + plugins: [preserveOptionalRequire], }); diff --git a/packages/typegpu/src/core/buffer/buffer.ts b/packages/typegpu/src/core/buffer/buffer.ts index e4a8eb9759..b877e6fa73 100644 --- a/packages/typegpu/src/core/buffer/buffer.ts +++ b/packages/typegpu/src/core/buffer/buffer.ts @@ -181,6 +181,15 @@ export function INTERNAL_createBuffer( return new TgpuBufferImpl(group, typeSchema, initialOrBuffer); } +export function INTERNAL_applyBufferUsages( + buffer: TgpuBuffer, + usages: UsageLiteral[], +): void { + if (usages.length > 0) { + (buffer as TgpuBufferImpl).$usage(...usages); + } +} + // -------------- // Implementation // -------------- diff --git a/packages/typegpu/src/core/pipeline/computePipeline.ts b/packages/typegpu/src/core/pipeline/computePipeline.ts index 8b732d824c..63acaaa6c8 100644 --- a/packages/typegpu/src/core/pipeline/computePipeline.ts +++ b/packages/typegpu/src/core/pipeline/computePipeline.ts @@ -40,7 +40,14 @@ import type { TgpuSlot } from '../slot/slotTypes.ts'; import type { PrimitiveOffsetInfo } from '../../data/offsetUtils.ts'; import { warnIfOverflow } from './limitsOverflow.ts'; -import { DISPATCH_INDIRECT_SIZE, resolveIndirectOffset } from './pipelineUtils.ts'; +import { + collectBindGroupPairs, + DISPATCH_INDIRECT_SIZE, + resolveIndirectOffset, + restoreTimestampPriors, +} from './pipelineUtils.ts'; +import type { RestoreContext } from '../../serial/types.ts'; +import { invariant } from '../../errors.ts'; import { createWithPerformanceCallback, createWithTimestampWrites, @@ -149,6 +156,24 @@ export function INTERNAL_createComputePipeline( return new TgpuComputePipelineImpl(new ComputePipelineCore(branch, slotBindings, descriptor), {}); } +export function INTERNAL_restoreComputePipeline( + soul: TgpuComputePipelineSoul, + ctx: RestoreContext, +): TgpuComputePipeline { + invariant(soul.raw, 'A compute pipeline soul is only complete once materialized.'); + const root = ctx.getRoot(soul.device) as ExperimentalTgpuRoot; + const core = ComputePipelineCore.precompiled(root, { + pipeline: soul.raw, + usedBindGroupLayouts: soul.usedBindGroupLayouts ?? [], + catchall: undefined, + logResources: undefined, + }); + const pipeline: TgpuComputePipeline = new TgpuComputePipelineImpl(core, { + bindGroupLayoutMap: new Map(soul.bindGroups), + }); + return restoreTimestampPriors(pipeline, soul); +} + // -------------- // Implementation // -------------- @@ -191,7 +216,11 @@ class TgpuComputePipelineImpl implements TgpuComputePipeline { const memo = core.unwrap(); soul.raw = memo.pipeline; soul.usedBindGroupLayouts = memo.usedBindGroupLayouts; - soul.bindGroups = priors.bindGroupLayoutMap ? [...priors.bindGroupLayoutMap] : []; + soul.bindGroups = collectBindGroupPairs( + memo.usedBindGroupLayouts, + memo.catchall, + priors.bindGroupLayoutMap, + ); soul.timestampWrites = priors.timestampWrites; soul.performanceCallback = priors.performanceCallback; soul.nonTransferablePriors = nonTransferablePriorsOf(priors); @@ -365,13 +394,13 @@ class ComputePipelineCore implements SelfResolvable { #memo: Memo | undefined; #slotBindings: [TgpuSlot, unknown][]; - #descriptor: TgpuComputePipeline.Descriptor; + #descriptor: TgpuComputePipeline.Descriptor | undefined; #performanceCallbackQuerySet: TgpuQuerySet<'timestamp'> | undefined; constructor( root: ExperimentalTgpuRoot, slotBindings: [TgpuSlot, unknown][], - descriptor: TgpuComputePipeline.Descriptor, + descriptor: TgpuComputePipeline.Descriptor | undefined, ) { this.root = root; this.#slotBindings = slotBindings; @@ -381,9 +410,20 @@ class ComputePipelineCore implements SelfResolvable { : new NullPerformanceTracker(); } + static precompiled(root: ExperimentalTgpuRoot, memo: Memo): ComputePipelineCore { + const core = new ComputePipelineCore(root, [], undefined); + core.#memo = memo; + return core; + } + [$resolve](ctx: ResolutionCtx) { + const descriptor = this.#descriptor; + if (!descriptor) { + // Precompiled pipelines have nothing to contribute to the shader + return snip('', Void, /* origin */ 'runtime'); + } return ctx.withSlots(this.#slotBindings, () => { - ctx.resolve(this.#descriptor.compute); + ctx.resolve(descriptor.compute); return snip('', Void, /* origin */ 'runtime'); }); } diff --git a/packages/typegpu/src/core/pipeline/pipelineUtils.ts b/packages/typegpu/src/core/pipeline/pipelineUtils.ts index bced058e27..fb75ac48b9 100644 --- a/packages/typegpu/src/core/pipeline/pipelineUtils.ts +++ b/packages/typegpu/src/core/pipeline/pipelineUtils.ts @@ -2,13 +2,54 @@ import type { IndirectFlag, TgpuBuffer } from '../buffer/buffer.ts'; import { memoryLayoutOf, type PrimitiveOffsetInfo } from '../../data/offsetUtils.ts'; import { sizeOf } from '../../data/sizeOf.ts'; import type { BaseData } from '../../data/wgslTypes.ts'; +import type { TgpuBindGroup, TgpuBindGroupLayout } from '../../tgpuBindGroupLayout.ts'; +import type { TgpuVertexLayout } from '../vertexLayout/vertexLayout.ts'; import { isGPUBuffer } from '../../types.ts'; import { logger } from '../../tgpuLogger.ts'; +import type { Timeable, TimestampWritesPriors } from './timeable.ts'; export const DISPATCH_INDIRECT_SIZE = 12; // 3 x u32 (x, y, z) export const DRAW_INDIRECT_SIZE = 16; // 4 x 4 export const DRAW_INDEXED_INDIRECT_SIZE = 20; // 5 x 4 +export function collectBindGroupPairs( + layouts: TgpuBindGroupLayout[], + catchall: [number, TgpuBindGroup] | undefined, + map: Map | undefined, +): [TgpuBindGroupLayout, TgpuBindGroup | GPUBindGroup][] { + return layouts.flatMap((layout, idx): [TgpuBindGroupLayout, TgpuBindGroup | GPUBindGroup][] => { + const resource = catchall && idx === catchall[0] ? catchall[1] : map?.get(layout); + return resource ? [[layout, resource]] : []; + }); +} + +export function collectVertexBufferPairs( + layouts: TgpuVertexLayout[], + map: Map | undefined, +): [TgpuVertexLayout, Buffer][] { + return layouts.flatMap((layout): [TgpuVertexLayout, Buffer][] => { + const resource = map?.get(layout); + return resource ? [[layout, resource]] : []; + }); +} + +export function restoreTimestampPriors( + pipeline: T, + priors: { + readonly timestampWrites?: TimestampWritesPriors['timestampWrites'] | undefined; + readonly performanceCallback?: TimestampWritesPriors['performanceCallback'] | undefined; + }, +): T { + let result = pipeline; + if (priors.timestampWrites) { + result = result.withTimestampWrites(priors.timestampWrites); + } + if (priors.performanceCallback) { + result = result.withPerformanceCallback(priors.performanceCallback); + } + return result; +} + type IndirectOperation = 'dispatchWorkgroupsIndirect' | 'drawIndirect' | 'drawIndexedIndirect'; const IndirectOperationToRequiredData = { dispatchWorkgroupsIndirect: '3 x u32', diff --git a/packages/typegpu/src/core/pipeline/renderPipeline.ts b/packages/typegpu/src/core/pipeline/renderPipeline.ts index 69dafabcb0..07d8fd7b25 100644 --- a/packages/typegpu/src/core/pipeline/renderPipeline.ts +++ b/packages/typegpu/src/core/pipeline/renderPipeline.ts @@ -17,6 +17,7 @@ import { type WgslArray, type WgslStruct, } from '../../data/wgslTypes.ts'; +import { invariant } from '../../errors.ts'; import { resolve } from '../../resolutionCtx.ts'; import type { TgpuNamable } from '../../shared/meta.ts'; import { getName, PERF, setName } from '../../shared/meta.ts'; @@ -81,9 +82,12 @@ import { nonTransferablePriorsOf } from './priors.ts'; import { type PrimitiveOffsetInfo } from '../../data/offsetUtils.ts'; import { warnIfOverflow } from './limitsOverflow.ts'; import { + collectBindGroupPairs, + collectVertexBufferPairs, DRAW_INDEXED_INDIRECT_SIZE, DRAW_INDIRECT_SIZE, resolveIndirectOffset, + restoreTimestampPriors, } from './pipelineUtils.ts'; import { NullPerformanceTracker, @@ -91,6 +95,7 @@ import { type PerformanceTracker, } from './performanceTracker.ts'; import { logger } from '../../tgpuLogger.ts'; +import type { RestoreContext } from '../../serial/types.ts'; export interface RenderPipelineInternals { readonly core: RenderPipelineCore; @@ -344,13 +349,37 @@ export type AnyFragmentColorAttachment = ColorAttachment | Record, unknown][]; - descriptor: TgpuRenderPipeline.Descriptor; + /** Undefined for precompiled pipelines, which are never resolved again */ + descriptor: TgpuRenderPipeline.Descriptor | undefined; }; export function INTERNAL_createRenderPipeline(options: RenderPipelineCoreOptions) { return new TgpuRenderPipelineImpl(new RenderPipelineCore(options), {}); } +export function INTERNAL_restoreRenderPipeline( + soul: TgpuRenderPipelineSoul, + ctx: RestoreContext, +): TgpuRenderPipeline { + invariant(soul.raw, 'A render pipeline soul is only complete once materialized.'); + const root = ctx.getRoot(soul.device) as ExperimentalTgpuRoot; + const core = RenderPipelineCore.precompiled(root, { + pipeline: soul.raw, + usedBindGroupLayouts: soul.usedBindGroupLayouts ?? [], + catchall: undefined, + logResources: undefined, + usedVertexLayouts: soul.usedVertexLayouts ?? [], + fragmentOut: soul.fragmentOut, + }); + const pipeline: TgpuRenderPipeline = new TgpuRenderPipelineImpl(core, { + bindGroupLayoutMap: new Map(soul.bindGroups), + vertexLayoutMap: new Map(soul.vertexBuffers), + indexBuffer: soul.indexBuffer, + stencilReference: soul.stencilReference, + }); + return restoreTimestampPriors(pipeline, soul); +} + // -------------- // Implementation // -------------- @@ -411,11 +440,16 @@ class TgpuRenderPipelineImpl implements TgpuRenderPipeline { soul.raw = memo.pipeline; soul.usedBindGroupLayouts = memo.usedBindGroupLayouts; soul.usedVertexLayouts = memo.usedVertexLayouts; - soul.fragmentOut = - (core.options.descriptor.fragment as TgpuFragmentFn | undefined)?.shell?.returnType ?? - memo.fragmentOut; - soul.bindGroups = priors.bindGroupLayoutMap ? [...priors.bindGroupLayoutMap] : []; - soul.vertexBuffers = priors.vertexLayoutMap ? [...priors.vertexLayoutMap] : []; + soul.fragmentOut = memo.fragmentOut; + soul.bindGroups = collectBindGroupPairs( + memo.usedBindGroupLayouts, + memo.catchall, + priors.bindGroupLayoutMap, + ); + soul.vertexBuffers = collectVertexBufferPairs( + memo.usedVertexLayouts, + priors.vertexLayoutMap, + ); soul.indexBuffer = priors.indexBuffer; soul.stencilReference = priors.stencilReference; soul.timestampWrites = priors.timestampWrites; @@ -738,9 +772,22 @@ class RenderPipelineCore implements SelfResolvable { : new NullPerformanceTracker(); } + static precompiled(root: ExperimentalTgpuRoot, memo: Memo): RenderPipelineCore { + const core = new RenderPipelineCore({ + root, + slotBindings: [], + descriptor: undefined, + }); + core.#memo = memo; + return core; + } + [$resolve](ctx: ResolutionCtx): ResolvedSnippet { const { slotBindings } = this.options; - const { vertex, fragment, attribs = {} } = this.options.descriptor; + const { vertex, fragment, attribs = {} } = this.options.descriptor ?? {}; + if (!vertex) { + return snip('', Void, /* origin */ 'runtime'); + } this.#latestAutoVertexIn = undefined; this.#latestAutoFragmentOut = undefined; @@ -862,6 +909,9 @@ class RenderPipelineCore implements SelfResolvable { public resolveAndCreateShaderModule() { const { root, descriptor: tgpuDescriptor } = this.options; + if (!tgpuDescriptor) { + throw new Error('Precompiled pipelines are never resolved again.'); + } const device = root.device; const enableExtensions = wgslEnableExtensions.filter((extension) => root.enabledFeatures.has(wgslEnableExtensionToFeatureName[extension]), @@ -893,7 +943,7 @@ class RenderPipelineCore implements SelfResolvable { code, }); - const { vertex, fragment, attribs = {}, targets } = this.options.descriptor; + const { vertex, fragment, attribs = {}, targets } = tgpuDescriptor; const connectedAttribs = connectAttributesToShader( (vertex as TgpuVertexFn)?.shell?.in ?? this.#latestAutoVertexIn ?? {}, attribs, diff --git a/packages/typegpu/src/core/root/init.ts b/packages/typegpu/src/core/root/init.ts index 2c9a91fce7..e7ed406c46 100644 --- a/packages/typegpu/src/core/root/init.ts +++ b/packages/typegpu/src/core/root/init.ts @@ -94,6 +94,7 @@ import { allEq } from '../../std/boolean.ts'; import { getName, setName } from '../../shared/meta.ts'; import { logger } from '../../tgpuLogger.ts'; import { safeStringify } from '../../shared/stringify.ts'; +import type { RestoreContext } from '../../serial/types.ts'; /** * Changes the given array to a vec of 3 numbers, filling missing values with 1. @@ -216,6 +217,22 @@ export class TgpuGuardedComputePipelineImpl< } } +export function INTERNAL_restoreRoot(soul: TgpuRootSoul, ctx: RestoreContext): TgpuRoot { + return ctx.getRoot(soul.device); +} + +export function INTERNAL_restoreGuardedComputePipeline( + soul: TgpuGuardedComputePipelineSoul, + ctx: RestoreContext, +): TgpuGuardedComputePipeline { + return new TgpuGuardedComputePipelineImpl( + ctx.getRoot(soul.device) as ExperimentalTgpuRoot, + soul.pipeline, + soul.sizeUniform, + soul.workgroupSize, + ); +} + class WithBindingImpl implements WithBinding { readonly #getRoot: () => ExperimentalTgpuRoot; readonly #slotBindings: [TgpuSlot, unknown][]; @@ -430,8 +447,11 @@ class TgpuRootImpl extends WithBindingImpl implements TgpuRoot, ExperimentalTgpu createBindGroup< Entries extends Record = Record, - >(layout: TgpuBindGroupLayout, entries: ExtractBindGroupInputFromLayout) { - return new TgpuBindGroupImpl(layout, entries); + >( + layout: TgpuBindGroupLayout, + entries: ExtractBindGroupInputFromLayout, + ): TgpuBindGroup { + return new TgpuBindGroupImpl(this, layout, entries); } destroy() { @@ -629,6 +649,7 @@ export async function init(options?: InitOptions): Promise { unstable_logOptions: logOptions, unstable_shaderGenerator: shaderGenerator, } = options ?? {}; + const { optionalFeatures, ...deviceDescriptor } = deviceOpt ?? {}; if (!navigator.gpu) { throw new Error('WebGPU is not supported by this browser.'); @@ -641,13 +662,13 @@ export async function init(options?: InitOptions): Promise { } const availableFeatures: GPUFeatureName[] = []; - for (const feature of deviceOpt?.requiredFeatures ?? []) { + for (const feature of deviceDescriptor.requiredFeatures ?? []) { if (!adapter.features.has(feature)) { throw new Error(`Requested feature "${feature}" is not supported by the adapter.`); } availableFeatures.push(feature); } - for (const feature of deviceOpt?.optionalFeatures ?? []) { + for (const feature of optionalFeatures ?? []) { if (adapter.features.has(feature)) { availableFeatures.push(feature); } else { @@ -659,7 +680,7 @@ export async function init(options?: InitOptions): Promise { } const device = await adapter.requestDevice({ - ...deviceOpt, + ...deviceDescriptor, requiredFeatures: availableFeatures, }); diff --git a/packages/typegpu/src/core/texture/texture.ts b/packages/typegpu/src/core/texture/texture.ts index b34e4a122f..23ce6d7e88 100644 --- a/packages/typegpu/src/core/texture/texture.ts +++ b/packages/typegpu/src/core/texture/texture.ts @@ -223,8 +223,9 @@ export interface TgpuTextureRenderView { export function INTERNAL_createTexture( props: TextureProps, branch: ExperimentalTgpuRoot, + rawTexture?: GPUTexture, ): TgpuTexture { - return new TgpuTextureImpl(props, branch); + return new TgpuTextureImpl(props, branch, rawTexture); } export function isTexture(value: unknown): value is TgpuTexture { @@ -252,8 +253,10 @@ class TgpuTextureImpl implements TgpuTexture implements TgpuTexture implements TgpuTexture', but their schemas are exported as 'vec2b' + const key = kind.replace('', 'b'); + const schema = (d as unknown as Record)[key]; + if (!schema) { + throw new Error(`Data value of kind '${kind}' cannot be serialized.`); + } + return schema; +} + +export function INTERNAL_snapshotDataValue( + value: AnyVecInstance | AnyMatInstance, +): TgpuDataValueSnapshot { + const schema = schemaForKind(value.kind); + const bytes = new ArrayBuffer(sizeOf(schema)); + writeToArrayBuffer(bytes, schema, value); + return { type: 'data-value', kind: value.kind, bytes }; +} + +export function INTERNAL_restoreDataValue( + snapshot: TgpuDataValueSnapshot, +): AnyVecInstance | AnyMatInstance { + const schema = schemaForKind(snapshot.kind); + return readFromArrayBuffer(snapshot.bytes, schema) as AnyVecInstance | AnyMatInstance; +} diff --git a/packages/typegpu/src/serial/registry.ts b/packages/typegpu/src/serial/registry.ts new file mode 100644 index 0000000000..cf1d394ef6 --- /dev/null +++ b/packages/typegpu/src/serial/registry.ts @@ -0,0 +1,170 @@ +import { isData } from '../data/dataTypes.ts'; +import type { TgpuSoul } from '../shared/soul.ts'; +import { $internal, $soul } from '../shared/symbols.ts'; +import { + INTERNAL_restoreDataValue, + INTERNAL_snapshotDataValue, + isSnapshotableDataValue, + type TgpuDataValueSnapshot, +} from './dataValue.ts'; +import { soulRestorers, type TgpuResourceSoul, type TransferableResourceType } from './restore.ts'; +import { deserializeDataSchema, serializeDataSchema, type SerializedDataSchema } from './schema.ts'; +import type { RestoreContext } from './types.ts'; + +export type { TgpuResourceSoul, TransferableResourceType }; + +/** A live data schema, transferred as a description of itself */ +export interface TgpuDataSchemaSnapshot { + readonly type: 'data-schema'; + readonly schema: SerializedDataSchema; +} + +/** + * What travels between runtimes: a plain copy of a resource's soul, or one of + * the two kinds that own no soul - data schemas and vector/matrix instances + */ +export type TgpuResourceSnapshot = + | TgpuResourceSoul + | TgpuDataSchemaSnapshot + | TgpuDataValueSnapshot; + +type MaterializableInternals = { readonly materialize?: (() => unknown) | undefined }; + +/** + * Every data schema is callable, and host serializers claim functions before + * they ever ask whether a value is transferable, so schemas held by a soul are + * replaced with a description of themselves on the way out + */ +const DATA_SCHEMA_KEY = '~tgpuDataSchema'; + +type TaggedDataSchema = { [DATA_SCHEMA_KEY]: SerializedDataSchema }; + +function isPlainRecord(value: unknown): value is Record { + if (typeof value !== 'object' || value === null) { + return false; + } + const proto = Object.getPrototypeOf(value); + return proto === Object.prototype || proto === null; +} + +/** + * Rebuilds containers only when something inside them changed, so raw handles + * are passed through untouched. `path` keeps self-referencing objects finite + */ +function mapContainer( + value: unknown, + path: Set, + mapField: (field: unknown, path: Set) => unknown, +): unknown { + const isArray = Array.isArray(value); + if (!isArray && !isPlainRecord(value)) { + return value; + } + if (path.has(value as object)) { + return value; + } + + path.add(value as object); + try { + if (isArray) { + const mapped = value.map((field) => mapField(field, path)); + return mapped.some((field, idx) => field !== value[idx]) ? mapped : value; + } + const entries = Object.entries(value); + const mapped = entries.map(([key, field]): [string, unknown] => [key, mapField(field, path)]); + return mapped.some(([, field], idx) => field !== entries[idx]?.[1]) + ? Object.fromEntries(mapped) + : value; + } finally { + path.delete(value as object); + } +} + +function describeSchemas(value: unknown, path: Set = new Set()): unknown { + if (isData(value)) { + return { [DATA_SCHEMA_KEY]: serializeDataSchema(value) } satisfies TaggedDataSchema; + } + if (isSnapshotableDataValue(value)) { + return value; + } + return mapContainer(value, path, describeSchemas); +} + +function reviveSchemas(value: unknown, path: Set = new Set()): unknown { + if (isPlainRecord(value)) { + const described = value[DATA_SCHEMA_KEY]; + if (described) { + return deserializeDataSchema(described as SerializedDataSchema); + } + } + return mapContainer(value, path, reviveSchemas); +} + +function soulOf(value: unknown): TgpuSoul | undefined { + const soul = (value as { [$soul]?: TgpuSoul } | undefined)?.[$soul]; + return soul && soul.type in soulRestorers ? soul : undefined; +} + +export function isSnapshotableResource(value: unknown): boolean { + return isSnapshotableDataValue(value) || isData(value) || soulOf(value) !== undefined; +} + +/** Whether the value is a TypeGPU object that {@link snapshotResource} does not support */ +export function isNonTransferableResource(value: unknown): boolean { + return ( + typeof value === 'object' && + value !== null && + $internal in value && + !isSnapshotableResource(value) + ); +} + +/** + * Extracts what survives a device boundary: the resource's soul, completed by + * materialization for resources whose definition is a compiled object + */ +export function snapshotResource(value: unknown): TgpuResourceSnapshot | undefined { + if (isSnapshotableDataValue(value)) { + return INTERNAL_snapshotDataValue(value); + } + + if (isData(value)) { + return { type: 'data-schema', schema: serializeDataSchema(value) }; + } + + const soul = soulOf(value); + if (!soul) { + return undefined; + } + + (value as { [$internal]?: MaterializableInternals })[$internal]?.materialize?.(); + + const nonTransferable = (soul as { nonTransferablePriors?: string[] }).nonTransferablePriors; + if (nonTransferable?.length) { + throw new Error( + `TypeGPU '${soul.type}' cannot be transferred: ${nonTransferable.join(', ')} ${ + nonTransferable.length > 1 ? 'are' : 'is' + } bound to this runtime. Apply them after the resource crosses the boundary.`, + ); + } + + return describeSchemas({ ...soul }) as TgpuResourceSoul; +} + +export function restoreResource(snapshot: TgpuResourceSnapshot, ctx: RestoreContext): unknown { + if (snapshot.type === 'data-value') { + return INTERNAL_restoreDataValue(snapshot); + } + + if (snapshot.type === 'data-schema') { + return deserializeDataSchema(snapshot.schema); + } + + const restore = ( + soulRestorers as unknown as Record unknown> + )[snapshot.type]; + if (!restore) { + throw new Error(`TypeGPU resource '${snapshot.type}' has no restorer registered.`); + } + return restore(reviveSchemas(snapshot) as TgpuSoul, ctx); +} diff --git a/packages/typegpu/src/serial/restore.ts b/packages/typegpu/src/serial/restore.ts new file mode 100644 index 0000000000..0dfcfab546 --- /dev/null +++ b/packages/typegpu/src/serial/restore.ts @@ -0,0 +1,139 @@ +import { + INTERNAL_applyBufferUsages, + type TgpuBuffer, + type TgpuBufferSoul, + type UniformFlag, +} from '../core/buffer/buffer.ts'; +import type { StorageFlag } from '../extension.ts'; +import type { BaseData } from '../data/wgslTypes.ts'; +import type { ExperimentalTgpuRoot } from '../core/root/rootTypes.ts'; +import { TgpuBufferBindingImpl, type TgpuBufferBindingSoul } from '../core/buffer/bufferBinding.ts'; +import { constant, type TgpuConstSoul } from '../core/constant/tgpuConstant.ts'; +import { + INTERNAL_restoreComputePipeline, + type TgpuComputePipelineSoul, +} from '../core/pipeline/computePipeline.ts'; +import { + INTERNAL_restoreRenderPipeline, + type TgpuRenderPipelineSoul, +} from '../core/pipeline/renderPipeline.ts'; +import type { TgpuQuerySetSoul } from '../core/querySet/querySet.ts'; +import { INTERNAL_restoreGuardedComputePipeline, INTERNAL_restoreRoot } from '../core/root/init.ts'; +import type { TgpuGuardedComputePipelineSoul, TgpuRootSoul } from '../core/root/rootTypes.ts'; +import type { TgpuSamplerSoul } from '../core/sampler/sampler.ts'; +import { accessor, mutableAccessor } from '../core/slot/accessor.ts'; +import { slot } from '../core/slot/slot.ts'; +import type { + TgpuAccessorSoul, + TgpuMutableAccessor, + TgpuSlotSoul, +} from '../core/slot/slotTypes.ts'; +import { INTERNAL_createTexture, type TgpuTextureSoul } from '../core/texture/texture.ts'; +import type { AllowedUsages } from '../core/texture/usageExtension.ts'; +import { vertexLayout, type TgpuVertexLayoutSoul } from '../core/vertexLayout/vertexLayout.ts'; +import { arrayOf } from '../data/array.ts'; +import type { AnyData } from '../data/dataTypes.ts'; +import { disarrayOf } from '../data/disarray.ts'; +import { isDisarray } from '../data/dataTypes.ts'; +import { isWgslArray, type AnyWgslData } from '../data/wgslTypes.ts'; +import type { TextureProps } from '../core/texture/textureProps.ts'; +import { + INTERNAL_restoreBindGroup, + INTERNAL_restoreBindGroupLayout, + type TgpuBindGroupLayoutSoul, + type TgpuBindGroupSoul, +} from '../tgpuBindGroupLayout.ts'; +import type { WgslComparisonSamplerProps, WgslSamplerProps } from '../data/sampler.ts'; +import type { TgpuSoul } from '../shared/soul.ts'; +import type { RestoreContext } from './types.ts'; + +/** Every soul that can cross a device boundary, discriminated by `type` */ +export type TgpuResourceSoul = + | TgpuBufferSoul + | TgpuBufferBindingSoul + | TgpuTextureSoul + | TgpuSamplerSoul + | TgpuQuerySetSoul + | TgpuBindGroupLayoutSoul + | TgpuBindGroupSoul + | TgpuVertexLayoutSoul + | TgpuConstSoul + | TgpuSlotSoul + | TgpuAccessorSoul + | TgpuComputePipelineSoul + | TgpuRenderPipelineSoul + | TgpuGuardedComputePipelineSoul + | TgpuRootSoul; + +type Restorer = (soul: TSoul, ctx: RestoreContext) => unknown; + +function restoreBufferBinding(soul: TgpuBufferBindingSoul) { + return new TgpuBufferBindingImpl( + soul.type, + soul.buffer as TgpuBuffer & UniformFlag & StorageFlag, + ); +} + +/** + * The one place a soul turns back into a resource. Importing this module opts + * into every restorer, so it is kept out of the resources themselves + */ +export const soulRestorers = { + buffer: (soul: TgpuBufferSoul, ctx) => { + const buffer = ctx.getRoot(soul.device).createBuffer(soul.dataType as AnyData, soul.raw); + INTERNAL_applyBufferUsages(buffer, soul.usages); + return buffer; + }, + uniform: restoreBufferBinding, + mutable: restoreBufferBinding, + readonly: restoreBufferBinding, + texture: (soul: TgpuTextureSoul, ctx) => { + const texture = INTERNAL_createTexture( + soul.props, + ctx.getRoot(soul.device) as ExperimentalTgpuRoot, + soul.raw, + ); + if (soul.flagsOverridden) { + texture.$overrideFlags(soul.flags); + } else if (soul.usages.length > 0) { + texture.$usage(...(soul.usages as AllowedUsages[])); + } + return texture; + }, + sampler: (soul: TgpuSamplerSoul, ctx) => + ctx.getRoot(soul.device).createSampler(soul.props as WgslSamplerProps), + 'sampler-comparison': (soul: TgpuSamplerSoul, ctx) => + ctx.getRoot(soul.device).createComparisonSampler(soul.props as WgslComparisonSamplerProps), + 'query-set': (soul: TgpuQuerySetSoul, ctx) => + ctx.getRoot(soul.device).createQuerySet(soul.queryType, soul.count, soul.raw), + 'bind-group-layout': (soul: TgpuBindGroupLayoutSoul) => INTERNAL_restoreBindGroupLayout(soul), + 'bind-group': INTERNAL_restoreBindGroup as Restorer, + 'vertex-layout': (soul: TgpuVertexLayoutSoul) => { + const schema = soul.schema; + if (isWgslArray(schema)) { + const elementType = schema.elementType as AnyWgslData; + return vertexLayout((count) => arrayOf(elementType, count), soul.stepMode); + } + if (isDisarray(schema)) { + const elementType = schema.elementType as AnyData; + return vertexLayout((count) => disarrayOf(elementType, count), soul.stepMode); + } + throw new Error('TypeGPU vertex layout payload could not be reconstructed.'); + }, + const: (soul: TgpuConstSoul) => constant(soul.dataType as AnyData, soul.value), + slot: (soul: TgpuSlotSoul) => slot(soul.defaultValue), + accessor: (soul: TgpuAccessorSoul) => accessor(soul.schema as AnyData, soul.defaultValue), + 'mutable-accessor': (soul: TgpuAccessorSoul) => + mutableAccessor( + soul.schema as AnyData, + soul.defaultValue as TgpuMutableAccessor.In | undefined, + ), + 'compute-pipeline': INTERNAL_restoreComputePipeline as Restorer, + 'render-pipeline': INTERNAL_restoreRenderPipeline as Restorer, + 'guarded-compute-pipeline': + INTERNAL_restoreGuardedComputePipeline as Restorer, + root: INTERNAL_restoreRoot as Restorer, + // oxlint-disable-next-line typescript/no-explicit-any -- each restorer narrows its own soul +} satisfies Record>; + +export type TransferableResourceType = TgpuResourceSoul['type']; diff --git a/packages/typegpu/src/serial/schema.ts b/packages/typegpu/src/serial/schema.ts new file mode 100644 index 0000000000..9543928af3 --- /dev/null +++ b/packages/typegpu/src/serial/schema.ts @@ -0,0 +1,276 @@ +import * as d from '../data/index.ts'; +import { isWgslStorageTexture, isWgslTexture } from '../data/texture.ts'; +import { assertExhaustive } from '../shared/utilityTypes.ts'; + +type SerializedDataAttrib = + | { type: 'align'; value: number } + | { type: 'size'; value: number } + | { type: 'location'; value: number } + | { type: 'interpolate'; value: string } + | { type: 'builtin'; value: string } + | { type: 'invariant' }; + +export type SerializedDataSchema = + | { type: 'd'; key: string } + | { type: 'array'; element: SerializedDataSchema; count: number } + | { type: 'disarray'; element: SerializedDataSchema; count: number } + | { type: 'struct'; props: [string, SerializedDataSchema][] } + | { type: 'unstruct'; props: [string, SerializedDataSchema][] } + | { type: 'atomic'; inner: SerializedDataSchema } + | { type: 'decorated'; inner: SerializedDataSchema; attribs: SerializedDataAttrib[] } + | { type: 'sampled-texture'; kind: d.WgslTexture['type']; sampleType: SerializedDataSchema } + | { + type: 'storage-texture'; + kind: d.WgslStorageTexture['type']; + format: d.WgslStorageTexture['format']; + access: d.WgslStorageTexture['access']; + } + | { type: 'external-texture' }; + +// Maps schema singletons (e.g. `d.f32`) back to their `d` export names +let leafKeys: Map | undefined; + +function getDataSchemaKey(schema: d.BaseData): string | undefined { + if (!leafKeys) { + leafKeys = new Map(); + for (const [key, value] of Object.entries(d)) { + if ((typeof value === 'object' || typeof value === 'function') && value !== null) { + leafKeys.set(value, key); + } + } + } + return leafKeys.get(schema); +} + +let builtinsByName: Map | undefined; + +function getBuiltinByName(value: string): d.AnyData { + if (!builtinsByName) { + builtinsByName = new Map(); + for (const candidate of Object.values(d.builtin) as d.AnyData[]) { + if (!d.isDecorated(candidate) && !d.isLooseDecorated(candidate)) { + continue; + } + const builtin = candidate.attribs.find(d.isBuiltinAttrib); + if (builtin) { + builtinsByName.set(builtin.params[0], candidate); + } + } + } + const builtin = builtinsByName.get(value); + if (!builtin) { + throw new Error(`TypeGPU builtin '${value}' could not be reconstructed.`); + } + return builtin; +} + +function serializeAttrib(attrib: unknown): SerializedDataAttrib { + if (d.isAlignAttrib(attrib)) { + return { type: 'align', value: attrib.params[0] }; + } + if (d.isSizeAttrib(attrib)) { + return { type: 'size', value: attrib.params[0] }; + } + if (d.isLocationAttrib(attrib)) { + return { type: 'location', value: attrib.params[0] }; + } + if (d.isInterpolateAttrib(attrib)) { + return { type: 'interpolate', value: attrib.params[0] }; + } + if (d.isBuiltinAttrib(attrib)) { + return { type: 'builtin', value: attrib.params[0] }; + } + if (d.isInvariantAttrib(attrib)) { + return { type: 'invariant' }; + } + throw new Error('This TypeGPU schema decorator cannot be serialized yet.'); +} + +function applyAttrib(schema: d.AnyData, attrib: SerializedDataAttrib): d.AnyData { + if (attrib.type === 'align') { + return d.align(attrib.value, schema); + } + if (attrib.type === 'size') { + return d.size(attrib.value, schema); + } + if (attrib.type === 'location') { + return d.location(attrib.value, schema); + } + if (attrib.type === 'interpolate') { + return d.interpolate(attrib.value as never, schema as never); + } + if (attrib.type === 'builtin') { + return getBuiltinByName(attrib.value); + } + if (attrib.type === 'invariant') { + return d.invariant(schema as Parameters[0]); + } + assertExhaustive(attrib, 'schema.ts#applyAttrib'); +} + +function serializeProps(propTypes: Record): [string, SerializedDataSchema][] { + return Object.entries(propTypes).map(([prop, propType]) => [prop, serializeDataSchema(propType)]); +} + +function deserializeProps(props: [string, SerializedDataSchema][]): Record { + return Object.fromEntries(props.map(([prop, schema]) => [prop, deserializeDataSchema(schema)])); +} + +const sampledTextureConstructors = { + texture_1d: (sampleType) => d.texture1d(sampleType), + texture_2d: (sampleType) => d.texture2d(sampleType), + texture_2d_array: (sampleType) => d.texture2dArray(sampleType), + texture_3d: (sampleType) => d.texture3d(sampleType), + texture_cube: (sampleType) => d.textureCube(sampleType), + texture_cube_array: (sampleType) => d.textureCubeArray(sampleType), + texture_multisampled_2d: (sampleType) => d.textureMultisampled2d(sampleType), + texture_depth_2d: () => d.textureDepth2d(), + texture_depth_2d_array: () => d.textureDepth2dArray(), + texture_depth_cube: () => d.textureDepthCube(), + texture_depth_cube_array: () => d.textureDepthCubeArray(), + texture_depth_multisampled_2d: () => d.textureDepthMultisampled2d(), +} satisfies Record unknown>; + +const storageTextureConstructors = { + texture_storage_1d: (format, access) => d.textureStorage1d(format, access), + texture_storage_2d: (format, access) => d.textureStorage2d(format, access), + texture_storage_2d_array: (format, access) => d.textureStorage2dArray(format, access), + texture_storage_3d: (format, access) => d.textureStorage3d(format, access), +} satisfies Record< + d.WgslStorageTexture['type'], + (format: d.WgslStorageTexture['format'], access: d.WgslStorageTexture['access']) => unknown +>; + +export function serializeDataSchema(schema: d.BaseData): SerializedDataSchema { + const key = getDataSchemaKey(schema); + if (key) { + return { type: 'd', key }; + } + + if (isWgslTexture(schema)) { + return { + type: 'sampled-texture', + kind: schema.type, + sampleType: serializeDataSchema(schema.sampleType), + }; + } + + if (isWgslStorageTexture(schema)) { + return { + type: 'storage-texture', + kind: schema.type, + format: schema.format, + access: schema.access, + }; + } + + if (schema.type === 'texture_external') { + return { type: 'external-texture' }; + } + + if (d.isDecorated(schema) || d.isLooseDecorated(schema)) { + return { + type: 'decorated', + inner: serializeDataSchema(schema.inner as d.AnyData), + attribs: schema.attribs.map(serializeAttrib), + }; + } + + if (d.isAtomic(schema)) { + return { type: 'atomic', inner: serializeDataSchema(schema.inner as d.AnyData) }; + } + + if (d.isWgslArray(schema)) { + return { + type: 'array', + element: serializeDataSchema(schema.elementType as d.AnyData), + count: schema.elementCount, + }; + } + + if (d.isDisarray(schema)) { + return { + type: 'disarray', + element: serializeDataSchema(schema.elementType as d.AnyData), + count: schema.elementCount, + }; + } + + if (d.isWgslStruct(schema)) { + return { type: 'struct', props: serializeProps(schema.propTypes) }; + } + + if (d.isUnstruct(schema)) { + return { type: 'unstruct', props: serializeProps(schema.propTypes) }; + } + + throw new Error(`TypeGPU schema '${schema.type}' cannot be serialized yet.`); +} + +export function deserializeDataSchema(schema: SerializedDataSchema): d.AnyData { + if (schema.type === 'd') { + const leaf = (d as unknown as Record)[schema.key]; + if (!leaf) { + throw new Error(`TypeGPU schema 'd.${schema.key}' could not be reconstructed.`); + } + return leaf; + } + + if (schema.type === 'array') { + return d.arrayOf( + deserializeDataSchema(schema.element) as d.AnyWgslData, + schema.count, + ) as d.AnyData; + } + + if (schema.type === 'disarray') { + return d.disarrayOf(deserializeDataSchema(schema.element), schema.count) as d.AnyData; + } + + if (schema.type === 'struct') { + return d.struct(deserializeProps(schema.props) as Record) as d.AnyData; + } + + if (schema.type === 'unstruct') { + return d.unstruct(deserializeProps(schema.props)) as d.AnyData; + } + + if (schema.type === 'atomic') { + return d.atomic(deserializeDataSchema(schema.inner) as d.U32 | d.I32) as d.AnyData; + } + + if (schema.type === 'decorated') { + let result = deserializeDataSchema(schema.inner); + for (let i = schema.attribs.length - 1; i >= 0; i--) { + const attrib = schema.attribs[i] as SerializedDataAttrib; + result = applyAttrib(result, attrib); + } + return result; + } + + if (schema.type === 'sampled-texture') { + const constructor = sampledTextureConstructors[schema.kind]; + if (!constructor) { + throw new Error(`TypeGPU texture schema '${schema.kind}' could not be reconstructed.`); + } + return constructor( + deserializeDataSchema(schema.sampleType) as d.WgslTexture['sampleType'], + ) as d.AnyData; + } + + if (schema.type === 'external-texture') { + return d.textureExternal() as d.AnyData; + } + + if (schema.type === 'storage-texture') { + const constructor = storageTextureConstructors[schema.kind]; + if (!constructor) { + throw new Error( + `TypeGPU storage texture schema '${schema.kind}' could not be reconstructed.`, + ); + } + return constructor(schema.format, schema.access) as d.AnyData; + } + + throw new Error('TypeGPU schema payload could not be reconstructed.'); +} diff --git a/packages/typegpu/src/serial/types.ts b/packages/typegpu/src/serial/types.ts new file mode 100644 index 0000000000..1d73fb87e4 --- /dev/null +++ b/packages/typegpu/src/serial/types.ts @@ -0,0 +1,6 @@ +import type { TgpuRoot } from '../core/root/rootTypes.ts'; + +/** Lets restored resources resolve the root they belong to, identified by the shared `GPUDevice` */ +export interface RestoreContext { + getRoot(device: GPUDevice): TgpuRoot; +} diff --git a/packages/typegpu/src/std/bitcast.ts b/packages/typegpu/src/std/bitcast.ts index 208a168274..2b7fac5498 100644 --- a/packages/typegpu/src/std/bitcast.ts +++ b/packages/typegpu/src/std/bitcast.ts @@ -41,7 +41,7 @@ import type { } from '../data/wgslTypes.ts'; import { unifyStrict } from '../tgsl/conversion.ts'; import { SignatureNotSupportedError } from '../errors.ts'; -import { getName } from '../internal.ts'; +import { getName } from '../shared/meta.ts'; import type { Infer } from '../shared/repr.ts'; import { comptime } from '../core/function/comptime.ts'; diff --git a/packages/typegpu/src/tgpuBindGroupLayout.ts b/packages/typegpu/src/tgpuBindGroupLayout.ts index 51d91835af..d3fff5f66b 100644 --- a/packages/typegpu/src/tgpuBindGroupLayout.ts +++ b/packages/typegpu/src/tgpuBindGroupLayout.ts @@ -30,6 +30,8 @@ import { type WgslStorageTexture, type WgslTexture, } from './data/texture.ts'; +import type { TgpuRoot } from './core/root/rootTypes.ts'; +import type { RestoreContext } from './serial/types.ts'; import type { BaseData } from './data/wgslTypes.ts'; import { invariant, NotUniformError } from './errors.ts'; import { NotStorageError, type StorageFlag } from './extension.ts'; @@ -37,7 +39,7 @@ import type { TgpuNamable } from './shared/meta.ts'; import { getName, setName } from './shared/meta.ts'; import type { InferGPU, MemIdentity } from './shared/repr.ts'; import { safeStringify } from './shared/stringify.ts'; -import type { TgpuSoul } from './shared/soul.ts'; +import type { TgpuDeviceOwningSoul, TgpuSoul } from './shared/soul.ts'; import { $gpuValueOf, $internal, $soul } from './shared/symbols.ts'; import type { NullableToOptional, Prettify } from './shared/utilityTypes.ts'; import type { ResolvableObject, TgpuShaderStage } from './types.ts'; @@ -132,11 +134,15 @@ export interface TgpuBindGroupLayoutSoul extends TgpuSoul<'bind-group-layout'> { index?: number | undefined; } +/** + * A residue soul: entries may hold runtime-local resources (texture views), so + * the soul holds the layout and the created bind group instead of the recipe. + * It is completed by `[$internal].materialize()`. + */ export interface TgpuBindGroupSoul< Entries extends Record = Record, -> extends TgpuSoul<'bind-group'> { +> extends TgpuDeviceOwningSoul<'bind-group', GPUBindGroup> { readonly layout: TgpuBindGroupLayout; - readonly entries: Record; } export interface TgpuBindGroupLayout< @@ -244,6 +250,7 @@ export type ExtractBindGroupInputFromLayout = Record, > = { + readonly [$internal]: { readonly materialize: () => GPUBindGroup }; readonly [$soul]: TgpuBindGroupSoul; readonly resourceType: 'bind-group'; readonly layout: TgpuBindGroupLayout; @@ -256,6 +263,20 @@ export function bindGroupLayout); } +export function INTERNAL_restoreBindGroupLayout( + soul: TgpuBindGroupLayoutSoul, +): TgpuBindGroupLayout { + return bindGroupLayout({ ...soul.entries }).$idx(soul.index); +} + +export function INTERNAL_restoreBindGroup( + soul: TgpuBindGroupSoul, + ctx: RestoreContext, +): TgpuBindGroup { + invariant(soul.raw, 'A bind group soul is only complete once materialized.'); + return new TgpuBindGroupImpl(ctx.getRoot(soul.device), soul.layout, {}, soul.raw); +} + export function isBindGroupLayout(value: unknown): value is TgpuBindGroupLayout { return !!value && (value as TgpuBindGroupLayout).resourceType === 'bind-group-layout'; } @@ -465,24 +486,44 @@ class TgpuBindGroupLayoutImpl< export class TgpuBindGroupImpl< Entries extends Record = Record, > implements TgpuBindGroup { + readonly [$internal]: { readonly materialize: () => GPUBindGroup }; readonly [$soul]: TgpuBindGroupSoul; readonly resourceType = 'bind-group' as const; + readonly #entries: ExtractBindGroupInputFromLayout; + constructor( + root: TgpuRoot | undefined, layout: TgpuBindGroupLayout, entries: ExtractBindGroupInputFromLayout, + raw?: GPUBindGroup, ) { + this.#entries = entries; this[$soul] = { type: 'bind-group', + // Undefined only in rootless `tgpu.resolve()`, where the group is never unwrapped + device: root?.device as GPUDevice, layout, - entries: entries as Record, + raw, label: undefined, }; + this[$internal] = { + materialize: () => { + const soul = this[$soul]; + if (!soul.raw) { + invariant(root, 'Cannot unwrap a bind group created outside of a root.'); + soul.raw = root.unwrap(this); + } + return soul.raw; + }, + }; - // Checking if all entries are present. - for (const key of Object.keys(layout.entries)) { - if (layout.entries[key] !== null && !(key in entries)) { - throw new MissingBindingError(getName(layout), key); + if (!raw) { + // Checking if all entries are present. + for (const key of Object.keys(layout.entries)) { + if (layout.entries[key] !== null && !(key in entries)) { + throw new MissingBindingError(getName(layout), key); + } } } } @@ -492,10 +533,15 @@ export class TgpuBindGroupImpl< } get entries(): ExtractBindGroupInputFromLayout { - return this[$soul].entries as ExtractBindGroupInputFromLayout; + return this.#entries; } public unwrap(unwrapper: Unwrapper): GPUBindGroup { + const raw = this[$soul].raw; + if (raw) { + return raw; + } + const unwrapped = unwrapper.device.createBindGroup({ label: getName(this.layout) ?? '', layout: unwrapper.unwrap(this.layout), diff --git a/packages/typegpu/tests/computePipeline.test.ts b/packages/typegpu/tests/computePipeline.test.ts index aed07182ad..fdb50e621c 100644 --- a/packages/typegpu/tests/computePipeline.test.ts +++ b/packages/typegpu/tests/computePipeline.test.ts @@ -1,5 +1,6 @@ import { describe, expect, expectTypeOf, vi } from 'vitest'; -import { d, MissingBindGroupsError, tgpu, type TgpuComputePipeline } from 'typegpu'; +import { d, isBindGroup, MissingBindGroupsError, tgpu, type TgpuComputePipeline } from 'typegpu'; +import { restoreResource, snapshotResource } from 'typegpu/~internal'; import { it } from 'typegpu-testing-utility'; import { extensionEnabled } from 'typegpu/std'; @@ -383,6 +384,93 @@ describe('TgpuComputePipeline', () => { }); }); + it('should wrap raw compute pipelines with bind groups', ({ root, commandEncoder }) => { + const manualLayout = tgpu.bindGroupLayout({ params: { uniform: d.f32 } }); + const manualBindGroup = root.createBindGroup(manualLayout, { + params: root.createBuffer(d.f32).$usage('uniform'), + }); + const fixedUniform = root.createUniform(d.f32); + + const sourcePipeline = root + .createComputePipeline({ + compute: tgpu.computeFn({ workgroupSize: [1] })(() => { + 'use gpu'; + fixedUniform.$; + manualLayout.$.params; + }), + }) + .with(manualBindGroup); + + const snapshot = snapshotResource(sourcePipeline); + if (snapshot?.type !== 'compute-pipeline') { + throw new Error('Expected a compute pipeline snapshot'); + } + + const pipeline = restoreResource(snapshot, { getRoot: () => root }) as TgpuComputePipeline; + + const bindGroups = snapshot.bindGroups ?? []; + const usedBindGroupLayouts = snapshot.usedBindGroupLayouts ?? []; + expect(snapshot.device).toBe(root.device); + expect(bindGroups).toHaveLength(2); + expect(bindGroups.some(([, bindGroup]) => bindGroup === manualBindGroup)).toBe(true); + + pipeline.dispatchWorkgroups(1); + + const computePass = commandEncoder.mock.beginComputePass.mock.results[0]!.value as { + setPipeline: ReturnType; + setBindGroup: ReturnType; + }; + + expect(computePass.setPipeline).toHaveBeenCalledWith(snapshot.raw); + for (const [layout, bindGroup] of bindGroups) { + expect(computePass.setBindGroup).toHaveBeenCalledWith( + usedBindGroupLayouts.indexOf(layout), + isBindGroup(bindGroup) ? root.unwrap(bindGroup) : bindGroup, + ); + } + }); + + it('should let .with() override preset bind groups on raw compute pipelines', ({ + root, + commandEncoder, + }) => { + const manualLayout = tgpu.bindGroupLayout({ params: { uniform: d.f32 } }); + const manualBindGroup = root.createBindGroup(manualLayout, { + params: root.createBuffer(d.f32).$usage('uniform'), + }); + const overrideBindGroup = root.createBindGroup(manualLayout, { + params: root.createBuffer(d.f32).$usage('uniform'), + }); + + const sourcePipeline = root + .createComputePipeline({ + compute: tgpu.computeFn({ workgroupSize: [1] })(() => { + 'use gpu'; + manualLayout.$.params; + }), + }) + .with(manualBindGroup); + + const snapshot = snapshotResource(sourcePipeline); + if (snapshot?.type !== 'compute-pipeline') { + throw new Error('Expected a compute pipeline snapshot'); + } + + const pipeline = ( + restoreResource(snapshot, { getRoot: () => root }) as TgpuComputePipeline + ).with(overrideBindGroup); + + pipeline.dispatchWorkgroups(1); + + const computePass = commandEncoder.mock.beginComputePass.mock.results[0]!.value as { + setBindGroup: ReturnType; + }; + + expect(computePass.setBindGroup).toHaveBeenCalledTimes(1); + expect(computePass.setBindGroup.mock.calls[0]![0]).toBe(0); + expect(computePass.setBindGroup.mock.calls[0]![1]).toBe(root.unwrap(overrideBindGroup)); + }); + it('enables language extensions when their corresponding feature is enabled', ({ root, device, diff --git a/packages/typegpu/tests/internal/typeGuards.test.ts b/packages/typegpu/tests/internal/typeGuards.test.ts new file mode 100644 index 0000000000..5ca8a52685 --- /dev/null +++ b/packages/typegpu/tests/internal/typeGuards.test.ts @@ -0,0 +1,42 @@ +import { describe, expect, it } from 'vitest'; +import { + isGPUCommandEncoder, + isGPUComputePassEncoder, + isGPURenderBundleEncoder, + isGPURenderPassEncoder, +} from '../../src/core/pipeline/typeGuards.ts'; + +// JSI HostObjects expose native methods through property lookup, but their `has` trap reports false +function jsiLike(methods: Record void>): object { + return new Proxy( + {}, + { + get: (_target, key) => methods[String(key)], + has: () => false, + }, + ); +} + +describe('pipeline WebGPU type guards', () => { + it('recognizes a JSI-like command encoder without relying on `in`', () => { + const encoder = jsiLike({ beginRenderPass() {}, beginComputePass() {} }); + + expect(isGPUCommandEncoder(encoder)).toBe(true); + expect(isGPUComputePassEncoder(encoder)).toBe(false); + }); + + it('recognizes JSI-like compute and render pass encoders', () => { + const computePass = jsiLike({ dispatchWorkgroups() {} }); + const renderPass = jsiLike({ executeBundles() {}, draw() {} }); + + expect(isGPUComputePassEncoder(computePass)).toBe(true); + expect(isGPURenderPassEncoder(renderPass)).toBe(true); + }); + + it('keeps a JSI-like render bundle distinct from a render pass', () => { + const bundle = jsiLike({ draw() {}, finish() {} }); + + expect(isGPURenderBundleEncoder(bundle)).toBe(true); + expect(isGPURenderPassEncoder(bundle)).toBe(false); + }); +}); diff --git a/packages/typegpu/tests/renderPipeline.test.ts b/packages/typegpu/tests/renderPipeline.test.ts index ffa8352de0..d3062e8851 100644 --- a/packages/typegpu/tests/renderPipeline.test.ts +++ b/packages/typegpu/tests/renderPipeline.test.ts @@ -3,6 +3,7 @@ import { tgpu, common, d, + isBindGroup, MissingBindGroupsError, type TgpuFragmentFn, type TgpuFragmentFnShell, @@ -11,6 +12,7 @@ import { type TgpuVertexFn, type TgpuVertexFnShell, } from 'typegpu'; +import { restoreResource, snapshotResource } from 'typegpu/~internal'; import { it } from 'typegpu-testing-utility'; describe('render pipeline behavior', () => { @@ -491,6 +493,50 @@ describe('render pipeline behavior', () => { expect(renderPassEncoder.setStencilReference).toHaveBeenNthCalledWith(2, 7); }); + it('should wrap raw render pipelines with bind groups', ({ root, renderPassEncoder }) => { + const manualLayout = tgpu.bindGroupLayout({ params: { uniform: d.f32 } }); + const manualBindGroup = root.createBindGroup(manualLayout, { + params: root.createBuffer(d.f32).$usage('uniform'), + }); + const fixedUniform = root.createUniform(d.f32); + + const sourcePipeline = root + .createRenderPipeline({ + vertex: common.fullScreenTriangle, + fragment: () => { + 'use gpu'; + return d.vec4f(fixedUniform.$, manualLayout.$.params, 0, 1); + }, + }) + .with(manualBindGroup); + + const snapshot = snapshotResource(sourcePipeline); + if (snapshot?.type !== 'render-pipeline') { + throw new Error('Expected a render pipeline snapshot'); + } + + const pipeline = ( + restoreResource(snapshot, { getRoot: () => root }) as TgpuRenderPipeline + ).withColorAttachment({ view: {} as unknown as GPUTextureView }); + + const bindGroups = snapshot.bindGroups ?? []; + const usedBindGroupLayouts = snapshot.usedBindGroupLayouts ?? []; + expect(snapshot.device).toBe(root.device); + expect(snapshot.fragmentOut).toEqual({ '~tgpuDataSchema': { type: 'd', key: 'vec4f' } }); + expect(bindGroups).toHaveLength(2); + expect(bindGroups.some(([, bindGroup]) => bindGroup === manualBindGroup)).toBe(true); + + pipeline.draw(3); + + expect(renderPassEncoder.mock.setPipeline).toHaveBeenCalledWith(snapshot.raw); + for (const [layout, bindGroup] of bindGroups) { + expect(renderPassEncoder.mock.setBindGroup).toHaveBeenCalledWith( + usedBindGroupLayouts.indexOf(layout), + isBindGroup(bindGroup) ? root.unwrap(bindGroup) : bindGroup, + ); + } + }); + it('should onlly allow for drawIndexed with assigned index buffer', ({ root }) => { const vertexFn = tgpu .vertexFn({ diff --git a/packages/typegpu/tests/root.test.ts b/packages/typegpu/tests/root.test.ts index 7698007def..e9f1164a93 100644 --- a/packages/typegpu/tests/root.test.ts +++ b/packages/typegpu/tests/root.test.ts @@ -24,6 +24,25 @@ describe('TgpuRoot', () => { `[Error: WebGPU is not supported by this browser.]`, ); }); + + it('does not forward optionalFeatures to requestDevice', async ({ adapter }) => { + const root = await tgpu.init({ + device: { optionalFeatures: ['timestamp-query'] }, + }); + + expect(adapter.requestDevice.mock.calls).toMatchInlineSnapshot(` + [ + [ + { + "requiredFeatures": [ + "timestamp-query", + ], + }, + ], + ] + `); + root.destroy(); + }); }); describe('.createBuffer', () => { diff --git a/packages/typegpu/tests/serial.test.ts b/packages/typegpu/tests/serial.test.ts new file mode 100644 index 0000000000..a6a051da1d --- /dev/null +++ b/packages/typegpu/tests/serial.test.ts @@ -0,0 +1,347 @@ +import { describe, expect, vi } from 'vitest'; +import { tgpu, d, type TgpuRoot } from 'typegpu'; +import { deepEqual } from 'typegpu/data'; +import { isNonTransferableResource, restoreResource, snapshotResource } from 'typegpu/~internal'; +import { it } from 'typegpu-testing-utility'; + +function roundTrip(value: T, root: TgpuRoot): T { + const snapshot = snapshotResource(value); + if (!snapshot) { + throw new Error('Expected the value to be snapshotable.'); + } + return restoreResource(snapshot, { + getRoot: (device) => { + expect(device).toBe(root.device); + return root; + }, + }) as T; +} + +/** Stands in for the remote-function proxy a host serializer makes of a function */ +function remoteStub(): () => never { + return () => { + throw new Error('Tried to call a remote function.'); + }; +} + +/** + * Mimics what a host serializer does with a snapshot: every field that is + * itself transferable is snapshotted too, so souls may hold live resources. + * Functions are claimed before anything else, exactly as host serializers do + */ +function deepRoundTrip(value: T, root: TgpuRoot): T { + const ctx = { getRoot: () => root }; + + function encodeField(field: unknown): unknown { + if (typeof field === 'function') { + return remoteStub(); + } + const snapshot = snapshotResource(field); + if (snapshot) { + return { transferred: encodeSnapshot(snapshot) }; + } + return Array.isArray(field) ? field.map(encodeField) : field; + } + + function encodeSnapshot(snapshot: object): Record { + return Object.fromEntries( + Object.entries(snapshot).map(([key, field]) => [key, encodeField(field)]), + ); + } + + function decodeField(field: unknown): unknown { + if (field && typeof field === 'object' && 'transferred' in field) { + const snapshot = field.transferred as Record; + return restoreResource(decodeSnapshot(snapshot) as never, ctx); + } + return Array.isArray(field) ? field.map(decodeField) : field; + } + + function decodeSnapshot(snapshot: Record): Record { + return Object.fromEntries( + Object.entries(snapshot).map(([key, field]) => [key, decodeField(field)]), + ); + } + + return decodeField(encodeField(value)) as T; +} + +describe('resource snapshot protocol', () => { + it('round-trips buffers and buffer bindings', ({ root }) => { + const buffer = root.createBuffer(d.arrayOf(d.u32, 3)).$usage('storage', 'indirect'); + const rawBuffer = root.unwrap(buffer); + + const restored = roundTrip(buffer, root); + expect(restored.usableAsUniform).toBe(false); + expect(restored.usableAsStorage).toBe(true); + expect(restored.usableAsVertex).toBe(false); + expect(restored.usableAsIndex).toBe(false); + expect(restored.usableAsIndirect).toBe(true); + expect(root.unwrap(restored)).toBe(rawBuffer); + + const uniform = root.createUniform(d.vec2f, d.vec2f(1, 2)); + const restoredUniform = roundTrip(uniform, root); + expect(restoredUniform.resourceType).toBe('uniform'); + expect(root.unwrap(restoredUniform.buffer)).toBe(root.unwrap(uniform.buffer)); + }); + + it('round-trips bind group layouts, bind groups and textures', ({ root }) => { + const layout = tgpu + .bindGroupLayout({ + video: { externalTexture: d.textureExternal(), visibility: ['fragment'] }, + color: { + texture: d.texture2d(d.f32), + sampleType: 'unfilterable-float', + visibility: ['fragment'], + }, + target: { + storageTexture: d.textureStorage3d('rgba8unorm', 'read-write'), + visibility: ['compute'], + }, + cells: { storage: d.arrayOf(d.vec4f), access: 'mutable', visibility: ['compute'] }, + }) + .$idx(2); + + const restoredLayout = roundTrip(layout, root); + expect(restoredLayout.index).toBe(2); + const { cells, ...staticEntries } = restoredLayout.entries; + const { cells: _, ...originalStaticEntries } = layout.entries; + expect(staticEntries).toEqual(originalStaticEntries); + if (cells?.storage && 'type' in cells.storage && d.isWgslArray(cells.storage)) { + expect(cells.storage.elementCount).toBe(0); + expect(cells.storage.elementType).toBe(d.vec4f); + } else { + throw new Error('Expected a runtime-sized array storage layout entry.'); + } + + const groupLayout = tgpu.bindGroupLayout({ + values: { storage: d.arrayOf(d.u32, 4), access: 'mutable' }, + }); + const buffer = root.createBuffer(d.arrayOf(d.u32, 4)).$usage('storage'); + const bindGroup = root.createBindGroup(groupLayout, { values: buffer }); + const restoredGroup = roundTrip(bindGroup, root); + expect(restoredGroup.resourceType).toBe('bind-group'); + expect(restoredGroup.unwrap(root)).toBe(root.unwrap(bindGroup)); + + const texture = root + .createTexture({ size: [2, 2], format: 'rgba8unorm' }) + .$usage('sampled', 'render'); + const rawTexture = root.unwrap(texture); + const restoredTexture = roundTrip(texture, root); + expect(restoredTexture.props).toEqual(texture.props); + expect(restoredTexture.usableAsSampled).toBe(true); + expect(restoredTexture.usableAsStorage).toBe(false); + expect(restoredTexture.usableAsRender).toBe(true); + expect(root.unwrap(restoredTexture)).toBe(rawTexture); + }); + + it('round-trips compute, render and guarded compute pipelines', ({ root }) => { + const querySet = root.createQuerySet('timestamp', 2); + const callback = vi.fn(); + const computePipeline = root + .createComputePipeline({ + compute: tgpu.computeFn({ workgroupSize: [1] })(() => { + 'use gpu'; + }), + }) + .withTimestampWrites({ + querySet, + beginningOfPassWriteIndex: 0, + endOfPassWriteIndex: 1, + }) + .withPerformanceCallback(callback); + + const computeSnapshot = snapshotResource(roundTrip(computePipeline, root)); + if (computeSnapshot?.type !== 'compute-pipeline') { + throw new Error('Expected a compute pipeline snapshot'); + } + expect(computeSnapshot.performanceCallback).toBe(callback); + expect(computeSnapshot.timestampWrites?.beginningOfPassWriteIndex).toBe(0); + expect(computeSnapshot.timestampWrites?.endOfPassWriteIndex).toBe(1); + + const vertexLayout = tgpu.vertexLayout(d.arrayOf(d.vec2f)); + const vertexBuffer = root.createBuffer(vertexLayout.schemaForCount(3)).$usage('vertex'); + const shelledFragment = tgpu.fragmentFn({ out: d.vec4f })(() => { + 'use gpu'; + return d.vec4f(1, 0, 0, 1); + }); + const renderPipeline = root + .createRenderPipeline({ + attribs: { position: vertexLayout.attrib }, + vertex: ({ position }) => { + 'use gpu'; + return { $position: d.vec4f(position, 0, 1) }; + }, + fragment: shelledFragment, + targets: { format: 'rgba8unorm' }, + }) + .with(vertexLayout, vertexBuffer); + + const renderSnapshot = snapshotResource(roundTrip(renderPipeline, root)); + if (renderSnapshot?.type !== 'render-pipeline') { + throw new Error('Expected a render pipeline snapshot'); + } + // Shelled fragments carry their output on the descriptor, not the memo, + // and it leaves as a description since every schema is callable + expect(renderSnapshot.fragmentOut).toMatchInlineSnapshot(` + { + "~tgpuDataSchema": { + "attribs": [ + { + "type": "location", + "value": 0, + }, + ], + "inner": { + "key": "vec4f", + "type": "d", + }, + "type": "decorated", + }, + } + `); + expect(renderSnapshot.usedVertexLayouts).toEqual([vertexLayout]); + expect(renderSnapshot.vertexBuffers).toEqual([[vertexLayout, vertexBuffer]]); + + const groupLayout = tgpu.bindGroupLayout({ + values: { storage: d.arrayOf(d.u32, 4), access: 'mutable' }, + }); + const buffer = root.createBuffer(d.arrayOf(d.u32, 4)).$usage('storage'); + const bindGroup = root.createBindGroup(groupLayout, { values: buffer }); + const guarded = root + .createGuardedComputePipeline((x: number, y: number) => { + 'use gpu'; + groupLayout.$.values[x + y] = x; + }) + .with(bindGroup); + + const guardedSnapshot = snapshotResource(roundTrip(guarded, root)); + if (guardedSnapshot?.type !== 'guarded-compute-pipeline') { + throw new Error('Expected a guarded compute pipeline snapshot'); + } + expect(guardedSnapshot.workgroupSize).toEqual(d.vec3u(16, 16, 1)); + expect(guardedSnapshot.sizeUniform.resourceType).toBe('uniform'); + const innerSnapshot = snapshotResource(guardedSnapshot.pipeline); + if (innerSnapshot?.type !== 'compute-pipeline') { + throw new Error('Expected a compute pipeline snapshot'); + } + expect((innerSnapshot.bindGroups ?? []).some(([, group]) => group === bindGroup)).toBe(true); + }); + + it('round-trips slots, accessors, consts, samplers, query sets and vertex layouts', ({ + root, + }) => { + const slot = tgpu.slot(42); + const restoredSlot = roundTrip(slot, root); + expect(restoredSlot.resourceType).toBe('slot'); + expect(restoredSlot.defaultValue).toBe(42); + + const accessor = tgpu.accessor(d.vec3f, d.vec3f(1, 2, 3)); + const restoredAccessor = roundTrip(accessor, root); + expect(restoredAccessor.resourceType).toBe('accessor'); + expect(restoredAccessor.schema).toBe(d.vec3f); + expect(restoredAccessor.defaultValue).toEqual(d.vec3f(1, 2, 3)); + + const constant = tgpu['~unstable'].const(d.arrayOf(d.f32, 3), [1, 2, 3]); + const restoredConst = roundTrip(constant, root); + expect(restoredConst.resourceType).toBe('const'); + expect(restoredConst.$).toEqual([1, 2, 3]); + + const sampler = root.createSampler({ magFilter: 'linear', minFilter: 'linear' }); + expect(roundTrip(sampler, root).resourceType).toBe('sampler'); + const comparison = root.createComparisonSampler({ compare: 'less' }); + expect(roundTrip(comparison, root).resourceType).toBe('sampler-comparison'); + + const querySet = root.createQuerySet('timestamp', 2); + const restoredQuerySet = roundTrip(querySet, root); + expect(restoredQuerySet.resourceType).toBe('query-set'); + expect(restoredQuerySet.type).toBe('timestamp'); + expect(restoredQuerySet.count).toBe(2); + expect(restoredQuerySet.querySet).toBe(querySet.querySet); + + const vertexLayout = tgpu.vertexLayout( + (count) => d.arrayOf(d.struct({ position: d.location(0, d.vec2f) }), count), + 'instance', + ); + const restoredVertexLayout = roundTrip(vertexLayout, root); + expect(restoredVertexLayout.resourceType).toBe('vertex-layout'); + expect(restoredVertexLayout.stepMode).toBe(vertexLayout.stepMode); + expect(restoredVertexLayout.stride).toBe(vertexLayout.stride); + expect(deepEqual(restoredVertexLayout.schemaForCount(4), vertexLayout.schemaForCount(4))).toBe( + true, + ); + }); + + it('transfers souls that hold live schemas and live resources', ({ root }) => { + const Particle = d.struct({ pos: d.vec2f, alive: d.u32 }); + const buffer = root.createBuffer(d.arrayOf(Particle, 2)).$usage('storage'); + const restoredBuffer = deepRoundTrip(buffer, root); + expect(d.deepEqual(restoredBuffer.dataType, buffer.dataType)).toBe(true); + expect(restoredBuffer.dataType).not.toBe(buffer.dataType); + expect(root.unwrap(restoredBuffer)).toBe(root.unwrap(buffer)); + + const layout = tgpu + .bindGroupLayout({ + color: { texture: d.texture2d(d.f32), visibility: ['fragment'] }, + target: { storageTexture: d.textureStorage2d('rgba8unorm', 'write-only') }, + params: { uniform: Particle }, + }) + .$idx(1); + const restoredLayout = deepRoundTrip(layout, root); + expect(restoredLayout.index).toBe(1); + expect(d.deepEqual(restoredLayout.entries.color.texture, layout.entries.color.texture)).toBe( + true, + ); + expect( + d.deepEqual( + restoredLayout.entries.target.storageTexture, + layout.entries.target.storageTexture, + ), + ).toBe(true); + expect(d.deepEqual(restoredLayout.entries.params.uniform, Particle)).toBe(true); + + const groupLayout = tgpu.bindGroupLayout({ values: { storage: d.arrayOf(d.u32, 4) } }); + const bindGroup = root.createBindGroup(groupLayout, { + values: root.createBuffer(d.arrayOf(d.u32, 4)).$usage('storage'), + }); + const restoredGroup = deepRoundTrip(bindGroup, root); + expect(restoredGroup.unwrap(root)).toBe(root.unwrap(bindGroup)); + expect(restoredGroup.layout).not.toBe(groupLayout); + expect(restoredGroup.layout.resourceType).toBe('bind-group-layout'); + }); + + it('round-trips vector and matrix instances, rejects non-transferable resources', ({ root }) => { + const vec = d.vec3f(1.5, -2, 3.25); + const restoredVec = roundTrip(vec, root); + expect(restoredVec).not.toBe(vec); + expect(restoredVec).toEqual(vec); + + const mat = d.mat3x3f(1, 2, 3, 4, 5, 6, 7, 8, 9); + expect(roundTrip(mat, root)).toEqual(mat); + + const view = root + .createTexture({ size: [2, 2], format: 'rgba8unorm' }) + .$usage('sampled') + .createView(); + expect(snapshotResource(view)).toBeUndefined(); + expect(isNonTransferableResource(view)).toBe(true); + + const pipeline = root + .createRenderPipeline({ + vertex: () => { + 'use gpu'; + return { $position: d.vec4f(0, 0, 0, 1) }; + }, + fragment: () => { + 'use gpu'; + return d.vec4f(1, 0, 0, 1); + }, + targets: { format: 'rgba8unorm' }, + }) + .withColorAttachment({ view: {} as unknown as GPUTextureView }); + + expect(() => snapshotResource(pipeline)).toThrowErrorMatchingInlineSnapshot( + `[Error: TypeGPU 'render-pipeline' cannot be transferred: colorAttachment is bound to this runtime. Apply them after the resource crosses the boundary.]`, + ); + }); +}); diff --git a/packages/typegpu/tests/serializeDataSchema.test.ts b/packages/typegpu/tests/serializeDataSchema.test.ts new file mode 100644 index 0000000000..84377221c9 --- /dev/null +++ b/packages/typegpu/tests/serializeDataSchema.test.ts @@ -0,0 +1,283 @@ +import { describe, expect, it } from 'vitest'; +import { d } from 'typegpu'; +import { deepEqual } from 'typegpu/data'; +import { deserializeDataSchema, serializeDataSchema } from 'typegpu/~internal'; + +const schemas = [ + d.f32, + d.vec3f, + d.arrayOf( + d.struct({ + position: d.vec3f, + life: d.f32, + }), + 2, + ), + d.disarrayOf(d.unstruct({ id: d.u32, packed: d.uint16x2 }), 3), + d.atomic(d.u32), + d.align(16, d.size(32, d.struct({ value: d.vec2f }))), + d.location(2, d.vec4f), + d.interpolate('linear, sample', d.vec2f), + d.interpolate('flat, either', d.u32), + d.builtin.vertexIndex, + d.builtin.position, + d.invariant(d.builtin.position), + d.struct({ + position: d.invariant(d.builtin.position), + color: d.location(0, d.interpolate('linear, centroid', d.vec4f)), + index: d.location(1, d.interpolate('flat, either', d.u32)), + }), +]; + +describe('data schema serialization', () => { + it('serializes transferable schemas', () => { + expect(schemas.map(serializeDataSchema)).toMatchInlineSnapshot(` + [ + { + "key": "f32", + "type": "d", + }, + { + "key": "vec3f", + "type": "d", + }, + { + "count": 2, + "element": { + "props": [ + [ + "position", + { + "key": "vec3f", + "type": "d", + }, + ], + [ + "life", + { + "key": "f32", + "type": "d", + }, + ], + ], + "type": "struct", + }, + "type": "array", + }, + { + "count": 3, + "element": { + "props": [ + [ + "id", + { + "key": "u32", + "type": "d", + }, + ], + [ + "packed", + { + "key": "uint16x2", + "type": "d", + }, + ], + ], + "type": "unstruct", + }, + "type": "disarray", + }, + { + "inner": { + "key": "u32", + "type": "d", + }, + "type": "atomic", + }, + { + "attribs": [ + { + "type": "align", + "value": 16, + }, + { + "type": "size", + "value": 32, + }, + ], + "inner": { + "props": [ + [ + "value", + { + "key": "vec2f", + "type": "d", + }, + ], + ], + "type": "struct", + }, + "type": "decorated", + }, + { + "attribs": [ + { + "type": "location", + "value": 2, + }, + ], + "inner": { + "key": "vec4f", + "type": "d", + }, + "type": "decorated", + }, + { + "attribs": [ + { + "type": "interpolate", + "value": "linear, sample", + }, + ], + "inner": { + "key": "vec2f", + "type": "d", + }, + "type": "decorated", + }, + { + "attribs": [ + { + "type": "interpolate", + "value": "flat, either", + }, + ], + "inner": { + "key": "u32", + "type": "d", + }, + "type": "decorated", + }, + { + "attribs": [ + { + "type": "builtin", + "value": "vertex_index", + }, + ], + "inner": { + "key": "u32", + "type": "d", + }, + "type": "decorated", + }, + { + "attribs": [ + { + "type": "builtin", + "value": "position", + }, + ], + "inner": { + "key": "vec4f", + "type": "d", + }, + "type": "decorated", + }, + { + "attribs": [ + { + "type": "invariant", + }, + { + "type": "builtin", + "value": "position", + }, + ], + "inner": { + "key": "vec4f", + "type": "d", + }, + "type": "decorated", + }, + { + "props": [ + [ + "position", + { + "attribs": [ + { + "type": "invariant", + }, + { + "type": "builtin", + "value": "position", + }, + ], + "inner": { + "key": "vec4f", + "type": "d", + }, + "type": "decorated", + }, + ], + [ + "color", + { + "attribs": [ + { + "type": "location", + "value": 0, + }, + { + "type": "interpolate", + "value": "linear, centroid", + }, + ], + "inner": { + "key": "vec4f", + "type": "d", + }, + "type": "decorated", + }, + ], + [ + "index", + { + "attribs": [ + { + "type": "location", + "value": 1, + }, + { + "type": "interpolate", + "value": "flat, either", + }, + ], + "inner": { + "key": "u32", + "type": "d", + }, + "type": "decorated", + }, + ], + ], + "type": "struct", + }, + ] + `); + }); + + it('round-trips transferable schemas', () => { + for (const [index, schema] of schemas.entries()) { + const restored = deserializeDataSchema(serializeDataSchema(schema)); + expect(deepEqual(restored, schema), `schema #${index} (${schema.type})`).toBe(true); + } + }); + + it('rejects unsupported schemas', () => { + expect(() => serializeDataSchema(d.ptrFn(d.f32))).toThrowErrorMatchingInlineSnapshot( + `[Error: TypeGPU schema 'ptr' cannot be serialized yet.]`, + ); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 26295c0a2e..410ab11bb8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -353,10 +353,10 @@ importers: version: 7.1.0 jotai: specifier: ^2.15.0 - version: 2.15.0(@babel/core@7.29.0)(@babel/template@7.28.6)(@types/react@19.2.14)(react@19.2.6) + version: 2.15.0(@babel/core@7.29.0)(@babel/template@7.29.7)(@types/react@19.2.14)(react@19.2.6) jotai-location: specifier: ^0.6.2 - version: 0.6.2(jotai@2.15.0(@babel/core@7.29.0)(@babel/template@7.28.6)(@types/react@19.2.14)(react@19.2.6)) + version: 0.6.2(jotai@2.15.0(@babel/core@7.29.0)(@babel/template@7.29.7)(@types/react@19.2.14)(react@19.2.6)) lodash: specifier: ^4.18.1 version: 4.18.1 @@ -852,7 +852,7 @@ importers: dependencies: react-native-webgpu: specifier: '*' - version: 0.5.15(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.1.8)(react@19.2.6))(react@19.2.6) + version: 0.5.15(react-native-worklets@0.10.2(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(react-native@0.84.1(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(@types/react@19.1.8)(react@19.2.6))(react@19.2.6))(react-native@0.84.1(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(@types/react@19.1.8)(react@19.2.6))(react@19.2.6) devDependencies: '@testing-library/dom': specifier: ^10.4.1 @@ -886,7 +886,10 @@ importers: version: 19.2.6(react@19.2.6) react-native: specifier: 0.84.1 - version: 0.84.1(@babel/core@7.29.0)(@types/react@19.1.8)(react@19.2.6) + version: 0.84.1(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(@types/react@19.1.8)(react@19.2.6) + react-native-worklets: + specifier: 0.10.2 + version: 0.10.2(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(react-native@0.84.1(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(@types/react@19.1.8)(react@19.2.6))(react@19.2.6) tsdown: specifier: catalog:build version: 0.15.12(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(tsover@6.0.2)(unrun@0.2.31(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)) @@ -1190,10 +1193,18 @@ packages: resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} engines: {node: '>=6.9.0'} + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + '@babel/compat-data@7.29.0': resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} engines: {node: '>=6.9.0'} + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} + engines: {node: '>=6.9.0'} + '@babel/core@7.29.0': resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} engines: {node: '>=6.9.0'} @@ -1202,28 +1213,79 @@ packages: resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} engines: {node: '>=6.9.0'} + '@babel/generator@7.29.7': + resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==} + engines: {node: '>=6.9.0'} + '@babel/generator@8.0.0-rc.2': resolution: {integrity: sha512-oCQ1IKPwkzCeJzAPb7Fv8rQ9k5+1sG8mf2uoHiMInPYvkRfrDJxbTIbH51U+jstlkghus0vAi3EBvkfvEsYNLQ==} engines: {node: ^20.19.0 || >=22.12.0} + '@babel/helper-annotate-as-pure@7.29.7': + resolution: {integrity: sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==} + engines: {node: '>=6.9.0'} + '@babel/helper-compilation-targets@7.28.6': resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} engines: {node: '>=6.9.0'} + '@babel/helper-compilation-targets@7.29.7': + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-create-class-features-plugin@7.29.7': + resolution: {integrity: sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-create-regexp-features-plugin@7.29.7': + resolution: {integrity: sha512-907Uymvqgg1dwUA+7IGwFAOSYzQOuzPXKNJ1yxzwPffzkYFg2q2eHi1fIOs6sXkG9NbIUMunnUlkYsfRFNvomg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-define-polyfill-provider@0.6.8': + resolution: {integrity: sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + '@babel/helper-globals@7.28.0': resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} engines: {node: '>=6.9.0'} + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-member-expression-to-functions@7.29.7': + resolution: {integrity: sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==} + engines: {node: '>=6.9.0'} + '@babel/helper-module-imports@7.28.6': resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} engines: {node: '>=6.9.0'} + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} + engines: {node: '>=6.9.0'} + '@babel/helper-module-transforms@7.28.6': resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 + '@babel/helper-module-transforms@7.29.7': + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-optimise-call-expression@7.29.7': + resolution: {integrity: sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==} + engines: {node: '>=6.9.0'} + '@babel/helper-plugin-utils@7.27.1': resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} engines: {node: '>=6.9.0'} @@ -1232,6 +1294,26 @@ packages: resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} engines: {node: '>=6.9.0'} + '@babel/helper-plugin-utils@7.29.7': + resolution: {integrity: sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-remap-async-to-generator@7.29.7': + resolution: {integrity: sha512-16AMiW26DbXWBbr3B8wNozKM0ydMLB892vaOaJW/fPJdnT8vJk5sdkQcU/isqUxyCE0cEoa8wZOcbgDuC4b6Og==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-replace-supers@7.29.7': + resolution: {integrity: sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-skip-transparent-expression-wrappers@7.29.7': + resolution: {integrity: sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==} + engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.27.1': resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} @@ -1240,12 +1322,20 @@ packages: resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@8.0.0-rc.2': resolution: {integrity: sha512-xExUBkuXWJjVuIbO7z6q7/BA9bgfJDEhVL0ggrggLMbg0IzCUWGT1hZGE8qUH7Il7/RD/a6cZ3AAFrrlp1LF/A==} engines: {node: ^20.19.0 || >=22.12.0} - '@babel/helper-validator-option@7.27.1': - resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + '@babel/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-wrap-function@7.29.7': + resolution: {integrity: sha512-iES0Skag9ERIF68aXadpO6dbXa03mNWK3sEqJaMnLNs/eC3l0lkImdfoy6Y09/SfkpawdAB4RjQ7PVA7TcVGdw==} engines: {node: '>=6.9.0'} '@babel/helpers@7.28.6': @@ -1282,6 +1372,12 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true + '@babel/plugin-proposal-export-default-from@7.29.7': + resolution: {integrity: sha512-p+G5BNXDcy3bOXplhY4HybQ1GxH3i2Tppmdm/3epyRu2VgJJZuUlZ61MqRTg582Q7ZLBdP7fePYvsumSEkMxcQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-async-generators@7.8.4': resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} peerDependencies: @@ -1303,6 +1399,23 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-dynamic-import@7.8.3': + resolution: {integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-export-default-from@7.29.7': + resolution: {integrity: sha512-foag0BB37ROhdeIX9O8G0jX7hw0UekJc04cHMrYLOnrErsnBKqJGHJ8eDRpoCFZBvEPPygmmtw4qyU97qa4oOw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-flow@7.29.7': + resolution: {integrity: sha512-ajMX6QPcyomotqwpzhkYGxcK2i/us0rs1Qo9QvUpa+Fca0FTmqrzKrctoIYLMxcOhGZldGT/BAVkRGTWBiR8gQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-import-attributes@7.28.6': resolution: {integrity: sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==} engines: {node: '>=6.9.0'} @@ -1319,6 +1432,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-jsx@7.29.7': + resolution: {integrity: sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-logical-assignment-operators@7.10.4': resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} peerDependencies: @@ -1361,6 +1480,114 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-typescript@7.29.7': + resolution: {integrity: sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-arrow-functions@7.29.7': + resolution: {integrity: sha512-N7zArUXWzAMzm+/N0uPBeVB3Fam5lMxtUwMmDK5f/IBBS7a7p1qeUoxd/6CckXoxUdgsntq1Dh8xNW06maZbDQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-async-generator-functions@7.29.7': + resolution: {integrity: sha512-d98gXZkgswvkyohMBABkhm3GeXhYj8psWfwQ2C7gtfrKGTykQa/iOIi+JJhwMjPlZ6Vm2XN+DCf3Es1EoG4ZLA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-async-to-generator@7.29.7': + resolution: {integrity: sha512-pcUb2SS+RMo9TWVBwKGI5ShtoG7R+zBsFmCKDa6fe8c+hPr3XJlZgoE5j6i8W7gDjhyvy+85vmYexanvXh3d1w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-block-scoping@7.29.7': + resolution: {integrity: sha512-ONyr4+AZhKh8yKWInVxU9AXA9EbsyeLcL6V0dJy6M2/62vuvpGm29zzuymbTpdc451GEpDIdAyPLP3r+P61yKQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-class-properties@7.29.7': + resolution: {integrity: sha512-GtcpjFvanPfzNQi3eTitsCqtRRmmqzpy/A+yhTR1HaZo1Ly3EA8ZXxlPyHdR8/IuRMYc3E4wdGBewB2QKQjAaA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-classes@7.29.7': + resolution: {integrity: sha512-qV0OGGBVacduzQHE649JyCneOFI/maT+YKsO+K4Yi3xv2wTPNjM/W2o2gdzMwEAZz7fXNTHAe0NcSg30bIN69g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-destructuring@7.29.7': + resolution: {integrity: sha512-iPX8aD6H9zV5s7ZsqTdNocPN/MGQ5sSMnElKrktxjJRMnB2jN/1p2+R7GkfD6CAYoVFqy5A4XnSIUeGgJzIWpg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-flow-strip-types@7.29.7': + resolution: {integrity: sha512-wRHeUjUjCZnMHmiO5bRgjFLcoEh7JyTdByOW11ahhwNa4V0bmeGEaIvt51yq0zQp2yWIpqfxXXPyUP6GFJZHOQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-for-of@7.29.7': + resolution: {integrity: sha512-zeSIHh0+E1Um1WJRXCFlHQYu2ieJNdivLLjlBEp+dIBu3S51n+SZZmIXjxnItw6pz56Cn+KvK68BIBVsxq2JiQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-commonjs@7.29.7': + resolution: {integrity: sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-named-capturing-groups-regex@7.29.7': + resolution: {integrity: sha512-vuFoLwr4qnv2xbZ16SQd6uPcH5FNrLHhk/Jzo++0XJFcaDsr4gjJVg6j398oMHiC+83k/GiBzviwF5KBJkPUtQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-nullish-coalescing-operator@7.29.7': + resolution: {integrity: sha512-idmp1dFaekP9GbcMvG24Kvw2BfhFZjHnNJCkV4WuIY4PskJzwI3f1N5OdgYke38T7rftO6ERulFRn2cFeZwRkg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-optional-catch-binding@7.29.7': + resolution: {integrity: sha512-sLsyndxK2VwX6yNUOakMb7Sh553ZTe/vVM1XJ+9Z5aW1ytsc8xOIwmyk05NNjN60vkc5/KqoTH6hB4V41LJhng==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-optional-chaining@7.29.7': + resolution: {integrity: sha512-6GM1dhvK3gNODkXcEcMCOLEDCLSoZ/sBbro2Ax8HURyasQ4NshagQixkRFdh5niI6E4gmA/jYI/4aT7rRos3ZQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-private-methods@7.29.7': + resolution: {integrity: sha512-/6Rz4DK1ETDEM/bWHsPHcaEe7ZaT1EqSXjtSP/L0DijOYuaUhiRiOKcwpZ8P7zR4xXEHc2ITdiCgBm9Tpyv9ug==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-private-property-in-object@7.29.7': + resolution: {integrity: sha512-+BNo06dnrzdNNqCm1X6YUaVv0DKk8Q+JYcoZfOkLhYWNCXzlwTSRq8zGWayT1csjcpNXV9CQTBRRbmTLZac5cA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-display-name@7.29.7': + resolution: {integrity: sha512-+1wdDMGNb4UPeY3Q4L5yLiYe6TXPXubs4NjrgRFw13hPRLJfEMw2Q5OXkee6/IfdqePIeW4Jjwe3aBh7SdKz4Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-react-jsx-self@7.27.1': resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} engines: {node: '>=6.9.0'} @@ -1373,6 +1600,54 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-react-jsx@7.29.7': + resolution: {integrity: sha512-WsZulLVBUHXVj2cUcPVx6UE21TpalB6bHbSFErKT0Ib++ax24jjXe73FqlWvdylFOjiuPHYi6VCcgRad1ItN+A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-regenerator@7.29.7': + resolution: {integrity: sha512-rNNFV0DBAJp988xW2DOntfDoYn1eR8GGF5AT5vYc+rjyfaQkM242c9tZUHHPe7KYaiJizXPWhQTzzdbXySyhBw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-runtime@7.29.7': + resolution: {integrity: sha512-xmAscdE/AsqRW7vutbPNoUmu/nF5SrLKPs7aoJgEjo35lLKA/Bc0i2rMv/hr1+Y0o1bQCiVtith3u2vdgRL39Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-shorthand-properties@7.29.7': + resolution: {integrity: sha512-I+WYbGBAiCn7nA6xBrlgPH+MB7HWb4u8pv5S0Pv7OtwNvIFvCCb24YlttKEeUFVurfBCEaOTnuhlqsb7f0Z5Dg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-template-literals@7.29.7': + resolution: {integrity: sha512-NCSEJ4sLFU2gqAub45HYh4fus2yQ36rr6ei6vpU7NdoJqCpxvEG8E6eJpscGyXP3VHD2Ny+fSXr04k1hoUrFqA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typescript@7.29.7': + resolution: {integrity: sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-regex@7.29.7': + resolution: {integrity: sha512-7D/x/23/d/3VqZ0QA+LGbZMlGwZjztBygSWWWsfTPoQ1oQ6Q1P6Mr3d0kk42XabyUVw+fha3LqdRsFqeKqvCyA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/preset-typescript@7.29.7': + resolution: {integrity: sha512-/Foi8vKY2EVbed/1eZx0gJEEwHAIxogrySI7rULcRIvhZzbvoE/b5qG5Ghc0WKAFKOHA9SD1x7RsFlOYdutIiQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/runtime@7.26.9': resolution: {integrity: sha512-aA63XwOkcl4xxQa3HjPMqOP6LiK0ZDv3mUPYEFXkpHbaFjtGggE1A61FjFzJnB+p7/oy2gA8E+rcBNl/zC1tMg==} engines: {node: '>=6.9.0'} @@ -1385,10 +1660,18 @@ packages: resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} engines: {node: '>=6.9.0'} + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} + engines: {node: '>=6.9.0'} + '@babel/traverse@7.29.0': resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} engines: {node: '>=6.9.0'} + '@babel/traverse@7.29.7': + resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==} + engines: {node: '>=6.9.0'} + '@babel/types@7.29.0': resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} @@ -3326,12 +3609,28 @@ packages: resolution: {integrity: sha512-lAJ6PDZv95FdT9s9uhc9ivhikW1Zwh4j9XdXM7J2l4oUA3t37qfoBmTSDLuPyE3Bi+Xtwa11hJm0BUTT2sc/gg==} engines: {node: '>= 20.19.4'} + '@react-native/babel-plugin-codegen@0.86.0': + resolution: {integrity: sha512-qdsABWNW7uTll90l4Vh03gjeyu3WVDi2CyiiyvYGMRDcoYbjbQi6df3BMAm9lQI2yslZ1T14LlDDAsgTwNxplA==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + '@react-native/babel-preset@0.86.0': + resolution: {integrity: sha512-bYQcWiPySNvF4dns9Ls9gMmwgq66ohvM9Fwc/Kn8r85t66UNHxch3p1QwPiSorDelFauZwJbgo9+ReibTgvpbA==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + peerDependencies: + '@babel/core': '*' + '@react-native/codegen@0.84.1': resolution: {integrity: sha512-n1RIU0QAavgCg1uC5+s53arL7/mpM+16IBhJ3nCFSd/iK5tUmCwxQDcIDC703fuXfpub/ZygeSjVN8bcOWn0gA==} engines: {node: '>= 20.19.4'} peerDependencies: '@babel/core': '*' + '@react-native/codegen@0.86.0': + resolution: {integrity: sha512-uTs9DBo3+/lUqinsGZK0FKJRBVClrwMXoZToaDxE1Q2SL2e55vs2GwyZfIKzPl5uJnbu4PfFMIp0/mLXLWUMuA==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + peerDependencies: + '@babel/core': '*' + '@react-native/community-cli-plugin@0.84.1': resolution: {integrity: sha512-f6a+mJEJ6Joxlt/050TqYUr7uRRbeKnz8lnpL7JajhpsgZLEbkJRjH8HY5QiLcRdUwWFtizml4V+vcO3P4RxoQ==} engines: {node: '>= 20.19.4'} @@ -3364,6 +3663,20 @@ packages: resolution: {integrity: sha512-UsTe2AbUugsfyI7XIHMQq4E7xeC8a6GrYwuK+NohMMMJMxmyM3JkzIk+GB9e2il6ScEQNMJNaj+q+i5za8itxQ==} engines: {node: '>= 20.19.4'} + '@react-native/js-polyfills@0.86.0': + resolution: {integrity: sha512-zYy/Cjd1VTnZ2iCNaG9bDF9C3l2ntESiPRscjIlI5FKugu6aeTwsDSv1aI8Bc4Kp3vEdoVg+UQhLAhE4svREaQ==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + '@react-native/metro-babel-transformer@0.86.0': + resolution: {integrity: sha512-SjKej3E5qIahqo/G+rSOrmJUQM44RyKtWtO+VfmKAAMoJWkBFomM22hTLKCIS5cdbIAJ9COAmU+KAi2wVSO0wQ==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + peerDependencies: + '@babel/core': '*' + + '@react-native/metro-config@0.86.0': + resolution: {integrity: sha512-7v+xbTeEci9ZcQ/Z1OqI4RXcqN69wSMDYL5BAMvOReZ7U04+aDQ0/SQhClYPn6x2/RxM4WzMKSAuNyLKqvYVtw==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + '@react-native/normalize-colors@0.84.1': resolution: {integrity: sha512-/UPaQ4jl95soXnLDEJ6Cs6lnRXhwbxtT4KbZz+AFDees7prMV2NOLcHfCnzmTabf5Y3oxENMVBL666n4GMLcTA==} @@ -4866,9 +5179,30 @@ packages: resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + babel-plugin-polyfill-corejs2@0.4.17: + resolution: {integrity: sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-corejs3@0.13.0: + resolution: {integrity: sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-regenerator@0.6.8: + resolution: {integrity: sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + babel-plugin-syntax-hermes-parser@0.32.0: resolution: {integrity: sha512-m5HthL++AbyeEA2FcdwOLfVFvWYECOBObLHNqdR8ceY4TsEdn4LdX2oTvbB2QJSSElE2AWA/b2MXZ/PF/CqLZg==} + babel-plugin-syntax-hermes-parser@0.36.0: + resolution: {integrity: sha512-LhD0xdoedDw7ansQgXbB2DADLZIK/LRXuWNBPuVzMc5S2WK5GyT89tCM+cQzxFGO0mGyLK6D5TrVOJJzAoDy8Q==} + + babel-plugin-transform-flow-enums@0.0.2: + resolution: {integrity: sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ==} + babel-preset-current-node-syntax@1.2.0: resolution: {integrity: sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==} peerDependencies: @@ -5202,6 +5536,9 @@ packages: resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} engines: {node: '>=18'} + core-js-compat@3.49.0: + resolution: {integrity: sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==} + cosmiconfig@9.0.1: resolution: {integrity: sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==} engines: {node: '>=14'} @@ -6129,12 +6466,24 @@ packages: hermes-estree@0.33.3: resolution: {integrity: sha512-6kzYZHCk8Fy1Uc+t3HGYyJn3OL4aeqKLTyina4UFtWl8I0kSL7OmKThaiX+Uh2f8nGw3mo4Ifxg0M5Zk3/Oeqg==} + hermes-estree@0.35.0: + resolution: {integrity: sha512-xVx5Opwy8Oo1I5yGpVRhCvWL/iV3M+ylksSKVNlxxD90cpDpR/AR1jLYqK8HWihm065a6UI3HeyAmYzwS8NOOg==} + + hermes-estree@0.36.0: + resolution: {integrity: sha512-A1+8zn5oss2CFP7pKsOaxorQG6FNIz1WU1VDqruLPPZl3LVgeE2C5xfFg8Ow6/Ow4mSslLLtYP1J3n38eKyW9w==} + hermes-parser@0.32.0: resolution: {integrity: sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw==} hermes-parser@0.33.3: resolution: {integrity: sha512-Yg3HgaG4CqgyowtYjX/FsnPAuZdHOqSMtnbpylbptsQ9nwwSKsy6uRWcGO5RK0EqiX12q8HvDWKgeAVajRO5DA==} + hermes-parser@0.35.0: + resolution: {integrity: sha512-9JLjeHxBx8T4CAsydZR49PNZUaix+WpQJwu9p2010lu+7Kwl6D/7wYFFJxoz+aXkaaClp9Zfg6W6/zVlSJORaA==} + + hermes-parser@0.36.0: + resolution: {integrity: sha512-GdpwMmH5x6IpC1cijvcvYnlPB60Mh6kTSF/NFdYV/j56gYdi+0RIakYs+eqOV+bbO0SW7mgVVGSsTJxyPQfo3w==} + hookable@5.5.3: resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} @@ -6733,6 +7082,9 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} + lodash.debounce@4.0.8: + resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + lodash.memoize@4.1.2: resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} @@ -6949,60 +7301,118 @@ packages: resolution: {integrity: sha512-d9FfmgUEVejTiSb7bkQeLRGl6aeno2UpuPm3bo3rCYwxewj03ymvOn8s8vnS4fBqAPQ+cE9iQM40wh7nGXR+eA==} engines: {node: '>=20.19.4'} + metro-babel-transformer@0.84.4: + resolution: {integrity: sha512-rvCfz8snl9h20VcvpOHxZuHP1SlAkv4HXbzw7nyyVwu6Eqo5PRerbakQ9XmUCOsRy70spJ37O+G1TK8oMzo48g==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + metro-cache-key@0.83.5: resolution: {integrity: sha512-Ycl8PBajB7bhbAI7Rt0xEyiF8oJ0RWX8EKkolV1KfCUlC++V/GStMSGpPLwnnBZXZWkCC5edBPzv1Hz1Yi0Euw==} engines: {node: '>=20.19.4'} + metro-cache-key@0.84.4: + resolution: {integrity: sha512-wVO79aGrkYImpnaVS4+d5RrRBRPX31QtvKB3wKGBuiNSznduZTQHzsrJZRroFJSwnygrzdsGUtDQPuqqFjFdvw==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + metro-cache@0.83.5: resolution: {integrity: sha512-oH+s4U+IfZyg8J42bne2Skc90rcuESIYf86dYittcdWQtPfcaFXWpByPyTuWk3rR1Zz3Eh5HOrcVImfEhhJLng==} engines: {node: '>=20.19.4'} + metro-cache@0.84.4: + resolution: {integrity: sha512-gpcFQdSLUwUCk71saKoE64jLFbx2nwTfVCcPSULMNT8QYq0p1eZZE29Jvd0HtT/UlhC3ZOutLxJME5xqD2JUZg==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + metro-config@0.83.5: resolution: {integrity: sha512-JQ/PAASXH7yczgV6OCUSRhZYME+NU8NYjI2RcaG5ga4QfQ3T/XdiLzpSb3awWZYlDCcQb36l4Vl7i0Zw7/Tf9w==} engines: {node: '>=20.19.4'} + metro-config@0.84.4: + resolution: {integrity: sha512-PMotGDjXcXLWo2TMRH+VR99phFNgYTwqh4OoieIKK3yTJa1Jmkl+fZJxDO0jfBvNF+WESHciHvpNuBtXaF3B0Q==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + metro-core@0.83.5: resolution: {integrity: sha512-YcVcLCrf0ed4mdLa82Qob0VxYqfhmlRxUS8+TO4gosZo/gLwSvtdeOjc/Vt0pe/lvMNrBap9LlmvZM8FIsMgJQ==} engines: {node: '>=20.19.4'} + metro-core@0.84.4: + resolution: {integrity: sha512-HONpWC5LGXZn3ffkd4Hu6AIrfE7j4Z0g0wMo/goV24WOB3lhuFZ40KgvaDiSw8iyQHloMYay5N/wPX+z8oN/PQ==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + metro-file-map@0.83.5: resolution: {integrity: sha512-ZEt8s3a1cnYbn40nyCD+CsZdYSlwtFh2kFym4lo+uvfM+UMMH+r/BsrC6rbNClSrt+B7rU9T+Te/sh/NL8ZZKQ==} engines: {node: '>=20.19.4'} + metro-file-map@0.84.4: + resolution: {integrity: sha512-KSVDi/u60hKPx++NLu3MTIvyjzNoJnFAF8PQFxaj1jiSka/wjw+Ua6sNuJ0TDHQv+7AAoFQxeMgaRAe8Yic5wQ==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + metro-minify-terser@0.83.5: resolution: {integrity: sha512-Toe4Md1wS1PBqbvB0cFxBzKEVyyuYTUb0sgifAZh/mSvLH84qA1NAWik9sISWatzvfWf3rOGoUoO5E3f193a3Q==} engines: {node: '>=20.19.4'} + metro-minify-terser@0.84.4: + resolution: {integrity: sha512-5qpbaVOMC7CPitIpuewzVeGw7E+C3ykbv2mqTjQLl85Z3annSVGlSCTcsZjqXZzjupfK4Ztj3dDc4kc44NZwtQ==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + metro-resolver@0.83.5: resolution: {integrity: sha512-7p3GtzVUpbAweJeCcUJihJeOQl1bDuimO5ueo1K0BUpUtR41q5EilbQ3klt16UTPPMpA+tISWBtsrqU556mY1A==} engines: {node: '>=20.19.4'} + metro-resolver@0.84.4: + resolution: {integrity: sha512-1qLgbxQ5ZGhhutuPot1Yp348ofDsATL2WkrHF65TobqTT9K3P9qJXw38bomk7ncp5B7OYMfWwtyBZo1lCV792A==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + metro-runtime@0.83.5: resolution: {integrity: sha512-f+b3ue9AWTVlZe2Xrki6TAoFtKIqw30jwfk7GQ1rDUBQaE0ZQ+NkiMEtb9uwH7uAjJ87U7Tdx1Jg1OJqUfEVlA==} engines: {node: '>=20.19.4'} + metro-runtime@0.84.4: + resolution: {integrity: sha512-Jibypds4g7AhzdRKY+kDoj51s5EXMwgyp5ddtlreDAsWefMdOx+agWqgm0H2XSZ/ueanHHVM89fnf5OJnlxa8Q==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + metro-source-map@0.83.5: resolution: {integrity: sha512-VT9bb2KO2/4tWY9Z2yeZqTUao7CicKAOps9LUg2aQzsz+04QyuXL3qgf1cLUVRjA/D6G5u1RJAlN1w9VNHtODQ==} engines: {node: '>=20.19.4'} + metro-source-map@0.84.4: + resolution: {integrity: sha512-jbWkPxIesVuo1IWkvezmMJld6iu8nD62GsrZiV6jP37AOdbo4OBq1FJ+qkOg8sV05wAHB//jAbziuW0SlJfW4g==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + metro-symbolicate@0.83.5: resolution: {integrity: sha512-EMIkrjNRz/hF+p0RDdxoE60+dkaTLPN3vaaGkFmX5lvFdO6HPfHA/Ywznzkev+za0VhPQ5KSdz49/MALBRteHA==} engines: {node: '>=20.19.4'} hasBin: true + metro-symbolicate@0.84.4: + resolution: {integrity: sha512-OnfpacxUqGPZQ27t8qK9mFa7uqHIlVWeqRqkCbvMvreEBiamEeOn8krKtcwgP5M4cYDPwuSmCTopHMVthqG4zA==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + hasBin: true + metro-transform-plugins@0.83.5: resolution: {integrity: sha512-KxYKzZL+lt3Os5H2nx7YkbkWVduLZL5kPrE/Yq+Prm/DE1VLhpfnO6HtPs8vimYFKOa58ncl60GpoX0h7Wm0Vw==} engines: {node: '>=20.19.4'} + metro-transform-plugins@0.84.4: + resolution: {integrity: sha512-kehr6HbAecqD0/a3xLXobELdPaAmRAl8bel0qagPF4vhZtux93nS8S4eq2kgKt6J2GnQpVjSoW1PXdst04mwow==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + metro-transform-worker@0.83.5: resolution: {integrity: sha512-8N4pjkNXc6ytlP9oAM6MwqkvUepNSW39LKYl9NjUMpRDazBQ7oBpQDc8Sz4aI8jnH6AGhF7s1m/ayxkN1t04yA==} engines: {node: '>=20.19.4'} + metro-transform-worker@0.84.4: + resolution: {integrity: sha512-W1IYMvvXTu4MxYr7d9h7CeG2vpIr3bmLLIavkPY4O1ilzDrvS8z/NEe6y+pC44Ff7raMXQgYSfdqDUwN/i39gg==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + metro@0.83.5: resolution: {integrity: sha512-BgsXevY1MBac/3ZYv/RfNFf/4iuW9X7f4H8ZNkiH+r667HD9sVujxcmu4jvEzGCAm4/WyKdZCuyhAcyhTHOucQ==} engines: {node: '>=20.19.4'} hasBin: true + metro@0.84.4: + resolution: {integrity: sha512-8ETTubqfD6ornDy2zYDvRcKnVDOXdFJsjetYDBsY4oAsb6NJkiwFR+FaMESyGppFmQUyBQA4H4sFGxzcQSGtFA==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + hasBin: true + mhchemparser@4.2.1: resolution: {integrity: sha512-kYmyrCirqJf3zZ9t/0wGgRZ4/ZJw//VwaRVGA75C4nhE60vtnIzhl9J9ndkX/h6hxSN7pjg/cE0VxbnNM+bnDQ==} @@ -7321,6 +7731,10 @@ packages: resolution: {integrity: sha512-vNKPYC8L5ycVANANpF/S+WZHpfnRWKx/F3AYP4QMn6ZJTh+l2HOrId0clNkEmua58NB9vmI9Qh7YOoV/4folYg==} engines: {node: '>=20.19.4'} + ob1@0.84.4: + resolution: {integrity: sha512-eJXMpz4aQHXF/YBB9ddqZDIS+ooO91hObo9FoW/xBkr54/zCwYYCDqT/O54vNo8kOkWs5Ou/y28NgdrV0edQNA==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} @@ -7906,6 +8320,14 @@ packages: react-native-worklets: optional: true + react-native-worklets@0.10.2: + resolution: {integrity: sha512-LX27ejYI8veeDp59Z3rjo2pYyPa9euzSH8GUlem7cnNqfsDtGum8PQpkbzrqhLsWH0CjdeHR7p3sncCyYbwaVw==} + peerDependencies: + '@babel/core': '*' + '@react-native/metro-config': '*' + react: '*' + react-native: 0.83 - 0.86 + react-native@0.84.1: resolution: {integrity: sha512-0PjxOyXRu3tZ8EobabxSukvhKje2HJbsZikR0U+pvS0pYZza2hXKjcSBiBdFN4h9D0S3v6a8kkrDK6WTRKMwzg==} engines: {node: '>= 20.19.4'} @@ -7987,6 +8409,13 @@ packages: recma-stringify@1.0.0: resolution: {integrity: sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==} + regenerate-unicode-properties@10.2.2: + resolution: {integrity: sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==} + engines: {node: '>=4'} + + regenerate@1.4.2: + resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==} + regenerator-runtime@0.13.11: resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} @@ -8002,6 +8431,17 @@ packages: regex@6.1.0: resolution: {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==} + regexpu-core@6.4.0: + resolution: {integrity: sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==} + engines: {node: '>=4'} + + regjsgen@0.8.0: + resolution: {integrity: sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==} + + regjsparser@0.13.2: + resolution: {integrity: sha512-NgRBy2Nx/bE+9F27nVHnqcN5HjyLmecqsqx2PJHu3/IEtADD4WuxuXIVExD5PoSDFVrl78dOonfcOe5O+5nbzQ==} + hasBin: true + rehype-expressive-code@0.41.7: resolution: {integrity: sha512-25f8ZMSF1d9CMscX7Cft0TSQIqdwjce2gDOvQ+d/w0FovsMwrSt3ODP4P3Z7wO1jsIJ4eYyaDRnIR/27bd/EMQ==} @@ -8875,10 +9315,26 @@ packages: resolution: {integrity: sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==} engines: {node: '>=18.17'} + unicode-canonical-property-names-ecmascript@2.0.1: + resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==} + engines: {node: '>=4'} + unicode-emoji-modifier-base@1.0.0: resolution: {integrity: sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==} engines: {node: '>=4'} + unicode-match-property-ecmascript@2.0.0: + resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==} + engines: {node: '>=4'} + + unicode-match-property-value-ecmascript@2.2.1: + resolution: {integrity: sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==} + engines: {node: '>=4'} + + unicode-property-aliases-ecmascript@2.2.0: + resolution: {integrity: sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==} + engines: {node: '>=4'} + unicorn-magic@0.3.0: resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} engines: {node: '>=18'} @@ -9827,8 +10283,16 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + '@babel/compat-data@7.29.0': {} + '@babel/compat-data@7.29.7': {} + '@babel/core@7.29.0': dependencies: '@babel/code-frame': 7.29.0 @@ -9857,6 +10321,14 @@ snapshots: '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 + '@babel/generator@7.29.7': + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.0 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + '@babel/generator@8.0.0-rc.2': dependencies: '@babel/parser': 8.0.0-rc.3 @@ -9866,19 +10338,78 @@ snapshots: '@types/jsesc': 2.5.1 jsesc: 3.1.0 + '@babel/helper-annotate-as-pure@7.29.7': + dependencies: + '@babel/types': 7.29.0 + '@babel/helper-compilation-targets@7.28.6': dependencies: '@babel/compat-data': 7.29.0 - '@babel/helper-validator-option': 7.27.1 + '@babel/helper-validator-option': 7.29.7 browserslist: 4.28.1 lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-globals@7.28.0': {} + '@babel/helper-compilation-targets@7.29.7': + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.28.1 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-create-class-features-plugin@7.29.7(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-member-expression-to-functions': 7.29.7 + '@babel/helper-optimise-call-expression': 7.29.7 + '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.0) + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + '@babel/traverse': 7.29.7 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-create-regexp-features-plugin@7.29.7(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.29.7 + regexpu-core: 6.4.0 + semver: 6.3.1 + + '@babel/helper-define-polyfill-provider@0.6.8(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + debug: 4.4.3 + lodash.debounce: 4.0.8 + resolve: 1.22.11 + transitivePeerDependencies: + - supports-color + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-globals@7.29.7': {} + + '@babel/helper-member-expression-to-functions@7.29.7': + dependencies: + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color '@babel/helper-module-imports@7.28.6': dependencies: - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-imports@7.29.7': + dependencies: + '@babel/traverse': 7.29.7 '@babel/types': 7.29.0 transitivePeerDependencies: - supports-color @@ -9888,21 +10419,71 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-module-imports': 7.28.6 '@babel/helper-validator-identifier': 7.28.5 - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.29.7 transitivePeerDependencies: - supports-color + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-optimise-call-expression@7.29.7': + dependencies: + '@babel/types': 7.29.0 + '@babel/helper-plugin-utils@7.27.1': {} '@babel/helper-plugin-utils@7.28.6': {} + '@babel/helper-plugin-utils@7.29.7': {} + + '@babel/helper-remap-async-to-generator@7.29.7(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-wrap-function': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-replace-supers@7.29.7(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-member-expression-to-functions': 7.29.7 + '@babel/helper-optimise-call-expression': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-skip-transparent-expression-wrappers@7.29.7': + dependencies: + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + '@babel/helper-string-parser@7.27.1': {} '@babel/helper-validator-identifier@7.28.5': {} + '@babel/helper-validator-identifier@7.29.7': {} + '@babel/helper-validator-identifier@8.0.0-rc.2': {} - '@babel/helper-validator-option@7.27.1': {} + '@babel/helper-validator-option@7.29.7': {} + + '@babel/helper-wrap-function@7.29.7': + dependencies: + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color '@babel/helpers@7.28.6': dependencies: @@ -9933,80 +10514,234 @@ snapshots: dependencies: '@babel/types': 7.29.0 + '@babel/plugin-proposal-export-default-from@7.29.7(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-export-default-from@7.29.7(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-flow@7.29.7(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-arrow-functions@7.29.7(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-async-generator-functions@7.29.7(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-remap-async-to-generator': 7.29.7(@babel/core@7.29.0) + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-async-to-generator@7.29.7(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-remap-async-to-generator': 7.29.7(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-block-scoping@7.29.7(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-class-properties@7.29.7(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-classes@7.29.7(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-globals': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.0) + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-destructuring@7.29.7(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-flow-strip-types@7.29.7(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-syntax-flow': 7.29.7(@babel/core@7.29.0) + + '@babel/plugin-transform-for-of@7.29.7(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-commonjs@7.29.7(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-named-capturing-groups-regex@7.29.7(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-nullish-coalescing-operator@7.29.7(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-optional-catch-binding@7.29.7(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-optional-chaining@7.29.7(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-private-methods@7.29.7(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-private-property-in-object@7.29.7(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-react-display-name@7.29.7(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.29.0)': dependencies: @@ -10018,6 +10753,72 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-transform-react-jsx@7.29.7(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.0) + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-regenerator@7.29.7(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-runtime@7.29.7(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + babel-plugin-polyfill-corejs2: 0.4.17(@babel/core@7.29.0) + babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.29.0) + babel-plugin-polyfill-regenerator: 0.6.8(@babel/core@7.29.0) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-shorthand-properties@7.29.7(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-template-literals@7.29.7(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-typescript@7.29.7(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-unicode-regex@7.29.7(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/preset-typescript@7.29.7(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.0) + '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.0) + '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + '@babel/runtime@7.26.9': dependencies: regenerator-runtime: 0.14.1 @@ -10030,6 +10831,12 @@ snapshots: '@babel/parser': 7.29.0 '@babel/types': 7.29.0 + '@babel/template@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.0 + '@babel/traverse@7.29.0': dependencies: '@babel/code-frame': 7.29.0 @@ -10042,6 +10849,18 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/traverse@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/types': 7.29.0 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + '@babel/types@7.29.0': dependencies: '@babel/helper-string-parser': 7.27.1 @@ -11527,6 +12346,52 @@ snapshots: '@react-native/assets-registry@0.84.1': {} + '@react-native/babel-plugin-codegen@0.86.0(@babel/core@7.29.0)': + dependencies: + '@babel/traverse': 7.29.7 + '@react-native/codegen': 0.86.0(@babel/core@7.29.0) + transitivePeerDependencies: + - '@babel/core' + - supports-color + + '@react-native/babel-preset@0.86.0(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-proposal-export-default-from': 7.29.7(@babel/core@7.29.0) + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-export-default-from': 7.29.7(@babel/core@7.29.0) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-transform-async-generator-functions': 7.29.7(@babel/core@7.29.0) + '@babel/plugin-transform-async-to-generator': 7.29.7(@babel/core@7.29.0) + '@babel/plugin-transform-block-scoping': 7.29.7(@babel/core@7.29.0) + '@babel/plugin-transform-class-properties': 7.29.7(@babel/core@7.29.0) + '@babel/plugin-transform-classes': 7.29.7(@babel/core@7.29.0) + '@babel/plugin-transform-destructuring': 7.29.7(@babel/core@7.29.0) + '@babel/plugin-transform-flow-strip-types': 7.29.7(@babel/core@7.29.0) + '@babel/plugin-transform-for-of': 7.29.7(@babel/core@7.29.0) + '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.0) + '@babel/plugin-transform-named-capturing-groups-regex': 7.29.7(@babel/core@7.29.0) + '@babel/plugin-transform-nullish-coalescing-operator': 7.29.7(@babel/core@7.29.0) + '@babel/plugin-transform-optional-catch-binding': 7.29.7(@babel/core@7.29.0) + '@babel/plugin-transform-optional-chaining': 7.29.7(@babel/core@7.29.0) + '@babel/plugin-transform-private-methods': 7.29.7(@babel/core@7.29.0) + '@babel/plugin-transform-private-property-in-object': 7.29.7(@babel/core@7.29.0) + '@babel/plugin-transform-react-display-name': 7.29.7(@babel/core@7.29.0) + '@babel/plugin-transform-react-jsx': 7.29.7(@babel/core@7.29.0) + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-regenerator': 7.29.7(@babel/core@7.29.0) + '@babel/plugin-transform-runtime': 7.29.7(@babel/core@7.29.0) + '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.0) + '@babel/plugin-transform-unicode-regex': 7.29.7(@babel/core@7.29.0) + '@react-native/babel-plugin-codegen': 0.86.0(@babel/core@7.29.0) + babel-plugin-syntax-hermes-parser: 0.36.0 + babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.29.0) + react-refresh: 0.14.2 + transitivePeerDependencies: + - supports-color + '@react-native/codegen@0.84.1(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 @@ -11537,7 +12402,17 @@ snapshots: tinyglobby: 0.2.16 yargs: 17.7.2 - '@react-native/community-cli-plugin@0.84.1': + '@react-native/codegen@0.86.0(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/parser': 7.29.7 + hermes-parser: 0.36.0 + invariant: 2.2.4 + nullthrows: 1.1.1 + tinyglobby: 0.2.16 + yargs: 17.7.2 + + '@react-native/community-cli-plugin@0.84.1(@react-native/metro-config@0.86.0(@babel/core@7.29.0))': dependencies: '@react-native/dev-middleware': 0.84.1 debug: 4.4.3 @@ -11546,6 +12421,8 @@ snapshots: metro-config: 0.83.5 metro-core: 0.83.5 semver: 7.7.4 + optionalDependencies: + '@react-native/metro-config': 0.86.0(@babel/core@7.29.0) transitivePeerDependencies: - bufferutil - supports-color @@ -11584,14 +12461,37 @@ snapshots: '@react-native/js-polyfills@0.84.1': {} + '@react-native/js-polyfills@0.86.0': {} + + '@react-native/metro-babel-transformer@0.86.0(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@react-native/babel-preset': 0.86.0(@babel/core@7.29.0) + hermes-parser: 0.36.0 + nullthrows: 1.1.1 + transitivePeerDependencies: + - supports-color + + '@react-native/metro-config@0.86.0(@babel/core@7.29.0)': + dependencies: + '@react-native/js-polyfills': 0.86.0 + '@react-native/metro-babel-transformer': 0.86.0(@babel/core@7.29.0) + metro-config: 0.84.4 + metro-runtime: 0.84.4 + transitivePeerDependencies: + - '@babel/core' + - bufferutil + - supports-color + - utf-8-validate + '@react-native/normalize-colors@0.84.1': {} - '@react-native/virtualized-lists@0.84.1(@types/react@19.1.8)(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.1.8)(react@19.2.6))(react@19.2.6)': + '@react-native/virtualized-lists@0.84.1(@types/react@19.1.8)(react-native@0.84.1(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(@types/react@19.1.8)(react@19.2.6))(react@19.2.6)': dependencies: invariant: 2.2.4 nullthrows: 1.1.1 react: 19.2.6 - react-native: 0.84.1(@babel/core@7.29.0)(@types/react@19.1.8)(react@19.2.6) + react-native: 0.84.1(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(@types/react@19.1.8)(react@19.2.6) optionalDependencies: '@types/react': 19.1.8 @@ -13095,7 +13995,7 @@ snapshots: babel-plugin-istanbul@6.1.1: dependencies: - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-plugin-utils': 7.28.6 '@istanbuljs/load-nyc-config': 1.1.0 '@istanbuljs/schema': 0.1.3 istanbul-lib-instrument: 5.2.1 @@ -13110,10 +14010,44 @@ snapshots: '@types/babel__core': 7.20.5 '@types/babel__traverse': 7.20.7 + babel-plugin-polyfill-corejs2@0.4.17(@babel/core@7.29.0): + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-corejs3@0.13.0(@babel/core@7.29.0): + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0) + core-js-compat: 3.49.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-regenerator@0.6.8(@babel/core@7.29.0): + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + babel-plugin-syntax-hermes-parser@0.32.0: dependencies: hermes-parser: 0.32.0 + babel-plugin-syntax-hermes-parser@0.36.0: + dependencies: + hermes-parser: 0.36.0 + + babel-plugin-transform-flow-enums@0.0.2(@babel/core@7.29.0): + dependencies: + '@babel/plugin-syntax-flow': 7.29.7(@babel/core@7.29.0) + transitivePeerDependencies: + - '@babel/core' + babel-preset-current-node-syntax@1.2.0(@babel/core@7.29.0): dependencies: '@babel/core': 7.29.0 @@ -13441,6 +14375,10 @@ snapshots: cookie@1.1.1: {} + core-js-compat@3.49.0: + dependencies: + browserslist: 4.28.1 + cosmiconfig@9.0.1(tsover@6.0.2): dependencies: env-paths: 2.2.1 @@ -14609,6 +15547,10 @@ snapshots: hermes-estree@0.33.3: {} + hermes-estree@0.35.0: {} + + hermes-estree@0.36.0: {} + hermes-parser@0.32.0: dependencies: hermes-estree: 0.32.0 @@ -14617,6 +15559,14 @@ snapshots: dependencies: hermes-estree: 0.33.3 + hermes-parser@0.35.0: + dependencies: + hermes-estree: 0.35.0 + + hermes-parser@0.36.0: + dependencies: + hermes-estree: 0.36.0 + hookable@5.5.3: {} hookable@6.0.1: {} @@ -14796,7 +15746,7 @@ snapshots: istanbul-lib-instrument@5.2.1: dependencies: '@babel/core': 7.29.0 - '@babel/parser': 7.29.0 + '@babel/parser': 7.29.7 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 semver: 6.3.1 @@ -14921,14 +15871,14 @@ snapshots: jiti@2.6.1: {} - jotai-location@0.6.2(jotai@2.15.0(@babel/core@7.29.0)(@babel/template@7.28.6)(@types/react@19.2.14)(react@19.2.6)): + jotai-location@0.6.2(jotai@2.15.0(@babel/core@7.29.0)(@babel/template@7.29.7)(@types/react@19.2.14)(react@19.2.6)): dependencies: - jotai: 2.15.0(@babel/core@7.29.0)(@babel/template@7.28.6)(@types/react@19.2.14)(react@19.2.6) + jotai: 2.15.0(@babel/core@7.29.0)(@babel/template@7.29.7)(@types/react@19.2.14)(react@19.2.6) - jotai@2.15.0(@babel/core@7.29.0)(@babel/template@7.28.6)(@types/react@19.2.14)(react@19.2.6): + jotai@2.15.0(@babel/core@7.29.0)(@babel/template@7.29.7)(@types/react@19.2.14)(react@19.2.6): optionalDependencies: '@babel/core': 7.29.0 - '@babel/template': 7.28.6 + '@babel/template': 7.29.7 '@types/react': 19.2.14 react: 19.2.6 @@ -15154,6 +16104,8 @@ snapshots: dependencies: p-locate: 5.0.0 + lodash.debounce@4.0.8: {} + lodash.memoize@4.1.2: {} lodash.merge@4.6.2: {} @@ -15503,10 +16455,24 @@ snapshots: transitivePeerDependencies: - supports-color + metro-babel-transformer@0.84.4: + dependencies: + '@babel/core': 7.29.0 + flow-enums-runtime: 0.0.6 + hermes-parser: 0.35.0 + metro-cache-key: 0.84.4 + nullthrows: 1.1.1 + transitivePeerDependencies: + - supports-color + metro-cache-key@0.83.5: dependencies: flow-enums-runtime: 0.0.6 + metro-cache-key@0.84.4: + dependencies: + flow-enums-runtime: 0.0.6 + metro-cache@0.83.5: dependencies: exponential-backoff: 3.1.3 @@ -15516,6 +16482,15 @@ snapshots: transitivePeerDependencies: - supports-color + metro-cache@0.84.4: + dependencies: + exponential-backoff: 3.1.3 + flow-enums-runtime: 0.0.6 + https-proxy-agent: 7.0.6 + metro-core: 0.84.4 + transitivePeerDependencies: + - supports-color + metro-config@0.83.5: dependencies: connect: 3.7.0 @@ -15531,12 +16506,33 @@ snapshots: - supports-color - utf-8-validate + metro-config@0.84.4: + dependencies: + connect: 3.7.0 + flow-enums-runtime: 0.0.6 + jest-validate: 29.7.0 + metro: 0.84.4 + metro-cache: 0.84.4 + metro-core: 0.84.4 + metro-runtime: 0.84.4 + yaml: 2.8.3 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + metro-core@0.83.5: dependencies: flow-enums-runtime: 0.0.6 lodash.throttle: 4.1.1 metro-resolver: 0.83.5 + metro-core@0.84.4: + dependencies: + flow-enums-runtime: 0.0.6 + lodash.throttle: 4.1.1 + metro-resolver: 0.84.4 + metro-file-map@0.83.5: dependencies: debug: 4.4.3 @@ -15551,20 +16547,48 @@ snapshots: transitivePeerDependencies: - supports-color + metro-file-map@0.84.4: + dependencies: + debug: 4.4.3 + fb-watchman: 2.0.2 + flow-enums-runtime: 0.0.6 + graceful-fs: 4.2.11 + invariant: 2.2.4 + jest-worker: 29.7.0 + micromatch: 4.0.8 + nullthrows: 1.1.1 + walker: 1.0.8 + transitivePeerDependencies: + - supports-color + metro-minify-terser@0.83.5: dependencies: flow-enums-runtime: 0.0.6 terser: 5.44.1 + metro-minify-terser@0.84.4: + dependencies: + flow-enums-runtime: 0.0.6 + terser: 5.44.1 + metro-resolver@0.83.5: dependencies: flow-enums-runtime: 0.0.6 + metro-resolver@0.84.4: + dependencies: + flow-enums-runtime: 0.0.6 + metro-runtime@0.83.5: dependencies: '@babel/runtime': 7.26.9 flow-enums-runtime: 0.0.6 + metro-runtime@0.84.4: + dependencies: + '@babel/runtime': 7.26.9 + flow-enums-runtime: 0.0.6 + metro-source-map@0.83.5: dependencies: '@babel/traverse': 7.29.0 @@ -15579,6 +16603,20 @@ snapshots: transitivePeerDependencies: - supports-color + metro-source-map@0.84.4: + dependencies: + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.0 + flow-enums-runtime: 0.0.6 + invariant: 2.2.4 + metro-symbolicate: 0.84.4 + nullthrows: 1.1.1 + ob1: 0.84.4 + source-map: 0.5.7 + vlq: 1.0.1 + transitivePeerDependencies: + - supports-color + metro-symbolicate@0.83.5: dependencies: flow-enums-runtime: 0.0.6 @@ -15590,12 +16628,34 @@ snapshots: transitivePeerDependencies: - supports-color + metro-symbolicate@0.84.4: + dependencies: + flow-enums-runtime: 0.0.6 + invariant: 2.2.4 + metro-source-map: 0.84.4 + nullthrows: 1.1.1 + source-map: 0.5.7 + vlq: 1.0.1 + transitivePeerDependencies: + - supports-color + metro-transform-plugins@0.83.5: dependencies: '@babel/core': 7.29.0 '@babel/generator': 7.29.1 '@babel/template': 7.28.6 - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.29.7 + flow-enums-runtime: 0.0.6 + nullthrows: 1.1.1 + transitivePeerDependencies: + - supports-color + + metro-transform-plugins@0.84.4: + dependencies: + '@babel/core': 7.29.0 + '@babel/generator': 7.29.7 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.7 flow-enums-runtime: 0.0.6 nullthrows: 1.1.1 transitivePeerDependencies: @@ -15621,6 +16681,26 @@ snapshots: - supports-color - utf-8-validate + metro-transform-worker@0.84.4: + dependencies: + '@babel/core': 7.29.0 + '@babel/generator': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.0 + flow-enums-runtime: 0.0.6 + metro: 0.84.4 + metro-babel-transformer: 0.84.4 + metro-cache: 0.84.4 + metro-cache-key: 0.84.4 + metro-minify-terser: 0.84.4 + metro-source-map: 0.84.4 + metro-transform-plugins: 0.84.4 + nullthrows: 1.1.1 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + metro@0.83.5: dependencies: '@babel/code-frame': 7.29.0 @@ -15668,6 +16748,52 @@ snapshots: - supports-color - utf-8-validate + metro@0.84.4: + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/core': 7.29.0 + '@babel/generator': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.0 + accepts: 2.0.0 + ci-info: 2.0.0 + connect: 3.7.0 + debug: 4.4.3 + error-stack-parser: 2.1.4 + flow-enums-runtime: 0.0.6 + graceful-fs: 4.2.11 + hermes-parser: 0.35.0 + image-size: 1.2.1 + invariant: 2.2.4 + jest-worker: 29.7.0 + jsc-safe-url: 0.2.4 + lodash.throttle: 4.1.1 + metro-babel-transformer: 0.84.4 + metro-cache: 0.84.4 + metro-cache-key: 0.84.4 + metro-config: 0.84.4 + metro-core: 0.84.4 + metro-file-map: 0.84.4 + metro-resolver: 0.84.4 + metro-runtime: 0.84.4 + metro-source-map: 0.84.4 + metro-symbolicate: 0.84.4 + metro-transform-plugins: 0.84.4 + metro-transform-worker: 0.84.4 + mime-types: 3.0.2 + nullthrows: 1.1.1 + serialize-error: 2.1.0 + source-map: 0.5.7 + throat: 5.0.0 + ws: 7.5.10 + yargs: 17.7.2 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + mhchemparser@4.2.1: {} micromark-core-commonmark@2.0.3: @@ -16132,6 +17258,10 @@ snapshots: dependencies: flow-enums-runtime: 0.0.6 + ob1@0.84.4: + dependencies: + flow-enums-runtime: 0.0.6 + obug@2.1.1: {} ofetch@1.5.1: @@ -16711,21 +17841,44 @@ snapshots: react-is@18.3.1: {} - react-native-webgpu@0.5.15(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.1.8)(react@19.2.6))(react@19.2.6): + react-native-webgpu@0.5.15(react-native-worklets@0.10.2(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(react-native@0.84.1(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(@types/react@19.1.8)(react@19.2.6))(react@19.2.6))(react-native@0.84.1(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(@types/react@19.1.8)(react@19.2.6))(react@19.2.6): + dependencies: + react: 19.2.6 + react-native: 0.84.1(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(@types/react@19.1.8)(react@19.2.6) + optionalDependencies: + react-native-worklets: 0.10.2(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(react-native@0.84.1(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(@types/react@19.1.8)(react@19.2.6))(react@19.2.6) + + react-native-worklets@0.10.2(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(react-native@0.84.1(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(@types/react@19.1.8)(react@19.2.6))(react@19.2.6): dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-transform-arrow-functions': 7.29.7(@babel/core@7.29.0) + '@babel/plugin-transform-class-properties': 7.29.7(@babel/core@7.29.0) + '@babel/plugin-transform-classes': 7.29.7(@babel/core@7.29.0) + '@babel/plugin-transform-nullish-coalescing-operator': 7.29.7(@babel/core@7.29.0) + '@babel/plugin-transform-optional-chaining': 7.29.7(@babel/core@7.29.0) + '@babel/plugin-transform-shorthand-properties': 7.29.7(@babel/core@7.29.0) + '@babel/plugin-transform-template-literals': 7.29.7(@babel/core@7.29.0) + '@babel/plugin-transform-unicode-regex': 7.29.7(@babel/core@7.29.0) + '@babel/preset-typescript': 7.29.7(@babel/core@7.29.0) + '@babel/types': 7.29.0 + '@react-native/metro-config': 0.86.0(@babel/core@7.29.0) + convert-source-map: 2.0.0 react: 19.2.6 - react-native: 0.84.1(@babel/core@7.29.0)(@types/react@19.1.8)(react@19.2.6) + react-native: 0.84.1(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(@types/react@19.1.8)(react@19.2.6) + semver: 7.7.4 + transitivePeerDependencies: + - supports-color - react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.1.8)(react@19.2.6): + react-native@0.84.1(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(@types/react@19.1.8)(react@19.2.6): dependencies: '@jest/create-cache-key-function': 29.7.0 '@react-native/assets-registry': 0.84.1 '@react-native/codegen': 0.84.1(@babel/core@7.29.0) - '@react-native/community-cli-plugin': 0.84.1 + '@react-native/community-cli-plugin': 0.84.1(@react-native/metro-config@0.86.0(@babel/core@7.29.0)) '@react-native/gradle-plugin': 0.84.1 '@react-native/js-polyfills': 0.84.1 '@react-native/normalize-colors': 0.84.1 - '@react-native/virtualized-lists': 0.84.1(@types/react@19.1.8)(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.1.8)(react@19.2.6))(react@19.2.6) + '@react-native/virtualized-lists': 0.84.1(@types/react@19.1.8)(react-native@0.84.1(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(@types/react@19.1.8)(react@19.2.6))(react@19.2.6) abort-controller: 3.0.0 anser: 1.4.10 ansi-regex: 5.0.1 @@ -16841,6 +17994,12 @@ snapshots: unified: 11.0.5 vfile: 6.0.3 + regenerate-unicode-properties@10.2.2: + dependencies: + regenerate: 1.4.2 + + regenerate@1.4.2: {} + regenerator-runtime@0.13.11: {} regenerator-runtime@0.14.1: {} @@ -16855,6 +18014,21 @@ snapshots: dependencies: regex-utilities: 2.3.0 + regexpu-core@6.4.0: + dependencies: + regenerate: 1.4.2 + regenerate-unicode-properties: 10.2.2 + regjsgen: 0.8.0 + regjsparser: 0.13.2 + unicode-match-property-ecmascript: 2.0.0 + unicode-match-property-value-ecmascript: 2.2.1 + + regjsgen@0.8.0: {} + + regjsparser@0.13.2: + dependencies: + jsesc: 3.1.0 + rehype-expressive-code@0.41.7: dependencies: expressive-code: 0.41.7 @@ -17934,8 +19108,19 @@ snapshots: undici@6.23.0: {} + unicode-canonical-property-names-ecmascript@2.0.1: {} + unicode-emoji-modifier-base@1.0.0: {} + unicode-match-property-ecmascript@2.0.0: + dependencies: + unicode-canonical-property-names-ecmascript: 2.0.1 + unicode-property-aliases-ecmascript: 2.2.0 + + unicode-match-property-value-ecmascript@2.2.1: {} + + unicode-property-aliases-ecmascript@2.2.0: {} + unicorn-magic@0.3.0: {} unified@11.0.5: