Skip to content

DNM: Temp version to publish snapshot. #1295

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 127 additions & 1 deletion .github/workflows/kotlin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,27 @@ jobs :
build-root-directory : samples/tutorial
restore-cache-key : main-build-artifacts

jvm-drainExclusive-runtime-test :
name : Conflate Stale Renderings Runtime 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=drainExclusive
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/test/TEST-*.xml'

jvm-conflate-runtime-test :
name : Conflate Stale Renderings Runtime JVM Tests
runs-on : ubuntu-latest
Expand Down Expand Up @@ -306,6 +327,111 @@ jobs :
with:
report_paths: '**/build/test-results/test/TEST-*.xml'

jvm-conflate-drainExclusive-runtime-test:
name: Conflate Stale Renderings Runtime 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=conflate-drainExclusive
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/test/TEST-*.xml'

jvm-stateChange-drainExclusive-runtime-test:
name: Render On State Change Only Runtime 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=stateChange-drainExclusive
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/test/TEST-*.xml'

jvm-partial-drainExclusive-runtime-test:
name: Partial Tree Rendering Only Runtime 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=partial-drainExclusive
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/test/TEST-*.xml'

jvm-conflate-stateChange-drainExclusive-runtime-test:
name: Render On State Change Only and Conflate Stale Runtime 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=conflate-stateChange-drainExclusive
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/test/TEST-*.xml'

jvm-conflate-partial-drainExclusive-runtime-test:
name: Render On State Change Only and Conflate Stale Runtime 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=conflate-partial-drainExclusive
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/test/TEST-*.xml'

ios-tests :
name : iOS Tests
runs-on : macos-latest
Expand Down Expand Up @@ -412,7 +538,7 @@ jobs :
### <start-connected-check-shards>
shardNum: [ 1, 2, 3 ]
### <end-connected-check-shards>
runtime : [ conflate, stateChange, conflate-stateChange, partial, conflate-partial, stable ]
runtime : [ conflate, stateChange, drainExclusive, conflate-stateChange, partial, conflate-partial, stable, conflate-drainExclusive, stateChange-drainExclusive, partial-drainExclusive, conflate-partial-drainExclusive ]
steps :
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ android.useAndroidX=true
systemProp.org.gradle.internal.publish.checksums.insecure=true

GROUP=com.squareup.workflow1
VERSION_NAME=1.18.0-SNAPSHOT
VERSION_NAME=1.18.0-DEA-SNAPSHOT

POM_DESCRIPTION=Square Workflow

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.squareup.workflow1.config
import com.squareup.workflow1.RuntimeConfig
import com.squareup.workflow1.RuntimeConfigOptions
import com.squareup.workflow1.RuntimeConfigOptions.CONFLATE_STALE_RENDERINGS
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
Expand Down Expand Up @@ -33,6 +34,10 @@ public class AndroidRuntimeConfigTools {
*
* - `stable` Enables stable event handlers (changes the default value of the `remember`
* parameter of `RenderContext.eventHandler` functions from `false` to `true`)
*
* - `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.
*/
@WorkflowExperimentalRuntime
public fun getAppWorkflowRuntimeConfig(): RuntimeConfig {
Expand All @@ -48,6 +53,7 @@ public class AndroidRuntimeConfigTools {
"stateChange" -> config.add(RENDER_ONLY_WHEN_STATE_CHANGES)
"partial" -> config.addAll(setOf(RENDER_ONLY_WHEN_STATE_CHANGES, PARTIAL_TREE_RENDERING))
"stable" -> config.add(STABLE_EVENT_HANDLERS)
"drainExclusive" -> config.add(DRAIN_EXCLUSIVE_ACTIONS)
else -> throw IllegalArgumentException("Unrecognized runtime config option \"$it\"")
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.squareup.workflow1.config
import com.squareup.workflow1.RuntimeConfig
import com.squareup.workflow1.RuntimeConfigOptions
import com.squareup.workflow1.RuntimeConfigOptions.CONFLATE_STALE_RENDERINGS
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
Expand Down Expand Up @@ -35,6 +36,10 @@ public class JvmTestRuntimeConfigTools {
*
* - `stable` Enables stable event handlers (changes the default value of the `remember`
* parameter of `RenderContext.eventHandler` functions from `false` to `true`)
*
* - `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.
*/
@OptIn(WorkflowExperimentalRuntime::class)
public fun getTestRuntimeConfig(): RuntimeConfig {
Expand All @@ -50,6 +55,7 @@ public class JvmTestRuntimeConfigTools {
"stateChange" -> config.add(RENDER_ONLY_WHEN_STATE_CHANGES)
"partial" -> config.addAll(setOf(RENDER_ONLY_WHEN_STATE_CHANGES, PARTIAL_TREE_RENDERING))
"stable" -> config.add(STABLE_EVENT_HANDLERS)
"drainExclusive" -> config.add(DRAIN_EXCLUSIVE_ACTIONS)
else -> throw IllegalArgumentException("Unrecognized runtime config option \"$it\"")
}
}
Expand Down
1 change: 1 addition & 0 deletions workflow-core/api/workflow-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ public final class com/squareup/workflow1/PropsUpdated : com/squareup/workflow1/
public final class com/squareup/workflow1/RuntimeConfigOptions : java/lang/Enum {
public static final field CONFLATE_STALE_RENDERINGS Lcom/squareup/workflow1/RuntimeConfigOptions;
public static final field Companion Lcom/squareup/workflow1/RuntimeConfigOptions$Companion;
public static final field DRAIN_EXCLUSIVE_ACTIONS Lcom/squareup/workflow1/RuntimeConfigOptions;
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ public enum class RuntimeConfigOptions {
*/
@WorkflowExperimentalRuntime
STABLE_EVENT_HANDLERS,

/**
* 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.
*/
@WorkflowExperimentalRuntime
DRAIN_EXCLUSIVE_ACTIONS,
;

public companion object {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.squareup.workflow1

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
Expand All @@ -14,6 +15,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.yield

/**
* Launches the [workflow] in a new coroutine in [scope] and returns a [StateFlow] of its
Expand Down Expand Up @@ -167,13 +169,18 @@ public fun <PropsT, OutputT, RenderingT> renderWorkflowIn(
/**
* If [runtimeConfig] contains [RuntimeConfigOptions.RENDER_ONLY_WHEN_STATE_CHANGES] and
* we have not changed state, then return true to short circuit the render loop.
*
* @param actionResult: Whether this action has changed state.
* @param actionChainingHasChangedState: Whether any of the chained actions has changed state.
*/
fun shouldShortCircuitForUnchangedState(
actionResult: ActionProcessingResult,
actionChainingHasChangedState: Boolean = false
): Boolean {
return runtimeConfig.contains(RENDER_ONLY_WHEN_STATE_CHANGES) &&
actionResult is ActionApplied<*> &&
!actionResult.stateChanged
!actionResult.stateChanged &&
!actionChainingHasChangedState
}

scope.launch {
Expand All @@ -193,13 +200,53 @@ public fun <PropsT, OutputT, RenderingT> renderWorkflowIn(
// we don't surprise anyone with an unexpected rendering pass. Show's over, go home.
if (!isActive) return@launch

var drainingActionResult = actionResult
var actionChainingHasChangedState = false
if (runtimeConfig.contains(DRAIN_EXCLUSIVE_ACTIONS)) {
drain@ while (isActive &&
drainingActionResult is ActionApplied<*> &&
drainingActionResult.output == null
) {
actionChainingHasChangedState =
actionChainingHasChangedState || drainingActionResult.stateChanged
// We start by yielding, so that other coroutines can run if we are running on an
// immediate dispatcher with its own inner loop.
yield()
// We may have more mutually exclusive actions we can apply before a render pass.
drainingActionResult =
runner.processAction(waitForAnAction = false, skipChangedNodes = true)

// If no mutually exclusive actions processed, then go ahead and do the render pass.
if (drainingActionResult == ActionsExhausted) break@drain

// Now save last result if its not ActionsExhausted
actionResult = drainingActionResult

// If no state changed, send any output and start outer loop again.
if (shouldShortCircuitForUnchangedState(
actionResult = drainingActionResult,
actionChainingHasChangedState = actionChainingHasChangedState
)
) {
chainedInterceptor.onRuntimeLoopTick(RenderPassSkipped(endOfTick = true))
sendOutput(drainingActionResult, onOutput)
continue@outer
}
}
}

// Next Render Pass.
var nextRenderAndSnapshot: RenderingAndSnapshot<RenderingT> = runner.nextRendering()

if (runtimeConfig.contains(CONFLATE_STALE_RENDERINGS)) {
var conflationHasChangedState = false
conflate@ while (isActive && actionResult is ActionApplied<*> && actionResult.output == null) {
conflationHasChangedState = conflationHasChangedState || actionResult.stateChanged
conflate@ while (isActive &&
actionResult is ActionApplied<*> &&
actionResult.output == null
) {
actionChainingHasChangedState = actionChainingHasChangedState || actionResult.stateChanged
// We start by yielding, so that other coroutines can run if we are running on an
// immediate dispatcher with its own inner loop.
yield()
// We may have more actions we can process, this rendering could be stale.
actionResult = runner.processAction(waitForAnAction = false)

Expand All @@ -211,7 +258,7 @@ public fun <PropsT, OutputT, RenderingT> renderWorkflowIn(
actionResult = actionResult,
)
) {
if (conflationHasChangedState) {
if (actionChainingHasChangedState) {
chainedInterceptor.onRuntimeLoopTick(RenderPassSkipped(endOfTick = false))
// An earlier render changed state, so we need to pass that to the UI then we
// can skip this render.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,11 +150,14 @@ internal class SubtreeManager<PropsT, StateT, OutputT>(
*
* @return [Boolean] whether or not the children action queues are empty.
*/
fun onNextChildAction(selector: SelectBuilder<ActionProcessingResult>): Boolean {
fun onNextChildAction(
selector: SelectBuilder<ActionProcessingResult>,
skipChangedNodes: Boolean = false
): Boolean {
var empty = true
children.forEachActive { child ->
// Do this separately so the compiler doesn't avoid it if empty is already false.
val childEmpty = child.workflowNode.onNextAction(selector)
val childEmpty = child.workflowNode.onNextAction(selector, skipChangedNodes)
empty = childEmpty && empty
}
return empty
Expand Down
Loading
Loading