Skip to content

Commit a72a969

Browse files
POC: Add in Compose Runtime Adaptation
1 parent 8c524db commit a72a969

File tree

63 files changed

+2878
-456
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+2878
-456
lines changed

artifacts.json

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,42 @@
6262
"javaVersion": "1.8",
6363
"publicationName": "kotlinMultiplatform"
6464
},
65+
{
66+
"gradlePath": ":workflow-core-compose",
67+
"group": "com.squareup.workflow1",
68+
"artifactId": "workflow-core-compose-iosarm64",
69+
"description": "Workflow Core Compose",
70+
"packaging": "klib",
71+
"javaVersion": "1.8",
72+
"publicationName": "iosArm64"
73+
},
74+
{
75+
"gradlePath": ":workflow-core-compose",
76+
"group": "com.squareup.workflow1",
77+
"artifactId": "workflow-core-compose-iosx64",
78+
"description": "Workflow Core Compose",
79+
"packaging": "klib",
80+
"javaVersion": "1.8",
81+
"publicationName": "iosX64"
82+
},
83+
{
84+
"gradlePath": ":workflow-core-compose",
85+
"group": "com.squareup.workflow1",
86+
"artifactId": "workflow-core-compose-jvm",
87+
"description": "Workflow Core Compose",
88+
"packaging": "jar",
89+
"javaVersion": "1.8",
90+
"publicationName": "jvm"
91+
},
92+
{
93+
"gradlePath": ":workflow-core-compose",
94+
"group": "com.squareup.workflow1",
95+
"artifactId": "workflow-core-compose",
96+
"description": "Workflow Core Compose",
97+
"packaging": "jar",
98+
"javaVersion": "1.8",
99+
"publicationName": "kotlinMultiplatform"
100+
},
65101
{
66102
"gradlePath": ":workflow-runtime",
67103
"group": "com.squareup.workflow1",
@@ -215,4 +251,4 @@
215251
"javaVersion": "1.8",
216252
"publicationName": "maven"
217253
}
218-
]
254+
]

benchmarks/performance-poetry/complex-poetry/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ plugins {
22
id("com.android.application")
33
`kotlin-android`
44
id("kotlin-parcelize")
5+
id("app.cash.molecule")
56
}
67
android {
78
compileSdk = 32
@@ -56,6 +57,7 @@ dependencies {
5657
api(project(":samples:containers:android"))
5758
api(project(":samples:containers:common"))
5859
api(project(":samples:containers:poetry"))
60+
api(project(":workflow-core-compose"))
5961
api(project(":workflow-core"))
6062
api(project(":workflow-runtime"))
6163
api(project(":workflow-ui:core-android"))

benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/MaybeLoadingGatekeeperWorkflow.kt

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.squareup.benchmarks.performance.complex.poetry
22

3+
import androidx.compose.runtime.Composable
34
import com.squareup.benchmarks.performance.complex.poetry.instrumentation.ActionHandlingTracingInterceptor
45
import com.squareup.benchmarks.performance.complex.poetry.instrumentation.asTraceableWorker
56
import com.squareup.benchmarks.performance.complex.poetry.views.LoaderSpinner
@@ -8,19 +9,21 @@ import com.squareup.sample.container.overviewdetail.OverviewDetailScreen
89
import com.squareup.workflow1.Snapshot
910
import com.squareup.workflow1.StatefulWorkflow
1011
import com.squareup.workflow1.Workflow
12+
import com.squareup.workflow1.WorkflowExperimentalRuntime
1113
import com.squareup.workflow1.action
14+
import com.squareup.workflow1.compose.StatefulComposeWorkflow
1215
import com.squareup.workflow1.runningWorker
1316
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
1417
import kotlinx.coroutines.flow.Flow
1518

1619
typealias IsLoading = Boolean
1720

18-
@OptIn(WorkflowUiExperimentalApi::class)
21+
@OptIn(WorkflowUiExperimentalApi::class, WorkflowExperimentalRuntime::class)
1922
class MaybeLoadingGatekeeperWorkflow<T : Any>(
2023
private val childWithLoading: Workflow<T, Any, OverviewDetailScreen>,
2124
private val childProps: T,
2225
private val isLoading: Flow<Boolean>
23-
) : StatefulWorkflow<Unit, IsLoading, Unit, MayBeLoadingScreen>() {
26+
) : StatefulComposeWorkflow<Unit, IsLoading, Unit, MayBeLoadingScreen>() {
2427
override fun initialState(
2528
props: Unit,
2629
snapshot: Snapshot?
@@ -29,7 +32,7 @@ class MaybeLoadingGatekeeperWorkflow<T : Any>(
2932
override fun render(
3033
renderProps: Unit,
3134
renderState: IsLoading,
32-
context: RenderContext
35+
context: StatefulWorkflow<Unit, IsLoading, Unit, MayBeLoadingScreen>.RenderContext
3336
): MayBeLoadingScreen {
3437
context.runningWorker(isLoading.asTraceableWorker("GatekeeperLoading")) {
3538
action {
@@ -49,4 +52,29 @@ class MaybeLoadingGatekeeperWorkflow<T : Any>(
4952
}
5053

5154
override fun snapshotState(state: IsLoading): Snapshot? = null
55+
@Composable
56+
override fun Rendering(
57+
renderProps: Unit,
58+
renderState: IsLoading,
59+
context: RenderContext
60+
): MayBeLoadingScreen {
61+
context.runningWorker(isLoading.asTraceableWorker("GatekeeperLoading")) {
62+
action {
63+
state = it
64+
}
65+
}
66+
val maybeLoadingChild = context.ChildRendering(
67+
childWithLoading, childProps, "",
68+
) {
69+
action(ActionHandlingTracingInterceptor.keyForTrace("GatekeeperChildFinished")) {
70+
setOutput(
71+
Unit
72+
)
73+
}
74+
}
75+
return MayBeLoadingScreen(
76+
baseScreen = maybeLoadingChild,
77+
loaders = if (renderState) listOf(LoaderSpinner) else emptyList()
78+
)
79+
}
5280
}

benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/PerformancePoemWorkflow.kt

Lines changed: 128 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.squareup.benchmarks.performance.complex.poetry
22

3+
import androidx.compose.runtime.Composable
34
import com.squareup.benchmarks.performance.complex.poetry.PerformancePoemWorkflow.Action.ClearSelection
45
import com.squareup.benchmarks.performance.complex.poetry.PerformancePoemWorkflow.Action.HandleStanzaListOutput
56
import com.squareup.benchmarks.performance.complex.poetry.PerformancePoemWorkflow.Action.SelectNext
@@ -30,7 +31,9 @@ import com.squareup.workflow1.StatefulWorkflow
3031
import com.squareup.workflow1.Worker
3132
import com.squareup.workflow1.WorkflowAction
3233
import com.squareup.workflow1.WorkflowAction.Companion.noAction
34+
import com.squareup.workflow1.WorkflowExperimentalRuntime
3335
import com.squareup.workflow1.action
36+
import com.squareup.workflow1.compose.StatefulComposeWorkflow
3437
import com.squareup.workflow1.runningWorker
3538
import com.squareup.workflow1.ui.Screen
3639
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
@@ -57,10 +60,11 @@ import kotlinx.coroutines.flow.flow
5760
* break ties/conflicts with a token in the start/stop requests. We leave that complexity out
5861
* here. **
5962
*/
63+
@OptIn(WorkflowExperimentalRuntime::class)
6064
class PerformancePoemWorkflow(
6165
private val simulatedPerfConfig: SimulatedPerfConfig = SimulatedPerfConfig.NO_SIMULATED_PERF,
6266
private val isLoading: MutableStateFlow<Boolean>,
63-
) : PoemWorkflow, StatefulWorkflow<Poem, State, ClosePoem, OverviewDetailScreen>() {
67+
) : PoemWorkflow, StatefulComposeWorkflow<Poem, State, ClosePoem, OverviewDetailScreen>() {
6468

6569
sealed class State {
6670
val isLoading: Boolean = false
@@ -94,7 +98,7 @@ class PerformancePoemWorkflow(
9498
override fun render(
9599
renderProps: Poem,
96100
renderState: State,
97-
context: RenderContext
101+
context: StatefulWorkflow<Poem, State, ClosePoem, OverviewDetailScreen>.RenderContext
98102
): OverviewDetailScreen {
99103
if (simulatedPerfConfig.simultaneousActions > 0) {
100104
repeat(simulatedPerfConfig.simultaneousActions) { index ->
@@ -315,4 +319,126 @@ class PerformancePoemWorkflow(
315319
}
316320
}
317321
}
322+
323+
@OptIn(WorkflowUiExperimentalApi::class)
324+
@Composable
325+
override fun Rendering(
326+
renderProps: Poem,
327+
renderState: State,
328+
context: RenderContext
329+
): OverviewDetailScreen {
330+
when (renderState) {
331+
Initializing -> {
332+
// Again, the entire `Initializing` state is a smell, which is most obvious from the
333+
// use of `Worker.from { Unit }`. A Worker doing no work and only shuttling the state
334+
// along is usually the sign you have an extraneous state that can be collapsed!
335+
// Don't try this at home.
336+
context.runningWorker(
337+
Worker.from {
338+
isLoading.value = true
339+
},
340+
"initializing"
341+
) {
342+
action {
343+
isLoading.value = false
344+
state = Selected(NO_SELECTED_STANZA)
345+
}
346+
}
347+
return OverviewDetailScreen(overviewRendering = BackStackScreen(BlankScreen))
348+
}
349+
else -> {
350+
val (stanzaIndex, currentStateIsLoading, repeat) = when (renderState) {
351+
is ComplexCall -> Triple(renderState.payload, true, renderState.repeater)
352+
is Selected -> Triple(renderState.stanzaIndex, false, 0)
353+
Initializing -> throw IllegalStateException("No longer initializing.")
354+
}
355+
356+
if (currentStateIsLoading) {
357+
if (repeat > 0) {
358+
// Running a flow that emits 'repeat' number of times
359+
context.runningWorker(
360+
flow {
361+
while (true) {
362+
// As long as this Worker is running we want to be emitting values.
363+
delay(2)
364+
emit(repeat)
365+
}
366+
}.asTraceableWorker("EventRepetition")
367+
) {
368+
action {
369+
(state as? ComplexCall)?.let { currentState ->
370+
// Still repeating the complex call
371+
state = ComplexCall(
372+
payload = currentState.payload,
373+
repeater = (currentState.repeater - 1).coerceAtLeast(0)
374+
)
375+
}
376+
}
377+
}
378+
} else {
379+
context.runningWorker(
380+
worker = TraceableWorker.from("PoemLoading") {
381+
isLoading.value = true
382+
delay(simulatedPerfConfig.complexityDelay)
383+
// No Output for Worker is necessary because the selected index
384+
// is already in the state.
385+
}
386+
) {
387+
action {
388+
isLoading.value = false
389+
(state as? ComplexCall)?.let { currentState ->
390+
state = Selected(currentState.payload)
391+
}
392+
}
393+
}
394+
}
395+
}
396+
397+
val stanzaListOverview = context.ChildRendering(
398+
StanzaListWorkflow,
399+
StanzaListWorkflow.Props(
400+
poem = renderProps,
401+
eventHandlerTag = ActionHandlingTracingInterceptor::keyForTrace
402+
),
403+
key = "",
404+
) { selected ->
405+
HandleStanzaListOutput(simulatedPerfConfig, selected)
406+
}
407+
.copy(selection = stanzaIndex)
408+
409+
if (stanzaIndex != NO_SELECTED_STANZA) {
410+
val stackedStanzas = renderProps.stanzas.subList(0, stanzaIndex + 1)
411+
.mapIndexed { index, _ ->
412+
context.ChildRendering(
413+
StanzaWorkflow,
414+
Props(
415+
poem = renderProps,
416+
index = index,
417+
eventHandlerTag = ActionHandlingTracingInterceptor::keyForTrace
418+
),
419+
key = "$index",
420+
) {
421+
when (it) {
422+
CloseStanzas -> ClearSelection(simulatedPerfConfig)
423+
ShowPreviousStanza -> SelectPrevious(simulatedPerfConfig)
424+
ShowNextStanza -> SelectNext(simulatedPerfConfig)
425+
}
426+
}
427+
}.toBackStackScreen<Screen>()
428+
429+
return OverviewDetailScreen(
430+
overviewRendering = BackStackScreen(stanzaListOverview),
431+
detailRendering = stackedStanzas
432+
)
433+
}
434+
435+
return OverviewDetailScreen(
436+
overviewRendering = BackStackScreen(stanzaListOverview),
437+
selectDefault = {
438+
context.actionSink.send(HandleStanzaListOutput(simulatedPerfConfig, 0))
439+
}
440+
)
441+
}
442+
}
443+
}
318444
}

0 commit comments

Comments
 (0)