Skip to content

Commit 10814b1

Browse files
Merge pull request #1296 from square/sedwards/fix-no-change
Pass Rendering if any Conflations changed state
2 parents 9b8077c + bd8ed7e commit 10814b1

File tree

2 files changed

+97
-6
lines changed

2 files changed

+97
-6
lines changed

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

+12-3
Original file line numberDiff line numberDiff line change
@@ -165,9 +165,12 @@ public fun <PropsT, OutputT, RenderingT> renderWorkflowIn(
165165
* If [runtimeConfig] contains [RuntimeConfigOptions.RENDER_ONLY_WHEN_STATE_CHANGES] and
166166
* we have not changed state, then return true to short circuit the render loop.
167167
*/
168-
fun shouldShortCircuitForUnchangedState(actionResult: ActionProcessingResult): Boolean {
168+
fun shouldShortCircuitForUnchangedState(
169+
actionResult: ActionProcessingResult,
170+
conflationHasChangedState: Boolean = false
171+
): Boolean {
169172
return runtimeConfig.contains(RENDER_ONLY_WHEN_STATE_CHANGES) &&
170-
actionResult is ActionApplied<*> && !actionResult.stateChanged
173+
actionResult is ActionApplied<*> && !actionResult.stateChanged && !conflationHasChangedState
171174
}
172175

173176
scope.launch {
@@ -190,7 +193,9 @@ public fun <PropsT, OutputT, RenderingT> renderWorkflowIn(
190193
var nextRenderAndSnapshot: RenderingAndSnapshot<RenderingT> = runner.nextRendering()
191194

192195
if (runtimeConfig.contains(CONFLATE_STALE_RENDERINGS)) {
196+
var conflationHasChangedState = false
193197
conflate@ while (isActive && actionResult is ActionApplied<*> && actionResult.output == null) {
198+
conflationHasChangedState = conflationHasChangedState || actionResult.stateChanged
194199
// We start by yielding, because if we are on an Unconfined dispatcher, we want to give
195200
// other signals (like Workers listening to the same result) a chance to get dispatched
196201
// and queue their actions.
@@ -202,7 +207,11 @@ public fun <PropsT, OutputT, RenderingT> renderWorkflowIn(
202207
if (actionResult == ActionsExhausted) break@conflate
203208

204209
// Skip rendering if we had unchanged state, keep draining actions.
205-
if (shouldShortCircuitForUnchangedState(actionResult)) {
210+
if (shouldShortCircuitForUnchangedState(
211+
actionResult = actionResult,
212+
conflationHasChangedState = conflationHasChangedState
213+
)
214+
) {
206215
sendOutput(actionResult, onOutput)
207216
continue@outer
208217
}

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

+85-3
Original file line numberDiff line numberDiff line change
@@ -1393,7 +1393,6 @@ class RenderWorkflowInTest {
13931393
trigger.asWorker()
13941394
) {
13951395
action("") {
1396-
state = it
13971396
setOutput(it)
13981397
}
13991398
}
@@ -1403,10 +1402,9 @@ class RenderWorkflowInTest {
14031402
val workflow = Workflow.stateful<String, String, String>(
14041403
initialState = "unchanging state",
14051404
render = { renderState ->
1406-
renderChild(childWorkflow) { childOutput ->
1405+
renderChild(childWorkflow) { _ ->
14071406
action("childHandler") {
14081407
childHandlerActionExecuted++
1409-
state = childOutput
14101408
}
14111409
}
14121410
runningWorker(
@@ -1632,6 +1630,90 @@ class RenderWorkflowInTest {
16321630
}
16331631
}
16341632

1633+
@Test
1634+
fun for_conflate_and_render_only_when_state_changed_we_do_not_conflate_stacked_actions_into_one_rendering_if_previous_rendering_changed() {
1635+
runtimeTestRunner.runParametrizedTest(
1636+
paramSource = runtimeOptions
1637+
.filter {
1638+
it.first.contains(CONFLATE_STALE_RENDERINGS) &&
1639+
it.first.contains(RENDER_ONLY_WHEN_STATE_CHANGES)
1640+
},
1641+
before = ::setup,
1642+
) { (runtimeConfig: RuntimeConfig, workflowTracer: WorkflowTracer?, dispatcher: TestDispatcher) ->
1643+
runTest(dispatcher) {
1644+
check(runtimeConfig.contains(CONFLATE_STALE_RENDERINGS))
1645+
check(runtimeConfig.contains(RENDER_ONLY_WHEN_STATE_CHANGES))
1646+
1647+
var childHandlerActionExecuted = false
1648+
val trigger = MutableSharedFlow<String>()
1649+
val emitted = mutableListOf<String>()
1650+
1651+
val childWorkflow = Workflow.stateful<String, String, String>(
1652+
initialState = "unchanging state",
1653+
render = { renderState ->
1654+
runningWorker(
1655+
trigger.asWorker()
1656+
) {
1657+
action("") {
1658+
state = it
1659+
setOutput(it)
1660+
}
1661+
}
1662+
renderState
1663+
}
1664+
)
1665+
val workflow = Workflow.stateful<String, String, String>(
1666+
initialState = "unchanging state",
1667+
render = { renderState ->
1668+
renderChild(childWorkflow) { childOutput ->
1669+
action("childHandler") {
1670+
childHandlerActionExecuted = true
1671+
state = "$childOutput+update"
1672+
}
1673+
}
1674+
runningWorker(
1675+
trigger.asWorker()
1676+
) {
1677+
action("") {
1678+
// no state change now!
1679+
}
1680+
}
1681+
renderState
1682+
}
1683+
)
1684+
val props = MutableStateFlow(Unit)
1685+
val renderings = renderWorkflowIn(
1686+
workflow = workflow,
1687+
scope = backgroundScope,
1688+
props = props,
1689+
runtimeConfig = runtimeConfig,
1690+
workflowTracer = workflowTracer,
1691+
) { }
1692+
1693+
val collectionJob = launch {
1694+
// Collect this unconfined so we can get all the renderings faster than actions can
1695+
// be processed.
1696+
renderings.collect {
1697+
emitted += it.rendering
1698+
}
1699+
}
1700+
advanceIfStandard(dispatcher)
1701+
1702+
launch {
1703+
trigger.emit("changed state")
1704+
}
1705+
advanceIfStandard(dispatcher)
1706+
1707+
collectionJob.cancel()
1708+
1709+
// 2 renderings.
1710+
assertEquals(2, emitted.size)
1711+
assertEquals("changed state+update", emitted.last())
1712+
assertTrue(childHandlerActionExecuted)
1713+
}
1714+
}
1715+
}
1716+
16351717
private class ExpectedException : RuntimeException()
16361718

16371719
private fun <T1, T2> cartesianProduct(

0 commit comments

Comments
 (0)