Skip to content

feat: add worklet bridge for Reanimated shared value support#250

Open
mfazekas wants to merge 114 commits into
feat/rive-ios-experimentalfrom
feat/worklet-bridge
Open

feat: add worklet bridge for Reanimated shared value support#250
mfazekas wants to merge 114 commits into
feat/rive-ios-experimentalfrom
feat/worklet-bridge

Conversation

@mfazekas
Copy link
Copy Markdown
Collaborator

@mfazekas mfazekas commented May 12, 2026

Port of #79 to the experimental repo. Adds worklet bridge that installs Nitro's dispatcher on Reanimated's UI runtime, enabling ViewModel property listeners to drive SharedValues on the UI thread.


Stack:

  1. feat!: experimental Rive runtime backend (iOS + Android) #134
  2. feat: add worklet bridge for Reanimated shared value support #250
  3. feat: add react-query + async APIs for exerciser #252

mfazekas and others added 30 commits April 24, 2026 13:30
Add new experimental iOS backend (ios/new/) with synchronous API,
move legacy backend files to ios/legacy/, add getEnums() support,
retry listener streams on missingData, and restore TestComponentOverlay.

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)
getEnums() in legacy now throws directing users to the experimental
backend instead of creating throwaway Worker+File instances.

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)
… binding

Each Worker has its own C++ command server with its own m_artboards handle map.
Creating separate Workers per file meant artboard handles from one file were
invalid on another file's server. Using a shared singleton Worker fixes cross-file
artboard property set. Also wires fit/alignment through experimental Fit enum and
improves asset type detection with audio/font magic bytes.

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Happy <yesreply@happy.engineering>
Passing fit to the Rive() constructor breaks layout mode because
the MTKView drawable isn't ready yet. Set rive.fit after
setupRiveUIView() instead.
Port Flutter data binding tests for VM access, enums, creation
variants, list properties, artboard and image properties. Includes
new .riv test assets and react-native-harness upgrade to alpha.25.
…ceByIndex

Prevents fatal crash when passing negative numbers to Swift APIs
expecting unsigned integers.
CocoaPods doesn't embed SPM-resolved dynamic frameworks automatically.
Patches the embed script to include RiveRuntime when USE_RIVE_SPM=1.
Move 19 backend-specific files to src/legacy/java/, add Gradle sourceSets
switching via USE_RIVE_NEW_API property, prepare empty experimental dirs.
Uses the new handle-based Rive Android SDK (app.rive.*) with CommandQueue,
path-based ViewModelInstance property access, and Kotlin Flows for reactivity.
Throws UnsupportedOperationException for SMI inputs, text runs, and events.
Also fixes Gradle property resolution to use rootProject.findProperty.
Custom TextureView renderer with Choreographer render loop, CommandQueue
polling infrastructure, ViewModel resolution and property validation,
and example Compose test activities for manual testing.
Also fix viewModelByIndex/ByName on Android legacy to catch SDK
exceptions instead of letting them propagate as JniExceptions.
…fferences

Fix colorProperty setter Double→Int overflow on Android legacy
(toLong().toInt()). Make enum and replaceViewModel tests tolerant
of Android SDK behavioral differences.
Use .viewModelDefault(from: .name(vmName)) for default instance
creation instead of artboard-based lookup that only worked for
the default artboard's VM. Skip instanceName, color get/set, and
non-existent property checks on experimental iOS where the SDK
doesn't support them.
…anceName

Add async variants for viewModelByName, defaultArtboardViewModel, and
ViewModel create methods. Pass instanceName through at creation time on
both iOS and Android experimental backends as a workaround for the SDK
not exposing ViewModelInstance.name.
…rimental iOS

Color.argbValue is now public in rive-ios 6.15.2 — implement getValue via
blockingAsync, addListener via valueStream, and fix setter crash by using
UInt32(bitPattern:) for negative ARGB values from JS.
- Always use SPM for RiveRuntime (remove CocoaPods fallback)
- Add RiveSPMEmbedFix module to auto-embed RiveRuntime.framework
- Pin SPM to exactVersion instead of upToNextMajorVersion
- Replace USE_RIVE_SPM with USE_RIVE_EXPERIMENTAL_RUNTIME to select
  legacy vs experimental backend independently of SPM
…erimental iOS test issues

- Rename USE_RIVE_EXPERIMENTAL_RUNTIME to USE_RIVE_NEW_API (matches Android)
- Remove USE_RIVE_SPM and all SPM embedding hacks from podspec/Podfile
- Use standard CocoaPods dependency for RiveRuntime
- Emit initial value in experimental addListener (number/string/bool/enum/color)
- Guard tests that crash on experimental iOS (list ops, autoPlay, artboard/image loading)
- Handle createInstanceByName throwing on experimental backend
mfazekas added 24 commits April 24, 2026 13:30
…nings (#201)

## Summary
- Adds `RiveLog` — a general-purpose logging system that surfaces native
logs to JS via a configurable handler
- Default handler uses `console.error/warn/log` so logs show in the RN
console out of the box
- On both platforms, hooks into the Rive SDK's pluggable
`RiveLog.Logger` to unify C++ runtime logs (state machine, artboard,
command queue diagnostics) through the JS handler
- Adds `DeprecationWarning` utility that emits once-per-session warnings
when deprecated blocking methods are called

## Test plan
- [ ] Build exerciser on iOS + Android
- [ ] Call a deprecated method (e.g. `riveFile.viewModelByName(...)`) —
verify warning in RN console
- [ ] Call same method again — verify no duplicate warning
- [ ] `RiveLog.setHandler(() => {})` — verify all logs suppressed
- [ ] `RiveLog.resetHandler()` — verify back to default console output
- [ ] Verify `onError` prop on RiveView still works independently
… backend

RiveUIView creates its MTKView lazily during the first layout pass.
Setting rive.fit immediately after creating the view had no effect
because the drawable didn't exist yet. Store the desired fit as
pendingFit and apply it in layoutSubviews once the MTKView is present.
Add `RiveLog.setLogLevel(level)` to filter log output by severity.
Levels: `debug`, `info`, `warn`, `error`. Default is `warn`.

## Test plan
- `RiveLog.setLogLevel('error')` suppresses warn/info/debug logs
- `RiveLog.setLogLevel('debug')` shows all logs
Bump RiveRuntime from 6.19.0 to 6.19.1. Remove `@_spi(RiveExperimental)`
imports — no longer needed with this version.
The experimental factory already has `var backend: String {
"experimental" }`. This adds the matching property to the legacy side so
`RiveFileFactory.backend` works for both backends.
`Int32(value)` traps when ARGB color values exceed `Int32.max` (e.g.
`0xFF0000FF` = opaque blue). Uses `Int64` + `truncatingIfNeeded` for
safe conversion.
- Pass `fit:` directly in `Rive()` constructor instead of setting it
post-creation. Removes the `layoutSubviews`/`pendingFit` workaround
since rive-ios 6.19.1 handles this (rive-ios#443)
- Warn when `updateReferencedAssets` is called on experimental backend
(not supported — concurrency API can't update already-bound artboard
assets)
- Switch asset registration from parallel `TaskGroup` to sequential to
ensure command queue ordering
- Add debug logging to `ExperimentalAssetLoader`
- Add explicit `type` fields to OutOfBandAssets example
- Add `expo-font` plugin for `kanit_regular.ttf` in expo examples

## Test plan
- QuickStart + DataBindingArtboards render correctly with `Fit.Layout`
on first mount
- Out-of-Band Assets example loads initial assets correctly
- `updateReferencedAssets` logs a warning instead of silently failing
Add addInstanceAsync, addInstanceAtAsync, removeInstanceAsync,
removeInstanceAtAsync, swapAsync. Deprecate sync versions that
return before knowing if the operation succeeded.
The library module already depends on the correct version via
package.json runtimeVersions. The pin caused version conflicts.
… app

Debug activities that import app.rive.* directly, causing the example
app to need an explicit rive-android dependency. Easy to recreate
when needed for native SDK debugging.
Removes the workaround that stripped the `RiveRuntime.Swift` submodule
from modulemaps to avoid ODR conflicts. No longer needed since the
upstream XCFramework switched from C++/ObjC++ interop to C/ObjC interop
— the generated Swift header no longer emits `swift::` namespace types.

Tested building with both Xcode 16.4 and Xcode 26.2 with the unstripped
modulemap — no ODR errors.

**Note:** This PR depends on a RiveRuntime XCFramework built with C/ObjC
interop. If the build fails with ODR errors like
`'swift::Optional::init' has different definitions in different
modules`, the workaround still needs to be re-added (revert this PR).
You can verify by checking `grep -c 'namespace swift'
RiveRuntime-Swift.h` — it should be 0.
- Rename `ExperimentalViewConfiguration` → `ViewConfiguration`,
`ExperimentalBindData` → `BindData`, `ExperimentalAssetLoader` →
`AssetLoader`
- Rename `toExperimentalFit` → `toRiveFit`, `toExperimentalAlignment` →
`toRiveAlignment`
- Remove `RCTLog` debug lines from `RiveReactNativeView` and
`HybridRiveFileFactory`
- Remove `RCTLogInfo` from `AssetLoader` — error/warn logs are
sufficient
- Update "Experimental API" comments to "concurrency API"
Navigating away from a Rive screen produced:
```
Draw failed: Artboard instance is null
Failed to swap EGL buffers: EGL_BAD_SURFACE
```

The command queue processes commands in order: `close(artboard)` then
`draw(artboard)`. The draw finds the artboard already closed.

Fix: don't call `.close()` in `dispose()` — just null the handles and
stop the render loop. The command queue drains naturally and resources
are cleaned up by GC.

## Test plan
- Open any Rive example → navigate back → no errors in logcat
…race (#244)

- **OOB assets not showing**: `registerAssets` launched fire-and-forget
coroutines — file was loaded before assets finished registering. Now
`suspend` with `awaitAll()`.
- **updateReferencedAssets**: logs warning (same as iOS — not supported
in experimental backend).
- **NestedViewModel example**: fixed layout so Rive view doesn't get
squeezed to zero when log entries appear.

## Test plan
- Out-of-Band Assets: image, font, audio all load on Android
- NestedViewModel: Rive view stays visible after pressing Replace
Add coverage-ios package, configure RNRive + RiveRuntime pod
instrumentation, and document the integration process.
Remove stale `isExperimentalIOS` test skips and fix Android ViewModel
name resolution.

- **Test skips removed:** Color property (`argbValue` now public in
rive-ios 6.19.2), artboard/list/image databinding (crashes fixed in
experimental renderer)
- **Android fix:** Use `getDefaultViewModelInfo()` (available since
rive-android 11.3.2) to resolve ViewModel name for
`defaultArtboardViewModel()`, enabling `createInstanceByName` and other
name-dependent operations

All 105 harness tests pass on both iOS experimental and Android.
The rive-android API is no longer experimental in 11.4.1. Matches the
iOS rename done in #240.
Font file needed by the font fallback example. Referenced in
app.config.js via expo-font plugin.
Bump RiveRuntime from 6.19.2 to 6.20.0. Fixes Swift/ObjC interop issue
in the released version.
Adds coverage-ios dependency and portal resolutions pointing to the local
react-native-harness fork (feat/native-ios-coverage branch).
Adds getPropertiesAsync() to ViewModel and ViewModelInstance for runtime
introspection. Returns ViewModelPropertyInfo[] with name and type for each
property.
Port from #79. Adds RiveWorkletBridge
HybridObject that installs Nitro's dispatcher on Reanimated's UI runtime,
enabling ViewModel property listeners to drive SharedValues on the UI
thread without blocking when JS is busy.

Also bumps rive-ios to 6.20.2 and adds bouncing ball exerciser demo.
Suppress eslint no-deprecated warnings for runOnUI,
defaultArtboardViewModel, and createDefaultInstance.
These are properly fixed in the next stacked PR.
@mfazekas mfazekas force-pushed the feat/rive-ios-experimental branch from 80b8072 to f299fe1 Compare May 19, 2026 06:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant