diff --git a/.rive_head b/.rive_head index 6b9cbc3e..b3743d2a 100644 --- a/.rive_head +++ b/.rive_head @@ -1 +1 @@ -0f805654cfd333d978a98a4375a6d99d0d5c8107 +c65e93fee25dd3beea59ff09c5be48a0061c6605 diff --git a/kotlin/src/androidTest/java/app/rive/runtime/kotlin/core/RiveListenerTest.kt b/kotlin/src/androidTest/java/app/rive/runtime/kotlin/core/RiveListenerTest.kt index 91db3297..50985278 100644 --- a/kotlin/src/androidTest/java/app/rive/runtime/kotlin/core/RiveListenerTest.kt +++ b/kotlin/src/androidTest/java/app/rive/runtime/kotlin/core/RiveListenerTest.kt @@ -287,6 +287,7 @@ class RiveListenerTest { // animation came to an end inside this time period, this still means no state change renderer.advance(1.0f) + renderer.advance(0.01f) assertEquals(false, view.isPlaying) assertEquals(0, observer.states.size) diff --git a/kotlin/src/androidTest/java/app/rive/runtime/kotlin/core/RiveViewStateMachineTest.kt b/kotlin/src/androidTest/java/app/rive/runtime/kotlin/core/RiveViewStateMachineTest.kt index b41ee76a..4cf1a381 100644 --- a/kotlin/src/androidTest/java/app/rive/runtime/kotlin/core/RiveViewStateMachineTest.kt +++ b/kotlin/src/androidTest/java/app/rive/runtime/kotlin/core/RiveViewStateMachineTest.kt @@ -89,6 +89,7 @@ class RiveViewStateMachineTest { assert(mockView.artboardRenderer != null) // Let the state machine animation run its course. mockView.artboardRenderer!!.advance(1.01f) + mockView.artboardRenderer!!.advance(0.0f) assert(!mockView.isPlaying) } } @@ -113,6 +114,27 @@ class RiveViewStateMachineTest { } } + @Test + fun nestedStateMachinesContinuePlaying() { + UiThreadStatement.runOnUiThread { + // The main state machine's controller is not advancing, however, nested artboards + // need to continue playing + val mView = mockView as TestUtils.MockRiveAnimationView + val controller = mView.controller + mView.setRiveResource(R.raw.nested_settle) + mView.play("State Machine 1", isStateMachine = true) + assertEquals(1, controller.stateMachines.size) + + mView.artboardRenderer?.advance(0.5f); + assert(mView.isPlaying) + mView.artboardRenderer?.advance(0.5f); + assert(mView.isPlaying) + // The nested artboard should now be settled + mView.artboardRenderer?.advance(0.01f); + assert(!mView.isPlaying) // Playing is false + } + } + @Test @Ignore("We're not stopping state machines when all layers are stopped atm.") fun viewStateMachinesInstancesRemoveOnStop() { diff --git a/kotlin/src/androidTest/res/raw/nested_settle.riv b/kotlin/src/androidTest/res/raw/nested_settle.riv new file mode 100644 index 00000000..6e8cbb87 Binary files /dev/null and b/kotlin/src/androidTest/res/raw/nested_settle.riv differ diff --git a/kotlin/src/main/cpp/src/bindings/bindings_file.cpp b/kotlin/src/main/cpp/src/bindings/bindings_file.cpp index cba02339..931a7789 100644 --- a/kotlin/src/main/cpp/src/bindings/bindings_file.cpp +++ b/kotlin/src/main/cpp/src/bindings/bindings_file.cpp @@ -39,7 +39,12 @@ extern "C" { auto file = reinterpret_cast(ref); // Creates a new Artboard instance. - return (jlong)file->artboardNamed(JStringToString(env, name)).release(); + auto artboard = file->artboardNamed(JStringToString(env, name)); + if (artboard != nullptr) + { + artboard->advance(0.0); + } + return (jlong)artboard.release(); } JNIEXPORT void JNICALL @@ -70,7 +75,12 @@ extern "C" { auto file = reinterpret_cast(ref); // Creates a new Artboard instance. - return (jlong)file->artboardAt(index).release(); + auto artboard = file->artboardAt(index); + if (artboard != nullptr) + { + artboard->advance(0.0); + } + return (jlong)artboard.release(); } JNIEXPORT diff --git a/kotlin/src/main/java/app/rive/runtime/kotlin/controllers/RiveFileController.kt b/kotlin/src/main/java/app/rive/runtime/kotlin/controllers/RiveFileController.kt index 185d3f9e..371f895e 100644 --- a/kotlin/src/main/java/app/rive/runtime/kotlin/controllers/RiveFileController.kt +++ b/kotlin/src/main/java/app/rive/runtime/kotlin/controllers/RiveFileController.kt @@ -279,17 +279,26 @@ class RiveFileController( } } + val stateMachinesToPause = mutableListOf() stateMachines.forEach { stateMachineInstance -> if (playingStateMachines.contains(stateMachineInstance)) { val stillPlaying = resolveStateMachineAdvance(stateMachineInstance, elapsed) if (!stillPlaying) { - pause(stateMachine = stateMachineInstance) + stateMachinesToPause.add(stateMachineInstance) } } } - ab.advance(elapsed) + + // Only pause the state machines once the artboard has also settled, as nested + // artboards may still be advancing. This is to tie into the current logic of + // pausing only when current animations/machines are empty. In the future + // this can be simplified. + // Resolves: https://github.com/rive-app/rive-android/issues/338 + if (!ab.advance(elapsed)) { + stateMachinesToPause.forEach { pause(stateMachine = it) } + } notifyAdvance(elapsed) } }