Skip to content

Commit 28155a8

Browse files
Compose Runtime Integration Take 2
1 parent 39db0a7 commit 28155a8

File tree

38 files changed

+1009
-73
lines changed

38 files changed

+1009
-73
lines changed

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

Lines changed: 1 addition & 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

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

Lines changed: 31 additions & 0 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
@@ -48,5 +49,35 @@ class MaybeLoadingGatekeeperWorkflow<T : Any>(
4849
)
4950
}
5051

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

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

Lines changed: 148 additions & 0 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
@@ -57,6 +58,7 @@ import kotlinx.coroutines.flow.flow
5758
* break ties/conflicts with a token in the start/stop requests. We leave that complexity out
5859
* here. **
5960
*/
61+
@OptIn(WorkflowUiExperimentalApi::class)
6062
class PerformancePoemWorkflow(
6163
private val simulatedPerfConfig: SimulatedPerfConfig = SimulatedPerfConfig.NO_SIMULATED_PERF,
6264
private val isLoading: MutableStateFlow<Boolean>,
@@ -234,6 +236,152 @@ class PerformancePoemWorkflow(
234236
}
235237
}
236238

239+
@Composable
240+
override fun renderComposed(
241+
renderProps: Poem,
242+
renderState: State,
243+
context: RenderContext
244+
): OverviewDetailScreen {
245+
return when (renderState) {
246+
Initializing -> {
247+
// Again, the entire `Initializing` state is a smell, which is most obvious from the
248+
// use of `Worker.from { Unit }`. A Worker doing no work and only shuttling the state
249+
// along is usually the sign you have an extraneous state that can be collapsed!
250+
// Don't try this at home.
251+
context.runningWorker(
252+
Worker.from {
253+
isLoading.value = true
254+
},
255+
"initializing"
256+
) {
257+
action {
258+
isLoading.value = false
259+
state = Selected(NO_SELECTED_STANZA)
260+
}
261+
}
262+
OverviewDetailScreen(overviewRendering = BackStackScreen(BlankScreen))
263+
}
264+
else -> {
265+
val (stanzaIndex, currentStateIsLoading, repeat) = when (renderState) {
266+
is ComplexCall -> Triple(renderState.payload, true, renderState.repeater)
267+
is Selected -> Triple(renderState.stanzaIndex, false, 0)
268+
Initializing -> throw IllegalStateException("No longer initializing.")
269+
}
270+
271+
if (currentStateIsLoading) {
272+
if (repeat > 0) {
273+
// Running a flow that emits 'repeat' number of times
274+
context.runningWorker(
275+
flow {
276+
while (true) {
277+
// As long as this Worker is running we want to be emitting values.
278+
delay(2)
279+
emit(repeat)
280+
}
281+
}.asTraceableWorker("EventRepetition")
282+
) {
283+
action {
284+
(state as? ComplexCall)?.let { currentState ->
285+
// Still repeating the complex call
286+
state = ComplexCall(
287+
payload = currentState.payload,
288+
repeater = (currentState.repeater - 1).coerceAtLeast(0)
289+
)
290+
}
291+
}
292+
}
293+
} else {
294+
context.runningWorker(
295+
worker = TraceableWorker.from("PoemLoading") {
296+
isLoading.value = true
297+
delay(simulatedPerfConfig.complexityDelay)
298+
// No Output for Worker is necessary because the selected index
299+
// is already in the state.
300+
}
301+
) {
302+
action {
303+
isLoading.value = false
304+
(state as? ComplexCall)?.let { currentState ->
305+
state = Selected(currentState.payload)
306+
}
307+
}
308+
}
309+
}
310+
}
311+
312+
val previousStanzas: List<StanzaScreen> =
313+
if (stanzaIndex == NO_SELECTED_STANZA) emptyList()
314+
else renderProps.stanzas.subList(0, stanzaIndex)
315+
.mapIndexed { index, _ ->
316+
context.ChildRendering(
317+
StanzaWorkflow,
318+
Props(
319+
poem = renderProps,
320+
index = index,
321+
eventHandlerTag = ActionHandlingTracingInterceptor::keyForTrace
322+
),
323+
key = "$index"
324+
) {
325+
noAction()
326+
}
327+
}.map { originalStanzaScreen ->
328+
originalStanzaScreen
329+
}
330+
331+
val visibleStanza =
332+
if (stanzaIndex == NO_SELECTED_STANZA) {
333+
null
334+
} else {
335+
context.ChildRendering(
336+
StanzaWorkflow,
337+
Props(
338+
poem = renderProps,
339+
index = stanzaIndex,
340+
eventHandlerTag = ActionHandlingTracingInterceptor::keyForTrace
341+
),
342+
key = "$stanzaIndex"
343+
) {
344+
when (it) {
345+
CloseStanzas -> ClearSelection(simulatedPerfConfig)
346+
ShowPreviousStanza -> SelectPrevious(simulatedPerfConfig)
347+
ShowNextStanza -> SelectNext(simulatedPerfConfig)
348+
}
349+
}
350+
}
351+
352+
val stackedStanzas = visibleStanza?.let {
353+
(previousStanzas + visibleStanza).toBackStackScreen<Screen>()
354+
}
355+
356+
val stanzaListOverview =
357+
context.ChildRendering(
358+
StanzaListWorkflow,
359+
StanzaListWorkflow.Props(
360+
poem = renderProps,
361+
eventHandlerTag = ActionHandlingTracingInterceptor::keyForTrace
362+
),
363+
key = ""
364+
) { selected ->
365+
HandleStanzaListOutput(simulatedPerfConfig, selected)
366+
}
367+
.copy(selection = stanzaIndex)
368+
369+
stackedStanzas
370+
?.let {
371+
OverviewDetailScreen(
372+
overviewRendering = BackStackScreen(stanzaListOverview),
373+
detailRendering = it
374+
)
375+
} ?: OverviewDetailScreen(
376+
overviewRendering = BackStackScreen(stanzaListOverview),
377+
selectDefault = {
378+
context.actionSink.send(HandleStanzaListOutput(simulatedPerfConfig, 0))
379+
}
380+
)
381+
}
382+
}
383+
}
384+
237385
override fun snapshotState(state: State): Snapshot? = null
238386

239387
internal sealed class Action : WorkflowAction<Poem, State, ClosePoem>() {

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

Lines changed: 98 additions & 1 deletion
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.PerformancePoemsBrowserWorkflow.State
45
import com.squareup.benchmarks.performance.complex.poetry.PerformancePoemsBrowserWorkflow.State.ComplexCall
56
import com.squareup.benchmarks.performance.complex.poetry.PerformancePoemsBrowserWorkflow.State.Initializing
@@ -42,6 +43,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
4243
* break ties/conflicts with a token in the start/stop requests. We leave that complexity out
4344
* here. **
4445
*/
46+
@OptIn(WorkflowUiExperimentalApi::class)
4547
class PerformancePoemsBrowserWorkflow(
4648
private val simulatedPerfConfig: SimulatedPerfConfig,
4749
private val poemWorkflow: PoemWorkflow,
@@ -70,7 +72,6 @@ class PerformancePoemsBrowserWorkflow(
7072
return if (simulatedPerfConfig.useInitializingState) Initializing else NoSelection
7173
}
7274

73-
@OptIn(WorkflowUiExperimentalApi::class)
7475
override fun render(
7576
renderProps: List<Poem>,
7677
renderState: State,
@@ -154,6 +155,102 @@ class PerformancePoemsBrowserWorkflow(
154155
}
155156
}
156157

158+
@Composable
159+
override fun Rendering(
160+
renderProps: List<Poem>,
161+
renderState: State,
162+
context: RenderContext,
163+
hoistRendering: @Composable (OverviewDetailScreen) -> Unit
164+
) {
165+
val poemListProps = Props(
166+
poems = renderProps,
167+
eventHandlerTag = ActionHandlingTracingInterceptor::keyForTrace
168+
)
169+
context.ChildRendering(PoemListWorkflow, poemListProps, ""
170+
, hoistRendering = { poemListRendering ->
171+
when (renderState) {
172+
// Again, then entire `Initializing` state is a smell, which is most obvious from the
173+
// use of `Worker.from { Unit }`. A Worker doing no work and only shuttling the state
174+
// along is usually the sign you have an extraneous state that can be collapsed!
175+
// Don't try this at home.
176+
is Initializing -> {
177+
context.runningWorker(TraceableWorker.from("BrowserInitializing") { Unit }, "init") {
178+
isLoading.value = true
179+
action {
180+
isLoading.value = false
181+
state = NoSelection
182+
}
183+
}
184+
hoistRendering(OverviewDetailScreen(overviewRendering = BackStackScreen(BlankScreen)))
185+
}
186+
is NoSelection -> {
187+
hoistRendering(OverviewDetailScreen(
188+
overviewRendering = BackStackScreen(
189+
poemListRendering.copy(selection = NO_POEM_SELECTED)
190+
)
191+
))
192+
}
193+
is ComplexCall -> {
194+
context.runningWorker(
195+
TraceableWorker.from("ComplexCallBrowser(${renderState.payload})") {
196+
isLoading.value = true
197+
delay(simulatedPerfConfig.complexityDelay)
198+
// No Output for Worker is necessary because the selected index
199+
// is already in the state.
200+
}
201+
) {
202+
action {
203+
isLoading.value = false
204+
(state as? ComplexCall)?.let { currentState ->
205+
state = if (currentState.payload != NO_POEM_SELECTED) {
206+
Selected(currentState.payload)
207+
} else {
208+
NoSelection
209+
}
210+
}
211+
}
212+
}
213+
val poemOverview = OverviewDetailScreen(
214+
overviewRendering = BackStackScreen(
215+
poemListRendering.copy(selection = renderState.payload)
216+
)
217+
)
218+
if (renderState.payload != NO_POEM_SELECTED) {
219+
context.ChildRendering(
220+
poemWorkflow,
221+
renderProps[renderState.payload],
222+
key = "",
223+
hoistRendering = { poem: OverviewDetailScreen ->
224+
hoistRendering(poemOverview + poem)
225+
}
226+
) { clearSelection }
227+
} else {
228+
hoistRendering(poemOverview)
229+
}
230+
}
231+
is Selected -> {
232+
val poems = OverviewDetailScreen(
233+
overviewRendering = BackStackScreen(
234+
poemListRendering.copy(selection = renderState.poemIndex)
235+
)
236+
)
237+
context.ChildRendering(
238+
poemWorkflow,
239+
renderProps[renderState.poemIndex],
240+
key = "",
241+
hoistRendering = {
242+
hoistRendering(poems + it)
243+
}
244+
) { clearSelection }
245+
}
246+
}
247+
}
248+
) { selected ->
249+
choosePoem(selected)
250+
}
251+
252+
}
253+
157254
override fun snapshotState(state: State): Snapshot? = null
158255

159256
private fun choosePoem(

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ class PerformancePoetryActivity : AppCompatActivity() {
8484
}
8585

8686
val isFrameTimeout = intent.getBooleanExtra(EXTRA_RUNTIME_FRAME_TIMEOUT, false)
87-
val runtimeConfig = if (isFrameTimeout) FrameTimeout() else RenderPerAction
87+
val runtimeConfig = if (isFrameTimeout) FrameTimeout(useComposeInRuntime = true) else RenderPerAction
8888

8989
val component =
9090
PerformancePoetryComponent(installedInterceptor, simulatedPerfConfig, runtimeConfig)

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

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

3+
import androidx.compose.runtime.Composable
34
import androidx.tracing.trace
45
import com.squareup.workflow1.BaseRenderContext
56
import com.squareup.workflow1.WorkflowAction
@@ -70,6 +71,21 @@ class ActionHandlingTracingInterceptor : WorkflowInterceptor, Resettable {
7071
)
7172
}
7273

74+
@Composable
75+
override fun <P, S, O, R> Rendering(
76+
renderProps: P,
77+
renderState: S,
78+
hoistRendering: @Composable (R) -> Unit,
79+
context: BaseRenderContext<P, S, O>,
80+
session: WorkflowSession,
81+
proceed: @Composable (P, S, RenderContextInterceptor<P, S, O>?, @Composable (R) -> Unit) -> Unit
82+
) = proceed(
83+
renderProps,
84+
renderState,
85+
EventHandlingTracingRenderContextInterceptor(actionCounts),
86+
hoistRendering
87+
)
88+
7389
override fun reset() {
7490
actionCounts.clear()
7591
}

0 commit comments

Comments
 (0)