Skip to content

Commit 8e82ea0

Browse files
committed
fix(ci+android): restore the full harness suite; non-blocking legacy VMI getter
- expo57-example/jest.config.js scoped its roots to ../example/src, which silently dropped ~20 on-device suites (all of example/__tests__, including this PR's e2e guards) from every harness job. Widen the root to ../example so the full shared suite runs against the Expo app, per the config's own stated intent. - Replace the legacy Android getViewModelInstance main-hop (runBlocking(Dispatchers.Main.immediate), added earlier on this branch) with a main-thread-maintained @volatile snapshot: off-main callers get the last snapshot and schedule a refresh — eventually consistent, which suits the polling consumers (the async hook's ref path, harness waitFor loops). runBlocking JS→main can deadlock if the main thread ever waits on JS under the legacy bridge, and is the prime suspect in the android-legacy harness job timing out on all three attempts; the hazard is real regardless of that verdict.
1 parent 8859f66 commit 8e82ea0

3 files changed

Lines changed: 30 additions & 12 deletions

File tree

android/src/legacy/java/com/margelo/nitro/rive/HybridRiveView.kt

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import app.rive.runtime.kotlin.core.Alignment as RiveAlignment
1212
import app.rive.runtime.kotlin.core.errors.*
1313
import android.util.Log
1414
import kotlinx.coroutines.Dispatchers
15-
import kotlinx.coroutines.runBlocking
1615
import kotlinx.coroutines.withContext
1716

1817
fun Variant_HybridViewModelInstanceSpec_DataBindMode_DataBindByName?.toBindData(): BindData {
@@ -116,13 +115,9 @@ class HybridRiveView(val context: ThemedReactContext) : HybridRiveViewSpec() {
116115
}
117116

118117
override fun getViewModelInstance(): HybridViewModelInstanceSpec? {
119-
// Read on the main thread: the controller's state-machine list is mutated
120-
// there during startup and the legacy runtime has no internal
121-
// synchronization (issue #297 race class). Mirrors iOS's MainThread.run;
122-
// `immediate` avoids a deadlock when already called on main.
123-
val viewModelInstance =
124-
runBlocking(Dispatchers.Main.immediate) { view.getViewModelInstance() }
125-
?: return null
118+
// Thread-safety lives in RiveReactNativeView.getViewModelInstance(): it
119+
// returns a main-thread-maintained snapshot without blocking this thread.
120+
val viewModelInstance = view.getViewModelInstance() ?: return null
126121
return HybridViewModelInstance(viewModelInstance)
127122
}
128123

android/src/legacy/java/com/rive/RiveReactNativeView.kt

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,17 @@ class RiveReactNativeView(context: ThemedReactContext) : FrameLayout(context) {
167167
}
168168
}
169169

170-
fun getViewModelInstance(): ViewModelInstance? {
170+
// Cache maintained on the main thread: the controller's state-machine list
171+
// is mutated there and the legacy runtime has no internal synchronization
172+
// (issue #297 race class), so other threads must not traverse it. Off-main
173+
// reads return the last main-thread snapshot and schedule a refresh —
174+
// eventually consistent, which suits the polling callers (the async hook's
175+
// ref path, harness waitFor loops). Blocking on the main thread instead
176+
// would risk a JS↔main deadlock under the legacy bridge.
177+
@Volatile
178+
private var lastKnownViewModelInstance: ViewModelInstance? = null
179+
180+
private fun readViewModelInstanceOnMain(): ViewModelInstance? {
171181
val stateMachines = riveAnimationView?.controller?.stateMachines
172182
return if (!stateMachines.isNullOrEmpty()) {
173183
stateMachines.first().viewModelInstance
@@ -176,6 +186,17 @@ class RiveReactNativeView(context: ThemedReactContext) : FrameLayout(context) {
176186
}
177187
}
178188

189+
fun getViewModelInstance(): ViewModelInstance? {
190+
if (android.os.Looper.myLooper() == android.os.Looper.getMainLooper()) {
191+
lastKnownViewModelInstance = readViewModelInstanceOnMain()
192+
return lastKnownViewModelInstance
193+
}
194+
android.os.Handler(android.os.Looper.getMainLooper()).post {
195+
lastKnownViewModelInstance = readViewModelInstanceOnMain()
196+
}
197+
return lastKnownViewModelInstance
198+
}
199+
179200
fun applyDataBinding(bindData: BindData) {
180201
bindToStateMachine(bindData)
181202
}

expo57-example/jest.config.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
module.exports = {
22
preset: 'react-native-harness',
3-
// Run the bare example's harness suite against this Expo app; the shared
4-
// metro config redirects those files' imports into this workspace.
5-
roots: ['<rootDir>', '<rootDir>/../example/src'],
3+
// Run the bare example's FULL harness suite against this Expo app (both
4+
// example/__tests__ and example/src/__tests__); the shared metro config
5+
// redirects those files' imports and assets into this workspace. Scoping
6+
// this to example/src silently dropped ~20 on-device suites from CI.
7+
roots: ['<rootDir>', '<rootDir>/../example'],
68
};

0 commit comments

Comments
 (0)