Skip to content

Commit eec92fa

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

Some content is hidden

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

41 files changed

+1200
-71
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: 32 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,36 @@ 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(
65+
childWithLoading, childProps, "",
66+
hoistRendering = {
67+
hoistRendering(
68+
MayBeLoadingScreen(
69+
baseScreen = it,
70+
loaders = if (renderState) listOf(LoaderSpinner) else emptyList()
71+
)
72+
)
73+
}
74+
) {
75+
action(ActionHandlingTracingInterceptor.keyForTrace("GatekeeperChildFinished")) {
76+
setOutput(
77+
Unit
78+
)
79+
}
80+
}
81+
}
82+
5183
override fun snapshotState(state: IsLoading): Snapshot? = null
5284
}

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

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

3+
import androidx.compose.runtime.Composable
4+
import androidx.compose.runtime.MutableState
5+
import androidx.compose.runtime.mutableStateOf
6+
import androidx.compose.runtime.remember
37
import com.squareup.benchmarks.performance.complex.poetry.PerformancePoemWorkflow.Action.ClearSelection
48
import com.squareup.benchmarks.performance.complex.poetry.PerformancePoemWorkflow.Action.HandleStanzaListOutput
59
import com.squareup.benchmarks.performance.complex.poetry.PerformancePoemWorkflow.Action.SelectNext
@@ -16,6 +20,7 @@ import com.squareup.benchmarks.performance.complex.poetry.views.BlankScreen
1620
import com.squareup.sample.container.overviewdetail.OverviewDetailScreen
1721
import com.squareup.sample.poetry.PoemWorkflow
1822
import com.squareup.sample.poetry.PoemWorkflow.ClosePoem
23+
import com.squareup.sample.poetry.StanzaListScreen
1924
import com.squareup.sample.poetry.StanzaListWorkflow
2025
import com.squareup.sample.poetry.StanzaListWorkflow.NO_SELECTED_STANZA
2126
import com.squareup.sample.poetry.StanzaScreen
@@ -57,6 +62,7 @@ import kotlinx.coroutines.flow.flow
5762
* break ties/conflicts with a token in the start/stop requests. We leave that complexity out
5863
* here. **
5964
*/
65+
@OptIn(WorkflowUiExperimentalApi::class)
6066
class PerformancePoemWorkflow(
6167
private val simulatedPerfConfig: SimulatedPerfConfig = SimulatedPerfConfig.NO_SIMULATED_PERF,
6268
private val isLoading: MutableStateFlow<Boolean>,
@@ -234,6 +240,164 @@ class PerformancePoemWorkflow(
234240
}
235241
}
236242

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

239403
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: 102 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,106 @@ class PerformancePoemsBrowserWorkflow(
154155
}
155156
}
156157

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

159260
private fun choosePoem(

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,12 @@ class PerformancePoetryActivity : AppCompatActivity() {
8383
installedInterceptor = ActionHandlingTracingInterceptor()
8484
}
8585

86-
val isFrameTimeout = intent.getBooleanExtra(EXTRA_RUNTIME_FRAME_TIMEOUT, false)
87-
val runtimeConfig = if (isFrameTimeout) FrameTimeout() else RenderPerAction
86+
val isFrameTimeout = true; //intent.getBooleanExtra(EXTRA_RUNTIME_FRAME_TIMEOUT, false)
87+
val runtimeConfig = if (isFrameTimeout) {
88+
FrameTimeout(useComposeInRuntime = true)
89+
} else {
90+
RenderPerAction
91+
}
8892

8993
val component =
9094
PerformancePoetryComponent(installedInterceptor, simulatedPerfConfig, runtimeConfig)

0 commit comments

Comments
 (0)