@@ -24,12 +24,12 @@ import com.squareup.workflow1.intercept
24
24
import com.squareup.workflow1.internal.RealRenderContext.RememberStore
25
25
import com.squareup.workflow1.internal.RealRenderContext.SideEffectRunner
26
26
import com.squareup.workflow1.trace
27
+ import com.squareup.workflow1.workflowSessionToString
27
28
import kotlinx.coroutines.CancellationException
28
29
import kotlinx.coroutines.CoroutineName
29
30
import kotlinx.coroutines.CoroutineScope
30
31
import kotlinx.coroutines.CoroutineStart.LAZY
31
32
import kotlinx.coroutines.Job
32
- import kotlinx.coroutines.cancel
33
33
import kotlinx.coroutines.channels.Channel
34
34
import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED
35
35
import kotlinx.coroutines.launch
@@ -49,38 +49,42 @@ import kotlin.reflect.KType
49
49
* structured concurrency).
50
50
*/
51
51
@OptIn(WorkflowExperimentalApi ::class , WorkflowExperimentalRuntime ::class )
52
- internal class WorkflowNode <PropsT , StateT , OutputT , RenderingT >(
53
- val id : WorkflowNodeId ,
52
+ internal class StatefulWorkflowNode <PropsT , StateT , OutputT , RenderingT >(
53
+ id : WorkflowNodeId ,
54
54
workflow : StatefulWorkflow <PropsT , StateT , OutputT , RenderingT >,
55
55
initialProps : PropsT ,
56
56
snapshot : TreeSnapshot ? ,
57
57
baseContext : CoroutineContext ,
58
58
// Providing default value so we don't need to specify in test.
59
59
override val runtimeConfig : RuntimeConfig = RuntimeConfigOptions .DEFAULT_CONFIG ,
60
60
override val workflowTracer : WorkflowTracer ? = null ,
61
- private val emitAppliedActionToParent : (ActionApplied <OutputT >) -> ActionProcessingResult =
62
- { it },
61
+ emitAppliedActionToParent : (ActionApplied <OutputT >) -> ActionProcessingResult = { it },
63
62
override val parent : WorkflowSession ? = null ,
64
- private val interceptor : WorkflowInterceptor = NoopWorkflowInterceptor ,
63
+ interceptor : WorkflowInterceptor = NoopWorkflowInterceptor ,
65
64
idCounter : IdCounter ? = null
66
- ) : CoroutineScope, SideEffectRunner, RememberStore, WorkflowSession {
67
-
68
- /* *
69
- * Context that has a job that will live as long as this node.
70
- * Also adds a debug name to this coroutine based on its ID.
71
- */
72
- override val coroutineContext = baseContext + Job (baseContext[Job ]) + CoroutineName (id.toString())
73
-
74
- // WorkflowInstance properties
65
+ ) : AbstractWorkflowNode<PropsT, OutputT, RenderingT>(
66
+ id = id,
67
+ baseContext = baseContext,
68
+ interceptor = interceptor,
69
+ emitAppliedActionToParent = emitAppliedActionToParent,
70
+ ),
71
+ SideEffectRunner ,
72
+ RememberStore ,
73
+ WorkflowSession {
74
+
75
+ // WorkflowSession properties
75
76
override val identifier: WorkflowIdentifier get() = id.identifier
76
77
override val renderKey: String get() = id.name
77
78
override val sessionId: Long = idCounter.createId()
78
79
private var cachedWorkflowInstance: StatefulWorkflow <PropsT , StateT , OutputT , RenderingT >
79
80
private var interceptedWorkflowInstance: StatefulWorkflow <PropsT , StateT , OutputT , RenderingT >
80
81
82
+ override val session: WorkflowSession
83
+ get() = this
84
+
81
85
private val subtreeManager = SubtreeManager (
82
86
snapshotCache = snapshot?.childTreeSnapshots,
83
- contextForChildren = coroutineContext,
87
+ contextForChildren = scope. coroutineContext,
84
88
emitActionToParent = ::applyAction,
85
89
runtimeConfig = runtimeConfig,
86
90
workflowTracer = workflowTracer,
@@ -117,52 +121,54 @@ internal class WorkflowNode<PropsT, StateT, OutputT, RenderingT>(
117
121
private val context = RenderContext (baseRenderContext, workflow)
118
122
119
123
init {
120
- interceptor.onSessionStarted(this , this )
124
+ interceptor.onSessionStarted(workflowScope = scope, session = this )
121
125
122
126
cachedWorkflowInstance = workflow
123
- interceptedWorkflowInstance = interceptor.intercept(cachedWorkflowInstance, this )
124
- state = interceptedWorkflowInstance.initialState(initialProps, snapshot?.workflowSnapshot, this )
127
+ interceptedWorkflowInstance = interceptor.intercept(
128
+ workflow = cachedWorkflowInstance,
129
+ workflowSession = this
130
+ )
131
+ state = interceptedWorkflowInstance.initialState(
132
+ props = initialProps,
133
+ snapshot = snapshot?.workflowSnapshot,
134
+ workflowScope = scope
135
+ )
125
136
}
126
137
127
- override fun toString (): String {
128
- val parentDescription = parent?.let { " WorkflowInstance(…)" }
129
- return " WorkflowInstance(" +
130
- " identifier=$identifier , " +
131
- " renderKey=$renderKey , " +
132
- " instanceId=$sessionId , " +
133
- " parent=$parentDescription " +
134
- " )"
135
- }
138
+ override fun toString (): String = workflowSessionToString()
136
139
137
140
/* *
138
141
* Walk the tree of workflows, rendering each one and using
139
142
* [RenderContext][com.squareup.workflow1.BaseRenderContext] to give its children a chance to
140
143
* render themselves and aggregate those child renderings.
141
144
*/
142
145
@Suppress(" UNCHECKED_CAST" )
143
- fun render (
144
- workflow : StatefulWorkflow <PropsT , * , OutputT , RenderingT >,
146
+ override fun render (
147
+ workflow : Workflow <PropsT , OutputT , RenderingT >,
145
148
input : PropsT
146
- ): RenderingT =
147
- renderWithStateType(workflow as StatefulWorkflow <PropsT , StateT , OutputT , RenderingT >, input)
149
+ ): RenderingT = renderWithStateType(
150
+ workflow = workflow.asStatefulWorkflow() as
151
+ StatefulWorkflow <PropsT , StateT , OutputT , RenderingT >,
152
+ props = input
153
+ )
148
154
149
155
/* *
150
156
* Walk the tree of state machines again, this time gathering snapshots and aggregating them
151
157
* automatically.
152
158
*/
153
- fun snapshot (workflow : StatefulWorkflow < * , * , * , * > ): TreeSnapshot {
154
- @Suppress( " UNCHECKED_CAST " )
155
- val typedWorkflow = workflow as StatefulWorkflow < PropsT , StateT , OutputT , RenderingT >
156
- maybeUpdateCachedWorkflowInstance(typedWorkflow )
157
- return interceptor.onSnapshotStateWithChildren({
158
- val childSnapshots = subtreeManager.createChildSnapshots()
159
- val rootSnapshot = interceptedWorkflowInstance.snapshotState(state)
160
- TreeSnapshot (
161
- workflowSnapshot = rootSnapshot,
162
- // Create the snapshots eagerly since subtreeManager is mutable.
163
- childTreeSnapshots = { childSnapshots }
164
- )
165
- }, this )
159
+ override fun snapshot (): TreeSnapshot {
160
+ return interceptor.onSnapshotStateWithChildren(
161
+ proceed = {
162
+ val childSnapshots = subtreeManager.createChildSnapshots( )
163
+ val rootSnapshot = interceptedWorkflowInstance.snapshotState(state)
164
+ TreeSnapshot (
165
+ workflowSnapshot = rootSnapshot,
166
+ // Create the snapshots eagerly since subtreeManager is mutable.
167
+ childTreeSnapshots = { childSnapshots }
168
+ )
169
+ },
170
+ session = this
171
+ )
166
172
}
167
173
168
174
override fun runningSideEffect (
@@ -206,20 +212,7 @@ internal class WorkflowNode<PropsT, StateT, OutputT, RenderingT>(
206
212
return result.lastCalculated as ResultT
207
213
}
208
214
209
- /* *
210
- * Register select clauses for the next [result][ActionProcessingResult] from the state machine.
211
- *
212
- * Walk the tree of state machines, asking each one to wait for its next event. If something happen
213
- * that results in an output, that output is returned. Null means something happened that requires
214
- * a re-render, e.g. my state changed or a child state changed.
215
- *
216
- * It is an error to call this method after calling [cancel].
217
- *
218
- * Contrast this to [applyNextAvailableTreeAction], which is used to check for an action
219
- * that is already available without waiting, and then _immediately_ apply it.
220
- * The select clauses added here also call [applyAction] when one of them is selected.
221
- */
222
- fun registerTreeActionSelectors (selector : SelectBuilder <ActionProcessingResult >) {
215
+ override fun registerTreeActionSelectors (selector : SelectBuilder <ActionProcessingResult >) {
223
216
// Listen for any child workflow updates.
224
217
subtreeManager.registerChildActionSelectors(selector)
225
218
@@ -231,22 +224,7 @@ internal class WorkflowNode<PropsT, StateT, OutputT, RenderingT>(
231
224
}
232
225
}
233
226
234
- /* *
235
- * Will try to apply any immediately available actions in this action queue or any of our
236
- * children's.
237
- *
238
- * Contrast this to [registerTreeActionSelectors] which will add select clauses that will await
239
- * the next action. That will also end up with [applyAction] being called when the clauses is
240
- * selected.
241
- *
242
- * @param skipDirtyNodes Whether or not this should skip over any workflow nodes that are already
243
- * 'dirty' - that is, they had their own state changed as the result of a previous action before
244
- * the next render pass.
245
- *
246
- * @return [ActionProcessingResult] of the action processed, or [ActionsExhausted] if there were
247
- * none immediately available.
248
- */
249
- fun applyNextAvailableTreeAction (skipDirtyNodes : Boolean = false): ActionProcessingResult {
227
+ override fun applyNextAvailableTreeAction (skipDirtyNodes : Boolean ): ActionProcessingResult {
250
228
if (skipDirtyNodes && selfStateDirty) return ActionsExhausted
251
229
252
230
val result = subtreeManager.applyNextAvailableChildAction(skipDirtyNodes)
@@ -262,11 +240,11 @@ internal class WorkflowNode<PropsT, StateT, OutputT, RenderingT>(
262
240
/* *
263
241
* Cancels this state machine host, and any coroutines started as children of it.
264
242
*
265
- * This must be called when the caller will no longer call [registerTreeActionSelectors]. It is an error to call [registerTreeActionSelectors]
266
- * after calling this method.
243
+ * This must be called when the caller will no longer call [registerTreeActionSelectors]. It is an
244
+ * error to call [registerTreeActionSelectors] after calling this method.
267
245
*/
268
- fun cancel (cause : CancellationException ? = null ) {
269
- coroutineContext .cancel(cause)
246
+ override fun cancel (cause : CancellationException ? ) {
247
+ super .cancel(cause)
270
248
lastRendering = NullableInitBox ()
271
249
}
272
250
@@ -348,7 +326,6 @@ internal class WorkflowNode<PropsT, StateT, OutputT, RenderingT>(
348
326
* Applies [action] to this workflow's [state] and then passes the resulting [ActionApplied]
349
327
* via [emitAppliedActionToParent] to the parent, with additional information as to whether or
350
328
* not this action has changed the current node's state.
351
- *
352
329
*/
353
330
private fun applyAction (
354
331
action : WorkflowAction <PropsT , StateT , OutputT >,
@@ -389,7 +366,7 @@ internal class WorkflowNode<PropsT, StateT, OutputT, RenderingT>(
389
366
sideEffect : suspend CoroutineScope .() -> Unit
390
367
): SideEffectNode {
391
368
return workflowTracer.trace(" CreateSideEffectNode" ) {
392
- val scope = this + CoroutineName (" sideEffect[$key ] for $id " )
369
+ val scope = scope + CoroutineName (" sideEffect[$key ] for $id " )
393
370
val job = scope.launch(start = LAZY , block = sideEffect)
394
371
SideEffectNode (key, job)
395
372
}
0 commit comments