Skip to content

Commit 7c724a3

Browse files
1247: Partial Tree Rerendering
Track whether or not the state (or state of a child) has changed in the WorkflowNode. Pass lastRendering if its not.
1 parent 74fb6fb commit 7c724a3

File tree

3 files changed

+43
-28
lines changed

3 files changed

+43
-28
lines changed

workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowNode.kt

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,11 @@ internal class WorkflowNode<PropsT, StateT, OutputT, RenderingT>(
8686
)
8787
private val sideEffects = ActiveStagingList<SideEffectNode>()
8888
private var lastProps: PropsT = initialProps
89+
var lastRendering: RenderingT? = null
8990
private val eventActionsChannel =
9091
Channel<WorkflowAction<PropsT, StateT, OutputT>>(capacity = UNLIMITED)
9192
private var state: StateT
93+
private var subtreeStateDidChange: Boolean = true
9294

9395
private val baseRenderContext = RealRenderContext(
9496
renderer = subtreeManager,
@@ -205,16 +207,21 @@ internal class WorkflowNode<PropsT, StateT, OutputT, RenderingT>(
205207
coroutineContext.cancel(cause)
206208
}
207209

208-
// Call this after we have been passed any workflow instance, in [render] or [snapshot]. It may
209-
// have changed and we should check to see if we need to update our cached instances.
210+
/** Call this after we have been passed any workflow instance, in [render] or [snapshot]. It may
211+
* have changed and we should check to see if we need to update our cached instances.
212+
*
213+
* @return true if the instance has changed, otherwise false.
214+
*/
210215
private fun updateCachedWorkflowInstance(
211216
workflow: StatefulWorkflow<PropsT, StateT, OutputT, RenderingT>
212-
) {
217+
): Boolean {
213218
if (workflow !== cachedWorkflowInstance) {
214219
// instance has changed.
215220
interceptedWorkflowInstance = interceptor.intercept(workflow, this)
216221
cachedWorkflowInstance = workflow
222+
return true
217223
}
224+
return false
218225
}
219226

220227
/**
@@ -228,20 +235,22 @@ internal class WorkflowNode<PropsT, StateT, OutputT, RenderingT>(
228235
updateCachedWorkflowInstance(workflow)
229236
updatePropsAndState(props)
230237

231-
baseRenderContext.unfreeze()
232-
val rendering = interceptedWorkflowInstance.render(props, state, context)
233-
baseRenderContext.freeze()
238+
if (subtreeStateDidChange) {
239+
baseRenderContext.unfreeze()
240+
lastRendering = interceptedWorkflowInstance.render(props, state, context)
241+
baseRenderContext.freeze()
234242

235-
workflowTracer.trace("UpdateRuntimeTree") {
236-
// Tear down workflows and workers that are obsolete.
237-
subtreeManager.commitRenderedChildren()
238-
// Side effect jobs are launched lazily, since they can send actions to the sink, and can only
239-
// be started after context is frozen.
240-
sideEffects.forEachStaging { it.job.start() }
241-
sideEffects.commitStaging { it.job.cancel() }
243+
workflowTracer.trace("UpdateRuntimeTree") {
244+
// Tear down workflows and workers that are obsolete.
245+
subtreeManager.commitRenderedChildren()
246+
// Side effect jobs are launched lazily, since they can send actions to the sink, and can only
247+
// be started after context is frozen.
248+
sideEffects.forEachStaging { it.job.start() }
249+
sideEffects.commitStaging { it.job.cancel() }
250+
}
242251
}
243252

244-
return rendering
253+
return lastRendering!!
245254
}
246255

247256
private fun updatePropsAndState(
@@ -250,6 +259,7 @@ internal class WorkflowNode<PropsT, StateT, OutputT, RenderingT>(
250259
if (newProps != lastProps) {
251260
val newState = interceptedWorkflowInstance.onPropsChanged(lastProps, newProps, state)
252261
state = newState
262+
subtreeStateDidChange = true
253263
}
254264
lastProps = newProps
255265
}
@@ -270,6 +280,8 @@ internal class WorkflowNode<PropsT, StateT, OutputT, RenderingT>(
270280
// Changing state is sticky, we pass it up if it ever changed.
271281
stateChanged = actionApplied.stateChanged || (childResult?.stateChanged ?: false)
272282
)
283+
// Our state changed or one of our children's state changed.
284+
subtreeStateDidChange = aggregateActionApplied.stateChanged
273285
return if (actionApplied.output != null) {
274286
emitAppliedActionToParent(aggregateActionApplied)
275287
} else {

workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/WorkflowOperatorsTest.kt

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -207,23 +207,37 @@ class WorkflowOperatorsTest {
207207
private abstract class StateFlowWorkflow<T>(
208208
val name: String,
209209
val flow: StateFlow<T>
210-
) : StatelessWorkflow<Unit, Nothing, T>() {
210+
) : StatefulWorkflow<Unit, T, Nothing, T>() {
211211
var starts: Int = 0
212212
private set
213213

214+
override fun initialState(
215+
props: Unit,
216+
snapshot: Snapshot?
217+
): T {
218+
return flow.value
219+
}
220+
214221
private val rerenderWorker = object : Worker<T> {
215222
override fun run(): Flow<T> = flow.onStart { starts++ }
216223
}
217224

218225
override fun render(
219226
renderProps: Unit,
227+
renderState: T,
220228
context: RenderContext
221229
): T {
222230
// Listen to the flow to trigger a re-render when it updates.
223-
context.runningWorker(rerenderWorker as Worker<Any?>) { WorkflowAction.noAction() }
224-
return flow.value
231+
context.runningWorker(rerenderWorker) { output: T ->
232+
action("rerenderUpdate") {
233+
state = output
234+
}
235+
}
236+
return renderState
225237
}
226238

239+
override fun snapshotState(state: T): Snapshot? = null
240+
227241
override fun toString(): String = "StateFlowWorkflow($name)"
228242
}
229243
}

workflow-tracing/src/test/resources/com/squareup/workflow1/diagnostic/tracing/expected_trace_file.txt

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -63,17 +63,6 @@
6363
{"name":"Sink received: Worker<String> (2)","cat":"update","ph":"i","ts":0,"pid":0,"tid":0,"s":"t","args":{"action":"action(EmitWorkerOutputAction(worker=TypedWorker(java.lang.String (Kotlin reflection is not available)), key=))"}},
6464
{"name":"WorkflowAction: Worker<String> (2)","cat":"update","ph":"i","ts":0,"pid":0,"tid":0,"s":"p","args":{"action":"action(EmitWorkerOutputAction(worker=TypedWorker(java.lang.String (Kotlin reflection is not available)), key=))","oldState":"0","newState":"{no change}","output":"fired!"}},
6565
{"name":"Worker<String> (2)","ph":"O","ts":0,"pid":0,"tid":0,"id":"2","args":{"snapshot":"0"}},
66-
{"name":"Render Pass","cat":"rendering","ph":"B","ts":0,"pid":0,"tid":0,"args":{"props":"3"}},
67-
{"name":"used/free memory","ph":"C","ts":0,"pid":0,"tid":0,"args":{"usedMemory":1,"freeMemory":42}},
68-
{"name":"TestWorkflow (0)","cat":"rendering","ph":"B","ts":0,"pid":0,"tid":0,"args":{"workflowId":"0","props":"3","state":"changed state"}},
69-
{"name":"TestWorkflow (1)","cat":"rendering","ph":"B","ts":0,"pid":0,"tid":0,"args":{"workflowId":"1","props":"0","state":"initial"}},
70-
{"name":"TestWorkflow (1)","cat":"rendering","ph":"E","ts":0,"pid":0,"tid":0,"args":{"rendering":"initial"}},
71-
{"name":"Props changed: Worker<String> (2)","ph":"i","ts":0,"pid":0,"tid":0,"s":"t","args":{"oldProps":"TypedWorker(java.lang.String (Kotlin reflection is not available))","newProps":"TypedWorker(java.lang.String (Kotlin reflection is not available))","oldState":"0","newState":"{no change}"}},
72-
{"name":"Worker<String> (2)","cat":"rendering","ph":"B","ts":0,"pid":0,"tid":0,"args":{"workflowId":"2","props":"TypedWorker(java.lang.String (Kotlin reflection is not available))","state":"0"}},
73-
{"name":"Worker<String> (2)","cat":"rendering","ph":"E","ts":0,"pid":0,"tid":0,"args":{"rendering":"kotlin.Unit"}},
74-
{"name":"TestWorkflow (0)","cat":"rendering","ph":"E","ts":0,"pid":0,"tid":0,"args":{"rendering":"rendering"}},
75-
{"name":"Render Pass","cat":"rendering","ph":"E","ts":0,"pid":0,"tid":0,"args":{"rendering":"rendering"}},
76-
{"name":"used/free memory","ph":"C","ts":0,"pid":0,"tid":0,"args":{"usedMemory":1,"freeMemory":42}},
7766
{"name":"Snapshot","ph":"B","ts":0,"pid":0,"tid":0,"args":{}},
7867
{"name":"Snapshot","ph":"E","ts":0,"pid":0,"tid":0,"args":{}},
7968
{"name":"Props changed: {root}","ph":"i","ts":0,"pid":0,"tid":0,"s":"t","args":{"oldProps":"3","newProps":"4","oldState":"changed state","newState":"{no change}"}},

0 commit comments

Comments
 (0)