Skip to content

Commit 8f303c5

Browse files
1247: Partial Tree Rerendering
If PARTIAL_TREE_RENDERING is in the RuntimeConfig then, Track whether or not the state (or state of a descendant) changed in the WorkflowNode. Pass lastRendering if its not. Also adds tests for this behavior and expands the test runtime matrix. Also adds shards for the new runtime for instrumentation tests.
1 parent ce54070 commit 8f303c5

File tree

15 files changed

+391
-28
lines changed

15 files changed

+391
-28
lines changed

.github/workflows/kotlin.yml

+46-2
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,27 @@ jobs :
222222
with :
223223
report_paths : '**/build/test-results/test/TEST-*.xml'
224224

225+
jvm-partial-runtime-test:
226+
name: Partial Tree Rendering Only Runtime JVM Tests
227+
runs-on: ubuntu-latest
228+
timeout-minutes: 20
229+
steps:
230+
- name: Checkout
231+
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
232+
233+
- name: Check with Gradle
234+
uses: ./.github/actions/gradle-task
235+
with:
236+
task: jvmTest --continue -Pworkflow.runtime=baseline-partial
237+
restore-cache-key: main-build-artifacts
238+
239+
# Report as GitHub Pull Request Check.
240+
- name: Publish Test Report
241+
uses: mikepenz/action-junit-report@5f47764eec0e1c1f19f40c8e60a5ba47e47015c5 # v4
242+
if: always() # always run even if the previous step fails
243+
with:
244+
report_paths: '**/build/test-results/test/TEST-*.xml'
245+
225246
jvm-conflate-stateChange-runtime-test :
226247
name : Render On State Change Only and Conflate Stale Runtime JVM Tests
227248
runs-on : ubuntu-latest
@@ -243,6 +264,27 @@ jobs :
243264
with :
244265
report_paths : '**/build/test-results/test/TEST-*.xml'
245266

267+
jvm-conflate-partial-runtime-test:
268+
name: Render On State Change Only and Conflate Stale Runtime JVM Tests
269+
runs-on: ubuntu-latest
270+
timeout-minutes: 20
271+
steps:
272+
- name: Checkout
273+
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
274+
275+
- name: Check with Gradle
276+
uses: ./.github/actions/gradle-task
277+
with:
278+
task: jvmTest --continue -Pworkflow.runtime=conflate-partial
279+
restore-cache-key: main-build-artifacts
280+
281+
# Report as GitHub Pull Request Check.
282+
- name: Publish Test Report
283+
uses: mikepenz/action-junit-report@5f47764eec0e1c1f19f40c8e60a5ba47e47015c5 # v4
284+
if: always() # always run even if the previous step fails
285+
with:
286+
report_paths: '**/build/test-results/test/TEST-*.xml'
287+
246288
ios-tests :
247289
name : iOS Tests
248290
runs-on : macos-latest
@@ -349,7 +391,7 @@ jobs :
349391
### <start-connected-check-shards>
350392
shardNum: [ 1, 2, 3 ]
351393
### <end-connected-check-shards>
352-
runtime : [ conflate, baseline-stateChange, conflate-stateChange ]
394+
runtime : [ conflate, baseline-stateChange, conflate-stateChange, baseline-partial, conflate-partial ]
353395
steps :
354396
- name: Checkout
355397
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
@@ -378,8 +420,10 @@ jobs :
378420
- ios-tests
379421
- js-tests
380422
- jvm-conflate-runtime-test
381-
- jvm-conflate-stateChange-runtime-test
382423
- jvm-stateChange-runtime-test
424+
- jvm-partial-runtime-test
425+
- jvm-conflate-stateChange-runtime-test
426+
- jvm-conflate-partial-runtime-test
383427
- ktlint
384428
- performance-tests
385429
- runtime-instrumentation-tests

workflow-config/config-android/src/main/java/com/squareup/workflow1/config/AndroidRuntimeConfigTools.kt

+10
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.squareup.workflow1.config
33
import com.squareup.workflow1.RuntimeConfig
44
import com.squareup.workflow1.RuntimeConfigOptions
55
import com.squareup.workflow1.RuntimeConfigOptions.CONFLATE_STALE_RENDERINGS
6+
import com.squareup.workflow1.RuntimeConfigOptions.PARTIAL_TREE_RENDERING
67
import com.squareup.workflow1.RuntimeConfigOptions.RENDER_ONLY_WHEN_STATE_CHANGES
78
import com.squareup.workflow1.WorkflowExperimentalRuntime
89

@@ -27,6 +28,9 @@ public class AndroidRuntimeConfigTools {
2728
* Then, these can be combined (via '-') with:
2829
* "stateChange" : Only re-render when the state of some WorkflowNode has been changed by an
2930
* action cascade.
31+
* "partial" : Which includes "stateChange" as well as partial tree rendering, which only
32+
* re-renders each Workflow node if: 1) its state changed; or 2) one of its descendant's state
33+
* changed.
3034
*
3135
* E.g., "baseline-stateChange" to turn on the stateChange option with the baseline runtime.
3236
*
@@ -37,6 +41,12 @@ public class AndroidRuntimeConfigTools {
3741
"conflate" -> setOf(CONFLATE_STALE_RENDERINGS)
3842
"conflate-stateChange" -> setOf(CONFLATE_STALE_RENDERINGS, RENDER_ONLY_WHEN_STATE_CHANGES)
3943
"baseline-stateChange" -> setOf(RENDER_ONLY_WHEN_STATE_CHANGES)
44+
"conflate-partial" -> setOf(
45+
CONFLATE_STALE_RENDERINGS,
46+
RENDER_ONLY_WHEN_STATE_CHANGES,
47+
PARTIAL_TREE_RENDERING
48+
)
49+
"baseline-partial" -> setOf(RENDER_ONLY_WHEN_STATE_CHANGES, PARTIAL_TREE_RENDERING)
4050
"", "baseline" -> RuntimeConfigOptions.RENDER_PER_ACTION
4151
else ->
4252
throw IllegalArgumentException("Unrecognized config \"${BuildConfig.WORKFLOW_RUNTIME}\"")

workflow-config/config-jvm/src/main/java/com/squareup/workflow1/config/JvmTestRuntimeConfigTools.kt

+10
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.squareup.workflow1.config
33
import com.squareup.workflow1.RuntimeConfig
44
import com.squareup.workflow1.RuntimeConfigOptions
55
import com.squareup.workflow1.RuntimeConfigOptions.CONFLATE_STALE_RENDERINGS
6+
import com.squareup.workflow1.RuntimeConfigOptions.PARTIAL_TREE_RENDERING
67
import com.squareup.workflow1.RuntimeConfigOptions.RENDER_ONLY_WHEN_STATE_CHANGES
78
import com.squareup.workflow1.WorkflowExperimentalRuntime
89

@@ -27,6 +28,9 @@ public class JvmTestRuntimeConfigTools {
2728
* Then, these can be combined (via '-') with:
2829
* "stateChange" : Only re-render when the state of some WorkflowNode has been changed by an
2930
* action cascade.
31+
* "partial" : Which includes "stateChange" as well as partial tree rendering, which only
32+
* re-renders each Workflow node if: 1) its state changed; or 2) one of its descendant's state
33+
* changed.
3034
*
3135
* E.g., "baseline-stateChange" to turn on the stateChange option with the baseline runtime.
3236
*
@@ -38,6 +42,12 @@ public class JvmTestRuntimeConfigTools {
3842
"conflate" -> setOf(CONFLATE_STALE_RENDERINGS)
3943
"conflate-stateChange" -> setOf(CONFLATE_STALE_RENDERINGS, RENDER_ONLY_WHEN_STATE_CHANGES)
4044
"baseline-stateChange" -> setOf(RENDER_ONLY_WHEN_STATE_CHANGES)
45+
"conflate-partial" -> setOf(
46+
CONFLATE_STALE_RENDERINGS,
47+
RENDER_ONLY_WHEN_STATE_CHANGES,
48+
PARTIAL_TREE_RENDERING
49+
)
50+
"baseline-partial" -> setOf(RENDER_ONLY_WHEN_STATE_CHANGES, PARTIAL_TREE_RENDERING)
4151
"", "baseline" -> RuntimeConfigOptions.RENDER_PER_ACTION
4252
else ->
4353
throw IllegalArgumentException("Unrecognized config \"$runtimeConfig\"")

workflow-core/api/workflow-core.api

+20
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,26 @@ public abstract class com/squareup/workflow1/LifecycleWorker : com/squareup/work
7777
public final fun run ()Lkotlinx/coroutines/flow/Flow;
7878
}
7979

80+
public final class com/squareup/workflow1/NullableInitBox {
81+
public static final synthetic fun box-impl (Ljava/lang/Object;)Lcom/squareup/workflow1/NullableInitBox;
82+
public static fun constructor-impl (Ljava/lang/Object;)Ljava/lang/Object;
83+
public static synthetic fun constructor-impl$default (Ljava/lang/Object;ILkotlin/jvm/internal/DefaultConstructorMarker;)Ljava/lang/Object;
84+
public fun equals (Ljava/lang/Object;)Z
85+
public static fun equals-impl (Ljava/lang/Object;Ljava/lang/Object;)Z
86+
public static final fun equals-impl0 (Ljava/lang/Object;Ljava/lang/Object;)Z
87+
public static final fun getOrThrow-impl (Ljava/lang/Object;)Ljava/lang/Object;
88+
public fun hashCode ()I
89+
public static fun hashCode-impl (Ljava/lang/Object;)I
90+
public static final fun isInitialized-impl (Ljava/lang/Object;)Z
91+
public fun toString ()Ljava/lang/String;
92+
public static fun toString-impl (Ljava/lang/Object;)Ljava/lang/String;
93+
public final synthetic fun unbox-impl ()Ljava/lang/Object;
94+
}
95+
96+
public final class com/squareup/workflow1/NullableInitBox$Uninitialized {
97+
public static final field INSTANCE Lcom/squareup/workflow1/NullableInitBox$Uninitialized;
98+
}
99+
80100
public final class com/squareup/workflow1/PropsUpdated : com/squareup/workflow1/ActionProcessingResult {
81101
public static final field INSTANCE Lcom/squareup/workflow1/PropsUpdated;
82102
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.squareup.workflow1
2+
3+
import kotlin.jvm.JvmInline
4+
5+
/**
6+
* Used to wrap immutable nullable values whose holder may not yet be initialized.
7+
* Check [isInitialized] to see if the value has been assigned.
8+
*/
9+
@JvmInline
10+
public value class NullableInitBox<T>(private val _value: Any? = Uninitialized) {
11+
/**
12+
* Whether or not a value has been set for this [NullableInitBox]
13+
*/
14+
public val isInitialized: Boolean get() = _value !== Uninitialized
15+
16+
/**
17+
* Get the value this has been initialized with.
18+
*
19+
* @throws [IllegalStateException] if the value in the box has not been initialized.
20+
*/
21+
@Suppress("UNCHECKED_CAST")
22+
public fun getOrThrow(): T {
23+
check(isInitialized) { "NullableInitBox was fetched before it was initialized with a value." }
24+
return _value as T
25+
}
26+
27+
public object Uninitialized
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.squareup.workflow1
2+
3+
import kotlin.test.Test
4+
import kotlin.test.assertEquals
5+
import kotlin.test.assertFailsWith
6+
import kotlin.test.assertFalse
7+
import kotlin.test.assertTrue
8+
9+
class NullableInitBoxTest {
10+
11+
@Test fun reports_not_initialized() {
12+
val box = NullableInitBox<String>()
13+
14+
assertFalse(box.isInitialized)
15+
}
16+
17+
@Test fun reports_initialized() {
18+
val box = NullableInitBox<String>("Hello")
19+
20+
assertTrue(box.isInitialized)
21+
}
22+
23+
@Test fun returns_value() {
24+
val box = NullableInitBox<String>("Hello")
25+
26+
assertEquals("Hello", box.getOrThrow())
27+
}
28+
29+
@Test fun throws_exceptions() {
30+
val box = NullableInitBox<String>()
31+
32+
val exception = assertFailsWith<IllegalStateException> {
33+
box.getOrThrow()
34+
}
35+
36+
assertEquals(
37+
"NullableInitBox was fetched before it was initialized with a value.",
38+
exception.message
39+
)
40+
}
41+
}

workflow-core/src/commonTest/kotlin/com/squareup/workflow1/WorkerTest.kt

-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import kotlin.test.assertNotSame
1616
import kotlin.test.assertTrue
1717

1818
@ExperimentalCoroutinesApi
19-
@OptIn(ExperimentalStdlibApi::class)
2019
class WorkerTest {
2120

2221
@Test fun timer_returns_equivalent_workers_keyed() {

workflow-core/src/commonTest/kotlin/com/squareup/workflow1/WorkflowIdentifierTest.kt

-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import kotlin.test.assertFailsWith
1212
import kotlin.test.assertNotEquals
1313
import kotlin.test.assertNull
1414

15-
@OptIn(ExperimentalStdlibApi::class)
1615
internal class WorkflowIdentifierTest {
1716

1817
@Test fun restored_identifier_toString() {

workflow-runtime/api/workflow-runtime.api

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public final class com/squareup/workflow1/RenderingAndSnapshot {
2525
public final class com/squareup/workflow1/RuntimeConfigOptions : java/lang/Enum {
2626
public static final field CONFLATE_STALE_RENDERINGS Lcom/squareup/workflow1/RuntimeConfigOptions;
2727
public static final field Companion Lcom/squareup/workflow1/RuntimeConfigOptions$Companion;
28+
public static final field PARTIAL_TREE_RENDERING Lcom/squareup/workflow1/RuntimeConfigOptions;
2829
public static final field RENDER_ONLY_WHEN_STATE_CHANGES Lcom/squareup/workflow1/RuntimeConfigOptions;
2930
public static fun getEntries ()Lkotlin/enums/EnumEntries;
3031
public static fun valueOf (Ljava/lang/String;)Lcom/squareup/workflow1/RuntimeConfigOptions;

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

+16
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,22 @@ public enum class RuntimeConfigOptions {
3838
@WorkflowExperimentalRuntime
3939
RENDER_ONLY_WHEN_STATE_CHANGES,
4040

41+
/**
42+
* Only re-render each active Workflow node if:
43+
* 1. It's own state changed, OR
44+
* 2. One of it's descendant's state has changed.
45+
*
46+
* Otherwise return the cached rendering (as there is no way it could have changed).
47+
*
48+
* Note however that you must be careful using this because there may be external
49+
* state that your Workflow's draw in and re-render and if that is not explicitly
50+
* tracked within that Workflow's state, then it will not re-render. In this case,
51+
* make sure that the state is tracked within the Workflow's state (even through
52+
* an artificial token) in some way.
53+
*/
54+
@WorkflowExperimentalRuntime
55+
PARTIAL_TREE_RENDERING,
56+
4157
/**
4258
* If we have more actions to process, do so before passing the rendering to the UI layer.
4359
*/

0 commit comments

Comments
 (0)