Skip to content

Commit 5fe9107

Browse files
Compose Runtime Integration Take 2
Still todo on the Snapshotting and figure out how we want to 'freeze' to guard against out of lifecycle calls.
1 parent 39db0a7 commit 5fe9107

File tree

51 files changed

+1447
-104
lines changed

Some content is hidden

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

51 files changed

+1447
-104
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
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package com.squareup.benchmarks.performance.complex.poetry
2+
3+
import androidx.compose.runtime.Composable
4+
import androidx.compose.runtime.MutableState
5+
import androidx.compose.runtime.mutableStateOf
6+
import androidx.compose.runtime.remember
7+
import androidx.test.ext.junit.runners.AndroidJUnit4
8+
import app.cash.molecule.AndroidUiDispatcher
9+
import app.cash.molecule.launchMolecule
10+
import com.google.common.truth.Truth.assertThat
11+
import kotlinx.coroutines.CoroutineScope
12+
import kotlinx.coroutines.ExperimentalCoroutinesApi
13+
import org.junit.Test
14+
import org.junit.runner.RunWith
15+
16+
@RunWith(AndroidJUnit4::class)
17+
class ComposeInheritanceTest {
18+
19+
abstract class Parent<T> {
20+
@Composable
21+
open fun AComposable(
22+
hoistToggleState: @Composable (s: T) -> Unit
23+
): Unit = throw IllegalStateException("I don't want to be a parent.")
24+
}
25+
26+
class Child<T>(
27+
private val payload: T
28+
): Parent<T>() {
29+
@Composable
30+
override fun AComposable(
31+
hoistToggleState: @Composable (s: T) -> Unit
32+
) {
33+
println("Can you hear me now? $payload")
34+
hoistToggleState(payload)
35+
}
36+
}
37+
38+
@Composable
39+
fun <T> Emitter(someObject: Parent<T>): T? {
40+
val payload: MutableState<T?> = remember { mutableStateOf(null) }
41+
someObject.AComposable {
42+
payload.value = it
43+
}
44+
return payload.value
45+
}
46+
47+
@OptIn(ExperimentalCoroutinesApi::class)
48+
@Test fun testComposableOverloading() {
49+
val child: Parent<String> = Child<String>("a test")
50+
val testScope = CoroutineScope(AndroidUiDispatcher.Main)
51+
52+
val testFlow = testScope.launchMolecule {
53+
Emitter(child)
54+
}
55+
56+
assertThat(testFlow.value).isEqualTo("a test")
57+
}
58+
59+
}

benchmarks/performance-poetry/complex-poetry/src/androidTest/java/com/squareup/benchmarks/performance/complex/poetry/RenderPassTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ class RenderPassTest {
270270
staleRenderedNodes = 2350..2350
271271
),
272272
frameTimeoutExpectation = RenderExpectation(
273-
totalPasses = 88..97,
273+
totalPasses = 64..68,
274274
freshRenderedNodes = 106..108,
275275
staleRenderedNodes = 679..698
276276
)

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

Lines changed: 27 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,31 @@ class MaybeLoadingGatekeeperWorkflow<T : Any>(
4849
)
4950
}
5051

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

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

Lines changed: 143 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,143 @@ class PerformancePoemWorkflow(
234240
}
235241
}
236242

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

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

0 commit comments

Comments
 (0)