Skip to content

Commit

Permalink
POC: Add in Compose Runtime Adaptation
Browse files Browse the repository at this point in the history
  • Loading branch information
steve-the-edwards committed Oct 31, 2022
1 parent 8c524db commit 75150a9
Show file tree
Hide file tree
Showing 63 changed files with 2,894 additions and 456 deletions.
38 changes: 37 additions & 1 deletion artifacts.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,42 @@
"javaVersion": "1.8",
"publicationName": "kotlinMultiplatform"
},
{
"gradlePath": ":workflow-core-compose",
"group": "com.squareup.workflow1",
"artifactId": "workflow-core-compose-iosarm64",
"description": "Workflow Core Compose",
"packaging": "klib",
"javaVersion": "1.8",
"publicationName": "iosArm64"
},
{
"gradlePath": ":workflow-core-compose",
"group": "com.squareup.workflow1",
"artifactId": "workflow-core-compose-iosx64",
"description": "Workflow Core Compose",
"packaging": "klib",
"javaVersion": "1.8",
"publicationName": "iosX64"
},
{
"gradlePath": ":workflow-core-compose",
"group": "com.squareup.workflow1",
"artifactId": "workflow-core-compose-jvm",
"description": "Workflow Core Compose",
"packaging": "jar",
"javaVersion": "1.8",
"publicationName": "jvm"
},
{
"gradlePath": ":workflow-core-compose",
"group": "com.squareup.workflow1",
"artifactId": "workflow-core-compose",
"description": "Workflow Core Compose",
"packaging": "jar",
"javaVersion": "1.8",
"publicationName": "kotlinMultiplatform"
},
{
"gradlePath": ":workflow-runtime",
"group": "com.squareup.workflow1",
Expand Down Expand Up @@ -215,4 +251,4 @@
"javaVersion": "1.8",
"publicationName": "maven"
}
]
]
2 changes: 2 additions & 0 deletions benchmarks/performance-poetry/complex-poetry/build.gradle.kts
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 @@ -56,6 +57,7 @@ dependencies {
api(project(":samples:containers:android"))
api(project(":samples:containers:common"))
api(project(":samples:containers:poetry"))
api(project(":workflow-core-compose"))
api(project(":workflow-core"))
api(project(":workflow-runtime"))
api(project(":workflow-ui:core-android"))
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 @@ -49,4 +52,29 @@ class MaybeLoadingGatekeeperWorkflow<T : Any>(
}

override fun snapshotState(state: IsLoading): Snapshot? = null
@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()
)
}
}
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.PerformancePoemWorkflow.Action.ClearSelection
import com.squareup.benchmarks.performance.complex.poetry.PerformancePoemWorkflow.Action.HandleStanzaListOutput
import com.squareup.benchmarks.performance.complex.poetry.PerformancePoemWorkflow.Action.SelectNext
Expand Down Expand Up @@ -30,7 +31,9 @@ import com.squareup.workflow1.StatefulWorkflow
import com.squareup.workflow1.Worker
import com.squareup.workflow1.WorkflowAction
import com.squareup.workflow1.WorkflowAction.Companion.noAction
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.Screen
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
Expand All @@ -57,10 +60,11 @@ import kotlinx.coroutines.flow.flow
* break ties/conflicts with a token in the start/stop requests. We leave that complexity out
* here. **
*/
@OptIn(WorkflowExperimentalRuntime::class)
class PerformancePoemWorkflow(
private val simulatedPerfConfig: SimulatedPerfConfig = SimulatedPerfConfig.NO_SIMULATED_PERF,
private val isLoading: MutableStateFlow<Boolean>,
) : PoemWorkflow, StatefulWorkflow<Poem, State, ClosePoem, OverviewDetailScreen>() {
) : PoemWorkflow, StatefulComposeWorkflow<Poem, State, ClosePoem, OverviewDetailScreen>() {

sealed class State {
val isLoading: Boolean = false
Expand Down Expand Up @@ -94,7 +98,7 @@ class PerformancePoemWorkflow(
override fun render(
renderProps: Poem,
renderState: State,
context: RenderContext
context: StatefulWorkflow<Poem, State, ClosePoem, OverviewDetailScreen>.RenderContext
): OverviewDetailScreen {
if (simulatedPerfConfig.simultaneousActions > 0) {
repeat(simulatedPerfConfig.simultaneousActions) { index ->
Expand Down Expand Up @@ -315,4 +319,126 @@ class PerformancePoemWorkflow(
}
}
}

@OptIn(WorkflowUiExperimentalApi::class)
@Composable
override fun Rendering(
renderProps: Poem,
renderState: State,
context: RenderContext
): OverviewDetailScreen {
when (renderState) {
Initializing -> {
// Again, the entire `Initializing` state is a smell, which is most obvious from the
// use of `Worker.from { Unit }`. A Worker doing no work and only shuttling the state
// along is usually the sign you have an extraneous state that can be collapsed!
// Don't try this at home.
context.runningWorker(
Worker.from {
isLoading.value = true
},
"initializing"
) {
action {
isLoading.value = false
state = Selected(NO_SELECTED_STANZA)
}
}
return OverviewDetailScreen(overviewRendering = BackStackScreen(BlankScreen))
}
else -> {
val (stanzaIndex, currentStateIsLoading, repeat) = when (renderState) {
is ComplexCall -> Triple(renderState.payload, true, renderState.repeater)
is Selected -> Triple(renderState.stanzaIndex, false, 0)
Initializing -> throw IllegalStateException("No longer initializing.")
}

if (currentStateIsLoading) {
if (repeat > 0) {
// Running a flow that emits 'repeat' number of times
context.runningWorker(
flow {
while (true) {
// As long as this Worker is running we want to be emitting values.
delay(2)
emit(repeat)
}
}.asTraceableWorker("EventRepetition")
) {
action {
(state as? ComplexCall)?.let { currentState ->
// Still repeating the complex call
state = ComplexCall(
payload = currentState.payload,
repeater = (currentState.repeater - 1).coerceAtLeast(0)
)
}
}
}
} else {
context.runningWorker(
worker = TraceableWorker.from("PoemLoading") {
isLoading.value = true
delay(simulatedPerfConfig.complexityDelay)
// No Output for Worker is necessary because the selected index
// is already in the state.
}
) {
action {
isLoading.value = false
(state as? ComplexCall)?.let { currentState ->
state = Selected(currentState.payload)
}
}
}
}
}

val stanzaListOverview = context.ChildRendering(
StanzaListWorkflow,
StanzaListWorkflow.Props(
poem = renderProps,
eventHandlerTag = ActionHandlingTracingInterceptor::keyForTrace
),
key = "",
) { selected ->
HandleStanzaListOutput(simulatedPerfConfig, selected)
}
.copy(selection = stanzaIndex)

if (stanzaIndex != NO_SELECTED_STANZA) {
val stackedStanzas = renderProps.stanzas.subList(0, stanzaIndex + 1)
.mapIndexed { index, _ ->
context.ChildRendering(
StanzaWorkflow,
Props(
poem = renderProps,
index = index,
eventHandlerTag = ActionHandlingTracingInterceptor::keyForTrace
),
key = "$index",
) {
when (it) {
CloseStanzas -> ClearSelection(simulatedPerfConfig)
ShowPreviousStanza -> SelectPrevious(simulatedPerfConfig)
ShowNextStanza -> SelectNext(simulatedPerfConfig)
}
}
}.toBackStackScreen<Screen>()

return OverviewDetailScreen(
overviewRendering = BackStackScreen(stanzaListOverview),
detailRendering = stackedStanzas
)
}

return OverviewDetailScreen(
overviewRendering = BackStackScreen(stanzaListOverview),
selectDefault = {
context.actionSink.send(HandleStanzaListOutput(simulatedPerfConfig, 0))
}
)
}
}
}
}
Loading

0 comments on commit 75150a9

Please sign in to comment.