diff --git a/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/WorkerWorkflow.kt b/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/WorkerWorkflow.kt index 080bb5eaa4..983f7ac5de 100644 --- a/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/WorkerWorkflow.kt +++ b/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/WorkerWorkflow.kt @@ -35,7 +35,7 @@ internal class WorkerWorkflow( ImpostorWorkflow { override val realIdentifier: WorkflowIdentifier = unsnapshottableIdentifier(workerType) - override fun describeRealIdentifier(): String = "worker $workerType" + override fun describeRealIdentifier(): String = workerType.toString() override fun initialState( props: Worker, @@ -90,7 +90,7 @@ private class EmitWorkerOutputAction( ) : WorkflowAction() { override fun toString(): String = WorkflowIdentifierTypeNamer.uniqueName(EmitWorkerOutputAction::class) + - "(worker=$worker, key=\"$renderKey\")" + "(worker=$worker, key=$renderKey)" override fun Updater.apply() { setOutput(output) diff --git a/workflow-runtime/api/workflow-runtime.api b/workflow-runtime/api/workflow-runtime.api index 36c734eba5..ea6a09b24c 100644 --- a/workflow-runtime/api/workflow-runtime.api +++ b/workflow-runtime/api/workflow-runtime.api @@ -3,8 +3,10 @@ public final class com/squareup/workflow1/NoopWorkflowInterceptor : com/squareup public fun onInitialState (Ljava/lang/Object;Lcom/squareup/workflow1/Snapshot;Lkotlin/jvm/functions/Function2;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Ljava/lang/Object; public fun onPropsChanged (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Lkotlin/jvm/functions/Function3;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Ljava/lang/Object; 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; + public fun onRenderAndSnapshot (Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Lcom/squareup/workflow1/RenderingAndSnapshot; public fun onSessionStarted (Lkotlinx/coroutines/CoroutineScope;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)V public fun onSnapshotState (Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Lcom/squareup/workflow1/Snapshot; + public fun onSnapshotStateWithChildren (Lkotlin/jvm/functions/Function0;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Lcom/squareup/workflow1/TreeSnapshot; } public final class com/squareup/workflow1/RenderWorkflowKt { @@ -45,8 +47,10 @@ public class com/squareup/workflow1/SimpleLoggingWorkflowInterceptor : com/squar public fun onInitialState (Ljava/lang/Object;Lcom/squareup/workflow1/Snapshot;Lkotlin/jvm/functions/Function2;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Ljava/lang/Object; public fun onPropsChanged (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Lkotlin/jvm/functions/Function3;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Ljava/lang/Object; 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; + public fun onRenderAndSnapshot (Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Lcom/squareup/workflow1/RenderingAndSnapshot; public fun onSessionStarted (Lkotlinx/coroutines/CoroutineScope;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)V public fun onSnapshotState (Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Lcom/squareup/workflow1/Snapshot; + public fun onSnapshotStateWithChildren (Lkotlin/jvm/functions/Function0;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Lcom/squareup/workflow1/TreeSnapshot; } public final class com/squareup/workflow1/TreeSnapshot { @@ -68,16 +72,20 @@ public abstract interface class com/squareup/workflow1/WorkflowInterceptor { public abstract fun onInitialState (Ljava/lang/Object;Lcom/squareup/workflow1/Snapshot;Lkotlin/jvm/functions/Function2;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Ljava/lang/Object; 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; 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; + public abstract fun onRenderAndSnapshot (Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Lcom/squareup/workflow1/RenderingAndSnapshot; public abstract fun onSessionStarted (Lkotlinx/coroutines/CoroutineScope;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)V public abstract fun onSnapshotState (Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Lcom/squareup/workflow1/Snapshot; + public abstract fun onSnapshotStateWithChildren (Lkotlin/jvm/functions/Function0;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Lcom/squareup/workflow1/TreeSnapshot; } public final class com/squareup/workflow1/WorkflowInterceptor$DefaultImpls { public static fun onInitialState (Lcom/squareup/workflow1/WorkflowInterceptor;Ljava/lang/Object;Lcom/squareup/workflow1/Snapshot;Lkotlin/jvm/functions/Function2;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Ljava/lang/Object; 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; 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; + public static fun onRenderAndSnapshot (Lcom/squareup/workflow1/WorkflowInterceptor;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Lcom/squareup/workflow1/RenderingAndSnapshot; public static fun onSessionStarted (Lcom/squareup/workflow1/WorkflowInterceptor;Lkotlinx/coroutines/CoroutineScope;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)V public static fun onSnapshotState (Lcom/squareup/workflow1/WorkflowInterceptor;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Lcom/squareup/workflow1/Snapshot; + public static fun onSnapshotStateWithChildren (Lcom/squareup/workflow1/WorkflowInterceptor;Lkotlin/jvm/functions/Function0;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Lcom/squareup/workflow1/TreeSnapshot; } public abstract interface class com/squareup/workflow1/WorkflowInterceptor$RenderContextInterceptor { diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/WorkflowInterceptor.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/WorkflowInterceptor.kt index 6cc3c808e0..ed2427839c 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/WorkflowInterceptor.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/WorkflowInterceptor.kt @@ -87,6 +87,17 @@ public interface WorkflowInterceptor { session: WorkflowSession ): S = proceed(old, new, state) + /** + * Intercept a full rendering pass which involves rendering then snapshotting the workflow tree. + * This is useful for tracing purposes. + */ + public fun onRenderAndSnapshot( + renderProps: P, + proceed: (P) -> RenderingAndSnapshot, + session: WorkflowSession + ): RenderingAndSnapshot = + proceed(renderProps) + /** * Intercepts calls to [StatefulWorkflow.render]. */ @@ -98,6 +109,15 @@ public interface WorkflowInterceptor { session: WorkflowSession ): R = proceed(renderProps, renderState, null) + /** + * Intercept calls to [StatefulWorkflow.snapshotState] including the children calls. + * This is useful to intercept a rendering + snapshot traversal for tracing purposes. + */ + public fun onSnapshotStateWithChildren( + proceed: () -> TreeSnapshot, + session: WorkflowSession + ): TreeSnapshot = proceed() + /** * Intercepts calls to [StatefulWorkflow.snapshotState]. */ diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/ChainedWorkflowInterceptor.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/ChainedWorkflowInterceptor.kt index 82493b0c8f..0fef705106 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/ChainedWorkflowInterceptor.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/ChainedWorkflowInterceptor.kt @@ -2,7 +2,9 @@ package com.squareup.workflow1.internal import com.squareup.workflow1.BaseRenderContext import com.squareup.workflow1.NoopWorkflowInterceptor +import com.squareup.workflow1.RenderingAndSnapshot import com.squareup.workflow1.Snapshot +import com.squareup.workflow1.TreeSnapshot import com.squareup.workflow1.Workflow import com.squareup.workflow1.WorkflowAction import com.squareup.workflow1.WorkflowInterceptor @@ -57,6 +59,19 @@ internal class ChainedWorkflowInterceptor( return chainedProceed(old, new, state) } + override fun onRenderAndSnapshot( + renderProps: P, + proceed: (P) -> RenderingAndSnapshot, + session: WorkflowSession + ): RenderingAndSnapshot { + val chainedProceed = interceptors.foldRight(proceed) { workflowInterceptor, proceedAcc -> + { renderProps -> + workflowInterceptor.onRenderAndSnapshot(renderProps, proceedAcc, session) + } + } + return chainedProceed(renderProps) + } + override fun onRender( renderProps: P, renderState: S, @@ -81,6 +96,18 @@ internal class ChainedWorkflowInterceptor( return chainedProceed(renderProps, renderState, null) } + override fun onSnapshotStateWithChildren( + proceed: () -> TreeSnapshot, + session: WorkflowSession + ): TreeSnapshot { + val chainedProceed = interceptors.foldRight(proceed) { workflowInterceptor, proceedAcc -> + { + workflowInterceptor.onSnapshotStateWithChildren(proceedAcc, session) + } + } + return chainedProceed() + } + override fun onSnapshotState( state: S, proceed: (S) -> Snapshot?, diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowNode.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowNode.kt index 525e1ccd60..724af1ec06 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowNode.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowNode.kt @@ -117,14 +117,16 @@ internal class WorkflowNode( fun snapshot(workflow: StatefulWorkflow<*, *, *, *>): TreeSnapshot { @Suppress("UNCHECKED_CAST") val typedWorkflow = workflow as StatefulWorkflow - val childSnapshots = subtreeManager.createChildSnapshots() - val rootSnapshot = interceptor.intercept(typedWorkflow, this) - .snapshotState(state) - return TreeSnapshot( - workflowSnapshot = rootSnapshot, - // Create the snapshots eagerly since subtreeManager is mutable. - childTreeSnapshots = { childSnapshots } - ) + return interceptor.onSnapshotStateWithChildren({ + val childSnapshots = subtreeManager.createChildSnapshots() + val rootSnapshot = interceptor.intercept(typedWorkflow, this) + .snapshotState(state) + TreeSnapshot( + workflowSnapshot = rootSnapshot, + // Create the snapshots eagerly since subtreeManager is mutable. + childTreeSnapshots = { childSnapshots } + ) + }, this) } override fun runningSideEffect( diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowNodeId.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowNodeId.kt index e8b765c2df..410d6567d9 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowNodeId.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowNodeId.kt @@ -9,6 +9,7 @@ import com.squareup.workflow1.writeByteStringWithLength import com.squareup.workflow1.writeUtf8WithLength import okio.Buffer import okio.ByteString +import kotlin.LazyThreadSafetyMode.NONE /** * Value type that can be used to distinguish between different workflows of different types or @@ -23,6 +24,14 @@ internal data class WorkflowNodeId( name: String = "" ) : this(workflow.identifier, name) + private val debugString by lazy(NONE) { + if (name.isBlank()) { + identifier.toString() + } else { + "$identifier named $name" + } + } + fun matches( otherWorkflow: Workflow<*, *, *>, otherName: String @@ -48,6 +57,8 @@ internal data class WorkflowNodeId( return WorkflowNodeId(identifier, name) } } + + override fun toString() = debugString } internal fun , I, O, R> diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowRunner.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowRunner.kt index f242662bf1..a1469528ee 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowRunner.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowRunner.kt @@ -26,7 +26,7 @@ internal class WorkflowRunner( protoWorkflow: Workflow, props: StateFlow, snapshot: TreeSnapshot?, - interceptor: WorkflowInterceptor, + private val interceptor: WorkflowInterceptor, private val runtimeConfig: RuntimeConfig ) { private val workflow = protoWorkflow.asStatefulWorkflow() @@ -65,9 +65,11 @@ internal class WorkflowRunner( * between every subsequent call to [processAction]. */ fun nextRendering(): RenderingAndSnapshot { - val rendering = rootNode.render(workflow, currentProps) - val snapshot = rootNode.snapshot(workflow) - return RenderingAndSnapshot(rendering, snapshot) + return interceptor.onRenderAndSnapshot(currentProps, { props -> + val rendering = rootNode.render(workflow, props) + val snapshot = rootNode.snapshot(workflow) + RenderingAndSnapshot(rendering, snapshot) + }, rootNode) } /** diff --git a/workflow-testing/api/workflow-testing.api b/workflow-testing/api/workflow-testing.api index de6e445ee3..5e4778bc9f 100644 --- a/workflow-testing/api/workflow-testing.api +++ b/workflow-testing/api/workflow-testing.api @@ -3,8 +3,10 @@ public final class com/squareup/workflow1/testing/RenderIdempotencyChecker : com public fun onInitialState (Ljava/lang/Object;Lcom/squareup/workflow1/Snapshot;Lkotlin/jvm/functions/Function2;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Ljava/lang/Object; public fun onPropsChanged (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Lkotlin/jvm/functions/Function3;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Ljava/lang/Object; 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; + public fun onRenderAndSnapshot (Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Lcom/squareup/workflow1/RenderingAndSnapshot; public fun onSessionStarted (Lkotlinx/coroutines/CoroutineScope;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)V public fun onSnapshotState (Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Lcom/squareup/workflow1/Snapshot; + public fun onSnapshotStateWithChildren (Lkotlin/jvm/functions/Function0;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Lcom/squareup/workflow1/TreeSnapshot; } public abstract interface class com/squareup/workflow1/testing/RenderTestResult { diff --git a/workflow-testing/src/test/java/com/squareup/workflow1/testing/RealRenderTesterTest.kt b/workflow-testing/src/test/java/com/squareup/workflow1/testing/RealRenderTesterTest.kt index 43798bfb65..e1b12084de 100644 --- a/workflow-testing/src/test/java/com/squareup/workflow1/testing/RealRenderTesterTest.kt +++ b/workflow-testing/src/test/java/com/squareup/workflow1/testing/RealRenderTesterTest.kt @@ -82,7 +82,7 @@ internal class RealRenderTesterTest { assertEquals( "Expected only one output to be expected: " + - "child worker ${typeOf>()} expected to emit " + + "child ${typeOf>()} expected to emit " + "kotlin.Unit but WorkflowAction.noAction() was already processed.", failure.message ) @@ -591,7 +591,7 @@ internal class RealRenderTesterTest { tester.render() } assertEquals( - "Tried to render unexpected child worker ${typeOf()}", + "Tried to render unexpected child ${typeOf()}", error.message ) } @@ -698,7 +698,7 @@ internal class RealRenderTesterTest { } assertEquals( """ - Multiple expectations matched child worker ${typeOf()}: + Multiple expectations matched child ${typeOf()}: worker TestWorker duplicate expectation """.trimIndent(), @@ -754,7 +754,7 @@ internal class RealRenderTesterTest { tester.render() } assertEquals( - "Expected keys to be unique for worker " + + "Expected keys to be unique for " + "com.squareup.workflow1.Worker: key=\"\"", error.message ) diff --git a/workflow-tracing/api/workflow-tracing.api b/workflow-tracing/api/workflow-tracing.api index 06036c84af..df1698a239 100644 --- a/workflow-tracing/api/workflow-tracing.api +++ b/workflow-tracing/api/workflow-tracing.api @@ -15,8 +15,10 @@ public final class com/squareup/workflow1/diagnostic/tracing/TracingWorkflowInte public fun onInitialState (Ljava/lang/Object;Lcom/squareup/workflow1/Snapshot;Lkotlin/jvm/functions/Function2;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Ljava/lang/Object; public fun onPropsChanged (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Lkotlin/jvm/functions/Function3;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Ljava/lang/Object; 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; + public fun onRenderAndSnapshot (Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Lcom/squareup/workflow1/RenderingAndSnapshot; public fun onSessionStarted (Lkotlinx/coroutines/CoroutineScope;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)V public fun onSnapshotState (Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Lcom/squareup/workflow1/Snapshot; + public fun onSnapshotStateWithChildren (Lkotlin/jvm/functions/Function0;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Lcom/squareup/workflow1/TreeSnapshot; } public final class com/squareup/workflow1/diagnostic/tracing/TracingWorkflowInterceptorKt { diff --git a/workflow-tracing/src/test/resources/com/squareup/workflow1/diagnostic/tracing/expected_trace_file.txt b/workflow-tracing/src/test/resources/com/squareup/workflow1/diagnostic/tracing/expected_trace_file.txt index 2e711ec3ea..837a53df3f 100644 --- a/workflow-tracing/src/test/resources/com/squareup/workflow1/diagnostic/tracing/expected_trace_file.txt +++ b/workflow-tracing/src/test/resources/com/squareup/workflow1/diagnostic/tracing/expected_trace_file.txt @@ -60,8 +60,8 @@ {"name":"used/free memory","ph":"C","ts":0,"pid":0,"tid":0,"args":{"usedMemory":1,"freeMemory":42}}, {"name":"Snapshot","ph":"B","ts":0,"pid":0,"tid":0,"args":{}}, {"name":"Snapshot","ph":"E","ts":0,"pid":0,"tid":0,"args":{}}, -{"name":"Sink received: Worker (2)","cat":"update","ph":"i","ts":0,"pid":0,"tid":0,"s":"t","args":{"action":"sendAndAwaitApplication(com.squareup.workflow1.EmitWorkerOutputAction(worker=TypedWorker(java.lang.String (Kotlin reflection is not available)), key=\"\"))"}}, -{"name":"WorkflowAction: Worker (2)","cat":"update","ph":"i","ts":0,"pid":0,"tid":0,"s":"p","args":{"action":"sendAndAwaitApplication(com.squareup.workflow1.EmitWorkerOutputAction(worker=TypedWorker(java.lang.String (Kotlin reflection is not available)), key=\"\"))","oldState":"0","newState":"{no change}","output":"fired!"}}, +{"name":"Sink received: Worker (2)","cat":"update","ph":"i","ts":0,"pid":0,"tid":0,"s":"t","args":{"action":"sendAndAwaitApplication(com.squareup.workflow1.EmitWorkerOutputAction(worker=TypedWorker(java.lang.String (Kotlin reflection is not available)), key=))"}}, +{"name":"WorkflowAction: Worker (2)","cat":"update","ph":"i","ts":0,"pid":0,"tid":0,"s":"p","args":{"action":"sendAndAwaitApplication(com.squareup.workflow1.EmitWorkerOutputAction(worker=TypedWorker(java.lang.String (Kotlin reflection is not available)), key=))","oldState":"0","newState":"{no change}","output":"fired!"}}, {"name":"Worker (2)","ph":"O","ts":0,"pid":0,"tid":0,"id":"2","args":{"snapshot":"0"}}, {"name":"Render Pass","cat":"rendering","ph":"B","ts":0,"pid":0,"tid":0,"args":{"props":"3"}}, {"name":"used/free memory","ph":"C","ts":0,"pid":0,"tid":0,"args":{"usedMemory":1,"freeMemory":42}},