Skip to content

Commit 7067d19

Browse files
committed
Merge remote-tracking branch 'origin/compose_1.2.1' into compose_1.2.1
* origin/compose_1.2.1: update LeakCanary to `2.10` update dependency-guard baselines bump androidx-test core to `1.4.0`, Robolectric to `4.7.3` update compose runtime to 1.2.1, bump Android compileSdk to 32, targetSdk to 31 Add Recursion to Performance Poetry
2 parents 5403fbd + e2ee0fe commit 7067d19

File tree

8 files changed

+169
-84
lines changed

8 files changed

+169
-84
lines changed

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

Lines changed: 124 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,20 @@ import com.squareup.benchmarks.performance.complex.poetry.PerformancePoemsBrowse
44
import com.squareup.benchmarks.performance.complex.poetry.PerformancePoemsBrowserWorkflow.State.ComplexCall
55
import com.squareup.benchmarks.performance.complex.poetry.PerformancePoemsBrowserWorkflow.State.Initializing
66
import com.squareup.benchmarks.performance.complex.poetry.PerformancePoemsBrowserWorkflow.State.NoSelection
7+
import com.squareup.benchmarks.performance.complex.poetry.PerformancePoemsBrowserWorkflow.State.Recurse
78
import com.squareup.benchmarks.performance.complex.poetry.PerformancePoemsBrowserWorkflow.State.Selected
89
import com.squareup.benchmarks.performance.complex.poetry.instrumentation.ActionHandlingTracingInterceptor
910
import com.squareup.benchmarks.performance.complex.poetry.instrumentation.SimulatedPerfConfig
1011
import com.squareup.benchmarks.performance.complex.poetry.instrumentation.TraceableWorker
1112
import com.squareup.benchmarks.performance.complex.poetry.instrumentation.asTraceableWorker
1213
import com.squareup.benchmarks.performance.complex.poetry.views.BlankScreen
1314
import com.squareup.sample.container.overviewdetail.OverviewDetailScreen
15+
import com.squareup.sample.poetry.ConfigAndPoems
1416
import com.squareup.sample.poetry.PoemListScreen.Companion.NO_POEM_SELECTED
1517
import com.squareup.sample.poetry.PoemListWorkflow
1618
import com.squareup.sample.poetry.PoemListWorkflow.Props
1719
import com.squareup.sample.poetry.PoemWorkflow
1820
import com.squareup.sample.poetry.PoemsBrowserWorkflow
19-
import com.squareup.sample.poetry.model.Poem
2021
import com.squareup.workflow1.Snapshot
2122
import com.squareup.workflow1.StatefulWorkflow
2223
import com.squareup.workflow1.WorkflowAction.Companion.noAction
@@ -50,9 +51,11 @@ class PerformancePoemsBrowserWorkflow(
5051
private val isLoading: MutableStateFlow<Boolean>,
5152
) :
5253
PoemsBrowserWorkflow,
53-
StatefulWorkflow<List<Poem>, State, Unit, OverviewDetailScreen>() {
54+
StatefulWorkflow<ConfigAndPoems, State, Unit, OverviewDetailScreen>() {
5455

5556
sealed class State {
57+
object Recurse : State()
58+
5659
// N.B. This state is a smell. We include it to be able to mimic smells
5760
// we encounter in real life. Best practice would be to fold it
5861
// into [NoSelection] at the very least.
@@ -66,36 +69,63 @@ class PerformancePoemsBrowserWorkflow(
6669
}
6770

6871
override fun initialState(
69-
props: List<Poem>,
72+
props: ConfigAndPoems,
7073
snapshot: Snapshot?
7174
): State {
72-
return if (simulatedPerfConfig.useInitializingState) Initializing else NoSelection
75+
return if (props.first.first > 0 &&
76+
props.first.second == simulatedPerfConfig.recursionGraph.second
77+
) {
78+
Recurse
79+
} else if (simulatedPerfConfig.useInitializingState) {
80+
Initializing
81+
} else {
82+
NoSelection
83+
}
7384
}
7485

7586
@OptIn(WorkflowUiExperimentalApi::class)
7687
override fun render(
77-
renderProps: List<Poem>,
88+
renderProps: ConfigAndPoems,
7889
renderState: State,
7990
context: RenderContext
8091
): OverviewDetailScreen {
81-
if (simulatedPerfConfig.simultaneousActions > 0) {
82-
repeat(simulatedPerfConfig.simultaneousActions) { index ->
83-
context.runningWorker(
84-
worker = isLoading.asTraceableWorker("SimultaneousSubscribeBrowser-$index"),
85-
key = "Browser-$index"
92+
when (renderState) {
93+
is Recurse -> {
94+
val recursiveChild = PerformancePoemsBrowserWorkflow(
95+
simulatedPerfConfig,
96+
poemWorkflow,
97+
isLoading,
98+
)
99+
repeat(renderProps.first.second) { breadth ->
100+
val nextProps = renderProps.copy(
101+
first = renderProps.first.first - 1 to breadth
102+
)
103+
// When we repeat horizontally we ask the runtime to 'render' these Workflow nodes but
104+
// we don't use their renderings in what is passed to the UI layer as this is just to
105+
// fill out the Workflow tree to give the runtime more work to do. As such, we call
106+
// renderChild here but we ignore the rendering returned and do no action on any Output.
107+
// See SimulatedPerfConfig kdoc for more explanation.
108+
context.renderChild(
109+
child = recursiveChild,
110+
props = nextProps,
111+
key = "${nextProps.first},${nextProps.second}",
112+
) {
113+
noAction()
114+
}
115+
}
116+
val nextProps = renderProps.copy(
117+
first = renderProps.first.first - 1 to renderProps.first.second
118+
)
119+
return context.renderChild(
120+
child = recursiveChild,
121+
props = nextProps,
122+
key = "${nextProps.first},${nextProps.second}",
86123
) {
87-
noAction()
124+
action {
125+
setOutput(it)
126+
}
88127
}
89128
}
90-
}
91-
val poemListProps = Props(
92-
poems = renderProps,
93-
eventHandlerTag = ActionHandlingTracingInterceptor::keyForTrace
94-
)
95-
val poemListRendering = context.renderChild(PoemListWorkflow, poemListProps) { selected ->
96-
choosePoem(selected)
97-
}
98-
when (renderState) {
99129
// Again, then entire `Initializing` state is a smell, which is most obvious from the
100130
// use of `Worker.from { Unit }`. A Worker doing no work and only shuttling the state
101131
// along is usually the sign you have an extraneous state that can be collapsed!
@@ -110,58 +140,86 @@ class PerformancePoemsBrowserWorkflow(
110140
}
111141
return OverviewDetailScreen(overviewRendering = BackStackScreen(BlankScreen))
112142
}
113-
is NoSelection -> {
114-
return OverviewDetailScreen(
115-
overviewRendering = BackStackScreen(
116-
poemListRendering.copy(selection = NO_POEM_SELECTED)
117-
)
143+
144+
is ComplexCall, is NoSelection, is Selected -> {
145+
if (simulatedPerfConfig.simultaneousActions > 0) {
146+
repeat(simulatedPerfConfig.simultaneousActions) { index ->
147+
context.runningWorker(
148+
worker = isLoading.asTraceableWorker("SimultaneousSubscribeBrowser-$index"),
149+
key = "Browser-$index"
150+
) {
151+
noAction()
152+
}
153+
}
154+
}
155+
val poemListProps = Props(
156+
poems = renderProps.second,
157+
eventHandlerTag = ActionHandlingTracingInterceptor::keyForTrace
118158
)
119-
}
120-
is ComplexCall -> {
121-
context.runningWorker(
122-
TraceableWorker.from("ComplexCallBrowser(${renderState.payload})") {
123-
isLoading.value = true
124-
delay(simulatedPerfConfig.complexityDelay)
125-
// No Output for Worker is necessary because the selected index
126-
// is already in the state.
159+
val poemListRendering = context.renderChild(PoemListWorkflow, poemListProps) { selected ->
160+
choosePoem(selected)
161+
}
162+
when (renderState) {
163+
is NoSelection -> {
164+
return OverviewDetailScreen(
165+
overviewRendering = BackStackScreen(
166+
poemListRendering.copy(selection = NO_POEM_SELECTED)
167+
)
168+
)
127169
}
128-
) {
129-
action {
130-
isLoading.value = false
131-
(state as? ComplexCall)?.let { currentState ->
132-
state = if (currentState.payload != NO_POEM_SELECTED) {
133-
Selected(currentState.payload)
134-
} else {
135-
NoSelection
170+
171+
is ComplexCall -> {
172+
context.runningWorker(
173+
TraceableWorker.from("ComplexCallBrowser(${renderState.payload})") {
174+
isLoading.value = true
175+
delay(simulatedPerfConfig.complexityDelay)
176+
// No Output for Worker is necessary because the selected index
177+
// is already in the state.
178+
}
179+
) {
180+
action {
181+
isLoading.value = false
182+
(state as? ComplexCall)?.let { currentState ->
183+
state = if (currentState.payload != NO_POEM_SELECTED) {
184+
Selected(currentState.payload)
185+
} else {
186+
NoSelection
187+
}
188+
}
136189
}
137190
}
191+
var poems = OverviewDetailScreen(
192+
overviewRendering = BackStackScreen(
193+
poemListRendering.copy(selection = renderState.payload)
194+
)
195+
)
196+
if (renderState.payload != NO_POEM_SELECTED) {
197+
val poem: OverviewDetailScreen = context.renderChild(
198+
poemWorkflow,
199+
renderProps.second[renderState.payload]
200+
) { clearSelection }
201+
poems += poem
202+
}
203+
return poems
204+
}
205+
206+
is Selected -> {
207+
val poems = OverviewDetailScreen(
208+
overviewRendering = BackStackScreen(
209+
poemListRendering.copy(selection = renderState.poemIndex)
210+
)
211+
)
212+
val poem: OverviewDetailScreen = context.renderChild(
213+
poemWorkflow,
214+
renderProps.second[renderState.poemIndex]
215+
) { clearSelection }
216+
return poems + poem
217+
}
218+
219+
else -> {
220+
throw IllegalStateException("State can't change while rendering.")
138221
}
139222
}
140-
var poems = OverviewDetailScreen(
141-
overviewRendering = BackStackScreen(
142-
poemListRendering.copy(selection = renderState.payload)
143-
)
144-
)
145-
if (renderState.payload != NO_POEM_SELECTED) {
146-
val poem: OverviewDetailScreen = context.renderChild(
147-
poemWorkflow,
148-
renderProps[renderState.payload]
149-
) { clearSelection }
150-
poems += poem
151-
}
152-
return poems
153-
}
154-
is Selected -> {
155-
val poems = OverviewDetailScreen(
156-
overviewRendering = BackStackScreen(
157-
poemListRendering.copy(selection = renderState.poemIndex)
158-
)
159-
)
160-
val poem: OverviewDetailScreen = context.renderChild(
161-
poemWorkflow,
162-
renderProps[renderState.poemIndex]
163-
) { clearSelection }
164-
return poems + poem
165223
}
166224
}
167225
}

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,15 @@ class PerformancePoetryActivity : AppCompatActivity() {
5959

6060
setupMainLooperTracing()
6161

62+
val recursiveDepth = intent.getIntExtra(EXTRA_PERF_CONFIG_RECURSION_DEPTH, 0)
63+
val recursiveBreadth = intent.getIntExtra(EXTRA_PERF_CONFIG_RECURSION_BREADTH, 0)
64+
6265
// Default is just to have the basic 'delay' complexity.
6366
val simulatedPerfConfig = SimulatedPerfConfig(
6467
isComplex = true,
6568
complexityDelay = intent.getLongExtra(EXTRA_PERF_CONFIG_DELAY, 200L),
6669
useInitializingState = intent.getBooleanExtra(EXTRA_SCENARIO_CONFIG_INITIALIZING, false),
70+
recursionGraph = recursiveDepth to recursiveBreadth,
6771
repeatOnNext = intent.getIntExtra(EXTRA_PERF_CONFIG_REPEAT, 0),
6872
simultaneousActions = intent.getIntExtra(EXTRA_PERF_CONFIG_SIMULTANEOUS, 0),
6973
traceFrameLatency = intent.getBooleanExtra(EXTRA_TRACE_FRAME_LATENCY, false),
@@ -242,6 +246,10 @@ class PerformancePoetryActivity : AppCompatActivity() {
242246
const val EXTRA_PERF_CONFIG_REPEAT = "complex.poetry.performance.config.repeat.amount"
243247
const val EXTRA_PERF_CONFIG_DELAY = "complex.poetry.performance.config.delay.length"
244248
const val EXTRA_PERF_CONFIG_SIMULTANEOUS = "complex.poetry.performance.config.simultaneous"
249+
const val EXTRA_PERF_CONFIG_RECURSION_DEPTH =
250+
"complex.poetry.performance.config.recursion.depth"
251+
const val EXTRA_PERF_CONFIG_RECURSION_BREADTH =
252+
"complex.poetry.performance.config.recursion.breadth"
245253

246254
const val SELECT_ON_TIMEOUT_LOG_NAME =
247255
"kotlinx.coroutines.selects.SelectBuilderImpl\$onTimeout\$\$inlined\$Runnable"
@@ -257,7 +265,7 @@ class PerformancePoetryActivity : AppCompatActivity() {
257265

258266
class PoetryModel(
259267
savedState: SavedStateHandle,
260-
workflow: MaybeLoadingGatekeeperWorkflow<List<Poem>>,
268+
workflow: MaybeLoadingGatekeeperWorkflow<Pair<Pair<Int, Int>, List<Poem>>>,
261269
interceptor: WorkflowInterceptor?,
262270
runtimeConfig: RuntimeConfig
263271
) : ViewModel() {
@@ -274,7 +282,7 @@ class PoetryModel(
274282

275283
class Factory(
276284
owner: SavedStateRegistryOwner,
277-
private val workflow: MaybeLoadingGatekeeperWorkflow<List<Poem>>,
285+
private val workflow: MaybeLoadingGatekeeperWorkflow<Pair<Pair<Int, Int>, List<Poem>>>,
278286
private val workflowInterceptor: WorkflowInterceptor?,
279287
private val runtimeConfig: RuntimeConfig
280288
) : AbstractSavedStateViewModelFactory(owner, null) {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class PerformancePoetryComponent(
3434

3535
private val loadingGatekeeperForPoems = MaybeLoadingGatekeeperWorkflow(
3636
childWithLoading = poemsBrowserWorkflow,
37-
childProps = Poem.allPoems,
37+
childProps = Pair(simulatedPerfConfig.recursionGraph, Poem.allPoems),
3838
browserIsLoading.combine(poemIsLoading) { one, two -> one || two }
3939
)
4040

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

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,42 @@
11
package com.squareup.benchmarks.performance.complex.poetry.instrumentation
22

33
import android.os.Parcelable
4+
import com.squareup.sample.poetry.RecursionGraphConfig
45
import kotlinx.parcelize.Parcelize
56

67
/**
78
* We use this to 'simulate' different performance scenarios that we have seen that we want to
89
* be able to benchmark and monitor. Firstly we have a complexity which is just used to add some
910
* delay to selection activities - only use with Poetry right now.
1011
*
12+
* [isComplex] Determines whether or not we start a Worker in between state transitions that
13+
* roughly approximates doing I/O work or a network call.
14+
* [complexityDelay] Is the length of the delay for the work in between the state changes if
15+
* [isComplex] is true.
1116
* [useInitializingState] is a smell we have observed whereby an 'initializing' state is used
12-
* while waiting for the first values before doing the real Workflow work.
17+
* while waiting for the first values before doing the real Workflow work.
18+
* [recursionGraph] Determines the shape of the tree that is rendered by the Workflow runtime. The
19+
* first number n is the recursive 'depth'. This will have the [PerformancePoemBrowserWorkflow]
20+
* render itself recursively n times, before rendering its actual children - the poem list and
21+
* the poem. The second number m is the 'breadth'. This means that for each recursive depth layer
22+
* n, the [PerformancePoemBrowserWorkflow] will also be rendered as siblings m times. Only the
23+
* (m-1)th rendering is returned from render() though which means that the other renders will be
24+
* work for the runtime to render but they won't be passed to the UI layer.
25+
* [repeatOnNext] Is the number of times x that an action should be created when 'next' is pressed
26+
* in the poem before the action takes its effect - advances the state to the next stanza.
27+
* [simultaneousActions] Determines the number of workers y that should be created for each
28+
* 'complex' state transition. This will result in y actions that need to be handled
29+
* simultaneously and tries to represent a scenario of multiple Workflows listening to the same
30+
* action.
31+
* [traceRenderingPasses], [traceFrameLatency], [traceEventLatency] are all flags to add
32+
* instrumentation for different performance measurements.
1333
*/
1434
@Parcelize
1535
data class SimulatedPerfConfig(
1636
val isComplex: Boolean,
1737
val complexityDelay: Long,
1838
val useInitializingState: Boolean,
39+
val recursionGraph: RecursionGraphConfig = 0 to 0,
1940
val repeatOnNext: Int = 0,
2041
val simultaneousActions: Int = 0,
2142
val traceRenderingPasses: Boolean = false,
@@ -27,6 +48,7 @@ data class SimulatedPerfConfig(
2748
isComplex = false,
2849
complexityDelay = 0,
2950
useInitializingState = false,
51+
recursionGraph = 0 to 0,
3052
repeatOnNext = 0,
3153
simultaneousActions = 0,
3254
traceRenderingPasses = false,

samples/containers/app-poetry/src/main/java/com/squareup/sample/poetryapp/PoetryActivity.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ class PoetryModel(savedState: SavedStateHandle) : ViewModel() {
5252
renderWorkflowIn(
5353
workflow = RealPoemsBrowserWorkflow(RealPoemWorkflow()),
5454
scope = viewModelScope,
55-
prop = Poem.allPoems,
55+
prop = 0 to 0 to Poem.allPoems,
5656
savedStateHandle = savedState,
5757
runtimeConfig = AndroidRuntimeConfigTools.getAppWorkflowRuntimeConfig()
5858
)

samples/containers/poetry/src/main/java/com/squareup/sample/poetry/PoemsBrowserWorkflow.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@ import com.squareup.sample.container.overviewdetail.OverviewDetailScreen
44
import com.squareup.sample.poetry.model.Poem
55
import com.squareup.workflow1.Workflow
66

7+
typealias RecursionGraphConfig = Pair<Int, Int>
8+
typealias ConfigAndPoems = Pair<RecursionGraphConfig, List<Poem>>
9+
710
/**
811
* Provides an overview / detail treatment of a list of [Poem]s.
912
*
1013
* (Defining this as an interface allows us to use other implementations
1114
* in other contexts -- check out our :benchmarks module!)
1215
*/
13-
interface PoemsBrowserWorkflow : Workflow<List<Poem>, Unit, OverviewDetailScreen>
16+
interface PoemsBrowserWorkflow :
17+
Workflow<ConfigAndPoems, Unit, OverviewDetailScreen>

0 commit comments

Comments
 (0)