Skip to content

Commit f604a81

Browse files
Merge branch 'main' into sedwards/update-timeout
2 parents 6861727 + 9c8f14c commit f604a81

File tree

7 files changed

+249
-8
lines changed

7 files changed

+249
-8
lines changed

workflow-runtime/api/workflow-runtime.api

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ public final class com/squareup/workflow1/NoopWorkflowInterceptor : com/squareup
44
public fun onPropsChanged (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Lkotlin/jvm/functions/Function3;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Ljava/lang/Object;
55
public fun onRender (Ljava/lang/Object;Ljava/lang/Object;Lcom/squareup/workflow1/BaseRenderContext;Lkotlin/jvm/functions/Function3;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Ljava/lang/Object;
66
public fun onRenderAndSnapshot (Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Lcom/squareup/workflow1/RenderingAndSnapshot;
7+
public fun onRuntimeLoopTick (Lcom/squareup/workflow1/WorkflowInterceptor$RuntimeLoopOutcome;)V
78
public fun onSessionStarted (Lkotlinx/coroutines/CoroutineScope;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)V
89
public fun onSnapshotState (Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Lcom/squareup/workflow1/Snapshot;
910
public fun onSnapshotStateWithChildren (Lkotlin/jvm/functions/Function0;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Lcom/squareup/workflow1/TreeSnapshot;
@@ -32,6 +33,7 @@ public class com/squareup/workflow1/SimpleLoggingWorkflowInterceptor : com/squar
3233
public fun onPropsChanged (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Lkotlin/jvm/functions/Function3;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Ljava/lang/Object;
3334
public fun onRender (Ljava/lang/Object;Ljava/lang/Object;Lcom/squareup/workflow1/BaseRenderContext;Lkotlin/jvm/functions/Function3;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Ljava/lang/Object;
3435
public fun onRenderAndSnapshot (Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Lcom/squareup/workflow1/RenderingAndSnapshot;
36+
public fun onRuntimeLoopTick (Lcom/squareup/workflow1/WorkflowInterceptor$RuntimeLoopOutcome;)V
3537
public fun onSessionStarted (Lkotlinx/coroutines/CoroutineScope;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)V
3638
public fun onSnapshotState (Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Lcom/squareup/workflow1/Snapshot;
3739
public fun onSnapshotStateWithChildren (Lkotlin/jvm/functions/Function0;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Lcom/squareup/workflow1/TreeSnapshot;
@@ -54,6 +56,7 @@ public abstract interface class com/squareup/workflow1/WorkflowInterceptor {
5456
public abstract fun onPropsChanged (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Lkotlin/jvm/functions/Function3;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Ljava/lang/Object;
5557
public abstract fun onRender (Ljava/lang/Object;Ljava/lang/Object;Lcom/squareup/workflow1/BaseRenderContext;Lkotlin/jvm/functions/Function3;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Ljava/lang/Object;
5658
public abstract fun onRenderAndSnapshot (Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Lcom/squareup/workflow1/RenderingAndSnapshot;
59+
public abstract fun onRuntimeLoopTick (Lcom/squareup/workflow1/WorkflowInterceptor$RuntimeLoopOutcome;)V
5760
public abstract fun onSessionStarted (Lkotlinx/coroutines/CoroutineScope;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)V
5861
public abstract fun onSnapshotState (Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Lcom/squareup/workflow1/Snapshot;
5962
public abstract fun onSnapshotStateWithChildren (Lkotlin/jvm/functions/Function0;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Lcom/squareup/workflow1/TreeSnapshot;
@@ -64,6 +67,7 @@ public final class com/squareup/workflow1/WorkflowInterceptor$DefaultImpls {
6467
public static fun onPropsChanged (Lcom/squareup/workflow1/WorkflowInterceptor;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Lkotlin/jvm/functions/Function3;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Ljava/lang/Object;
6568
public static fun onRender (Lcom/squareup/workflow1/WorkflowInterceptor;Ljava/lang/Object;Ljava/lang/Object;Lcom/squareup/workflow1/BaseRenderContext;Lkotlin/jvm/functions/Function3;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Ljava/lang/Object;
6669
public static fun onRenderAndSnapshot (Lcom/squareup/workflow1/WorkflowInterceptor;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Lcom/squareup/workflow1/RenderingAndSnapshot;
70+
public static fun onRuntimeLoopTick (Lcom/squareup/workflow1/WorkflowInterceptor;Lcom/squareup/workflow1/WorkflowInterceptor$RuntimeLoopOutcome;)V
6771
public static fun onSessionStarted (Lcom/squareup/workflow1/WorkflowInterceptor;Lkotlinx/coroutines/CoroutineScope;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)V
6872
public static fun onSnapshotState (Lcom/squareup/workflow1/WorkflowInterceptor;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Lcom/squareup/workflow1/Snapshot;
6973
public static fun onSnapshotStateWithChildren (Lcom/squareup/workflow1/WorkflowInterceptor;Lkotlin/jvm/functions/Function0;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Lcom/squareup/workflow1/TreeSnapshot;
@@ -83,6 +87,18 @@ public final class com/squareup/workflow1/WorkflowInterceptor$RenderContextInter
8387
public static fun onRunningSideEffect (Lcom/squareup/workflow1/WorkflowInterceptor$RenderContextInterceptor;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)V
8488
}
8589

90+
public final class com/squareup/workflow1/WorkflowInterceptor$RenderPassSkipped : com/squareup/workflow1/WorkflowInterceptor$RuntimeLoopOutcome {
91+
public static final field INSTANCE Lcom/squareup/workflow1/WorkflowInterceptor$RenderPassSkipped;
92+
}
93+
94+
public final class com/squareup/workflow1/WorkflowInterceptor$RenderPassesComplete : com/squareup/workflow1/WorkflowInterceptor$RuntimeLoopOutcome {
95+
public fun <init> (Lcom/squareup/workflow1/RenderingAndSnapshot;)V
96+
public final fun getRenderingAndSnapshot ()Lcom/squareup/workflow1/RenderingAndSnapshot;
97+
}
98+
99+
public abstract interface class com/squareup/workflow1/WorkflowInterceptor$RuntimeLoopOutcome {
100+
}
101+
86102
public abstract interface class com/squareup/workflow1/WorkflowInterceptor$WorkflowSession {
87103
public abstract fun getIdentifier ()Lcom/squareup/workflow1/WorkflowIdentifier;
88104
public abstract fun getParent ()Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;

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

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package com.squareup.workflow1
22

33
import com.squareup.workflow1.RuntimeConfigOptions.CONFLATE_STALE_RENDERINGS
44
import com.squareup.workflow1.RuntimeConfigOptions.RENDER_ONLY_WHEN_STATE_CHANGES
5+
import com.squareup.workflow1.WorkflowInterceptor.RenderPassSkipped
6+
import com.squareup.workflow1.WorkflowInterceptor.RenderPassesComplete
57
import com.squareup.workflow1.internal.WorkflowRunner
68
import com.squareup.workflow1.internal.chained
79
import kotlinx.coroutines.CancellationException
@@ -129,7 +131,9 @@ public fun <PropsT, OutputT, RenderingT> renderWorkflowIn(
129131
// coroutine to calculate the initial rendering.
130132
val renderingsAndSnapshots = MutableStateFlow(
131133
try {
132-
runner.nextRendering()
134+
runner.nextRendering().also {
135+
chainedInterceptor.onRuntimeLoopTick(RenderPassesComplete(it))
136+
}
133137
} catch (e: Throwable) {
134138
// If any part of the workflow runtime fails, the scope should be cancelled. We're not in a
135139
// coroutine yet however, so if the first render pass fails it won't cancel the runtime,
@@ -169,7 +173,9 @@ public fun <PropsT, OutputT, RenderingT> renderWorkflowIn(
169173
conflationHasChangedState: Boolean = false
170174
): Boolean {
171175
return runtimeConfig.contains(RENDER_ONLY_WHEN_STATE_CHANGES) &&
172-
actionResult is ActionApplied<*> && !actionResult.stateChanged && !conflationHasChangedState
176+
actionResult is ActionApplied<*> &&
177+
!actionResult.stateChanged &&
178+
!conflationHasChangedState
173179
}
174180

175181
scope.launch {
@@ -180,6 +186,7 @@ public fun <PropsT, OutputT, RenderingT> renderWorkflowIn(
180186
var actionResult: ActionProcessingResult = runner.processAction()
181187

182188
if (shouldShortCircuitForUnchangedState(actionResult)) {
189+
chainedInterceptor.onRuntimeLoopTick(RenderPassSkipped)
183190
sendOutput(actionResult, onOutput)
184191
continue@outer
185192
}
@@ -207,6 +214,7 @@ public fun <PropsT, OutputT, RenderingT> renderWorkflowIn(
207214
conflationHasChangedState = conflationHasChangedState
208215
)
209216
) {
217+
chainedInterceptor.onRuntimeLoopTick(RenderPassSkipped)
210218
sendOutput(actionResult, onOutput)
211219
continue@outer
212220
}
@@ -219,7 +227,9 @@ public fun <PropsT, OutputT, RenderingT> renderWorkflowIn(
219227
}
220228

221229
// Pass on the rendering to the UI.
222-
renderingsAndSnapshots.value = nextRenderAndSnapshot
230+
renderingsAndSnapshots.value = nextRenderAndSnapshot.also {
231+
chainedInterceptor.onRuntimeLoopTick(RenderPassesComplete(it))
232+
}
223233

224234
// Emit the Output
225235
sendOutput(actionResult, onOutput)

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

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ import kotlin.reflect.KType
1212
* Provides hooks into the workflow runtime that can be used to instrument or modify the behavior
1313
* of workflows.
1414
*
15-
* This interface's methods mirror the methods of [StatefulWorkflow]. It also has one additional
16-
* method, [onSessionStarted], that is notified when a workflow is started. Each method returns the
17-
* same thing as the corresponding method on [StatefulWorkflow], and receives the same parameters
18-
* as well as two extra parameters:
15+
* This interface's methods mirror the methods of [StatefulWorkflow], and two additional methods,
16+
* explained below.
17+
* Each method returns the same thing as the corresponding method on [StatefulWorkflow], and
18+
* receives the same parameters as well as two extra parameters:
1919
*
2020
* - **`proceed`** – A function that _exactly_ mirrors the corresponding function on
2121
* [StatefulWorkflow], accepting the same parameters and returning the same thing. An interceptor
@@ -28,6 +28,16 @@ import kotlin.reflect.KType
2828
*
2929
* All methods have default no-op implementations.
3030
*
31+
* ## Additional Methods
32+
*
33+
* There are 2 more methods in this interface:
34+
*
35+
* 1. [onSessionStarted] - called when a new [WorkflowSession] is created the first time a
36+
* workflow is rendered with the [CoroutineScope] for that session.
37+
* 1. [onRuntimeLoopTick] - Called to report the [RuntimeLoopOutcome] of each tick of the runtime
38+
* loop. In the simplest case this is the application of one action and one render pass, but
39+
* optimizations can change that. See [onRenderingUpdated] for more.
40+
*
3141
* ## On Profiling
3242
*
3343
* Note that the [WorkflowInterceptor]'s methods will call the actual methods with the proceed
@@ -129,6 +139,29 @@ public interface WorkflowInterceptor {
129139
session: WorkflowSession
130140
): Snapshot? = proceed(state)
131141

142+
/**
143+
* Called to report the [outcome] of each tick of the runtime loop. In the simplest case
144+
* this is the application of one action and one render pass, but optimizations can
145+
* change that:
146+
*
147+
* - With the `RENDER_ONLY_WHEN_STATE_CHANGES` optimization, there may not be a render pass
148+
* at all, in which case [RenderPassSkipped] is the outcome.
149+
* - With the `CONFLATE_STALE_RENDERINGS` optimization, there could be multiple render passes.
150+
* - If there is at least one render pass, then [RenderPassesComplete] is passed as the outcome,
151+
* which includes the actual [RenderingAndSnapshot] returned from the runtime.
152+
*
153+
* @param outcome The [RuntimeLoopOutcome] of the tick of the runtime loop.
154+
*/
155+
public fun onRuntimeLoopTick(
156+
outcome: RuntimeLoopOutcome
157+
): Unit = Unit
158+
159+
public sealed interface RuntimeLoopOutcome
160+
public object RenderPassSkipped : RuntimeLoopOutcome
161+
public class RenderPassesComplete<R>(
162+
public val renderingAndSnapshot: RenderingAndSnapshot<R>
163+
) : RuntimeLoopOutcome
164+
132165
/**
133166
* Information about the session of a workflow in the runtime that a [WorkflowInterceptor] method
134167
* is intercepting.

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import com.squareup.workflow1.Workflow
99
import com.squareup.workflow1.WorkflowAction
1010
import com.squareup.workflow1.WorkflowInterceptor
1111
import com.squareup.workflow1.WorkflowInterceptor.RenderContextInterceptor
12+
import com.squareup.workflow1.WorkflowInterceptor.RuntimeLoopOutcome
1213
import com.squareup.workflow1.WorkflowInterceptor.WorkflowSession
1314
import kotlinx.coroutines.CoroutineScope
1415
import kotlin.reflect.KType
@@ -123,6 +124,12 @@ internal class ChainedWorkflowInterceptor(
123124
return chainedProceed(state)
124125
}
125126

127+
override fun onRuntimeLoopTick(outcome: RuntimeLoopOutcome) {
128+
interceptors.forEach { interceptor ->
129+
interceptor.onRuntimeLoopTick(outcome)
130+
}
131+
}
132+
126133
private fun <P, S, O> RenderContextInterceptor<P, S, O>?.wrap(
127134
inner: RenderContextInterceptor<P, S, O>?
128135
) = when {

0 commit comments

Comments
 (0)