@felipe-software/react-native-gpui 0.0.0-placeholder → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/.reanimated-prebuilt/react-native-reanimated.mjs +29373 -0
  2. package/LICENSE +21 -0
  3. package/README.md +658 -1
  4. package/cli/bin.ts +379 -0
  5. package/cli/bundle.ts +32 -0
  6. package/cli/commands/do.ts +213 -0
  7. package/cli/commands/flow.ts +283 -0
  8. package/cli/commands/get.ts +584 -0
  9. package/cli/commands/shot.ts +312 -0
  10. package/cli/commands/trace.ts +293 -0
  11. package/cli/host.ts +714 -0
  12. package/cli/hotReload.ts +210 -0
  13. package/cli/run.ts +66 -0
  14. package/cli/selectors.ts +205 -0
  15. package/dist/Animated.d.ts +120 -0
  16. package/dist/ChatList.d.ts +28 -0
  17. package/dist/Dimensions.d.ts +31 -0
  18. package/dist/StyleSheet.d.ts +18 -0
  19. package/dist/Window.d.ts +33 -0
  20. package/dist/apis.d.ts +165 -0
  21. package/dist/app-state.d.ts +27 -0
  22. package/dist/chat-list-types.d.ts +136 -0
  23. package/dist/colors.d.ts +32 -0
  24. package/dist/commands.d.ts +230 -0
  25. package/dist/components.d.ts +419 -0
  26. package/dist/hotModules.d.ts +1 -0
  27. package/dist/index.d.ts +31 -0
  28. package/dist/index.development.js +14876 -0
  29. package/dist/index.js +12 -0
  30. package/dist/keyboard.d.ts +140 -0
  31. package/dist/native-menu.d.ts +7 -0
  32. package/dist/platform-driver.d.ts +32 -0
  33. package/dist/platform-services.d.ts +35 -0
  34. package/dist/portal.d.ts +43 -0
  35. package/dist/raf.d.ts +4 -0
  36. package/dist/reanimated/colors-processor-shim.d.ts +10 -0
  37. package/dist/reanimated/reanimated-host.d.ts +10 -0
  38. package/dist/reanimated/seam.d.ts +34 -0
  39. package/dist/reanimated/ui-entry.d.ts +2 -0
  40. package/dist/reanimated/worklet-channel.d.ts +10 -0
  41. package/dist/reanimated/worklet-runtime.d.ts +466 -0
  42. package/dist/reanimated/worklets.d.ts +199 -0
  43. package/dist/reconciler.d.ts +102 -0
  44. package/dist/refresh.d.ts +1 -0
  45. package/dist/render.d.ts +37 -0
  46. package/dist/runtime.d.ts +235 -0
  47. package/dist/surfaces.d.ts +51 -0
  48. package/dist/types.d.ts +276 -0
  49. package/dist/ui-runtime.js +42276 -0
  50. package/dist/window-context.d.ts +7 -0
  51. package/dist/window-host.d.ts +41 -0
  52. package/dist/wire-delta.d.ts +20 -0
  53. package/package.json +177 -1
  54. package/scripts/bundle-app.mjs +88 -0
  55. package/scripts/conformance-utils.mjs +293 -0
  56. package/scripts/hot-update-alias-plugin.mjs +88 -0
  57. package/scripts/native-package-exports-plugin.mjs +235 -0
  58. package/scripts/pixel.mjs +152 -0
  59. package/scripts/png.mjs +198 -0
  60. package/scripts/react-refresh-swc.mjs +35 -0
  61. package/scripts/reanimated-bun-plugin.mjs +188 -0
  62. package/scripts/service-binary.d.mts +27 -0
  63. package/scripts/service-binary.mjs +100 -0
  64. package/src/reanimated/colors-processor-shim.ts +119 -0
  65. package/src/reanimated/reanimated-host.ts +69 -0
  66. package/src/reanimated/rngpui-reanimated-prebuilt.d.ts +7 -0
  67. package/src/reanimated/seam.ts +566 -0
  68. package/src/reanimated/ui-entry.ts +80 -0
  69. package/src/reanimated/worklet-channel.ts +64 -0
  70. package/src/reanimated/worklet-runtime.ts +2569 -0
  71. package/src/reanimated/worklets.ts +1334 -0
  72. package/src/refresh.ts +33 -0
@@ -0,0 +1,1334 @@
1
+ // react-native-worklets — the cross-runtime surface for the gpui JavaScriptCore target.
2
+ //
3
+ // port of soot's react-native-worklets stub
4
+ // (packages/compat/src/stubs/react-native-worklets-pkg/index.ts) onto rngpui's
5
+ // existing export surface. the bundler redirect maps every
6
+ // `react-native-worklets` import onto this module, so every export the previous
7
+ // single-runtime stub had MUST continue to exist (the export surface is a contract).
8
+ //
9
+ // ARCHITECTURE (see plans/off-thread-reanimated.md): rngpui runs TWO JavaScriptCore
10
+ // runtimes — the React runtime (role 'react') and the UI runtime (role 'ui') — that
11
+ // cross only as JSON strings through rust host fns, with shared-value primitives in
12
+ // a shared ArrayBuffer (`globalThis.__rngpui_svSlots`). on the React runtime, with
13
+ // the host bridge present (`typeof __rngpui_uiPost === 'function'`), workletized
14
+ // runOnUI / scheduleOnUI / runOnUIAsync DISPATCH to the UI runtime via the worklet
15
+ // runtime. SharedValues mirror through the shared slots so UI-side mappers and
16
+ // animation clocks mutate the same values. runOnJS-tagged callbacks round-trip back
17
+ // to the React runtime.
18
+ //
19
+ // WITHOUT the bridge (bun unit tests, early bundle eval, headless harnesses): every
20
+ // function degrades to LOCAL execution — the previous identity-passthrough behavior
21
+ // — because `runtime()` returns null and `shouldUseRemoteUIRuntime()` is false. the
22
+ // existing unit tests stay green unchanged.
23
+
24
+ // Force the reanimated native seam to evaluate (and install `__reanimatedModuleProxy`,
25
+ // `global._updateProps`, `__RUNTIME_KIND`, the version shims, the frame-callback
26
+ // registry). react-native-worklets is ALWAYS imported by reanimated before its
27
+ // NativeReanimated constructor runs, and reanimated's own seam import can be
28
+ // tree-shaken when the turbomodule path is dead, so this is the reliable install
29
+ // point. the seam's globals must exist before the first `withSpring(...)` evaluates,
30
+ // AND before we install the worklet runtime below (the runtime reads __RUNTIME_KIND-
31
+ // adjacent globals). keep this import FIRST.
32
+ import './seam'
33
+
34
+ import {
35
+ installWorkletRuntime,
36
+ markObjectSV,
37
+ type WorkletRuntime as WorkletRuntimeInstance,
38
+ type WorkletRuntimeRole,
39
+ } from './worklet-runtime'
40
+ import { installChannel, makeChannel } from './worklet-channel'
41
+
42
+ // =============================================================================
43
+ // types
44
+ // =============================================================================
45
+
46
+ export interface WorkletFunction {
47
+ __workletHash?: number
48
+ __initData?: { code?: string; location?: string; sourceMap?: string }
49
+ __closure?: Record<string, unknown>
50
+ (...args: unknown[]): unknown
51
+ }
52
+
53
+ export interface SerializableRef {
54
+ __init?: () => unknown
55
+ }
56
+
57
+ export interface Synchronizable<T> {
58
+ getBlocking(): T
59
+ setBlocking(value: T): void
60
+ }
61
+
62
+ export interface IWorkletsModule {
63
+ makeShareableClone: (value: unknown) => unknown
64
+ scheduleOnUI: (fn: () => void) => void
65
+ executeOnUIRuntimeSync: <T extends (...args: unknown[]) => unknown>(fn: T) => T
66
+ createWorkletRuntime: (name: string, initializer?: () => void) => unknown
67
+ }
68
+
69
+ export type MakeShareableClone = (value: unknown) => unknown
70
+ export type WorkletRuntime = unknown
71
+
72
+ // =============================================================================
73
+ // runtime kind — the React runtime claims UI (2) so reanimated v4's runtimeKind
74
+ // branches take the UI path (animations actually run, setNativeProps executes,
75
+ // cancelAnimation writes the SV directly). The seam installs __RUNTIME_KIND=2; the
76
+ // guards below read it live.
77
+ // =============================================================================
78
+
79
+ export enum RuntimeKind {
80
+ ReactNative = 1,
81
+ UI = 2,
82
+ Worker = 3,
83
+ }
84
+
85
+ const RNGPUI_UI_RUNTIME_HOLDER = { __rngpui_ui_runtime: true }
86
+ const RNGPUI_UI_SCHEDULER_HOLDER = { __rngpui_ui_scheduler: true }
87
+
88
+ export function getUIRuntimeHolder(): object {
89
+ return RNGPUI_UI_RUNTIME_HOLDER
90
+ }
91
+
92
+ export function getUISchedulerHolder(): object {
93
+ return RNGPUI_UI_SCHEDULER_HOLDER
94
+ }
95
+
96
+ export const UIRuntimeId = 1
97
+
98
+ // =============================================================================
99
+ // runtime install — wire the per-runtime WorkletRuntime + channel at module load,
100
+ // role auto-detected from which rust host post-fn is present in THIS runtime:
101
+ // - __rngpui_uiPost present ⇒ we are the React runtime ⇒ role 'react' (the
102
+ // bundler redirects this stub onto the app bundle = React runtime, but the UI
103
+ // bundle's ui-entry.ts also imports the worklets redirect, so this code runs on
104
+ // both — detect, don't assume).
105
+ // - __rngpui_jsPost present (and not __rngpui_uiPost) ⇒ role 'ui'.
106
+ // Without either host fn (bun unit tests, early bundle eval): no runtime installs
107
+ // and every path stays local exactly as the previous single-runtime stub did.
108
+ //
109
+ // `runtime()` reads the installed singleton off globalThis (so the runtime module
110
+ // and this stub agree without a cross-import cycle for resolution).
111
+ // =============================================================================
112
+
113
+ function detectRole(): WorkletRuntimeRole | null {
114
+ const g = globalThis as { __rngpui_uiPost?: unknown; __rngpui_jsPost?: unknown }
115
+ if (typeof g.__rngpui_uiPost === 'function') return 'react'
116
+ if (typeof g.__rngpui_jsPost === 'function') return 'ui'
117
+ return null
118
+ }
119
+
120
+ // shouldUseRemoteUIRuntime: in rngpui this is exactly "host bridge present AND role
121
+ // react" — the React runtime dispatches worklets to the UI runtime; the UI runtime
122
+ // runs them locally (it has __rngpui_jsPost but not __rngpui_uiPost). collapses to
123
+ // false without the bridge, so every dispatch stays local in bun tests.
124
+ function shouldUseRemoteUIRuntime(): boolean {
125
+ return detectRole() === 'react'
126
+ }
127
+
128
+ ;(() => {
129
+ const role = detectRole()
130
+ if (!role) return
131
+ const channel = makeChannel(role)
132
+ const rt = installWorkletRuntime({ role, channel })
133
+ if (rt) installChannel(role, rt)
134
+ })()
135
+
136
+ // =============================================================================
137
+ // createShareable — wire an upstream-decorated SharedValue across the React↔UI
138
+ // shared slots. `mutables.ts` calls `createShareable(UIRuntimeId, initial,
139
+ // { hostDecorator, guestDecorator })`; upstream's `mutableHostDecorator` builds the
140
+ // full SharedValue surface (value getter/setter, modify, addListener, _animation,
141
+ // _isReanimatedSharedValue) around `{value: initial}`.
142
+ //
143
+ // routing (faithful to soot):
144
+ // 1. allocate a shared slot with the initial primitive (object slot for arrays /
145
+ // plain objects)
146
+ // 2. let upstream's `mutableHostDecorator` build its full surface
147
+ // 3. redirect writes through the slot via the public addListener bridge so the
148
+ // peer sees them (skip-notify on the bounce-back to avoid loops)
149
+ // 4. attach `_id = slotId` so serializeValue routes this as {kind:'sv'} and the
150
+ // peer's deserializeValue hands the worklet body a proxy bound to the same slot
151
+ //
152
+ // Without the runtime (bun tests) the decorated SV still works locally; it just
153
+ // won't propagate cross-runtime.
154
+ // =============================================================================
155
+
156
+ interface ShareableGuestBase<TValue = unknown> {
157
+ isHost: false
158
+ __shareableRef: true
159
+ getSync(): TValue
160
+ setAsync(value: TValue | ((prev: TValue) => TValue)): void
161
+ setSync(value: TValue | ((prev: TValue) => TValue)): void
162
+ getAsync(): Promise<TValue>
163
+ modify?(modifier: (value: TValue) => TValue, forceUpdate?: boolean): void
164
+ }
165
+
166
+ interface ShareableConfig<H = unknown, G = unknown> {
167
+ hostDecorator?: (host: { value: unknown }) => H
168
+ guestDecorator?: (guest: ShareableGuestBase) => G
169
+ initSynchronously?: boolean
170
+ }
171
+
172
+ const _RNGPUI_SAB_SUPPRESS_NOTIFY = Symbol('rngpui_sab_suppress_notify')
173
+
174
+ // keeps the host-decorated SharedValue (its peer-write→`_value` bridge) alive
175
+ // exactly as long as the consumer holds the guest surface, so the slot is reclaimed
176
+ // when the *consumer's* object dies, not earlier.
177
+ const SAB_HOST_TARGET = Symbol('rngpui_sab_host_target')
178
+
179
+ // reclaim slot ids when a SharedValue's JS wrapper is garbage-collected. real
180
+ // reanimated releases the native Shareable when the JS Mutable is collected; this is
181
+ // the faithful analog. it is the SOLE caller of rt.freeSlot (exactly once per
182
+ // registered wrapper), so there is no free→reuse→stale-finalizer ABA: the slot is
183
+ // not on the free-list until this fires, and it fires once.
184
+ const sabSlotFinalizer = new FinalizationRegistry<{
185
+ rt: RuntimeShape
186
+ slotId: number
187
+ }>(({ rt, slotId }) => {
188
+ try {
189
+ rt.freeSlot(slotId)
190
+ } catch {}
191
+ })
192
+
193
+ // throttle the "createShareable failed; falling back to local-only" warnings. when
194
+ // the worklet runtime exhausts its slot pool during a re-render storm,
195
+ // createShareable's catch fires for every one of hundreds of thousands of failed
196
+ // allocs; an unthrottled console.warn per call floods the console and contributes to
197
+ // OOM-ing the runtime. log at most once per window with a suppressed count.
198
+ const shareableFallbackThrottle = { lastAt: 0, suppressed: 0 }
199
+ function warnShareableFallback(label: string, err: unknown): void {
200
+ shareableFallbackThrottle.suppressed++
201
+ const now = Date.now()
202
+ if (now - shareableFallbackThrottle.lastAt <= 2000) return
203
+ console.warn(
204
+ `[rngpui-sab] ${label}; falling back to local-only (+${shareableFallbackThrottle.suppressed} suppressed):`,
205
+ err,
206
+ )
207
+ shareableFallbackThrottle.lastAt = now
208
+ shareableFallbackThrottle.suppressed = 0
209
+ }
210
+ const RNGPUI_REANIMATED_ANIMATION_DESCRIPTOR = Symbol.for('rngpui.reanimatedAnimationDescriptor')
211
+ const RNGPUI_GESTURE_EVENT_TIMESTAMP = Symbol.for('rngpui.gestureEventTimestamp')
212
+ const RNGPUI_ANIMATION_REQUEST = '__rngpuiReanimatedAnimation'
213
+
214
+ const HOST_SET_SHARED_VALUE_WORKLET = markSyntheticWorklet(function (
215
+ sharedValue: unknown,
216
+ payload: unknown,
217
+ ) {
218
+ 'worklet'
219
+ function revive(value: unknown): unknown {
220
+ if (
221
+ value &&
222
+ typeof value === 'object' &&
223
+ (value as Record<string, unknown>).__rngpuiReanimatedAnimation === true
224
+ ) {
225
+ const request = value as { type?: unknown; args?: unknown[] }
226
+ const factories = (
227
+ globalThis as unknown as {
228
+ __rngpuiReanimatedAnimationFactories?: Record<string, (...args: unknown[]) => unknown>
229
+ }
230
+ ).__rngpuiReanimatedAnimationFactories
231
+ const factory = typeof request.type === 'string' ? factories?.[request.type] : null
232
+ if (typeof factory !== 'function') {
233
+ throw new Error(`[rngpui worklets] missing animation factory ${request.type}`)
234
+ }
235
+ return factory(...(Array.isArray(request.args) ? request.args.map(revive) : []))
236
+ }
237
+ if (Array.isArray(value)) return value.map(revive)
238
+ if (value && typeof value === 'object' && Object.getPrototypeOf(value) === Object.prototype) {
239
+ const out: Record<string, unknown> = {}
240
+ for (const key of Object.keys(value as Record<string, unknown>)) {
241
+ out[key] = revive((value as Record<string, unknown>)[key])
242
+ }
243
+ return out
244
+ }
245
+ return value
246
+ }
247
+ ;(sharedValue as { value: unknown }).value = revive(payload)
248
+ },
249
+ 0x50071,
250
+ // explicit source keeps the cross-runtime function independent of transpiler changes to
251
+ // Function#toString. this is the exact JS equivalent evaluated on the UI runtime. keep in sync.
252
+ `function (sharedValue, payload) {
253
+ function revive(value) {
254
+ if (value && typeof value === 'object' && value.__rngpuiReanimatedAnimation === true) {
255
+ var factories = globalThis.__rngpuiReanimatedAnimationFactories
256
+ var factory = typeof value.type === 'string' && factories ? factories[value.type] : null
257
+ if (typeof factory !== 'function') {
258
+ throw new Error('[rngpui worklets] missing animation factory ' + value.type)
259
+ }
260
+ var args = Array.isArray(value.args) ? value.args.map(revive) : []
261
+ return factory.apply(null, args)
262
+ }
263
+ if (Array.isArray(value)) return value.map(revive)
264
+ if (value && typeof value === 'object' && Object.getPrototypeOf(value) === Object.prototype) {
265
+ var out = {}
266
+ for (var key in value) {
267
+ if (Object.prototype.hasOwnProperty.call(value, key)) out[key] = revive(value[key])
268
+ }
269
+ return out
270
+ }
271
+ return value
272
+ }
273
+ sharedValue.value = revive(payload)
274
+ }`)
275
+
276
+ function getSabRuntime(): RuntimeShape | null {
277
+ return runtime()
278
+ }
279
+
280
+ function isSabBackable(value: unknown): value is number | boolean {
281
+ return typeof value === 'number' || typeof value === 'boolean'
282
+ }
283
+
284
+ function valueToFloat(v: unknown): number {
285
+ if (typeof v === 'number') return v
286
+ if (typeof v === 'boolean') return v ? 1 : 0
287
+ return Number.NaN
288
+ }
289
+
290
+ function markSyntheticWorklet<T extends (...args: unknown[]) => unknown>(
291
+ fn: T,
292
+ hash: number,
293
+ code?: string,
294
+ ): T & WorkletFunction {
295
+ const worklet = fn as T & WorkletFunction
296
+ Object.defineProperty(worklet, '__workletHash', {
297
+ value: hash,
298
+ enumerable: false,
299
+ configurable: true,
300
+ writable: false,
301
+ })
302
+ Object.defineProperty(worklet, '__initData', {
303
+ // Synthetic worklets that cross runtimes pass explicit source above. toString is
304
+ // sufficient for local compatibility stubs that never leave this source runtime.
305
+ value: { code: code ?? fn.toString() },
306
+ enumerable: false,
307
+ configurable: true,
308
+ writable: false,
309
+ })
310
+ Object.defineProperty(worklet, '__closure', {
311
+ value: {},
312
+ enumerable: false,
313
+ configurable: true,
314
+ writable: true,
315
+ })
316
+ return worklet
317
+ }
318
+
319
+ export function createShareable<TValue, THostDecorated = unknown, TGuestDecorated = unknown>(
320
+ _hostRuntimeId: number,
321
+ initial: TValue,
322
+ config?: ShareableConfig<THostDecorated, TGuestDecorated>,
323
+ ): unknown {
324
+ // legacy single-arg form (`createShareable(value)`) — passthrough.
325
+ if (config === undefined && typeof _hostRuntimeId !== 'number') {
326
+ return _hostRuntimeId
327
+ }
328
+
329
+ const base: { value: TValue } = { value: initial }
330
+ // upstream wraps `hostDecorator` with the babel-worklets transform, which reads
331
+ // captured variables via `this.__closure`. invoking it locally requires `this` to
332
+ // be the function itself — same convention the worklet runtime uses inside
333
+ // handleRunWorklet (`fn.apply(fn, args)`).
334
+ const decorated = config?.hostDecorator
335
+ ? config.hostDecorator.call(config.hostDecorator, base)
336
+ : base
337
+
338
+ // object SVs (arrays, plain objects) route through the parallel object-slot path:
339
+ // each runtime keeps a Map<id, value> and updates ship as svUpdateBatch
340
+ // objectUpdates.
341
+ if (!isSabBackable(initial)) {
342
+ try {
343
+ const rt = getSabRuntime()
344
+ if (!rt) return decorated
345
+ const host = wireObjectSharedValue(rt, decorated, initial)
346
+ if (shouldUseRemoteUIRuntime() && config?.guestDecorator && hasSharedValueId(host)) {
347
+ const slotId = host._id
348
+ const guest = createGuestSharedValue(rt, slotId, 'object', false, config.guestDecorator)
349
+ Object.defineProperty(guest as object, SAB_HOST_TARGET, {
350
+ value: host,
351
+ enumerable: false,
352
+ configurable: true,
353
+ writable: false,
354
+ })
355
+ sabSlotFinalizer.register(guest as object, { rt, slotId })
356
+ return guest
357
+ }
358
+ if (hasSharedValueId(host)) {
359
+ sabSlotFinalizer.register(host as object, { rt, slotId: host._id })
360
+ }
361
+ return host
362
+ } catch (err) {
363
+ warnShareableFallback('object createShareable failed', err)
364
+ return decorated
365
+ }
366
+ }
367
+
368
+ try {
369
+ const rt = getSabRuntime()
370
+ if (!rt) {
371
+ // worklet runtime not yet wired (test contexts without the host bridge). the
372
+ // decorated SV still works locally, it just won't propagate cross-runtime.
373
+ return decorated
374
+ }
375
+
376
+ const isBool = typeof initial === 'boolean'
377
+ // tell the runtime this slot holds a boolean so worklet-side proxy reads restore
378
+ // the boolean type (Float64 0/1 → false/true), matching upstream.
379
+ const slotId = rt.allocSharedValueSlot(valueToFloat(initial), isBool)
380
+
381
+ const target = decorated as unknown as {
382
+ addListener?: (id: number, listener: (v: unknown) => void) => void
383
+ _value?: unknown
384
+ [k: symbol]: boolean | undefined
385
+ }
386
+
387
+ // upstream's `_value` is defined non-configurable, so we can't override it.
388
+ // instead use the public `addListener(id, fn)` surface to inject a sync listener
389
+ // that bounces every settled write into the slot. this catches both
390
+ // animation-frame writes (valueSetter → _value = animation.current) and direct
391
+ // `.value =` assignments (which route through valueSetter → _value =).
392
+ //
393
+ // the suppression flag prevents the peer-notify roundtrip from looping: when a
394
+ // peer write arrives, we replay it through `_value =` to fire upstream's local
395
+ // listeners (mappers, observers); that fires our sync listener too, but
396
+ // suppression is set so it skips the write back.
397
+ //
398
+ // we use `syncToPeer` (not `writeSlot`) so the sync listener does NOT fire the
399
+ // runtime's local subscribers — the upstream `_value` listeners already fired the
400
+ // fan-out for this local write. firing the runtime subscribers here would recurse
401
+ // back into upstream's `_value` setter and double the upstream listener work for
402
+ // every local write.
403
+ const SAB_SYNC_LISTENER_ID = -(slotId * 2 + 1)
404
+ if (typeof target.addListener === 'function') {
405
+ target.addListener(SAB_SYNC_LISTENER_ID, (newValue: unknown) => {
406
+ if (target[_RNGPUI_SAB_SUPPRESS_NOTIFY]) return
407
+ rt.syncToPeer(slotId, valueToFloat(newValue))
408
+ })
409
+ }
410
+
411
+ // peer writes route through the upstream `_value` setter to fire the local
412
+ // listener fan-out (mappers included) without going through the animation router
413
+ // on `.value`. the listener holds `target` WEAKLY and re-derives the per-instance
414
+ // setter each call: a strong capture here would pin every SharedValue in the
415
+ // long-lived runtime's `listeners` Map forever (the root slot leak). once the
416
+ // wrapper is GC'd the FinalizationRegistry frees the slot, and the resulting
417
+ // releaseSlotState() drops this now-dead listener.
418
+ const weakTarget = new WeakRef(target as object)
419
+ rt.subscribeSlot(slotId, (next) => {
420
+ const t = weakTarget.deref() as typeof target | undefined
421
+ if (!t) return
422
+ const setter = Object.getOwnPropertyDescriptor(t, '_value')?.set
423
+ if (!setter) return
424
+ t[_RNGPUI_SAB_SUPPRESS_NOTIFY] = true
425
+ try {
426
+ setter.call(t, isBool ? next !== 0 : next)
427
+ } finally {
428
+ t[_RNGPUI_SAB_SUPPRESS_NOTIFY] = false
429
+ }
430
+ })
431
+
432
+ // tag with `_id` so the runtime's serializeValue routes this as `{kind:'sv'}` and
433
+ // the peer's deserializeValue hands the worklet body a proxy that reads/writes the
434
+ // same slot.
435
+ Object.defineProperty(target, '_id', {
436
+ value: slotId,
437
+ enumerable: false,
438
+ configurable: true,
439
+ writable: false,
440
+ })
441
+
442
+ if (shouldUseRemoteUIRuntime() && config?.guestDecorator) {
443
+ const guest = createGuestSharedValue(rt, slotId, 'primitive', isBool, config.guestDecorator)
444
+ Object.defineProperty(guest as object, SAB_HOST_TARGET, {
445
+ value: target,
446
+ enumerable: false,
447
+ configurable: true,
448
+ writable: false,
449
+ })
450
+ sabSlotFinalizer.register(guest as object, { rt, slotId })
451
+ return guest
452
+ }
453
+
454
+ sabSlotFinalizer.register(target as object, { rt, slotId })
455
+ return target as THostDecorated
456
+ } catch (err) {
457
+ warnShareableFallback('createShareable failed', err)
458
+ return decorated
459
+ }
460
+ }
461
+
462
+ function hasSharedValueId(value: unknown): value is { _id: number } {
463
+ return (
464
+ !!value && typeof value === 'object' && typeof (value as { _id?: unknown })._id === 'number'
465
+ )
466
+ }
467
+
468
+ type SharedValueSlotKind = 'primitive' | 'object'
469
+
470
+ function createGuestSharedValue<TGuestDecorated>(
471
+ rt: RuntimeShape,
472
+ slotId: number,
473
+ kind: SharedValueSlotKind,
474
+ isBool: boolean,
475
+ guestDecorator: (guest: ShareableGuestBase) => TGuestDecorated,
476
+ ): TGuestDecorated {
477
+ let guestSurface: unknown
478
+ const read = () => {
479
+ if (kind === 'object') return rt.readObjectSlot(slotId)
480
+ const value = rt.readSlot(slotId)
481
+ return isBool ? value !== 0 : value
482
+ }
483
+ // the SAB-plumbing methods must be NON-ENUMERABLE. reanimated's
484
+ // `mutableGuestDecorator` `value` getter calls `mutable.getSync()` and the setter
485
+ // calls `mutable.setAsync()`, so these have to survive for the lifetime of the
486
+ // SharedValue. but consumers iterate the SharedValue with `Object.keys()` — e.g.
487
+ // @gorhom/bottom-sheet's `resetContext` worklet does
488
+ // `Object.keys(context).map(k => context[k] = undefined)`. an enumerable
489
+ // getSync/setAsync would be wiped and `value` access would throw `getSync is not a
490
+ // function`.
491
+ //
492
+ // a plain-value write into the guest SharedValue. the shared slot (and the
493
+ // object-slot Map) is shared memory between the React and UI runtimes, so writing
494
+ // it locally is both (a) immediately visible to a same-frame `getSync()` on this
495
+ // runtime and (b) propagated to the peer via the `svUpdateBatch` flush — which
496
+ // fires the UI-side host SharedValue's listeners (mappers, useAnimatedStyle). this
497
+ // preserves the upstream contract: a worklet that writes `sv.value = x` then
498
+ // synchronously reads it back sees its own write.
499
+ const writeLocalSlot = (value: unknown) => {
500
+ if (kind === 'object') {
501
+ rt.writeObjectSlot(slotId, value)
502
+ } else {
503
+ rt.writeSlot(slotId, valueToFloat(value))
504
+ }
505
+ }
506
+ // route every write through the host runtime ONLY when the payload is (or contains)
507
+ // an animation descriptor — `withTiming`/`withSpring`/… must run their per-frame
508
+ // driver on the reanimated UI runtime. plain values stay synchronous via the shared
509
+ // slot.
510
+ //
511
+ // upstream invariant: ALL writes go through ONE `valueSetter` on ONE UI runtime, and
512
+ // a plain `.value =` write nulls `mutable._animation`, which cancels any running
513
+ // animation (`cancelAnimation` is literally `sv.value = sv.value`). our split runs
514
+ // the animation driver on the UI host but keeps plain writes React-local for
515
+ // same-frame sync read-back — so a plain write would never reach the UI valueSetter
516
+ // and the previous gesture's `onEnd` `withSpring` is never cancelled. mirror the
517
+ // FIRST plain write after an animation to the host valueSetter so its `_animation` is
518
+ // nulled exactly like upstream; keep mirroring until the host setter has actually
519
+ // applied the cancel/reset.
520
+ let hostAnimationPending = false
521
+ let hostPlainSetScheduled = false
522
+ let hostPlainSetPayload: unknown
523
+ let hostPlainSetGeneration = 0
524
+ let hostPlainSetSettledGeneration = 0
525
+ let hostPlainSetInFlight = 0
526
+ const flushHostPlainSet = () => {
527
+ if (!hostPlainSetScheduled) return
528
+ const payload = hostPlainSetPayload
529
+ hostPlainSetScheduled = false
530
+ hostPlainSetPayload = undefined
531
+ const generation = ++hostPlainSetGeneration
532
+ hostPlainSetInFlight++
533
+ void dispatchHostValueSet(rt, guestSurface, payload, true)
534
+ .catch(() => {})
535
+ .finally(() => {
536
+ hostPlainSetInFlight = Math.max(0, hostPlainSetInFlight - 1)
537
+ hostPlainSetSettledGeneration = Math.max(hostPlainSetSettledGeneration, generation)
538
+ if (
539
+ hostPlainSetSettledGeneration >= hostPlainSetGeneration &&
540
+ hostPlainSetInFlight === 0 &&
541
+ !hostPlainSetScheduled
542
+ ) {
543
+ hostAnimationPending = false
544
+ }
545
+ })
546
+ }
547
+ const queueHostPlainSet = (value: unknown) => {
548
+ hostPlainSetPayload = encodeHostValue(value)
549
+ if (hostPlainSetScheduled) return
550
+ hostPlainSetScheduled = true
551
+ queueMicrotask(flushHostPlainSet)
552
+ }
553
+ const resolveSetValue = (value: unknown): unknown => {
554
+ if (
555
+ typeof value === 'function' &&
556
+ (value as unknown as Record<string, unknown>).__isAnimationDefinition !== true
557
+ ) {
558
+ return (value as (current: unknown) => unknown)(read())
559
+ }
560
+ return value
561
+ }
562
+ const setValue = (value: unknown) => {
563
+ const resolved = resolveSetValue(value)
564
+ if (containsAnimationRequest(resolved)) {
565
+ flushHostPlainSet()
566
+ hostAnimationPending = true
567
+ void dispatchHostValueSet(rt, guestSurface, encodeHostValue(resolved), false).catch(() => {})
568
+ } else {
569
+ writeLocalSlot(resolved)
570
+ if (hostAnimationPending) {
571
+ queueHostPlainSet(resolved)
572
+ }
573
+ }
574
+ }
575
+ const guest = {} as ShareableGuestBase
576
+ Object.defineProperties(guest, {
577
+ isHost: { value: false, enumerable: false, configurable: true, writable: true },
578
+ __shareableRef: { value: true, enumerable: false, configurable: true, writable: true },
579
+ getSync: { value: read, enumerable: false, configurable: true, writable: true },
580
+ getAsync: {
581
+ value: () => Promise.resolve(read()),
582
+ enumerable: false,
583
+ configurable: true,
584
+ writable: true,
585
+ },
586
+ setAsync: { value: setValue, enumerable: false, configurable: true, writable: true },
587
+ setSync: { value: setValue, enumerable: false, configurable: true, writable: true },
588
+ })
589
+ guestSurface = guestDecorator.call(guestDecorator, guest)
590
+ Object.defineProperty(guestSurface as object, '_id', {
591
+ value: slotId,
592
+ enumerable: false,
593
+ configurable: true,
594
+ writable: false,
595
+ })
596
+ if (kind === 'object') {
597
+ markObjectSV(guestSurface as object)
598
+ }
599
+ return guestSurface as TGuestDecorated
600
+ }
601
+
602
+ function dispatchHostValueSet(
603
+ rt: RuntimeShape,
604
+ guestSurface: unknown,
605
+ payload: unknown,
606
+ awaitReply: boolean,
607
+ ): Promise<unknown> {
608
+ return rt
609
+ .dispatchWorklet(HOST_SET_SHARED_VALUE_WORKLET, [guestSurface, payload], { awaitReply })
610
+ .catch((err: unknown) => {
611
+ console.error('[rngpui worklets] shared value host set failed:', err)
612
+ throw err
613
+ })
614
+ }
615
+
616
+ function encodeHostValue(value: unknown): unknown {
617
+ const descriptor = readAnimationDescriptor(value)
618
+ if (descriptor) {
619
+ return {
620
+ [RNGPUI_ANIMATION_REQUEST]: true,
621
+ type: descriptor.type,
622
+ args: descriptor.args.map(encodeHostValue),
623
+ }
624
+ }
625
+ if (Array.isArray(value)) return value.map(encodeHostValue)
626
+ if (value && typeof value === 'object' && Object.getPrototypeOf(value) === Object.prototype) {
627
+ const out: Record<string, unknown> = {}
628
+ for (const key of Object.keys(value as Record<string, unknown>)) {
629
+ out[key] = encodeHostValue((value as Record<string, unknown>)[key])
630
+ }
631
+ return out
632
+ }
633
+ return value
634
+ }
635
+
636
+ // true when `value` is an animation descriptor (`withTiming`/`withSpring`/…) or has
637
+ // one nested inside an array / plain object — those must run their per-frame driver
638
+ // on the reanimated UI runtime, so the write routes through
639
+ // `HOST_SET_SHARED_VALUE_WORKLET`. plain values write the shared slot directly.
640
+ function containsAnimationRequest(value: unknown): boolean {
641
+ if (!value || typeof value !== 'object') return false
642
+ if (readAnimationDescriptor(value)) return true
643
+ if (Array.isArray(value)) return value.some(containsAnimationRequest)
644
+ if (Object.getPrototypeOf(value) === Object.prototype) {
645
+ for (const key of Object.keys(value as Record<string, unknown>)) {
646
+ if (containsAnimationRequest((value as Record<string, unknown>)[key])) return true
647
+ }
648
+ }
649
+ return false
650
+ }
651
+
652
+ function readAnimationDescriptor(value: unknown): { type: string; args: unknown[] } | null {
653
+ if (!value || typeof value !== 'object') return null
654
+ const descriptor = (value as { [RNGPUI_REANIMATED_ANIMATION_DESCRIPTOR]?: unknown })[
655
+ RNGPUI_REANIMATED_ANIMATION_DESCRIPTOR
656
+ ]
657
+ if (!descriptor || typeof descriptor !== 'object') return null
658
+ const type = (descriptor as { type?: unknown }).type
659
+ const args = (descriptor as { args?: unknown }).args
660
+ if (typeof type !== 'string' || !Array.isArray(args)) return null
661
+ return { type, args }
662
+ }
663
+
664
+ // =============================================================================
665
+ // runtime accessor — duck-typed against worklet-runtime.ts. resolved via globalThis
666
+ // so the stub and the runtime agree without an import-time resolution cycle.
667
+ // =============================================================================
668
+
669
+ interface RuntimeShape {
670
+ dispatchWorklet(
671
+ fn: WorkletFunction,
672
+ args: unknown[],
673
+ opts?: { awaitReply?: boolean; eventTimestamp?: number },
674
+ ): Promise<unknown>
675
+ registerJSCallback<T extends (...args: unknown[]) => unknown>(fn: T): T
676
+ allocSharedValueSlot(initial: number, isBool?: boolean): number
677
+ readSlot(id: number): number
678
+ writeSlot(id: number, value: number, skipNotify?: boolean): boolean
679
+ syncToPeer(id: number, value: number): boolean
680
+ subscribeSlot(id: number, fn: (value: number) => void): () => void
681
+ allocObjectSharedValueSlot(initial: unknown): number
682
+ readObjectSlot(id: number): unknown
683
+ writeObjectSlot(id: number, value: unknown, skipNotify?: boolean): boolean
684
+ syncObjectToPeer(id: number, value: unknown): boolean
685
+ subscribeObjectSlot(id: number, fn: (value: unknown) => void): () => void
686
+ freeSlot(id: number): void
687
+ }
688
+
689
+ /**
690
+ * Wire an upstream-decorated object-shaped SharedValue (initial is array or plain
691
+ * object) for cross-runtime propagation. Same routing pattern as the primitive path:
692
+ * sync listener bounces local writes to the peer, peer writes route through `_value`
693
+ * setter so upstream listeners (mappers, useDerivedValue) fan out.
694
+ */
695
+ function wireObjectSharedValue(rt: RuntimeShape, decorated: unknown, initial: unknown): unknown {
696
+ const slotId = rt.allocObjectSharedValueSlot(initial)
697
+
698
+ const target = decorated as unknown as {
699
+ addListener?: (id: number, listener: (v: unknown) => void) => void
700
+ modify?: (modifier: (value: unknown) => unknown, forceUpdate?: boolean) => void
701
+ value?: unknown
702
+ _value?: unknown
703
+ [k: symbol]: boolean | undefined
704
+ }
705
+
706
+ const SAB_SYNC_LISTENER_ID = -(slotId * 2 + 1)
707
+ if (typeof target.addListener === 'function') {
708
+ target.addListener(SAB_SYNC_LISTENER_ID, (newValue: unknown) => {
709
+ if (target[_RNGPUI_SAB_SUPPRESS_NOTIFY]) return
710
+ rt.syncObjectToPeer(slotId, newValue)
711
+ })
712
+ }
713
+
714
+ const _valueDesc = Object.getOwnPropertyDescriptor(target, '_value')
715
+
716
+ const readCurrentValue = () => {
717
+ try {
718
+ if (_valueDesc?.get) return _valueDesc.get.call(target)
719
+ } catch {}
720
+ try {
721
+ return target.value
722
+ } catch {}
723
+ return initial
724
+ }
725
+
726
+ // upstream ViewDescriptorsSet.add mutates the descriptor array in place and calls
727
+ // modify(..., false). valueSetter then intentionally skips listener fan-out because
728
+ // the returned array reference is unchanged. on native that mutation happens on the
729
+ // UI runtime where mappers read the same shareable. in rngpui it happens on react,
730
+ // so explicitly mirror object-SV modify results into the UI object slot.
731
+ const originalModify = target.modify
732
+ if (typeof originalModify === 'function') {
733
+ target.modify = (modifier, forceUpdate = true) => {
734
+ originalModify.call(target, modifier, forceUpdate)
735
+ if (!target[_RNGPUI_SAB_SUPPRESS_NOTIFY]) {
736
+ rt.syncObjectToPeer(slotId, readCurrentValue())
737
+ }
738
+ }
739
+ }
740
+
741
+ // weak target + re-derived setter, same rationale as the primitive path: a strong
742
+ // capture would pin every object SharedValue in the runtime's objectListeners Map
743
+ // forever. releaseSlotState() (via the FinalizationRegistry) drops this listener
744
+ // once the wrapper is collected.
745
+ const weakObjTarget = new WeakRef(target as object)
746
+ rt.subscribeObjectSlot(slotId, (next) => {
747
+ const t = weakObjTarget.deref() as typeof target | undefined
748
+ if (!t) return
749
+ const setter = Object.getOwnPropertyDescriptor(t, '_value')?.set
750
+ if (!setter) return
751
+ t[_RNGPUI_SAB_SUPPRESS_NOTIFY] = true
752
+ try {
753
+ setter.call(t, next)
754
+ } finally {
755
+ t[_RNGPUI_SAB_SUPPRESS_NOTIFY] = false
756
+ }
757
+ })
758
+
759
+ Object.defineProperty(target, '_id', {
760
+ value: slotId,
761
+ enumerable: false,
762
+ configurable: true,
763
+ writable: false,
764
+ })
765
+ // brand so the runtime's serializeValue routes this as kind:'svObject' instead of
766
+ // the slot-backed kind:'sv'.
767
+ markObjectSV(target as object)
768
+
769
+ return target
770
+ }
771
+
772
+ function runtime(): RuntimeShape | null {
773
+ const r = (globalThis as { __rngpui_worklet_runtime?: RuntimeShape }).__rngpui_worklet_runtime
774
+ return r ?? null
775
+ }
776
+
777
+ function isWorkletFn(fn: unknown): fn is WorkletFunction {
778
+ if (typeof fn !== 'function') return false
779
+ const f = fn as WorkletFunction
780
+ return typeof f.__workletHash === 'number' && typeof f.__initData?.code === 'string'
781
+ }
782
+
783
+ // =============================================================================
784
+ // core dispatchers
785
+ // =============================================================================
786
+
787
+ /**
788
+ * In real reanimated, runOnJS schedules `fn` on the JS runtime. On rngpui the React
789
+ * runtime IS the JS runtime, so the wrapper executes locally — the worklet runtime
790
+ * registers the wrapper so that when it's captured in a closure that later ships to
791
+ * the UI runtime, the UI-side proxy routes call-args back here. Without the runtime,
792
+ * identity.
793
+ */
794
+ export function runOnJS<T extends (...args: unknown[]) => unknown>(fn: T): T {
795
+ const rt = runtime()
796
+ if (!rt) return fn
797
+ return rt.registerJSCallback(fn)
798
+ }
799
+
800
+ /**
801
+ * `runOnUI(fn)` returns a wrapper that schedules workletized functions on the UI
802
+ * runtime; the UI runtime itself runs them on its local microtask path.
803
+ */
804
+ export function runOnUI<T extends (...args: unknown[]) => unknown>(fn: T): T {
805
+ const wrapped = ((...args: unknown[]) => {
806
+ scheduleOnUI(fn, ...args)
807
+ }) as unknown as T
808
+ return wrapped
809
+ }
810
+
811
+ /**
812
+ * `runOnRuntime(runtime, fn)` is the explicit cross-runtime escape hatch. rngpui has
813
+ * one peer runtime (the UI runtime); this routes through the worklet runtime when
814
+ * available so callers that genuinely need off-thread execution still get it.
815
+ * Degrades to local execution when no peer is wired.
816
+ */
817
+ export function runOnRuntime<T extends (...args: unknown[]) => unknown>(
818
+ _runtime: unknown,
819
+ fn: T,
820
+ ): T {
821
+ const wrapped = ((...args: unknown[]) => {
822
+ const rt = runtime()
823
+ if (!rt || !isWorkletFn(fn)) return (fn as (...a: unknown[]) => unknown).apply(fn, args)
824
+ return rt.dispatchWorklet(fn as unknown as WorkletFunction, args, { awaitReply: false })
825
+ }) as unknown as T
826
+ return wrapped
827
+ }
828
+
829
+ /** Real signature: scheduleOnRN(fn, ...args) — calls fn(...args) async. */
830
+ export function scheduleOnRN<T extends (...args: unknown[]) => unknown>(
831
+ fn: T,
832
+ ...args: unknown[]
833
+ ): void {
834
+ if (typeof fn !== 'function') return
835
+ const scheduledArgs = args.map(cloneScheduledValue)
836
+ // we ARE the RN runtime (react). just queue locally.
837
+ queueMicrotask(() => {
838
+ try {
839
+ fn.apply(null, scheduledArgs)
840
+ } catch (err) {
841
+ console.error('[rngpui worklets] scheduleOnRN threw:', err)
842
+ }
843
+ })
844
+ }
845
+
846
+ function cloneScheduledValue(value: unknown): unknown {
847
+ if (Array.isArray(value)) return value.map(cloneScheduledValue)
848
+ if (!value || typeof value !== 'object') return value
849
+ const proto = Object.getPrototypeOf(value)
850
+ if (proto !== Object.prototype && proto !== null) return value
851
+ return Object.fromEntries(
852
+ Object.entries(value as Record<string, unknown>).map(([key, item]) => [
853
+ key,
854
+ cloneScheduledValue(item),
855
+ ]),
856
+ )
857
+ }
858
+
859
+ /**
860
+ * `scheduleOnUI(fn, ...args)` — real iOS schedules `fn` on the UI runtime. rngpui's
861
+ * UI runtime is the second JavaScriptCore runtime, so workletized callbacks dispatch there
862
+ * from the React runtime. plain callbacks (not serializable worklets) and the no-
863
+ * bridge path keep the local microtask behavior.
864
+ */
865
+ export function scheduleOnUI<T extends (...args: unknown[]) => unknown>(
866
+ fn: T,
867
+ ...args: unknown[]
868
+ ): void {
869
+ const rt = runtime()
870
+ const isWorklet = isWorkletFn(fn)
871
+ const dispatchToShell = shouldUseRemoteUIRuntime() && !!rt && isWorklet
872
+ if (dispatchToShell) {
873
+ rt.dispatchWorklet(fn as unknown as WorkletFunction, args, { awaitReply: false }).catch(
874
+ (err: unknown) => {
875
+ console.error('[rngpui worklets] scheduleOnUI dispatch failed:', err)
876
+ },
877
+ )
878
+ return
879
+ }
880
+ queueMicrotask(() => {
881
+ try {
882
+ // call with fn as `this` so the babel-emitted `this.__closure.X` references
883
+ // resolve, same convention as the worklet runtime.
884
+ ;(fn as { _recur?: unknown })._recur = fn
885
+ fn.apply(fn, args)
886
+ } catch (err) {
887
+ console.error('[rngpui worklets] scheduleOnUI local threw:', err)
888
+ }
889
+ })
890
+ }
891
+
892
+ /**
893
+ * Invoke a GestureDetector lifecycle callback respecting RN's worklet / non-worklet
894
+ * runtime split. a callback that IS a worklet runs on the UI runtime — the same
895
+ * runtime as the `useAnimatedStyle` mapper and the `withTiming`/`withSpring` driver —
896
+ * so a shared value's `valueSetter` animation lifecycle stays coherent.
897
+ *
898
+ * the `gesture` carries RNGH's `runOnJS` flag. a callback is dispatched to the UI
899
+ * runtime ONLY when it is a worklet AND the gesture did not opt into `runOnJS(true)`.
900
+ * a `runOnJS(true)` gesture (e.g. tamagui's press gesture, whose onBegin/onEnd are
901
+ * babel-workletized yet mutate React state) must run on the React runtime. a
902
+ * non-worklet callback — and every callback when the bridge is absent — runs
903
+ * synchronously on the React runtime as before.
904
+ */
905
+ export function invokeGestureCallback(
906
+ // biome-ignore lint/suspicious/noExplicitAny: gesture callbacks carry event-specific payloads
907
+ cb: ((...a: any[]) => unknown) | null | undefined,
908
+ gesture: { _runOnJS?: boolean } | null | undefined,
909
+ ...args: unknown[]
910
+ ): void {
911
+ if (typeof cb !== 'function') return
912
+ const rt = runtime()
913
+ const runOnJSFlag = gesture?._runOnJS === true
914
+ const eventTimestamp =
915
+ args[0] &&
916
+ typeof args[0] === 'object' &&
917
+ typeof (args[0] as Record<symbol, unknown>)[RNGPUI_GESTURE_EVENT_TIMESTAMP] === 'number'
918
+ ? ((args[0] as Record<symbol, unknown>)[RNGPUI_GESTURE_EVENT_TIMESTAMP] as number)
919
+ : undefined
920
+ if (rt && shouldUseRemoteUIRuntime() && isWorkletFn(cb) && !runOnJSFlag) {
921
+ const dispatchOpts: { awaitReply: false; eventTimestamp?: number } = { awaitReply: false }
922
+ if (typeof eventTimestamp === 'number') {
923
+ dispatchOpts.eventTimestamp = eventTimestamp
924
+ }
925
+ rt.dispatchWorklet(cb as unknown as WorkletFunction, args, dispatchOpts).catch(
926
+ (err: unknown) => {
927
+ console.error('[rngpui rngh] gesture callback dispatch failed:', err)
928
+ },
929
+ )
930
+ return
931
+ }
932
+ ;(cb as { _recur?: unknown })._recur = cb
933
+ cb.apply(cb, args)
934
+ }
935
+
936
+ /**
937
+ * Sync UI execution is not feasible across runtimes (a blocking wait on the React
938
+ * runtime would freeze the bundle). Best-effort: identity. Upstream only uses
939
+ * executeOnUIRuntimeSync for init-time reads — apps that genuinely need synchronous
940
+ * UI-runtime semantics will see local/stale state, which is acceptable for those
941
+ * bootstrap reads.
942
+ */
943
+ export function executeOnUIRuntimeSync<T extends (...args: unknown[]) => unknown>(fn: T): T {
944
+ return fn
945
+ }
946
+
947
+ /** Sync runOnUI — runs LOCALLY on the React runtime. cross-runtime sync is impossible;
948
+ * upstream only uses this for init-time reads. set `this = fn` so the babel-emitted
949
+ * `this.__closure.X` and `this._recur` references inside worklet bodies resolve. */
950
+ export function runOnUISync<T extends (...args: unknown[]) => unknown>(
951
+ fn: T,
952
+ ...args: unknown[]
953
+ ): unknown {
954
+ ;(fn as { _recur?: unknown })._recur = fn
955
+ return fn.apply(fn, args)
956
+ }
957
+
958
+ // runOnUIAsync awaits the dispatch result.
959
+ export function runOnUIAsync<T extends (...args: unknown[]) => unknown>(
960
+ fn: T,
961
+ ...args: unknown[]
962
+ ): Promise<unknown> {
963
+ const rt = runtime()
964
+ if (!rt || !isWorkletFn(fn) || !shouldUseRemoteUIRuntime()) {
965
+ return Promise.resolve(fn.apply(fn, args))
966
+ }
967
+ return rt.dispatchWorklet(fn as unknown as WorkletFunction, args, { awaitReply: true })
968
+ }
969
+
970
+ // =============================================================================
971
+ // shareables — rngpui shares state via the slot-backed SharedValue layer when
972
+ // applicable; the rest of these are identity matching the previous stub contract.
973
+ // =============================================================================
974
+
975
+ // stable across the React realm; the runtime serializer reads the same Symbol.for
976
+ // key to emit a `shareableRef` instead of a deep snapshot, so a re-shipped shareable
977
+ // resolves to the SAME persistent UI-side object (upstream `serializableMappingCache`
978
+ // contract). without the stamp the serializer deep-clones reanimated's `remoteState`
979
+ // on every stopMapper/startMapper, breaking 2nd-animation spring continuity.
980
+ const SHAREABLE_ID_KEY = Symbol.for('rngpui.shareableId')
981
+ let nextShareableId = 1
982
+
983
+ export function makeShareable<T>(value: T): T {
984
+ // identity on the home (react) runtime — matches upstream, where the shareable on
985
+ // its home runtime is just the object. only stamp plain mutable records (reanimated
986
+ // `remoteState`, scroll-handler state); leave primitives / SharedValues / functions
987
+ // / frozen objects untouched.
988
+ if (
989
+ value !== null &&
990
+ typeof value === 'object' &&
991
+ Object.getPrototypeOf(value) === Object.prototype &&
992
+ !Object.isFrozen(value)
993
+ ) {
994
+ const rec = value as Record<symbol, unknown>
995
+ if (typeof rec[SHAREABLE_ID_KEY] !== 'number') {
996
+ try {
997
+ Object.defineProperty(value, SHAREABLE_ID_KEY, {
998
+ value: nextShareableId++,
999
+ enumerable: false,
1000
+ writable: false,
1001
+ configurable: false,
1002
+ })
1003
+ } catch {
1004
+ // sealed between check and define — fall back to identity (the deep-snapshot
1005
+ // path; no continuity, but no crash).
1006
+ }
1007
+ }
1008
+ }
1009
+ return value
1010
+ }
1011
+
1012
+ export function createSerializable(config: { __init?: () => unknown }): unknown {
1013
+ return config
1014
+ }
1015
+
1016
+ export function createSynchronizable<T>(initial: T): Synchronizable<T> {
1017
+ let value = initial
1018
+ return {
1019
+ getBlocking() {
1020
+ return value
1021
+ },
1022
+ setBlocking(v: T) {
1023
+ value = v
1024
+ },
1025
+ }
1026
+ }
1027
+
1028
+ const SERIALIZABLE_MAPPING_CACHE = Symbol.for('rngpui.serializableMappingCache')
1029
+ export const serializableMappingCache = ((
1030
+ globalThis as Record<symbol, WeakMap<object, unknown> | undefined>
1031
+ )[SERIALIZABLE_MAPPING_CACHE] ??= new WeakMap<object, unknown>())
1032
+
1033
+ // =============================================================================
1034
+ // worklet introspection — polyfill __closure/__workletHash on demand so upstream's
1035
+ // areWorkletsEqual (useHandler/useEvent) never throws on a not-yet-transformed fn.
1036
+ // =============================================================================
1037
+
1038
+ export function isWorkletFunction(fn: unknown): fn is WorkletFunction {
1039
+ if (typeof fn !== 'function') return false
1040
+ const f = fn as WorkletFunction
1041
+ if (f.__closure === undefined) {
1042
+ try {
1043
+ Object.defineProperty(f, '__closure', { value: {}, writable: true, configurable: true })
1044
+ } catch {}
1045
+ }
1046
+ if (f.__workletHash === undefined) {
1047
+ try {
1048
+ Object.defineProperty(f, '__workletHash', { value: 0, writable: true, configurable: true })
1049
+ } catch {}
1050
+ }
1051
+ return true
1052
+ }
1053
+
1054
+ export function createWorkletRuntime(name: string, _initializer?: () => void): unknown {
1055
+ return { name }
1056
+ }
1057
+
1058
+ export function callMicrotasks(): void {
1059
+ // host js engine drains microtasks after each loop tick.
1060
+ }
1061
+
1062
+ export const WorkletsModule: IWorkletsModule = {
1063
+ makeShareableClone: (value: unknown) => value,
1064
+ scheduleOnUI: (fn: () => void) => scheduleOnUI(fn),
1065
+ executeOnUIRuntimeSync: (fn) => fn,
1066
+ createWorkletRuntime: (name: string) => ({ name }),
1067
+ }
1068
+
1069
+ export function useWorklet<T>(fn: T, _deps?: unknown[]): T {
1070
+ return fn
1071
+ }
1072
+
1073
+ export function createWorklet<T extends (...args: unknown[]) => unknown>(fn: T): T {
1074
+ return fn
1075
+ }
1076
+
1077
+ export class WorkletRuntimeClass {
1078
+ name: string
1079
+ constructor(name = 'default') {
1080
+ this.name = name
1081
+ }
1082
+ run<T>(fn: () => T): T {
1083
+ return fn()
1084
+ }
1085
+ destroy(): void {}
1086
+ }
1087
+
1088
+ export const Worklets = {
1089
+ defaultContext: { name: 'default' },
1090
+ createContext(name = 'default') {
1091
+ return { name }
1092
+ },
1093
+ createRuntime(name = 'default', _initializer?: () => void) {
1094
+ return new WorkletRuntimeClass(name)
1095
+ },
1096
+ createSharedValue<T>(initial: T) {
1097
+ return { value: initial }
1098
+ },
1099
+ }
1100
+
1101
+ export function currentContext() {
1102
+ return Worklets.defaultContext
1103
+ }
1104
+
1105
+ export const shareableMappingCache = serializableMappingCache
1106
+ export function makeShareableCloneRecursive(value: unknown) {
1107
+ return value
1108
+ }
1109
+ export function makeShareableCloneOnUIRecursive(value: unknown) {
1110
+ return value
1111
+ }
1112
+ export function isShareableRef(_value: unknown): boolean {
1113
+ return false
1114
+ }
1115
+ export const isSerializableRef = isShareableRef
1116
+
1117
+ // =============================================================================
1118
+ // runtime-kind type guards — the React runtime claims UI (2) via the seam's
1119
+ // __RUNTIME_KIND=2, so isUIRuntime/isWorkletRuntime are true and isRNRuntime is
1120
+ // false. Upstream gates the runOnUI hop on these; reporting UI keeps reanimated's
1121
+ // hook/component layer on the eager path while runOnUI still dispatches to the real
1122
+ // UI runtime when the bridge is present. Read the live global so the guards stay
1123
+ // honest if the kind ever changes.
1124
+ // =============================================================================
1125
+
1126
+ export function getRuntimeKind(): RuntimeKind {
1127
+ const kind = (globalThis as { __RUNTIME_KIND?: number }).__RUNTIME_KIND
1128
+ return kind === RuntimeKind.ReactNative || kind === RuntimeKind.Worker ? kind : RuntimeKind.UI
1129
+ }
1130
+
1131
+ export function scheduleOnRuntime(_runtime: unknown, fn: WorkletFunction, ...args: unknown[]) {
1132
+ scheduleOnUI(fn, ...args)
1133
+ }
1134
+
1135
+ export function isRNRuntime(): boolean {
1136
+ return getRuntimeKind() === RuntimeKind.ReactNative
1137
+ }
1138
+
1139
+ export function isUIRuntime(): boolean {
1140
+ return getRuntimeKind() === RuntimeKind.UI
1141
+ }
1142
+
1143
+ export function isWorkerRuntime(): boolean {
1144
+ return getRuntimeKind() === RuntimeKind.Worker
1145
+ }
1146
+
1147
+ export function isWorkletRuntime(): boolean {
1148
+ return getRuntimeKind() !== RuntimeKind.ReactNative
1149
+ }
1150
+
1151
+ export function runOnRuntimeAsync<T extends (...args: unknown[]) => unknown>(
1152
+ runtime: unknown,
1153
+ fn: T,
1154
+ ): T {
1155
+ return runOnRuntime(runtime, fn)
1156
+ }
1157
+
1158
+ export function runOnRuntimeSyncWithId<T extends (...args: unknown[]) => unknown>(
1159
+ _runtimeId: number,
1160
+ fn: T,
1161
+ ...args: unknown[]
1162
+ ): unknown {
1163
+ return runOnUISync(fn, ...args)
1164
+ }
1165
+
1166
+ export function scheduleOnRuntimeWithId(
1167
+ _runtimeId: number,
1168
+ fn: WorkletFunction,
1169
+ ...args: unknown[]
1170
+ ): void {
1171
+ scheduleOnUI(fn, ...args)
1172
+ }
1173
+
1174
+ export function isShareable(value: unknown): boolean {
1175
+ if (value == null || typeof value !== 'object') return false
1176
+ const v = value as { __isShareable?: boolean; _isReanimatedSharedValue?: boolean }
1177
+ return Boolean(v.__isShareable || v._isReanimatedSharedValue)
1178
+ }
1179
+
1180
+ export function isSynchronizable(value: unknown): boolean {
1181
+ if (value == null || typeof value !== 'object') return false
1182
+ const v = value as { __isSynchronizable?: boolean; addListener?: unknown; removeListener?: unknown }
1183
+ if (v.__isSynchronizable) return true
1184
+ return typeof v.addListener === 'function' && typeof v.removeListener === 'function'
1185
+ }
1186
+
1187
+ const _customSerializables: Array<{ klass: unknown; serializer: unknown }> = []
1188
+ export function registerCustomSerializable(klass: unknown, serializer: unknown): void {
1189
+ _customSerializables.push({ klass, serializer })
1190
+ }
1191
+
1192
+ export function getStaticFeatureFlag(_name: string): boolean {
1193
+ return false
1194
+ }
1195
+ export function getDynamicFeatureFlag(_name: string): boolean {
1196
+ return false
1197
+ }
1198
+ export function setDynamicFeatureFlag(_name: string, _value: boolean): void {}
1199
+
1200
+ // Reanimated installs this debug hook during module initialization. Its native
1201
+ // implementation is a no-op that reports false, and RNGPUI has no slow-animation mode.
1202
+ export function toggleSlowAnimationsOnUIRuntime(): boolean {
1203
+ return false
1204
+ }
1205
+
1206
+ export default {
1207
+ runOnJS,
1208
+ runOnUI,
1209
+ runOnRuntime,
1210
+ scheduleOnRN,
1211
+ scheduleOnUI,
1212
+ makeShareable,
1213
+ createSerializable,
1214
+ createSynchronizable,
1215
+ serializableMappingCache,
1216
+ isWorkletFunction,
1217
+ executeOnUIRuntimeSync,
1218
+ createWorkletRuntime,
1219
+ callMicrotasks,
1220
+ WorkletsModule,
1221
+ RuntimeKind,
1222
+ useWorklet,
1223
+ createWorklet,
1224
+ WorkletRuntime: WorkletRuntimeClass,
1225
+ Worklets,
1226
+ currentContext,
1227
+ }
1228
+
1229
+ // install `globalThis.__workletsModuleProxy` so reanimated's NativeWorklets
1230
+ // constructor doesn't throw "Native part of Worklets doesn't seem to be initialized"
1231
+ // before the bundle finishes evaluating. Identity-shaped serializables + local/
1232
+ // dispatched scheduling — there is one worklet execution path (this module) for
1233
+ // mappers, useAnimatedStyle, _updateProps.
1234
+ ;(() => {
1235
+ const g = globalThis as Record<string, unknown> & { __workletsModuleProxy?: unknown }
1236
+ if (g.__workletsModuleProxy !== undefined) return
1237
+ const wrapAsSerializable = (value: unknown) => ({ __init: () => value })
1238
+ const proxy = {
1239
+ makeShareableClone: (value: unknown) => value,
1240
+ makeShareableCloneOnUI: (value: unknown) => value,
1241
+ createSerializable: (value: unknown) => wrapAsSerializable(value),
1242
+ createSerializableImport: () => wrapAsSerializable(undefined),
1243
+ createSerializableString: (s: string) => wrapAsSerializable(s),
1244
+ createSerializableNumber: (n: number) => wrapAsSerializable(n),
1245
+ createSerializableBoolean: (b: boolean) => wrapAsSerializable(b),
1246
+ createSerializableBigInt: (b: bigint) => wrapAsSerializable(b),
1247
+ createSerializableUndefined: () => wrapAsSerializable(undefined),
1248
+ createSerializableNull: () => wrapAsSerializable(null),
1249
+ createSerializableTurboModuleLike: (props: object) => wrapAsSerializable(props),
1250
+ createSerializableObject: (obj: object) => wrapAsSerializable(obj),
1251
+ createSerializableHostObject: (obj: object) => wrapAsSerializable(obj),
1252
+ createSerializableArray: (arr: unknown[]) => wrapAsSerializable(arr),
1253
+ createSerializableMap: (keys: unknown[], values: unknown[]) =>
1254
+ wrapAsSerializable(new Map(keys.map((k, i) => [k, values[i]]))),
1255
+ createSerializableSet: (values: unknown[]) => wrapAsSerializable(new Set(values)),
1256
+ createSerializableInitializer: (obj: object) => wrapAsSerializable(obj),
1257
+ createSerializableFunction: (fn: (...a: unknown[]) => unknown) => wrapAsSerializable(fn),
1258
+ createSerializableWorklet: (worklet: object) => wrapAsSerializable(worklet),
1259
+ createCustomSerializable: (data: unknown) => data,
1260
+ registerCustomSerializable: () => {},
1261
+ createShareable: (_hostRuntimeId: number, initial: unknown) => wrapAsSerializable(initial),
1262
+ scheduleOnUI: (serializable: { __init?: () => unknown }) => {
1263
+ const fn = serializable?.__init?.()
1264
+ if (typeof fn === 'function') scheduleOnUI(fn as (...args: unknown[]) => unknown)
1265
+ },
1266
+ runOnUISync: (worklet: { __init?: () => unknown }) => worklet?.__init?.(),
1267
+ createWorkletRuntime: (name: string) => ({ name }),
1268
+ scheduleOnRuntime: () => {},
1269
+ scheduleOnRuntimeWithId: () => {},
1270
+ runOnRuntimeSync: () => undefined,
1271
+ runOnRuntimeSyncWithId: () => undefined,
1272
+ createSynchronizable: (value: unknown) => ({ value, __init: () => value }),
1273
+ synchronizableGetDirty: (ref: { value?: unknown }) => ref?.value,
1274
+ synchronizableGetBlocking: (ref: { value?: unknown }) => ref?.value,
1275
+ synchronizableSetBlocking: (ref: { value?: unknown }, next: unknown) => {
1276
+ if (ref) ref.value = next
1277
+ },
1278
+ synchronizableLock: () => {},
1279
+ synchronizableUnlock: () => {},
1280
+ reportFatalErrorOnJS: (msg: string, stack: string) => {
1281
+ console.error('[reanimated worklet]', msg, stack)
1282
+ },
1283
+ getStaticFeatureFlag: () => false,
1284
+ setDynamicFeatureFlag: () => {},
1285
+ getUIRuntimeHolder: () => RNGPUI_UI_RUNTIME_HOLDER,
1286
+ getUISchedulerHolder: () => RNGPUI_UI_SCHEDULER_HOLDER,
1287
+ }
1288
+ Object.defineProperty(g, '__workletsModuleProxy', {
1289
+ value: proxy,
1290
+ writable: true,
1291
+ enumerable: false,
1292
+ configurable: true,
1293
+ })
1294
+ // reanimated v4 branches heavily on __RUNTIME_KIND. claim UI (2) — see the seam.
1295
+ ;(g as { __RUNTIME_KIND?: number }).__RUNTIME_KIND = RuntimeKind.UI
1296
+ })()
1297
+
1298
+ // lazy object-shareable wirer for the worklet-runtime serializer.
1299
+ //
1300
+ // createShareable's object branch wires a SharedValue EAGERLY (allocates an object
1301
+ // slot, attaches `_id` + the OBJECT_SV_BRAND, installs the peer-sync listeners). when
1302
+ // that eager wiring can't run — the slot pool is exhausted under churn or the runtime
1303
+ // isn't wired yet — it falls back to the local-only `decorated` mutable, which has NO
1304
+ // `_id`. if such a mutable is later captured in a worklet closure and shipped to the
1305
+ // UI runtime, the serializer would deep-clone it into a plain object whose peer-side
1306
+ // `.get()` is missing → tamagui's animatedStyle worklet throws `configRef.get is not
1307
+ // a function`.
1308
+ //
1309
+ // upstream reanimated never has this gap: a SharedValue is ALWAYS shippable because
1310
+ // its shareable clone is made LAZILY at ship time (makeShareableCloneRecursive).
1311
+ // restore that invariant by exposing the SAME wireObjectSharedValue used by
1312
+ // createShareable so the serializer can wire an unwired mutable on first ship.
1313
+ ;(() => {
1314
+ const g = globalThis as Record<string, unknown> & {
1315
+ __rngpui_wire_object_shareable?: (value: { value: unknown }) => number | null
1316
+ }
1317
+ if (typeof g.__rngpui_wire_object_shareable === 'function') return
1318
+ g.__rngpui_wire_object_shareable = (value: { value: unknown }): number | null => {
1319
+ if (hasSharedValueId(value)) return value._id
1320
+ const rt = getSabRuntime()
1321
+ if (!rt) return null
1322
+ try {
1323
+ const host = wireObjectSharedValue(rt, value, value.value)
1324
+ return hasSharedValueId(host) ? host._id : null
1325
+ } catch (err) {
1326
+ warnShareableFallback('lazy object shareable wiring failed', err)
1327
+ return null
1328
+ }
1329
+ }
1330
+ })()
1331
+
1332
+ // reference the imported runtime instance type so the import is not elided; it
1333
+ // documents the shape `runtime()` duck-types against.
1334
+ export type { WorkletRuntimeInstance }