Skip to content

Commit 9c6504f

Browse files
Merge pull request #1342 from square/sedwards/stateless-cache
1337: Cache `StatelessWorkflow.RenderContext` in the `StatefulWorkflow.RenderContext`
2 parents 9973936 + 33cc287 commit 9c6504f

File tree

4 files changed

+19
-71
lines changed

4 files changed

+19
-71
lines changed

workflow-core/api/workflow-core.api

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ public final class com/squareup/workflow1/StatefulWorkflow$Companion {
257257
}
258258

259259
public final class com/squareup/workflow1/StatefulWorkflow$RenderContext : com/squareup/workflow1/BaseRenderContext {
260+
public final fun asStatelessRenderContext ()Lcom/squareup/workflow1/StatelessWorkflow$RenderContext;
260261
public final fun eventHandler (Ljava/lang/String;Ljava/lang/Boolean;Lkotlin/jvm/functions/Function1;)Lkotlin/jvm/functions/Function0;
261262
public static synthetic fun eventHandler$default (Lcom/squareup/workflow1/StatefulWorkflow$RenderContext;Ljava/lang/String;Ljava/lang/Boolean;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lkotlin/jvm/functions/Function0;
262263
public fun getActionSink ()Lcom/squareup/workflow1/Sink;
@@ -288,17 +289,6 @@ public final class com/squareup/workflow1/StatelessWorkflow$RenderContext : com/
288289
public fun runningSideEffect (Ljava/lang/String;Lkotlin/jvm/functions/Function2;)V
289290
}
290291

291-
public final class com/squareup/workflow1/StatelessWorkflow$StatelessAsStatefulWorkflow : com/squareup/workflow1/StatefulWorkflow {
292-
public fun <init> (Lcom/squareup/workflow1/StatelessWorkflow;)V
293-
public final fun clearCache ()V
294-
public synthetic fun initialState (Ljava/lang/Object;Lcom/squareup/workflow1/Snapshot;)Ljava/lang/Object;
295-
public fun initialState (Ljava/lang/Object;Lcom/squareup/workflow1/Snapshot;)V
296-
public synthetic fun render (Ljava/lang/Object;Ljava/lang/Object;Lcom/squareup/workflow1/StatefulWorkflow$RenderContext;)Ljava/lang/Object;
297-
public fun render (Ljava/lang/Object;Lkotlin/Unit;Lcom/squareup/workflow1/StatefulWorkflow$RenderContext;)Ljava/lang/Object;
298-
public synthetic fun snapshotState (Ljava/lang/Object;)Lcom/squareup/workflow1/Snapshot;
299-
public fun snapshotState (Lkotlin/Unit;)Lcom/squareup/workflow1/Snapshot;
300-
}
301-
302292
public final class com/squareup/workflow1/TypedWorker : com/squareup/workflow1/Worker {
303293
public fun <init> (Lkotlin/reflect/KType;Lkotlinx/coroutines/flow/Flow;)V
304294
public fun doesSameWorkAs (Lcom/squareup/workflow1/Worker;)Z
@@ -424,7 +414,6 @@ public final class com/squareup/workflow1/WorkflowTracerKt {
424414

425415
public final class com/squareup/workflow1/Workflows {
426416
public static final fun RenderContext (Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/StatefulWorkflow;)Lcom/squareup/workflow1/StatefulWorkflow$RenderContext;
427-
public static final fun RenderContext (Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/StatelessWorkflow;)Lcom/squareup/workflow1/StatelessWorkflow$RenderContext;
428417
public static final fun action (Lcom/squareup/workflow1/StatefulWorkflow;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/WorkflowAction;
429418
public static final fun action (Lcom/squareup/workflow1/StatefulWorkflow;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/WorkflowAction;
430419
public static final fun action (Lcom/squareup/workflow1/StatelessWorkflow;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/WorkflowAction;

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,20 @@ public abstract class StatefulWorkflow<
706706
}
707707
?: onFailedCast(name, CurrentStateT::class, state)
708708
}
709+
710+
private var statelessContext: StatelessWorkflow.RenderContext<PropsT, OutputT>? = null
711+
712+
/**
713+
* Fetch the [StatelessWorkflow.RenderContext] that is equivalent to this [RenderContext]
714+
* with the `StateT` type erased.
715+
*
716+
* We cache this value so that it does not need to be continually recreated and will be stable
717+
* for compose. See [statelessContext] for the instance.
718+
*/
719+
public fun asStatelessRenderContext(): StatelessWorkflow.RenderContext<PropsT, OutputT> =
720+
statelessContext ?: StatelessWorkflow.RenderContext(this).also {
721+
statelessContext = it
722+
}
709723
}
710724

711725
/**

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

Lines changed: 4 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -227,23 +227,9 @@ public abstract class StatelessWorkflow<PropsT, OutputT, out RenderingT> :
227227
* Class type returned by [asStatefulWorkflow].
228228
* See [statefulWorkflow] for the instance.
229229
*/
230-
inner class StatelessAsStatefulWorkflow :
230+
private inner class StatelessAsStatefulWorkflow :
231231
StatefulWorkflow<PropsT, Unit, OutputT, RenderingT>() {
232232

233-
/**
234-
* We want to cache the render context so that we don't have to recreate it each time
235-
* render() is called.
236-
*/
237-
private var cachedStatelessRenderContext:
238-
StatelessWorkflow.RenderContext<PropsT, OutputT>? = null
239-
240-
/**
241-
* We must know if the RenderContext we are passed (which is a StatefulWorkflow.RenderContext)
242-
* has changed, so keep track of it.
243-
*/
244-
private var canonicalStatefulRenderContext:
245-
StatefulWorkflow.RenderContext<PropsT, Unit, OutputT>? = null
246-
247233
override fun initialState(
248234
props: PropsT,
249235
snapshot: Snapshot?
@@ -254,37 +240,12 @@ public abstract class StatelessWorkflow<PropsT, OutputT, out RenderingT> :
254240
renderState: Unit,
255241
context: RenderContext<PropsT, Unit, OutputT>
256242
): RenderingT {
257-
// The `RenderContext` used *might* change - primarily in the case of our tests. E.g., The
258-
// `RenderTester` uses a special NoOp context to render twice to test for idempotency.
259-
// In order to support a changed render context but keep caching, we check to see if the
260-
// instance passed in has changed.
261-
if (cachedStatelessRenderContext == null || context !== canonicalStatefulRenderContext) {
262-
// Recreate it if the StatefulWorkflow.RenderContext we are passed has changed.
263-
cachedStatelessRenderContext = RenderContext(context, this@StatelessWorkflow)
264-
}
265-
canonicalStatefulRenderContext = context
266-
// Pass the StatelessWorkflow.RenderContext to our StatelessWorkflow.
267-
return render(renderProps, cachedStatelessRenderContext!!)
243+
// Pass `asStatelessRenderContext()`. Caching for that will be handled by the
244+
// `StatefulWorkflow.RenderContext` itself.
245+
return render(renderProps, context.asStatelessRenderContext())
268246
}
269247

270248
override fun snapshotState(state: Unit): Snapshot? = null
271-
272-
/**
273-
* When we are finished with at least one node that holds on to this workflow instance,
274-
* then we clear the cache. The reason we do that every time is that it *might* be the last
275-
* node that is caching this instance, and if so, we do not want to leak these cached
276-
* render contexts.
277-
*
278-
* Yes, that means that it might have to be re-created again when this instance is used
279-
* multiple times. The current design for how we get a [StatefulWorkflow] from the
280-
* [StatelessWorkflow] is a failed compromise between performance (caching) and type-safe
281-
* brevity (erasing the `StateT` type from the concerns of [StatelessWorkflow]). It needs
282-
* to be fixed with a bigger re-write (https://github.com/square/workflow-kotlin/issues/1337).
283-
*/
284-
fun clearCache() {
285-
cachedStatelessRenderContext = null
286-
canonicalStatefulRenderContext = null
287-
}
288249
}
289250

290251
private val statefulWorkflow: StatefulWorkflow<PropsT, Unit, OutputT, RenderingT> =
@@ -325,17 +286,6 @@ public abstract class StatelessWorkflow<PropsT, OutputT, out RenderingT> :
325286
statefulWorkflow
326287
}
327288

328-
/**
329-
* Creates a `RenderContext` from a [BaseRenderContext] for the given [StatelessWorkflow].
330-
*/
331-
@Suppress("UNCHECKED_CAST")
332-
public fun <PropsT, OutputT, RenderingT> RenderContext(
333-
baseContext: BaseRenderContext<PropsT, *, OutputT>,
334-
workflow: StatelessWorkflow<PropsT, OutputT, RenderingT>
335-
): StatelessWorkflow.RenderContext<PropsT, OutputT> =
336-
(baseContext as? StatelessWorkflow.RenderContext<PropsT, OutputT>)
337-
?: StatelessWorkflow.RenderContext<PropsT, OutputT>(baseContext)
338-
339289
/**
340290
* Returns a stateless [Workflow] via the given [render] function.
341291
*

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

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import com.squareup.workflow1.RuntimeConfig
99
import com.squareup.workflow1.RuntimeConfigOptions
1010
import com.squareup.workflow1.RuntimeConfigOptions.PARTIAL_TREE_RENDERING
1111
import com.squareup.workflow1.StatefulWorkflow
12-
import com.squareup.workflow1.StatelessWorkflow
1312
import com.squareup.workflow1.TreeSnapshot
1413
import com.squareup.workflow1.Workflow
1514
import com.squareup.workflow1.WorkflowAction
@@ -237,10 +236,6 @@ internal class WorkflowNode<PropsT, StateT, OutputT, RenderingT>(
237236
fun cancel(cause: CancellationException? = null) {
238237
coroutineContext.cancel(cause)
239238
lastRendering = NullableInitBox()
240-
(
241-
cachedWorkflowInstance as?
242-
StatelessWorkflow<PropsT, OutputT, RenderingT>.StatelessAsStatefulWorkflow
243-
)?.clearCache()
244239
}
245240

246241
/**

0 commit comments

Comments
 (0)