Skip to content

Commit cca6c00

Browse files
Cache WorkflowIdentifier
1 parent a39c288 commit cca6c00

File tree

13 files changed

+92
-23
lines changed

13 files changed

+92
-23
lines changed

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ android.useAndroidX=true
88
systemProp.org.gradle.internal.publish.checksums.insecure=true
99

1010
GROUP=com.squareup.workflow1
11-
VERSION_NAME=1.8.0-beta13-SNAPSHOT
11+
VERSION_NAME=1.8.0-beta13-cache-SNAPSHOT
1212

1313
POM_DESCRIPTION=Square Workflow
1414

samples/compose-samples/src/main/java/com/squareup/sample/compose/hellocomposeworkflow/ComposeWorkflow.kt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
package com.squareup.sample.compose.hellocomposeworkflow
22

33
import androidx.compose.runtime.Composable
4+
import com.squareup.workflow1.IdCacheable
45
import com.squareup.workflow1.RenderContext
56
import com.squareup.workflow1.Sink
67
import com.squareup.workflow1.StatefulWorkflow
78
import com.squareup.workflow1.Workflow
9+
import com.squareup.workflow1.WorkflowIdentifier
10+
import com.squareup.workflow1.computeIdentifier
811
import com.squareup.workflow1.ui.ViewEnvironment
912
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
1013
import com.squareup.workflow1.ui.compose.ComposeScreen
14+
import kotlin.LazyThreadSafetyMode.NONE
1115

1216
/**
1317
* A stateless [Workflow] that [renders][RenderingContent] itself as a [Composable] function.
@@ -29,7 +33,7 @@ import com.squareup.workflow1.ui.compose.ComposeScreen
2933
*/
3034
@WorkflowUiExperimentalApi
3135
abstract class ComposeWorkflow<in PropsT, out OutputT : Any> :
32-
Workflow<PropsT, OutputT, ComposeScreen> {
36+
Workflow<PropsT, OutputT, ComposeScreen>, IdCacheable {
3337

3438
/**
3539
* Renders [props] by emitting Compose UI. This function will be called to update the UI whenever
@@ -48,6 +52,12 @@ abstract class ComposeWorkflow<in PropsT, out OutputT : Any> :
4852

4953
override fun asStatefulWorkflow(): StatefulWorkflow<PropsT, *, OutputT, ComposeScreen> =
5054
ComposeWorkflowImpl(this)
55+
56+
override val cachedIdentifier: WorkflowIdentifier by lazy(
57+
mode = NONE
58+
) {
59+
computeIdentifier()
60+
}
5161
}
5262

5363
/**

workflow-core/api/workflow-core.api

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ public final class com/squareup/workflow1/BaseRenderContext$DefaultImpls {
4848
public static synthetic fun renderChild$default (Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/lang/Object;
4949
}
5050

51+
public abstract interface class com/squareup/workflow1/IdCacheable {
52+
public abstract fun getCachedIdentifier ()Lcom/squareup/workflow1/WorkflowIdentifier;
53+
}
54+
5155
public abstract interface class com/squareup/workflow1/ImpostorWorkflow {
5256
public abstract fun describeRealIdentifier ()Ljava/lang/String;
5357
public abstract fun getRealIdentifier ()Lcom/squareup/workflow1/WorkflowIdentifier;
@@ -115,9 +119,10 @@ public final class com/squareup/workflow1/Snapshots {
115119
public static final fun writeUtf8WithLength (Lokio/BufferedSink;Ljava/lang/String;)Lokio/BufferedSink;
116120
}
117121

118-
public abstract class com/squareup/workflow1/StatefulWorkflow : com/squareup/workflow1/Workflow {
122+
public abstract class com/squareup/workflow1/StatefulWorkflow : com/squareup/workflow1/IdCacheable, com/squareup/workflow1/Workflow {
119123
public fun <init> ()V
120124
public final fun asStatefulWorkflow ()Lcom/squareup/workflow1/StatefulWorkflow;
125+
public fun getCachedIdentifier ()Lcom/squareup/workflow1/WorkflowIdentifier;
121126
public abstract fun initialState (Ljava/lang/Object;Lcom/squareup/workflow1/Snapshot;)Ljava/lang/Object;
122127
public fun onPropsChanged (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
123128
public abstract fun render (Ljava/lang/Object;Ljava/lang/Object;Lcom/squareup/workflow1/StatefulWorkflow$RenderContext;)Ljava/lang/Object;
@@ -141,9 +146,10 @@ public final class com/squareup/workflow1/StatefulWorkflow$RenderContext : com/s
141146
public fun runningSideEffect (Ljava/lang/String;Lkotlin/jvm/functions/Function2;)V
142147
}
143148

144-
public abstract class com/squareup/workflow1/StatelessWorkflow : com/squareup/workflow1/Workflow {
149+
public abstract class com/squareup/workflow1/StatelessWorkflow : com/squareup/workflow1/IdCacheable, com/squareup/workflow1/Workflow {
145150
public fun <init> ()V
146151
public final fun asStatefulWorkflow ()Lcom/squareup/workflow1/StatefulWorkflow;
152+
public fun getCachedIdentifier ()Lcom/squareup/workflow1/WorkflowIdentifier;
147153
public abstract fun render (Ljava/lang/Object;Lcom/squareup/workflow1/StatelessWorkflow$RenderContext;)Ljava/lang/Object;
148154
}
149155

@@ -283,6 +289,7 @@ public final class com/squareup/workflow1/Workflows {
283289
public static synthetic fun action$default (Lcom/squareup/workflow1/StatelessWorkflow;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/squareup/workflow1/WorkflowAction;
284290
public static synthetic fun action$default (Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/squareup/workflow1/WorkflowAction;
285291
public static final fun applyTo (Lcom/squareup/workflow1/WorkflowAction;Ljava/lang/Object;Ljava/lang/Object;)Lkotlin/Pair;
292+
public static final fun computeIdentifier (Lcom/squareup/workflow1/Workflow;)Lcom/squareup/workflow1/WorkflowIdentifier;
286293
public static final fun contraMap (Lcom/squareup/workflow1/Sink;Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/Sink;
287294
public static final fun getIdentifier (Lcom/squareup/workflow1/Workflow;)Lcom/squareup/workflow1/WorkflowIdentifier;
288295
public static final fun mapRendering (Lcom/squareup/workflow1/Workflow;Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/Workflow;
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.squareup.workflow1
2+
3+
/**
4+
* If your Workflow caches its [WorkflowIdentifier] (to avoid frequent lookups) then implement
5+
* this interface. Note that [StatefulWorkflow] and [StatelessWorkflow] already implement this,
6+
* so you only need to do so if you do not extend one of those classes.
7+
*
8+
* If your Workflow is an [ImpostorWorkflow] use the lazy delegate pattern that [StatefulWorkflow]
9+
* and [StatelessWorkflow] do in order to initialize everything in the proper order.
10+
*/
11+
public interface IdCacheable {
12+
13+
public val cachedIdentifier: WorkflowIdentifier
14+
}

workflow-core/src/commonMain/kotlin/com/squareup/workflow1/ImpostorWorkflow.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import kotlin.jvm.JvmName
1818
public interface ImpostorWorkflow {
1919
/**
2020
* The [WorkflowIdentifier] of another workflow to be combined with the identifier of this
21-
* workflow, as obtained by [Workflow.identifier].
21+
* workflow, as obtained by [Workflow.computeIdentifier].
2222
*
2323
* For workflows that implement operators, this should be the identifier of the upstream
2424
* [Workflow] that this workflow wraps.

workflow-core/src/commonMain/kotlin/com/squareup/workflow1/StatefulWorkflow.kt

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package com.squareup.workflow1
66

77
import com.squareup.workflow1.StatefulWorkflow.RenderContext
88
import com.squareup.workflow1.WorkflowAction.Companion.toString
9+
import kotlin.LazyThreadSafetyMode.NONE
910
import kotlin.jvm.JvmMultifileClass
1011
import kotlin.jvm.JvmName
1112

@@ -69,7 +70,7 @@ public abstract class StatefulWorkflow<
6970
StateT,
7071
out OutputT,
7172
out RenderingT
72-
> : Workflow<PropsT, OutputT, RenderingT> {
73+
> : Workflow<PropsT, OutputT, RenderingT>, IdCacheable {
7374

7475
public inner class RenderContext internal constructor(
7576
baseContext: BaseRenderContext<PropsT, StateT, OutputT>
@@ -129,6 +130,18 @@ public abstract class StatefulWorkflow<
129130
context: RenderContext
130131
): RenderingT
131132

133+
/**
134+
* Use a lazy delegate so that any [ImpostorWorkflow.realIdentifier] will have been computed
135+
* before this is initialized and cached.
136+
*
137+
* We use [LazyThreadSafetyMode.NONE] because access to these identifiers is thread-confined.
138+
*/
139+
override val cachedIdentifier: WorkflowIdentifier by lazy(
140+
mode = NONE
141+
) {
142+
computeIdentifier()
143+
}
144+
132145
/**
133146
* Called whenever the state changes to generate a new [Snapshot] of the state.
134147
*

workflow-core/src/commonMain/kotlin/com/squareup/workflow1/StatelessWorkflow.kt

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
package com.squareup.workflow1
55

6+
import kotlin.LazyThreadSafetyMode.NONE
67
import kotlin.jvm.JvmMultifileClass
78
import kotlin.jvm.JvmName
89

@@ -24,7 +25,7 @@ import kotlin.jvm.JvmName
2425
* @see StatefulWorkflow
2526
*/
2627
public abstract class StatelessWorkflow<in PropsT, out OutputT, out RenderingT> :
27-
Workflow<PropsT, OutputT, RenderingT> {
28+
Workflow<PropsT, OutputT, RenderingT>, IdCacheable {
2829

2930
@Suppress("UNCHECKED_CAST")
3031
public inner class RenderContext internal constructor(
@@ -57,6 +58,18 @@ public abstract class StatelessWorkflow<in PropsT, out OutputT, out RenderingT>
5758
context: RenderContext
5859
): RenderingT
5960

61+
/**
62+
* Use a lazy delegate so that any [ImpostorWorkflow.realIdentifier] will have been computed
63+
* before this is initialized and cached.
64+
*
65+
* We use [LazyThreadSafetyMode.NONE] because access to these identifiers is thread-confined.
66+
*/
67+
override val cachedIdentifier: WorkflowIdentifier by lazy(
68+
mode = NONE
69+
) {
70+
computeIdentifier()
71+
}
72+
6073
/**
6174
* Satisfies the [Workflow] interface by wrapping `this` in a [StatefulWorkflow] with `Unit`
6275
* state.

workflow-core/src/commonMain/kotlin/com/squareup/workflow1/Workflow.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ Workflow<PropsT, OutputT, FromRenderingT>.mapRendering(
120120
transform: (FromRenderingT) -> ToRenderingT
121121
): Workflow<PropsT, OutputT, ToRenderingT> =
122122
object : StatelessWorkflow<PropsT, OutputT, ToRenderingT>(), ImpostorWorkflow {
123-
override val realIdentifier: WorkflowIdentifier get() = this@mapRendering.identifier
123+
override val realIdentifier: WorkflowIdentifier = this@mapRendering.identifier
124124

125125
override fun render(
126126
renderProps: PropsT,

workflow-core/src/commonMain/kotlin/com/squareup/workflow1/WorkflowIdentifier.kt

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -145,17 +145,31 @@ public class WorkflowIdentifier internal constructor(
145145

146146
/**
147147
* The [WorkflowIdentifier] that identifies this [Workflow].
148+
*
149+
* This can carry some cost if the Workflow class does not extend [StatefulWorkflow] or
148150
*/
149-
public val Workflow<*, *, *>.identifier: WorkflowIdentifier
151+
public inline val Workflow<*, *, *>.identifier: WorkflowIdentifier
150152
get() {
151-
val maybeImpostor = this as? ImpostorWorkflow
152-
return WorkflowIdentifier(
153-
type = Snapshottable(this::class),
154-
proxiedIdentifier = maybeImpostor?.realIdentifier,
155-
description = maybeImpostor?.let { it::describeRealIdentifier }
156-
)
153+
return when (this) {
154+
is IdCacheable -> cachedIdentifier
155+
else -> computeIdentifier()
156+
}
157157
}
158158

159+
/**
160+
* Compute the [WorkflowIdentifier] for this Workflow. [StatefulWorkflow] and [StatelessWorkflow]
161+
* will call this and then cache the value in their internal _identifier property so as to prevent
162+
* the extra work needed to create the [WorkflowIdentifier] and look up the class name each time.
163+
*/
164+
public fun Workflow<*, *, *>.computeIdentifier(): WorkflowIdentifier {
165+
val maybeImpostor = this as? ImpostorWorkflow
166+
return WorkflowIdentifier(
167+
type = Snapshottable(this::class),
168+
proxiedIdentifier = maybeImpostor?.realIdentifier,
169+
description = maybeImpostor?.let { it::describeRealIdentifier }
170+
)
171+
}
172+
159173
/**
160174
* Creates a [WorkflowIdentifier] that is not capable of being snapshotted and will cause any
161175
* [ImpostorWorkflow] workflow identified by it to also not be snapshotted.

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import com.squareup.workflow1.WorkflowIdentifier
1212
import com.squareup.workflow1.WorkflowInterceptor
1313
import com.squareup.workflow1.WorkflowInterceptor.RenderContextInterceptor
1414
import com.squareup.workflow1.WorkflowInterceptor.WorkflowSession
15-
import com.squareup.workflow1.identifier
1615
import com.squareup.workflow1.parse
1716
import com.squareup.workflow1.rendering
1817
import kotlinx.coroutines.CoroutineScope

0 commit comments

Comments
 (0)