From ad7bcf6e15cfa02bc710315546983e1c83564e22 Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Tue, 8 Jul 2025 17:06:30 -0700 Subject: [PATCH 1/2] Use WorkStealingDispatcher in runtime, behind a flag. # Conflicts: # workflow-core/src/commonMain/kotlin/com/squareup/workflow1/RuntimeConfig.kt # workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/RenderWorkflow.kt # workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/RenderWorkflowInTest.kt --- workflow-core/api/workflow-core.api | 2 + .../com/squareup/workflow1/RuntimeConfig.kt | 14 ++ .../com/squareup/workflow1/RenderWorkflow.kt | 19 +++ .../workflow1/RenderWorkflowInTest.kt | 133 +++++++++++++++++- 4 files changed, 166 insertions(+), 2 deletions(-) diff --git a/workflow-core/api/workflow-core.api b/workflow-core/api/workflow-core.api index 0d4ec7f2cd..0761fb28e7 100644 --- a/workflow-core/api/workflow-core.api +++ b/workflow-core/api/workflow-core.api @@ -168,6 +168,7 @@ public final class com/squareup/workflow1/RuntimeConfigOptions : java/lang/Enum public static final field PARTIAL_TREE_RENDERING Lcom/squareup/workflow1/RuntimeConfigOptions; public static final field RENDER_ONLY_WHEN_STATE_CHANGES Lcom/squareup/workflow1/RuntimeConfigOptions; public static final field STABLE_EVENT_HANDLERS Lcom/squareup/workflow1/RuntimeConfigOptions; + public static final field WORK_STEALING_DISPATCHER Lcom/squareup/workflow1/RuntimeConfigOptions; public static fun getEntries ()Lkotlin/enums/EnumEntries; public static fun valueOf (Ljava/lang/String;)Lcom/squareup/workflow1/RuntimeConfigOptions; public static fun values ()[Lcom/squareup/workflow1/RuntimeConfigOptions; @@ -180,6 +181,7 @@ public final class com/squareup/workflow1/RuntimeConfigOptions$Companion { } public final class com/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions : java/lang/Enum { + public static final field ALL Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; public static final field CONFLATE Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; public static final field DEA Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; public static final field DEFAULT Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; diff --git a/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/RuntimeConfig.kt b/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/RuntimeConfig.kt index d2e5ebf4d8..1a5c0ea3c0 100644 --- a/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/RuntimeConfig.kt +++ b/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/RuntimeConfig.kt @@ -67,6 +67,13 @@ public enum class RuntimeConfigOptions { @WorkflowExperimentalRuntime STABLE_EVENT_HANDLERS, + /** + * Wrap the dispatcher passed to the runtime with a special dispatcher that can be advanced + * explicitly, to allow any tasks scheduled by the workflow runtime to run before certain phases. + */ + @WorkflowExperimentalRuntime + WORK_STEALING_DISPATCHER, + /** * If we have more actions to process that are queued on nodes not affected by the last * action application, then we will continue to process those actions before another render @@ -161,6 +168,13 @@ public enum class RuntimeConfigOptions { DRAIN_EXCLUSIVE_ACTIONS, ) ), + + /** + * Always contains all [RuntimeConfigOptions]. Other values in this enum may happen to contain + * the same set at some point in time, but this one will also always be updated to include new + * ones as they're added. + */ + ALL(RuntimeConfigOptions.entries.toSet()) } } } diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/RenderWorkflow.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/RenderWorkflow.kt index 3c617e2779..ec047c1e93 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/RenderWorkflow.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/RenderWorkflow.kt @@ -4,6 +4,8 @@ import com.squareup.workflow1.RuntimeConfigOptions.CONFLATE_STALE_RENDERINGS import com.squareup.workflow1.RuntimeConfigOptions.DRAIN_EXCLUSIVE_ACTIONS import com.squareup.workflow1.RuntimeConfigOptions.RENDER_ONLY_WHEN_STATE_CHANGES import com.squareup.workflow1.WorkflowInterceptor.RenderPassSkipped +import com.squareup.workflow1.WorkflowInterceptor.RenderPassesComplete +import com.squareup.workflow1.internal.WorkStealingDispatcher import com.squareup.workflow1.WorkflowInterceptor.RenderingConflated import com.squareup.workflow1.WorkflowInterceptor.RenderingProduced import com.squareup.workflow1.internal.WorkflowRunner @@ -16,6 +18,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.isActive import kotlinx.coroutines.launch +import kotlinx.coroutines.plus /** * Launches the [workflow] in a new coroutine in [scope] and returns a [StateFlow] of its @@ -142,6 +145,15 @@ public fun renderWorkflowIn( ): StateFlow> { val chainedInterceptor = interceptors.chained() + val dispatcher = if (RuntimeConfigOptions.WORK_STEALING_DISPATCHER in runtimeConfig) { + WorkStealingDispatcher.wrapDispatcherFrom(scope.coroutineContext) + } else { + null + } + + @Suppress("NAME_SHADOWING") + val scope = dispatcher?.let { scope + dispatcher } ?: scope + val runner = WorkflowRunner( scope, workflow, @@ -249,6 +261,13 @@ public fun renderWorkflowIn( actionDrainingHasChangedState || actionResult.stateChanged // We may have more actions we can process, this rendering could be stale. // This will check for any actions that are immediately available and apply them. + // We advance the dispatcher first to allow any coroutines that were launched by the last + // render pass to start up and potentially enqueue actions. + dispatcher?.let { + workflowTracer.trace("AdvancingWorkflowDispatcher") { + dispatcher.advanceUntilIdle() + } + } actionResult = runner.applyNextAvailableTreeAction() // If no actions processed, then no new rendering needed. Pass on to UI. diff --git a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/RenderWorkflowInTest.kt b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/RenderWorkflowInTest.kt index 5263fabdf4..9d01b4281d 100644 --- a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/RenderWorkflowInTest.kt +++ b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/RenderWorkflowInTest.kt @@ -7,22 +7,29 @@ import com.squareup.workflow1.RuntimeConfigOptions.Companion.RuntimeOptions.DEFA import com.squareup.workflow1.RuntimeConfigOptions.DRAIN_EXCLUSIVE_ACTIONS import com.squareup.workflow1.RuntimeConfigOptions.PARTIAL_TREE_RENDERING import com.squareup.workflow1.RuntimeConfigOptions.RENDER_ONLY_WHEN_STATE_CHANGES +import com.squareup.workflow1.RuntimeConfigOptions.WORK_STEALING_DISPATCHER import com.squareup.workflow1.WorkflowInterceptor.RenderPassSkipped import com.squareup.workflow1.WorkflowInterceptor.RenderingProduced import com.squareup.workflow1.WorkflowInterceptor.RuntimeUpdate import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.cancel +import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.produceIn import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.isActive +import kotlinx.coroutines.job import kotlinx.coroutines.launch +import kotlinx.coroutines.plus import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.test.StandardTestDispatcher @@ -46,7 +53,7 @@ import kotlin.test.assertTrue @Burst class RenderWorkflowInTest( useTracer: Boolean = false, - useUnconfined: Boolean = true, + private val useUnconfined: Boolean = true, private val runtime: RuntimeOptions = DEFAULT ) { @@ -1502,7 +1509,9 @@ class RenderWorkflowInTest( @Test fun for_conflate_we_do_not_conflate_stacked_actions_into_one_rendering_if_output() { - if (runtimeConfig.contains(CONFLATE_STALE_RENDERINGS)) { + if (CONFLATE_STALE_RENDERINGS in runtimeConfig && + WORK_STEALING_DISPATCHER !in runtimeConfig + ) { runTest(dispatcherUsed) { check(runtimeConfig.contains(CONFLATE_STALE_RENDERINGS)) @@ -1746,6 +1755,126 @@ class RenderWorkflowInTest( } } + /** + * When the [CONFLATE_STALE_RENDERINGS] flag is specified, the runtime will repeatedly run all + * enqueued WorkflowActions after a render pass, before emitting the rendering to the external + * flow. When the [WORK_STEALING_DISPATCHER] flag is specified at the same time, any coroutines + * launched (or even resumed) since the render pass will be allowed to run _before_ checking for + * actions. This means that any new side effects or workers started by the render pass will be + * allowed to run to their first suspension point before the rendering is emitted. And if they + * happen to emit more actions as part of that, then those actions will also be processed, etc. + * until no more actions are available – only then will the rendering actually be emitted. + */ + @Test + fun new_effect_coroutines_dispatched_before_rendering_emitted_when_work_stealing_dispatcher() { + // This tests is specifically for standard dispatching behavior. It currently only works when + // CSR is enabled, although an additional test for DEA should be added. + if (WORK_STEALING_DISPATCHER !in runtimeConfig || + CONFLATE_STALE_RENDERINGS !in runtimeConfig || + useUnconfined + ) { + return + } + + runTest(dispatcherUsed) { + val workflow = Workflow.stateful(initialState = 0) { effectCount -> + // Because of the WSD, this effect will be allowed to run after the render pass but before + // emitting the rendering OR checking for new actions, in the CSR loop. Since it emits an + // action, that action will be processed and trigger a second render pass. + runningSideEffect("sender") { + actionSink.send( + action("0") { + expect(2) + this.state++ + } + ) + } + + if (effectCount >= 1) { + // This effect will be started by the first action and cancelled only when the runtime + // is cancelled. + // It will also start in the CSR loop, and trigger a third render pass before emitting the + // rendering. + runningSideEffect("0") { + expect(3) + actionSink.send( + action("1") { + expect(4) + this.state++ + } + ) + awaitCancellation { + expect(9) + } + } + } + + if (effectCount >= 2) { + // This effect will be started by the second action, and cancelled by its own action in + // the same run of the CSR loop again. + runningSideEffect("1") { + expect(5) + actionSink.send( + action("-1") { + expect(6) + this.state-- + } + ) + awaitCancellation { + expect(7) + } + } + } + } + + // We collect the renderings flow to a channel to drive the runtime loop by receiving from the + // channel. We can't use testScheduler.advanceUntilIdle() et al because we only want the test + // scheduler to run tasks until a rendering is available, not indefinitely. + val renderings = renderWorkflowIn( + workflow = workflow, + // Run in this scope so it is advanced by advanceUntilIdle. + scope = backgroundScope, + props = MutableStateFlow(Unit), + runtimeConfig = runtimeConfig, + workflowTracer = testTracer, + onOutput = {} + ).produceIn(backgroundScope + Dispatchers.Unconfined) + + expect(0) + // Receiving the first rendering allows the runtime coroutine to start. The first rendering + // is returned synchronously. + renderings.receive() + expect(1) + // Receiving the second rendering will allow the runtime to continue until the rendering is + // emitted. Since the CSR loop will start all our effects before emitting the next rendering, + // only one rendering will be emitted for all those render passes. + renderings.receive() + expect(8) + + // No more renderings should be produced. + testScheduler.advanceUntilIdle() + assertTrue(renderings.isEmpty) + + // Cancel the whole workflow runtime, including all effects. + backgroundScope.coroutineContext.job.cancelAndJoin() + expect(10) + } + } + + private suspend fun awaitCancellation(onFinally: () -> Unit) { + try { + awaitCancellation() + } finally { + onFinally() + } + } + + private var expectCounter = 0 + private fun expect(expected: Int) { + assertEquals(expected, expectCounter) + expectCounter++ + } + @Test fun for_drain_exclusive_we_handle_multiple_actions_in_one_render_or_not() = runTest( dispatcherUsed From d9bbcff493029e404940c8d209cf46d172c5f169 Mon Sep 17 00:00:00 2001 From: Stephen Edwards Date: Wed, 23 Jul 2025 10:13:44 -0400 Subject: [PATCH 2/2] Update WSD Use, Further Tests, Add to CI --- .github/workflows/kotlin.yml | 52 ++- .../config/AndroidRuntimeConfigTools.kt | 8 + .../config/JvmTestRuntimeConfigTools.kt | 8 + workflow-core/api/workflow-core.api | 71 +++- .../com/squareup/workflow1/RuntimeConfig.kt | 345 ++++++++++++++++-- .../AndroidDispatchersRenderWorkflowInTest.kt | 106 ++++-- .../com/squareup/workflow1/RenderWorkflow.kt | 23 +- .../workflow1/RenderWorkflowInTest.kt | 8 +- .../workflow1/internal/WorkflowRunnerTest.kt | 4 +- .../workflow1/WorkflowsLifecycleTests.kt | 4 +- 10 files changed, 522 insertions(+), 107 deletions(-) diff --git a/.github/workflows/kotlin.yml b/.github/workflows/kotlin.yml index 2457beed8b..2faa718593 100644 --- a/.github/workflows/kotlin.yml +++ b/.github/workflows/kotlin.yml @@ -239,7 +239,7 @@ jobs: uses: mikepenz/action-junit-report@5f47764eec0e1c1f19f40c8e60a5ba47e47015c5 # v4 if: always() # always run even if the previous step fails with: - report_paths: '**/build/test-results/test/TEST-*.xml' + report_paths: '**/build/test-results/jvmTest/TEST-*.xml' jvm-conflate-runtime-test: name: CSR JVM Tests @@ -260,7 +260,7 @@ jobs: uses: mikepenz/action-junit-report@5f47764eec0e1c1f19f40c8e60a5ba47e47015c5 # v4 if: always() # always run even if the previous step fails with: - report_paths: '**/build/test-results/test/TEST-*.xml' + report_paths: '**/build/test-results/jvmTest/TEST-*.xml' jvm-stateChange-runtime-test: name: SCO JVM Tests @@ -281,7 +281,7 @@ jobs: uses: mikepenz/action-junit-report@5f47764eec0e1c1f19f40c8e60a5ba47e47015c5 # v4 if: always() # always run even if the previous step fails with: - report_paths: '**/build/test-results/test/TEST-*.xml' + report_paths: '**/build/test-results/jvmTest/TEST-*.xml' jvm-stable-handlers-test: name: SEH JVM Tests @@ -302,7 +302,7 @@ jobs: uses: mikepenz/action-junit-report@5f47764eec0e1c1f19f40c8e60a5ba47e47015c5 # v4 if: always() # always run even if the previous step fails with: - report_paths: '**/build/test-results/test/TEST-*.xml' + report_paths: '**/build/test-results/jvmTest/TEST-*.xml' jvm-partial-runtime-test: name: PTR JVM Tests @@ -323,7 +323,7 @@ jobs: uses: mikepenz/action-junit-report@5f47764eec0e1c1f19f40c8e60a5ba47e47015c5 # v4 if: always() # always run even if the previous step fails with: - report_paths: '**/build/test-results/test/TEST-*.xml' + report_paths: '**/build/test-results/jvmTest/TEST-*.xml' jvm-conflate-stateChange-runtime-test: name: SCO, CSR JVM Tests @@ -344,7 +344,7 @@ jobs: uses: mikepenz/action-junit-report@5f47764eec0e1c1f19f40c8e60a5ba47e47015c5 # v4 if: always() # always run even if the previous step fails with: - report_paths: '**/build/test-results/test/TEST-*.xml' + report_paths: '**/build/test-results/jvmTest/TEST-*.xml' jvm-conflate-partial-runtime-test: name: CSR, PTR JVM Tests @@ -365,7 +365,7 @@ jobs: uses: mikepenz/action-junit-report@5f47764eec0e1c1f19f40c8e60a5ba47e47015c5 # v4 if: always() # always run even if the previous step fails with: - report_paths: '**/build/test-results/test/TEST-*.xml' + report_paths: '**/build/test-results/jvmTest/TEST-*.xml' jvm-conflate-drainExclusive-runtime-test: name: CSR, DEA JVM Tests @@ -386,7 +386,7 @@ jobs: uses: mikepenz/action-junit-report@5f47764eec0e1c1f19f40c8e60a5ba47e47015c5 # v4 if: always() # always run even if the previous step fails with: - report_paths: '**/build/test-results/test/TEST-*.xml' + report_paths: '**/build/test-results/jvmTest/TEST-*.xml' jvm-stateChange-drainExclusive-runtime-test: name: SCO, DEA JVM Tests @@ -407,7 +407,7 @@ jobs: uses: mikepenz/action-junit-report@5f47764eec0e1c1f19f40c8e60a5ba47e47015c5 # v4 if: always() # always run even if the previous step fails with: - report_paths: '**/build/test-results/test/TEST-*.xml' + report_paths: '**/build/test-results/jvmTest/TEST-*.xml' jvm-partial-drainExclusive-runtime-test: name: PTR, DEA JVM Tests @@ -428,7 +428,7 @@ jobs: uses: mikepenz/action-junit-report@5f47764eec0e1c1f19f40c8e60a5ba47e47015c5 # v4 if: always() # always run even if the previous step fails with: - report_paths: '**/build/test-results/test/TEST-*.xml' + report_paths: '**/build/test-results/jvmTest/TEST-*.xml' jvm-conflate-stateChange-drainExclusive-runtime-test: name: SCO, CSR, DEA JVM Tests @@ -449,7 +449,7 @@ jobs: uses: mikepenz/action-junit-report@5f47764eec0e1c1f19f40c8e60a5ba47e47015c5 # v4 if: always() # always run even if the previous step fails with: - report_paths: '**/build/test-results/test/TEST-*.xml' + report_paths: '**/build/test-results/jvmTest/TEST-*.xml' jvm-conflate-partial-drainExclusive-runtime-test: name: CSR, PTR, DEA JVM Tests @@ -470,7 +470,28 @@ jobs: uses: mikepenz/action-junit-report@5f47764eec0e1c1f19f40c8e60a5ba47e47015c5 # v4 if: always() # always run even if the previous step fails with: - report_paths: '**/build/test-results/test/TEST-*.xml' + report_paths: '**/build/test-results/jvmTest/TEST-*.xml' + + jvm-all-runtime-test: + name: ALL Optimizations JVM Tests + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - name: Checkout + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Check with Gradle + uses: ./.github/actions/gradle-task + with: + task: jvmTest --continue -Pworkflow.runtime=all + restore-cache-key: main-build-artifacts + + # Report as GitHub Pull Request Check. + - name: Publish Test Report + uses: mikepenz/action-junit-report@5f47764eec0e1c1f19f40c8e60a5ba47e47015c5 # v4 + if: always() # always run even if the previous step fails + with: + report_paths: '**/build/test-results/jvmTest/TEST-*.xml' ios-tests: name: iOS Tests @@ -491,7 +512,7 @@ jobs: uses: mikepenz/action-junit-report@5f47764eec0e1c1f19f40c8e60a5ba47e47015c5 # v4 if: always() # always run even if the previous step fails with: - report_paths: '**/build/test-results/test/TEST-*.xml' + report_paths: '**/build/test-results/iosX64Test/TEST-*.xml' js-tests: name: JS Tests @@ -513,7 +534,7 @@ jobs: uses: mikepenz/action-junit-report@5f47764eec0e1c1f19f40c8e60a5ba47e47015c5 # v4 if: always() # always run even if the previous step fails with: - report_paths: '**/build/test-results/test/TEST-*.xml' + report_paths: '**/build/test-results/jsTest/TEST-*.xml' performance-tests: name: Performance tests @@ -578,7 +599,7 @@ jobs: ### shardNum: [ 1, 2, 3 ] ### - runtime: [ conflate, stateChange, drainExclusive, conflate-stateChange, partial, conflate-partial, stable, conflate-drainExclusive, stateChange-drainExclusive, partial-drainExclusive, conflate-partial-drainExclusive ] + runtime: [ conflate, stateChange, drainExclusive, conflate-stateChange, partial, conflate-partial, stable, conflate-drainExclusive, stateChange-drainExclusive, partial-drainExclusive, conflate-partial-drainExclusive, all ] steps: - name: Checkout uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 @@ -618,6 +639,7 @@ jobs: - jvm-partial-drainExclusive-runtime-test - jvm-conflate-stateChange-drainExclusive-runtime-test - jvm-conflate-partial-drainExclusive-runtime-test + - jvm-all-runtime-test - ktlint - performance-tests - runtime-instrumentation-tests diff --git a/workflow-config/config-android/src/main/java/com/squareup/workflow1/config/AndroidRuntimeConfigTools.kt b/workflow-config/config-android/src/main/java/com/squareup/workflow1/config/AndroidRuntimeConfigTools.kt index 56798220e7..3a7dcca286 100644 --- a/workflow-config/config-android/src/main/java/com/squareup/workflow1/config/AndroidRuntimeConfigTools.kt +++ b/workflow-config/config-android/src/main/java/com/squareup/workflow1/config/AndroidRuntimeConfigTools.kt @@ -7,6 +7,7 @@ import com.squareup.workflow1.RuntimeConfigOptions.DRAIN_EXCLUSIVE_ACTIONS import com.squareup.workflow1.RuntimeConfigOptions.PARTIAL_TREE_RENDERING import com.squareup.workflow1.RuntimeConfigOptions.RENDER_ONLY_WHEN_STATE_CHANGES import com.squareup.workflow1.RuntimeConfigOptions.STABLE_EVENT_HANDLERS +import com.squareup.workflow1.RuntimeConfigOptions.WORK_STEALING_DISPATCHER import com.squareup.workflow1.WorkflowExperimentalRuntime public class AndroidRuntimeConfigTools { @@ -38,6 +39,11 @@ public class AndroidRuntimeConfigTools { * - `drainExclusive` Enables draining exclusive actions. If we have more actions to process * that are queued on nodes not affected by the last action application, then we will * continue to process those actions before another render pass. + * + * - `stealingDispatcher` Enables turning on the [WorkStealingDispatcher] to try and drain + * available tasks. + * + * - `all` Enables all optimizations. */ @WorkflowExperimentalRuntime public fun getAppWorkflowRuntimeConfig(): RuntimeConfig { @@ -54,6 +60,8 @@ public class AndroidRuntimeConfigTools { "partial" -> config.addAll(setOf(RENDER_ONLY_WHEN_STATE_CHANGES, PARTIAL_TREE_RENDERING)) "stable" -> config.add(STABLE_EVENT_HANDLERS) "drainExclusive" -> config.add(DRAIN_EXCLUSIVE_ACTIONS) + "stealingDispatcher" -> config.add(WORK_STEALING_DISPATCHER) + "all" -> config.addAll(RuntimeConfigOptions.ALL) else -> throw IllegalArgumentException("Unrecognized runtime config option \"$it\"") } } diff --git a/workflow-config/config-jvm/src/main/java/com/squareup/workflow1/config/JvmTestRuntimeConfigTools.kt b/workflow-config/config-jvm/src/main/java/com/squareup/workflow1/config/JvmTestRuntimeConfigTools.kt index 2d525ff384..a522c5c00e 100644 --- a/workflow-config/config-jvm/src/main/java/com/squareup/workflow1/config/JvmTestRuntimeConfigTools.kt +++ b/workflow-config/config-jvm/src/main/java/com/squareup/workflow1/config/JvmTestRuntimeConfigTools.kt @@ -7,6 +7,7 @@ import com.squareup.workflow1.RuntimeConfigOptions.DRAIN_EXCLUSIVE_ACTIONS import com.squareup.workflow1.RuntimeConfigOptions.PARTIAL_TREE_RENDERING import com.squareup.workflow1.RuntimeConfigOptions.RENDER_ONLY_WHEN_STATE_CHANGES import com.squareup.workflow1.RuntimeConfigOptions.STABLE_EVENT_HANDLERS +import com.squareup.workflow1.RuntimeConfigOptions.WORK_STEALING_DISPATCHER import com.squareup.workflow1.WorkflowExperimentalRuntime public class JvmTestRuntimeConfigTools { @@ -40,6 +41,11 @@ public class JvmTestRuntimeConfigTools { * - `drainExclusive` Enables draining exclusive actions. If we have more actions to process * that are queued on nodes not affected by the last action application, then we will * continue to process those actions before another render pass. + * + * - `stealingDispatcher` Enables turning on the [WorkStealingDispatcher] to try and drain + * available tasks. + * + * - `all` Enables all optimizations. */ @OptIn(WorkflowExperimentalRuntime::class) public fun getTestRuntimeConfig(): RuntimeConfig { @@ -56,6 +62,8 @@ public class JvmTestRuntimeConfigTools { "partial" -> config.addAll(setOf(RENDER_ONLY_WHEN_STATE_CHANGES, PARTIAL_TREE_RENDERING)) "stable" -> config.add(STABLE_EVENT_HANDLERS) "drainExclusive" -> config.add(DRAIN_EXCLUSIVE_ACTIONS) + "stealingDispatcher" -> config.add(WORK_STEALING_DISPATCHER) + "all" -> config.addAll(RuntimeConfigOptions.ALL) else -> throw IllegalArgumentException("Unrecognized runtime config option \"$it\"") } } diff --git a/workflow-core/api/workflow-core.api b/workflow-core/api/workflow-core.api index 0761fb28e7..d6f2a627c4 100644 --- a/workflow-core/api/workflow-core.api +++ b/workflow-core/api/workflow-core.api @@ -183,23 +183,68 @@ public final class com/squareup/workflow1/RuntimeConfigOptions$Companion { public final class com/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions : java/lang/Enum { public static final field ALL Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; public static final field CONFLATE Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; - public static final field DEA Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; - public static final field DEFAULT Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field CONFLATE_DRAIN Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field CONFLATE_DRAIN_STEAL Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field CONFLATE_STABLE Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field CONFLATE_STABLE_DRAIN Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field CONFLATE_STABLE_DRAIN_STEAL Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field CONFLATE_STABLE_STEAL Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field CONFLATE_STEAL Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field DRAIN Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field DRAIN_STEAL Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field NONE Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field PARTIAL_TREE Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field PARTIAL_TREE_CONFLATE Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field PARTIAL_TREE_CONFLATE_DRAIN Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field PARTIAL_TREE_CONFLATE_DRAIN_STEAL Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field PARTIAL_TREE_CONFLATE_STABLE Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field PARTIAL_TREE_CONFLATE_STABLE_DRAIN Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field PARTIAL_TREE_CONFLATE_STABLE_DRAIN_STEAL Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field PARTIAL_TREE_CONFLATE_STABLE_STEAL Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field PARTIAL_TREE_CONFLATE_STEAL Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field PARTIAL_TREE_DRAIN Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field PARTIAL_TREE_DRAIN_STEAL Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field PARTIAL_TREE_STABLE Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field PARTIAL_TREE_STABLE_DRAIN Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field PARTIAL_TREE_STABLE_DRAIN_STEAL Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field PARTIAL_TREE_STABLE_STEAL Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field PARTIAL_TREE_STEAL Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; public static final field RENDER_ONLY Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; public static final field RENDER_ONLY_CONFLATE Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; - public static final field RENDER_ONLY_CONFLATE_DEA Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; - public static final field RENDER_ONLY_CONFLATE_PARTIAL Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; - public static final field RENDER_ONLY_CONFLATE_PARTIAL_DEA Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; - public static final field RENDER_ONLY_CONFLATE_PARTIAL_STABLE Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; - public static final field RENDER_ONLY_CONFLATE_PARTIAL_STABLE_DEA Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field RENDER_ONLY_CONFLATE_DRAIN Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field RENDER_ONLY_CONFLATE_DRAIN_STEAL Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; public static final field RENDER_ONLY_CONFLATE_STABLE Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; - public static final field RENDER_ONLY_DEA Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; - public static final field RENDER_ONLY_DEA_STABLE Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; - public static final field RENDER_ONLY_PARTIAL Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; - public static final field RENDER_ONLY_PARTIAL_DEA Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; - public static final field RENDER_ONLY_PARTIAL_STABLE Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; - public static final field RENDER_ONLY_PARTIAL_STABLE_DEA Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field RENDER_ONLY_CONFLATE_STABLE_DRAIN Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field RENDER_ONLY_CONFLATE_STABLE_DRAIN_STEAL Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field RENDER_ONLY_CONFLATE_STABLE_STEAL Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field RENDER_ONLY_CONFLATE_STEAL Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field RENDER_ONLY_DRAIN Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field RENDER_ONLY_DRAIN_STEAL Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field RENDER_ONLY_PARTIAL_TREE Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field RENDER_ONLY_PARTIAL_TREE_CONFLATE Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field RENDER_ONLY_PARTIAL_TREE_CONFLATE_DRAIN Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field RENDER_ONLY_PARTIAL_TREE_CONFLATE_DRAIN_STEAL Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field RENDER_ONLY_PARTIAL_TREE_CONFLATE_STABLE Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field RENDER_ONLY_PARTIAL_TREE_CONFLATE_STABLE_DRAIN Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field RENDER_ONLY_PARTIAL_TREE_CONFLATE_STABLE_STEAL Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field RENDER_ONLY_PARTIAL_TREE_CONFLATE_STEAL Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field RENDER_ONLY_PARTIAL_TREE_DRAIN Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field RENDER_ONLY_PARTIAL_TREE_DRAIN_STEAL Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field RENDER_ONLY_PARTIAL_TREE_STABLE Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field RENDER_ONLY_PARTIAL_TREE_STABLE_DRAIN Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field RENDER_ONLY_PARTIAL_TREE_STABLE_DRAIN_STEAL Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field RENDER_ONLY_PARTIAL_TREE_STABLE_STEAL Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field RENDER_ONLY_PARTIAL_TREE_STEAL Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field RENDER_ONLY_STABLE Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field RENDER_ONLY_STABLE_DRAIN Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field RENDER_ONLY_STABLE_DRAIN_STEAL Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field RENDER_ONLY_STABLE_STEAL Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field RENDER_ONLY_STEAL Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; public static final field STABLE Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field STABLE_DRAIN Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field STABLE_DRAIN_STEAL Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field STABLE_STEAL Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; + public static final field STEAL Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; public static fun getEntries ()Lkotlin/enums/EnumEntries; public final fun getRuntimeConfig ()Ljava/util/Set; public static fun valueOf (Ljava/lang/String;)Lcom/squareup/workflow1/RuntimeConfigOptions$Companion$RuntimeOptions; diff --git a/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/RuntimeConfig.kt b/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/RuntimeConfig.kt index 1a5c0ea3c0..026870f9af 100644 --- a/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/RuntimeConfig.kt +++ b/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/RuntimeConfig.kt @@ -56,6 +56,18 @@ public enum class RuntimeConfigOptions { /** * If we have more actions to process, do so before passing the rendering to the UI layer. + * + * Note that this is *not effective* when using an immediate or unconfined dispatcher as the + * handling of an action happens synchronously after the coroutine sending it into the sink + * is resumed. This means it never dispatches multiple coroutines that send actions into the sink + * before handling the first, so there is never multiple actions to apply! + * + * Note further that [WORK_STEALING_DISPATCHER] does not fix this for immediate dispatchers. In + * order to use this optimization, a non-immediate dispatcher must be used. If using a non- + * immediate dispatcher, we recommend that you ensure that the Workflow runtime completes all + * known updates before the next 'frame.' This can be done using a dispatcher that is integrated + * with the frame clock on your platform. E.g., on Android we recommend using Compose UI's + * `AndroidUiDispatcher.Main` in order to access these optimizations. */ @WorkflowExperimentalRuntime CONFLATE_STALE_RENDERINGS, @@ -78,6 +90,18 @@ public enum class RuntimeConfigOptions { * If we have more actions to process that are queued on nodes not affected by the last * action application, then we will continue to process those actions before another render * pass. + * + * Note that this is *not effective* when using an immediate or unconfined dispatcher as the + * handling of an action happens synchronously after the coroutine sending it into the sink + * is resumed. This means it never dispatches multiple coroutines that send actions into the sink + * before handling the first, so there is never multiple actions to apply! + * + * Note further that [WORK_STEALING_DISPATCHER] does not fix this for immediate dispatchers. In + * order to use this optimization, a non-immediate dispatcher must be used. If using a non- + * immediate dispatcher, we recommend that you ensure that the Workflow runtime completes all + * known updates before the next 'frame.' This can be done using a dispatcher that is integrated + * with the frame clock on your platform. E.g., on Android we recommend using Compose UI's + * `AndroidUiDispatcher.Main` in order to access these optimizations. */ @WorkflowExperimentalRuntime DRAIN_EXCLUSIVE_ACTIONS, @@ -105,67 +129,340 @@ public enum class RuntimeConfigOptions { enum class RuntimeOptions( val runtimeConfig: RuntimeConfig ) { - DEFAULT(RENDER_PER_ACTION), + NONE(RENDER_PER_ACTION), RENDER_ONLY(setOf(RENDER_ONLY_WHEN_STATE_CHANGES)), + PARTIAL_TREE(setOf(PARTIAL_TREE_RENDERING)), CONFLATE(setOf(CONFLATE_STALE_RENDERINGS)), STABLE(setOf(STABLE_EVENT_HANDLERS)), - - DEA(setOf(DRAIN_EXCLUSIVE_ACTIONS)), + DRAIN(setOf(DRAIN_EXCLUSIVE_ACTIONS)), + STEAL(setOf(WORK_STEALING_DISPATCHER)), + RENDER_ONLY_PARTIAL_TREE(setOf(RENDER_ONLY_WHEN_STATE_CHANGES, PARTIAL_TREE_RENDERING)), RENDER_ONLY_CONFLATE(setOf(RENDER_ONLY_WHEN_STATE_CHANGES, CONFLATE_STALE_RENDERINGS)), - RENDER_ONLY_PARTIAL(setOf(RENDER_ONLY_WHEN_STATE_CHANGES, PARTIAL_TREE_RENDERING)), - - RENDER_ONLY_DEA(setOf(RENDER_ONLY_WHEN_STATE_CHANGES, DRAIN_EXCLUSIVE_ACTIONS)), + RENDER_ONLY_STABLE(setOf(RENDER_ONLY_WHEN_STATE_CHANGES, STABLE_EVENT_HANDLERS)), + RENDER_ONLY_DRAIN(setOf(RENDER_ONLY_WHEN_STATE_CHANGES, DRAIN_EXCLUSIVE_ACTIONS)), + RENDER_ONLY_STEAL(setOf(RENDER_ONLY_WHEN_STATE_CHANGES, WORK_STEALING_DISPATCHER)), + PARTIAL_TREE_CONFLATE(setOf(PARTIAL_TREE_RENDERING, CONFLATE_STALE_RENDERINGS)), + PARTIAL_TREE_STABLE(setOf(PARTIAL_TREE_RENDERING, STABLE_EVENT_HANDLERS)), + PARTIAL_TREE_DRAIN(setOf(PARTIAL_TREE_RENDERING, DRAIN_EXCLUSIVE_ACTIONS)), + PARTIAL_TREE_STEAL(setOf(PARTIAL_TREE_RENDERING, WORK_STEALING_DISPATCHER)), + CONFLATE_STABLE(setOf(CONFLATE_STALE_RENDERINGS, STABLE_EVENT_HANDLERS)), + CONFLATE_DRAIN(setOf(CONFLATE_STALE_RENDERINGS, DRAIN_EXCLUSIVE_ACTIONS)), + CONFLATE_STEAL(setOf(CONFLATE_STALE_RENDERINGS, WORK_STEALING_DISPATCHER)), + STABLE_DRAIN(setOf(STABLE_EVENT_HANDLERS, DRAIN_EXCLUSIVE_ACTIONS)), + STABLE_STEAL(setOf(STABLE_EVENT_HANDLERS, WORK_STEALING_DISPATCHER)), + DRAIN_STEAL(setOf(DRAIN_EXCLUSIVE_ACTIONS, WORK_STEALING_DISPATCHER)), + RENDER_ONLY_PARTIAL_TREE_CONFLATE( + setOf( + RENDER_ONLY_WHEN_STATE_CHANGES, + PARTIAL_TREE_RENDERING, + CONFLATE_STALE_RENDERINGS + ) + ), + RENDER_ONLY_PARTIAL_TREE_STABLE( + setOf( + RENDER_ONLY_WHEN_STATE_CHANGES, + PARTIAL_TREE_RENDERING, + STABLE_EVENT_HANDLERS + ) + ), + RENDER_ONLY_PARTIAL_TREE_DRAIN( + setOf( + RENDER_ONLY_WHEN_STATE_CHANGES, + PARTIAL_TREE_RENDERING, + DRAIN_EXCLUSIVE_ACTIONS + ) + ), + RENDER_ONLY_PARTIAL_TREE_STEAL( + setOf( + RENDER_ONLY_WHEN_STATE_CHANGES, + PARTIAL_TREE_RENDERING, + WORK_STEALING_DISPATCHER + ) + ), RENDER_ONLY_CONFLATE_STABLE( - setOf(RENDER_ONLY_WHEN_STATE_CHANGES, CONFLATE_STALE_RENDERINGS, STABLE_EVENT_HANDLERS) + setOf( + RENDER_ONLY_WHEN_STATE_CHANGES, + CONFLATE_STALE_RENDERINGS, + STABLE_EVENT_HANDLERS + ) ), - RENDER_ONLY_CONFLATE_PARTIAL( - setOf(RENDER_ONLY_WHEN_STATE_CHANGES, CONFLATE_STALE_RENDERINGS, PARTIAL_TREE_RENDERING) + RENDER_ONLY_CONFLATE_DRAIN( + setOf( + RENDER_ONLY_WHEN_STATE_CHANGES, + CONFLATE_STALE_RENDERINGS, + DRAIN_EXCLUSIVE_ACTIONS + ) ), - - RENDER_ONLY_CONFLATE_DEA( - setOf(RENDER_ONLY_WHEN_STATE_CHANGES, CONFLATE_STALE_RENDERINGS, DRAIN_EXCLUSIVE_ACTIONS) + RENDER_ONLY_CONFLATE_STEAL( + setOf( + RENDER_ONLY_WHEN_STATE_CHANGES, + CONFLATE_STALE_RENDERINGS, + WORK_STEALING_DISPATCHER + ) ), - RENDER_ONLY_PARTIAL_STABLE( - setOf(RENDER_ONLY_WHEN_STATE_CHANGES, PARTIAL_TREE_RENDERING, STABLE_EVENT_HANDLERS) + RENDER_ONLY_STABLE_DRAIN( + setOf( + RENDER_ONLY_WHEN_STATE_CHANGES, + STABLE_EVENT_HANDLERS, + DRAIN_EXCLUSIVE_ACTIONS + ) ), - - RENDER_ONLY_PARTIAL_DEA( - setOf(RENDER_ONLY_WHEN_STATE_CHANGES, PARTIAL_TREE_RENDERING, DRAIN_EXCLUSIVE_ACTIONS) + RENDER_ONLY_STABLE_STEAL( + setOf( + RENDER_ONLY_WHEN_STATE_CHANGES, + STABLE_EVENT_HANDLERS, + WORK_STEALING_DISPATCHER + ) ), - RENDER_ONLY_DEA_STABLE( - setOf(RENDER_ONLY_WHEN_STATE_CHANGES, DRAIN_EXCLUSIVE_ACTIONS, STABLE_EVENT_HANDLERS) + RENDER_ONLY_DRAIN_STEAL( + setOf( + RENDER_ONLY_WHEN_STATE_CHANGES, + DRAIN_EXCLUSIVE_ACTIONS, + WORK_STEALING_DISPATCHER + ) ), - RENDER_ONLY_CONFLATE_PARTIAL_STABLE( + PARTIAL_TREE_CONFLATE_STABLE( + setOf( + PARTIAL_TREE_RENDERING, + CONFLATE_STALE_RENDERINGS, + STABLE_EVENT_HANDLERS + ) + ), + PARTIAL_TREE_CONFLATE_DRAIN( + setOf( + PARTIAL_TREE_RENDERING, + CONFLATE_STALE_RENDERINGS, + DRAIN_EXCLUSIVE_ACTIONS + ) + ), + PARTIAL_TREE_CONFLATE_STEAL( + setOf( + PARTIAL_TREE_RENDERING, + CONFLATE_STALE_RENDERINGS, + WORK_STEALING_DISPATCHER + ) + ), + PARTIAL_TREE_STABLE_DRAIN( + setOf( + PARTIAL_TREE_RENDERING, + STABLE_EVENT_HANDLERS, + DRAIN_EXCLUSIVE_ACTIONS + ) + ), + PARTIAL_TREE_STABLE_STEAL( + setOf( + PARTIAL_TREE_RENDERING, + STABLE_EVENT_HANDLERS, + WORK_STEALING_DISPATCHER + ) + ), + PARTIAL_TREE_DRAIN_STEAL( + setOf( + PARTIAL_TREE_RENDERING, + DRAIN_EXCLUSIVE_ACTIONS, + WORK_STEALING_DISPATCHER + ) + ), + CONFLATE_STABLE_DRAIN( + setOf( + CONFLATE_STALE_RENDERINGS, + STABLE_EVENT_HANDLERS, + DRAIN_EXCLUSIVE_ACTIONS + ) + ), + CONFLATE_STABLE_STEAL( + setOf( + CONFLATE_STALE_RENDERINGS, + STABLE_EVENT_HANDLERS, + WORK_STEALING_DISPATCHER + ) + ), + CONFLATE_DRAIN_STEAL( + setOf( + CONFLATE_STALE_RENDERINGS, + DRAIN_EXCLUSIVE_ACTIONS, + WORK_STEALING_DISPATCHER + ) + ), + STABLE_DRAIN_STEAL( + setOf( + STABLE_EVENT_HANDLERS, + DRAIN_EXCLUSIVE_ACTIONS, + WORK_STEALING_DISPATCHER + ) + ), + RENDER_ONLY_PARTIAL_TREE_CONFLATE_STABLE( setOf( RENDER_ONLY_WHEN_STATE_CHANGES, + PARTIAL_TREE_RENDERING, CONFLATE_STALE_RENDERINGS, + STABLE_EVENT_HANDLERS + ) + ), + RENDER_ONLY_PARTIAL_TREE_CONFLATE_DRAIN( + setOf( + RENDER_ONLY_WHEN_STATE_CHANGES, + PARTIAL_TREE_RENDERING, + CONFLATE_STALE_RENDERINGS, + DRAIN_EXCLUSIVE_ACTIONS + ) + ), + RENDER_ONLY_PARTIAL_TREE_CONFLATE_STEAL( + setOf( + RENDER_ONLY_WHEN_STATE_CHANGES, + PARTIAL_TREE_RENDERING, + CONFLATE_STALE_RENDERINGS, + WORK_STEALING_DISPATCHER + ) + ), + RENDER_ONLY_PARTIAL_TREE_STABLE_DRAIN( + setOf( + RENDER_ONLY_WHEN_STATE_CHANGES, + PARTIAL_TREE_RENDERING, + STABLE_EVENT_HANDLERS, + DRAIN_EXCLUSIVE_ACTIONS + ) + ), + RENDER_ONLY_PARTIAL_TREE_STABLE_STEAL( + setOf( + RENDER_ONLY_WHEN_STATE_CHANGES, + PARTIAL_TREE_RENDERING, + STABLE_EVENT_HANDLERS, + WORK_STEALING_DISPATCHER + ) + ), + RENDER_ONLY_PARTIAL_TREE_DRAIN_STEAL( + setOf( + RENDER_ONLY_WHEN_STATE_CHANGES, + PARTIAL_TREE_RENDERING, + DRAIN_EXCLUSIVE_ACTIONS, + WORK_STEALING_DISPATCHER + ) + ), + RENDER_ONLY_CONFLATE_STABLE_DRAIN( + setOf( + RENDER_ONLY_WHEN_STATE_CHANGES, + CONFLATE_STALE_RENDERINGS, + STABLE_EVENT_HANDLERS, + DRAIN_EXCLUSIVE_ACTIONS + ) + ), + RENDER_ONLY_CONFLATE_STABLE_STEAL( + setOf( + RENDER_ONLY_WHEN_STATE_CHANGES, + CONFLATE_STALE_RENDERINGS, + STABLE_EVENT_HANDLERS, + WORK_STEALING_DISPATCHER + ) + ), + RENDER_ONLY_CONFLATE_DRAIN_STEAL( + setOf( + RENDER_ONLY_WHEN_STATE_CHANGES, + CONFLATE_STALE_RENDERINGS, + DRAIN_EXCLUSIVE_ACTIONS, + WORK_STEALING_DISPATCHER + ) + ), + RENDER_ONLY_STABLE_DRAIN_STEAL( + setOf( + RENDER_ONLY_WHEN_STATE_CHANGES, + STABLE_EVENT_HANDLERS, + DRAIN_EXCLUSIVE_ACTIONS, + WORK_STEALING_DISPATCHER + ) + ), + PARTIAL_TREE_CONFLATE_STABLE_DRAIN( + setOf( + PARTIAL_TREE_RENDERING, + CONFLATE_STALE_RENDERINGS, + STABLE_EVENT_HANDLERS, + DRAIN_EXCLUSIVE_ACTIONS + ) + ), + PARTIAL_TREE_CONFLATE_STABLE_STEAL( + setOf( + PARTIAL_TREE_RENDERING, + CONFLATE_STALE_RENDERINGS, + STABLE_EVENT_HANDLERS, + WORK_STEALING_DISPATCHER + ) + ), + PARTIAL_TREE_CONFLATE_DRAIN_STEAL( + setOf( + PARTIAL_TREE_RENDERING, + CONFLATE_STALE_RENDERINGS, + DRAIN_EXCLUSIVE_ACTIONS, + WORK_STEALING_DISPATCHER + ) + ), + PARTIAL_TREE_STABLE_DRAIN_STEAL( + setOf( PARTIAL_TREE_RENDERING, STABLE_EVENT_HANDLERS, + DRAIN_EXCLUSIVE_ACTIONS, + WORK_STEALING_DISPATCHER ) ), - RENDER_ONLY_CONFLATE_PARTIAL_DEA( + CONFLATE_STABLE_DRAIN_STEAL( + setOf( + CONFLATE_STALE_RENDERINGS, + STABLE_EVENT_HANDLERS, + DRAIN_EXCLUSIVE_ACTIONS, + WORK_STEALING_DISPATCHER + ) + ), + RENDER_ONLY_PARTIAL_TREE_CONFLATE_STABLE_DRAIN( setOf( RENDER_ONLY_WHEN_STATE_CHANGES, + PARTIAL_TREE_RENDERING, CONFLATE_STALE_RENDERINGS, + STABLE_EVENT_HANDLERS, + DRAIN_EXCLUSIVE_ACTIONS + ) + ), + RENDER_ONLY_PARTIAL_TREE_CONFLATE_STABLE_STEAL( + setOf( + RENDER_ONLY_WHEN_STATE_CHANGES, PARTIAL_TREE_RENDERING, + CONFLATE_STALE_RENDERINGS, + STABLE_EVENT_HANDLERS, + WORK_STEALING_DISPATCHER + ) + ), + RENDER_ONLY_PARTIAL_TREE_CONFLATE_DRAIN_STEAL( + setOf( + RENDER_ONLY_WHEN_STATE_CHANGES, + PARTIAL_TREE_RENDERING, + CONFLATE_STALE_RENDERINGS, DRAIN_EXCLUSIVE_ACTIONS, + WORK_STEALING_DISPATCHER ) ), - RENDER_ONLY_PARTIAL_STABLE_DEA( + RENDER_ONLY_PARTIAL_TREE_STABLE_DRAIN_STEAL( setOf( RENDER_ONLY_WHEN_STATE_CHANGES, PARTIAL_TREE_RENDERING, STABLE_EVENT_HANDLERS, DRAIN_EXCLUSIVE_ACTIONS, + WORK_STEALING_DISPATCHER ) ), - RENDER_ONLY_CONFLATE_PARTIAL_STABLE_DEA( + RENDER_ONLY_CONFLATE_STABLE_DRAIN_STEAL( setOf( RENDER_ONLY_WHEN_STATE_CHANGES, CONFLATE_STALE_RENDERINGS, + STABLE_EVENT_HANDLERS, + DRAIN_EXCLUSIVE_ACTIONS, + WORK_STEALING_DISPATCHER + ) + ), + PARTIAL_TREE_CONFLATE_STABLE_DRAIN_STEAL( + setOf( PARTIAL_TREE_RENDERING, + CONFLATE_STALE_RENDERINGS, STABLE_EVENT_HANDLERS, DRAIN_EXCLUSIVE_ACTIONS, + WORK_STEALING_DISPATCHER ) ), @@ -174,7 +471,7 @@ public enum class RuntimeConfigOptions { * the same set at some point in time, but this one will also always be updated to include new * ones as they're added. */ - ALL(RuntimeConfigOptions.entries.toSet()) + ALL(RuntimeConfigOptions.ALL) } } } diff --git a/workflow-runtime-android/src/androidTest/java/com/squareup/workflow1/android/AndroidDispatchersRenderWorkflowInTest.kt b/workflow-runtime-android/src/androidTest/java/com/squareup/workflow1/android/AndroidDispatchersRenderWorkflowInTest.kt index e26fc09838..41dfba87f7 100644 --- a/workflow-runtime-android/src/androidTest/java/com/squareup/workflow1/android/AndroidDispatchersRenderWorkflowInTest.kt +++ b/workflow-runtime-android/src/androidTest/java/com/squareup/workflow1/android/AndroidDispatchersRenderWorkflowInTest.kt @@ -8,7 +8,7 @@ import app.cash.burst.Burst import com.squareup.workflow1.RenderingAndSnapshot import com.squareup.workflow1.RuntimeConfigOptions.CONFLATE_STALE_RENDERINGS import com.squareup.workflow1.RuntimeConfigOptions.Companion.RuntimeOptions -import com.squareup.workflow1.RuntimeConfigOptions.Companion.RuntimeOptions.DEFAULT +import com.squareup.workflow1.RuntimeConfigOptions.Companion.RuntimeOptions.ALL import com.squareup.workflow1.RuntimeConfigOptions.DRAIN_EXCLUSIVE_ACTIONS import com.squareup.workflow1.Workflow import com.squareup.workflow1.WorkflowAction @@ -18,10 +18,13 @@ import com.squareup.workflow1.WorkflowInterceptor.RenderingProduced import com.squareup.workflow1.WorkflowInterceptor.RuntimeUpdate import com.squareup.workflow1.WorkflowInterceptor.WorkflowSession import com.squareup.workflow1.action +import com.squareup.workflow1.android.RuntimeDispatcher.COMPOSE_UI +import com.squareup.workflow1.android.RuntimeDispatcher.IMMEDIATE import com.squareup.workflow1.asWorker import com.squareup.workflow1.renderChild import com.squareup.workflow1.runningWorker import com.squareup.workflow1.stateful +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -39,17 +42,28 @@ import kotlin.test.assertEquals @OptIn(WorkflowExperimentalRuntime::class) @Burst class AndroidDispatchersRenderWorkflowInTest( - private val runtime: RuntimeOptions = DEFAULT + private val runtime: RuntimeOptions = ALL, + // Note that we test with the Main.immediate dispatcher and with Composes's + // AndroidUiDispatcher.Main. Only the latter, which is not immediate, allows us to process + // multiple actions with CSR and with DEA. This is because an immediate dispatcher will never + // dispatch multiple coroutines to send actions before applying and handling the first one. + private val dispatcher: RuntimeDispatcher = COMPOSE_UI, ) { + private lateinit var runtimeContext: CoroutineContext + @Before fun setup() { + runtimeContext = when (dispatcher) { + IMMEDIATE -> Dispatchers.Main.immediate + COMPOSE_UI -> AndroidUiDispatcher.Main + } resetCounters() } @Test fun optimizations_for_multiple_worker_actions_same_trigger() { - val childWorkflow = Workflow.stateful( + val childWorkflow = Workflow.stateful( initialState = "unchanged state", render = { renderState -> runningWorker( @@ -65,7 +79,7 @@ class AndroidDispatchersRenderWorkflowInTest( renderState } ) - val workflow = Workflow.stateful( + val workflow = Workflow.stateful( initialState = "unchanged state", render = { renderState -> renderChild(childWorkflow) { childOutput -> @@ -113,17 +127,21 @@ class AndroidDispatchersRenderWorkflowInTest( // There are 2 attempts to produce a rendering for Conflate (initial and then the update.) // And otherwise there are *5* attempts to produce a new rendering. expectedRenderingsProduced = - if (runtime.runtimeConfig.contains(CONFLATE_STALE_RENDERINGS)) 2 else 5, - // Regardless only ever 2 renderings are consumed as the compose dispatcher drains all of - // the coroutines to update state before the collector can consume a rendering. - expectedRenderingsConsumed = 2 + if (runtime.runtimeConfig.contains(CONFLATE_STALE_RENDERINGS) && + dispatcher == COMPOSE_UI + ) { + 2 + } else { + 5 + }, + expectedRenderingsConsumed = if (dispatcher == COMPOSE_UI) 2 else 5 ) } @Test fun optimizations_for_multiple_side_effect_actions() { - val childWorkflow = Workflow.stateful( + val childWorkflow = Workflow.stateful( initialState = "unchanged state", render = { renderState -> runningSideEffect("childSideEffect") { @@ -172,17 +190,21 @@ class AndroidDispatchersRenderWorkflowInTest( // There are 2 attempts to produce a rendering for Conflate (initial and then the update.) // And otherwise there are *3* attempts to produce a new rendering. expectedRenderingsProduced = - if (runtime.runtimeConfig.contains(CONFLATE_STALE_RENDERINGS)) 2 else 3, - // Regardless only ever 2 renderings are consumed as the compose dispatcher drains all of - // the coroutines to update state before the collector can consume a rendering. - expectedRenderingsConsumed = 2 + if (runtime.runtimeConfig.contains(CONFLATE_STALE_RENDERINGS) && + dispatcher == COMPOSE_UI + ) { + 2 + } else { + 3 + }, + expectedRenderingsConsumed = if (dispatcher == COMPOSE_UI) 2 else 3 ) } @Test fun optimizations_for_exclusive_actions() { - val childWorkflow = Workflow.stateful( + val childWorkflow = Workflow.stateful( initialState = "unchanged state", render = { renderState -> runningWorker( @@ -216,19 +238,25 @@ class AndroidDispatchersRenderWorkflowInTest( workflow = workflow, targetRendering = "state change+u1", // 2 for DEA (initial synchronous + 1 for the update); 3 otherwise given the 2 child actions. - expectedRenderPasses = if (runtime.runtimeConfig.contains(DRAIN_EXCLUSIVE_ACTIONS)) 2 else 3, + expectedRenderPasses = if (runtime.runtimeConfig.contains(DRAIN_EXCLUSIVE_ACTIONS) && + dispatcher == COMPOSE_UI + ) { + 2 + } else { + 3 + }, // There are 2 attempts to produce a rendering for Conflate & DEA (initial and then the // update.) And otherwise there are *3* attempts to produce a new rendering. expectedRenderingsProduced = - if (runtime.runtimeConfig.contains(CONFLATE_STALE_RENDERINGS) || - runtime.runtimeConfig.contains(DRAIN_EXCLUSIVE_ACTIONS) + if (( + runtime.runtimeConfig.contains(CONFLATE_STALE_RENDERINGS) || + runtime.runtimeConfig.contains(DRAIN_EXCLUSIVE_ACTIONS) + ) && dispatcher == COMPOSE_UI ) { 2 } else { 3 }, - // Regardless only ever 2 renderings are consumed as the compose dispatcher drains all of - // the coroutines to update state before the collector can consume a rendering. expectedRenderingsConsumed = 2 ) } @@ -252,18 +280,17 @@ class AndroidDispatchersRenderWorkflowInTest( } ) - // We are rendering using Compose's AndroidUiDispatcher.Main. val renderings = renderWorkflowIn( workflow = workflow, scope = backgroundScope + - ANDROID_WORKFLOW_RUNTIME_CONTEXT, + runtimeContext, props = MutableStateFlow(Unit).asStateFlow(), runtimeConfig = runtime.runtimeConfig, workflowTracer = null, interceptors = emptyList() ) {} - val collectionJob = launch(AndroidUiDispatcher.Main) { + val collectionJob = launch(runtimeContext) { renderings.collect { if (it == "changed state") { // The rendering we were looking for! @@ -305,14 +332,14 @@ class AndroidDispatchersRenderWorkflowInTest( val renderings = renderWorkflowIn( workflow = workflow, scope = backgroundScope + - ANDROID_WORKFLOW_RUNTIME_CONTEXT, + runtimeContext, props = MutableStateFlow(Unit).asStateFlow(), runtimeConfig = runtime.runtimeConfig, workflowTracer = null, interceptors = emptyList() ) {} - val collectionJob = launch(AndroidUiDispatcher.Main) { + val collectionJob = launch(runtimeContext) { renderings.collect { if (it.name == "neverends+neverends") { // The rendering we were looking for after the event! @@ -394,7 +421,7 @@ class AndroidDispatchersRenderWorkflowInTest( val renderings = renderWorkflowIn( workflow = workflow, scope = backgroundScope + - AndroidUiDispatcher.Main, + runtimeContext, props = props, runtimeConfig = runtime.runtimeConfig, workflowTracer = null, @@ -404,7 +431,7 @@ class AndroidDispatchersRenderWorkflowInTest( val targetRenderingReceived = Mutex(locked = true) val frameCallbackComplete = Mutex(locked = true) - val collectionJob = launch(AndroidUiDispatcher.Main) { + val collectionJob = launch(runtimeContext) { renderings.collect { renderingsConsumed += it if (it == targetRendering) { @@ -415,7 +442,7 @@ class AndroidDispatchersRenderWorkflowInTest( } } - launch(AndroidUiDispatcher.Main) { + launch(runtimeContext) { Choreographers.postOnFrameRendered { // We are expecting this to happen last, after we get the rendering! expectInOrder(1) @@ -432,27 +459,30 @@ class AndroidDispatchersRenderWorkflowInTest( assertEquals( expected = expectedRenderPasses, actual = renderPasses, - message = "Expected $expectedRenderPasses render passes for config ${runtime.runtimeConfig}." - ) - assertEquals( - expected = expectedRenderingsConsumed, - actual = renderingsConsumed.size, - message = "Expected $expectedRenderingsConsumed consumed renderings for config" + - " ${runtime.runtimeConfig}." + message = "Expected $expectedRenderPasses render passes for config ${runtime.runtimeConfig}" + + "; Dispatcher: ${dispatcher.name}." ) assertEquals( expected = expectedRenderingsProduced, actual = renderingsProduced, message = "Expected $expectedRenderingsProduced renderings to be produced" + - " (passed signal to interceptor) for config ${runtime.runtimeConfig}." + " (passed signal to interceptor) for config ${runtime.runtimeConfig};" + + "Dispatcher: ${dispatcher.name}." + ) + assertEquals( + expected = expectedRenderingsConsumed, + actual = renderingsConsumed.size, + message = "Expected $expectedRenderingsConsumed consumed renderings for config" + + " ${runtime.runtimeConfig}; Dispatcher: ${dispatcher.name}." ) assertEquals( expected = targetRendering, actual = renderingsConsumed.last() ) } +} - companion object { - val ANDROID_WORKFLOW_RUNTIME_CONTEXT: CoroutineContext = AndroidUiDispatcher.Main - } +enum class RuntimeDispatcher { + IMMEDIATE, + COMPOSE_UI } diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/RenderWorkflow.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/RenderWorkflow.kt index ec047c1e93..a635d2c8b4 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/RenderWorkflow.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/RenderWorkflow.kt @@ -4,10 +4,9 @@ import com.squareup.workflow1.RuntimeConfigOptions.CONFLATE_STALE_RENDERINGS import com.squareup.workflow1.RuntimeConfigOptions.DRAIN_EXCLUSIVE_ACTIONS import com.squareup.workflow1.RuntimeConfigOptions.RENDER_ONLY_WHEN_STATE_CHANGES import com.squareup.workflow1.WorkflowInterceptor.RenderPassSkipped -import com.squareup.workflow1.WorkflowInterceptor.RenderPassesComplete -import com.squareup.workflow1.internal.WorkStealingDispatcher import com.squareup.workflow1.WorkflowInterceptor.RenderingConflated import com.squareup.workflow1.WorkflowInterceptor.RenderingProduced +import com.squareup.workflow1.internal.WorkStealingDispatcher import com.squareup.workflow1.internal.WorkflowRunner import com.squareup.workflow1.internal.chained import kotlinx.coroutines.CancellationException @@ -213,6 +212,16 @@ public fun renderWorkflowIn( !actionResult.stateChanged } + /** + * We advance the dispatcher first to allow any coroutines that were launched by the last + * render pass to start up and potentially enqueue actions. + */ + fun WorkStealingDispatcher.drainTasksIfPossible() { + workflowTracer.trace("AdvancingWorkflowDispatcher") { + advanceUntilIdle() + } + } + scope.launch { outer@ while (isActive) { // It might look weird to start by waiting for an action before getting the rendering below, @@ -239,6 +248,7 @@ public fun renderWorkflowIn( actionDrainingHasChangedState = actionDrainingHasChangedState || drainingActionResult.stateChanged + dispatcher?.drainTasksIfPossible() drainingActionResult = runner.applyNextAvailableTreeAction(skipDirtyNodes = true) // If no actions processed, then we can't apply any more actions. @@ -261,13 +271,8 @@ public fun renderWorkflowIn( actionDrainingHasChangedState || actionResult.stateChanged // We may have more actions we can process, this rendering could be stale. // This will check for any actions that are immediately available and apply them. - // We advance the dispatcher first to allow any coroutines that were launched by the last - // render pass to start up and potentially enqueue actions. - dispatcher?.let { - workflowTracer.trace("AdvancingWorkflowDispatcher") { - dispatcher.advanceUntilIdle() - } - } + + dispatcher?.drainTasksIfPossible() actionResult = runner.applyNextAvailableTreeAction() // If no actions processed, then no new rendering needed. Pass on to UI. diff --git a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/RenderWorkflowInTest.kt b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/RenderWorkflowInTest.kt index 9d01b4281d..aa9c05f9d7 100644 --- a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/RenderWorkflowInTest.kt +++ b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/RenderWorkflowInTest.kt @@ -3,7 +3,7 @@ package com.squareup.workflow1 import app.cash.burst.Burst import com.squareup.workflow1.RuntimeConfigOptions.CONFLATE_STALE_RENDERINGS import com.squareup.workflow1.RuntimeConfigOptions.Companion.RuntimeOptions -import com.squareup.workflow1.RuntimeConfigOptions.Companion.RuntimeOptions.DEFAULT +import com.squareup.workflow1.RuntimeConfigOptions.Companion.RuntimeOptions.NONE import com.squareup.workflow1.RuntimeConfigOptions.DRAIN_EXCLUSIVE_ACTIONS import com.squareup.workflow1.RuntimeConfigOptions.PARTIAL_TREE_RENDERING import com.squareup.workflow1.RuntimeConfigOptions.RENDER_ONLY_WHEN_STATE_CHANGES @@ -54,7 +54,7 @@ import kotlin.test.assertTrue class RenderWorkflowInTest( useTracer: Boolean = false, private val useUnconfined: Boolean = true, - private val runtime: RuntimeOptions = DEFAULT + private val runtime: RuntimeOptions = NONE ) { private val runtimeConfig = runtime.runtimeConfig @@ -290,7 +290,7 @@ class RenderWorkflowInTest( } @Test fun saves_to_and_restores_from_snapshot( - runtime2: RuntimeOptions = DEFAULT + runtime2: RuntimeOptions = NONE ) = runTest(dispatcherUsed) { val workflow = Workflow.stateful Unit>>( initialState = { _, snapshot -> @@ -564,7 +564,7 @@ class RenderWorkflowInTest( } @Test fun tracer_includes_expected_sections() { - if (runtime == DEFAULT && testTracer != null) { + if (runtime == NONE && testTracer != null) { runTest(UnconfinedTestDispatcher()) { // Only test default so we only have one 'golden value' to assert against. // We are only testing the tracer correctness here, which should be agnostic of runtime. diff --git a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/WorkflowRunnerTest.kt b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/WorkflowRunnerTest.kt index 0097939327..d31a25b69d 100644 --- a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/WorkflowRunnerTest.kt +++ b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/WorkflowRunnerTest.kt @@ -6,7 +6,7 @@ import com.squareup.workflow1.NoopWorkflowInterceptor import com.squareup.workflow1.RuntimeConfig import com.squareup.workflow1.RuntimeConfigOptions import com.squareup.workflow1.RuntimeConfigOptions.Companion.RuntimeOptions -import com.squareup.workflow1.RuntimeConfigOptions.Companion.RuntimeOptions.DEFAULT +import com.squareup.workflow1.RuntimeConfigOptions.Companion.RuntimeOptions.NONE import com.squareup.workflow1.Worker import com.squareup.workflow1.Workflow import com.squareup.workflow1.WorkflowExperimentalRuntime @@ -35,7 +35,7 @@ import kotlin.test.assertTrue @OptIn(ExperimentalCoroutinesApi::class, WorkflowExperimentalRuntime::class) @Burst internal class WorkflowRunnerTest( - runtime: RuntimeOptions = DEFAULT + runtime: RuntimeOptions = NONE ) { private lateinit var scope: TestScope diff --git a/workflow-testing/src/test/java/com/squareup/workflow1/WorkflowsLifecycleTests.kt b/workflow-testing/src/test/java/com/squareup/workflow1/WorkflowsLifecycleTests.kt index c45b4393d6..c4efa65506 100644 --- a/workflow-testing/src/test/java/com/squareup/workflow1/WorkflowsLifecycleTests.kt +++ b/workflow-testing/src/test/java/com/squareup/workflow1/WorkflowsLifecycleTests.kt @@ -5,7 +5,7 @@ package com.squareup.workflow1 import app.cash.burst.Burst import com.squareup.workflow1.RuntimeConfigOptions.CONFLATE_STALE_RENDERINGS import com.squareup.workflow1.RuntimeConfigOptions.Companion.RuntimeOptions -import com.squareup.workflow1.RuntimeConfigOptions.Companion.RuntimeOptions.DEFAULT +import com.squareup.workflow1.RuntimeConfigOptions.Companion.RuntimeOptions.NONE import com.squareup.workflow1.RuntimeConfigOptions.DRAIN_EXCLUSIVE_ACTIONS import com.squareup.workflow1.testing.headlessIntegrationTest import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -22,7 +22,7 @@ import kotlin.test.assertEquals @OptIn(WorkflowExperimentalRuntime::class, WorkflowExperimentalApi::class) @Burst class WorkflowsLifecycleTests( - private val runtime: RuntimeOptions = DEFAULT + private val runtime: RuntimeOptions = NONE ) { private val runtimeConfig = runtime.runtimeConfig