Skip to content

Commit 0134243

Browse files
Cache WorkflowIdentifier
1 parent 8df5f39 commit 0134243

File tree

7 files changed

+91
-6
lines changed

7 files changed

+91
-6
lines changed

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
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
810
import com.squareup.workflow1.ui.ViewEnvironment
911
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
1012
import com.squareup.workflow1.ui.compose.ComposeScreen
@@ -29,7 +31,7 @@ import com.squareup.workflow1.ui.compose.ComposeScreen
2931
*/
3032
@WorkflowUiExperimentalApi
3133
abstract class ComposeWorkflow<in PropsT, out OutputT : Any> :
32-
Workflow<PropsT, OutputT, ComposeScreen> {
34+
Workflow<PropsT, OutputT, ComposeScreen>, IdCacheable {
3335

3436
/**
3537
* Renders [props] by emitting Compose UI. This function will be called to update the UI whenever
@@ -48,6 +50,8 @@ abstract class ComposeWorkflow<in PropsT, out OutputT : Any> :
4850

4951
override fun asStatefulWorkflow(): StatefulWorkflow<PropsT, *, OutputT, ComposeScreen> =
5052
ComposeWorkflowImpl(this)
53+
54+
override var cachedIdentifier: WorkflowIdentifier? = null
5155
}
5256

5357
/**

workflow-core/api/workflow-core.api

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ 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+
public abstract fun setCachedIdentifier (Lcom/squareup/workflow1/WorkflowIdentifier;)V
54+
}
55+
5156
public abstract interface class com/squareup/workflow1/ImpostorWorkflow {
5257
public abstract fun describeRealIdentifier ()Ljava/lang/String;
5358
public abstract fun getRealIdentifier ()Lcom/squareup/workflow1/WorkflowIdentifier;
@@ -115,12 +120,14 @@ public final class com/squareup/workflow1/Snapshots {
115120
public static final fun writeUtf8WithLength (Lokio/BufferedSink;Ljava/lang/String;)Lokio/BufferedSink;
116121
}
117122

118-
public abstract class com/squareup/workflow1/StatefulWorkflow : com/squareup/workflow1/Workflow {
123+
public abstract class com/squareup/workflow1/StatefulWorkflow : com/squareup/workflow1/IdCacheable, com/squareup/workflow1/Workflow {
119124
public fun <init> ()V
120125
public final fun asStatefulWorkflow ()Lcom/squareup/workflow1/StatefulWorkflow;
126+
public fun getCachedIdentifier ()Lcom/squareup/workflow1/WorkflowIdentifier;
121127
public abstract fun initialState (Ljava/lang/Object;Lcom/squareup/workflow1/Snapshot;)Ljava/lang/Object;
122128
public fun onPropsChanged (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
123129
public abstract fun render (Ljava/lang/Object;Ljava/lang/Object;Lcom/squareup/workflow1/StatefulWorkflow$RenderContext;)Ljava/lang/Object;
130+
public fun setCachedIdentifier (Lcom/squareup/workflow1/WorkflowIdentifier;)V
124131
public abstract fun snapshotState (Ljava/lang/Object;)Lcom/squareup/workflow1/Snapshot;
125132
}
126133

@@ -141,10 +148,12 @@ public final class com/squareup/workflow1/StatefulWorkflow$RenderContext : com/s
141148
public fun runningSideEffect (Ljava/lang/String;Lkotlin/jvm/functions/Function2;)V
142149
}
143150

144-
public abstract class com/squareup/workflow1/StatelessWorkflow : com/squareup/workflow1/Workflow {
151+
public abstract class com/squareup/workflow1/StatelessWorkflow : com/squareup/workflow1/IdCacheable, com/squareup/workflow1/Workflow {
145152
public fun <init> ()V
146153
public final fun asStatefulWorkflow ()Lcom/squareup/workflow1/StatefulWorkflow;
154+
public fun getCachedIdentifier ()Lcom/squareup/workflow1/WorkflowIdentifier;
147155
public abstract fun render (Ljava/lang/Object;Lcom/squareup/workflow1/StatelessWorkflow$RenderContext;)Ljava/lang/Object;
156+
public fun setCachedIdentifier (Lcom/squareup/workflow1/WorkflowIdentifier;)V
148157
}
149158

150159
public final class com/squareup/workflow1/StatelessWorkflow$RenderContext : com/squareup/workflow1/BaseRenderContext {
@@ -284,6 +293,7 @@ public final class com/squareup/workflow1/Workflows {
284293
public static synthetic fun action$default (Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/squareup/workflow1/WorkflowAction;
285294
public static final fun applyTo (Lcom/squareup/workflow1/WorkflowAction;Ljava/lang/Object;Ljava/lang/Object;)Lkotlin/Pair;
286295
public static final fun contraMap (Lcom/squareup/workflow1/Sink;Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/Sink;
296+
public static final fun getComputedIdentifier (Lcom/squareup/workflow1/Workflow;)Lcom/squareup/workflow1/WorkflowIdentifier;
287297
public static final fun getIdentifier (Lcom/squareup/workflow1/Workflow;)Lcom/squareup/workflow1/WorkflowIdentifier;
288298
public static final fun mapRendering (Lcom/squareup/workflow1/Workflow;Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/Workflow;
289299
public static final fun renderChild (Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;
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+
* Your Workflow can just assign null to this value as the [identifier] extension will use it
9+
* for caching.
10+
*/
11+
public interface IdCacheable {
12+
13+
public var 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.computedIdentifier].
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: 10 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,14 @@ 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 var cachedIdentifier: WorkflowIdentifier? = null
140+
132141
/**
133142
* Called whenever the state changes to generate a new [Snapshot] of the state.
134143
*

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

Lines changed: 10 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,14 @@ 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 var cachedIdentifier: WorkflowIdentifier? = null
68+
6069
/**
6170
* Satisfies the [Workflow] interface by wrapping `this` in a [StatefulWorkflow] with `Unit`
6271
* state.

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,13 @@ public class WorkflowIdentifier internal constructor(
113113
return result
114114
}
115115

116+
/**
117+
* Used to detect when this [WorkflowIdentifier] is a deeply stubbed mock. Stubs are provided
118+
* until a primitive, in this case the typeName. This lets us determine if we are a mock
119+
* object, or a real one. Mea culpa.
120+
*/
121+
internal val deepNameCheck = type.typeName
122+
116123
public companion object {
117124
private const val NO_PROXY_IDENTIFIER_TAG = 0.toByte()
118125
private const val PROXY_IDENTIFIER_TAG = 1.toByte()
@@ -145,8 +152,40 @@ public class WorkflowIdentifier internal constructor(
145152

146153
/**
147154
* The [WorkflowIdentifier] that identifies this [Workflow].
155+
*
156+
* This can carry some cost if the Workflow class does not implement [IdCacheable]. Note that
157+
* [StatelessWorkflow] and [StatefulWorkflow] do implement the caching.
148158
*/
149159
public val Workflow<*, *, *>.identifier: WorkflowIdentifier
160+
get() {
161+
return when (this) {
162+
is IdCacheable -> {
163+
// The following lines look more complex than they need to be. If we have not yet cached
164+
// the identifier, we do. But we return the [computedIdentifier] value directly as in the
165+
// case of tests which use mocks we want to ensure that we return what is on line 180 -
166+
// "WorkflowIdentifier(Snapshottable(this::class))" as that depends solely on types.
167+
// We do the 'senseless' comparison of .type.typeName here to detect the case where we
168+
// we have mocks with deep stubs but the name itself (a String) is null.
169+
// The reason this is so complicated is this caching has been added afterword via the
170+
// [IdCacheable] interface so that the [Workflow] interface itself remains unchanged.
171+
@Suppress("SENSELESS_COMPARISON")
172+
if (cachedIdentifier == null || cachedIdentifier!!.deepNameCheck == null) {
173+
return computedIdentifier.also {
174+
cachedIdentifier = it
175+
}
176+
}
177+
cachedIdentifier!!
178+
}
179+
else -> computedIdentifier
180+
}
181+
}
182+
183+
/**
184+
* The computed [WorkflowIdentifier] for this Workflow. Any [IdCacheable] Workflow should call this
185+
* and then store the value in the cachedIdentifier property so as to prevent
186+
* the extra work needed to create the [WorkflowIdentifier] and look up the class name each time.
187+
*/
188+
public val Workflow<*, *, *>.computedIdentifier: WorkflowIdentifier
150189
get() {
151190
val maybeImpostor = this as? ImpostorWorkflow
152191
return WorkflowIdentifier(

0 commit comments

Comments
 (0)