Skip to content

Compose in the Runtime Take 3 #824

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
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
8 changes: 8 additions & 0 deletions artifacts.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@
"packaging": "jar",
"javaVersion": "1.8"
},
{
"gradlePath": ":workflow-core-compose",
"group": "com.squareup.workflow1",
"artifactId": "workflow-core-compose",
"description": "Workflow Core Compose",
"packaging": "jar",
"javaVersion": "1.8"
},
{
"gradlePath": ":workflow-runtime",
"group": "com.squareup.workflow1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ plugins {
id("com.android.application")
`kotlin-android`
id("kotlin-parcelize")
id("app.cash.molecule")
}
android {
compileSdk = 32
Expand Down Expand Up @@ -51,6 +52,7 @@ dependencies {
// API on an app module so these are transitive dependencies for the benchmarks.
api(project(":samples:containers:android"))
api(project(":samples:containers:poetry"))
api(project(":workflow-core-compose"))
api(project(":workflow-ui:core-android"))

implementation(libs.androidx.activity.ktx)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.squareup.benchmarks.performance.complex.poetry.PerformancePoetryActivity.Companion.EXTRA_PERF_CONFIG_INITIALIZING
import com.squareup.benchmarks.performance.complex.poetry.PerformancePoetryActivity.Companion.EXTRA_PERF_CONFIG_REPEAT
import com.squareup.benchmarks.performance.complex.poetry.PerformancePoetryActivity.Companion.EXTRA_RUNTIME_COMPOSE_RUNTIME
import com.squareup.benchmarks.performance.complex.poetry.PerformancePoetryActivity.Companion.EXTRA_RUNTIME_FRAME_TIMEOUT
import com.squareup.benchmarks.performance.complex.poetry.cyborgs.landscapeOrientation
import com.squareup.benchmarks.performance.complex.poetry.cyborgs.openRavenAndNavigate
Expand All @@ -16,6 +17,7 @@ import com.squareup.benchmarks.performance.complex.poetry.cyborgs.waitForPoetry
import com.squareup.benchmarks.performance.complex.poetry.instrumentation.RenderPassCountingInterceptor
import org.junit.Assert.fail
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith

Expand All @@ -30,7 +32,8 @@ class RenderPassTest {
val useInitializingState: Boolean,
val useHighFrequencyRange: Boolean,
val baselineExpectation: RenderExpectation,
val frameTimeoutExpectation: RenderExpectation
val frameTimeoutExpectation: RenderExpectation,
val frameTimeoutComposeExpectation: RenderExpectation
)

data class RenderExpectation(
Expand All @@ -56,15 +59,15 @@ class RenderPassTest {
}

@Test fun renderPassCounterBaselineComplexWithInitializingState() {
runRenderPassCounter(COMPLEX_INITIALIZING, useFrameTimeout = false)
runRenderPassCounter(COMPLEX_INITIALIZING)
}

@Test fun renderPassCounterBaselineComplexNoInitializingState() {
runRenderPassCounter(COMPLEX_NO_INITIALIZING, useFrameTimeout = false)
runRenderPassCounter(COMPLEX_NO_INITIALIZING)
}

@Test fun renderPassCounterBaselineComplexNoInitializingStateHighFrequencyEvents() {
runRenderPassCounter(COMPLEX_NO_INITIALIZING_HIGH_FREQUENCY, useFrameTimeout = false)
runRenderPassCounter(COMPLEX_NO_INITIALIZING_HIGH_FREQUENCY)
}

@Test fun renderPassCounterFrameTimeoutComplexWithInitializingState() {
Expand All @@ -79,19 +82,43 @@ class RenderPassTest {
runRenderPassCounter(COMPLEX_NO_INITIALIZING_HIGH_FREQUENCY, useFrameTimeout = true)
}

@Ignore(
"Not sure why but this gets stuck on initializing. Compose doesn't get the next" +
" frame when this is started by the test, but it does when running directly. See #835"
)
@Test fun renderPassCounterFrameTimeoutComposeComplexWithInitializingState() {
runRenderPassCounter(COMPLEX_INITIALIZING, useFrameTimeout = true, useCompose = true)
}

@Test fun renderPassCounterFrameTimeoutComposeComplexNoInitializingState() {
runRenderPassCounter(COMPLEX_NO_INITIALIZING, useFrameTimeout = true, useCompose = true)
}

@Test fun renderPassCounterFrameTimeoutComposeComplexNoInitializingStateHighFrequencyEvents() {
runRenderPassCounter(
COMPLEX_NO_INITIALIZING_HIGH_FREQUENCY,
useFrameTimeout = true,
useCompose = true
)
}

private fun runRenderPassCounter(
scenario: Scenario,
useFrameTimeout: Boolean
useFrameTimeout: Boolean = false,
useCompose: Boolean = false
) {
if (useCompose) require(useFrameTimeout) { "Only use Compose Frame Timeout." }

val intent = Intent(context, PerformancePoetryActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
putExtra(
EXTRA_PERF_CONFIG_INITIALIZING,
scenario.useInitializingState
)
putExtra(EXTRA_RUNTIME_FRAME_TIMEOUT, useFrameTimeout)
if (useFrameTimeout) {
putExtra(EXTRA_RUNTIME_FRAME_TIMEOUT, useFrameTimeout)
putExtra(EXTRA_RUNTIME_COMPOSE_RUNTIME, useCompose)
}
if (scenario.useHighFrequencyRange) {
putExtra(EXTRA_PERF_CONFIG_REPEAT, PerformancePoetryActivity.HIGH_FREQUENCY_REPEAT_COUNT)
Expand All @@ -111,11 +138,19 @@ class RenderPassTest {
device.openRavenAndNavigate()

val expectation =
if (useFrameTimeout) scenario.frameTimeoutExpectation else
if (useFrameTimeout) {
if (useCompose) {
scenario.frameTimeoutComposeExpectation
} else {
scenario.frameTimeoutExpectation
}
} else {
scenario.baselineExpectation
}

val title = if (useFrameTimeout) {
"Runtime: FrameTimeout; "
val usingCompose = if (useCompose) "(Using Compose Optimizations)" else "(No Compose)"
"Runtime: FrameTimeout $usingCompose;"
} else {
"Runtime: RenderPerAction; "
} + scenario.title
Expand Down Expand Up @@ -227,6 +262,11 @@ class RenderPassTest {
totalPasses = 41..42,
freshRenderedNodes = 85..85,
staleRenderedNodes = 436..436
),
frameTimeoutComposeExpectation = RenderExpectation(
totalPasses = 41..42,
freshRenderedNodes = 85..85,
staleRenderedNodes = 436..436
)
)

Expand All @@ -243,6 +283,11 @@ class RenderPassTest {
totalPasses = 40..41,
freshRenderedNodes = 83..83,
staleRenderedNodes = 431..431
),
frameTimeoutComposeExpectation = RenderExpectation(
totalPasses = 40..41,
freshRenderedNodes = 113..113,
staleRenderedNodes = 82..82
)
)

Expand Down Expand Up @@ -270,9 +315,14 @@ class RenderPassTest {
staleRenderedNodes = 2350..2350
),
frameTimeoutExpectation = RenderExpectation(
totalPasses = 88..97,
totalPasses = 60..64,
freshRenderedNodes = 106..108,
staleRenderedNodes = 679..698
),
frameTimeoutComposeExpectation = RenderExpectation(
totalPasses = 59..64,
freshRenderedNodes = 259..259,
staleRenderedNodes = 207..207
)
)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.squareup.benchmarks.performance.complex.poetry

import androidx.compose.runtime.Composable
import com.squareup.benchmarks.performance.complex.poetry.instrumentation.ActionHandlingTracingInterceptor
import com.squareup.benchmarks.performance.complex.poetry.instrumentation.asTraceableWorker
import com.squareup.benchmarks.performance.complex.poetry.views.LoaderSpinner
Expand All @@ -8,19 +9,21 @@ import com.squareup.sample.container.overviewdetail.OverviewDetailScreen
import com.squareup.workflow1.Snapshot
import com.squareup.workflow1.StatefulWorkflow
import com.squareup.workflow1.Workflow
import com.squareup.workflow1.WorkflowExperimentalRuntime
import com.squareup.workflow1.action
import com.squareup.workflow1.compose.StatefulComposeWorkflow
import com.squareup.workflow1.runningWorker
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
import kotlinx.coroutines.flow.Flow

typealias IsLoading = Boolean

@OptIn(WorkflowUiExperimentalApi::class)
@OptIn(WorkflowUiExperimentalApi::class, WorkflowExperimentalRuntime::class)
class MaybeLoadingGatekeeperWorkflow<T : Any>(
private val childWithLoading: Workflow<T, Any, OverviewDetailScreen>,
private val childProps: T,
private val isLoading: Flow<Boolean>
) : StatefulWorkflow<Unit, IsLoading, Unit, MayBeLoadingScreen>() {
) : StatefulComposeWorkflow<Unit, IsLoading, Unit, MayBeLoadingScreen>() {
override fun initialState(
props: Unit,
snapshot: Snapshot?
Expand All @@ -29,7 +32,7 @@ class MaybeLoadingGatekeeperWorkflow<T : Any>(
override fun render(
renderProps: Unit,
renderState: IsLoading,
context: RenderContext
context: StatefulWorkflow<Unit, IsLoading, Unit, MayBeLoadingScreen>.RenderContext
): MayBeLoadingScreen {
context.runningWorker(isLoading.asTraceableWorker("GatekeeperLoading")) {
action {
Expand All @@ -48,5 +51,31 @@ class MaybeLoadingGatekeeperWorkflow<T : Any>(
)
}

@Composable
override fun Rendering(
renderProps: Unit,
renderState: IsLoading,
context: RenderContext,
): MayBeLoadingScreen {
context.runningWorker(isLoading.asTraceableWorker("GatekeeperLoading")) {
action {
state = it
}
}
val maybeLoadingChild = context.ChildRendering(
childWithLoading, childProps, "",
) {
action(ActionHandlingTracingInterceptor.keyForTrace("GatekeeperChildFinished")) {
setOutput(
Unit
)
}
}
return MayBeLoadingScreen(
baseScreen = maybeLoadingChild,
loaders = if (renderState) listOf(LoaderSpinner) else emptyList()
)
}

override fun snapshotState(state: IsLoading): Snapshot? = null
}
Loading