@@ -4,19 +4,20 @@ import com.squareup.benchmarks.performance.complex.poetry.PerformancePoemsBrowse
44import com.squareup.benchmarks.performance.complex.poetry.PerformancePoemsBrowserWorkflow.State.ComplexCall
55import com.squareup.benchmarks.performance.complex.poetry.PerformancePoemsBrowserWorkflow.State.Initializing
66import com.squareup.benchmarks.performance.complex.poetry.PerformancePoemsBrowserWorkflow.State.NoSelection
7+ import com.squareup.benchmarks.performance.complex.poetry.PerformancePoemsBrowserWorkflow.State.Recurse
78import com.squareup.benchmarks.performance.complex.poetry.PerformancePoemsBrowserWorkflow.State.Selected
89import com.squareup.benchmarks.performance.complex.poetry.instrumentation.ActionHandlingTracingInterceptor
910import com.squareup.benchmarks.performance.complex.poetry.instrumentation.SimulatedPerfConfig
1011import com.squareup.benchmarks.performance.complex.poetry.instrumentation.TraceableWorker
1112import com.squareup.benchmarks.performance.complex.poetry.instrumentation.asTraceableWorker
1213import com.squareup.benchmarks.performance.complex.poetry.views.BlankScreen
1314import com.squareup.sample.container.overviewdetail.OverviewDetailScreen
15+ import com.squareup.sample.poetry.ConfigAndPoems
1416import com.squareup.sample.poetry.PoemListScreen.Companion.NO_POEM_SELECTED
1517import com.squareup.sample.poetry.PoemListWorkflow
1618import com.squareup.sample.poetry.PoemListWorkflow.Props
1719import com.squareup.sample.poetry.PoemWorkflow
1820import com.squareup.sample.poetry.PoemsBrowserWorkflow
19- import com.squareup.sample.poetry.model.Poem
2021import com.squareup.workflow1.Snapshot
2122import com.squareup.workflow1.StatefulWorkflow
2223import 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 }
0 commit comments