Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 31 additions & 15 deletions kotlin/src/main/kotlin/app/rive/Rive.kt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ private const val GENERAL_TAG = "Rive/UI"
private const val STATE_MACHINE_TAG = "Rive/UI/SM"
private const val DRAW_TAG = "Rive/UI/Draw"

internal object RiveInitialDrawPolicy {
fun shouldDrawBeforeLifecycleLoop(
surfaceAvailable: Boolean,
firstDrawRequestedForSurface: Boolean,
): Boolean = surfaceAvailable && !firstDrawRequestedForSurface
}

/**
* Represents the result of an operation - typically loading - that can be in a loading, error,
* or success state. This includes Rive file loading. The Success result must be unwrapped to the
Expand Down Expand Up @@ -206,6 +213,7 @@ fun Rive(

val currentOnBitmapAvailable by rememberUpdatedState(onBitmapAvailable)
var bitmapCallbackSent by remember { mutableStateOf(false) }
var firstDrawRequested by remember(surface) { mutableStateOf(false) }

// In debug builds, output the reasons for recomposition
RebuggerWrapper(
Expand Down Expand Up @@ -295,6 +303,18 @@ fun Rive(
backgroundColor,
playing,
) {
fun drawFrame(drawSurface: RiveSurface, deltaTime: kotlin.time.Duration) {
firstDrawRequested = true
riveWorker.advanceStateMachine(stateMachineHandle, deltaTime)
riveWorker.draw(
artboardHandle,
stateMachineHandle,
drawSurface,
fit,
backgroundColor
)
}

if (surface == null) {
RiveLog.d(DRAW_TAG) { "Surface is null, skipping drawing" }
return@LaunchedEffect
Expand All @@ -311,16 +331,19 @@ fun Rive(
RiveLog.d(DRAW_TAG) { "Surface was released before draw, skipping frame" }
return@LaunchedEffect
}
riveWorker.draw(
artboardHandle,
stateMachineHandle,
drawSurface,
fit,
backgroundColor
)
drawFrame(drawSurface, 0.nanoseconds)

return@LaunchedEffect
}
val initialSurface = surface
if (RiveInitialDrawPolicy.shouldDrawBeforeLifecycleLoop(
surfaceAvailable = initialSurface != null,
firstDrawRequestedForSurface = firstDrawRequested,
)
) {
RiveLog.d(DRAW_TAG) { "Drawing initial visible frame before lifecycle animation loop" }
drawFrame(initialSurface!!, 0.nanoseconds)
}
lifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) {
RiveLog.d(DRAW_TAG) { "Starting drawing with $artboardHandle and $stateMachineHandle" }
var lastFrameTime = 0.nanoseconds
Expand All @@ -343,14 +366,7 @@ fun Rive(
return@repeatOnLifecycle
}

riveWorker.advanceStateMachine(stateMachineHandle, deltaTime)
riveWorker.draw(
artboardHandle,
stateMachineHandle,
drawSurface,
fit,
backgroundColor
)
drawFrame(drawSurface, deltaTime)
}
RiveLog.d(DRAW_TAG) { "Ending drawing with $artboardHandle and $stateMachineHandle" }
}
Expand Down
27 changes: 27 additions & 0 deletions kotlin/src/test/kotlin/app/rive/RiveInitialDrawPolicyUnitTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package app.rive

import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe

class RiveInitialDrawPolicyUnitTest : FunSpec({
test("a newly available surface needs one draw before the lifecycle animation loop starts") {
RiveInitialDrawPolicy.shouldDrawBeforeLifecycleLoop(
surfaceAvailable = true,
firstDrawRequestedForSurface = false,
) shouldBe true
}

test("a missing surface cannot draw before the lifecycle animation loop starts") {
RiveInitialDrawPolicy.shouldDrawBeforeLifecycleLoop(
surfaceAvailable = false,
firstDrawRequestedForSurface = false,
) shouldBe false
}

test("an already-drawn surface does not need another lifecycle-independent draw") {
RiveInitialDrawPolicy.shouldDrawBeforeLifecycleLoop(
surfaceAvailable = true,
firstDrawRequestedForSurface = true,
) shouldBe false
}
})