From b7732ca818d434193fb7be470cb37c2bb1f3d7c2 Mon Sep 17 00:00:00 2001 From: Stephen Edwards Date: Tue, 28 Jun 2022 17:58:06 -0400 Subject: [PATCH 1/3] Compose Runtime Integration Take 2 Still todo on the Snapshotting and figure out how we want to 'freeze' to guard against out of lifecycle calls. --- .../complex-poetry/build.gradle.kts | 1 + .../complex/poetry/ComposeInheritanceTest.kt | 59 ++++++++ .../complex/poetry/RenderPassTest.kt | 2 +- .../poetry/MaybeLoadingGatekeeperWorkflow.kt | 27 ++++ .../complex/poetry/PerformancePoemWorkflow.kt | 143 ++++++++++++++++++ .../poetry/PerformancePoemsBrowserWorkflow.kt | 95 +++++++++++- .../poetry/PerformancePoetryActivity.kt | 6 +- .../ActionHandlingTracingInterceptor.kt | 14 ++ .../PerformanceTracingInterceptor.kt | 50 ++++-- .../RenderPassCountingInterceptor.kt | 39 ++++- build.gradle.kts | 4 + gradle/libs.versions.toml | 32 ++-- samples/compose-samples/build.gradle.kts | 2 +- .../sample/poetry/PoemListWorkflow.kt | 9 ++ .../sample/poetry/RealPoemWorkflow.kt | 10 ++ .../sample/poetry/RealPoemsBrowserWorkflow.kt | 10 ++ .../sample/poetry/StanzaListWorkflow.kt | 9 ++ .../squareup/sample/poetry/StanzaWorkflow.kt | 9 ++ settings.gradle.kts | 4 + workflow-core/api/workflow-core.api | 20 +++ workflow-core/build.gradle.kts | 6 +- .../squareup/workflow1/BaseRenderContext.kt | 41 +++++ .../squareup/workflow1/StatefulWorkflow.kt | 94 ++++++++---- .../squareup/workflow1/StatelessWorkflow.kt | 36 ++++- .../com/squareup/workflow1/WorkerWorkflow.kt | 10 ++ .../kotlin/com/squareup/workflow1/Workflow.kt | 9 ++ .../workflow1/ComposeInheritanceTest.kt | 68 +++++++++ workflow-runtime/api/workflow-runtime.api | 58 ++++++- workflow-runtime/build.gradle.kts | 6 +- .../com/squareup/workflow1/RenderWorkflow.kt | 58 +++++-- .../com/squareup/workflow1/RuntimeConfig.kt | 10 +- .../SimpleLoggingWorkflowInterceptor.kt | 12 ++ .../squareup/workflow1/WorkflowInterceptor.kt | 69 +++++++++ .../workflow1/WorkflowRuntimeClock.kt | 81 ++++++++++ .../internal/ChainedWorkflowInterceptor.kt | 53 +++++++ .../workflow1/internal/RealRenderContext.kt | 26 +++- .../workflow1/internal/SubtreeManager.kt | 37 ++++- .../workflow1/internal/SystemUtils.kt | 5 + .../workflow1/internal/WorkflowChildNode.kt | 20 +++ .../workflow1/internal/WorkflowNode.kt | 108 +++++++++++-- .../workflow1/internal/WorkflowRunner.kt | 30 +++- .../SimpleLoggingWorkflowInterceptorTest.kt | 11 ++ .../workflow1/WorkflowInterceptorTest.kt | 45 ++++++ .../ChainedWorkflowInterceptorTest.kt | 11 ++ .../internal/RealRenderContextTest.kt | 29 ++++ .../workflow1/internal/SystemUtils.kt | 2 + workflow-testing/api/workflow-testing.api | 2 + .../workflow1/testing/RealRenderTester.kt | 64 ++++++++ workflow-tracing/api/workflow-tracing.api | 1 + workflow-ui/compose-tooling/build.gradle.kts | 2 +- workflow-ui/compose/build.gradle.kts | 2 +- 51 files changed, 1447 insertions(+), 104 deletions(-) create mode 100644 benchmarks/performance-poetry/complex-poetry/src/androidTest/java/com/squareup/benchmarks/performance/complex/poetry/ComposeInheritanceTest.kt create mode 100644 workflow-core/src/commonTest/kotlin/com/squareup/workflow1/ComposeInheritanceTest.kt create mode 100644 workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/WorkflowRuntimeClock.kt diff --git a/benchmarks/performance-poetry/complex-poetry/build.gradle.kts b/benchmarks/performance-poetry/complex-poetry/build.gradle.kts index 1b141a7fb3..5211e244bf 100644 --- a/benchmarks/performance-poetry/complex-poetry/build.gradle.kts +++ b/benchmarks/performance-poetry/complex-poetry/build.gradle.kts @@ -2,6 +2,7 @@ plugins { id("com.android.application") `kotlin-android` id("kotlin-parcelize") + id("app.cash.molecule") } android { compileSdk = 32 diff --git a/benchmarks/performance-poetry/complex-poetry/src/androidTest/java/com/squareup/benchmarks/performance/complex/poetry/ComposeInheritanceTest.kt b/benchmarks/performance-poetry/complex-poetry/src/androidTest/java/com/squareup/benchmarks/performance/complex/poetry/ComposeInheritanceTest.kt new file mode 100644 index 0000000000..a1ebe2d7bd --- /dev/null +++ b/benchmarks/performance-poetry/complex-poetry/src/androidTest/java/com/squareup/benchmarks/performance/complex/poetry/ComposeInheritanceTest.kt @@ -0,0 +1,59 @@ +package com.squareup.benchmarks.performance.complex.poetry + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.test.ext.junit.runners.AndroidJUnit4 +import app.cash.molecule.AndroidUiDispatcher +import app.cash.molecule.launchMolecule +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class ComposeInheritanceTest { + + abstract class Parent { + @Composable + open fun AComposable( + hoistToggleState: @Composable (s: T) -> Unit + ): Unit = throw IllegalStateException("I don't want to be a parent.") + } + + class Child( + private val payload: T + ): Parent() { + @Composable + override fun AComposable( + hoistToggleState: @Composable (s: T) -> Unit + ) { + println("Can you hear me now? $payload") + hoistToggleState(payload) + } + } + + @Composable + fun Emitter(someObject: Parent): T? { + val payload: MutableState = remember { mutableStateOf(null) } + someObject.AComposable { + payload.value = it + } + return payload.value + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test fun testComposableOverloading() { + val child: Parent = Child("a test") + val testScope = CoroutineScope(AndroidUiDispatcher.Main) + + val testFlow = testScope.launchMolecule { + Emitter(child) + } + + assertThat(testFlow.value).isEqualTo("a test") + } + +} diff --git a/benchmarks/performance-poetry/complex-poetry/src/androidTest/java/com/squareup/benchmarks/performance/complex/poetry/RenderPassTest.kt b/benchmarks/performance-poetry/complex-poetry/src/androidTest/java/com/squareup/benchmarks/performance/complex/poetry/RenderPassTest.kt index 611b9288d2..b3e611f5b2 100644 --- a/benchmarks/performance-poetry/complex-poetry/src/androidTest/java/com/squareup/benchmarks/performance/complex/poetry/RenderPassTest.kt +++ b/benchmarks/performance-poetry/complex-poetry/src/androidTest/java/com/squareup/benchmarks/performance/complex/poetry/RenderPassTest.kt @@ -270,7 +270,7 @@ class RenderPassTest { staleRenderedNodes = 2350..2350 ), frameTimeoutExpectation = RenderExpectation( - totalPasses = 88..97, + totalPasses = 64..68, freshRenderedNodes = 106..108, staleRenderedNodes = 679..698 ) diff --git a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/MaybeLoadingGatekeeperWorkflow.kt b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/MaybeLoadingGatekeeperWorkflow.kt index d6c64532ec..4efcdcb185 100644 --- a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/MaybeLoadingGatekeeperWorkflow.kt +++ b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/MaybeLoadingGatekeeperWorkflow.kt @@ -1,5 +1,6 @@ package com.squareup.benchmarks.performance.complex.poetry +import androidx.compose.runtime.Composable import com.squareup.benchmarks.performance.complex.poetry.instrumentation.ActionHandlingTracingInterceptor import com.squareup.benchmarks.performance.complex.poetry.instrumentation.asTraceableWorker import com.squareup.benchmarks.performance.complex.poetry.views.LoaderSpinner @@ -48,5 +49,31 @@ class MaybeLoadingGatekeeperWorkflow( ) } + @Composable + override fun Rendering( + renderProps: Unit, + renderState: IsLoading, + context: RenderContext, + ): MayBeLoadingScreen { + context.runningWorker(isLoading.asTraceableWorker("GatekeeperLoading")) { + action { + state = it + } + } + val maybeLoadingChild = context.ChildRendering( + childWithLoading, childProps, "", + ) { + action(ActionHandlingTracingInterceptor.keyForTrace("GatekeeperChildFinished")) { + setOutput( + Unit + ) + } + } + return MayBeLoadingScreen( + baseScreen = maybeLoadingChild, + loaders = if (renderState) listOf(LoaderSpinner) else emptyList() + ) + } + override fun snapshotState(state: IsLoading): Snapshot? = null } diff --git a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/PerformancePoemWorkflow.kt b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/PerformancePoemWorkflow.kt index a24d2cf554..82a72c6ddc 100644 --- a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/PerformancePoemWorkflow.kt +++ b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/PerformancePoemWorkflow.kt @@ -1,5 +1,9 @@ package com.squareup.benchmarks.performance.complex.poetry +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import com.squareup.benchmarks.performance.complex.poetry.PerformancePoemWorkflow.Action.ClearSelection import com.squareup.benchmarks.performance.complex.poetry.PerformancePoemWorkflow.Action.HandleStanzaListOutput import com.squareup.benchmarks.performance.complex.poetry.PerformancePoemWorkflow.Action.SelectNext @@ -16,6 +20,7 @@ import com.squareup.benchmarks.performance.complex.poetry.views.BlankScreen import com.squareup.sample.container.overviewdetail.OverviewDetailScreen import com.squareup.sample.poetry.PoemWorkflow import com.squareup.sample.poetry.PoemWorkflow.ClosePoem +import com.squareup.sample.poetry.StanzaListScreen import com.squareup.sample.poetry.StanzaListWorkflow import com.squareup.sample.poetry.StanzaListWorkflow.NO_SELECTED_STANZA import com.squareup.sample.poetry.StanzaScreen @@ -57,6 +62,7 @@ import kotlinx.coroutines.flow.flow * break ties/conflicts with a token in the start/stop requests. We leave that complexity out * here. ** */ +@OptIn(WorkflowUiExperimentalApi::class) class PerformancePoemWorkflow( private val simulatedPerfConfig: SimulatedPerfConfig = SimulatedPerfConfig.NO_SIMULATED_PERF, private val isLoading: MutableStateFlow, @@ -234,6 +240,143 @@ class PerformancePoemWorkflow( } } + @Composable + override fun Rendering( + renderProps: Poem, + renderState: State, + context: RenderContext, + ): OverviewDetailScreen { + when (renderState) { + Initializing -> { + // Again, the entire `Initializing` state is a smell, which is most obvious from the + // use of `Worker.from { Unit }`. A Worker doing no work and only shuttling the state + // along is usually the sign you have an extraneous state that can be collapsed! + // Don't try this at home. + context.runningWorker( + Worker.from { + isLoading.value = true + }, + "initializing" + ) { + action { + isLoading.value = false + state = Selected(NO_SELECTED_STANZA) + } + } + return OverviewDetailScreen(overviewRendering = BackStackScreen(BlankScreen)) + } + else -> { + val (stanzaIndex, currentStateIsLoading, repeat) = when (renderState) { + is ComplexCall -> Triple(renderState.payload, true, renderState.repeater) + is Selected -> Triple(renderState.stanzaIndex, false, 0) + Initializing -> throw IllegalStateException("No longer initializing.") + } + + if (currentStateIsLoading) { + if (repeat > 0) { + // Running a flow that emits 'repeat' number of times + context.runningWorker( + flow { + while (true) { + // As long as this Worker is running we want to be emitting values. + delay(2) + emit(repeat) + } + }.asTraceableWorker("EventRepetition") + ) { + action { + (state as? ComplexCall)?.let { currentState -> + // Still repeating the complex call + state = ComplexCall( + payload = currentState.payload, + repeater = (currentState.repeater - 1).coerceAtLeast(0) + ) + } + } + } + } else { + context.runningWorker( + worker = TraceableWorker.from("PoemLoading") { + isLoading.value = true + delay(simulatedPerfConfig.complexityDelay) + // No Output for Worker is necessary because the selected index + // is already in the state. + } + ) { + action { + isLoading.value = false + (state as? ComplexCall)?.let { currentState -> + state = Selected(currentState.payload) + } + } + } + } + } + + val stanzaListOverview = context.ChildRendering( + StanzaListWorkflow, + StanzaListWorkflow.Props( + poem = renderProps, + eventHandlerTag = ActionHandlingTracingInterceptor::keyForTrace + ), + key = "", + ) { selected -> + HandleStanzaListOutput(simulatedPerfConfig, selected) + } + .copy(selection = stanzaIndex) + + if (stanzaIndex != NO_SELECTED_STANZA) { + val previousStanzas = renderProps.stanzas.subList(0, stanzaIndex) + .mapIndexed { index, _ -> + context.ChildRendering( + StanzaWorkflow, + Props( + poem = renderProps, + index = index, + eventHandlerTag = ActionHandlingTracingInterceptor::keyForTrace + ), + key = "$index", + ) { + noAction() + } + } + val visibleStanza = context.ChildRendering( + StanzaWorkflow, + Props( + poem = renderProps, + index = stanzaIndex, + eventHandlerTag = ActionHandlingTracingInterceptor::keyForTrace + ), + key = "$stanzaIndex", + ) { + when (it) { + CloseStanzas -> ClearSelection(simulatedPerfConfig) + ShowPreviousStanza -> SelectPrevious(simulatedPerfConfig) + ShowNextStanza -> SelectNext(simulatedPerfConfig) + } + } + + val stackedStanzas = visibleStanza.let { + (previousStanzas + it).toBackStackScreen() + } + + return OverviewDetailScreen( + overviewRendering = BackStackScreen(stanzaListOverview), + detailRendering = stackedStanzas + ) + } + + + return OverviewDetailScreen( + overviewRendering = BackStackScreen(stanzaListOverview), + selectDefault = { + context.actionSink.send(HandleStanzaListOutput(simulatedPerfConfig, 0)) + } + ) + } + } + } + override fun snapshotState(state: State): Snapshot? = null internal sealed class Action : WorkflowAction() { diff --git a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/PerformancePoemsBrowserWorkflow.kt b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/PerformancePoemsBrowserWorkflow.kt index bb12e905c7..287fc30510 100644 --- a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/PerformancePoemsBrowserWorkflow.kt +++ b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/PerformancePoemsBrowserWorkflow.kt @@ -1,5 +1,6 @@ package com.squareup.benchmarks.performance.complex.poetry +import androidx.compose.runtime.Composable import com.squareup.benchmarks.performance.complex.poetry.PerformancePoemsBrowserWorkflow.State import com.squareup.benchmarks.performance.complex.poetry.PerformancePoemsBrowserWorkflow.State.ComplexCall import com.squareup.benchmarks.performance.complex.poetry.PerformancePoemsBrowserWorkflow.State.Initializing @@ -42,6 +43,7 @@ import kotlinx.coroutines.flow.MutableStateFlow * break ties/conflicts with a token in the start/stop requests. We leave that complexity out * here. ** */ +@OptIn(WorkflowUiExperimentalApi::class) class PerformancePoemsBrowserWorkflow( private val simulatedPerfConfig: SimulatedPerfConfig, private val poemWorkflow: PoemWorkflow, @@ -70,7 +72,6 @@ class PerformancePoemsBrowserWorkflow( return if (simulatedPerfConfig.useInitializingState) Initializing else NoSelection } - @OptIn(WorkflowUiExperimentalApi::class) override fun render( renderProps: List, renderState: State, @@ -154,6 +155,98 @@ class PerformancePoemsBrowserWorkflow( } } + @Composable + override fun Rendering( + renderProps: List, + renderState: State, + context: RenderContext, + ): OverviewDetailScreen { + val poemListProps = Props( + poems = renderProps, + eventHandlerTag = ActionHandlingTracingInterceptor::keyForTrace + ) + + val poemListRendering = context.ChildRendering( + child = PoemListWorkflow, + props = poemListProps, + key = "", + ) { selected -> + choosePoem(selected) + } + when (renderState) { + // Again, then entire `Initializing` state is a smell, which is most obvious from the + // use of `Worker.from { Unit }`. A Worker doing no work and only shuttling the state + // along is usually the sign you have an extraneous state that can be collapsed! + // Don't try this at home. + is Initializing -> { + context.runningWorker(TraceableWorker.from("BrowserInitializing") { Unit }, "init") { + isLoading.value = true + action { + isLoading.value = false + state = NoSelection + } + } + return OverviewDetailScreen(overviewRendering = BackStackScreen(BlankScreen)) + } + is NoSelection -> { + return OverviewDetailScreen( + overviewRendering = BackStackScreen( + poemListRendering.copy(selection = NO_POEM_SELECTED) + ) + ) + } + is ComplexCall -> { + context.runningWorker( + TraceableWorker.from("ComplexCallBrowser(${renderState.payload})") { + isLoading.value = true + delay(simulatedPerfConfig.complexityDelay) + // No Output for Worker is necessary because the selected index + // is already in the state. + } + ) { + action { + isLoading.value = false + (state as? ComplexCall)?.let { currentState -> + state = if (currentState.payload != NO_POEM_SELECTED) { + Selected(currentState.payload) + } else { + NoSelection + } + } + } + } + val poemOverview = OverviewDetailScreen( + overviewRendering = BackStackScreen( + poemListRendering.copy(selection = renderState.payload) + ) + ) + val poems = if (renderState.payload != NO_POEM_SELECTED) { + poemOverview + context.ChildRendering( + poemWorkflow, + renderProps[renderState.payload], + key = "", + ) { clearSelection } + } else { + poemOverview + } + return poems + } + is Selected -> { + val poemOverview = OverviewDetailScreen( + overviewRendering = BackStackScreen( + poemListRendering.copy(selection = renderState.poemIndex) + ) + ) + return poemOverview + context.ChildRendering( + poemWorkflow, + renderProps[renderState.poemIndex], + key = "", + ) { clearSelection } + } + } + + } + override fun snapshotState(state: State): Snapshot? = null private fun choosePoem( diff --git a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/PerformancePoetryActivity.kt b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/PerformancePoetryActivity.kt index 19d1784236..d3c3a714f0 100644 --- a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/PerformancePoetryActivity.kt +++ b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/PerformancePoetryActivity.kt @@ -84,7 +84,11 @@ class PerformancePoetryActivity : AppCompatActivity() { } val isFrameTimeout = intent.getBooleanExtra(EXTRA_RUNTIME_FRAME_TIMEOUT, false) - val runtimeConfig = if (isFrameTimeout) FrameTimeout() else RenderPerAction + val runtimeConfig = if (isFrameTimeout) { + FrameTimeout(useComposeInRuntime = true) + } else { + RenderPerAction + } val component = PerformancePoetryComponent(installedInterceptor, simulatedPerfConfig, runtimeConfig) diff --git a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/instrumentation/ActionHandlingTracingInterceptor.kt b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/instrumentation/ActionHandlingTracingInterceptor.kt index 9d851dbb12..9890679503 100644 --- a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/instrumentation/ActionHandlingTracingInterceptor.kt +++ b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/instrumentation/ActionHandlingTracingInterceptor.kt @@ -1,5 +1,6 @@ package com.squareup.benchmarks.performance.complex.poetry.instrumentation +import androidx.compose.runtime.Composable import androidx.tracing.trace import com.squareup.workflow1.BaseRenderContext import com.squareup.workflow1.WorkflowAction @@ -70,6 +71,19 @@ class ActionHandlingTracingInterceptor : WorkflowInterceptor, Resettable { ) } + @Composable + override fun Rendering( + renderProps: P, + renderState: S, + context: BaseRenderContext, + session: WorkflowSession, + proceed: @Composable (P, S, RenderContextInterceptor?) -> R + ): R = proceed( + renderProps, + renderState, + EventHandlingTracingRenderContextInterceptor(actionCounts) + ) + override fun reset() { actionCounts.clear() } diff --git a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/instrumentation/PerformanceTracingInterceptor.kt b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/instrumentation/PerformanceTracingInterceptor.kt index 4b0756915c..ebcf11f82d 100644 --- a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/instrumentation/PerformanceTracingInterceptor.kt +++ b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/instrumentation/PerformanceTracingInterceptor.kt @@ -1,5 +1,6 @@ package com.squareup.benchmarks.performance.complex.poetry.instrumentation +import androidx.compose.runtime.Composable import androidx.tracing.Trace import com.squareup.benchmarks.performance.complex.poetry.PerformancePoemWorkflow import com.squareup.benchmarks.performance.complex.poetry.PerformancePoemsBrowserWorkflow @@ -28,8 +29,35 @@ class PerformanceTracingInterceptor( proceed: (P, S, RenderContextInterceptor?) -> R, session: WorkflowSession ): R { - val isRoot = session.parent == null val traceIdIndex = NODES_TO_TRACE.indexOfFirst { it.second == session.identifier } + val isRoot = before(traceIdIndex, session) + return proceed(renderProps, renderState, null).also { + after(traceIdIndex = traceIdIndex, isRoot = isRoot) + } + } + + @Composable + override fun Rendering( + renderProps: P, + renderState: S, + context: BaseRenderContext, + session: WorkflowSession, + proceed: @Composable (P, S, RenderContextInterceptor?) -> R + ): R { + // TODO: Fix that these are illegal side effects in a Composable + val traceIdIndex = NODES_TO_TRACE.indexOfFirst { it.second == session.identifier } + val isRoot = before(traceIdIndex, session) + return proceed(renderProps, renderState, null).also { + after(traceIdIndex = traceIdIndex, isRoot = isRoot) + } + } + + private fun before( + traceIdIndex: Int, + session: WorkflowSession + ): Boolean { + val isRoot = session.parent == null + val renderPassMarker = totalRenderPasses.toString() .padStart(RENDER_PASS_DIGITS, '0') @@ -44,17 +72,21 @@ class PerformanceTracingInterceptor( "${NODES_TO_TRACE[traceIdIndex].first}_" Trace.beginSection(sectionName) } + return isRoot + } - return proceed(renderProps, renderState, null).also { - if (traceIdIndex > -1 && !sample) { + private fun after( + traceIdIndex: Int, + isRoot: Boolean + ) { + if (traceIdIndex > -1 && !sample) { + Trace.endSection() + } + if (isRoot) { + if (!sample || totalRenderPasses.mod(2) == 0) { Trace.endSection() } - if (isRoot) { - if (!sample || totalRenderPasses.mod(2) == 0) { - Trace.endSection() - } - totalRenderPasses++ - } + totalRenderPasses++ } } diff --git a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/instrumentation/RenderPassCountingInterceptor.kt b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/instrumentation/RenderPassCountingInterceptor.kt index 748b691324..e6d27d9fdd 100644 --- a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/instrumentation/RenderPassCountingInterceptor.kt +++ b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/instrumentation/RenderPassCountingInterceptor.kt @@ -1,5 +1,6 @@ package com.squareup.benchmarks.performance.complex.poetry.instrumentation +import androidx.compose.runtime.Composable import com.squareup.workflow1.BaseRenderContext import com.squareup.workflow1.WorkflowInterceptor import com.squareup.workflow1.WorkflowInterceptor.RenderContextInterceptor @@ -25,6 +26,33 @@ class RenderPassCountingInterceptor : WorkflowInterceptor, Resettable { proceed: (P, S, RenderContextInterceptor?) -> R, session: WorkflowSession ): R { + val isRoot = before(session, renderState) + return proceed(renderProps, renderState, null).also { + after(isRoot) + } + } + + @Composable + override fun Rendering( + renderProps: P, + renderState: S, + context: BaseRenderContext, + session: WorkflowSession, + proceed: @Composable + (P, S, RenderContextInterceptor?) -> R + ): R { + // TODO: This counting is a side effect that is technically illegal here in a Composable and it + // is certainly not idempotent. + val isRoot = before(session, renderState) + return proceed(renderProps, renderState, null).also { + after(isRoot) + } + } + + private fun before( + session: WorkflowSession, + renderState: S + ): Boolean { val isRoot = session.parent == null if (isRoot) { @@ -46,12 +74,13 @@ class RenderPassCountingInterceptor : WorkflowInterceptor, Resettable { } nodeStates[session.sessionId] = renderStateString } + return isRoot + } - return proceed(renderProps, renderState, null).also { - if (isRoot) { - renderEfficiencyTracking.totalRenderPasses += 1 - renderEfficiencyTracking.totalNodeStats += renderPassStats - } + private fun after(isRoot: Boolean) { + if (isRoot) { + renderEfficiencyTracking.totalRenderPasses += 1 + renderEfficiencyTracking.totalNodeStats += renderPassStats } } diff --git a/build.gradle.kts b/build.gradle.kts index 5ab4bf20c4..463d690d8e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,6 +10,7 @@ buildscript { classpath(libs.kotlin.serialization.gradle.plugin) classpath(libs.kotlinx.binaryCompatibility.gradle.plugin) classpath(libs.kotlin.gradle.plugin) + classpath(libs.molecule.gradle.plugin) classpath(libs.google.ksp) classpath(libs.ktlint.gradle) classpath(libs.vanniktech.publish) @@ -19,6 +20,9 @@ buildscript { mavenCentral() gradlePluginPortal() google() + maven { + url = uri("https://oss.sonatype.org/content/repositories/snapshots/") + } // For binary compatibility validator. maven { url = uri("https://kotlin.bintray.com/kotlinx") } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e572fc5df9..924ed0edb7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,8 +9,6 @@ targetSdk = "30" androidx-activity = "1.3.0" androidx-appcompat = "1.3.1" androidx-benchmark = "1.1.0-rc03" -androidx-compose = "1.1.0-rc01" -androidx-compose-compiler = "1.1.0-rc02" androidx-constraintlayout = "2.1.2" androidx-core = "1.6.0" androidx-fragment = "1.3.6" @@ -32,6 +30,9 @@ androidx-transition = "1.4.1" androidx-viewbinding = "4.2.1" androidx-work = "2.6.0" +compose = "1.1.0" +compose-compiler = "1.1.0" + detekt = "1.19.0" dokka = "1.5.31" dependencyGuard = "0.1.0" @@ -49,8 +50,8 @@ kotest = "5.1.0" kotlin = "1.6.10" kotlinx-binary-compatibility = "0.6.0" -kotlinx-coroutines = "1.5.1" -# The 1.5.1 test artifact is jvm-only. The commonTest module should use 1.6.1. +kotlinx-coroutines = "1.5.2" +# The 1.5.2 test artifact is jvm-only. The commonTest module should use 1.6.1. kotlinx-coroutines-test-common = "1.6.1" kotlinx-serialization-json = "1.3.2" kotlinx-benchmark = "0.4.2" @@ -64,6 +65,9 @@ mockito-core = "3.3.3" mockito-kotlin = "3.2.0" mockk = "1.11.0" + +molecule = "0.3.0-SNAPSHOT" + robolectric = "4.6.1" rxjava2-android = "2.1.1" @@ -100,8 +104,12 @@ kotlinx-benchmark = { id = "org.jetbrains.kotlinx.benchmark", version.ref = "kot ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" } mavenPublish = { id = "com.vanniktech.maven.publish", version.ref = "vanniktech-publish" } +molecule = { id = "app.cash.molecule", version.ref = "molecule" } + [libraries] +molecule-gradle-plugin = { module = "app.cash.molecule:molecule-gradle-plugin", version.ref = "molecule"} + android-gradle-plugin = { module = "com.android.tools.build:gradle", version.ref = "androidTools" } androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity" } @@ -112,13 +120,16 @@ androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "a androidx-macro-benchmark = { module = "androidx.benchmark:benchmark-macro-junit4", version.ref = "androidx-benchmark" } -androidx-compose-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "androidx-compose" } +androidx-compose-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "compose" } + +androidx-compose-material = { module = "androidx.compose.material:material", version.ref = "compose" } -androidx-compose-material = { module = "androidx.compose.material:material", version.ref = "androidx-compose" } +androidx-compose-ui = { module = "androidx.compose.ui:ui", version.ref = "compose" } +androidx-compose-ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "compose" } +androidx-compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose" } -androidx-compose-ui = { module = "androidx.compose.ui:ui", version.ref = "androidx-compose" } -androidx-compose-ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "androidx-compose" } -androidx-compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "androidx-compose" } +compose-runtime = { module = "org.jetbrains.compose.runtime:runtime", version.ref = "compose"} +compose-compiler = { module = "org.jetbrains.compose.compiler:compiler", version.ref = "compose"} androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "androidx-constraintlayout" } @@ -203,6 +214,9 @@ mockito-kotlin = { module = "org.mockito.kotlin:mockito-kotlin", version.ref = " mockk = { module = "io.mockk:mockk", version.ref = "mockk" } +molecule-runtime = { module = "app.cash.molecule:molecule-runtime", version.ref = "molecule"} +molecule-testing = { module = "app.cash.molecule:molecule-testing", version.ref = "molecule"} + robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" } rxjava2-rxandroid = { module = "io.reactivex.rxjava2:rxandroid", version.ref = "rxjava2-android" } diff --git a/samples/compose-samples/build.gradle.kts b/samples/compose-samples/build.gradle.kts index c7ee68f31a..91908dac29 100644 --- a/samples/compose-samples/build.gradle.kts +++ b/samples/compose-samples/build.gradle.kts @@ -14,7 +14,7 @@ android { compose = true } composeOptions { - kotlinCompilerExtensionVersion = libs.versions.androidx.compose.compiler.get() + kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get() } } diff --git a/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/PoemListWorkflow.kt b/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/PoemListWorkflow.kt index a9ab0f5f05..0f92a045e0 100644 --- a/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/PoemListWorkflow.kt +++ b/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/PoemListWorkflow.kt @@ -1,5 +1,6 @@ package com.squareup.sample.poetry +import androidx.compose.runtime.Composable import com.squareup.sample.poetry.PoemListWorkflow.Props import com.squareup.sample.poetry.model.Poem import com.squareup.workflow1.StatelessWorkflow @@ -25,4 +26,12 @@ object PoemListWorkflow : StatelessWorkflow() { ) { index -> setOutput(index) } ) } + + @Composable + override fun Rendering( + renderProps: Props, + context: RenderContext + ): PoemListScreen { + return render(renderProps, context) + } } diff --git a/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/RealPoemWorkflow.kt b/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/RealPoemWorkflow.kt index 11fb9ab692..b82e6dfe66 100644 --- a/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/RealPoemWorkflow.kt +++ b/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/RealPoemWorkflow.kt @@ -1,5 +1,6 @@ package com.squareup.sample.poetry +import androidx.compose.runtime.Composable import com.squareup.sample.container.overviewdetail.OverviewDetailScreen import com.squareup.sample.poetry.PoemWorkflow.ClosePoem import com.squareup.sample.poetry.RealPoemWorkflow.Action.ClearSelection @@ -116,4 +117,13 @@ class RealPoemWorkflow : PoemWorkflow, } } } + + @Composable + override fun Rendering( + renderProps: Poem, + renderState: SelectedStanza, + context: RenderContext, + ): OverviewDetailScreen { + TODO("Not yet implemented") + } } diff --git a/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/RealPoemsBrowserWorkflow.kt b/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/RealPoemsBrowserWorkflow.kt index 09b579868d..54eef60e69 100644 --- a/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/RealPoemsBrowserWorkflow.kt +++ b/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/RealPoemsBrowserWorkflow.kt @@ -1,5 +1,6 @@ package com.squareup.sample.poetry +import androidx.compose.runtime.Composable import com.squareup.sample.container.overviewdetail.OverviewDetailScreen import com.squareup.sample.poetry.PoemListScreen.Companion.NO_POEM_SELECTED import com.squareup.sample.poetry.PoemListWorkflow.Props @@ -68,4 +69,13 @@ class RealPoemsBrowserWorkflow( } private val clearSelection = choosePoem(NO_POEM_SELECTED) + + @Composable + override fun Rendering( + renderProps: List, + renderState: SelectedPoem, + context: RenderContext, + ): OverviewDetailScreen { + TODO("Not yet implemented") + } } diff --git a/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/StanzaListWorkflow.kt b/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/StanzaListWorkflow.kt index 570cdee4e8..ef7f7f69e4 100644 --- a/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/StanzaListWorkflow.kt +++ b/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/StanzaListWorkflow.kt @@ -1,5 +1,6 @@ package com.squareup.sample.poetry +import androidx.compose.runtime.Composable import com.squareup.sample.poetry.StanzaListWorkflow.Props import com.squareup.sample.poetry.model.Poem import com.squareup.workflow1.StatelessWorkflow @@ -45,4 +46,12 @@ object StanzaListWorkflow : StatelessWorkflow() { ) } } + + @Composable + override fun Rendering( + renderProps: Props, + context: RenderContext + ): StanzaScreen { + return render(renderProps, context) + } } diff --git a/settings.gradle.kts b/settings.gradle.kts index 662672fedb..29143c826f 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -9,6 +9,8 @@ pluginManagement { google() // For binary compatibility validator. maven { url = uri("https://kotlin.bintray.com/kotlinx") } + // For molecule SNAPSHOT + maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots/")} } } @@ -29,6 +31,8 @@ dependencyResolutionManagement { repositories { mavenCentral() google() + // For molecule SNAPSHOT + maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots/")} // See androidx.dev (can use this for Snapshot builds of AndroidX) // maven { url = java.net.URI.create("https://androidx.dev/snapshots/builds/8224905/artifacts/repository") } } diff --git a/workflow-core/api/workflow-core.api b/workflow-core/api/workflow-core.api index e7fe83557f..880b816783 100644 --- a/workflow-core/api/workflow-core.api +++ b/workflow-core/api/workflow-core.api @@ -2,6 +2,7 @@ public abstract interface class com/squareup/workflow1/ActionProcessingResult { } public abstract interface class com/squareup/workflow1/BaseRenderContext { + public abstract fun ChildRendering (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)V public abstract fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function10;)Lkotlin/jvm/functions/Function9; public abstract fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function11;)Lkotlin/jvm/functions/Function10; public abstract fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;)Lkotlin/jvm/functions/Function0; @@ -54,6 +55,7 @@ public final class com/squareup/workflow1/ImpostorWorkflow$DefaultImpls { } public abstract class com/squareup/workflow1/LifecycleWorker : com/squareup/workflow1/Worker { + public static final field $stable I public fun ()V public fun doesSameWorkAs (Lcom/squareup/workflow1/Worker;)Z public fun onStarted ()V @@ -62,6 +64,7 @@ public abstract class com/squareup/workflow1/LifecycleWorker : com/squareup/work } public final class com/squareup/workflow1/PropsUpdated : com/squareup/workflow1/ActionProcessingResult { + public static final field $stable I public static final field INSTANCE Lcom/squareup/workflow1/PropsUpdated; } @@ -70,6 +73,7 @@ public abstract interface class com/squareup/workflow1/Sink { } public final class com/squareup/workflow1/Snapshot { + public static final field $stable I public static final field Companion Lcom/squareup/workflow1/Snapshot$Companion; public synthetic fun (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun bytes ()Lokio/ByteString; @@ -114,7 +118,9 @@ public final class com/squareup/workflow1/Snapshots { } public abstract class com/squareup/workflow1/StatefulWorkflow : com/squareup/workflow1/Workflow { + public static final field $stable I public fun ()V + public fun Rendering (Ljava/lang/Object;Ljava/lang/Object;Lcom/squareup/workflow1/StatefulWorkflow$RenderContext;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;I)V public final fun asStatefulWorkflow ()Lcom/squareup/workflow1/StatefulWorkflow; public abstract fun initialState (Ljava/lang/Object;Lcom/squareup/workflow1/Snapshot;)Ljava/lang/Object; public fun onPropsChanged (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; @@ -124,6 +130,7 @@ public abstract class com/squareup/workflow1/StatefulWorkflow : com/squareup/wor public final class com/squareup/workflow1/StatefulWorkflow$RenderContext : com/squareup/workflow1/BaseRenderContext { public fun (Lcom/squareup/workflow1/StatefulWorkflow;Lcom/squareup/workflow1/BaseRenderContext;)V + public fun ChildRendering (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)V public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function10;)Lkotlin/jvm/functions/Function9; public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function11;)Lkotlin/jvm/functions/Function10; public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;)Lkotlin/jvm/functions/Function0; @@ -141,13 +148,16 @@ public final class com/squareup/workflow1/StatefulWorkflow$RenderContext : com/s } public abstract class com/squareup/workflow1/StatelessWorkflow : com/squareup/workflow1/Workflow { + public static final field $stable I public fun ()V + public fun Rendering (Ljava/lang/Object;Lcom/squareup/workflow1/StatelessWorkflow$RenderContext;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;I)V public final fun asStatefulWorkflow ()Lcom/squareup/workflow1/StatefulWorkflow; public abstract fun render (Ljava/lang/Object;Lcom/squareup/workflow1/StatelessWorkflow$RenderContext;)Ljava/lang/Object; } public final class com/squareup/workflow1/StatelessWorkflow$RenderContext : com/squareup/workflow1/BaseRenderContext { public fun (Lcom/squareup/workflow1/StatelessWorkflow;Lcom/squareup/workflow1/BaseRenderContext;)V + public fun ChildRendering (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)V public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function10;)Lkotlin/jvm/functions/Function9; public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function11;)Lkotlin/jvm/functions/Function10; public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;)Lkotlin/jvm/functions/Function0; @@ -165,6 +175,7 @@ public final class com/squareup/workflow1/StatelessWorkflow$RenderContext : com/ } public final class com/squareup/workflow1/TimeoutForFrame : com/squareup/workflow1/ActionProcessingResult { + public static final field $stable I public static final field INSTANCE Lcom/squareup/workflow1/TimeoutForFrame; } @@ -222,6 +233,7 @@ public final class com/squareup/workflow1/Workflow$Companion { } public abstract class com/squareup/workflow1/WorkflowAction { + public static final field $stable I public static final field Companion Lcom/squareup/workflow1/WorkflowAction$Companion; public fun ()V public abstract fun apply (Lcom/squareup/workflow1/WorkflowAction$Updater;)V @@ -241,6 +253,7 @@ public final class com/squareup/workflow1/WorkflowAction$Updater { } public final class com/squareup/workflow1/WorkflowIdentifier { + public static final field $stable I public static final field Companion Lcom/squareup/workflow1/WorkflowIdentifier$Companion; public fun (Lcom/squareup/workflow1/WorkflowIdentifierType;Lcom/squareup/workflow1/WorkflowIdentifier;Lkotlin/jvm/functions/Function0;)V public synthetic fun (Lcom/squareup/workflow1/WorkflowIdentifierType;Lcom/squareup/workflow1/WorkflowIdentifier;Lkotlin/jvm/functions/Function0;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -260,10 +273,12 @@ public final class com/squareup/workflow1/WorkflowIdentifierExKt { } public abstract class com/squareup/workflow1/WorkflowIdentifierType { + public static final field $stable I public abstract fun getTypeName ()Ljava/lang/String; } public final class com/squareup/workflow1/WorkflowIdentifierType$Snapshottable : com/squareup/workflow1/WorkflowIdentifierType { + public static final field $stable I public fun (Ljava/lang/String;Lkotlin/reflect/KClass;)V public synthetic fun (Ljava/lang/String;Lkotlin/reflect/KClass;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun (Lkotlin/reflect/KClass;)V @@ -279,6 +294,7 @@ public final class com/squareup/workflow1/WorkflowIdentifierType$Snapshottable : } public final class com/squareup/workflow1/WorkflowIdentifierType$Unsnapshottable : com/squareup/workflow1/WorkflowIdentifierType { + public static final field $stable I public fun (Lkotlin/reflect/KType;)V public final fun component1 ()Lkotlin/reflect/KType; public final fun copy (Lkotlin/reflect/KType;)Lcom/squareup/workflow1/WorkflowIdentifierType$Unsnapshottable; @@ -291,6 +307,7 @@ public final class com/squareup/workflow1/WorkflowIdentifierType$Unsnapshottable } public final class com/squareup/workflow1/WorkflowOutput : com/squareup/workflow1/ActionProcessingResult { + public static final field $stable I public fun (Ljava/lang/Object;)V public fun equals (Ljava/lang/Object;)Z public final fun getValue ()Ljava/lang/Object; @@ -299,6 +316,9 @@ public final class com/squareup/workflow1/WorkflowOutput : com/squareup/workflow } public final class com/squareup/workflow1/Workflows { + public static final fun ChildRendering (Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V + public static final fun ChildRendering (Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/Workflow;Ljava/lang/String;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V + public static final fun ChildRendering (Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/Workflow;Ljava/lang/String;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V public static final fun RenderContext (Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/StatefulWorkflow;)Lcom/squareup/workflow1/StatefulWorkflow$RenderContext; public static final fun RenderContext (Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/StatelessWorkflow;)Lcom/squareup/workflow1/StatelessWorkflow$RenderContext; public static final fun action (Lcom/squareup/workflow1/StatefulWorkflow;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/WorkflowAction; diff --git a/workflow-core/build.gradle.kts b/workflow-core/build.gradle.kts index ed9b091fef..d597d5fdb8 100644 --- a/workflow-core/build.gradle.kts +++ b/workflow-core/build.gradle.kts @@ -1,11 +1,13 @@ plugins { `kotlin-multiplatform` published + id("app.cash.molecule") } kotlin { jvm { withJava() } - ios() + // TODO: No native targets yet for Molecule until Compose 1.2.0 available in JB KMP runtime. + // ios() sourceSets { all { @@ -16,9 +18,11 @@ kotlin { val commonMain by getting { dependencies { api(libs.kotlin.jdk6) + api(libs.compose.runtime) api(libs.kotlinx.coroutines.core) // For Snapshot. api(libs.squareup.okio) + implementation(libs.molecule.runtime) } } val commonTest by getting { diff --git a/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/BaseRenderContext.kt b/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/BaseRenderContext.kt index c727556de0..61a11d62d6 100644 --- a/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/BaseRenderContext.kt +++ b/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/BaseRenderContext.kt @@ -4,6 +4,7 @@ package com.squareup.workflow1 +import androidx.compose.runtime.Composable import com.squareup.workflow1.WorkflowAction.Companion.noAction import kotlinx.coroutines.CoroutineScope import kotlin.jvm.JvmMultifileClass @@ -78,6 +79,14 @@ public interface BaseRenderContext { handler: (ChildOutputT) -> WorkflowAction ): ChildRenderingT + @Composable + public fun ChildRendering( + child: Workflow, + props: ChildPropsT, + key: String, + handler: (ChildOutputT) -> WorkflowAction + ): ChildRenderingT + /** * Ensures [sideEffect] is running with the given [key]. * @@ -241,6 +250,38 @@ BaseRenderContext.renderChild( child: Workflow, key: String = "" ): ChildRenderingT = renderChild(child, Unit, key) { noAction() } + +/** + * Convenience alias of [BaseRenderContext.ChildRendering] for workflows that don't take props. + */ +@Composable +public fun +BaseRenderContext.ChildRendering( + child: Workflow, + key: String = "", + handler: (ChildOutputT) -> WorkflowAction +): ChildRenderingT = ChildRendering(child, Unit, key, handler) +/** + * Convenience alias of [BaseRenderContext.ChildRendering] for workflows that don't emit output. + */ +@Composable +public fun +BaseRenderContext.ChildRendering( + child: Workflow, + props: ChildPropsT, + key: String = "", +): ChildRenderingT = ChildRendering(child, props, key) { noAction() } +/** + * Convenience alias of [BaseRenderContext.ChildRendering] for children that don't take props or emit + * output. + */ +@Composable +public fun +BaseRenderContext.ChildRendering( + child: Workflow, + key: String = "", +): ChildRenderingT = ChildRendering(child, Unit, key) { noAction() } + /** * Ensures a [Worker] that never emits anything is running. Since [worker] can't emit anything, * it can't trigger any [WorkflowAction]s. diff --git a/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/StatefulWorkflow.kt b/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/StatefulWorkflow.kt index 031235ff4c..6db896bc4a 100644 --- a/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/StatefulWorkflow.kt +++ b/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/StatefulWorkflow.kt @@ -4,6 +4,7 @@ package com.squareup.workflow1 +import androidx.compose.runtime.Composable import com.squareup.workflow1.StatefulWorkflow.RenderContext import com.squareup.workflow1.WorkflowAction.Companion.toString import kotlin.jvm.JvmMultifileClass @@ -105,6 +106,22 @@ public abstract class StatefulWorkflow< state: StateT ): StateT = state + /** + * Called whenever the state changes to generate a new [Snapshot] of the state. + * + * **Snapshots must be lazy.** + * + * Serialization must not be done at the time this method is called, + * since the state will be snapshotted frequently but the serialized form may only be needed very + * rarely. + * + * If the workflow does not have any state, or should always be started from scratch, return + * `null` from this method. + * + * @see initialState + */ + public abstract fun snapshotState(state: StateT): Snapshot? + /** * Called at least once† any time one of the following things happens: * - This workflow's [renderProps] changes (via the parent passing a different one in). @@ -129,21 +146,12 @@ public abstract class StatefulWorkflow< context: RenderContext ): RenderingT - /** - * Called whenever the state changes to generate a new [Snapshot] of the state. - * - * **Snapshots must be lazy.** - * - * Serialization must not be done at the time this method is called, - * since the state will be snapshotted frequently but the serialized form may only be needed very - * rarely. - * - * If the workflow does not have any state, or should always be started from scratch, return - * `null` from this method. - * - * @see initialState - */ - public abstract fun snapshotState(state: StateT): Snapshot? + @Composable + public abstract fun Rendering( + renderProps: PropsT, + renderState: StateT, + context: RenderContext, + ): RenderingT /** * Satisfies the [Workflow] interface by returning `this`. @@ -172,6 +180,12 @@ public inline fun Workflow.Companion.state props: PropsT, state: StateT ) -> RenderingT, + noinline Render: @Composable BaseRenderContext.( + props: PropsT, + state: StateT + ) -> RenderingT = { props, state -> + render(props, state) + }, crossinline snapshot: (StateT) -> Snapshot?, crossinline onPropsChanged: ( old: PropsT, @@ -198,20 +212,32 @@ public inline fun Workflow.Companion.state ): RenderingT = render(context, renderProps, renderState) override fun snapshotState(state: StateT) = snapshot(state) + + @Composable + override fun Rendering( + renderProps: PropsT, + renderState: StateT, + context: RenderContext, + ): RenderingT = Render(context, renderProps, renderState) } /** * Returns a stateful [Workflow], with no props, implemented via the given functions. */ -public inline fun Workflow.Companion.stateful( - crossinline initialState: (Snapshot?) -> StateT, - crossinline render: BaseRenderContext.(state: StateT) -> RenderingT, - crossinline snapshot: (StateT) -> Snapshot? -): StatefulWorkflow = stateful( - { _, initialSnapshot -> initialState(initialSnapshot) }, - { _, state -> render(state) }, - snapshot -) +// public inline fun Workflow.Companion.stateful( +// crossinline initialState: (Snapshot?) -> StateT, +// crossinline render: BaseRenderContext.(state: StateT) -> RenderingT, +// noinline Render: @Composable BaseRenderContext.( +// state: StateT +// ) -> RenderingT = { state -> +// render(state) +// }, +// crossinline snapshot: (StateT) -> Snapshot? +// ): StatefulWorkflow = stateful( +// { _, initialSnapshot: Snapshot -> initialState(initialSnapshot) }, +// { _, state: StateT -> render(state) }, +// snapshot +// ) /** * Returns a stateful [Workflow] implemented via the given functions. @@ -224,14 +250,21 @@ public inline fun Workflow.Companion.state props: PropsT, state: StateT ) -> RenderingT, + noinline Render: @Composable BaseRenderContext.( + props: PropsT, + state: StateT + ) -> RenderingT = { props, state -> + render(props, state) + }, crossinline onPropsChanged: ( old: PropsT, new: PropsT, state: StateT ) -> StateT = { _, _, state -> state } ): StatefulWorkflow = stateful( - { props, _ -> initialState(props) }, + { props: PropsT, _ -> initialState(props) }, render, + Render, { null }, onPropsChanged ) @@ -243,7 +276,12 @@ public inline fun Workflow.Companion.state */ public inline fun Workflow.Companion.stateful( initialState: StateT, - crossinline render: BaseRenderContext.(state: StateT) -> RenderingT + crossinline render: BaseRenderContext.(state: StateT) -> RenderingT, + noinline Render: @Composable BaseRenderContext.( + state: StateT + ) -> RenderingT = { state -> + render(state) + }, ): StatefulWorkflow = stateful( { initialState }, { _, state -> render(state) } @@ -258,7 +296,7 @@ public inline fun Workflow.Companion.stateful( * @param update Function that defines the workflow update. */ public fun -StatefulWorkflow.action( + StatefulWorkflow.action( name: String = "", update: WorkflowAction.Updater.() -> Unit ): WorkflowAction = action({ name }, update) @@ -273,7 +311,7 @@ StatefulWorkflow.action( * @param update Function that defines the workflow update. */ public fun -StatefulWorkflow.action( + StatefulWorkflow.action( name: () -> String, update: WorkflowAction.Updater.() -> Unit ): WorkflowAction = object : WorkflowAction() { diff --git a/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/StatelessWorkflow.kt b/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/StatelessWorkflow.kt index f6545524e2..cec122b45a 100644 --- a/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/StatelessWorkflow.kt +++ b/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/StatelessWorkflow.kt @@ -3,6 +3,7 @@ package com.squareup.workflow1 +import androidx.compose.runtime.Composable import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName @@ -33,10 +34,19 @@ public abstract class StatelessWorkflow baseContext as BaseRenderContext @Suppress("UNCHECKED_CAST") - private val statefulWorkflow = Workflow.stateful( - initialState = { Unit }, - render = { props, _ -> render(props, RenderContext(this, this@StatelessWorkflow)) } - ) + private val statefulWorkflow: StatefulWorkflow + get() { + val Render: @Composable BaseRenderContext. + (props: PropsT, _: Unit) -> RenderingT = + @Composable { props, _ -> + (this@StatelessWorkflow).Rendering(props, RenderContext(this, this@StatelessWorkflow)) + } + return Workflow.stateful( + initialState = { Unit }, + render = { props, _ -> render(props, RenderContext(this, this@StatelessWorkflow)) }, + Render = Render + ) + } /** * Called at least once any time one of the following things happens: @@ -57,6 +67,12 @@ public abstract class StatelessWorkflow context: RenderContext ): RenderingT + @Composable + public open fun Rendering( + renderProps: PropsT, + context: RenderContext, + ): RenderingT = render(renderProps, context) + /** * Satisfies the [Workflow] interface by wrapping `this` in a [StatefulWorkflow] with `Unit` * state. @@ -94,6 +110,14 @@ public inline fun Workflow.Companion.stateless( renderProps: PropsT, context: RenderContext ): RenderingT = render(context, renderProps) + + @Composable + override fun Rendering( + renderProps: PropsT, + context: RenderContext + ): RenderingT { + TODO("Not yet implemented") + } } /** @@ -113,7 +137,7 @@ public fun Workflow.Companion.rendering( * @param update Function that defines the workflow update. */ public fun -StatelessWorkflow.action( + StatelessWorkflow.action( name: String = "", update: WorkflowAction.Updater.() -> Unit ): WorkflowAction = action({ name }, update) @@ -128,7 +152,7 @@ StatelessWorkflow.action( * @param update Function that defines the workflow update. */ public fun -StatelessWorkflow.action( + StatelessWorkflow.action( name: () -> String, update: WorkflowAction.Updater.() -> Unit ): WorkflowAction = object : WorkflowAction() { 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 c5ec0bba90..41b697bd26 100644 --- a/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/WorkerWorkflow.kt +++ b/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/WorkerWorkflow.kt @@ -3,6 +3,7 @@ package com.squareup.workflow1 +import androidx.compose.runtime.Composable import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.withContext @@ -61,6 +62,15 @@ internal class WorkerWorkflow( } override fun snapshotState(state: Int): Snapshot? = null + + @Composable + override fun Rendering( + renderProps: Worker, + renderState: Int, + context: RenderContext, + ): Unit { + TODO("Not yet implemented") + } } /** diff --git a/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/Workflow.kt b/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/Workflow.kt index ce2aaf685d..172629fb67 100644 --- a/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/Workflow.kt +++ b/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/Workflow.kt @@ -3,6 +3,7 @@ package com.squareup.workflow1 +import androidx.compose.runtime.Composable import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName @@ -135,4 +136,12 @@ Workflow.mapRendering( override fun describeRealIdentifier(): String = "${this@mapRendering.identifier}.mapRendering()" override fun toString(): String = "${this@mapRendering}.mapRendering()" + + @Composable + override fun Rendering( + renderProps: PropsT, + context: RenderContext + ): ToRenderingT { + TODO("Not yet implemented") + } } diff --git a/workflow-core/src/commonTest/kotlin/com/squareup/workflow1/ComposeInheritanceTest.kt b/workflow-core/src/commonTest/kotlin/com/squareup/workflow1/ComposeInheritanceTest.kt new file mode 100644 index 0000000000..5b21d5dfcb --- /dev/null +++ b/workflow-core/src/commonTest/kotlin/com/squareup/workflow1/ComposeInheritanceTest.kt @@ -0,0 +1,68 @@ +package com.squareup.workflow1 + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.BroadcastFrameClock +import app.cash.molecule.launchMolecule +import kotlinx.coroutines.CoroutineScope +import org.junit.Test + +class ComposeInheritanceTest { + abstract class Abstract { + @Composable + public abstract fun AComposable( + input1: I1, + input2: I2, + hoistState: @Composable (s: T) -> Unit + ): Unit + + abstract fun someOtherFunction(): T + } + + + public class Concrete( + private val payload: String + ) : Abstract() { + @Composable + public override fun AComposable( + input1: Unit, + input2: String, + hoistState: @Composable (s: String) -> Unit + ) { + println("Can you hear me now? $payload") + hoistState(payload + input2) + } + + override fun someOtherFunction(): String { + return payload + } + } + + @Composable + public fun Emitter( + i1: I1, + i2: I2, + theObjectHoldingComposables: Abstract + ): T? { + val payload: MutableState = remember { mutableStateOf(null) } + theObjectHoldingComposables.AComposable(i1, i2) @Composable { + payload.value = it + } + return payload.value + } + + @Test fun testInheritance() { + val objectUnderTest = Concrete("a test") + val broadcastFrameClock = BroadcastFrameClock {} + val testScope = CoroutineScope(broadcastFrameClock) + + val testFlow = testScope.launchMolecule { + Emitter(Unit, " again", objectUnderTest) + } + + assert(testFlow.value.contentEquals("a test again")) + } +} + diff --git a/workflow-runtime/api/workflow-runtime.api b/workflow-runtime/api/workflow-runtime.api index bac7d553b5..be4b92253b 100644 --- a/workflow-runtime/api/workflow-runtime.api +++ b/workflow-runtime/api/workflow-runtime.api @@ -1,5 +1,16 @@ +public final class com/squareup/workflow1/Latch { + public fun ()V + public final fun await (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun closeLatch ()V + public final fun isOpen ()Z + public final fun openLatch ()V + public final fun withClosed (Lkotlin/jvm/functions/Function0;)Ljava/lang/Object; +} + public final class com/squareup/workflow1/NoopWorkflowInterceptor : com/squareup/workflow1/WorkflowInterceptor { + public static final field $stable I public static final field INSTANCE Lcom/squareup/workflow1/NoopWorkflowInterceptor; + public fun Rendering (Ljava/lang/Object;Ljava/lang/Object;Lkotlin/jvm/functions/Function3;Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lkotlin/jvm/functions/Function6;Landroidx/compose/runtime/Composer;I)V 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; @@ -13,6 +24,7 @@ public final class com/squareup/workflow1/RenderWorkflowKt { } public final class com/squareup/workflow1/RenderingAndSnapshot { + public static final field $stable I public fun (Ljava/lang/Object;Lcom/squareup/workflow1/TreeSnapshot;)V public final fun component1 ()Ljava/lang/Object; public final fun component2 ()Lcom/squareup/workflow1/TreeSnapshot; @@ -22,6 +34,7 @@ public final class com/squareup/workflow1/RenderingAndSnapshot { public abstract interface class com/squareup/workflow1/RuntimeConfig { public static final field Companion Lcom/squareup/workflow1/RuntimeConfig$Companion; + public abstract fun getUseComposeInRuntime ()Z } public final class com/squareup/workflow1/RuntimeConfig$Companion { @@ -29,24 +42,37 @@ public final class com/squareup/workflow1/RuntimeConfig$Companion { } public final class com/squareup/workflow1/RuntimeConfig$FrameTimeout : com/squareup/workflow1/RuntimeConfig { + public static final field $stable I public fun ()V - public fun (J)V - public synthetic fun (JILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (JZ)V + public synthetic fun (JZILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()J - public final fun copy (J)Lcom/squareup/workflow1/RuntimeConfig$FrameTimeout; - public static synthetic fun copy$default (Lcom/squareup/workflow1/RuntimeConfig$FrameTimeout;JILjava/lang/Object;)Lcom/squareup/workflow1/RuntimeConfig$FrameTimeout; + public final fun component2 ()Z + public final fun copy (JZ)Lcom/squareup/workflow1/RuntimeConfig$FrameTimeout; + public static synthetic fun copy$default (Lcom/squareup/workflow1/RuntimeConfig$FrameTimeout;JZILjava/lang/Object;)Lcom/squareup/workflow1/RuntimeConfig$FrameTimeout; public fun equals (Ljava/lang/Object;)Z public final fun getFrameTimeoutMs ()J + public fun getUseComposeInRuntime ()Z public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class com/squareup/workflow1/RuntimeConfig$RenderPerAction : com/squareup/workflow1/RuntimeConfig { + public static final field $stable I public static final field INSTANCE Lcom/squareup/workflow1/RuntimeConfig$RenderPerAction; + public fun getUseComposeInRuntime ()Z +} + +public final class com/squareup/workflow1/SignalHolder { + public fun ()V + public final fun getSignal$wf1_workflow_runtime ()Lkotlin/jvm/functions/Function0; + public final fun setSignal$wf1_workflow_runtime (Lkotlin/jvm/functions/Function0;)V } public class com/squareup/workflow1/SimpleLoggingWorkflowInterceptor : com/squareup/workflow1/WorkflowInterceptor { + public static final field $stable I public fun ()V + public fun Rendering (Ljava/lang/Object;Ljava/lang/Object;Lkotlin/jvm/functions/Function3;Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lkotlin/jvm/functions/Function6;Landroidx/compose/runtime/Composer;I)V protected fun log (Ljava/lang/String;)V protected fun logAfterMethod (Ljava/lang/String;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;[Lkotlin/Pair;)V protected fun logBeforeMethod (Ljava/lang/String;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;[Lkotlin/Pair;)V @@ -59,6 +85,7 @@ public class com/squareup/workflow1/SimpleLoggingWorkflowInterceptor : com/squar } public final class com/squareup/workflow1/TreeSnapshot { + public static final field $stable I public static final field Companion Lcom/squareup/workflow1/TreeSnapshot$Companion; public fun (Lcom/squareup/workflow1/Snapshot;Lkotlin/jvm/functions/Function0;)V public fun equals (Ljava/lang/Object;)Z @@ -77,6 +104,7 @@ public abstract interface annotation class com/squareup/workflow1/WorkflowExperi } public abstract interface class com/squareup/workflow1/WorkflowInterceptor { + public abstract fun Rendering (Ljava/lang/Object;Ljava/lang/Object;Lkotlin/jvm/functions/Function3;Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lkotlin/jvm/functions/Function6;Landroidx/compose/runtime/Composer;I)V 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; @@ -85,6 +113,7 @@ public abstract interface class com/squareup/workflow1/WorkflowInterceptor { } public final class com/squareup/workflow1/WorkflowInterceptor$DefaultImpls { + public static fun Rendering (Lcom/squareup/workflow1/WorkflowInterceptor;Ljava/lang/Object;Ljava/lang/Object;Lkotlin/jvm/functions/Function3;Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lkotlin/jvm/functions/Function6;Landroidx/compose/runtime/Composer;I)V 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; @@ -93,12 +122,14 @@ public final class com/squareup/workflow1/WorkflowInterceptor$DefaultImpls { } public abstract interface class com/squareup/workflow1/WorkflowInterceptor$RenderContextInterceptor { + public abstract fun ChildRendering (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function7;Landroidx/compose/runtime/Composer;I)V public abstract fun onActionSent (Lcom/squareup/workflow1/WorkflowAction;Lkotlin/jvm/functions/Function1;)V public abstract fun onRenderChild (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function4;)Ljava/lang/Object; public abstract fun onRunningSideEffect (Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)V } public final class com/squareup/workflow1/WorkflowInterceptor$RenderContextInterceptor$DefaultImpls { + public static fun ChildRendering (Lcom/squareup/workflow1/WorkflowInterceptor$RenderContextInterceptor;Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function7;Landroidx/compose/runtime/Composer;I)V public static fun onActionSent (Lcom/squareup/workflow1/WorkflowInterceptor$RenderContextInterceptor;Lcom/squareup/workflow1/WorkflowAction;Lkotlin/jvm/functions/Function1;)V public static fun onRenderChild (Lcom/squareup/workflow1/WorkflowInterceptor$RenderContextInterceptor;Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function4;)Ljava/lang/Object; public static fun onRunningSideEffect (Lcom/squareup/workflow1/WorkflowInterceptor$RenderContextInterceptor;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)V @@ -115,6 +146,16 @@ public final class com/squareup/workflow1/WorkflowInterceptorKt { public static final fun intercept (Lcom/squareup/workflow1/WorkflowInterceptor;Lcom/squareup/workflow1/StatefulWorkflow;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Lcom/squareup/workflow1/StatefulWorkflow; } +public final class com/squareup/workflow1/WorkflowRuntimeClock : androidx/compose/runtime/MonotonicFrameClock { + public fun (Lcom/squareup/workflow1/Latch;)V + public fun fold (Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object; + public fun get (Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element; + public fun getKey ()Lkotlin/coroutines/CoroutineContext$Key; + public fun minusKey (Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext; + public fun plus (Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext; + public fun withFrameNanos (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + public final class com/squareup/workflow1/internal/ActiveStagingList { public fun ()V public final fun commitStaging (Lkotlin/jvm/functions/Function1;)V @@ -125,6 +166,7 @@ public final class com/squareup/workflow1/internal/ActiveStagingList { public final class com/squareup/workflow1/internal/ChainedWorkflowInterceptor : com/squareup/workflow1/WorkflowInterceptor { public fun (Ljava/util/List;)V + public fun Rendering (Ljava/lang/Object;Ljava/lang/Object;Lkotlin/jvm/functions/Function3;Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lkotlin/jvm/functions/Function6;Landroidx/compose/runtime/Composer;I)V 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; @@ -164,6 +206,7 @@ public abstract interface class com/squareup/workflow1/internal/InlineLinkedList public final class com/squareup/workflow1/internal/RealRenderContext : com/squareup/workflow1/BaseRenderContext, com/squareup/workflow1/Sink { public fun (Lcom/squareup/workflow1/internal/RealRenderContext$Renderer;Lcom/squareup/workflow1/internal/RealRenderContext$SideEffectRunner;Lkotlinx/coroutines/channels/SendChannel;)V + public fun ChildRendering (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)V public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function10;)Lkotlin/jvm/functions/Function9; public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function11;)Lkotlin/jvm/functions/Function10; public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;)Lkotlin/jvm/functions/Function0; @@ -176,6 +219,7 @@ public final class com/squareup/workflow1/internal/RealRenderContext : com/squar public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function8;)Lkotlin/jvm/functions/Function7; public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function9;)Lkotlin/jvm/functions/Function8; public final fun freeze ()V + public final fun freezeIdempotently ()V public fun getActionSink ()Lcom/squareup/workflow1/Sink; public fun renderChild (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public fun runningSideEffect (Ljava/lang/String;Lkotlin/jvm/functions/Function2;)V @@ -184,6 +228,7 @@ public final class com/squareup/workflow1/internal/RealRenderContext : com/squar } public abstract interface class com/squareup/workflow1/internal/RealRenderContext$Renderer { + public abstract fun Rendering (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)V public abstract fun render (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; } @@ -204,6 +249,7 @@ public final class com/squareup/workflow1/internal/SideEffectNode : com/squareup public final class com/squareup/workflow1/internal/SubtreeManager : com/squareup/workflow1/internal/RealRenderContext$Renderer { public fun (Ljava/util/Map;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lcom/squareup/workflow1/WorkflowInterceptor;Lcom/squareup/workflow1/internal/IdCounter;)V public synthetic fun (Ljava/util/Map;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lcom/squareup/workflow1/WorkflowInterceptor;Lcom/squareup/workflow1/internal/IdCounter;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun Rendering (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)V public final fun commitRenderedChildren ()V public final fun createChildSnapshots ()Ljava/util/Map; public fun render (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; @@ -212,6 +258,7 @@ public final class com/squareup/workflow1/internal/SubtreeManager : com/squareup public final class com/squareup/workflow1/internal/SystemUtilsKt { public static final fun currentTimeMillis ()J + public static final fun nanoTime ()J } public final class com/squareup/workflow1/internal/ThrowablesKt { @@ -220,6 +267,7 @@ public final class com/squareup/workflow1/internal/ThrowablesKt { public final class com/squareup/workflow1/internal/WorkflowChildNode : com/squareup/workflow1/internal/InlineLinkedList$InlineListNode { public fun (Lcom/squareup/workflow1/Workflow;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/internal/WorkflowNode;)V + public final fun Rendering (Lcom/squareup/workflow1/StatefulWorkflow;Ljava/lang/Object;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;I)V public final fun acceptChildOutput (Ljava/lang/Object;)Lcom/squareup/workflow1/WorkflowAction; public final fun getId ()Lcom/squareup/workflow1/internal/WorkflowNodeId; public synthetic fun getNextListNode ()Lcom/squareup/workflow1/internal/InlineLinkedList$InlineListNode; @@ -236,6 +284,7 @@ public final class com/squareup/workflow1/internal/WorkflowChildNode : com/squar public final class com/squareup/workflow1/internal/WorkflowNode : com/squareup/workflow1/WorkflowInterceptor$WorkflowSession, com/squareup/workflow1/internal/RealRenderContext$SideEffectRunner, kotlinx/coroutines/CoroutineScope { public fun (Lcom/squareup/workflow1/internal/WorkflowNodeId;Lcom/squareup/workflow1/StatefulWorkflow;Ljava/lang/Object;Lcom/squareup/workflow1/TreeSnapshot;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lcom/squareup/workflow1/WorkflowInterceptor;Lcom/squareup/workflow1/internal/IdCounter;)V public synthetic fun (Lcom/squareup/workflow1/internal/WorkflowNodeId;Lcom/squareup/workflow1/StatefulWorkflow;Ljava/lang/Object;Lcom/squareup/workflow1/TreeSnapshot;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lcom/squareup/workflow1/WorkflowInterceptor;Lcom/squareup/workflow1/internal/IdCounter;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun Rendering (Lcom/squareup/workflow1/StatefulWorkflow;Ljava/lang/Object;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;I)V public final fun cancel (Ljava/util/concurrent/CancellationException;)V public static synthetic fun cancel$default (Lcom/squareup/workflow1/internal/WorkflowNode;Ljava/util/concurrent/CancellationException;ILjava/lang/Object;)V public fun getCoroutineContext ()Lkotlin/coroutines/CoroutineContext; @@ -283,6 +332,7 @@ public final class com/squareup/workflow1/internal/WorkflowRunner { public fun (Lkotlinx/coroutines/CoroutineScope;Lcom/squareup/workflow1/Workflow;Lkotlinx/coroutines/flow/StateFlow;Lcom/squareup/workflow1/TreeSnapshot;Lcom/squareup/workflow1/WorkflowInterceptor;Lcom/squareup/workflow1/RuntimeConfig;)V public final fun cancelRuntime (Ljava/util/concurrent/CancellationException;)V public static synthetic fun cancelRuntime$default (Lcom/squareup/workflow1/internal/WorkflowRunner;Ljava/util/concurrent/CancellationException;ILjava/lang/Object;)V + public final fun nextComposedRendering (Landroidx/compose/runtime/Composer;I)Lcom/squareup/workflow1/RenderingAndSnapshot; public final fun nextRendering ()Lcom/squareup/workflow1/RenderingAndSnapshot; public final fun processActions (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } diff --git a/workflow-runtime/build.gradle.kts b/workflow-runtime/build.gradle.kts index 57f66e1cca..2370ac86e6 100644 --- a/workflow-runtime/build.gradle.kts +++ b/workflow-runtime/build.gradle.kts @@ -5,6 +5,7 @@ plugins { `kotlin-multiplatform` published id("org.jetbrains.kotlinx.benchmark") + id("app.cash.molecule") } kotlin { @@ -25,7 +26,8 @@ kotlin { } } } - ios() + // TODO: No native targets yet for Molecule until Compose 1.2.0 available in JB KMP runtime. + // ios() sourceSets { all { @@ -36,7 +38,9 @@ kotlin { val commonMain by getting { dependencies { api(project(":workflow-core")) + api(libs.compose.runtime) api(libs.kotlinx.coroutines.core) + implementation(libs.molecule.runtime) } } val commonTest by getting { diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/RenderWorkflow.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/RenderWorkflow.kt index 476faefd3b..c8c1f00730 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/RenderWorkflow.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/RenderWorkflow.kt @@ -1,7 +1,10 @@ package com.squareup.workflow1 +import androidx.compose.runtime.BroadcastFrameClock +import app.cash.molecule.launchMolecule import com.squareup.workflow1.internal.WorkflowRunner import com.squareup.workflow1.internal.chained +import com.squareup.workflow1.internal.nanoTime import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -11,6 +14,8 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.isActive import kotlinx.coroutines.launch +import kotlinx.coroutines.plus +import kotlinx.coroutines.yield /** * Launches the [workflow] in a new coroutine in [scope] and returns a [StateFlow] of its @@ -116,24 +121,41 @@ public fun renderWorkflowIn( val runner = WorkflowRunner(scope, workflow, props, initialSnapshot, chainedInterceptor, runtimeConfig) + var composeWaitingForFrame = false + val composeRuntimeClock = BroadcastFrameClock { + composeWaitingForFrame = true + } + // Rendering is synchronous, so we can run the first render pass before launching the runtime // coroutine to calculate the initial rendering. - val renderingsAndSnapshots = MutableStateFlow( - try { - runner.nextRendering() - } catch (e: Throwable) { - // If any part of the workflow runtime fails, the scope should be cancelled. We're not in a - // coroutine yet however, so if the first render pass fails it won't cancel the runtime, - // but this is an implementation detail so we must cancel the scope manually to keep the - // contract. - val cancellation = - (e as? CancellationException) ?: CancellationException("Workflow runtime failed", e) - runner.cancelRuntime(cancellation) - throw e + val renderingsAndSnapshots = if (runtimeConfig.useComposeInRuntime) { + val clockedScope = scope + composeRuntimeClock + + clockedScope.launchMolecule { + runner.nextComposedRendering() } - ) + } else { + MutableStateFlow( + try { + runner.nextRendering() + } catch (e: Throwable) { + // If any part of the workflow runtime fails, the scope should be cancelled. We're not in a + // coroutine yet however, so if the first render pass fails it won't cancel the runtime, + // but this is an implementation detail so we must cancel the scope manually to keep the + // contract. + val cancellation = + (e as? CancellationException) ?: CancellationException("Workflow runtime failed", e) + runner.cancelRuntime(cancellation) + throw e + } + ) + } scope.launch { + // if (runtimeConfig.useComposeInRuntime) { + // //synchronous first render. + // renderSignal.emit(Unit) + // } while (isActive) { // It might look weird to start by consuming the output before getting the rendering below, // but remember the first render pass already occurred above, before this coroutine was even @@ -146,7 +168,15 @@ public fun renderWorkflowIn( // After receiving an output, the next render pass must be done before emitting that output, // so that the workflow states appear consistent to observers of the outputs and renderings. - renderingsAndSnapshots.value = runner.nextRendering() + if (runtimeConfig.useComposeInRuntime) { + if (composeWaitingForFrame) { + composeWaitingForFrame = false + composeRuntimeClock.sendFrame(nanoTime()) + yield() + } + } else { + (renderingsAndSnapshots as MutableStateFlow).value = runner.nextRendering() + } output?.let { onOutput(it.value) } } } diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/RuntimeConfig.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/RuntimeConfig.kt index b9e5f3435e..478f251b41 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/RuntimeConfig.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/RuntimeConfig.kt @@ -19,18 +19,24 @@ public annotation class WorkflowExperimentalRuntime * A specification of the Workflow Runtime. */ public sealed interface RuntimeConfig { + public val useComposeInRuntime: Boolean /** * This version of the runtime will process as many actions as possible after one is received * until [frameTimeoutMs] has passed, at which point it will render(). */ @WorkflowExperimentalRuntime - public data class FrameTimeout(public val frameTimeoutMs: Long = 30L) : RuntimeConfig + public data class FrameTimeout( + public val frameTimeoutMs: Long = 30L, + public override val useComposeInRuntime: Boolean = false + ) : RuntimeConfig /** * This is the baseline runtime which will process one action at a time, calling render() after * each one. */ - public object RenderPerAction : RuntimeConfig + public object RenderPerAction : RuntimeConfig { + override val useComposeInRuntime: Boolean = false + } public companion object { public val DEFAULT_CONFIG: RuntimeConfig = RenderPerAction diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/SimpleLoggingWorkflowInterceptor.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/SimpleLoggingWorkflowInterceptor.kt index 2b6e624338..4d5658d4cb 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/SimpleLoggingWorkflowInterceptor.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/SimpleLoggingWorkflowInterceptor.kt @@ -1,5 +1,6 @@ package com.squareup.workflow1 +import androidx.compose.runtime.Composable import com.squareup.workflow1.WorkflowInterceptor.RenderContextInterceptor import com.squareup.workflow1.WorkflowInterceptor.WorkflowSession import kotlinx.coroutines.CoroutineScope @@ -48,6 +49,17 @@ public open class SimpleLoggingWorkflowInterceptor : WorkflowInterceptor { proceed(renderProps, renderState, SimpleLoggingContextInterceptor(session)) } + @Composable + override fun Rendering( + renderProps: P, + renderState: S, + context: BaseRenderContext, + session: WorkflowSession, + proceed: @Composable (P, S, RenderContextInterceptor?) -> R + ): R = logMethod("onRender", session) { + proceed(renderProps, renderState, SimpleLoggingContextInterceptor(session)) + } + override fun onSnapshotState( state: S, proceed: (S) -> Snapshot?, 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..03c62ecf99 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/WorkflowInterceptor.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/WorkflowInterceptor.kt @@ -1,5 +1,6 @@ package com.squareup.workflow1 +import androidx.compose.runtime.Composable import com.squareup.workflow1.WorkflowInterceptor.RenderContextInterceptor import com.squareup.workflow1.WorkflowInterceptor.WorkflowSession import kotlinx.coroutines.CoroutineScope @@ -98,6 +99,15 @@ public interface WorkflowInterceptor { session: WorkflowSession ): R = proceed(renderProps, renderState, null) + @Composable + public fun Rendering( + renderProps: P, + renderState: S, + context: BaseRenderContext, + session: WorkflowSession, + proceed: @Composable (P, S, RenderContextInterceptor?) -> R + ): R = proceed(renderProps, renderState, null) + /** * Intercepts calls to [StatefulWorkflow.snapshotState]. */ @@ -226,6 +236,20 @@ public interface WorkflowInterceptor { handler: (CO) -> WorkflowAction ) -> CR ): CR = proceed(child, childProps, key, handler) + + @Composable + public fun ChildRendering( + child: Workflow, + childProps: CP, + key: String, + handler: (CO) -> WorkflowAction, + proceed: @Composable ( + child: Workflow, + childProps: CP, + key: String, + handler: (CO) -> WorkflowAction + ) -> CR + ): CR = proceed(child, childProps, key, handler) } } @@ -270,6 +294,35 @@ internal fun WorkflowInterceptor.intercept( session = workflowSession, ) + @Composable + override fun Rendering( + renderProps: P, + renderState: S, + context: RenderContext, + ): R { + // Cannot annotate anonymous functions with @Composable and cannot infer type of + // this when a lambda. So need this variable to make it explicit. + val anonProceed: @Composable (P, S, RenderContextInterceptor?) -> R = + @Composable { props: P, + state: S, + interceptor: RenderContextInterceptor? -> + val interceptedContext = interceptor?.let { InterceptedRenderContext(context, it) } + ?: context + workflow.Rendering( + props, + state, + RenderContext(interceptedContext, this) + ) + } + return Rendering( + renderProps = renderProps, + renderState = renderState, + context = context, + session = workflowSession, + proceed = anonProceed + ) + } + override fun snapshotState(state: S) = onSnapshotState(state, workflow::snapshotState, workflowSession) @@ -299,6 +352,22 @@ private class InterceptedRenderContext( baseRenderContext.renderChild(iChild, iProps, iKey, iHandler) } + @Composable + override fun ChildRendering( + child: Workflow, + props: ChildPropsT, + key: String, + handler: (ChildOutputT) -> WorkflowAction + ): ChildRenderingT = + interceptor.ChildRendering( + child, + props, + key, + handler + ) @Composable { iChild, iProps, iKey, iHandler -> + baseRenderContext.ChildRendering(iChild, iProps, iKey, iHandler) + } + override fun runningSideEffect( key: String, sideEffect: suspend CoroutineScope.() -> Unit diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/WorkflowRuntimeClock.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/WorkflowRuntimeClock.kt new file mode 100644 index 0000000000..f5145a1198 --- /dev/null +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/WorkflowRuntimeClock.kt @@ -0,0 +1,81 @@ +package com.squareup.workflow1 + +import androidx.compose.runtime.MonotonicFrameClock +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlin.coroutines.Continuation +import kotlin.coroutines.resume + +/** + * Could use [PausableMonotonicFrameClock] but we'd need to wrap that around something. + */ +internal class WorkflowRuntimeClock( + private var workflowFrameLatch: Latch +) : MonotonicFrameClock { + override suspend fun withFrameNanos(onFrame: (frameTimeNanos: Long) -> R): R { + workflowFrameLatch.await() + return onFrame(0L) // frame time not used in Compose runtime. + } +} + +/** + * Class internal to androidx.compose.runtime. Useful here! + */ +internal class Latch { + + private val lock = Any() + private var awaiters = mutableListOf>() + private var spareList = mutableListOf>() + + private var _isOpen = true + val isOpen get() = synchronized(lock) { _isOpen } + + inline fun withClosed(block: () -> R): R { + closeLatch() + return try { + block() + } finally { + openLatch() + } + } + + fun closeLatch() { + synchronized(lock) { + _isOpen = false + } + } + + fun openLatch() { + synchronized(lock) { + if (isOpen) return + + // Rotate the lists so that if a resumed continuation on an immediate dispatcher + // bound to the thread calling openLatch immediately awaits again we don't disrupt + // iteration of resuming the rest. This is also why we set isClosed before resuming. + val toResume = awaiters + awaiters = spareList + spareList = toResume + _isOpen = true + + for (i in 0 until toResume.size) { + toResume[i].resume(Unit) + } + toResume.clear() + } + } + + suspend fun await() { + if (isOpen) return + + suspendCancellableCoroutine { co -> + synchronized(lock) { + awaiters.add(co) + } + + co.invokeOnCancellation { + synchronized(lock) { + awaiters.remove(co) + } + } + } + } +} 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..a5df80873d 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 @@ -1,5 +1,6 @@ package com.squareup.workflow1.internal +import androidx.compose.runtime.Composable import com.squareup.workflow1.BaseRenderContext import com.squareup.workflow1.NoopWorkflowInterceptor import com.squareup.workflow1.Snapshot @@ -81,6 +82,36 @@ internal class ChainedWorkflowInterceptor( return chainedProceed(renderProps, renderState, null) } + @Composable + override fun Rendering( + renderProps: P, + renderState: S, + context: BaseRenderContext, + session: WorkflowSession, + proceed: @Composable (P, S, RenderContextInterceptor?) -> R + ): R { + val chainedProceed = interceptors.foldRight(proceed) { workflowInterceptor, proceedAcc -> + { props, state, outerContextInterceptor -> + // Holding compiler's hand for function type. + val proceedInternal = + @Composable { p: P, + s: S, + innerContextInterceptor: RenderContextInterceptor? -> + val contextInterceptor = outerContextInterceptor.wrap(innerContextInterceptor) + proceedAcc(p, s, contextInterceptor) + } + workflowInterceptor.Rendering( + props, + state, + context, + proceed = proceedInternal, + session = session, + ) + } + } + return chainedProceed(renderProps, renderState, null) + } + override fun onSnapshotState( state: S, proceed: (S) -> Snapshot?, @@ -129,6 +160,28 @@ internal class ChainedWorkflowInterceptor( inner.onRenderChild(c, p, k, h, proceed) } + @Composable + override fun ChildRendering( + child: Workflow, + childProps: CP, + key: String, + handler: (CO) -> WorkflowAction, + proceed: @Composable ( + child: Workflow, + childProps: CP, + key: String, + handler: (CO) -> WorkflowAction + ) -> CR + ): CR = + outer.ChildRendering( + child, + childProps, + key, + handler + ) @Composable { c, p, k, h, -> + inner.ChildRendering(c, p, k, h, proceed) + } + override fun onRunningSideEffect( key: String, sideEffect: suspend () -> Unit, diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/RealRenderContext.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/RealRenderContext.kt index fdc42ecc73..b5b5f3273d 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/RealRenderContext.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/RealRenderContext.kt @@ -2,6 +2,8 @@ package com.squareup.workflow1.internal +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf import com.squareup.workflow1.BaseRenderContext import com.squareup.workflow1.Sink import com.squareup.workflow1.Workflow @@ -22,6 +24,14 @@ internal class RealRenderContext( key: String, handler: (ChildOutputT) -> WorkflowAction ): ChildRenderingT + + @Composable + fun Rendering( + child: Workflow, + props: ChildPropsT, + key: String, + handler: (ChildOutputT) -> WorkflowAction + ): ChildRenderingT } interface SideEffectRunner { @@ -57,15 +67,25 @@ internal class RealRenderContext( key: String, handler: (ChildOutputT) -> WorkflowAction ): ChildRenderingT { - checkNotFrozen() + // checkNotFrozen() return renderer.render(child, props, key, handler) } + @Composable + override fun ChildRendering( + child: Workflow, + props: ChildPropsT, + key: String, + handler: (ChildOutputT) -> WorkflowAction + ): ChildRenderingT { + return renderer.Rendering(child, props, key, handler) + } + override fun runningSideEffect( key: String, sideEffect: suspend CoroutineScope.() -> Unit ) { - checkNotFrozen() + // checkNotFrozen() sideEffectRunner.runningSideEffect(key, sideEffect) } @@ -73,7 +93,7 @@ internal class RealRenderContext( * Freezes this context so that any further calls to this context will throw. */ fun freeze() { - checkNotFrozen() + // checkNotFrozen() frozen = true } diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/SubtreeManager.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/SubtreeManager.kt index 434b16c194..8a9f1255cd 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/SubtreeManager.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/SubtreeManager.kt @@ -1,5 +1,7 @@ package com.squareup.workflow1.internal +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import com.squareup.workflow1.ActionProcessingResult import com.squareup.workflow1.NoopWorkflowInterceptor import com.squareup.workflow1.TreeSnapshot @@ -114,6 +116,39 @@ internal class SubtreeManager( key: String, handler: (ChildOutputT) -> WorkflowAction ): ChildRenderingT { + val stagedChild = prepareStagedChild( + child, + props, + key, + handler + ) + return stagedChild.render(child.asStatefulWorkflow(), props) + } + + @Composable + override fun Rendering( + child: Workflow, + props: ChildPropsT, + key: String, + handler: (ChildOutputT) -> WorkflowAction + ): ChildRenderingT { + val stagedChild = remember (child, props, key, handler) { + prepareStagedChild( + child, + props, + key, + handler + ) + } + return stagedChild.Rendering(child.asStatefulWorkflow(), props) + } + + private fun prepareStagedChild( + child: Workflow, + props: ChildPropsT, + key: String, + handler: (ChildOutputT) -> WorkflowAction + ): WorkflowChildNode<*, *, *, *, *> { // Prevent duplicate workflows with the same key. children.forEachStaging { require(!(it.matches(child, key))) { @@ -127,7 +162,7 @@ internal class SubtreeManager( create = { createChildNode(child, props, key, handler) } ) stagedChild.setHandler(handler) - return stagedChild.render(child.asStatefulWorkflow(), props) + return stagedChild } /** diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/SystemUtils.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/SystemUtils.kt index 6249da899a..f701de085c 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/SystemUtils.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/SystemUtils.kt @@ -5,3 +5,8 @@ package com.squareup.workflow1.internal * to use after we have processed some actions. Use this milliseconds since epoch for that. */ internal expect fun currentTimeMillis(): Long + +/** + * Current time in nanoseconds. + */ +internal expect fun nanoTime(): Long diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowChildNode.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowChildNode.kt index b6a833a95e..4d54dd7581 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowChildNode.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowChildNode.kt @@ -1,5 +1,8 @@ package com.squareup.workflow1.internal +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import com.squareup.workflow1.StatefulWorkflow import com.squareup.workflow1.Workflow import com.squareup.workflow1.WorkflowAction @@ -58,6 +61,23 @@ internal class WorkflowChildNode< ) as R } + @Composable + fun Rendering( + workflow: StatefulWorkflow<*, *, *, *>, + props: Any? + ): R { + val renderingState = remember { mutableStateOf(null) } + @Suppress("UNCHECKED_CAST") + workflowNode.Rendering( + workflow as StatefulWorkflow, + props as ChildPropsT, + setRendering = { + renderingState.value = it as R + } + ) + return renderingState.value!! + } + /** * Wrapper around [handler] that allows calling it with erased types. */ 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 db4921d8c2..2d19de78a9 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 @@ -1,5 +1,11 @@ package com.squareup.workflow1.internal +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.key +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import com.squareup.workflow1.ActionProcessingResult import com.squareup.workflow1.NoopWorkflowInterceptor import com.squareup.workflow1.RenderContext @@ -72,13 +78,13 @@ internal class WorkflowNode( private var lastProps: PropsT = initialProps private val eventActionsChannel = Channel>(capacity = UNLIMITED) - private var state: StateT + private val state: MutableState init { interceptor.onSessionStarted(this, this) - state = interceptor.intercept(workflow, this) - .initialState(initialProps, snapshot?.workflowSnapshot) + state = mutableStateOf(interceptor.intercept(workflow = workflow, workflowSession = this) + .initialState(initialProps, snapshot?.workflowSnapshot)) } override fun toString(): String { @@ -103,16 +109,35 @@ internal class WorkflowNode( ): RenderingT = renderWithStateType(workflow as StatefulWorkflow, input) + + /** + * This returns Unit so that the Recomposer will consider this a separate Recompose scope that + * can be independently recomposed. + */ + @Suppress("UNCHECKED_CAST") + @Composable + fun Rendering( + workflow: StatefulWorkflow, + input: PropsT, + setRendering: (RenderingT) -> Unit + ): Unit = + RenderingWithStateType( + workflow as StatefulWorkflow, + input, + setRendering + ) + /** * Walk the tree of state machines again, this time gathering snapshots and aggregating them * automatically. */ fun snapshot(workflow: StatefulWorkflow<*, *, *, *>): TreeSnapshot { + // TODO: Figure out how to use `rememberSaveable` for Compose runtime here. @Suppress("UNCHECKED_CAST") val typedWorkflow = workflow as StatefulWorkflow val childSnapshots = subtreeManager.createChildSnapshots() val rootSnapshot = interceptor.intercept(typedWorkflow, this) - .snapshotState(state) + .snapshotState(state.value) return TreeSnapshot( workflowSnapshot = rootSnapshot, // Create the snapshots eagerly since subtreeManager is mutable. @@ -186,17 +211,63 @@ internal class WorkflowNode( eventActionsChannel = eventActionsChannel ) val rendering = interceptor.intercept(workflow, this) - .render(props, state, RenderContext(context, workflow)) + .render(props, state.value, RenderContext(context, workflow)) context.freeze() + commitAndUpdateScopes() + + return rendering + } + + @Composable + private fun RenderingWithStateType( + workflow: StatefulWorkflow, + props: PropsT, + setRendering: (RenderingT) -> Unit + ): Unit { + key(props) { + UpdatePropsAndState(workflow, props) + } + + val realRenderContext = remember(subtreeManager, eventActionsChannel) { + RealRenderContext( + renderer = subtreeManager, + sideEffectRunner = this, + eventActionsChannel = eventActionsChannel + ) + } + val context = remember(realRenderContext, workflow) { + RenderContext( + realRenderContext, + workflow + ) + } + setRendering(interceptor.intercept(workflow, this) + .Rendering(props, state.value, context)) + + SideEffect { + realRenderContext.freeze() + commitAndUpdateScopes() + } + } + + + @Composable + private fun renderWorkflowInSeparateRecomposeScope( + workflow: StatefulWorkflow, + props: PropsT, + setRendering: (RenderingT) -> Unit + ): Unit { + + } + + private fun commitAndUpdateScopes() { // Tear down workflows and workers that are obsolete. subtreeManager.commitRenderedChildren() // Side effect jobs are launched lazily, since they can send actions to the sink, and can only // be started after context is frozen. sideEffects.forEachStaging { it.job.start() } sideEffects.commitStaging { it.job.cancel() } - - return rendering } private fun updatePropsAndState( @@ -205,19 +276,34 @@ internal class WorkflowNode( ) { if (newProps != lastProps) { val newState = interceptor.intercept(workflow, this) - .onPropsChanged(lastProps, newProps, state) - state = newState + .onPropsChanged(lastProps, newProps, state.value) + state.value = newState } lastProps = newProps } + @Composable + private fun UpdatePropsAndState( + workflow: StatefulWorkflow, + newProps: PropsT + ) { + if (newProps != lastProps) { + val newState = interceptor.intercept(workflow, this) + .onPropsChanged(lastProps, newProps, state.value) + state.value = newState + } + SideEffect { + lastProps = newProps + } + } + /** * Applies [action] to this workflow's [state] and * [emits an output to its parent][emitOutputToParent] if necessary. */ private fun applyAction(action: WorkflowAction): T? { - val (newState, tickResult) = action.applyTo(lastProps, state) - state = newState + val (newState, tickResult) = action.applyTo(lastProps, state.value) + state.value = newState @Suppress("UNCHECKED_CAST") return tickResult?.let { emitOutputToParent(it.value) } as T? } 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 9a7e70fec2..f101803238 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 @@ -1,5 +1,9 @@ package com.squareup.workflow1.internal +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import com.squareup.workflow1.ActionProcessingResult import com.squareup.workflow1.PropsUpdated import com.squareup.workflow1.RenderingAndSnapshot @@ -32,7 +36,7 @@ internal class WorkflowRunner( ) { private val workflow = protoWorkflow.asStatefulWorkflow() private val idCounter = IdCounter() - private var currentProps: PropsT = props.value + private var currentProps: MutableState = mutableStateOf(props.value) // Props is a StateFlow, it will immediately produce an item. Without additional handling, the // first call to processActions will see that new props value and trigger another render pass, @@ -52,7 +56,7 @@ internal class WorkflowRunner( private val rootNode = WorkflowNode( id = workflow.id(), workflow = workflow, - initialProps = currentProps, + initialProps = currentProps.value, snapshot = snapshot, baseContext = scope.coroutineContext, interceptor = interceptor, @@ -66,11 +70,29 @@ internal class WorkflowRunner( * between every subsequent call to [processActions]. */ fun nextRendering(): RenderingAndSnapshot { - val rendering = rootNode.render(workflow, currentProps) + val rendering = rootNode.render(workflow, currentProps.value) val snapshot = rootNode.snapshot(workflow) return RenderingAndSnapshot(rendering, snapshot) } + @Composable + fun nextComposedRendering(): RenderingAndSnapshot { + val renderingState = remember { mutableStateOf(null) } + + rootNode.Rendering(workflow, currentProps.value) { + renderingState.value = it + } + + val snapshot = remember { + // need to key this on state inside WorkflowNode. LIkely have a Compose version. + rootNode.snapshot(workflow) + } + + return remember(renderingState.value, snapshot) { + RenderingAndSnapshot(renderingState.value!!, snapshot) + } + } + /** * Stop processing and go to render on 1 of 3 conditions: * 1. Props have changed. @@ -130,7 +152,7 @@ internal class WorkflowRunner( channelResult.exceptionOrNull()?.let { throw it } channelResult.getOrNull()?.let { newProps -> if (currentProps != newProps) { - currentProps = newProps + currentProps.value = newProps } } // Return PropsUpdated to tell the caller to do another render pass, but not emit an output. diff --git a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/SimpleLoggingWorkflowInterceptorTest.kt b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/SimpleLoggingWorkflowInterceptorTest.kt index eafd9cb171..e707cde7c7 100644 --- a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/SimpleLoggingWorkflowInterceptorTest.kt +++ b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/SimpleLoggingWorkflowInterceptorTest.kt @@ -1,5 +1,6 @@ package com.squareup.workflow1 +import androidx.compose.runtime.Composable import com.squareup.workflow1.WorkflowInterceptor.WorkflowSession import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.cancel @@ -97,6 +98,16 @@ internal class SimpleLoggingWorkflowInterceptorTest { fail() } + @Composable + override fun ChildRendering( + child: Workflow, + props: ChildPropsT, + key: String, + handler: (ChildOutputT) -> WorkflowAction + ): ChildRenderingT { + fail() + } + override fun runningSideEffect( key: String, sideEffect: suspend CoroutineScope.() -> Unit diff --git a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/WorkflowInterceptorTest.kt b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/WorkflowInterceptorTest.kt index f53335429c..5f581b8428 100644 --- a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/WorkflowInterceptorTest.kt +++ b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/WorkflowInterceptorTest.kt @@ -2,6 +2,7 @@ package com.squareup.workflow1 +import androidx.compose.runtime.Composable import com.squareup.workflow1.WorkflowInterceptor.RenderContextInterceptor import com.squareup.workflow1.WorkflowInterceptor.WorkflowSession import kotlinx.coroutines.CoroutineScope @@ -62,6 +63,17 @@ internal class WorkflowInterceptorTest { handler: (ChildOutputT) -> WorkflowAction ): ChildRenderingT = fail() + @Composable + override fun ChildRendering( + child: Workflow, + props: ChildPropsT, + key: String, + hoistRendering: @Composable (ChildRenderingT) -> Unit, + handler: (ChildOutputT) -> WorkflowAction + ) { + fail() + } + override fun runningSideEffect( key: String, sideEffect: suspend CoroutineScope.() -> Unit @@ -102,6 +114,17 @@ internal class WorkflowInterceptorTest { handler: (ChildOutputT) -> WorkflowAction ): ChildRenderingT = fail() + @Composable + override fun ChildRendering( + child: Workflow, + props: ChildPropsT, + key: String, + hoistRendering: @Composable (ChildRenderingT) -> Unit, + handler: (ChildOutputT) -> WorkflowAction + ) { + fail() + } + override fun runningSideEffect( key: String, sideEffect: suspend CoroutineScope.() -> Unit @@ -135,6 +158,17 @@ internal class WorkflowInterceptorTest { handler: (ChildOutputT) -> WorkflowAction ): ChildRenderingT = fail() + @Composable + override fun ChildRendering( + child: Workflow, + props: ChildPropsT, + key: String, + hoistRendering: @Composable (ChildRenderingT) -> Unit, + handler: (ChildOutputT) -> WorkflowAction + ) { + fail() + } + override fun runningSideEffect( key: String, sideEffect: suspend CoroutineScope.() -> Unit @@ -194,6 +228,17 @@ internal class WorkflowInterceptorTest { handler: (ChildOutputT) -> WorkflowAction ): ChildRenderingT = fail() + @Composable + override fun ChildRendering( + child: Workflow, + props: ChildPropsT, + key: String, + hoistRendering: @Composable (ChildRenderingT) -> Unit, + handler: (ChildOutputT) -> WorkflowAction + ) { + fail() + } + override fun runningSideEffect( key: String, sideEffect: suspend CoroutineScope.() -> Unit diff --git a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/ChainedWorkflowInterceptorTest.kt b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/ChainedWorkflowInterceptorTest.kt index 896f775703..321fd5a222 100644 --- a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/ChainedWorkflowInterceptorTest.kt +++ b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/ChainedWorkflowInterceptorTest.kt @@ -2,6 +2,7 @@ package com.squareup.workflow1.internal +import androidx.compose.runtime.Composable import com.squareup.workflow1.BaseRenderContext import com.squareup.workflow1.NoopWorkflowInterceptor import com.squareup.workflow1.Sink @@ -308,6 +309,16 @@ internal class ChainedWorkflowInterceptorTest { fail() } + @Composable + override fun ChildRendering( + child: Workflow, + props: ChildPropsT, + key: String, + handler: (ChildOutputT) -> WorkflowAction + ): ChildRenderingT { + fail() + } + override fun runningSideEffect( key: String, sideEffect: suspend CoroutineScope.() -> Unit diff --git a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/RealRenderContextTest.kt b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/RealRenderContextTest.kt index 0162798b56..fc30e79808 100644 --- a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/RealRenderContextTest.kt +++ b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/RealRenderContextTest.kt @@ -2,6 +2,7 @@ package com.squareup.workflow1.internal +import androidx.compose.runtime.Composable import com.squareup.workflow1.Snapshot import com.squareup.workflow1.StatefulWorkflow import com.squareup.workflow1.Workflow @@ -47,6 +48,25 @@ internal class RealRenderContextTest { key, handler as (Any) -> WorkflowAction ) as ChildRenderingT + + @Suppress("UNCHECKED_CAST") + @Composable + override fun Rendering( + child: Workflow, + props: ChildPropsT, + key: String, + hoistRendering: @Composable (ChildRenderingT) -> Unit, + handler: (ChildOutputT) -> WorkflowAction + ) { + hoistRendering( + Rendering( + child, + props, + key, + handler as (Any) -> WorkflowAction + ) as ChildRenderingT + ) + } } private class TestRunner : SideEffectRunner { @@ -82,6 +102,15 @@ internal class RealRenderContextTest { key: String, handler: (ChildOutputT) -> WorkflowAction ): ChildRenderingT = fail() + + @Composable + override fun Rendering( + child: Workflow, + props: ChildPropsT, + key: String, + hoistRendering: @Composable (ChildRenderingT) -> Unit, + handler: (ChildOutputT) -> WorkflowAction + ): Unit = fail() } private class PoisonRunner : SideEffectRunner { diff --git a/workflow-runtime/src/jvmMain/kotlin/com/squareup/workflow1/internal/SystemUtils.kt b/workflow-runtime/src/jvmMain/kotlin/com/squareup/workflow1/internal/SystemUtils.kt index 354e7ef9ca..b41392a09d 100644 --- a/workflow-runtime/src/jvmMain/kotlin/com/squareup/workflow1/internal/SystemUtils.kt +++ b/workflow-runtime/src/jvmMain/kotlin/com/squareup/workflow1/internal/SystemUtils.kt @@ -1,3 +1,5 @@ package com.squareup.workflow1.internal internal actual fun currentTimeMillis(): Long = System.currentTimeMillis() + +internal actual fun nanoTime(): Long = System.nanoTime() diff --git a/workflow-testing/api/workflow-testing.api b/workflow-testing/api/workflow-testing.api index 940ad14b6b..2220f8e269 100644 --- a/workflow-testing/api/workflow-testing.api +++ b/workflow-testing/api/workflow-testing.api @@ -1,6 +1,7 @@ public final class com/squareup/workflow1/testing/RealRenderTester : com/squareup/workflow1/testing/RenderTester, com/squareup/workflow1/BaseRenderContext, com/squareup/workflow1/Sink, com/squareup/workflow1/testing/RenderTestResult { public fun (Lcom/squareup/workflow1/StatefulWorkflow;Ljava/lang/Object;Ljava/lang/Object;Ljava/util/List;Ljava/util/List;ZLcom/squareup/workflow1/WorkflowAction;Ljava/util/List;Ljava/util/List;)V public synthetic fun (Lcom/squareup/workflow1/StatefulWorkflow;Ljava/lang/Object;Ljava/lang/Object;Ljava/util/List;Ljava/util/List;ZLcom/squareup/workflow1/WorkflowAction;Ljava/util/List;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun ChildRendering (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function10;)Lkotlin/jvm/functions/Function9; public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function11;)Lkotlin/jvm/functions/Function10; public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;)Lkotlin/jvm/functions/Function0; @@ -91,6 +92,7 @@ public final class com/squareup/workflow1/testing/RealRenderTesterKt { public final class com/squareup/workflow1/testing/RenderIdempotencyChecker : com/squareup/workflow1/WorkflowInterceptor { public static final field INSTANCE Lcom/squareup/workflow1/testing/RenderIdempotencyChecker; + public fun Rendering (Ljava/lang/Object;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lkotlin/jvm/functions/Function4;)V 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; diff --git a/workflow-testing/src/main/java/com/squareup/workflow1/testing/RealRenderTester.kt b/workflow-testing/src/main/java/com/squareup/workflow1/testing/RealRenderTester.kt index 4e230c57ae..1ed0a0a246 100644 --- a/workflow-testing/src/main/java/com/squareup/workflow1/testing/RealRenderTester.kt +++ b/workflow-testing/src/main/java/com/squareup/workflow1/testing/RealRenderTester.kt @@ -1,5 +1,6 @@ package com.squareup.workflow1.testing +import androidx.compose.runtime.Composable import com.squareup.workflow1.BaseRenderContext import com.squareup.workflow1.RenderContext import com.squareup.workflow1.Sink @@ -221,6 +222,69 @@ internal class RealRenderTester( return match.childRendering as ChildRenderingT } + @Composable + override fun ChildRendering( + child: Workflow, + props: ChildPropsT, + key: String, + hoistRendering: @Composable (ChildRenderingT) -> Unit, + handler: (ChildOutputT) -> WorkflowAction + ) { + val identifierPair = Pair(child.identifier, key) + require(identifierPair !in renderedChildren) { + "Expected keys to be unique for ${child.identifier}: key=\"$key\"" + } + renderedChildren += identifierPair + + val description = buildString { + append("child ") + append(child.identifier) + if (key.isNotEmpty()) { + append(" with key \"$key\"") + } + } + val invocation = createRenderChildInvocation(child, props, key) + val matches = expectations.filterIsInstance() + .mapNotNull { + val matchResult = it.matcher(invocation) + if (matchResult is Matched) Pair(it, matchResult) else null + } + if (matches.isEmpty()) { + throw AssertionError("Tried to render unexpected $description") + } + + val exactMatches = matches.filter { it.first.exactMatch } + val (_, match) = when { + exactMatches.size == 1 -> { + exactMatches.single() + .also { (expected, _) -> + expectations -= expected + consumedExpectations += expected + } + } + exactMatches.size > 1 -> { + throw AssertionError( + "Multiple expectations matched $description:\n" + + exactMatches.joinToString(separator = "\n") { " ${it.first.describe()}" } + ) + } + // Inexact matches are not consumable. + else -> matches.first() + } + + if (match.output != null) { + check(processedAction == null) { + "Expected only one output to be expected: $description expected to emit " + + "${match.output.value} but $processedAction was already processed." + } + @Suppress("UNCHECKED_CAST") + processedAction = handler(match.output.value as ChildOutputT) + } + + @Suppress("UNCHECKED_CAST") + hoistRendering(match.childRendering as ChildRenderingT) + } + override fun runningSideEffect( key: String, sideEffect: suspend CoroutineScope.() -> Unit diff --git a/workflow-tracing/api/workflow-tracing.api b/workflow-tracing/api/workflow-tracing.api index bdce09a3c4..4732d47574 100644 --- a/workflow-tracing/api/workflow-tracing.api +++ b/workflow-tracing/api/workflow-tracing.api @@ -21,6 +21,7 @@ public final class com/squareup/workflow1/diagnostic/tracing/TracingWorkflowInte public fun (Lcom/squareup/workflow1/diagnostic/tracing/MemoryStats;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)V public fun (Lcom/squareup/workflow1/diagnostic/tracing/MemoryStats;Lkotlin/jvm/functions/Function2;)V public synthetic fun (Lcom/squareup/workflow1/diagnostic/tracing/MemoryStats;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun Rendering (Ljava/lang/Object;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lkotlin/jvm/functions/Function4;)V 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; diff --git a/workflow-ui/compose-tooling/build.gradle.kts b/workflow-ui/compose-tooling/build.gradle.kts index f7ffc1e37d..d7906a0a9b 100644 --- a/workflow-ui/compose-tooling/build.gradle.kts +++ b/workflow-ui/compose-tooling/build.gradle.kts @@ -11,7 +11,7 @@ plugins { android { buildFeatures.compose = true composeOptions { - kotlinCompilerExtensionVersion = libs.versions.androidx.compose.compiler.get() + kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get() } } diff --git a/workflow-ui/compose/build.gradle.kts b/workflow-ui/compose/build.gradle.kts index ee921395c4..468b71a810 100644 --- a/workflow-ui/compose/build.gradle.kts +++ b/workflow-ui/compose/build.gradle.kts @@ -11,7 +11,7 @@ plugins { android { buildFeatures.compose = true composeOptions { - kotlinCompilerExtensionVersion = libs.versions.androidx.compose.compiler.get() + kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get() } } From d48eaf6cbc7f8a0fc93d802a0917757d85757cf4 Mon Sep 17 00:00:00 2001 From: Stephen Edwards Date: Thu, 7 Jul 2022 18:06:47 -0400 Subject: [PATCH 2/3] Cleanup Side Effects; Able to Freeze --- .../complex/poetry/ComposeInheritanceTest.kt | 59 ------- .../complex/poetry/RenderPassTest.kt | 68 +++++++- .../complex/poetry/PerformancePoemWorkflow.kt | 34 +--- .../poetry/PerformancePoemsBrowserWorkflow.kt | 41 +++-- .../poetry/PerformancePoetryActivity.kt | 5 +- .../complex/poetry/cyborgs/PoetryCyborgs.kt | 1 + .../ActionHandlingTracingInterceptor.kt | 16 +- .../PerformanceTracingInterceptor.kt | 13 +- .../RenderPassCountingInterceptor.kt | 18 +- dependencies/classpath.txt | 1 + .../dependencies/runtimeClasspath.txt | 4 +- .../dependencies/releaseRuntimeClasspath.txt | 18 +- .../dependencies/runtimeClasspath.txt | 8 +- workflow-core/api/workflow-core.api | 25 ++- .../dependencies/jvmRuntimeClasspath.txt | 8 +- .../dependencies/runtimeClasspath.txt | 8 +- .../squareup/workflow1/StatefulWorkflow.kt | 157 ++++++++++++++---- .../squareup/workflow1/StatelessWorkflow.kt | 37 +++-- .../com/squareup/workflow1/WorkerWorkflow.kt | 4 +- .../workflow1/ComposeInheritanceTest.kt | 68 -------- workflow-runtime/api/workflow-runtime.api | 34 ++-- .../dependencies/jvmRuntimeClasspath.txt | 8 +- .../jvmWorkflowNodeRuntimeClasspath.txt | 6 + .../com/squareup/workflow1/RenderWorkflow.kt | 17 +- .../SimpleLoggingWorkflowInterceptor.kt | 5 +- .../squareup/workflow1/WorkflowInterceptor.kt | 7 +- .../workflow1/internal/ActiveStagingList.kt | 22 +++ .../internal/ChainedWorkflowInterceptor.kt | 45 +++-- .../workflow1/internal/InlineLinkedList.kt | 14 ++ .../workflow1/internal/RealRenderContext.kt | 8 +- .../workflow1/internal/SubtreeManager.kt | 44 ++++- .../workflow1/internal/WorkflowChildNode.kt | 10 +- .../workflow1/internal/WorkflowNode.kt | 70 ++++---- .../workflow1/internal/WorkflowRunner.kt | 21 +-- .../workflow1/WorkflowInterceptorTest.kt | 12 +- .../internal/RealRenderContextTest.kt | 20 +-- .../dependencies/runtimeClasspath.txt | 12 +- workflow-testing/api/workflow-testing.api | 4 +- .../dependencies/runtimeClasspath.txt | 12 +- .../workflow1/testing/RealRenderTester.kt | 5 +- workflow-tracing/api/workflow-tracing.api | 2 +- .../dependencies/runtimeClasspath.txt | 8 +- .../dependencies/releaseRuntimeClasspath.txt | 35 ++-- .../dependencies/releaseRuntimeClasspath.txt | 29 ++-- .../dependencies/releaseRuntimeClasspath.txt | 10 +- .../dependencies/runtimeClasspath.txt | 4 +- .../dependencies/releaseRuntimeClasspath.txt | 10 +- .../dependencies/runtimeClasspath.txt | 4 +- .../dependencies/releaseRuntimeClasspath.txt | 10 +- 49 files changed, 623 insertions(+), 458 deletions(-) delete mode 100644 benchmarks/performance-poetry/complex-poetry/src/androidTest/java/com/squareup/benchmarks/performance/complex/poetry/ComposeInheritanceTest.kt delete mode 100644 workflow-core/src/commonTest/kotlin/com/squareup/workflow1/ComposeInheritanceTest.kt diff --git a/benchmarks/performance-poetry/complex-poetry/src/androidTest/java/com/squareup/benchmarks/performance/complex/poetry/ComposeInheritanceTest.kt b/benchmarks/performance-poetry/complex-poetry/src/androidTest/java/com/squareup/benchmarks/performance/complex/poetry/ComposeInheritanceTest.kt deleted file mode 100644 index a1ebe2d7bd..0000000000 --- a/benchmarks/performance-poetry/complex-poetry/src/androidTest/java/com/squareup/benchmarks/performance/complex/poetry/ComposeInheritanceTest.kt +++ /dev/null @@ -1,59 +0,0 @@ -package com.squareup.benchmarks.performance.complex.poetry - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.test.ext.junit.runners.AndroidJUnit4 -import app.cash.molecule.AndroidUiDispatcher -import app.cash.molecule.launchMolecule -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -class ComposeInheritanceTest { - - abstract class Parent { - @Composable - open fun AComposable( - hoistToggleState: @Composable (s: T) -> Unit - ): Unit = throw IllegalStateException("I don't want to be a parent.") - } - - class Child( - private val payload: T - ): Parent() { - @Composable - override fun AComposable( - hoistToggleState: @Composable (s: T) -> Unit - ) { - println("Can you hear me now? $payload") - hoistToggleState(payload) - } - } - - @Composable - fun Emitter(someObject: Parent): T? { - val payload: MutableState = remember { mutableStateOf(null) } - someObject.AComposable { - payload.value = it - } - return payload.value - } - - @OptIn(ExperimentalCoroutinesApi::class) - @Test fun testComposableOverloading() { - val child: Parent = Child("a test") - val testScope = CoroutineScope(AndroidUiDispatcher.Main) - - val testFlow = testScope.launchMolecule { - Emitter(child) - } - - assertThat(testFlow.value).isEqualTo("a test") - } - -} diff --git a/benchmarks/performance-poetry/complex-poetry/src/androidTest/java/com/squareup/benchmarks/performance/complex/poetry/RenderPassTest.kt b/benchmarks/performance-poetry/complex-poetry/src/androidTest/java/com/squareup/benchmarks/performance/complex/poetry/RenderPassTest.kt index b3e611f5b2..7bc03ff1c7 100644 --- a/benchmarks/performance-poetry/complex-poetry/src/androidTest/java/com/squareup/benchmarks/performance/complex/poetry/RenderPassTest.kt +++ b/benchmarks/performance-poetry/complex-poetry/src/androidTest/java/com/squareup/benchmarks/performance/complex/poetry/RenderPassTest.kt @@ -8,6 +8,7 @@ import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.squareup.benchmarks.performance.complex.poetry.PerformancePoetryActivity.Companion.EXTRA_PERF_CONFIG_INITIALIZING import com.squareup.benchmarks.performance.complex.poetry.PerformancePoetryActivity.Companion.EXTRA_PERF_CONFIG_REPEAT +import com.squareup.benchmarks.performance.complex.poetry.PerformancePoetryActivity.Companion.EXTRA_RUNTIME_COMPOSE_RUNTIME import com.squareup.benchmarks.performance.complex.poetry.PerformancePoetryActivity.Companion.EXTRA_RUNTIME_FRAME_TIMEOUT import com.squareup.benchmarks.performance.complex.poetry.cyborgs.landscapeOrientation import com.squareup.benchmarks.performance.complex.poetry.cyborgs.openRavenAndNavigate @@ -16,6 +17,7 @@ import com.squareup.benchmarks.performance.complex.poetry.cyborgs.waitForPoetry import com.squareup.benchmarks.performance.complex.poetry.instrumentation.RenderPassCountingInterceptor import org.junit.Assert.fail import org.junit.Before +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith @@ -30,7 +32,8 @@ class RenderPassTest { val useInitializingState: Boolean, val useHighFrequencyRange: Boolean, val baselineExpectation: RenderExpectation, - val frameTimeoutExpectation: RenderExpectation + val frameTimeoutExpectation: RenderExpectation, + val frameTimeoutComposeExpectation: RenderExpectation ) data class RenderExpectation( @@ -56,15 +59,15 @@ class RenderPassTest { } @Test fun renderPassCounterBaselineComplexWithInitializingState() { - runRenderPassCounter(COMPLEX_INITIALIZING, useFrameTimeout = false) + runRenderPassCounter(COMPLEX_INITIALIZING) } @Test fun renderPassCounterBaselineComplexNoInitializingState() { - runRenderPassCounter(COMPLEX_NO_INITIALIZING, useFrameTimeout = false) + runRenderPassCounter(COMPLEX_NO_INITIALIZING) } @Test fun renderPassCounterBaselineComplexNoInitializingStateHighFrequencyEvents() { - runRenderPassCounter(COMPLEX_NO_INITIALIZING_HIGH_FREQUENCY, useFrameTimeout = false) + runRenderPassCounter(COMPLEX_NO_INITIALIZING_HIGH_FREQUENCY) } @Test fun renderPassCounterFrameTimeoutComplexWithInitializingState() { @@ -79,10 +82,33 @@ class RenderPassTest { runRenderPassCounter(COMPLEX_NO_INITIALIZING_HIGH_FREQUENCY, useFrameTimeout = true) } + @Ignore( + "Not sure why but this gets stuck on initializing. Compose doesn't get the next" + + " frame when this is started by the test, but it does when running directly." + ) + @Test fun renderPassCounterFrameTimeoutComposeComplexWithInitializingState() { + runRenderPassCounter(COMPLEX_INITIALIZING, useFrameTimeout = true, useCompose = true) + } + + @Test fun renderPassCounterFrameTimeoutComposeComplexNoInitializingState() { + runRenderPassCounter(COMPLEX_NO_INITIALIZING, useFrameTimeout = true, useCompose = true) + } + + @Test fun renderPassCounterFrameTimeoutComposeComplexNoInitializingStateHighFrequencyEvents() { + runRenderPassCounter( + COMPLEX_NO_INITIALIZING_HIGH_FREQUENCY, + useFrameTimeout = true, + useCompose = true + ) + } + private fun runRenderPassCounter( scenario: Scenario, - useFrameTimeout: Boolean + useFrameTimeout: Boolean = false, + useCompose: Boolean = false ) { + if (useCompose) require(useFrameTimeout) { "Only use Compose Frame Timeout." } + val intent = Intent(context, PerformancePoetryActivity::class.java).apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) @@ -90,8 +116,9 @@ class RenderPassTest { EXTRA_PERF_CONFIG_INITIALIZING, scenario.useInitializingState ) + putExtra(EXTRA_RUNTIME_FRAME_TIMEOUT, useFrameTimeout) if (useFrameTimeout) { - putExtra(EXTRA_RUNTIME_FRAME_TIMEOUT, useFrameTimeout) + putExtra(EXTRA_RUNTIME_COMPOSE_RUNTIME, useCompose) } if (scenario.useHighFrequencyRange) { putExtra(EXTRA_PERF_CONFIG_REPEAT, PerformancePoetryActivity.HIGH_FREQUENCY_REPEAT_COUNT) @@ -111,11 +138,19 @@ class RenderPassTest { device.openRavenAndNavigate() val expectation = - if (useFrameTimeout) scenario.frameTimeoutExpectation else + if (useFrameTimeout) { + if (useCompose) { + scenario.frameTimeoutComposeExpectation + } else { + scenario.frameTimeoutExpectation + } + } else { scenario.baselineExpectation + } val title = if (useFrameTimeout) { - "Runtime: FrameTimeout; " + val usingCompose = if (useCompose) "(Using Compose Optimizations)" else "(No Compose)" + "Runtime: FrameTimeout $usingCompose;" } else { "Runtime: RenderPerAction; " } + scenario.title @@ -227,6 +262,11 @@ class RenderPassTest { totalPasses = 41..42, freshRenderedNodes = 85..85, staleRenderedNodes = 436..436 + ), + frameTimeoutComposeExpectation = RenderExpectation( + totalPasses = 41..42, + freshRenderedNodes = 85..85, + staleRenderedNodes = 436..436 ) ) @@ -243,6 +283,11 @@ class RenderPassTest { totalPasses = 40..41, freshRenderedNodes = 83..83, staleRenderedNodes = 431..431 + ), + frameTimeoutComposeExpectation = RenderExpectation( + totalPasses = 40..41, + freshRenderedNodes = 113..113, + staleRenderedNodes = 82..82 ) ) @@ -270,9 +315,14 @@ class RenderPassTest { staleRenderedNodes = 2350..2350 ), frameTimeoutExpectation = RenderExpectation( - totalPasses = 64..68, + totalPasses = 60..64, freshRenderedNodes = 106..108, staleRenderedNodes = 679..698 + ), + frameTimeoutComposeExpectation = RenderExpectation( + totalPasses = 59..64, + freshRenderedNodes = 259..259, + staleRenderedNodes = 207..207 ) ) diff --git a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/PerformancePoemWorkflow.kt b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/PerformancePoemWorkflow.kt index 82a72c6ddc..d12f2e6202 100644 --- a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/PerformancePoemWorkflow.kt +++ b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/PerformancePoemWorkflow.kt @@ -1,9 +1,6 @@ package com.squareup.benchmarks.performance.complex.poetry import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import com.squareup.benchmarks.performance.complex.poetry.PerformancePoemWorkflow.Action.ClearSelection import com.squareup.benchmarks.performance.complex.poetry.PerformancePoemWorkflow.Action.HandleStanzaListOutput import com.squareup.benchmarks.performance.complex.poetry.PerformancePoemWorkflow.Action.SelectNext @@ -20,7 +17,6 @@ import com.squareup.benchmarks.performance.complex.poetry.views.BlankScreen import com.squareup.sample.container.overviewdetail.OverviewDetailScreen import com.squareup.sample.poetry.PoemWorkflow import com.squareup.sample.poetry.PoemWorkflow.ClosePoem -import com.squareup.sample.poetry.StanzaListScreen import com.squareup.sample.poetry.StanzaListWorkflow import com.squareup.sample.poetry.StanzaListWorkflow.NO_SELECTED_STANZA import com.squareup.sample.poetry.StanzaScreen @@ -326,7 +322,7 @@ class PerformancePoemWorkflow( .copy(selection = stanzaIndex) if (stanzaIndex != NO_SELECTED_STANZA) { - val previousStanzas = renderProps.stanzas.subList(0, stanzaIndex) + val stackedStanzas = renderProps.stanzas.subList(0, stanzaIndex + 1) .mapIndexed { index, _ -> context.ChildRendering( StanzaWorkflow, @@ -337,28 +333,13 @@ class PerformancePoemWorkflow( ), key = "$index", ) { - noAction() + when (it) { + CloseStanzas -> ClearSelection(simulatedPerfConfig) + ShowPreviousStanza -> SelectPrevious(simulatedPerfConfig) + ShowNextStanza -> SelectNext(simulatedPerfConfig) + } } - } - val visibleStanza = context.ChildRendering( - StanzaWorkflow, - Props( - poem = renderProps, - index = stanzaIndex, - eventHandlerTag = ActionHandlingTracingInterceptor::keyForTrace - ), - key = "$stanzaIndex", - ) { - when (it) { - CloseStanzas -> ClearSelection(simulatedPerfConfig) - ShowPreviousStanza -> SelectPrevious(simulatedPerfConfig) - ShowNextStanza -> SelectNext(simulatedPerfConfig) - } - } - - val stackedStanzas = visibleStanza.let { - (previousStanzas + it).toBackStackScreen() - } + }.toBackStackScreen() return OverviewDetailScreen( overviewRendering = BackStackScreen(stanzaListOverview), @@ -366,7 +347,6 @@ class PerformancePoemWorkflow( ) } - return OverviewDetailScreen( overviewRendering = BackStackScreen(stanzaListOverview), selectDefault = { diff --git a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/PerformancePoemsBrowserWorkflow.kt b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/PerformancePoemsBrowserWorkflow.kt index 287fc30510..424f73028a 100644 --- a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/PerformancePoemsBrowserWorkflow.kt +++ b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/PerformancePoemsBrowserWorkflow.kt @@ -25,6 +25,7 @@ import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.container.BackStackScreen import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow +import java.lang.IllegalStateException /** * Version of [PoemsBrowserWorkflow] that takes in a [SimulatedPerfConfig] to control the @@ -161,6 +162,22 @@ class PerformancePoemsBrowserWorkflow( renderState: State, context: RenderContext, ): OverviewDetailScreen { + + // Again, then entire `Initializing` state is a smell, which is most obvious from the + // use of `Worker.from { Unit }`. A Worker doing no work and only shuttling the state + // along is usually the sign you have an extraneous state that can be collapsed! + // Don't try this at home. + if (renderState is Initializing) { + context.runningWorker(TraceableWorker.from("BrowserInitializing") { Unit }, "init") { + isLoading.value = true + action { + isLoading.value = false + state = NoSelection + } + } + return OverviewDetailScreen(overviewRendering = BackStackScreen(BlankScreen)) + } + val poemListProps = Props( poems = renderProps, eventHandlerTag = ActionHandlingTracingInterceptor::keyForTrace @@ -174,26 +191,12 @@ class PerformancePoemsBrowserWorkflow( choosePoem(selected) } when (renderState) { - // Again, then entire `Initializing` state is a smell, which is most obvious from the - // use of `Worker.from { Unit }`. A Worker doing no work and only shuttling the state - // along is usually the sign you have an extraneous state that can be collapsed! - // Don't try this at home. - is Initializing -> { - context.runningWorker(TraceableWorker.from("BrowserInitializing") { Unit }, "init") { - isLoading.value = true - action { - isLoading.value = false - state = NoSelection - } - } - return OverviewDetailScreen(overviewRendering = BackStackScreen(BlankScreen)) - } is NoSelection -> { return OverviewDetailScreen( - overviewRendering = BackStackScreen( - poemListRendering.copy(selection = NO_POEM_SELECTED) - ) + overviewRendering = BackStackScreen( + poemListRendering.copy(selection = NO_POEM_SELECTED) ) + ) } is ComplexCall -> { context.runningWorker( @@ -243,8 +246,10 @@ class PerformancePoemsBrowserWorkflow( key = "", ) { clearSelection } } + else -> { + throw IllegalStateException("$renderState state is impossible.") + } } - } override fun snapshotState(state: State): Snapshot? = null diff --git a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/PerformancePoetryActivity.kt b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/PerformancePoetryActivity.kt index d3c3a714f0..29e1b3b489 100644 --- a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/PerformancePoetryActivity.kt +++ b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/PerformancePoetryActivity.kt @@ -84,8 +84,9 @@ class PerformancePoetryActivity : AppCompatActivity() { } val isFrameTimeout = intent.getBooleanExtra(EXTRA_RUNTIME_FRAME_TIMEOUT, false) + val useCompose = intent.getBooleanExtra(EXTRA_RUNTIME_COMPOSE_RUNTIME, false) val runtimeConfig = if (isFrameTimeout) { - FrameTimeout(useComposeInRuntime = true) + FrameTimeout(useComposeInRuntime = useCompose) } else { RenderPerAction } @@ -247,6 +248,8 @@ class PerformancePoetryActivity : AppCompatActivity() { const val EXTRA_PERF_CONFIG_DELAY = "complex.poetry.performance.config.delay.length" const val EXTRA_RUNTIME_FRAME_TIMEOUT = "complex.poetry.performance.config.runtime.frametimeout" + const val EXTRA_RUNTIME_COMPOSE_RUNTIME = + "complex.poetry.performance.config.runtime.compose" const val SELECT_ON_TIMEOUT_LOG_NAME = "kotlinx.coroutines.selects.SelectBuilderImpl\$onTimeout\$\$inlined\$Runnable" diff --git a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/cyborgs/PoetryCyborgs.kt b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/cyborgs/PoetryCyborgs.kt index 50298a8ec1..de3c38846d 100644 --- a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/cyborgs/PoetryCyborgs.kt +++ b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/cyborgs/PoetryCyborgs.kt @@ -48,6 +48,7 @@ fun UiDevice.resetToRootPoetryList() { */ fun UiDevice.openRavenAndNavigate() { waitForIdle() + waitForIdle(5_000) waitForAndClick(RavenPoemSelector) waitForLoadingInterstitial() waitForAndClick(By.textStartsWith("Deep into that darkness peering")) diff --git a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/instrumentation/ActionHandlingTracingInterceptor.kt b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/instrumentation/ActionHandlingTracingInterceptor.kt index 9890679503..92d0145611 100644 --- a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/instrumentation/ActionHandlingTracingInterceptor.kt +++ b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/instrumentation/ActionHandlingTracingInterceptor.kt @@ -1,6 +1,7 @@ package com.squareup.benchmarks.performance.complex.poetry.instrumentation import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.tracing.trace import com.squareup.workflow1.BaseRenderContext import com.squareup.workflow1.WorkflowAction @@ -78,11 +79,16 @@ class ActionHandlingTracingInterceptor : WorkflowInterceptor, Resettable { context: BaseRenderContext, session: WorkflowSession, proceed: @Composable (P, S, RenderContextInterceptor?) -> R - ): R = proceed( - renderProps, - renderState, - EventHandlingTracingRenderContextInterceptor(actionCounts) - ) + ): R { + val rci = remember { + EventHandlingTracingRenderContextInterceptor(actionCounts) + } + return proceed( + renderProps, + renderState, + rci + ) + } override fun reset() { actionCounts.clear() diff --git a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/instrumentation/PerformanceTracingInterceptor.kt b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/instrumentation/PerformanceTracingInterceptor.kt index ebcf11f82d..cedbf77f0b 100644 --- a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/instrumentation/PerformanceTracingInterceptor.kt +++ b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/instrumentation/PerformanceTracingInterceptor.kt @@ -1,6 +1,8 @@ package com.squareup.benchmarks.performance.complex.poetry.instrumentation import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.remember import androidx.tracing.Trace import com.squareup.benchmarks.performance.complex.poetry.PerformancePoemWorkflow import com.squareup.benchmarks.performance.complex.poetry.PerformancePoemsBrowserWorkflow @@ -45,11 +47,16 @@ class PerformanceTracingInterceptor( proceed: @Composable (P, S, RenderContextInterceptor?) -> R ): R { // TODO: Fix that these are illegal side effects in a Composable - val traceIdIndex = NODES_TO_TRACE.indexOfFirst { it.second == session.identifier } - val isRoot = before(traceIdIndex, session) - return proceed(renderProps, renderState, null).also { + val traceIdIndex = remember(session) { + NODES_TO_TRACE.indexOfFirst { it.second == session.identifier } + } + val isRoot = remember(session) { + before(traceIdIndex, session) + } + SideEffect { after(traceIdIndex = traceIdIndex, isRoot = isRoot) } + return proceed(renderProps, renderState, null) } private fun before( diff --git a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/instrumentation/RenderPassCountingInterceptor.kt b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/instrumentation/RenderPassCountingInterceptor.kt index e6d27d9fdd..eb35b57fef 100644 --- a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/instrumentation/RenderPassCountingInterceptor.kt +++ b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/instrumentation/RenderPassCountingInterceptor.kt @@ -1,6 +1,8 @@ package com.squareup.benchmarks.performance.complex.poetry.instrumentation import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.remember import com.squareup.workflow1.BaseRenderContext import com.squareup.workflow1.WorkflowInterceptor import com.squareup.workflow1.WorkflowInterceptor.RenderContextInterceptor @@ -16,7 +18,7 @@ import com.squareup.workflow1.WorkflowInterceptor.WorkflowSession */ class RenderPassCountingInterceptor : WorkflowInterceptor, Resettable { val renderEfficiencyTracking = RenderEfficiency() - lateinit var renderPassStats: RenderStats + private var renderPassStats: RenderStats = RenderStats() private val nodeStates: MutableMap = mutableMapOf() override fun onRender( @@ -41,12 +43,13 @@ class RenderPassCountingInterceptor : WorkflowInterceptor, Resettable { proceed: @Composable (P, S, RenderContextInterceptor?) -> R ): R { - // TODO: This counting is a side effect that is technically illegal here in a Composable and it - // is certainly not idempotent. - val isRoot = before(session, renderState) - return proceed(renderProps, renderState, null).also { + val isRoot = remember(session, renderState) { + before(session, renderState) + } + SideEffect { after(isRoot) } + return proceed(renderProps, renderState, null) } private fun before( @@ -55,10 +58,6 @@ class RenderPassCountingInterceptor : WorkflowInterceptor, Resettable { ): Boolean { val isRoot = session.parent == null - if (isRoot) { - renderPassStats = RenderStats() - } - renderPassStats.apply { // Update stats for this render pass with this node. val renderStateString = renderState.toString() @@ -81,6 +80,7 @@ class RenderPassCountingInterceptor : WorkflowInterceptor, Resettable { if (isRoot) { renderEfficiencyTracking.totalRenderPasses += 1 renderEfficiencyTracking.totalNodeStats += renderPassStats + renderPassStats = RenderStats() } } diff --git a/dependencies/classpath.txt b/dependencies/classpath.txt index 82368bdac6..563e003c90 100644 --- a/dependencies/classpath.txt +++ b/dependencies/classpath.txt @@ -1,5 +1,6 @@ androidx.databinding:databinding-common:7.1.3 androidx.databinding:databinding-compiler-common:7.1.3 +app.cash.molecule:molecule-gradle-plugin:0.3.0-SNAPSHOT com.android.databinding:baseLibrary:7.1.3 com.android.tools.analytics-library:crash:30.1.3 com.android.tools.analytics-library:protos:30.1.3 diff --git a/trace-encoder/dependencies/runtimeClasspath.txt b/trace-encoder/dependencies/runtimeClasspath.txt index ec8fab80ac..3271233380 100644 --- a/trace-encoder/dependencies/runtimeClasspath.txt +++ b/trace-encoder/dependencies/runtimeClasspath.txt @@ -6,6 +6,6 @@ org.jetbrains.kotlin:kotlin-stdlib-common:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.10 org.jetbrains.kotlin:kotlin-stdlib:1.6.10 -org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.1 -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1 +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.2 +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2 org.jetbrains:annotations:13.0 diff --git a/workflow-config/config-android/dependencies/releaseRuntimeClasspath.txt b/workflow-config/config-android/dependencies/releaseRuntimeClasspath.txt index 0e0e4fb6ef..60c456112a 100644 --- a/workflow-config/config-android/dependencies/releaseRuntimeClasspath.txt +++ b/workflow-config/config-android/dependencies/releaseRuntimeClasspath.txt @@ -1,12 +1,26 @@ :workflow-core :workflow-runtime +androidx.annotation:annotation:1.1.0 +androidx.arch.core:core-common:2.1.0 +androidx.arch.core:core-runtime:2.1.0 +androidx.collection:collection:1.0.0 +androidx.compose.runtime:runtime:1.1.0 +androidx.core:core-ktx:1.2.0 +androidx.core:core:1.2.0 +androidx.lifecycle:lifecycle-common:2.4.0 +androidx.lifecycle:lifecycle-runtime:2.4.0 +androidx.versionedparcelable:versionedparcelable:1.1.0 +app.cash.molecule:molecule-runtime-android:0.3.0-SNAPSHOT +app.cash.molecule:molecule-runtime:0.3.0-SNAPSHOT com.squareup.okio:okio-jvm:3.0.0 com.squareup.okio:okio:3.0.0 +org.jetbrains.compose.runtime:runtime:1.1.0 org.jetbrains.kotlin:kotlin-bom:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-common:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.10 org.jetbrains.kotlin:kotlin-stdlib:1.6.10 -org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.1 -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1 +org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2 +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.2 +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2 org.jetbrains:annotations:13.0 diff --git a/workflow-config/config-jvm/dependencies/runtimeClasspath.txt b/workflow-config/config-jvm/dependencies/runtimeClasspath.txt index 0e0e4fb6ef..8a28ebcd9e 100644 --- a/workflow-config/config-jvm/dependencies/runtimeClasspath.txt +++ b/workflow-config/config-jvm/dependencies/runtimeClasspath.txt @@ -1,12 +1,16 @@ :workflow-core :workflow-runtime +app.cash.molecule:molecule-runtime-jvm:0.3.0-SNAPSHOT +app.cash.molecule:molecule-runtime:0.3.0-SNAPSHOT com.squareup.okio:okio-jvm:3.0.0 com.squareup.okio:okio:3.0.0 +org.jetbrains.compose.runtime:runtime-desktop:1.1.0 +org.jetbrains.compose.runtime:runtime:1.1.0 org.jetbrains.kotlin:kotlin-bom:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-common:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.10 org.jetbrains.kotlin:kotlin-stdlib:1.6.10 -org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.1 -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1 +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.2 +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2 org.jetbrains:annotations:13.0 diff --git a/workflow-core/api/workflow-core.api b/workflow-core/api/workflow-core.api index 880b816783..16d38b86ec 100644 --- a/workflow-core/api/workflow-core.api +++ b/workflow-core/api/workflow-core.api @@ -2,7 +2,7 @@ public abstract interface class com/squareup/workflow1/ActionProcessingResult { } public abstract interface class com/squareup/workflow1/BaseRenderContext { - public abstract fun ChildRendering (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)V + public abstract fun ChildRendering (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; public abstract fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function10;)Lkotlin/jvm/functions/Function9; public abstract fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function11;)Lkotlin/jvm/functions/Function10; public abstract fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;)Lkotlin/jvm/functions/Function0; @@ -120,7 +120,7 @@ public final class com/squareup/workflow1/Snapshots { public abstract class com/squareup/workflow1/StatefulWorkflow : com/squareup/workflow1/Workflow { public static final field $stable I public fun ()V - public fun Rendering (Ljava/lang/Object;Ljava/lang/Object;Lcom/squareup/workflow1/StatefulWorkflow$RenderContext;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;I)V + public fun Rendering (Ljava/lang/Object;Ljava/lang/Object;Lcom/squareup/workflow1/StatefulWorkflow$RenderContext;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; public final fun asStatefulWorkflow ()Lcom/squareup/workflow1/StatefulWorkflow; public abstract fun initialState (Ljava/lang/Object;Lcom/squareup/workflow1/Snapshot;)Ljava/lang/Object; public fun onPropsChanged (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; @@ -130,7 +130,7 @@ public abstract class com/squareup/workflow1/StatefulWorkflow : com/squareup/wor public final class com/squareup/workflow1/StatefulWorkflow$RenderContext : com/squareup/workflow1/BaseRenderContext { public fun (Lcom/squareup/workflow1/StatefulWorkflow;Lcom/squareup/workflow1/BaseRenderContext;)V - public fun ChildRendering (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)V + public fun ChildRendering (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function10;)Lkotlin/jvm/functions/Function9; public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function11;)Lkotlin/jvm/functions/Function10; public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;)Lkotlin/jvm/functions/Function0; @@ -150,14 +150,14 @@ public final class com/squareup/workflow1/StatefulWorkflow$RenderContext : com/s public abstract class com/squareup/workflow1/StatelessWorkflow : com/squareup/workflow1/Workflow { public static final field $stable I public fun ()V - public fun Rendering (Ljava/lang/Object;Lcom/squareup/workflow1/StatelessWorkflow$RenderContext;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;I)V + public fun Rendering (Ljava/lang/Object;Lcom/squareup/workflow1/StatelessWorkflow$RenderContext;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; public final fun asStatefulWorkflow ()Lcom/squareup/workflow1/StatefulWorkflow; public abstract fun render (Ljava/lang/Object;Lcom/squareup/workflow1/StatelessWorkflow$RenderContext;)Ljava/lang/Object; } public final class com/squareup/workflow1/StatelessWorkflow$RenderContext : com/squareup/workflow1/BaseRenderContext { public fun (Lcom/squareup/workflow1/StatelessWorkflow;Lcom/squareup/workflow1/BaseRenderContext;)V - public fun ChildRendering (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)V + public fun ChildRendering (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function10;)Lkotlin/jvm/functions/Function9; public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function11;)Lkotlin/jvm/functions/Function10; public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;)Lkotlin/jvm/functions/Function0; @@ -211,6 +211,8 @@ public final class com/squareup/workflow1/Worker$DefaultImpls { public final class com/squareup/workflow1/WorkerWorkflow : com/squareup/workflow1/StatefulWorkflow, com/squareup/workflow1/ImpostorWorkflow { public fun (Lkotlin/reflect/KType;Ljava/lang/String;)V + public fun Rendering (Lcom/squareup/workflow1/Worker;ILcom/squareup/workflow1/StatefulWorkflow$RenderContext;Landroidx/compose/runtime/Composer;I)V + public synthetic fun Rendering (Ljava/lang/Object;Ljava/lang/Object;Lcom/squareup/workflow1/StatefulWorkflow$RenderContext;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; public fun describeRealIdentifier ()Ljava/lang/String; public fun getRealIdentifier ()Lcom/squareup/workflow1/WorkflowIdentifier; public final fun getWorkerType ()Lkotlin/reflect/KType; @@ -316,9 +318,9 @@ public final class com/squareup/workflow1/WorkflowOutput : com/squareup/workflow } public final class com/squareup/workflow1/Workflows { - public static final fun ChildRendering (Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V - public static final fun ChildRendering (Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/Workflow;Ljava/lang/String;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V - public static final fun ChildRendering (Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/Workflow;Ljava/lang/String;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun ChildRendering (Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Landroidx/compose/runtime/Composer;II)Ljava/lang/Object; + public static final fun ChildRendering (Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/Workflow;Ljava/lang/String;Landroidx/compose/runtime/Composer;II)Ljava/lang/Object; + public static final fun ChildRendering (Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/Workflow;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)Ljava/lang/Object; public static final fun RenderContext (Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/StatefulWorkflow;)Lcom/squareup/workflow1/StatefulWorkflow$RenderContext; public static final fun RenderContext (Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/StatelessWorkflow;)Lcom/squareup/workflow1/StatelessWorkflow$RenderContext; public static final fun action (Lcom/squareup/workflow1/StatefulWorkflow;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/WorkflowAction; @@ -352,12 +354,19 @@ public final class com/squareup/workflow1/Workflows { public static synthetic fun runningWorker$default (Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/Worker;Lkotlin/reflect/KType;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V public static final fun sendAndAwaitApplication (Lcom/squareup/workflow1/Sink;Lcom/squareup/workflow1/WorkflowAction;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun stateful (Lcom/squareup/workflow1/Workflow$Companion;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Lcom/squareup/workflow1/StatefulWorkflow; + public static final fun stateful (Lcom/squareup/workflow1/Workflow$Companion;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function4;)Lcom/squareup/workflow1/StatefulWorkflow; public static final fun stateful (Lcom/squareup/workflow1/Workflow$Companion;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/StatefulWorkflow; + public static final fun stateful (Lcom/squareup/workflow1/Workflow$Companion;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/StatefulWorkflow; public static final fun stateful (Lcom/squareup/workflow1/Workflow$Companion;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;)Lcom/squareup/workflow1/StatefulWorkflow; + public static final fun stateful (Lcom/squareup/workflow1/Workflow$Companion;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function5;Lkotlin/jvm/functions/Function3;)Lcom/squareup/workflow1/StatefulWorkflow; public static final fun stateful (Lcom/squareup/workflow1/Workflow$Companion;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;)Lcom/squareup/workflow1/StatefulWorkflow; + public static final fun stateful (Lcom/squareup/workflow1/Workflow$Companion;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function5;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;)Lcom/squareup/workflow1/StatefulWorkflow; public static synthetic fun stateful$default (Lcom/squareup/workflow1/Workflow$Companion;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lcom/squareup/workflow1/StatefulWorkflow; + public static synthetic fun stateful$default (Lcom/squareup/workflow1/Workflow$Companion;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function5;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lcom/squareup/workflow1/StatefulWorkflow; public static synthetic fun stateful$default (Lcom/squareup/workflow1/Workflow$Companion;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lcom/squareup/workflow1/StatefulWorkflow; + public static synthetic fun stateful$default (Lcom/squareup/workflow1/Workflow$Companion;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function5;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lcom/squareup/workflow1/StatefulWorkflow; public static final fun stateless (Lcom/squareup/workflow1/Workflow$Companion;Lkotlin/jvm/functions/Function2;)Lcom/squareup/workflow1/Workflow; + public static final fun stateless (Lcom/squareup/workflow1/Workflow$Companion;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function2;)Lcom/squareup/workflow1/Workflow; public static final fun transform (Lcom/squareup/workflow1/Worker;Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/Worker; public static final fun unsnapshottableIdentifier (Lkotlin/reflect/KType;)Lcom/squareup/workflow1/WorkflowIdentifier; } diff --git a/workflow-core/dependencies/jvmRuntimeClasspath.txt b/workflow-core/dependencies/jvmRuntimeClasspath.txt index 81d8321d14..f2b5104fb7 100644 --- a/workflow-core/dependencies/jvmRuntimeClasspath.txt +++ b/workflow-core/dependencies/jvmRuntimeClasspath.txt @@ -1,9 +1,13 @@ +app.cash.molecule:molecule-runtime-jvm:0.3.0-SNAPSHOT +app.cash.molecule:molecule-runtime:0.3.0-SNAPSHOT com.squareup.okio:okio-jvm:3.0.0 com.squareup.okio:okio:3.0.0 +org.jetbrains.compose.runtime:runtime-desktop:1.1.0 +org.jetbrains.compose.runtime:runtime:1.1.0 org.jetbrains.kotlin:kotlin-bom:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.10 org.jetbrains.kotlin:kotlin-stdlib:1.6.10 -org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.1 -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1 +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.2 +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2 org.jetbrains:annotations:13.0 diff --git a/workflow-core/dependencies/runtimeClasspath.txt b/workflow-core/dependencies/runtimeClasspath.txt index 19adaaffcb..d8576d6ee2 100644 --- a/workflow-core/dependencies/runtimeClasspath.txt +++ b/workflow-core/dependencies/runtimeClasspath.txt @@ -1,10 +1,14 @@ +app.cash.molecule:molecule-runtime-jvm:0.3.0-SNAPSHOT +app.cash.molecule:molecule-runtime:0.3.0-SNAPSHOT com.squareup.okio:okio-jvm:3.0.0 com.squareup.okio:okio:3.0.0 +org.jetbrains.compose.runtime:runtime-desktop:1.1.0 +org.jetbrains.compose.runtime:runtime:1.1.0 org.jetbrains.kotlin:kotlin-bom:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-common:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.10 org.jetbrains.kotlin:kotlin-stdlib:1.6.10 -org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.1 -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1 +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.2 +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2 org.jetbrains:annotations:13.0 diff --git a/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/StatefulWorkflow.kt b/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/StatefulWorkflow.kt index 6db896bc4a..124bc4d11a 100644 --- a/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/StatefulWorkflow.kt +++ b/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/StatefulWorkflow.kt @@ -147,11 +147,13 @@ public abstract class StatefulWorkflow< ): RenderingT @Composable - public abstract fun Rendering( + public open fun Rendering( renderProps: PropsT, renderState: StateT, context: RenderContext, - ): RenderingT + ): RenderingT { + return render(renderProps, renderState, context) + } /** * Satisfies the [Workflow] interface by returning `this`. @@ -180,7 +182,30 @@ public inline fun Workflow.Companion.state props: PropsT, state: StateT ) -> RenderingT, - noinline Render: @Composable BaseRenderContext.( + crossinline snapshot: (StateT) -> Snapshot?, + crossinline onPropsChanged: ( + old: PropsT, + new: PropsT, + state: StateT + ) -> StateT = { _, _, state -> state } +): StatefulWorkflow = + stateful( + initialState = initialState, + render = render, + Rendering = { props, state -> + render(props, state) + }, + snapshot = snapshot, + onPropsChanged = onPropsChanged + ) + +public inline fun Workflow.Companion.stateful( + crossinline initialState: (PropsT, Snapshot?) -> StateT, + crossinline render: BaseRenderContext.( + props: PropsT, + state: StateT + ) -> RenderingT, + noinline Rendering: @Composable BaseRenderContext.( props: PropsT, state: StateT ) -> RenderingT = { props, state -> @@ -218,26 +243,48 @@ public inline fun Workflow.Companion.state renderProps: PropsT, renderState: StateT, context: RenderContext, - ): RenderingT = Render(context, renderProps, renderState) + ): RenderingT = Rendering(context, renderProps, renderState) } /** * Returns a stateful [Workflow], with no props, implemented via the given functions. */ -// public inline fun Workflow.Companion.stateful( -// crossinline initialState: (Snapshot?) -> StateT, -// crossinline render: BaseRenderContext.(state: StateT) -> RenderingT, -// noinline Render: @Composable BaseRenderContext.( -// state: StateT -// ) -> RenderingT = { state -> -// render(state) -// }, -// crossinline snapshot: (StateT) -> Snapshot? -// ): StatefulWorkflow = stateful( -// { _, initialSnapshot: Snapshot -> initialState(initialSnapshot) }, -// { _, state: StateT -> render(state) }, -// snapshot -// ) +public inline fun Workflow.Companion.stateful( + crossinline initialState: (Snapshot?) -> StateT, + crossinline render: BaseRenderContext.(state: StateT) -> RenderingT, + crossinline snapshot: (StateT) -> Snapshot? +): StatefulWorkflow = + stateful( + initialState = { _, initialSnapshot: Snapshot? -> initialState(initialSnapshot) }, + render = { _, state -> render(state) }, + snapshot = snapshot + ) + +/** + * Version of the above supporting [Rendering] for Composed Workflows. + */ +public fun Workflow.Companion.stateful( + initialState: (Snapshot?) -> StateT, + render: BaseRenderContext.(state: StateT) -> RenderingT, + Rendering: @Composable BaseRenderContext.( + state: StateT + ) -> RenderingT, + snapshot: (StateT) -> Snapshot? +): StatefulWorkflow { + @Suppress("LocalVariableName") + val RenderingWithProps: @Composable BaseRenderContext.( + props: Unit, + state: StateT + ) -> RenderingT = @Composable { _: Unit, state: StateT -> + Rendering(state) + } + return stateful( + initialState = { _: Unit, initialSnapshot: Snapshot? -> initialState(initialSnapshot) }, + render = { _: Unit, state: StateT -> render(state) }, + Rendering = RenderingWithProps, + snapshot = snapshot + ) +} /** * Returns a stateful [Workflow] implemented via the given functions. @@ -250,23 +297,42 @@ public inline fun Workflow.Companion.state props: PropsT, state: StateT ) -> RenderingT, - noinline Render: @Composable BaseRenderContext.( + crossinline onPropsChanged: ( + old: PropsT, + new: PropsT, + state: StateT + ) -> StateT = { _, _, state -> state } +): StatefulWorkflow = stateful( + initialState = { props, _ -> initialState(props) }, + render = render, + snapshot = { null }, + onPropsChanged = onPropsChanged +) + +/** + * Version of the above supporting [Rendering] for Composed Workflows. + */ +public inline fun Workflow.Companion.stateful( + crossinline initialState: (PropsT) -> StateT, + crossinline render: BaseRenderContext.( props: PropsT, state: StateT - ) -> RenderingT = { props, state -> - render(props, state) - }, + ) -> RenderingT, + noinline Rendering: @Composable BaseRenderContext.( + props: PropsT, + state: StateT + ) -> RenderingT, crossinline onPropsChanged: ( old: PropsT, new: PropsT, state: StateT ) -> StateT = { _, _, state -> state } ): StatefulWorkflow = stateful( - { props: PropsT, _ -> initialState(props) }, - render, - Render, - { null }, - onPropsChanged + initialState = { props: PropsT, _ -> initialState(props) }, + render = render, + Rendering = Rendering, + snapshot = { null }, + onPropsChanged = onPropsChanged ) /** @@ -277,16 +343,35 @@ public inline fun Workflow.Companion.state public inline fun Workflow.Companion.stateful( initialState: StateT, crossinline render: BaseRenderContext.(state: StateT) -> RenderingT, - noinline Render: @Composable BaseRenderContext.( - state: StateT - ) -> RenderingT = { state -> - render(state) - }, ): StatefulWorkflow = stateful( - { initialState }, - { _, state -> render(state) } + initialState = { initialState }, + render = { _, state -> render(state) } ) +/** + * Version of the above supporting [Rendering] for Compose Workflows + */ +public fun Workflow.Companion.stateful( + initialState: StateT, + render: BaseRenderContext.(state: StateT) -> RenderingT, + Rendering: @Composable BaseRenderContext.( + state: StateT + ) -> RenderingT, +): StatefulWorkflow { + @Suppress("LocalVariableName") + val RenderWithProps: @Composable BaseRenderContext.( + props: Unit, + state: StateT + ) -> RenderingT = @Composable { _: Unit, state: StateT -> + Rendering(state) + } + return stateful( + initialState = { initialState }, + render = { _, state -> render(state) }, + Rendering = RenderWithProps, + ) +} + /** * Convenience to create a [WorkflowAction] with parameter types matching those * of the receiving [StatefulWorkflow]. The action will invoke the given [lambda][update] @@ -296,7 +381,7 @@ public inline fun Workflow.Companion.stateful( * @param update Function that defines the workflow update. */ public fun - StatefulWorkflow.action( +StatefulWorkflow.action( name: String = "", update: WorkflowAction.Updater.() -> Unit ): WorkflowAction = action({ name }, update) @@ -311,7 +396,7 @@ public fun * @param update Function that defines the workflow update. */ public fun - StatefulWorkflow.action( +StatefulWorkflow.action( name: () -> String, update: WorkflowAction.Updater.() -> Unit ): WorkflowAction = object : WorkflowAction() { diff --git a/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/StatelessWorkflow.kt b/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/StatelessWorkflow.kt index cec122b45a..8748b90ae5 100644 --- a/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/StatelessWorkflow.kt +++ b/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/StatelessWorkflow.kt @@ -4,6 +4,7 @@ package com.squareup.workflow1 import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName @@ -36,15 +37,19 @@ public abstract class StatelessWorkflow @Suppress("UNCHECKED_CAST") private val statefulWorkflow: StatefulWorkflow get() { - val Render: @Composable BaseRenderContext. - (props: PropsT, _: Unit) -> RenderingT = + @Suppress("LocalVariableName") + val Rendering: @Composable BaseRenderContext + .(props: PropsT, _: Unit) -> RenderingT = @Composable { props, _ -> - (this@StatelessWorkflow).Rendering(props, RenderContext(this, this@StatelessWorkflow)) + val context = remember(this, this@StatelessWorkflow) { + RenderContext(this, this@StatelessWorkflow) + } + (this@StatelessWorkflow).Rendering(props, context) } return Workflow.stateful( initialState = { Unit }, render = { props, _ -> render(props, RenderContext(this, this@StatelessWorkflow)) }, - Render = Render + Rendering = Rendering ) } @@ -103,7 +108,19 @@ public fun RenderContext( * their own internal state. */ public inline fun Workflow.Companion.stateless( - crossinline render: BaseRenderContext.(props: PropsT) -> RenderingT + crossinline render: BaseRenderContext.(props: PropsT) -> RenderingT, +): Workflow = stateless( + Rendering = { props: PropsT -> + render(props) + }, + render = render +) + +public inline fun Workflow.Companion.stateless( + noinline Rendering: @Composable BaseRenderContext.( + props: PropsT + ) -> RenderingT, + crossinline render: BaseRenderContext.(props: PropsT) -> RenderingT, ): Workflow = object : StatelessWorkflow() { override fun render( @@ -115,9 +132,7 @@ public inline fun Workflow.Companion.stateless( override fun Rendering( renderProps: PropsT, context: RenderContext - ): RenderingT { - TODO("Not yet implemented") - } + ): RenderingT = Rendering(context, renderProps) } /** @@ -126,7 +141,7 @@ public inline fun Workflow.Companion.stateless( */ public fun Workflow.Companion.rendering( rendering: RenderingT -): Workflow = stateless { rendering } +): Workflow = stateless(render = { rendering }) /** * Convenience to create a [WorkflowAction] with parameter types matching those @@ -137,7 +152,7 @@ public fun Workflow.Companion.rendering( * @param update Function that defines the workflow update. */ public fun - StatelessWorkflow.action( +StatelessWorkflow.action( name: String = "", update: WorkflowAction.Updater.() -> Unit ): WorkflowAction = action({ name }, update) @@ -152,7 +167,7 @@ public fun * @param update Function that defines the workflow update. */ public fun - StatelessWorkflow.action( +StatelessWorkflow.action( name: () -> String, update: WorkflowAction.Updater.() -> Unit ): WorkflowAction = object : WorkflowAction() { 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 41b697bd26..a1d9f7b5f1 100644 --- a/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/WorkerWorkflow.kt +++ b/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/WorkerWorkflow.kt @@ -68,8 +68,8 @@ internal class WorkerWorkflow( renderProps: Worker, renderState: Int, context: RenderContext, - ): Unit { - TODO("Not yet implemented") + ) { + render(renderProps, renderState, context) } } diff --git a/workflow-core/src/commonTest/kotlin/com/squareup/workflow1/ComposeInheritanceTest.kt b/workflow-core/src/commonTest/kotlin/com/squareup/workflow1/ComposeInheritanceTest.kt deleted file mode 100644 index 5b21d5dfcb..0000000000 --- a/workflow-core/src/commonTest/kotlin/com/squareup/workflow1/ComposeInheritanceTest.kt +++ /dev/null @@ -1,68 +0,0 @@ -package com.squareup.workflow1 - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.BroadcastFrameClock -import app.cash.molecule.launchMolecule -import kotlinx.coroutines.CoroutineScope -import org.junit.Test - -class ComposeInheritanceTest { - abstract class Abstract { - @Composable - public abstract fun AComposable( - input1: I1, - input2: I2, - hoistState: @Composable (s: T) -> Unit - ): Unit - - abstract fun someOtherFunction(): T - } - - - public class Concrete( - private val payload: String - ) : Abstract() { - @Composable - public override fun AComposable( - input1: Unit, - input2: String, - hoistState: @Composable (s: String) -> Unit - ) { - println("Can you hear me now? $payload") - hoistState(payload + input2) - } - - override fun someOtherFunction(): String { - return payload - } - } - - @Composable - public fun Emitter( - i1: I1, - i2: I2, - theObjectHoldingComposables: Abstract - ): T? { - val payload: MutableState = remember { mutableStateOf(null) } - theObjectHoldingComposables.AComposable(i1, i2) @Composable { - payload.value = it - } - return payload.value - } - - @Test fun testInheritance() { - val objectUnderTest = Concrete("a test") - val broadcastFrameClock = BroadcastFrameClock {} - val testScope = CoroutineScope(broadcastFrameClock) - - val testFlow = testScope.launchMolecule { - Emitter(Unit, " again", objectUnderTest) - } - - assert(testFlow.value.contentEquals("a test again")) - } -} - diff --git a/workflow-runtime/api/workflow-runtime.api b/workflow-runtime/api/workflow-runtime.api index be4b92253b..40342760dc 100644 --- a/workflow-runtime/api/workflow-runtime.api +++ b/workflow-runtime/api/workflow-runtime.api @@ -10,7 +10,7 @@ public final class com/squareup/workflow1/Latch { public final class com/squareup/workflow1/NoopWorkflowInterceptor : com/squareup/workflow1/WorkflowInterceptor { public static final field $stable I public static final field INSTANCE Lcom/squareup/workflow1/NoopWorkflowInterceptor; - public fun Rendering (Ljava/lang/Object;Ljava/lang/Object;Lkotlin/jvm/functions/Function3;Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lkotlin/jvm/functions/Function6;Landroidx/compose/runtime/Composer;I)V + public fun Rendering (Ljava/lang/Object;Ljava/lang/Object;Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lkotlin/jvm/functions/Function5;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; 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; @@ -63,16 +63,10 @@ public final class com/squareup/workflow1/RuntimeConfig$RenderPerAction : com/sq public fun getUseComposeInRuntime ()Z } -public final class com/squareup/workflow1/SignalHolder { - public fun ()V - public final fun getSignal$wf1_workflow_runtime ()Lkotlin/jvm/functions/Function0; - public final fun setSignal$wf1_workflow_runtime (Lkotlin/jvm/functions/Function0;)V -} - public class com/squareup/workflow1/SimpleLoggingWorkflowInterceptor : com/squareup/workflow1/WorkflowInterceptor { public static final field $stable I public fun ()V - public fun Rendering (Ljava/lang/Object;Ljava/lang/Object;Lkotlin/jvm/functions/Function3;Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lkotlin/jvm/functions/Function6;Landroidx/compose/runtime/Composer;I)V + public fun Rendering (Ljava/lang/Object;Ljava/lang/Object;Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lkotlin/jvm/functions/Function5;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; protected fun log (Ljava/lang/String;)V protected fun logAfterMethod (Ljava/lang/String;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;[Lkotlin/Pair;)V protected fun logBeforeMethod (Ljava/lang/String;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;[Lkotlin/Pair;)V @@ -104,7 +98,7 @@ public abstract interface annotation class com/squareup/workflow1/WorkflowExperi } public abstract interface class com/squareup/workflow1/WorkflowInterceptor { - public abstract fun Rendering (Ljava/lang/Object;Ljava/lang/Object;Lkotlin/jvm/functions/Function3;Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lkotlin/jvm/functions/Function6;Landroidx/compose/runtime/Composer;I)V + public abstract fun Rendering (Ljava/lang/Object;Ljava/lang/Object;Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lkotlin/jvm/functions/Function5;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; 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; @@ -113,7 +107,7 @@ public abstract interface class com/squareup/workflow1/WorkflowInterceptor { } public final class com/squareup/workflow1/WorkflowInterceptor$DefaultImpls { - public static fun Rendering (Lcom/squareup/workflow1/WorkflowInterceptor;Ljava/lang/Object;Ljava/lang/Object;Lkotlin/jvm/functions/Function3;Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lkotlin/jvm/functions/Function6;Landroidx/compose/runtime/Composer;I)V + public static fun Rendering (Lcom/squareup/workflow1/WorkflowInterceptor;Ljava/lang/Object;Ljava/lang/Object;Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lkotlin/jvm/functions/Function5;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; 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; @@ -122,14 +116,14 @@ public final class com/squareup/workflow1/WorkflowInterceptor$DefaultImpls { } public abstract interface class com/squareup/workflow1/WorkflowInterceptor$RenderContextInterceptor { - public abstract fun ChildRendering (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function7;Landroidx/compose/runtime/Composer;I)V + public abstract fun ChildRendering (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function6;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; public abstract fun onActionSent (Lcom/squareup/workflow1/WorkflowAction;Lkotlin/jvm/functions/Function1;)V public abstract fun onRenderChild (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function4;)Ljava/lang/Object; public abstract fun onRunningSideEffect (Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)V } public final class com/squareup/workflow1/WorkflowInterceptor$RenderContextInterceptor$DefaultImpls { - public static fun ChildRendering (Lcom/squareup/workflow1/WorkflowInterceptor$RenderContextInterceptor;Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function7;Landroidx/compose/runtime/Composer;I)V + public static fun ChildRendering (Lcom/squareup/workflow1/WorkflowInterceptor$RenderContextInterceptor;Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function6;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; public static fun onActionSent (Lcom/squareup/workflow1/WorkflowInterceptor$RenderContextInterceptor;Lcom/squareup/workflow1/WorkflowAction;Lkotlin/jvm/functions/Function1;)V public static fun onRenderChild (Lcom/squareup/workflow1/WorkflowInterceptor$RenderContextInterceptor;Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function4;)Ljava/lang/Object; public static fun onRunningSideEffect (Lcom/squareup/workflow1/WorkflowInterceptor$RenderContextInterceptor;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)V @@ -159,14 +153,16 @@ public final class com/squareup/workflow1/WorkflowRuntimeClock : androidx/compos public final class com/squareup/workflow1/internal/ActiveStagingList { public fun ()V public final fun commitStaging (Lkotlin/jvm/functions/Function1;)V + public final fun firstActiveOrNull (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/internal/InlineLinkedList$InlineListNode; public final fun forEachActive (Lkotlin/jvm/functions/Function1;)V public final fun forEachStaging (Lkotlin/jvm/functions/Function1;)V + public final fun removeAndStage (Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/internal/InlineLinkedList$InlineListNode;)V public final fun retainOrCreate (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;)Lcom/squareup/workflow1/internal/InlineLinkedList$InlineListNode; } public final class com/squareup/workflow1/internal/ChainedWorkflowInterceptor : com/squareup/workflow1/WorkflowInterceptor { public fun (Ljava/util/List;)V - public fun Rendering (Ljava/lang/Object;Ljava/lang/Object;Lkotlin/jvm/functions/Function3;Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lkotlin/jvm/functions/Function6;Landroidx/compose/runtime/Composer;I)V + public fun Rendering (Ljava/lang/Object;Ljava/lang/Object;Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lkotlin/jvm/functions/Function5;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; 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; @@ -190,6 +186,7 @@ public final class com/squareup/workflow1/internal/IdCounterKt { public final class com/squareup/workflow1/internal/InlineLinkedList { public fun ()V public final fun clear ()V + public final fun firstOrNull (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/internal/InlineLinkedList$InlineListNode; public final fun forEach (Lkotlin/jvm/functions/Function1;)V public final fun getHead ()Lcom/squareup/workflow1/internal/InlineLinkedList$InlineListNode; public final fun getTail ()Lcom/squareup/workflow1/internal/InlineLinkedList$InlineListNode; @@ -206,7 +203,7 @@ public abstract interface class com/squareup/workflow1/internal/InlineLinkedList public final class com/squareup/workflow1/internal/RealRenderContext : com/squareup/workflow1/BaseRenderContext, com/squareup/workflow1/Sink { public fun (Lcom/squareup/workflow1/internal/RealRenderContext$Renderer;Lcom/squareup/workflow1/internal/RealRenderContext$SideEffectRunner;Lkotlinx/coroutines/channels/SendChannel;)V - public fun ChildRendering (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)V + public fun ChildRendering (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function10;)Lkotlin/jvm/functions/Function9; public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function11;)Lkotlin/jvm/functions/Function10; public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;)Lkotlin/jvm/functions/Function0; @@ -219,7 +216,6 @@ public final class com/squareup/workflow1/internal/RealRenderContext : com/squar public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function8;)Lkotlin/jvm/functions/Function7; public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function9;)Lkotlin/jvm/functions/Function8; public final fun freeze ()V - public final fun freezeIdempotently ()V public fun getActionSink ()Lcom/squareup/workflow1/Sink; public fun renderChild (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public fun runningSideEffect (Ljava/lang/String;Lkotlin/jvm/functions/Function2;)V @@ -228,7 +224,7 @@ public final class com/squareup/workflow1/internal/RealRenderContext : com/squar } public abstract interface class com/squareup/workflow1/internal/RealRenderContext$Renderer { - public abstract fun Rendering (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)V + public abstract fun Rendering (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; public abstract fun render (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; } @@ -249,7 +245,7 @@ public final class com/squareup/workflow1/internal/SideEffectNode : com/squareup public final class com/squareup/workflow1/internal/SubtreeManager : com/squareup/workflow1/internal/RealRenderContext$Renderer { public fun (Ljava/util/Map;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lcom/squareup/workflow1/WorkflowInterceptor;Lcom/squareup/workflow1/internal/IdCounter;)V public synthetic fun (Ljava/util/Map;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lcom/squareup/workflow1/WorkflowInterceptor;Lcom/squareup/workflow1/internal/IdCounter;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun Rendering (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)V + public fun Rendering (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; public final fun commitRenderedChildren ()V public final fun createChildSnapshots ()Ljava/util/Map; public fun render (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; @@ -267,7 +263,7 @@ public final class com/squareup/workflow1/internal/ThrowablesKt { public final class com/squareup/workflow1/internal/WorkflowChildNode : com/squareup/workflow1/internal/InlineLinkedList$InlineListNode { public fun (Lcom/squareup/workflow1/Workflow;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/internal/WorkflowNode;)V - public final fun Rendering (Lcom/squareup/workflow1/StatefulWorkflow;Ljava/lang/Object;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;I)V + public final fun Rendering (Lcom/squareup/workflow1/StatefulWorkflow;Ljava/lang/Object;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; public final fun acceptChildOutput (Ljava/lang/Object;)Lcom/squareup/workflow1/WorkflowAction; public final fun getId ()Lcom/squareup/workflow1/internal/WorkflowNodeId; public synthetic fun getNextListNode ()Lcom/squareup/workflow1/internal/InlineLinkedList$InlineListNode; @@ -284,7 +280,7 @@ public final class com/squareup/workflow1/internal/WorkflowChildNode : com/squar public final class com/squareup/workflow1/internal/WorkflowNode : com/squareup/workflow1/WorkflowInterceptor$WorkflowSession, com/squareup/workflow1/internal/RealRenderContext$SideEffectRunner, kotlinx/coroutines/CoroutineScope { public fun (Lcom/squareup/workflow1/internal/WorkflowNodeId;Lcom/squareup/workflow1/StatefulWorkflow;Ljava/lang/Object;Lcom/squareup/workflow1/TreeSnapshot;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lcom/squareup/workflow1/WorkflowInterceptor;Lcom/squareup/workflow1/internal/IdCounter;)V public synthetic fun (Lcom/squareup/workflow1/internal/WorkflowNodeId;Lcom/squareup/workflow1/StatefulWorkflow;Ljava/lang/Object;Lcom/squareup/workflow1/TreeSnapshot;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lcom/squareup/workflow1/WorkflowInterceptor;Lcom/squareup/workflow1/internal/IdCounter;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun Rendering (Lcom/squareup/workflow1/StatefulWorkflow;Ljava/lang/Object;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;I)V + public final fun Rendering (Lcom/squareup/workflow1/StatefulWorkflow;Ljava/lang/Object;Landroidx/compose/runtime/MutableState;Landroidx/compose/runtime/Composer;I)V public final fun cancel (Ljava/util/concurrent/CancellationException;)V public static synthetic fun cancel$default (Lcom/squareup/workflow1/internal/WorkflowNode;Ljava/util/concurrent/CancellationException;ILjava/lang/Object;)V public fun getCoroutineContext ()Lkotlin/coroutines/CoroutineContext; diff --git a/workflow-runtime/dependencies/jvmRuntimeClasspath.txt b/workflow-runtime/dependencies/jvmRuntimeClasspath.txt index 645cfead0c..ae2a254c1f 100644 --- a/workflow-runtime/dependencies/jvmRuntimeClasspath.txt +++ b/workflow-runtime/dependencies/jvmRuntimeClasspath.txt @@ -1,10 +1,14 @@ :workflow-core +app.cash.molecule:molecule-runtime-jvm:0.3.0-SNAPSHOT +app.cash.molecule:molecule-runtime:0.3.0-SNAPSHOT com.squareup.okio:okio-jvm:3.0.0 com.squareup.okio:okio:3.0.0 +org.jetbrains.compose.runtime:runtime-desktop:1.1.0 +org.jetbrains.compose.runtime:runtime:1.1.0 org.jetbrains.kotlin:kotlin-bom:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.10 org.jetbrains.kotlin:kotlin-stdlib:1.6.10 -org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.1 -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1 +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.2 +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2 org.jetbrains:annotations:13.0 diff --git a/workflow-runtime/dependencies/jvmWorkflowNodeRuntimeClasspath.txt b/workflow-runtime/dependencies/jvmWorkflowNodeRuntimeClasspath.txt index 34e2961192..4c79eb0088 100644 --- a/workflow-runtime/dependencies/jvmWorkflowNodeRuntimeClasspath.txt +++ b/workflow-runtime/dependencies/jvmWorkflowNodeRuntimeClasspath.txt @@ -1,9 +1,15 @@ +app.cash.molecule:molecule-runtime-jvm:0.3.0-SNAPSHOT +app.cash.molecule:molecule-runtime:0.3.0-SNAPSHOT net.sf.jopt-simple:jopt-simple:5.0.4 org.apache.commons:commons-math3:3.2 +org.jetbrains.compose.runtime:runtime-desktop:1.1.0 +org.jetbrains.compose.runtime:runtime:1.1.0 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.10 org.jetbrains.kotlin:kotlin-stdlib:1.6.10 org.jetbrains.kotlinx:kotlinx-benchmark-runtime-jvm:0.4.2 org.jetbrains.kotlinx:kotlinx-benchmark-runtime:0.4.2 +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.2 +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2 org.jetbrains:annotations:13.0 org.openjdk.jmh:jmh-core:1.34 diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/RenderWorkflow.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/RenderWorkflow.kt index c8c1f00730..d3913ae038 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/RenderWorkflow.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/RenderWorkflow.kt @@ -4,7 +4,6 @@ import androidx.compose.runtime.BroadcastFrameClock import app.cash.molecule.launchMolecule import com.squareup.workflow1.internal.WorkflowRunner import com.squareup.workflow1.internal.chained -import com.squareup.workflow1.internal.nanoTime import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -152,10 +151,6 @@ public fun renderWorkflowIn( } scope.launch { - // if (runtimeConfig.useComposeInRuntime) { - // //synchronous first render. - // renderSignal.emit(Unit) - // } while (isActive) { // It might look weird to start by consuming the output before getting the rendering below, // but remember the first render pass already occurred above, before this coroutine was even @@ -169,9 +164,19 @@ public fun renderWorkflowIn( // After receiving an output, the next render pass must be done before emitting that output, // so that the workflow states appear consistent to observers of the outputs and renderings. if (runtimeConfig.useComposeInRuntime) { + // TODO: Figure out how to handle the case where the state changes on the first action + // from the first rendering? - e.g. with a Worker.from { Unit } initializing state. + // if (!composeWaitingForFrame) { + // // We want to make sure Compose is waiting for a frame in case action processing finished + // // before Compose was ready to recompose. + // while (!composeWaitingForFrame) { + // delay(20) + // } + // yield() + // } if (composeWaitingForFrame) { composeWaitingForFrame = false - composeRuntimeClock.sendFrame(nanoTime()) + composeRuntimeClock.sendFrame(0L) yield() } } else { diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/SimpleLoggingWorkflowInterceptor.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/SimpleLoggingWorkflowInterceptor.kt index 4d5658d4cb..659229b2b8 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/SimpleLoggingWorkflowInterceptor.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/SimpleLoggingWorkflowInterceptor.kt @@ -5,6 +5,7 @@ import com.squareup.workflow1.WorkflowInterceptor.RenderContextInterceptor import com.squareup.workflow1.WorkflowInterceptor.WorkflowSession import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job +import java.lang.IllegalStateException /** * A [WorkflowInterceptor] that just prints all method calls using [log]. @@ -56,8 +57,8 @@ public open class SimpleLoggingWorkflowInterceptor : WorkflowInterceptor { context: BaseRenderContext, session: WorkflowSession, proceed: @Composable (P, S, RenderContextInterceptor?) -> R - ): R = logMethod("onRender", session) { - proceed(renderProps, renderState, SimpleLoggingContextInterceptor(session)) + ): R { + throw IllegalStateException("Do not use this interceptor with Compose runtime yet.") } override fun onSnapshotState( 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 03c62ecf99..3f8d571bb3 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/WorkflowInterceptor.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/WorkflowInterceptor.kt @@ -302,16 +302,17 @@ internal fun WorkflowInterceptor.intercept( ): R { // Cannot annotate anonymous functions with @Composable and cannot infer type of // this when a lambda. So need this variable to make it explicit. - val anonProceed: @Composable (P, S, RenderContextInterceptor?) -> R = + val reifiedProceed: @Composable (P, S, RenderContextInterceptor?) -> R = @Composable { props: P, state: S, interceptor: RenderContextInterceptor? -> val interceptedContext = interceptor?.let { InterceptedRenderContext(context, it) } ?: context + val renderContext = RenderContext(interceptedContext, this) workflow.Rendering( props, state, - RenderContext(interceptedContext, this) + renderContext ) } return Rendering( @@ -319,7 +320,7 @@ internal fun WorkflowInterceptor.intercept( renderState = renderState, context = context, session = workflowSession, - proceed = anonProceed + proceed = reifiedProceed ) } diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/ActiveStagingList.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/ActiveStagingList.kt index 2bc1b97ba6..97101d9886 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/ActiveStagingList.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/ActiveStagingList.kt @@ -48,6 +48,28 @@ internal class ActiveStagingList> { return staged } + /** + * Looks for the first item matching [predicate] in the active list and removes it from the active + * list. Then puts [child] into the staging list. + */ + inline fun removeAndStage( + predicate: (T) -> Boolean, + child: T? + ) { + active.removeFirst(predicate) + child?.let { + staging += it + } + } + + /** + * Returns a reference to the first item matching [predicate] in the active list, or null if + * not found. + */ + inline fun firstActiveOrNull( + predicate: (T) -> Boolean + ): T? = active.firstOrNull(predicate) + /** * Swaps the active and staging list and clears the old active list, passing items in the * old active list to [onRemove]. 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 a5df80873d..ff3ae288b1 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 @@ -1,6 +1,7 @@ package com.squareup.workflow1.internal import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import com.squareup.workflow1.BaseRenderContext import com.squareup.workflow1.NoopWorkflowInterceptor import com.squareup.workflow1.Snapshot @@ -90,23 +91,31 @@ internal class ChainedWorkflowInterceptor( session: WorkflowSession, proceed: @Composable (P, S, RenderContextInterceptor?) -> R ): R { - val chainedProceed = interceptors.foldRight(proceed) { workflowInterceptor, proceedAcc -> - { props, state, outerContextInterceptor -> - // Holding compiler's hand for function type. - val proceedInternal = - @Composable { p: P, - s: S, - innerContextInterceptor: RenderContextInterceptor? -> - val contextInterceptor = outerContextInterceptor.wrap(innerContextInterceptor) - proceedAcc(p, s, contextInterceptor) - } - workflowInterceptor.Rendering( - props, - state, - context, - proceed = proceedInternal, - session = session, - ) + val chainedProceed = remember(session) { + interceptors.foldRight(proceed) { workflowInterceptor, proceedAcc -> + { props, state, outerContextInterceptor -> + // Holding compiler's hand for function type. + val proceedInternal = + remember<@Composable (P, S, RenderContextInterceptor?) -> R>( + outerContextInterceptor + ) { + @Composable { p: P, + s: S, + innerContextInterceptor: RenderContextInterceptor? -> + val contextInterceptor = remember(innerContextInterceptor) { + outerContextInterceptor.wrap(innerContextInterceptor) + } + proceedAcc(p, s, contextInterceptor) + } + } + workflowInterceptor.Rendering( + props, + state, + context, + proceed = proceedInternal, + session = session, + ) + } } } return chainedProceed(renderProps, renderState, null) @@ -178,7 +187,7 @@ internal class ChainedWorkflowInterceptor( childProps, key, handler - ) @Composable { c, p, k, h, -> + ) @Composable { c, p, k, h -> inner.ChildRendering(c, p, k, h, proceed) } diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/InlineLinkedList.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/InlineLinkedList.kt index 500b4d377b..b156dcc44a 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/InlineLinkedList.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/InlineLinkedList.kt @@ -95,6 +95,20 @@ internal class InlineLinkedList> { } } + /** + * Returns the first item matching [predicate] in the list, or null. + */ + inline fun firstOrNull(predicate: (T) -> Boolean): T? { + var currentNode = head + while (currentNode != null) { + if (predicate(currentNode)) { + return currentNode + } + currentNode = currentNode.nextListNode + } + return null + } + /** * Removes all elements from the list. */ diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/RealRenderContext.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/RealRenderContext.kt index b5b5f3273d..872cf04912 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/RealRenderContext.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/RealRenderContext.kt @@ -3,7 +3,7 @@ package com.squareup.workflow1.internal import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.key import com.squareup.workflow1.BaseRenderContext import com.squareup.workflow1.Sink import com.squareup.workflow1.Workflow @@ -67,7 +67,7 @@ internal class RealRenderContext( key: String, handler: (ChildOutputT) -> WorkflowAction ): ChildRenderingT { - // checkNotFrozen() + checkNotFrozen() return renderer.render(child, props, key, handler) } @@ -85,7 +85,7 @@ internal class RealRenderContext( key: String, sideEffect: suspend CoroutineScope.() -> Unit ) { - // checkNotFrozen() + checkNotFrozen() sideEffectRunner.runningSideEffect(key, sideEffect) } @@ -93,7 +93,7 @@ internal class RealRenderContext( * Freezes this context so that any further calls to this context will throw. */ fun freeze() { - // checkNotFrozen() + checkNotFrozen() frozen = true } diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/SubtreeManager.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/SubtreeManager.kt index 8a9f1255cd..d7675b6180 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/SubtreeManager.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/SubtreeManager.kt @@ -1,6 +1,8 @@ package com.squareup.workflow1.internal import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import com.squareup.workflow1.ActionProcessingResult import com.squareup.workflow1.NoopWorkflowInterceptor @@ -132,15 +134,15 @@ internal class SubtreeManager( key: String, handler: (ChildOutputT) -> WorkflowAction ): ChildRenderingT { - val stagedChild = remember (child, props, key, handler) { - prepareStagedChild( + val stagedChild = + StagedChild( child, props, key, handler ) - } - return stagedChild.Rendering(child.asStatefulWorkflow(), props) + val statefulChild = remember(child) { child.asStatefulWorkflow() } + return stagedChild.Rendering(statefulChild, props) } private fun prepareStagedChild( @@ -165,6 +167,40 @@ internal class SubtreeManager( return stagedChild } + /** + * Prepare the staged child while only modifying [children] in a SideEffect. This will ensure + * that we do not inappropriately modify non-snapshot state. + */ + @Composable + private fun StagedChild( + child: Workflow, + props: ChildPropsT, + key: String, + handler: (ChildOutputT) -> WorkflowAction + ): WorkflowChildNode<*, *, *, *, *> { + val childState = remember(child, key, props, handler) { + children.forEachStaging { + require(!(it.matches(child, key))) { + "Expected keys to be unique for ${child.identifier}: key=\"$key\"" + } + } + mutableStateOf( + children.firstActiveOrNull { + it.matches(child, key) + } ?: createChildNode(child, props, key, handler) + ) + } + + SideEffect { + // Modify the [children] lists in a side-effect when composition is committed. + children.removeAndStage( + predicate = { it.matches(child, key) }, + child = childState.value + ) + } + return childState.value + } + /** * Uses [selector] to invoke [WorkflowNode.tick] for every running child workflow this instance * is managing. diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowChildNode.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowChildNode.kt index 4d54dd7581..9cef8334c3 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowChildNode.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowChildNode.kt @@ -66,16 +66,14 @@ internal class WorkflowChildNode< workflow: StatefulWorkflow<*, *, *, *>, props: Any? ): R { - val renderingState = remember { mutableStateOf(null) } + val rendering = remember { mutableStateOf(null) } @Suppress("UNCHECKED_CAST") - workflowNode.Rendering( + (workflowNode as WorkflowNode).Rendering( workflow as StatefulWorkflow, props as ChildPropsT, - setRendering = { - renderingState.value = it as R - } + rendering, ) - return renderingState.value!! + return rendering.value!! } /** 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 2d19de78a9..5a3c220b47 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 @@ -83,8 +83,10 @@ internal class WorkflowNode( init { interceptor.onSessionStarted(this, this) - state = mutableStateOf(interceptor.intercept(workflow = workflow, workflowSession = this) - .initialState(initialProps, snapshot?.workflowSnapshot)) + state = mutableStateOf( + interceptor.intercept(workflow = workflow, workflowSession = this) + .initialState(initialProps, snapshot?.workflowSnapshot) + ) } override fun toString(): String { @@ -109,23 +111,26 @@ internal class WorkflowNode( ): RenderingT = renderWithStateType(workflow as StatefulWorkflow, input) - /** * This returns Unit so that the Recomposer will consider this a separate Recompose scope that * can be independently recomposed. + * + * We pass in the MutableState directly rather than setRendering() to save Compose + * having to memoize the lambda for such a frequenct call. */ @Suppress("UNCHECKED_CAST") @Composable fun Rendering( workflow: StatefulWorkflow, input: PropsT, - setRendering: (RenderingT) -> Unit - ): Unit = + rendering: MutableState + ) { RenderingWithStateType( workflow as StatefulWorkflow, input, - setRendering + rendering ) + } /** * Walk the tree of state machines again, this time gathering snapshots and aggregating them @@ -223,44 +228,34 @@ internal class WorkflowNode( private fun RenderingWithStateType( workflow: StatefulWorkflow, props: PropsT, - setRendering: (RenderingT) -> Unit - ): Unit { - key(props) { - UpdatePropsAndState(workflow, props) - } - - val realRenderContext = remember(subtreeManager, eventActionsChannel) { - RealRenderContext( + rendering: MutableState + ) { + UpdatePropsAndState(workflow, props) + + val (baseRenderContext, renderContext) = remember( + state.value, + props, + workflow, + rendering.value + ) { + // Use the RenderContext once. After rendering successfully it is frozen until new state. + val base = RealRenderContext( renderer = subtreeManager, sideEffectRunner = this, eventActionsChannel = eventActionsChannel ) + base to RenderContext(workflow = workflow, baseContext = base) } - val context = remember(realRenderContext, workflow) { - RenderContext( - realRenderContext, - workflow - ) - } - setRendering(interceptor.intercept(workflow, this) - .Rendering(props, state.value, context)) + + rendering.value = interceptor.intercept(workflow, this) + .Rendering(props, state.value, renderContext) SideEffect { - realRenderContext.freeze() + baseRenderContext.freeze() commitAndUpdateScopes() } } - - @Composable - private fun renderWorkflowInSeparateRecomposeScope( - workflow: StatefulWorkflow, - props: PropsT, - setRendering: (RenderingT) -> Unit - ): Unit { - - } - private fun commitAndUpdateScopes() { // Tear down workflows and workers that are obsolete. subtreeManager.commitRenderedChildren() @@ -287,10 +282,11 @@ internal class WorkflowNode( workflow: StatefulWorkflow, newProps: PropsT ) { - if (newProps != lastProps) { - val newState = interceptor.intercept(workflow, this) - .onPropsChanged(lastProps, newProps, state.value) - state.value = newState + key(newProps) { + if (newProps != lastProps) { + state.value = interceptor.intercept(workflow, this@WorkflowNode) + .onPropsChanged(lastProps, newProps, state.value) + } } SideEffect { lastProps = newProps 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 f101803238..609e6788b2 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 @@ -1,9 +1,10 @@ package com.squareup.workflow1.internal import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import com.squareup.workflow1.ActionProcessingResult import com.squareup.workflow1.PropsUpdated import com.squareup.workflow1.RenderingAndSnapshot @@ -36,7 +37,7 @@ internal class WorkflowRunner( ) { private val workflow = protoWorkflow.asStatefulWorkflow() private val idCounter = IdCounter() - private var currentProps: MutableState = mutableStateOf(props.value) + private var currentProps: PropsT by mutableStateOf(props.value) // Props is a StateFlow, it will immediately produce an item. Without additional handling, the // first call to processActions will see that new props value and trigger another render pass, @@ -56,7 +57,7 @@ internal class WorkflowRunner( private val rootNode = WorkflowNode( id = workflow.id(), workflow = workflow, - initialProps = currentProps.value, + initialProps = currentProps, snapshot = snapshot, baseContext = scope.coroutineContext, interceptor = interceptor, @@ -70,27 +71,23 @@ internal class WorkflowRunner( * between every subsequent call to [processActions]. */ fun nextRendering(): RenderingAndSnapshot { - val rendering = rootNode.render(workflow, currentProps.value) + val rendering = rootNode.render(workflow, currentProps) val snapshot = rootNode.snapshot(workflow) return RenderingAndSnapshot(rendering, snapshot) } @Composable fun nextComposedRendering(): RenderingAndSnapshot { - val renderingState = remember { mutableStateOf(null) } + val rendering = remember { mutableStateOf(null) } - rootNode.Rendering(workflow, currentProps.value) { - renderingState.value = it - } + rootNode.Rendering(workflow, currentProps, rendering) val snapshot = remember { // need to key this on state inside WorkflowNode. LIkely have a Compose version. rootNode.snapshot(workflow) } - return remember(renderingState.value, snapshot) { - RenderingAndSnapshot(renderingState.value!!, snapshot) - } + return RenderingAndSnapshot(rendering.value!!, snapshot) } /** @@ -152,7 +149,7 @@ internal class WorkflowRunner( channelResult.exceptionOrNull()?.let { throw it } channelResult.getOrNull()?.let { newProps -> if (currentProps != newProps) { - currentProps.value = newProps + currentProps = newProps } } // Return PropsUpdated to tell the caller to do another render pass, but not emit an output. diff --git a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/WorkflowInterceptorTest.kt b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/WorkflowInterceptorTest.kt index 5f581b8428..55c827ff56 100644 --- a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/WorkflowInterceptorTest.kt +++ b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/WorkflowInterceptorTest.kt @@ -68,9 +68,8 @@ internal class WorkflowInterceptorTest { child: Workflow, props: ChildPropsT, key: String, - hoistRendering: @Composable (ChildRenderingT) -> Unit, handler: (ChildOutputT) -> WorkflowAction - ) { + ): ChildRenderingT { fail() } @@ -119,9 +118,8 @@ internal class WorkflowInterceptorTest { child: Workflow, props: ChildPropsT, key: String, - hoistRendering: @Composable (ChildRenderingT) -> Unit, handler: (ChildOutputT) -> WorkflowAction - ) { + ): ChildRenderingT { fail() } @@ -163,9 +161,8 @@ internal class WorkflowInterceptorTest { child: Workflow, props: ChildPropsT, key: String, - hoistRendering: @Composable (ChildRenderingT) -> Unit, handler: (ChildOutputT) -> WorkflowAction - ) { + ): ChildRenderingT { fail() } @@ -233,9 +230,8 @@ internal class WorkflowInterceptorTest { child: Workflow, props: ChildPropsT, key: String, - hoistRendering: @Composable (ChildRenderingT) -> Unit, handler: (ChildOutputT) -> WorkflowAction - ) { + ): ChildRenderingT { fail() } diff --git a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/RealRenderContextTest.kt b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/RealRenderContextTest.kt index fc30e79808..5c6ef6d5e7 100644 --- a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/RealRenderContextTest.kt +++ b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/RealRenderContextTest.kt @@ -55,18 +55,13 @@ internal class RealRenderContextTest { child: Workflow, props: ChildPropsT, key: String, - hoistRendering: @Composable (ChildRenderingT) -> Unit, handler: (ChildOutputT) -> WorkflowAction - ) { - hoistRendering( - Rendering( - child, - props, - key, - handler as (Any) -> WorkflowAction - ) as ChildRenderingT - ) - } + ): ChildRenderingT = Rendering( + child, + props, + key, + handler as (Any) -> WorkflowAction + ) as ChildRenderingT } private class TestRunner : SideEffectRunner { @@ -108,9 +103,8 @@ internal class RealRenderContextTest { child: Workflow, props: ChildPropsT, key: String, - hoistRendering: @Composable (ChildRenderingT) -> Unit, handler: (ChildOutputT) -> WorkflowAction - ): Unit = fail() + ): ChildRenderingT = fail() } private class PoisonRunner : SideEffectRunner { diff --git a/workflow-rx2/dependencies/runtimeClasspath.txt b/workflow-rx2/dependencies/runtimeClasspath.txt index 2e945cf86d..98e3c603de 100644 --- a/workflow-rx2/dependencies/runtimeClasspath.txt +++ b/workflow-rx2/dependencies/runtimeClasspath.txt @@ -1,15 +1,19 @@ :workflow-core +app.cash.molecule:molecule-runtime-jvm:0.3.0-SNAPSHOT +app.cash.molecule:molecule-runtime:0.3.0-SNAPSHOT com.squareup.okio:okio-jvm:3.0.0 com.squareup.okio:okio:3.0.0 io.reactivex.rxjava2:rxjava:2.2.21 +org.jetbrains.compose.runtime:runtime-desktop:1.1.0 +org.jetbrains.compose.runtime:runtime:1.1.0 org.jetbrains.kotlin:kotlin-bom:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-common:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.10 org.jetbrains.kotlin:kotlin-stdlib:1.6.10 -org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.1 -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1 -org.jetbrains.kotlinx:kotlinx-coroutines-reactive:1.5.1 -org.jetbrains.kotlinx:kotlinx-coroutines-rx2:1.5.1 +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.2 +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2 +org.jetbrains.kotlinx:kotlinx-coroutines-reactive:1.5.2 +org.jetbrains.kotlinx:kotlinx-coroutines-rx2:1.5.2 org.jetbrains:annotations:13.0 org.reactivestreams:reactive-streams:1.0.3 diff --git a/workflow-testing/api/workflow-testing.api b/workflow-testing/api/workflow-testing.api index 2220f8e269..028dc77e6f 100644 --- a/workflow-testing/api/workflow-testing.api +++ b/workflow-testing/api/workflow-testing.api @@ -1,7 +1,7 @@ public final class com/squareup/workflow1/testing/RealRenderTester : com/squareup/workflow1/testing/RenderTester, com/squareup/workflow1/BaseRenderContext, com/squareup/workflow1/Sink, com/squareup/workflow1/testing/RenderTestResult { public fun (Lcom/squareup/workflow1/StatefulWorkflow;Ljava/lang/Object;Ljava/lang/Object;Ljava/util/List;Ljava/util/List;ZLcom/squareup/workflow1/WorkflowAction;Ljava/util/List;Ljava/util/List;)V public synthetic fun (Lcom/squareup/workflow1/StatefulWorkflow;Ljava/lang/Object;Ljava/lang/Object;Ljava/util/List;Ljava/util/List;ZLcom/squareup/workflow1/WorkflowAction;Ljava/util/List;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun ChildRendering (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V + public fun ChildRendering (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function10;)Lkotlin/jvm/functions/Function9; public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function11;)Lkotlin/jvm/functions/Function10; public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;)Lkotlin/jvm/functions/Function0; @@ -92,7 +92,7 @@ public final class com/squareup/workflow1/testing/RealRenderTesterKt { public final class com/squareup/workflow1/testing/RenderIdempotencyChecker : com/squareup/workflow1/WorkflowInterceptor { public static final field INSTANCE Lcom/squareup/workflow1/testing/RenderIdempotencyChecker; - public fun Rendering (Ljava/lang/Object;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lkotlin/jvm/functions/Function4;)V + public fun Rendering (Ljava/lang/Object;Ljava/lang/Object;Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; 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; diff --git a/workflow-testing/dependencies/runtimeClasspath.txt b/workflow-testing/dependencies/runtimeClasspath.txt index e8d8cd8d08..9093652080 100644 --- a/workflow-testing/dependencies/runtimeClasspath.txt +++ b/workflow-testing/dependencies/runtimeClasspath.txt @@ -2,18 +2,22 @@ :workflow-config:config-jvm :workflow-core :workflow-runtime +app.cash.molecule:molecule-runtime-jvm:0.3.0-SNAPSHOT +app.cash.molecule:molecule-runtime:0.3.0-SNAPSHOT com.squareup.okio:okio-jvm:3.0.0 com.squareup.okio:okio:3.0.0 net.java.dev.jna:jna-platform:5.5.0 net.java.dev.jna:jna:5.5.0 +org.jetbrains.compose.runtime:runtime-desktop:1.1.0 +org.jetbrains.compose.runtime:runtime:1.1.0 org.jetbrains.kotlin:kotlin-bom:1.6.10 org.jetbrains.kotlin:kotlin-reflect:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-common:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.10 org.jetbrains.kotlin:kotlin-stdlib:1.6.10 -org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.1 -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1 -org.jetbrains.kotlinx:kotlinx-coroutines-debug:1.5.1 -org.jetbrains.kotlinx:kotlinx-coroutines-test:1.5.1 +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.2 +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2 +org.jetbrains.kotlinx:kotlinx-coroutines-debug:1.5.2 +org.jetbrains.kotlinx:kotlinx-coroutines-test:1.5.2 org.jetbrains:annotations:13.0 diff --git a/workflow-testing/src/main/java/com/squareup/workflow1/testing/RealRenderTester.kt b/workflow-testing/src/main/java/com/squareup/workflow1/testing/RealRenderTester.kt index 1ed0a0a246..6c40356468 100644 --- a/workflow-testing/src/main/java/com/squareup/workflow1/testing/RealRenderTester.kt +++ b/workflow-testing/src/main/java/com/squareup/workflow1/testing/RealRenderTester.kt @@ -227,9 +227,8 @@ internal class RealRenderTester( child: Workflow, props: ChildPropsT, key: String, - hoistRendering: @Composable (ChildRenderingT) -> Unit, handler: (ChildOutputT) -> WorkflowAction - ) { + ): ChildRenderingT { val identifierPair = Pair(child.identifier, key) require(identifierPair !in renderedChildren) { "Expected keys to be unique for ${child.identifier}: key=\"$key\"" @@ -282,7 +281,7 @@ internal class RealRenderTester( } @Suppress("UNCHECKED_CAST") - hoistRendering(match.childRendering as ChildRenderingT) + return match.childRendering as ChildRenderingT } override fun runningSideEffect( diff --git a/workflow-tracing/api/workflow-tracing.api b/workflow-tracing/api/workflow-tracing.api index 4732d47574..e29127d49b 100644 --- a/workflow-tracing/api/workflow-tracing.api +++ b/workflow-tracing/api/workflow-tracing.api @@ -21,7 +21,7 @@ public final class com/squareup/workflow1/diagnostic/tracing/TracingWorkflowInte public fun (Lcom/squareup/workflow1/diagnostic/tracing/MemoryStats;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)V public fun (Lcom/squareup/workflow1/diagnostic/tracing/MemoryStats;Lkotlin/jvm/functions/Function2;)V public synthetic fun (Lcom/squareup/workflow1/diagnostic/tracing/MemoryStats;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun Rendering (Ljava/lang/Object;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lkotlin/jvm/functions/Function4;)V + public fun Rendering (Ljava/lang/Object;Ljava/lang/Object;Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; 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; diff --git a/workflow-tracing/dependencies/runtimeClasspath.txt b/workflow-tracing/dependencies/runtimeClasspath.txt index c694d8f99c..6853129da0 100644 --- a/workflow-tracing/dependencies/runtimeClasspath.txt +++ b/workflow-tracing/dependencies/runtimeClasspath.txt @@ -1,15 +1,19 @@ :trace-encoder :workflow-core :workflow-runtime +app.cash.molecule:molecule-runtime-jvm:0.3.0-SNAPSHOT +app.cash.molecule:molecule-runtime:0.3.0-SNAPSHOT com.squareup.moshi:moshi-adapters:1.13.0 com.squareup.moshi:moshi:1.13.0 com.squareup.okio:okio-jvm:3.0.0 com.squareup.okio:okio:3.0.0 +org.jetbrains.compose.runtime:runtime-desktop:1.1.0 +org.jetbrains.compose.runtime:runtime:1.1.0 org.jetbrains.kotlin:kotlin-bom:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-common:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.10 org.jetbrains.kotlin:kotlin-stdlib:1.6.10 -org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.1 -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1 +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.2 +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2 org.jetbrains:annotations:13.0 diff --git a/workflow-ui/compose-tooling/dependencies/releaseRuntimeClasspath.txt b/workflow-ui/compose-tooling/dependencies/releaseRuntimeClasspath.txt index 846b8f021b..a45789daa1 100644 --- a/workflow-ui/compose-tooling/dependencies/releaseRuntimeClasspath.txt +++ b/workflow-ui/compose-tooling/dependencies/releaseRuntimeClasspath.txt @@ -16,24 +16,24 @@ androidx.arch.core:core-common:2.1.0 androidx.arch.core:core-runtime:2.1.0 androidx.autofill:autofill:1.0.0 androidx.collection:collection:1.1.0 -androidx.compose.animation:animation-core:1.1.0-rc01 -androidx.compose.animation:animation:1.1.0-rc01 -androidx.compose.foundation:foundation-layout:1.1.0-rc01 -androidx.compose.foundation:foundation:1.1.0-rc01 +androidx.compose.animation:animation-core:1.1.0 +androidx.compose.animation:animation:1.1.0 +androidx.compose.foundation:foundation-layout:1.1.0 +androidx.compose.foundation:foundation:1.1.0 androidx.compose.material:material-icons-core:1.0.0 androidx.compose.material:material-ripple:1.0.0 androidx.compose.material:material:1.0.0 -androidx.compose.runtime:runtime-saveable:1.1.0-rc01 -androidx.compose.runtime:runtime:1.1.0-rc01 -androidx.compose.ui:ui-geometry:1.1.0-rc01 -androidx.compose.ui:ui-graphics:1.1.0-rc01 -androidx.compose.ui:ui-text:1.1.0-rc01 -androidx.compose.ui:ui-tooling-data:1.1.0-rc01 -androidx.compose.ui:ui-tooling-preview:1.1.0-rc01 -androidx.compose.ui:ui-tooling:1.1.0-rc01 -androidx.compose.ui:ui-unit:1.1.0-rc01 -androidx.compose.ui:ui-util:1.1.0-rc01 -androidx.compose.ui:ui:1.1.0-rc01 +androidx.compose.runtime:runtime-saveable:1.1.0 +androidx.compose.runtime:runtime:1.1.0 +androidx.compose.ui:ui-geometry:1.1.0 +androidx.compose.ui:ui-graphics:1.1.0 +androidx.compose.ui:ui-text:1.1.0 +androidx.compose.ui:ui-tooling-data:1.1.0 +androidx.compose.ui:ui-tooling-preview:1.1.0 +androidx.compose.ui:ui-tooling:1.1.0 +androidx.compose.ui:ui-unit:1.1.0 +androidx.compose.ui:ui-util:1.1.0 +androidx.compose.ui:ui:1.1.0 androidx.core:core-ktx:1.6.0 androidx.core:core:1.6.0 androidx.cursoradapter:cursoradapter:1.0.0 @@ -51,7 +51,7 @@ androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0 androidx.lifecycle:lifecycle-viewmodel-savedstate:2.4.0 androidx.lifecycle:lifecycle-viewmodel:2.4.0 androidx.loader:loader:1.0.0 -androidx.profileinstaller:profileinstaller:1.1.0-rc01 +androidx.profileinstaller:profileinstaller:1.1.0 androidx.savedstate:savedstate-ktx:1.1.0 androidx.savedstate:savedstate:1.1.0 androidx.startup:startup-runtime:1.0.0 @@ -61,8 +61,11 @@ androidx.vectordrawable:vectordrawable-animated:1.1.0 androidx.vectordrawable:vectordrawable:1.1.0 androidx.versionedparcelable:versionedparcelable:1.1.1 androidx.viewpager:viewpager:1.0.0 +app.cash.molecule:molecule-runtime-android:0.3.0-SNAPSHOT +app.cash.molecule:molecule-runtime:0.3.0-SNAPSHOT com.squareup.okio:okio-jvm:3.0.0 com.squareup.okio:okio:3.0.0 +org.jetbrains.compose.runtime:runtime:1.1.0 org.jetbrains.kotlin:kotlin-bom:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-common:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10 diff --git a/workflow-ui/compose/dependencies/releaseRuntimeClasspath.txt b/workflow-ui/compose/dependencies/releaseRuntimeClasspath.txt index 3ec147f709..451d58e91b 100644 --- a/workflow-ui/compose/dependencies/releaseRuntimeClasspath.txt +++ b/workflow-ui/compose/dependencies/releaseRuntimeClasspath.txt @@ -13,18 +13,18 @@ androidx.arch.core:core-common:2.1.0 androidx.arch.core:core-runtime:2.1.0 androidx.autofill:autofill:1.0.0 androidx.collection:collection:1.1.0 -androidx.compose.animation:animation-core:1.1.0-rc01 -androidx.compose.animation:animation:1.1.0-rc01 -androidx.compose.foundation:foundation-layout:1.1.0-rc01 -androidx.compose.foundation:foundation:1.1.0-rc01 -androidx.compose.runtime:runtime-saveable:1.1.0-rc01 -androidx.compose.runtime:runtime:1.1.0-rc01 -androidx.compose.ui:ui-geometry:1.1.0-rc01 -androidx.compose.ui:ui-graphics:1.1.0-rc01 -androidx.compose.ui:ui-text:1.1.0-rc01 -androidx.compose.ui:ui-unit:1.1.0-rc01 -androidx.compose.ui:ui-util:1.1.0-rc01 -androidx.compose.ui:ui:1.1.0-rc01 +androidx.compose.animation:animation-core:1.1.0 +androidx.compose.animation:animation:1.1.0 +androidx.compose.foundation:foundation-layout:1.1.0 +androidx.compose.foundation:foundation:1.1.0 +androidx.compose.runtime:runtime-saveable:1.1.0 +androidx.compose.runtime:runtime:1.1.0 +androidx.compose.ui:ui-geometry:1.1.0 +androidx.compose.ui:ui-graphics:1.1.0 +androidx.compose.ui:ui-text:1.1.0 +androidx.compose.ui:ui-unit:1.1.0 +androidx.compose.ui:ui-util:1.1.0 +androidx.compose.ui:ui:1.1.0 androidx.core:core-ktx:1.6.0 androidx.core:core:1.6.0 androidx.cursoradapter:cursoradapter:1.0.0 @@ -42,7 +42,7 @@ androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0 androidx.lifecycle:lifecycle-viewmodel-savedstate:2.4.0 androidx.lifecycle:lifecycle-viewmodel:2.4.0 androidx.loader:loader:1.0.0 -androidx.profileinstaller:profileinstaller:1.1.0-rc01 +androidx.profileinstaller:profileinstaller:1.1.0 androidx.savedstate:savedstate:1.1.0 androidx.startup:startup-runtime:1.0.0 androidx.tracing:tracing:1.0.0 @@ -51,8 +51,11 @@ androidx.vectordrawable:vectordrawable-animated:1.1.0 androidx.vectordrawable:vectordrawable:1.1.0 androidx.versionedparcelable:versionedparcelable:1.1.1 androidx.viewpager:viewpager:1.0.0 +app.cash.molecule:molecule-runtime-android:0.3.0-SNAPSHOT +app.cash.molecule:molecule-runtime:0.3.0-SNAPSHOT com.squareup.okio:okio-jvm:3.0.0 com.squareup.okio:okio:3.0.0 +org.jetbrains.compose.runtime:runtime:1.1.0 org.jetbrains.kotlin:kotlin-bom:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-common:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10 diff --git a/workflow-ui/container-android/dependencies/releaseRuntimeClasspath.txt b/workflow-ui/container-android/dependencies/releaseRuntimeClasspath.txt index 1f15c44077..9a66deff2b 100644 --- a/workflow-ui/container-android/dependencies/releaseRuntimeClasspath.txt +++ b/workflow-ui/container-android/dependencies/releaseRuntimeClasspath.txt @@ -11,6 +11,7 @@ androidx.appcompat:appcompat:1.3.1 androidx.arch.core:core-common:2.1.0 androidx.arch.core:core-runtime:2.1.0 androidx.collection:collection:1.1.0 +androidx.compose.runtime:runtime:1.1.0 androidx.core:core-ktx:1.6.0 androidx.core:core:1.6.0 androidx.cursoradapter:cursoradapter:1.0.0 @@ -34,14 +35,17 @@ androidx.vectordrawable:vectordrawable-animated:1.1.0 androidx.vectordrawable:vectordrawable:1.1.0 androidx.versionedparcelable:versionedparcelable:1.1.1 androidx.viewpager:viewpager:1.0.0 +app.cash.molecule:molecule-runtime-android:0.3.0-SNAPSHOT +app.cash.molecule:molecule-runtime:0.3.0-SNAPSHOT com.squareup.okio:okio-jvm:3.0.0 com.squareup.okio:okio:3.0.0 +org.jetbrains.compose.runtime:runtime:1.1.0 org.jetbrains.kotlin:kotlin-bom:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-common:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.10 org.jetbrains.kotlin:kotlin-stdlib:1.6.10 -org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1 -org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.1 -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1 +org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2 +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.2 +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2 org.jetbrains:annotations:13.0 diff --git a/workflow-ui/container-common/dependencies/runtimeClasspath.txt b/workflow-ui/container-common/dependencies/runtimeClasspath.txt index 5a9d007180..d232d2e3e1 100644 --- a/workflow-ui/container-common/dependencies/runtimeClasspath.txt +++ b/workflow-ui/container-common/dependencies/runtimeClasspath.txt @@ -6,6 +6,6 @@ org.jetbrains.kotlin:kotlin-stdlib-common:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.10 org.jetbrains.kotlin:kotlin-stdlib:1.6.10 -org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.1 -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1 +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.2 +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2 org.jetbrains:annotations:13.0 diff --git a/workflow-ui/core-android/dependencies/releaseRuntimeClasspath.txt b/workflow-ui/core-android/dependencies/releaseRuntimeClasspath.txt index ee28cf72b7..5a69c38578 100644 --- a/workflow-ui/core-android/dependencies/releaseRuntimeClasspath.txt +++ b/workflow-ui/core-android/dependencies/releaseRuntimeClasspath.txt @@ -7,6 +7,7 @@ androidx.annotation:annotation:1.2.0 androidx.arch.core:core-common:2.1.0 androidx.arch.core:core-runtime:2.1.0 androidx.collection:collection:1.1.0 +androidx.compose.runtime:runtime:1.1.0 androidx.core:core-ktx:1.6.0 androidx.core:core:1.6.0 androidx.customview:customview:1.0.0 @@ -25,14 +26,17 @@ androidx.tracing:tracing:1.0.0 androidx.transition:transition:1.4.1 androidx.versionedparcelable:versionedparcelable:1.1.1 androidx.viewpager:viewpager:1.0.0 +app.cash.molecule:molecule-runtime-android:0.3.0-SNAPSHOT +app.cash.molecule:molecule-runtime:0.3.0-SNAPSHOT com.squareup.okio:okio-jvm:3.0.0 com.squareup.okio:okio:3.0.0 +org.jetbrains.compose.runtime:runtime:1.1.0 org.jetbrains.kotlin:kotlin-bom:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-common:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.10 org.jetbrains.kotlin:kotlin-stdlib:1.6.10 -org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1 -org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.1 -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1 +org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2 +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.2 +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2 org.jetbrains:annotations:13.0 diff --git a/workflow-ui/core-common/dependencies/runtimeClasspath.txt b/workflow-ui/core-common/dependencies/runtimeClasspath.txt index 19adaaffcb..6a9b6ed46f 100644 --- a/workflow-ui/core-common/dependencies/runtimeClasspath.txt +++ b/workflow-ui/core-common/dependencies/runtimeClasspath.txt @@ -5,6 +5,6 @@ org.jetbrains.kotlin:kotlin-stdlib-common:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.10 org.jetbrains.kotlin:kotlin-stdlib:1.6.10 -org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.1 -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1 +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.2 +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2 org.jetbrains:annotations:13.0 diff --git a/workflow-ui/radiography/dependencies/releaseRuntimeClasspath.txt b/workflow-ui/radiography/dependencies/releaseRuntimeClasspath.txt index a5efc68589..1378806cc1 100644 --- a/workflow-ui/radiography/dependencies/releaseRuntimeClasspath.txt +++ b/workflow-ui/radiography/dependencies/releaseRuntimeClasspath.txt @@ -8,6 +8,7 @@ androidx.annotation:annotation:1.2.0 androidx.arch.core:core-common:2.1.0 androidx.arch.core:core-runtime:2.1.0 androidx.collection:collection:1.1.0 +androidx.compose.runtime:runtime:1.1.0 androidx.core:core-ktx:1.6.0 androidx.core:core:1.6.0 androidx.customview:customview:1.0.0 @@ -26,16 +27,19 @@ androidx.tracing:tracing:1.0.0 androidx.transition:transition:1.4.1 androidx.versionedparcelable:versionedparcelable:1.1.1 androidx.viewpager:viewpager:1.0.0 +app.cash.molecule:molecule-runtime-android:0.3.0-SNAPSHOT +app.cash.molecule:molecule-runtime:0.3.0-SNAPSHOT com.squareup.curtains:curtains:1.2.1 com.squareup.okio:okio-jvm:3.0.0 com.squareup.okio:okio:3.0.0 com.squareup.radiography:radiography:2.4.0 +org.jetbrains.compose.runtime:runtime:1.1.0 org.jetbrains.kotlin:kotlin-bom:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-common:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.10 org.jetbrains.kotlin:kotlin-stdlib:1.6.10 -org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1 -org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.1 -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1 +org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2 +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.2 +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2 org.jetbrains:annotations:13.0 From dcec792d8cae1ac2bbd3e7926bfeb75a43e0fe0e Mon Sep 17 00:00:00 2001 From: Stephen Edwards Date: Wed, 13 Jul 2022 17:06:05 -0400 Subject: [PATCH 3/3] Move Compose runtime optimizations out of core/runtime --- artifacts.json | 8 + .../complex-poetry/build.gradle.kts | 1 + .../complex/poetry/RenderPassTest.kt | 2 +- .../poetry/MaybeLoadingGatekeeperWorkflow.kt | 8 +- .../complex/poetry/PerformancePoemWorkflow.kt | 8 +- .../poetry/PerformancePoemsBrowserWorkflow.kt | 8 +- .../poetry/PerformancePoetryActivity.kt | 7 +- .../ActionHandlingTracingInterceptor.kt | 11 +- .../PerformanceTracingInterceptor.kt | 10 +- .../RenderPassCountingInterceptor.kt | 10 +- .../sample/poetry/PoemListWorkflow.kt | 9 - .../sample/poetry/RealPoemWorkflow.kt | 10 - .../sample/poetry/RealPoemsBrowserWorkflow.kt | 10 - .../sample/poetry/StanzaListWorkflow.kt | 10 +- .../squareup/sample/poetry/StanzaWorkflow.kt | 9 - settings.gradle.kts | 1 + .../dependencies/releaseRuntimeClasspath.txt | 14 - .../dependencies/runtimeClasspath.txt | 4 - workflow-core-compose/README.md | 9 + .../api/workflow-core-compose.api | 201 +++++++++ workflow-core-compose/build.gradle.kts | 36 ++ .../dependencies/jvmRuntimeClasspath.txt | 15 + .../dependencies/runtimeClasspath.txt | 16 + workflow-core-compose/gradle.properties | 3 + .../compose/BaseComposeRenderContext.kt | 57 +++ .../ChainedComposeWorkflowInterceptor.kt | 124 ++++++ .../workflow1/compose/ComposeRuntimePlugin.kt | 72 ++++ .../compose/ComposeSubtreeManager.kt | 124 ++++++ .../compose/ComposeWorkflowInterceptor.kt | 168 ++++++++ .../compose/RealComposeRenderContext.kt | 46 ++ .../compose/StatefulComposeWorkflow.kt | 228 ++++++++++ .../compose/StatelessComposeWorkflow.kt | 138 ++++++ .../compose/WorkflowComposeChildNode.kt | 50 +++ .../workflow1/compose/WorkflowComposeNode.kt | 140 +++++++ .../compose/WorkflowComposeRunner.kt | 66 +++ workflow-core/api/workflow-core.api | 34 +- workflow-core/build.gradle.kts | 6 +- .../dependencies/jvmRuntimeClasspath.txt | 4 - .../dependencies/runtimeClasspath.txt | 4 - .../squareup/workflow1/BaseRenderContext.kt | 40 -- .../squareup/workflow1/StatefulWorkflow.kt | 126 +----- .../squareup/workflow1/StatelessWorkflow.kt | 51 +-- .../com/squareup/workflow1/WorkerWorkflow.kt | 10 - .../kotlin/com/squareup/workflow1/Workflow.kt | 9 - workflow-runtime/api/workflow-runtime.api | 392 ++++++++++-------- workflow-runtime/build.gradle.kts | 3 - .../dependencies/jvmRuntimeClasspath.txt | 4 - .../jvmWorkflowNodeRuntimeClasspath.txt | 6 - .../{internal => }/ActiveStagingList.kt | 22 +- .../ChainedWorkflowInterceptor.kt | 78 +--- .../workflow1/{internal => }/IdCounter.kt | 6 +- .../{internal => }/InlineLinkedList.kt | 24 +- .../{internal => }/RealRenderContext.kt | 42 +- .../com/squareup/workflow1/RenderWorkflow.kt | 53 +-- .../SimpleLoggingWorkflowInterceptor.kt | 13 - .../{internal => }/SubtreeManager.kt | 93 +---- .../com/squareup/workflow1/TreeSnapshot.kt | 5 +- .../{internal => }/WorkflowChildNode.kt | 42 +- .../squareup/workflow1/WorkflowInterceptor.kt | 74 +--- .../workflow1/{internal => }/WorkflowNode.kt | 186 +++------ .../{internal => }/WorkflowNodeId.kt | 17 +- .../{internal => }/WorkflowRunner.kt | 75 ++-- .../workflow1/WorkflowRuntimeClock.kt | 81 ---- .../workflow1/WorkflowRuntimePlugin.kt | 41 ++ .../workflow1/internal/SideEffectNode.kt | 2 +- .../{internal => }/ActiveStagingListTest.kt | 4 +- .../ChainedWorkflowInterceptorTest.kt | 26 +- .../{internal => }/InlineLinkedListTest.kt | 4 +- .../{internal => }/RealRenderContextTest.kt | 41 +- .../SimpleLoggingWorkflowInterceptorTest.kt | 11 - .../{internal => }/SubtreeManagerTest.kt | 15 +- .../squareup/workflow1/TreeSnapshotTest.kt | 2 - .../workflow1/WorkflowInterceptorTest.kt | 41 -- .../{internal => }/WorkflowNodeTest.kt | 176 ++++---- .../{internal => }/WorkflowRunnerTest.kt | 13 +- .../workflow1/WorkflowNodeBenchmark.kt | 6 +- .../dependencies/runtimeClasspath.txt | 4 - workflow-testing/api/workflow-testing.api | 2 - .../dependencies/runtimeClasspath.txt | 4 - .../workflow1/testing/RealRenderTester.kt | 63 --- workflow-tracing/api/workflow-tracing.api | 1 - .../dependencies/runtimeClasspath.txt | 4 - .../dependencies/releaseRuntimeClasspath.txt | 3 - .../dependencies/releaseRuntimeClasspath.txt | 3 - .../dependencies/releaseRuntimeClasspath.txt | 4 - workflow-ui/core-android/api/core-android.api | 12 +- .../dependencies/releaseRuntimeClasspath.txt | 4 - .../workflow1/ui/AndroidRenderWorkflow.kt | 29 +- .../dependencies/releaseRuntimeClasspath.txt | 4 - 89 files changed, 2120 insertions(+), 1532 deletions(-) create mode 100644 workflow-core-compose/README.md create mode 100644 workflow-core-compose/api/workflow-core-compose.api create mode 100644 workflow-core-compose/build.gradle.kts create mode 100644 workflow-core-compose/dependencies/jvmRuntimeClasspath.txt create mode 100644 workflow-core-compose/dependencies/runtimeClasspath.txt create mode 100644 workflow-core-compose/gradle.properties create mode 100644 workflow-core-compose/src/commonMain/kotlin/com/squareup/workflow1/compose/BaseComposeRenderContext.kt create mode 100644 workflow-core-compose/src/commonMain/kotlin/com/squareup/workflow1/compose/ChainedComposeWorkflowInterceptor.kt create mode 100644 workflow-core-compose/src/commonMain/kotlin/com/squareup/workflow1/compose/ComposeRuntimePlugin.kt create mode 100644 workflow-core-compose/src/commonMain/kotlin/com/squareup/workflow1/compose/ComposeSubtreeManager.kt create mode 100644 workflow-core-compose/src/commonMain/kotlin/com/squareup/workflow1/compose/ComposeWorkflowInterceptor.kt create mode 100644 workflow-core-compose/src/commonMain/kotlin/com/squareup/workflow1/compose/RealComposeRenderContext.kt create mode 100644 workflow-core-compose/src/commonMain/kotlin/com/squareup/workflow1/compose/StatefulComposeWorkflow.kt create mode 100644 workflow-core-compose/src/commonMain/kotlin/com/squareup/workflow1/compose/StatelessComposeWorkflow.kt create mode 100644 workflow-core-compose/src/commonMain/kotlin/com/squareup/workflow1/compose/WorkflowComposeChildNode.kt create mode 100644 workflow-core-compose/src/commonMain/kotlin/com/squareup/workflow1/compose/WorkflowComposeNode.kt create mode 100644 workflow-core-compose/src/commonMain/kotlin/com/squareup/workflow1/compose/WorkflowComposeRunner.kt rename workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/{internal => }/ActiveStagingList.kt (81%) rename workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/{internal => }/ChainedWorkflowInterceptor.kt (62%) rename workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/{internal => }/IdCounter.kt (74%) rename workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/{internal => }/InlineLinkedList.kt (82%) rename workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/{internal => }/RealRenderContext.kt (61%) rename workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/{internal => }/SubtreeManager.kt (68%) rename workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/{internal => }/WorkflowChildNode.kt (56%) rename workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/{internal => }/WorkflowNode.kt (59%) rename workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/{internal => }/WorkflowNodeId.kt (72%) rename workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/{internal => }/WorkflowRunner.kt (71%) delete mode 100644 workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/WorkflowRuntimeClock.kt create mode 100644 workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/WorkflowRuntimePlugin.kt rename workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/{internal => }/ActiveStagingListTest.kt (95%) rename workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/{internal => }/ChainedWorkflowInterceptorTest.kt (92%) rename workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/{internal => }/InlineLinkedListTest.kt (98%) rename workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/{internal => }/RealRenderContextTest.kt (88%) rename workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/{internal => }/SubtreeManagerTest.kt (94%) rename workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/{internal => }/WorkflowNodeTest.kt (93%) rename workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/{internal => }/WorkflowRunnerTest.kt (95%) diff --git a/artifacts.json b/artifacts.json index 6cddeb3fa0..13a5897392 100644 --- a/artifacts.json +++ b/artifacts.json @@ -23,6 +23,14 @@ "packaging": "jar", "javaVersion": "1.8" }, + { + "gradlePath": ":workflow-core-compose", + "group": "com.squareup.workflow1", + "artifactId": "workflow-core-compose", + "description": "Workflow Core Compose", + "packaging": "jar", + "javaVersion": "1.8" + }, { "gradlePath": ":workflow-runtime", "group": "com.squareup.workflow1", diff --git a/benchmarks/performance-poetry/complex-poetry/build.gradle.kts b/benchmarks/performance-poetry/complex-poetry/build.gradle.kts index 5211e244bf..3358153d18 100644 --- a/benchmarks/performance-poetry/complex-poetry/build.gradle.kts +++ b/benchmarks/performance-poetry/complex-poetry/build.gradle.kts @@ -52,6 +52,7 @@ dependencies { // API on an app module so these are transitive dependencies for the benchmarks. api(project(":samples:containers:android")) api(project(":samples:containers:poetry")) + api(project(":workflow-core-compose")) api(project(":workflow-ui:core-android")) implementation(libs.androidx.activity.ktx) diff --git a/benchmarks/performance-poetry/complex-poetry/src/androidTest/java/com/squareup/benchmarks/performance/complex/poetry/RenderPassTest.kt b/benchmarks/performance-poetry/complex-poetry/src/androidTest/java/com/squareup/benchmarks/performance/complex/poetry/RenderPassTest.kt index 7bc03ff1c7..6c9c8fa38a 100644 --- a/benchmarks/performance-poetry/complex-poetry/src/androidTest/java/com/squareup/benchmarks/performance/complex/poetry/RenderPassTest.kt +++ b/benchmarks/performance-poetry/complex-poetry/src/androidTest/java/com/squareup/benchmarks/performance/complex/poetry/RenderPassTest.kt @@ -84,7 +84,7 @@ class RenderPassTest { @Ignore( "Not sure why but this gets stuck on initializing. Compose doesn't get the next" + - " frame when this is started by the test, but it does when running directly." + " frame when this is started by the test, but it does when running directly. See #835" ) @Test fun renderPassCounterFrameTimeoutComposeComplexWithInitializingState() { runRenderPassCounter(COMPLEX_INITIALIZING, useFrameTimeout = true, useCompose = true) diff --git a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/MaybeLoadingGatekeeperWorkflow.kt b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/MaybeLoadingGatekeeperWorkflow.kt index 4efcdcb185..929c3857b9 100644 --- a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/MaybeLoadingGatekeeperWorkflow.kt +++ b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/MaybeLoadingGatekeeperWorkflow.kt @@ -9,19 +9,21 @@ import com.squareup.sample.container.overviewdetail.OverviewDetailScreen import com.squareup.workflow1.Snapshot import com.squareup.workflow1.StatefulWorkflow import com.squareup.workflow1.Workflow +import com.squareup.workflow1.WorkflowExperimentalRuntime import com.squareup.workflow1.action +import com.squareup.workflow1.compose.StatefulComposeWorkflow import com.squareup.workflow1.runningWorker import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import kotlinx.coroutines.flow.Flow typealias IsLoading = Boolean -@OptIn(WorkflowUiExperimentalApi::class) +@OptIn(WorkflowUiExperimentalApi::class, WorkflowExperimentalRuntime::class) class MaybeLoadingGatekeeperWorkflow( private val childWithLoading: Workflow, private val childProps: T, private val isLoading: Flow -) : StatefulWorkflow() { +) : StatefulComposeWorkflow() { override fun initialState( props: Unit, snapshot: Snapshot? @@ -30,7 +32,7 @@ class MaybeLoadingGatekeeperWorkflow( override fun render( renderProps: Unit, renderState: IsLoading, - context: RenderContext + context: StatefulWorkflow.RenderContext ): MayBeLoadingScreen { context.runningWorker(isLoading.asTraceableWorker("GatekeeperLoading")) { action { diff --git a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/PerformancePoemWorkflow.kt b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/PerformancePoemWorkflow.kt index d12f2e6202..22ded5608f 100644 --- a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/PerformancePoemWorkflow.kt +++ b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/PerformancePoemWorkflow.kt @@ -31,7 +31,9 @@ import com.squareup.workflow1.StatefulWorkflow import com.squareup.workflow1.Worker import com.squareup.workflow1.WorkflowAction import com.squareup.workflow1.WorkflowAction.Companion.noAction +import com.squareup.workflow1.WorkflowExperimentalRuntime import com.squareup.workflow1.action +import com.squareup.workflow1.compose.StatefulComposeWorkflow import com.squareup.workflow1.runningWorker import com.squareup.workflow1.ui.Screen import com.squareup.workflow1.ui.WorkflowUiExperimentalApi @@ -58,11 +60,11 @@ import kotlinx.coroutines.flow.flow * break ties/conflicts with a token in the start/stop requests. We leave that complexity out * here. ** */ -@OptIn(WorkflowUiExperimentalApi::class) +@OptIn(WorkflowUiExperimentalApi::class, WorkflowExperimentalRuntime::class) class PerformancePoemWorkflow( private val simulatedPerfConfig: SimulatedPerfConfig = SimulatedPerfConfig.NO_SIMULATED_PERF, private val isLoading: MutableStateFlow, -) : PoemWorkflow, StatefulWorkflow() { +) : PoemWorkflow, StatefulComposeWorkflow() { sealed class State { // N.B. This state is a smell. We include it to be able to mimic smells @@ -95,7 +97,7 @@ class PerformancePoemWorkflow( override fun render( renderProps: Poem, renderState: State, - context: RenderContext + context: StatefulWorkflow.RenderContext ): OverviewDetailScreen { return when (renderState) { Initializing -> { diff --git a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/PerformancePoemsBrowserWorkflow.kt b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/PerformancePoemsBrowserWorkflow.kt index 424f73028a..2d5c7d7634 100644 --- a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/PerformancePoemsBrowserWorkflow.kt +++ b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/PerformancePoemsBrowserWorkflow.kt @@ -19,7 +19,9 @@ import com.squareup.sample.poetry.PoemsBrowserWorkflow import com.squareup.sample.poetry.model.Poem import com.squareup.workflow1.Snapshot import com.squareup.workflow1.StatefulWorkflow +import com.squareup.workflow1.WorkflowExperimentalRuntime import com.squareup.workflow1.action +import com.squareup.workflow1.compose.StatefulComposeWorkflow import com.squareup.workflow1.runningWorker import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.container.BackStackScreen @@ -44,14 +46,14 @@ import java.lang.IllegalStateException * break ties/conflicts with a token in the start/stop requests. We leave that complexity out * here. ** */ -@OptIn(WorkflowUiExperimentalApi::class) +@OptIn(WorkflowUiExperimentalApi::class, WorkflowExperimentalRuntime::class) class PerformancePoemsBrowserWorkflow( private val simulatedPerfConfig: SimulatedPerfConfig, private val poemWorkflow: PoemWorkflow, private val isLoading: MutableStateFlow, ) : PoemsBrowserWorkflow, - StatefulWorkflow, State, Unit, OverviewDetailScreen>() { + StatefulComposeWorkflow, State, Unit, OverviewDetailScreen>() { sealed class State { // N.B. This state is a smell. We include it to be able to mimic smells @@ -76,7 +78,7 @@ class PerformancePoemsBrowserWorkflow( override fun render( renderProps: List, renderState: State, - context: RenderContext + context: StatefulWorkflow, State, Unit, OverviewDetailScreen>.RenderContext ): OverviewDetailScreen { val poemListProps = Props( poems = renderProps, diff --git a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/PerformancePoetryActivity.kt b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/PerformancePoetryActivity.kt index 29e1b3b489..7349110e09 100644 --- a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/PerformancePoetryActivity.kt +++ b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/PerformancePoetryActivity.kt @@ -1,3 +1,5 @@ +@file:OptIn(WorkflowExperimentalRuntime::class) + package com.squareup.benchmarks.performance.complex.poetry import android.content.pm.ApplicationInfo @@ -25,6 +27,7 @@ import com.squareup.workflow1.RuntimeConfig.FrameTimeout import com.squareup.workflow1.RuntimeConfig.RenderPerAction import com.squareup.workflow1.WorkflowExperimentalRuntime import com.squareup.workflow1.WorkflowInterceptor +import com.squareup.workflow1.compose.ComposeRuntimePlugin import com.squareup.workflow1.ui.Screen import com.squareup.workflow1.ui.ViewEnvironment.Companion.EMPTY import com.squareup.workflow1.ui.ViewRegistry @@ -270,12 +273,14 @@ class PoetryModel( runtimeConfig: RuntimeConfig ) : ViewModel() { @OptIn(WorkflowUiExperimentalApi::class) val renderings: StateFlow by lazy { + val runtimePlugin = if (runtimeConfig.useComposeInRuntime) ComposeRuntimePlugin else null renderWorkflowIn( workflow = workflow, scope = viewModelScope, savedStateHandle = savedState, interceptors = interceptor?.let { listOf(it) } ?: emptyList(), - runtimeConfig = runtimeConfig + runtimeConfig = runtimeConfig, + workflowRuntimePlugin = runtimePlugin ) } diff --git a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/instrumentation/ActionHandlingTracingInterceptor.kt b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/instrumentation/ActionHandlingTracingInterceptor.kt index 92d0145611..d89fa8d7ee 100644 --- a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/instrumentation/ActionHandlingTracingInterceptor.kt +++ b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/instrumentation/ActionHandlingTracingInterceptor.kt @@ -8,6 +8,9 @@ import com.squareup.workflow1.WorkflowAction import com.squareup.workflow1.WorkflowInterceptor import com.squareup.workflow1.WorkflowInterceptor.RenderContextInterceptor import com.squareup.workflow1.WorkflowInterceptor.WorkflowSession +import com.squareup.workflow1.compose.BaseComposeRenderContext +import com.squareup.workflow1.compose.ComposeWorkflowInterceptor +import com.squareup.workflow1.compose.ComposeWorkflowInterceptor.ComposeRenderContextInterceptor /** * We use this [WorkflowInterceptor] to add in tracing for the main thread messages that handle @@ -22,13 +25,13 @@ import com.squareup.workflow1.WorkflowInterceptor.WorkflowSession * annotate the [Worker] using [TraceableWorker] which will set it up with a key such that when * the action for the result is sent to the sink the main thread message will be traced. */ -class ActionHandlingTracingInterceptor : WorkflowInterceptor, Resettable { +class ActionHandlingTracingInterceptor : ComposeWorkflowInterceptor, Resettable { private val actionCounts: MutableMap = mutableMapOf() class EventHandlingTracingRenderContextInterceptor( private val actionCounts: MutableMap - ) : RenderContextInterceptor { + ) : ComposeRenderContextInterceptor { override fun onActionSent( action: WorkflowAction, proceed: (WorkflowAction) -> Unit @@ -76,9 +79,9 @@ class ActionHandlingTracingInterceptor : WorkflowInterceptor, Resettable { override fun Rendering( renderProps: P, renderState: S, - context: BaseRenderContext, + context: BaseComposeRenderContext, session: WorkflowSession, - proceed: @Composable (P, S, RenderContextInterceptor?) -> R + proceed: @Composable (P, S, ComposeRenderContextInterceptor?) -> R ): R { val rci = remember { EventHandlingTracingRenderContextInterceptor(actionCounts) diff --git a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/instrumentation/PerformanceTracingInterceptor.kt b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/instrumentation/PerformanceTracingInterceptor.kt index cedbf77f0b..1accb5327c 100644 --- a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/instrumentation/PerformanceTracingInterceptor.kt +++ b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/instrumentation/PerformanceTracingInterceptor.kt @@ -7,9 +7,11 @@ import androidx.tracing.Trace import com.squareup.benchmarks.performance.complex.poetry.PerformancePoemWorkflow import com.squareup.benchmarks.performance.complex.poetry.PerformancePoemsBrowserWorkflow import com.squareup.workflow1.BaseRenderContext -import com.squareup.workflow1.WorkflowInterceptor import com.squareup.workflow1.WorkflowInterceptor.RenderContextInterceptor import com.squareup.workflow1.WorkflowInterceptor.WorkflowSession +import com.squareup.workflow1.compose.BaseComposeRenderContext +import com.squareup.workflow1.compose.ComposeWorkflowInterceptor +import com.squareup.workflow1.compose.ComposeWorkflowInterceptor.ComposeRenderContextInterceptor import com.squareup.workflow1.workflowIdentifier /** @@ -21,7 +23,7 @@ import com.squareup.workflow1.workflowIdentifier */ class PerformanceTracingInterceptor( private val sample: Boolean = false -) : WorkflowInterceptor, Resettable { +) : ComposeWorkflowInterceptor, Resettable { private var totalRenderPasses = 0 override fun onRender( @@ -42,9 +44,9 @@ class PerformanceTracingInterceptor( override fun Rendering( renderProps: P, renderState: S, - context: BaseRenderContext, + context: BaseComposeRenderContext, session: WorkflowSession, - proceed: @Composable (P, S, RenderContextInterceptor?) -> R + proceed: @Composable (P, S, ComposeRenderContextInterceptor?) -> R ): R { // TODO: Fix that these are illegal side effects in a Composable val traceIdIndex = remember(session) { diff --git a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/instrumentation/RenderPassCountingInterceptor.kt b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/instrumentation/RenderPassCountingInterceptor.kt index eb35b57fef..af4ed26e84 100644 --- a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/instrumentation/RenderPassCountingInterceptor.kt +++ b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/instrumentation/RenderPassCountingInterceptor.kt @@ -4,9 +4,11 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect import androidx.compose.runtime.remember import com.squareup.workflow1.BaseRenderContext -import com.squareup.workflow1.WorkflowInterceptor import com.squareup.workflow1.WorkflowInterceptor.RenderContextInterceptor import com.squareup.workflow1.WorkflowInterceptor.WorkflowSession +import com.squareup.workflow1.compose.BaseComposeRenderContext +import com.squareup.workflow1.compose.ComposeWorkflowInterceptor +import com.squareup.workflow1.compose.ComposeWorkflowInterceptor.ComposeRenderContextInterceptor /** * Used to count the number of render passes for a Workflow tree as well as each time that a node @@ -16,7 +18,7 @@ import com.squareup.workflow1.WorkflowInterceptor.WorkflowSession * This is convenient to use in integration tests that verify that the # of render passes and the * ratio of 'fresh' to 'stale' renderings for a scenario are constant. */ -class RenderPassCountingInterceptor : WorkflowInterceptor, Resettable { +class RenderPassCountingInterceptor : ComposeWorkflowInterceptor, Resettable { val renderEfficiencyTracking = RenderEfficiency() private var renderPassStats: RenderStats = RenderStats() private val nodeStates: MutableMap = mutableMapOf() @@ -38,10 +40,10 @@ class RenderPassCountingInterceptor : WorkflowInterceptor, Resettable { override fun Rendering( renderProps: P, renderState: S, - context: BaseRenderContext, + context: BaseComposeRenderContext, session: WorkflowSession, proceed: @Composable - (P, S, RenderContextInterceptor?) -> R + (P, S, ComposeRenderContextInterceptor?) -> R ): R { val isRoot = remember(session, renderState) { before(session, renderState) diff --git a/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/PoemListWorkflow.kt b/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/PoemListWorkflow.kt index 0f92a045e0..a9ab0f5f05 100644 --- a/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/PoemListWorkflow.kt +++ b/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/PoemListWorkflow.kt @@ -1,6 +1,5 @@ package com.squareup.sample.poetry -import androidx.compose.runtime.Composable import com.squareup.sample.poetry.PoemListWorkflow.Props import com.squareup.sample.poetry.model.Poem import com.squareup.workflow1.StatelessWorkflow @@ -26,12 +25,4 @@ object PoemListWorkflow : StatelessWorkflow() { ) { index -> setOutput(index) } ) } - - @Composable - override fun Rendering( - renderProps: Props, - context: RenderContext - ): PoemListScreen { - return render(renderProps, context) - } } diff --git a/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/RealPoemWorkflow.kt b/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/RealPoemWorkflow.kt index b82e6dfe66..11fb9ab692 100644 --- a/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/RealPoemWorkflow.kt +++ b/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/RealPoemWorkflow.kt @@ -1,6 +1,5 @@ package com.squareup.sample.poetry -import androidx.compose.runtime.Composable import com.squareup.sample.container.overviewdetail.OverviewDetailScreen import com.squareup.sample.poetry.PoemWorkflow.ClosePoem import com.squareup.sample.poetry.RealPoemWorkflow.Action.ClearSelection @@ -117,13 +116,4 @@ class RealPoemWorkflow : PoemWorkflow, } } } - - @Composable - override fun Rendering( - renderProps: Poem, - renderState: SelectedStanza, - context: RenderContext, - ): OverviewDetailScreen { - TODO("Not yet implemented") - } } diff --git a/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/RealPoemsBrowserWorkflow.kt b/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/RealPoemsBrowserWorkflow.kt index 54eef60e69..09b579868d 100644 --- a/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/RealPoemsBrowserWorkflow.kt +++ b/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/RealPoemsBrowserWorkflow.kt @@ -1,6 +1,5 @@ package com.squareup.sample.poetry -import androidx.compose.runtime.Composable import com.squareup.sample.container.overviewdetail.OverviewDetailScreen import com.squareup.sample.poetry.PoemListScreen.Companion.NO_POEM_SELECTED import com.squareup.sample.poetry.PoemListWorkflow.Props @@ -69,13 +68,4 @@ class RealPoemsBrowserWorkflow( } private val clearSelection = choosePoem(NO_POEM_SELECTED) - - @Composable - override fun Rendering( - renderProps: List, - renderState: SelectedPoem, - context: RenderContext, - ): OverviewDetailScreen { - TODO("Not yet implemented") - } } diff --git a/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/StanzaListWorkflow.kt b/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/StanzaListWorkflow.kt index ef7f7f69e4..a4a1bee382 100644 --- a/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/StanzaListWorkflow.kt +++ b/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/StanzaListWorkflow.kt @@ -1,6 +1,6 @@ package com.squareup.sample.poetry -import androidx.compose.runtime.Composable +import com.squareup.sample.poetry.StanzaListWorkflow.NO_SELECTED_STANZA import com.squareup.sample.poetry.StanzaListWorkflow.Props import com.squareup.sample.poetry.model.Poem import com.squareup.workflow1.StatelessWorkflow @@ -46,12 +46,4 @@ object StanzaListWorkflow : StatelessWorkflow() { ) } } - - @Composable - override fun Rendering( - renderProps: Props, - context: RenderContext - ): StanzaScreen { - return render(renderProps, context) - } } diff --git a/settings.gradle.kts b/settings.gradle.kts index 29143c826f..19d7d39a94 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -67,6 +67,7 @@ include( ":workflow-config:config-android", ":workflow-config:config-jvm", ":workflow-core", + ":workflow-core-compose", ":workflow-runtime", ":workflow-rx2", ":workflow-testing", diff --git a/workflow-config/config-android/dependencies/releaseRuntimeClasspath.txt b/workflow-config/config-android/dependencies/releaseRuntimeClasspath.txt index 60c456112a..30bf3f85e6 100644 --- a/workflow-config/config-android/dependencies/releaseRuntimeClasspath.txt +++ b/workflow-config/config-android/dependencies/releaseRuntimeClasspath.txt @@ -1,26 +1,12 @@ :workflow-core :workflow-runtime -androidx.annotation:annotation:1.1.0 -androidx.arch.core:core-common:2.1.0 -androidx.arch.core:core-runtime:2.1.0 -androidx.collection:collection:1.0.0 -androidx.compose.runtime:runtime:1.1.0 -androidx.core:core-ktx:1.2.0 -androidx.core:core:1.2.0 -androidx.lifecycle:lifecycle-common:2.4.0 -androidx.lifecycle:lifecycle-runtime:2.4.0 -androidx.versionedparcelable:versionedparcelable:1.1.0 -app.cash.molecule:molecule-runtime-android:0.3.0-SNAPSHOT -app.cash.molecule:molecule-runtime:0.3.0-SNAPSHOT com.squareup.okio:okio-jvm:3.0.0 com.squareup.okio:okio:3.0.0 -org.jetbrains.compose.runtime:runtime:1.1.0 org.jetbrains.kotlin:kotlin-bom:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-common:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.10 org.jetbrains.kotlin:kotlin-stdlib:1.6.10 -org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2 org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.2 org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2 org.jetbrains:annotations:13.0 diff --git a/workflow-config/config-jvm/dependencies/runtimeClasspath.txt b/workflow-config/config-jvm/dependencies/runtimeClasspath.txt index 8a28ebcd9e..30bf3f85e6 100644 --- a/workflow-config/config-jvm/dependencies/runtimeClasspath.txt +++ b/workflow-config/config-jvm/dependencies/runtimeClasspath.txt @@ -1,11 +1,7 @@ :workflow-core :workflow-runtime -app.cash.molecule:molecule-runtime-jvm:0.3.0-SNAPSHOT -app.cash.molecule:molecule-runtime:0.3.0-SNAPSHOT com.squareup.okio:okio-jvm:3.0.0 com.squareup.okio:okio:3.0.0 -org.jetbrains.compose.runtime:runtime-desktop:1.1.0 -org.jetbrains.compose.runtime:runtime:1.1.0 org.jetbrains.kotlin:kotlin-bom:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-common:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10 diff --git a/workflow-core-compose/README.md b/workflow-core-compose/README.md new file mode 100644 index 0000000000..81ef869adf --- /dev/null +++ b/workflow-core-compose/README.md @@ -0,0 +1,9 @@ +# Workflow Runtime with Compose Optimizations. + +This module contains extensions on the Workflow Core and Workflow Runtime classes that allow +for the Compose runtime to optimize which workflows are rendered in a render pass. + +This is entirely experimental and has no dedicated tests yet, so please do not use unless you +are experimenting. + +To use it you can pass the [ComposeRuntimePlugin] to [renderWorkflowIn]. diff --git a/workflow-core-compose/api/workflow-core-compose.api b/workflow-core-compose/api/workflow-core-compose.api new file mode 100644 index 0000000000..89e3bfa7d8 --- /dev/null +++ b/workflow-core-compose/api/workflow-core-compose.api @@ -0,0 +1,201 @@ +public abstract interface class com/squareup/workflow1/compose/BaseComposeRenderContext : com/squareup/workflow1/BaseRenderContext { + public abstract fun ChildRendering (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; +} + +public final class com/squareup/workflow1/compose/BaseComposeRenderContext$DefaultImpls { + public static fun eventHandler (Lcom/squareup/workflow1/compose/BaseComposeRenderContext;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function10;)Lkotlin/jvm/functions/Function9; + public static fun eventHandler (Lcom/squareup/workflow1/compose/BaseComposeRenderContext;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function11;)Lkotlin/jvm/functions/Function10; + public static fun eventHandler (Lcom/squareup/workflow1/compose/BaseComposeRenderContext;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;)Lkotlin/jvm/functions/Function0; + public static fun eventHandler (Lcom/squareup/workflow1/compose/BaseComposeRenderContext;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function2;)Lkotlin/jvm/functions/Function1; + public static fun eventHandler (Lcom/squareup/workflow1/compose/BaseComposeRenderContext;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function3;)Lkotlin/jvm/functions/Function2; + public static fun eventHandler (Lcom/squareup/workflow1/compose/BaseComposeRenderContext;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function4;)Lkotlin/jvm/functions/Function3; + public static fun eventHandler (Lcom/squareup/workflow1/compose/BaseComposeRenderContext;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function5;)Lkotlin/jvm/functions/Function4; + public static fun eventHandler (Lcom/squareup/workflow1/compose/BaseComposeRenderContext;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function6;)Lkotlin/jvm/functions/Function5; + public static fun eventHandler (Lcom/squareup/workflow1/compose/BaseComposeRenderContext;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function7;)Lkotlin/jvm/functions/Function6; + public static fun eventHandler (Lcom/squareup/workflow1/compose/BaseComposeRenderContext;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function8;)Lkotlin/jvm/functions/Function7; + public static fun eventHandler (Lcom/squareup/workflow1/compose/BaseComposeRenderContext;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function9;)Lkotlin/jvm/functions/Function8; +} + +public final class com/squareup/workflow1/compose/BaseComposeRenderContextKt { + public static final fun ChildRendering (Lcom/squareup/workflow1/compose/BaseComposeRenderContext;Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Landroidx/compose/runtime/Composer;II)Ljava/lang/Object; + public static final fun ChildRendering (Lcom/squareup/workflow1/compose/BaseComposeRenderContext;Lcom/squareup/workflow1/Workflow;Ljava/lang/String;Landroidx/compose/runtime/Composer;II)Ljava/lang/Object; + public static final fun ChildRendering (Lcom/squareup/workflow1/compose/BaseComposeRenderContext;Lcom/squareup/workflow1/Workflow;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)Ljava/lang/Object; +} + +public final class com/squareup/workflow1/compose/ChainedComposeWorkflowInterceptor : com/squareup/workflow1/ChainedWorkflowInterceptor, com/squareup/workflow1/compose/ComposeWorkflowInterceptor { + public static final field $stable I + public fun (Ljava/util/List;)V + public fun Rendering (Ljava/lang/Object;Ljava/lang/Object;Lcom/squareup/workflow1/compose/BaseComposeRenderContext;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lkotlin/jvm/functions/Function5;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; + public final fun wrap (Lcom/squareup/workflow1/compose/ComposeWorkflowInterceptor$ComposeRenderContextInterceptor;Lcom/squareup/workflow1/compose/ComposeWorkflowInterceptor$ComposeRenderContextInterceptor;)Lcom/squareup/workflow1/compose/ComposeWorkflowInterceptor$ComposeRenderContextInterceptor; +} + +public final class com/squareup/workflow1/compose/ChainedComposeWorkflowInterceptorKt { + public static final fun chained (Ljava/util/List;)Lcom/squareup/workflow1/compose/ComposeWorkflowInterceptor; +} + +public final class com/squareup/workflow1/compose/ComposeRuntimePlugin : com/squareup/workflow1/WorkflowRuntimePlugin { + public static final field $stable I + public static final field INSTANCE Lcom/squareup/workflow1/compose/ComposeRuntimePlugin; + public fun chainedInterceptors (Ljava/util/List;)Lcom/squareup/workflow1/WorkflowInterceptor; + public fun createWorkflowRunner (Lkotlinx/coroutines/CoroutineScope;Lcom/squareup/workflow1/Workflow;Lkotlinx/coroutines/flow/StateFlow;Lcom/squareup/workflow1/TreeSnapshot;Lcom/squareup/workflow1/WorkflowInterceptor;Lcom/squareup/workflow1/RuntimeConfig;)Lcom/squareup/workflow1/WorkflowRunner; + public fun initializeRenderingStream (Lcom/squareup/workflow1/WorkflowRunner;Lkotlinx/coroutines/CoroutineScope;)Lkotlinx/coroutines/flow/StateFlow; + public fun nextRendering (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/squareup/workflow1/compose/ComposeSubtreeManager : com/squareup/workflow1/SubtreeManager, com/squareup/workflow1/compose/RealComposeRenderContext$ComposeRenderer { + public static final field $stable I + public fun (Ljava/util/Map;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lcom/squareup/workflow1/compose/ComposeWorkflowInterceptor;Lcom/squareup/workflow1/IdCounter;)V + public synthetic fun (Ljava/util/Map;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lcom/squareup/workflow1/compose/ComposeWorkflowInterceptor;Lcom/squareup/workflow1/IdCounter;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun Rendering (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; + public synthetic fun createChildNode (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/WorkflowChildNode; + public synthetic fun getInterceptor ()Lcom/squareup/workflow1/WorkflowInterceptor; +} + +public abstract interface class com/squareup/workflow1/compose/ComposeWorkflowInterceptor : com/squareup/workflow1/WorkflowInterceptor { + public abstract fun Rendering (Ljava/lang/Object;Ljava/lang/Object;Lcom/squareup/workflow1/compose/BaseComposeRenderContext;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lkotlin/jvm/functions/Function5;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; +} + +public abstract interface class com/squareup/workflow1/compose/ComposeWorkflowInterceptor$ComposeRenderContextInterceptor : com/squareup/workflow1/WorkflowInterceptor$RenderContextInterceptor { + public abstract fun ChildRendering (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function6;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; +} + +public final class com/squareup/workflow1/compose/ComposeWorkflowInterceptor$ComposeRenderContextInterceptor$DefaultImpls { + public static fun ChildRendering (Lcom/squareup/workflow1/compose/ComposeWorkflowInterceptor$ComposeRenderContextInterceptor;Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function6;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; + public static fun onActionSent (Lcom/squareup/workflow1/compose/ComposeWorkflowInterceptor$ComposeRenderContextInterceptor;Lcom/squareup/workflow1/WorkflowAction;Lkotlin/jvm/functions/Function1;)V + public static fun onRenderChild (Lcom/squareup/workflow1/compose/ComposeWorkflowInterceptor$ComposeRenderContextInterceptor;Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function4;)Ljava/lang/Object; + public static fun onRunningSideEffect (Lcom/squareup/workflow1/compose/ComposeWorkflowInterceptor$ComposeRenderContextInterceptor;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)V +} + +public final class com/squareup/workflow1/compose/ComposeWorkflowInterceptor$DefaultImpls { + public static fun Rendering (Lcom/squareup/workflow1/compose/ComposeWorkflowInterceptor;Ljava/lang/Object;Ljava/lang/Object;Lcom/squareup/workflow1/compose/BaseComposeRenderContext;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lkotlin/jvm/functions/Function5;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; + public static fun onInitialState (Lcom/squareup/workflow1/compose/ComposeWorkflowInterceptor;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/compose/ComposeWorkflowInterceptor;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/compose/ComposeWorkflowInterceptor;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 onSessionStarted (Lcom/squareup/workflow1/compose/ComposeWorkflowInterceptor;Lkotlinx/coroutines/CoroutineScope;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)V + public static fun onSnapshotState (Lcom/squareup/workflow1/compose/ComposeWorkflowInterceptor;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Lcom/squareup/workflow1/Snapshot; +} + +public final class com/squareup/workflow1/compose/ComposeWorkflowInterceptorKt { + public static final fun asComposeWorkflowInterceptor (Lcom/squareup/workflow1/WorkflowInterceptor;)Lcom/squareup/workflow1/compose/ComposeWorkflowInterceptor; + public static final fun intercept (Lcom/squareup/workflow1/compose/ComposeWorkflowInterceptor;Lcom/squareup/workflow1/compose/StatefulComposeWorkflow;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Lcom/squareup/workflow1/compose/StatefulComposeWorkflow; +} + +public class com/squareup/workflow1/compose/InterceptedComposeRenderContext : com/squareup/workflow1/InterceptedRenderContext, com/squareup/workflow1/Sink, com/squareup/workflow1/compose/BaseComposeRenderContext { + public static final field $stable I + public fun (Lcom/squareup/workflow1/compose/BaseComposeRenderContext;Lcom/squareup/workflow1/compose/ComposeWorkflowInterceptor$ComposeRenderContextInterceptor;)V + public fun ChildRendering (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; +} + +public final class com/squareup/workflow1/compose/NoopComposeWorkflowInterceptor : com/squareup/workflow1/compose/ComposeWorkflowInterceptor { + public static final field $stable I + public static final field INSTANCE Lcom/squareup/workflow1/compose/NoopComposeWorkflowInterceptor; + public fun Rendering (Ljava/lang/Object;Ljava/lang/Object;Lcom/squareup/workflow1/compose/BaseComposeRenderContext;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lkotlin/jvm/functions/Function5;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; + 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 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 final class com/squareup/workflow1/compose/RealComposeRenderContext : com/squareup/workflow1/RealRenderContext, com/squareup/workflow1/compose/BaseComposeRenderContext { + public static final field $stable I + public fun (Lcom/squareup/workflow1/compose/RealComposeRenderContext$ComposeRenderer;Lcom/squareup/workflow1/RealRenderContext$SideEffectRunner;Lkotlinx/coroutines/channels/SendChannel;)V + public fun ChildRendering (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; + public synthetic fun getRenderer ()Lcom/squareup/workflow1/RealRenderContext$Renderer; +} + +public abstract interface class com/squareup/workflow1/compose/RealComposeRenderContext$ComposeRenderer : com/squareup/workflow1/RealRenderContext$Renderer { + public abstract fun Rendering (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; +} + +public abstract class com/squareup/workflow1/compose/StatefulComposeWorkflow : com/squareup/workflow1/StatefulWorkflow { + public static final field $stable I + public fun ()V + public abstract fun Rendering (Ljava/lang/Object;Ljava/lang/Object;Lcom/squareup/workflow1/compose/StatefulComposeWorkflow$RenderContext;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; +} + +public final class com/squareup/workflow1/compose/StatefulComposeWorkflow$RenderContext : com/squareup/workflow1/StatefulWorkflow$RenderContext, com/squareup/workflow1/compose/BaseComposeRenderContext { + public fun (Lcom/squareup/workflow1/compose/StatefulComposeWorkflow;Lcom/squareup/workflow1/compose/BaseComposeRenderContext;)V + public fun ChildRendering (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; + public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function10;)Lkotlin/jvm/functions/Function9; + public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function11;)Lkotlin/jvm/functions/Function10; + public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;)Lkotlin/jvm/functions/Function0; + public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function2;)Lkotlin/jvm/functions/Function1; + public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function3;)Lkotlin/jvm/functions/Function2; + public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function4;)Lkotlin/jvm/functions/Function3; + public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function5;)Lkotlin/jvm/functions/Function4; + public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function6;)Lkotlin/jvm/functions/Function5; + public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function7;)Lkotlin/jvm/functions/Function6; + public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function8;)Lkotlin/jvm/functions/Function7; + public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function9;)Lkotlin/jvm/functions/Function8; + public fun getActionSink ()Lcom/squareup/workflow1/Sink; + public fun renderChild (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; + public fun runningSideEffect (Ljava/lang/String;Lkotlin/jvm/functions/Function2;)V +} + +public final class com/squareup/workflow1/compose/StatefulComposeWorkflowKt { + public static final fun ComposeRenderContext (Lcom/squareup/workflow1/compose/BaseComposeRenderContext;Lcom/squareup/workflow1/compose/StatefulComposeWorkflow;)Lcom/squareup/workflow1/compose/StatefulComposeWorkflow$RenderContext; + public static final fun asComposeWorkflow (Lcom/squareup/workflow1/StatefulWorkflow;Lkotlin/jvm/functions/Function6;)Lcom/squareup/workflow1/compose/StatefulComposeWorkflow; + public static synthetic fun asComposeWorkflow$default (Lcom/squareup/workflow1/StatefulWorkflow;Lkotlin/jvm/functions/Function6;ILjava/lang/Object;)Lcom/squareup/workflow1/compose/StatefulComposeWorkflow; + public static final fun composedStateful (Lcom/squareup/workflow1/Workflow$Companion;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function4;)Lcom/squareup/workflow1/StatefulWorkflow; + public static final fun composedStateful (Lcom/squareup/workflow1/Workflow$Companion;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/StatefulWorkflow; + public static final fun composedStateful (Lcom/squareup/workflow1/Workflow$Companion;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function5;Lkotlin/jvm/functions/Function3;)Lcom/squareup/workflow1/StatefulWorkflow; + public static final fun composedStateful (Lcom/squareup/workflow1/Workflow$Companion;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function5;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;)Lcom/squareup/workflow1/StatefulWorkflow; + public static synthetic fun composedStateful$default (Lcom/squareup/workflow1/Workflow$Companion;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function5;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lcom/squareup/workflow1/StatefulWorkflow; + public static synthetic fun composedStateful$default (Lcom/squareup/workflow1/Workflow$Companion;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function5;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lcom/squareup/workflow1/StatefulWorkflow; +} + +public abstract class com/squareup/workflow1/compose/StatelessComposeWorkflow : com/squareup/workflow1/StatelessWorkflow { + public static final field $stable I + public fun ()V + public abstract fun Rendering (Ljava/lang/Object;Lcom/squareup/workflow1/compose/StatelessComposeWorkflow$RenderContext;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; + protected fun getStatefulWorkflow ()Lcom/squareup/workflow1/StatefulWorkflow; +} + +public final class com/squareup/workflow1/compose/StatelessComposeWorkflow$RenderContext : com/squareup/workflow1/StatelessWorkflow$RenderContext, com/squareup/workflow1/compose/BaseComposeRenderContext { + public fun (Lcom/squareup/workflow1/compose/StatelessComposeWorkflow;Lcom/squareup/workflow1/compose/BaseComposeRenderContext;)V + public fun ChildRendering (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; + public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function10;)Lkotlin/jvm/functions/Function9; + public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function11;)Lkotlin/jvm/functions/Function10; + public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;)Lkotlin/jvm/functions/Function0; + public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function2;)Lkotlin/jvm/functions/Function1; + public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function3;)Lkotlin/jvm/functions/Function2; + public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function4;)Lkotlin/jvm/functions/Function3; + public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function5;)Lkotlin/jvm/functions/Function4; + public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function6;)Lkotlin/jvm/functions/Function5; + public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function7;)Lkotlin/jvm/functions/Function6; + public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function8;)Lkotlin/jvm/functions/Function7; + public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function9;)Lkotlin/jvm/functions/Function8; + public fun getActionSink ()Lcom/squareup/workflow1/Sink; + public fun renderChild (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; + public fun runningSideEffect (Ljava/lang/String;Lkotlin/jvm/functions/Function2;)V +} + +public final class com/squareup/workflow1/compose/StatelessComposeWorkflowKt { + public static final fun ComposeRenderContext (Lcom/squareup/workflow1/compose/BaseComposeRenderContext;Lcom/squareup/workflow1/compose/StatelessComposeWorkflow;)Lcom/squareup/workflow1/compose/StatelessComposeWorkflow$RenderContext; + public static final fun asComposeWorkflow (Lcom/squareup/workflow1/StatelessWorkflow;Lkotlin/jvm/functions/Function5;)Lcom/squareup/workflow1/compose/StatelessComposeWorkflow; + public static synthetic fun asComposeWorkflow$default (Lcom/squareup/workflow1/StatelessWorkflow;Lkotlin/jvm/functions/Function5;ILjava/lang/Object;)Lcom/squareup/workflow1/compose/StatelessComposeWorkflow; + public static final fun composedStateless (Lcom/squareup/workflow1/Workflow$Companion;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function2;)Lcom/squareup/workflow1/Workflow; +} + +public final class com/squareup/workflow1/compose/WorkflowComposeChildNode : com/squareup/workflow1/WorkflowChildNode { + public static final field $stable I + public fun (Lcom/squareup/workflow1/Workflow;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/WorkflowNode;)V + public final fun Rendering (Lcom/squareup/workflow1/compose/StatefulComposeWorkflow;Ljava/lang/Object;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; +} + +public final class com/squareup/workflow1/compose/WorkflowComposeNode : com/squareup/workflow1/WorkflowNode { + public static final field $stable I + public fun (Lcom/squareup/workflow1/WorkflowNodeId;Lcom/squareup/workflow1/compose/StatefulComposeWorkflow;Ljava/lang/Object;Lcom/squareup/workflow1/TreeSnapshot;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lcom/squareup/workflow1/compose/ComposeWorkflowInterceptor;Lcom/squareup/workflow1/IdCounter;)V + public synthetic fun (Lcom/squareup/workflow1/WorkflowNodeId;Lcom/squareup/workflow1/compose/StatefulComposeWorkflow;Ljava/lang/Object;Lcom/squareup/workflow1/TreeSnapshot;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lcom/squareup/workflow1/compose/ComposeWorkflowInterceptor;Lcom/squareup/workflow1/IdCounter;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun Rendering (Lcom/squareup/workflow1/compose/StatefulComposeWorkflow;Ljava/lang/Object;Landroidx/compose/runtime/MutableState;Landroidx/compose/runtime/Composer;I)V + public synthetic fun getSubtreeManager ()Lcom/squareup/workflow1/SubtreeManager; +} + +public final class com/squareup/workflow1/compose/WorkflowComposeRunner : com/squareup/workflow1/WorkflowRunner { + public static final field $stable I + public fun (Lkotlinx/coroutines/CoroutineScope;Lcom/squareup/workflow1/Workflow;Lkotlinx/coroutines/flow/StateFlow;Lcom/squareup/workflow1/TreeSnapshot;Lcom/squareup/workflow1/compose/ComposeWorkflowInterceptor;Lcom/squareup/workflow1/RuntimeConfig;)V + public synthetic fun getRootNode ()Lcom/squareup/workflow1/WorkflowNode; + public final fun nextComposedRendering (Landroidx/compose/runtime/Composer;I)Lcom/squareup/workflow1/RenderingAndSnapshot; +} + diff --git a/workflow-core-compose/build.gradle.kts b/workflow-core-compose/build.gradle.kts new file mode 100644 index 0000000000..06c6c19e54 --- /dev/null +++ b/workflow-core-compose/build.gradle.kts @@ -0,0 +1,36 @@ +plugins { + `kotlin-multiplatform` + published + id("app.cash.molecule") +} + +kotlin { + jvm { withJava() } + // TODO: No native targets yet for Molecule until Compose 1.2.0 available in JB KMP runtime. + // ios() + + sourceSets { + all { + languageSettings.apply { + optIn("kotlin.RequiresOptIn") + } + } + val commonMain by getting { + dependencies { + api(project(":workflow-core")) + api(project(":workflow-runtime")) + api(libs.kotlin.jdk6) + api(libs.compose.runtime) + api(libs.kotlinx.coroutines.core) + implementation(libs.molecule.runtime) + } + } + val commonTest by getting { + dependencies { + implementation(libs.kotlinx.atomicfu) + implementation(libs.kotlinx.coroutines.test.common) + implementation(libs.kotlin.test.jdk) + } + } + } +} diff --git a/workflow-core-compose/dependencies/jvmRuntimeClasspath.txt b/workflow-core-compose/dependencies/jvmRuntimeClasspath.txt new file mode 100644 index 0000000000..82c4153cf1 --- /dev/null +++ b/workflow-core-compose/dependencies/jvmRuntimeClasspath.txt @@ -0,0 +1,15 @@ +:workflow-core +:workflow-runtime +app.cash.molecule:molecule-runtime-jvm:0.3.0-SNAPSHOT +app.cash.molecule:molecule-runtime:0.3.0-SNAPSHOT +com.squareup.okio:okio-jvm:3.0.0 +com.squareup.okio:okio:3.0.0 +org.jetbrains.compose.runtime:runtime-desktop:1.1.0 +org.jetbrains.compose.runtime:runtime:1.1.0 +org.jetbrains.kotlin:kotlin-bom:1.6.10 +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10 +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.10 +org.jetbrains.kotlin:kotlin-stdlib:1.6.10 +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.2 +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2 +org.jetbrains:annotations:13.0 diff --git a/workflow-core-compose/dependencies/runtimeClasspath.txt b/workflow-core-compose/dependencies/runtimeClasspath.txt new file mode 100644 index 0000000000..8a28ebcd9e --- /dev/null +++ b/workflow-core-compose/dependencies/runtimeClasspath.txt @@ -0,0 +1,16 @@ +:workflow-core +:workflow-runtime +app.cash.molecule:molecule-runtime-jvm:0.3.0-SNAPSHOT +app.cash.molecule:molecule-runtime:0.3.0-SNAPSHOT +com.squareup.okio:okio-jvm:3.0.0 +com.squareup.okio:okio:3.0.0 +org.jetbrains.compose.runtime:runtime-desktop:1.1.0 +org.jetbrains.compose.runtime:runtime:1.1.0 +org.jetbrains.kotlin:kotlin-bom:1.6.10 +org.jetbrains.kotlin:kotlin-stdlib-common:1.6.10 +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10 +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.10 +org.jetbrains.kotlin:kotlin-stdlib:1.6.10 +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.2 +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2 +org.jetbrains:annotations:13.0 diff --git a/workflow-core-compose/gradle.properties b/workflow-core-compose/gradle.properties new file mode 100644 index 0000000000..e79c8dfe30 --- /dev/null +++ b/workflow-core-compose/gradle.properties @@ -0,0 +1,3 @@ +POM_ARTIFACT_ID=workflow-core-compose +POM_NAME=Workflow Core Compose +POM_PACKAGING=jar diff --git a/workflow-core-compose/src/commonMain/kotlin/com/squareup/workflow1/compose/BaseComposeRenderContext.kt b/workflow-core-compose/src/commonMain/kotlin/com/squareup/workflow1/compose/BaseComposeRenderContext.kt new file mode 100644 index 0000000000..d6baed7110 --- /dev/null +++ b/workflow-core-compose/src/commonMain/kotlin/com/squareup/workflow1/compose/BaseComposeRenderContext.kt @@ -0,0 +1,57 @@ +package com.squareup.workflow1.compose + +import androidx.compose.runtime.Composable +import com.squareup.workflow1.BaseRenderContext +import com.squareup.workflow1.Workflow +import com.squareup.workflow1.WorkflowAction +import com.squareup.workflow1.WorkflowAction.Companion.noAction + +/** + * @see [BaseRenderContext]. This is the version which adds support for the Compose optimized + * runtime. + */ +public interface BaseComposeRenderContext : + BaseRenderContext { + + /** + * @see [BaseRenderContext.renderChild] as this is equivalent, except as a Composable. + */ + @Composable + public fun ChildRendering( + child: Workflow, + props: ChildPropsT, + key: String, + handler: (ChildOutputT) -> WorkflowAction + ): ChildRenderingT +} + +/** + * Convenience alias of [BaseComposeRenderContext.ChildRendering] for workflows that don't take props. + */ +@Composable +public fun +BaseComposeRenderContext.ChildRendering( + child: Workflow, + key: String = "", + handler: (ChildOutputT) -> WorkflowAction +): ChildRenderingT = ChildRendering(child, Unit, key, handler) +/** + * Convenience alias of [BaseComposeRenderContext.ChildRendering] for workflows that don't emit output. + */ +@Composable +public fun +BaseComposeRenderContext.ChildRendering( + child: Workflow, + props: ChildPropsT, + key: String = "", +): ChildRenderingT = ChildRendering(child, props, key) { noAction() } +/** + * Convenience alias of [BaseComposeRenderContext.ChildRendering] for children that don't take props or emit + * output. + */ +@Composable +public fun +BaseComposeRenderContext.ChildRendering( + child: Workflow, + key: String = "", +): ChildRenderingT = ChildRendering(child, Unit, key) { noAction() } diff --git a/workflow-core-compose/src/commonMain/kotlin/com/squareup/workflow1/compose/ChainedComposeWorkflowInterceptor.kt b/workflow-core-compose/src/commonMain/kotlin/com/squareup/workflow1/compose/ChainedComposeWorkflowInterceptor.kt new file mode 100644 index 0000000000..4c20091645 --- /dev/null +++ b/workflow-core-compose/src/commonMain/kotlin/com/squareup/workflow1/compose/ChainedComposeWorkflowInterceptor.kt @@ -0,0 +1,124 @@ +package com.squareup.workflow1.compose + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import com.squareup.workflow1.ChainedWorkflowInterceptor +import com.squareup.workflow1.Workflow +import com.squareup.workflow1.WorkflowAction +import com.squareup.workflow1.WorkflowInterceptor.RenderContextInterceptor +import com.squareup.workflow1.WorkflowInterceptor.WorkflowSession +import com.squareup.workflow1.compose.ComposeWorkflowInterceptor.ComposeRenderContextInterceptor + +internal fun List.chained(): ComposeWorkflowInterceptor = + when { + isEmpty() -> NoopComposeWorkflowInterceptor + size == 1 -> single() + else -> ChainedComposeWorkflowInterceptor(this) + } + +public class ChainedComposeWorkflowInterceptor( + override val interceptors: List +) : ChainedWorkflowInterceptor(interceptors), ComposeWorkflowInterceptor { + + @Composable + public override fun Rendering( + renderProps: P, + renderState: S, + context: BaseComposeRenderContext, + session: WorkflowSession, + proceed: @Composable (P, S, ComposeRenderContextInterceptor?) -> R + ): R { + val chainedProceed = remember(session) { + interceptors.foldRight(proceed) { workflowInterceptor, proceedAcc -> + { props, state, outerContextInterceptor -> + // Holding compiler's hand for function type. + val proceedInternal = + remember<@Composable (P, S, ComposeRenderContextInterceptor?) -> R>( + outerContextInterceptor + ) { + @Composable { p: P, + s: S, + innerContextInterceptor: ComposeRenderContextInterceptor? -> + val contextInterceptor = remember(innerContextInterceptor) { + outerContextInterceptor.wrap(innerContextInterceptor) + } + proceedAcc(p, s, contextInterceptor) + } + } + workflowInterceptor.Rendering( + props, + state, + context, + proceed = proceedInternal, + session = session, + ) + } + } + } + return chainedProceed(renderProps, renderState, null) + } + + public fun ComposeRenderContextInterceptor?.wrap( + inner: ComposeRenderContextInterceptor? + ): ComposeRenderContextInterceptor? = when { + this == null && inner == null -> null + this == null -> inner + inner == null -> this + else -> { + // Share the base implementation. + val regularRenderContextInterceptor = (this as RenderContextInterceptor).wrap(inner) + object : ComposeRenderContextInterceptor { + // If we don't use !!, the compiler complains about the non-elvis dot accesses below. + @Suppress("UNNECESSARY_NOT_NULL_ASSERTION") + val outer = this@wrap!! + + override fun onActionSent( + action: WorkflowAction, + proceed: (WorkflowAction) -> Unit + ) = regularRenderContextInterceptor!!.onActionSent(action, proceed) + + override fun onRenderChild( + child: Workflow, + childProps: CP, + key: String, + handler: (CO) -> WorkflowAction, + proceed: ( + child: Workflow, + props: CP, + key: String, + handler: (CO) -> WorkflowAction + ) -> CR + ): CR = + regularRenderContextInterceptor!!.onRenderChild(child, childProps, key, handler, proceed) + + @Composable + override fun ChildRendering( + child: Workflow, + childProps: CP, + key: String, + handler: (CO) -> WorkflowAction, + proceed: @Composable ( + child: Workflow, + childProps: CP, + key: String, + handler: (CO) -> WorkflowAction + ) -> CR + ): CR = + outer.ChildRendering( + child, + childProps, + key, + handler + ) @Composable { c, p, k, h -> + inner.ChildRendering(c, p, k, h, proceed) + } + + override fun onRunningSideEffect( + key: String, + sideEffect: suspend () -> Unit, + proceed: (key: String, sideEffect: suspend () -> Unit) -> Unit + ) = regularRenderContextInterceptor!!.onRunningSideEffect(key, sideEffect, proceed) + } + } + } +} diff --git a/workflow-core-compose/src/commonMain/kotlin/com/squareup/workflow1/compose/ComposeRuntimePlugin.kt b/workflow-core-compose/src/commonMain/kotlin/com/squareup/workflow1/compose/ComposeRuntimePlugin.kt new file mode 100644 index 0000000000..6eaa99440b --- /dev/null +++ b/workflow-core-compose/src/commonMain/kotlin/com/squareup/workflow1/compose/ComposeRuntimePlugin.kt @@ -0,0 +1,72 @@ +package com.squareup.workflow1.compose + +import androidx.compose.runtime.BroadcastFrameClock +import app.cash.molecule.launchMolecule +import com.squareup.workflow1.RenderingAndSnapshot +import com.squareup.workflow1.RuntimeConfig +import com.squareup.workflow1.TreeSnapshot +import com.squareup.workflow1.Workflow +import com.squareup.workflow1.WorkflowExperimentalRuntime +import com.squareup.workflow1.WorkflowInterceptor +import com.squareup.workflow1.WorkflowRunner +import com.squareup.workflow1.WorkflowRuntimePlugin +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.plus +import kotlinx.coroutines.yield + +/** + * [WorkflowRuntimePlugin] implementation that adds in a Compose optimized runtime. This will + * attempt to prevent any unnecessary renderings when the state (tracked using Compose) has + * not changed. + * + * Use [StatefulComposeWorkflow] and [StatelessComposeWorkflow] to take advantage of these + * runtime optimizations if your [Workflow] is not a leaf in the tree. Leaf workflows will be + * converted and handled automatically. + */ +@WorkflowExperimentalRuntime +public object ComposeRuntimePlugin : WorkflowRuntimePlugin { + + private var composeWaitingForFrame = false + private val composeRuntimeClock = BroadcastFrameClock { + composeWaitingForFrame = true + } + + override fun createWorkflowRunner( + scope: CoroutineScope, + protoWorkflow: Workflow, + props: StateFlow, + snapshot: TreeSnapshot?, + interceptor: WorkflowInterceptor, + runtimeConfig: RuntimeConfig + ): WorkflowRunner = WorkflowComposeRunner( + scope, + protoWorkflow, + props, + snapshot, + interceptor.asComposeWorkflowInterceptor(), + runtimeConfig, + ) + + override fun initializeRenderingStream( + workflowRunner: WorkflowRunner, + runtimeScope: CoroutineScope + ): StateFlow> { + val clockedScope = runtimeScope + composeRuntimeClock + + return clockedScope.launchMolecule { + (workflowRunner as WorkflowComposeRunner).nextComposedRendering() + } + } + + override suspend fun nextRendering() { + if (composeWaitingForFrame) { + composeWaitingForFrame = false + composeRuntimeClock.sendFrame(0L) + yield() + } + } + + override fun chainedInterceptors(interceptors: List): WorkflowInterceptor = + interceptors.map { it.asComposeWorkflowInterceptor() }.chained() +} diff --git a/workflow-core-compose/src/commonMain/kotlin/com/squareup/workflow1/compose/ComposeSubtreeManager.kt b/workflow-core-compose/src/commonMain/kotlin/com/squareup/workflow1/compose/ComposeSubtreeManager.kt new file mode 100644 index 0000000000..fd37be41b8 --- /dev/null +++ b/workflow-core-compose/src/commonMain/kotlin/com/squareup/workflow1/compose/ComposeSubtreeManager.kt @@ -0,0 +1,124 @@ +package com.squareup.workflow1.compose + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import com.squareup.workflow1.IdCounter +import com.squareup.workflow1.SubtreeManager +import com.squareup.workflow1.TreeSnapshot +import com.squareup.workflow1.Workflow +import com.squareup.workflow1.WorkflowAction +import com.squareup.workflow1.WorkflowExperimentalRuntime +import com.squareup.workflow1.WorkflowInterceptor.WorkflowSession +import com.squareup.workflow1.WorkflowNodeId +import com.squareup.workflow1.id +import com.squareup.workflow1.identifier +import kotlin.coroutines.CoroutineContext + +/** + * @see [SubtreeManager]. This is the version which adds support for the Compose optimized runtime. + */ +@WorkflowExperimentalRuntime +public class ComposeSubtreeManager( + snapshotCache: Map?, + contextForChildren: CoroutineContext, + emitActionToParent: (WorkflowAction) -> Any?, + workflowSession: WorkflowSession? = null, + override val interceptor: ComposeWorkflowInterceptor = NoopComposeWorkflowInterceptor, + idCounter: IdCounter? = null +) : SubtreeManager( + snapshotCache, + contextForChildren, + emitActionToParent, + workflowSession, + interceptor, + idCounter +), + RealComposeRenderContext.ComposeRenderer { + + @Composable + override fun Rendering( + child: Workflow, + props: ChildPropsT, + key: String, + handler: (ChildOutputT) -> WorkflowAction + ): ChildRenderingT { + val stagedChild = + StagedChild( + child, + props, + key, + handler + ) + val statefulChild = remember(child) { child.asStatefulWorkflow().asComposeWorkflow() } + return stagedChild.Rendering(statefulChild, props) + } + + /** + * Prepare the staged child while only modifying [children] in a SideEffect. This will ensure + * that we do not inappropriately modify non-snapshot state. + */ + @Composable + private fun StagedChild( + child: Workflow, + props: ChildPropsT, + key: String, + handler: (ChildOutputT) -> WorkflowAction + ): WorkflowComposeChildNode<*, *, *, *, *> { + val childState = remember(child, key, props, handler) { + children.forEachStaging { + require(!(it.matches(child, key))) { + "Expected keys to be unique for ${child.identifier}: key=\"$key\"" + } + } + mutableStateOf( + children.firstActiveOrNull { + it.matches(child, key) + } ?: createChildNode(child, props, key, handler) + ) + } + + SideEffect { + // Modify the [children] lists in a side-effect when composition is committed. + children.removeAndStage( + predicate = { it.matches(child, key) }, + child = childState.value + ) + } + return childState.value as WorkflowComposeChildNode<*, *, *, *, *> + } + + override fun createChildNode( + child: Workflow, + initialProps: ChildPropsT, + key: String, + handler: (ChildOutputT) -> WorkflowAction + ): WorkflowComposeChildNode { + val id = child.id(key) + lateinit var node: WorkflowComposeChildNode + + fun acceptChildOutput(output: ChildOutputT): Any? { + val action = node.acceptChildOutput(output) + return emitActionToParent(action) + } + + val childTreeSnapshots = snapshotCache?.get(id) + + val workflowNode = WorkflowComposeNode( + id = id, + child.asStatefulWorkflow().asComposeWorkflow(), + initialProps, + childTreeSnapshots, + contextForChildren, + ::acceptChildOutput, + workflowSession, + interceptor, + idCounter = idCounter + ).apply { + startSession() + } + return WorkflowComposeChildNode(child, handler, workflowNode) + .also { node = it } + } +} diff --git a/workflow-core-compose/src/commonMain/kotlin/com/squareup/workflow1/compose/ComposeWorkflowInterceptor.kt b/workflow-core-compose/src/commonMain/kotlin/com/squareup/workflow1/compose/ComposeWorkflowInterceptor.kt new file mode 100644 index 0000000000..b8f61806da --- /dev/null +++ b/workflow-core-compose/src/commonMain/kotlin/com/squareup/workflow1/compose/ComposeWorkflowInterceptor.kt @@ -0,0 +1,168 @@ +package com.squareup.workflow1.compose + +import androidx.compose.runtime.Composable +import com.squareup.workflow1.BaseRenderContext +import com.squareup.workflow1.InterceptedRenderContext +import com.squareup.workflow1.Sink +import com.squareup.workflow1.Snapshot +import com.squareup.workflow1.Workflow +import com.squareup.workflow1.WorkflowAction +import com.squareup.workflow1.WorkflowExperimentalRuntime +import com.squareup.workflow1.WorkflowInterceptor +import com.squareup.workflow1.WorkflowInterceptor.RenderContextInterceptor +import com.squareup.workflow1.WorkflowInterceptor.WorkflowSession +import com.squareup.workflow1.compose.ComposeWorkflowInterceptor.ComposeRenderContextInterceptor +import com.squareup.workflow1.intercept +import kotlinx.coroutines.CoroutineScope + +/** + * Provides hooks into the workflow runtime when it is using the Compose optimizations. + * It can be used to instrument or modify the behavior of workflows. + * + * @see [WorkflowInterceptor] for full documentation. + */ +public interface ComposeWorkflowInterceptor : WorkflowInterceptor { + + @Composable + public fun Rendering( + renderProps: P, + renderState: S, + context: BaseComposeRenderContext, + session: WorkflowSession, + proceed: @Composable (P, S, ComposeRenderContextInterceptor?) -> R + ): R = proceed(renderProps, renderState, null) + + /** + * Intercepts calls to [BaseComposeRenderContext.ChildRendering], allowing the + * interceptor to wrap or replace the [child] Workflow, its [childProps], + * [key], and the [handler] function to be applied to the child's output. + * + * @see [RenderContextInterceptor] + */ + public interface ComposeRenderContextInterceptor : RenderContextInterceptor { + @Composable + public fun ChildRendering( + child: Workflow, + childProps: CP, + key: String, + handler: (CO) -> WorkflowAction, + proceed: @Composable ( + child: Workflow, + childProps: CP, + key: String, + handler: (CO) -> WorkflowAction + ) -> CR + ): CR = proceed(child, childProps, key, handler) + } +} + +public fun WorkflowInterceptor.asComposeWorkflowInterceptor(): ComposeWorkflowInterceptor { + val originalInterceptor = this + if (originalInterceptor is ComposeWorkflowInterceptor) { + return originalInterceptor + } + return object : ComposeWorkflowInterceptor { + + override fun onSessionStarted( + workflowScope: CoroutineScope, + session: WorkflowSession + ) = originalInterceptor.onSessionStarted(workflowScope, session) + + override fun onInitialState( + props: P, + snapshot: Snapshot?, + proceed: (P, Snapshot?) -> S, + session: WorkflowSession + ): S = originalInterceptor.onInitialState(props, snapshot, proceed, session) + + override fun onPropsChanged( + old: P, + new: P, + state: S, + proceed: (P, P, S) -> S, + session: WorkflowSession + ): S = originalInterceptor.onPropsChanged(old, new, state, proceed, session) + + override fun onRender( + renderProps: P, + renderState: S, + context: BaseRenderContext, + proceed: (P, S, RenderContextInterceptor?) -> R, + session: WorkflowSession + ): R = originalInterceptor.onRender(renderProps, renderState, context, proceed, session) + + override fun onSnapshotState( + state: S, + proceed: (S) -> Snapshot?, + session: WorkflowSession + ): Snapshot? = originalInterceptor.onSnapshotState(state, proceed, session) + } +} + +/** A [ComposeWorkflowInterceptor] that does not intercept anything. */ +public object NoopComposeWorkflowInterceptor : ComposeWorkflowInterceptor + +/** + * Returns a [StatefulComposeWorkflow] that will intercept all calls to [workflow] via this + * [ComposeWorkflowInterceptor]. + */ +@WorkflowExperimentalRuntime +public fun ComposeWorkflowInterceptor.intercept( + workflow: StatefulComposeWorkflow, + workflowSession: WorkflowSession +): StatefulComposeWorkflow = if (this === NoopComposeWorkflowInterceptor) { + workflow +} else { + (this as WorkflowInterceptor).intercept(workflow, workflowSession).asComposeWorkflow( + RenderingImpl = { renderProps, renderState, context -> + // Cannot annotate anonymous functions with @Composable and cannot infer type of + // this when a lambda. So need this variable to make it explicit. + val reifiedProceed: @Composable (P, S, ComposeRenderContextInterceptor?) -> R = + @Composable { props: P, + state: S, + interceptor: ComposeRenderContextInterceptor? -> + val interceptedContext = interceptor?.let { InterceptedComposeRenderContext(context, it) } + ?: context + val renderContext = ComposeRenderContext(interceptedContext, this) + workflow.Rendering( + props, + state, + renderContext + ) + } + Rendering( + renderProps = renderProps, + renderState = renderState, + context = context, + session = workflowSession, + proceed = reifiedProceed + ) + } + ) +} + +public open class InterceptedComposeRenderContext( + private val baseRenderContext: BaseComposeRenderContext, + private val interceptor: ComposeRenderContextInterceptor +) : BaseComposeRenderContext, Sink>, + InterceptedRenderContext( + baseRenderContext, + interceptor + ) { + + @Composable + override fun ChildRendering( + child: Workflow, + props: ChildPropsT, + key: String, + handler: (ChildOutputT) -> WorkflowAction + ): ChildRenderingT = + interceptor.ChildRendering( + child, + props, + key, + handler + ) @Composable { iChild, iProps, iKey, iHandler -> + baseRenderContext.ChildRendering(iChild, iProps, iKey, iHandler) + } +} diff --git a/workflow-core-compose/src/commonMain/kotlin/com/squareup/workflow1/compose/RealComposeRenderContext.kt b/workflow-core-compose/src/commonMain/kotlin/com/squareup/workflow1/compose/RealComposeRenderContext.kt new file mode 100644 index 0000000000..55aa1ecac8 --- /dev/null +++ b/workflow-core-compose/src/commonMain/kotlin/com/squareup/workflow1/compose/RealComposeRenderContext.kt @@ -0,0 +1,46 @@ +package com.squareup.workflow1.compose + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import com.squareup.workflow1.RealRenderContext +import com.squareup.workflow1.Workflow +import com.squareup.workflow1.WorkflowAction +import kotlinx.coroutines.channels.SendChannel + +/** + * @see [RealRenderContext]. This is the version that supports Compose runtime optimizations. + */ +public class RealComposeRenderContext( + override val renderer: ComposeRenderer, + sideEffectRunner: SideEffectRunner, + eventActionsChannel: SendChannel> +) : RealRenderContext( + renderer, + sideEffectRunner, + eventActionsChannel, +), + BaseComposeRenderContext { + + public interface ComposeRenderer : Renderer { + @Composable + public fun Rendering( + child: Workflow, + props: ChildPropsT, + key: String, + handler: (ChildOutputT) -> WorkflowAction + ): ChildRenderingT + } + + @Composable + override fun ChildRendering( + child: Workflow, + props: ChildPropsT, + key: String, + handler: (ChildOutputT) -> WorkflowAction + ): ChildRenderingT { + remember(this) { + checkNotFrozen() + } + return renderer.Rendering(child, props, key, handler) + } +} diff --git a/workflow-core-compose/src/commonMain/kotlin/com/squareup/workflow1/compose/StatefulComposeWorkflow.kt b/workflow-core-compose/src/commonMain/kotlin/com/squareup/workflow1/compose/StatefulComposeWorkflow.kt new file mode 100644 index 0000000000..88a519142f --- /dev/null +++ b/workflow-core-compose/src/commonMain/kotlin/com/squareup/workflow1/compose/StatefulComposeWorkflow.kt @@ -0,0 +1,228 @@ +package com.squareup.workflow1.compose + +import androidx.compose.runtime.Composable +import com.squareup.workflow1.BaseRenderContext +import com.squareup.workflow1.Snapshot +import com.squareup.workflow1.StatefulWorkflow +import com.squareup.workflow1.Workflow +import com.squareup.workflow1.WorkflowExperimentalRuntime + +/** + * @see [StatefulWorkflow]. This is the extension of that which supports the Compose runtime + * optimizations for the children of this Workflow - i.e. Rendering() will not be called if the + * state of children has not changed. + * + * N.B. This is easily confused with + * [com.squareup.sample.compose.hellocomposeworkflow.ComposeWorkflow] which is a sample showing a + * much more radical modification of the Workflow API to support using Compose directly for more + * than just render() optimizations. + */ +@WorkflowExperimentalRuntime +public abstract class StatefulComposeWorkflow : + StatefulWorkflow() { + + @Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE") + public inner class RenderContext internal constructor( + baseContext: BaseComposeRenderContext + ) : StatefulWorkflow.RenderContext(baseContext), + BaseComposeRenderContext<@UnsafeVariance PropsT, StateT, @UnsafeVariance OutputT> by baseContext + + @Composable + public abstract fun Rendering( + renderProps: PropsT, + renderState: StateT, + context: RenderContext, + ): RenderingT +} + +/** + * Turn this [StatefulWorkflow] into a [StatefulComposeWorkflow] with the [RenderingImpl] function. + * + * If none is provided, it will default to calling [StatefulWorkflow.render]. + */ +@WorkflowExperimentalRuntime +public fun +StatefulWorkflow.asComposeWorkflow( + RenderingImpl: @Composable StatefulComposeWorkflow.( + PropsT, + StateT, + StatefulComposeWorkflow.RenderContext + ) -> RenderingT = { p, s, rc -> + render(p, s, rc) + } +): + StatefulComposeWorkflow { + val originalWorkflow = this + if (originalWorkflow is StatefulComposeWorkflow) { + return originalWorkflow + } + return object : StatefulComposeWorkflow() { + + @Composable + override fun Rendering( + renderProps: PropsT, + renderState: StateT, + context: RenderContext + ): RenderingT = RenderingImpl(renderProps, renderState, context) + + override fun initialState( + props: PropsT, + snapshot: Snapshot? + ): StateT = originalWorkflow.initialState(props, snapshot) + + override fun snapshotState(state: StateT): Snapshot? = originalWorkflow.snapshotState(state) + + override fun render( + renderProps: PropsT, + renderState: StateT, + context: StatefulWorkflow.RenderContext + ): RenderingT = originalWorkflow.render(renderProps, renderState, context) + } +} + +/** + * Creates a [StatefulComposeWorkflow.RenderContext] from a [BaseComposeRenderContext] for the given + * [StatefulComposeWorkflow]. + */ +@WorkflowExperimentalRuntime +@Suppress("UNCHECKED_CAST") +public fun ComposeRenderContext( + baseContext: BaseComposeRenderContext, + workflow: StatefulComposeWorkflow +): StatefulComposeWorkflow.RenderContext = + (baseContext as? StatefulComposeWorkflow.RenderContext) + ?: workflow.RenderContext(baseContext) + +/** + * Returns a Composed stateful [Workflow], defined by the given functions. + */ +@WorkflowExperimentalRuntime +public inline fun Workflow.Companion.composedStateful( + crossinline initialState: (PropsT, Snapshot?) -> StateT, + crossinline render: BaseRenderContext.( + props: PropsT, + state: StateT + ) -> RenderingT, + noinline Rendering: @Composable BaseComposeRenderContext.( + props: PropsT, + state: StateT + ) -> RenderingT = { props, state -> + render(props, state) + }, + crossinline snapshot: (StateT) -> Snapshot?, + crossinline onPropsChanged: ( + old: PropsT, + new: PropsT, + state: StateT + ) -> StateT = { _, _, state -> state } +): StatefulWorkflow = + object : StatefulComposeWorkflow() { + override fun initialState( + props: PropsT, + snapshot: Snapshot? + ): StateT = initialState(props, snapshot) + + override fun onPropsChanged( + old: PropsT, + new: PropsT, + state: StateT + ): StateT = onPropsChanged(old, new, state) + + override fun render( + renderProps: PropsT, + renderState: StateT, + context: StatefulWorkflow.RenderContext + ): RenderingT = render(context, renderProps, renderState) + + override fun snapshotState(state: StateT) = snapshot(state) + + @Composable + override fun Rendering( + renderProps: PropsT, + renderState: StateT, + context: RenderContext, + ): RenderingT = Rendering(context, renderProps, renderState) + } + +/** + * Returns a Composed stateful [Workflow], with no props, implemented via the given functions. + */ +@WorkflowExperimentalRuntime +public fun Workflow.Companion.composedStateful( + initialState: (Snapshot?) -> StateT, + render: BaseRenderContext.(state: StateT) -> RenderingT, + Rendering: @Composable BaseComposeRenderContext.( + state: StateT + ) -> RenderingT, + snapshot: (StateT) -> Snapshot? +): StatefulWorkflow { + @Suppress("LocalVariableName") + val RenderingWithProps: @Composable BaseComposeRenderContext.( + props: Unit, + state: StateT + ) -> RenderingT = @Composable { _: Unit, state: StateT -> + Rendering(state) + } + return composedStateful( + initialState = { _: Unit, initialSnapshot: Snapshot? -> initialState(initialSnapshot) }, + render = { _: Unit, state: StateT -> render(state) }, + Rendering = RenderingWithProps, + snapshot = snapshot + ) +} + +/** + * Returns a Composed stateful [Workflow] implemented via the given functions. + * + * This overload does not support snapshotting, but there are other overloads that do. + */ +@WorkflowExperimentalRuntime +public inline fun Workflow.Companion.composedStateful( + crossinline initialState: (PropsT) -> StateT, + crossinline render: BaseRenderContext.( + props: PropsT, + state: StateT + ) -> RenderingT, + noinline Rendering: @Composable BaseComposeRenderContext.( + props: PropsT, + state: StateT + ) -> RenderingT, + crossinline onPropsChanged: ( + old: PropsT, + new: PropsT, + state: StateT + ) -> StateT = { _, _, state -> state } +): StatefulWorkflow = composedStateful( + initialState = { props: PropsT, _ -> initialState(props) }, + render = render, + Rendering = Rendering, + snapshot = { null }, + onPropsChanged = onPropsChanged +) + +/** + * Returns a Composed stateful [Workflow], with no props, implemented via the given function. + * + * This overload does not support snapshots, but there are others that do. + */ +@WorkflowExperimentalRuntime +public fun Workflow.Companion.composedStateful( + initialState: StateT, + render: BaseRenderContext.(state: StateT) -> RenderingT, + Rendering: @Composable BaseComposeRenderContext.( + state: StateT + ) -> RenderingT, +): StatefulWorkflow { + @Suppress("LocalVariableName") + val RenderWithProps: @Composable BaseComposeRenderContext.( + props: Unit, + state: StateT + ) -> RenderingT = @Composable { _: Unit, state: StateT -> + Rendering(state) + } + return composedStateful( + initialState = { initialState }, + render = { _, state -> render(state) }, + Rendering = RenderWithProps, + ) +} diff --git a/workflow-core-compose/src/commonMain/kotlin/com/squareup/workflow1/compose/StatelessComposeWorkflow.kt b/workflow-core-compose/src/commonMain/kotlin/com/squareup/workflow1/compose/StatelessComposeWorkflow.kt new file mode 100644 index 0000000000..a61bd9a2ef --- /dev/null +++ b/workflow-core-compose/src/commonMain/kotlin/com/squareup/workflow1/compose/StatelessComposeWorkflow.kt @@ -0,0 +1,138 @@ +package com.squareup.workflow1.compose + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import com.squareup.workflow1.BaseRenderContext +import com.squareup.workflow1.RenderContext +import com.squareup.workflow1.StatefulWorkflow +import com.squareup.workflow1.StatelessWorkflow +import com.squareup.workflow1.Workflow +import com.squareup.workflow1.WorkflowExperimentalRuntime + +/** + * @see [StatelessWorkflow]. This is the extension of that which supports the Compose runtime + * optimizations for the children of this Workflow - i.e. Rendering() will not be called if the + * state of children has not changed. + * + * * N.B. This is easily confused with + * [com.squareup.sample.compose.hellocomposeworkflow.ComposeWorkflow] which is a sample showing a + * much more radical modification of the Workflow API to support using Compose directly for more + * than just render() optimizations. + */ +@WorkflowExperimentalRuntime +public abstract class StatelessComposeWorkflow : + StatelessWorkflow() { + + @Suppress("UNCHECKED_CAST", "DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE") + public inner class RenderContext constructor( + baseContext: BaseComposeRenderContext + ) : StatelessWorkflow.RenderContext(baseContext), + BaseComposeRenderContext<@UnsafeVariance PropsT, Nothing, @UnsafeVariance OutputT> by + baseContext as BaseComposeRenderContext + + override val statefulWorkflow: StatefulWorkflow + get() { + @Suppress("LocalVariableName") + val Rendering: @Composable BaseComposeRenderContext + .(props: PropsT, _: Unit) -> RenderingT = + @Composable { props, _ -> + val context = remember(this, this@StatelessComposeWorkflow) { + ComposeRenderContext(this, this@StatelessComposeWorkflow) + } + (this@StatelessComposeWorkflow).Rendering(props, context) + } + return Workflow.composedStateful( + initialState = { Unit }, + render = { props, _ -> + render( + props, + RenderContext( + this, + this@StatelessComposeWorkflow as StatelessWorkflow + ) + ) + }, + Rendering = Rendering + ) + } + + @Composable + public abstract fun Rendering( + renderProps: PropsT, + context: RenderContext, + ): RenderingT +} + +/** + * Turn this [StatelessWorkflow] into a [StatelessComposeWorkflow] with the [Rendering] function. + * + * If none is provided, it will default to calling [StatelessWorkflow.render]. + */ +@WorkflowExperimentalRuntime +public fun +StatelessWorkflow.asComposeWorkflow( + RenderingImpl: @Composable StatelessComposeWorkflow.( + PropsT, + StatelessWorkflow.RenderContext + ) -> RenderingT = { p, rc -> + render(p, rc) + } +): StatelessComposeWorkflow { + val originalWorkflow = this + if (originalWorkflow is StatelessComposeWorkflow) { + return originalWorkflow + } + return object : StatelessComposeWorkflow() { + + @Composable + override fun Rendering( + renderProps: PropsT, + context: RenderContext + ): RenderingT = RenderingImpl(renderProps, context) + + override fun render( + renderProps: PropsT, + context: StatelessWorkflow.RenderContext + ): RenderingT = originalWorkflow.render(renderProps, context) + } +} + +/** + * Creates a [StatelessComposeWorkflow.RenderContext] from a [BaseComposeRenderContext] + * for the given [StatelessComposeWorkflow]. + */ +@WorkflowExperimentalRuntime +@Suppress("UNCHECKED_CAST") +public fun ComposeRenderContext( + baseContext: BaseComposeRenderContext, + workflow: StatelessComposeWorkflow +): StatelessComposeWorkflow.RenderContext = + (baseContext as? StatelessComposeWorkflow.RenderContext) + ?: workflow.RenderContext(baseContext) + +/** + * Returns a stateless, composed [Workflow] via the given [render] function. + * + * Note that while the returned workflow doesn't have any _internal_ state of its own, it may use + * [props][PropsT] received from its parent, and it may render child workflows that do have + * their own internal state. + */ +@WorkflowExperimentalRuntime +public inline fun Workflow.Companion.composedStateless( + noinline Rendering: @Composable BaseComposeRenderContext.( + props: PropsT + ) -> RenderingT, + crossinline render: BaseRenderContext.(props: PropsT) -> RenderingT, +): Workflow = + object : StatelessComposeWorkflow() { + override fun render( + renderProps: PropsT, + context: StatelessWorkflow.RenderContext + ): RenderingT = render(context, renderProps) + + @Composable + override fun Rendering( + renderProps: PropsT, + context: RenderContext + ): RenderingT = Rendering(context, renderProps) + } diff --git a/workflow-core-compose/src/commonMain/kotlin/com/squareup/workflow1/compose/WorkflowComposeChildNode.kt b/workflow-core-compose/src/commonMain/kotlin/com/squareup/workflow1/compose/WorkflowComposeChildNode.kt new file mode 100644 index 0000000000..b43fbbf56e --- /dev/null +++ b/workflow-core-compose/src/commonMain/kotlin/com/squareup/workflow1/compose/WorkflowComposeChildNode.kt @@ -0,0 +1,50 @@ +package com.squareup.workflow1.compose + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import com.squareup.workflow1.Workflow +import com.squareup.workflow1.WorkflowAction +import com.squareup.workflow1.WorkflowChildNode +import com.squareup.workflow1.WorkflowExperimentalRuntime +import com.squareup.workflow1.WorkflowNode + +/** + * @see [WorkflowChildNode]. This version supports piping Composable [Rendering]. + */ +@WorkflowExperimentalRuntime +public class WorkflowComposeChildNode< + ChildPropsT, + ChildOutputT, + ParentPropsT, + ParentStateT, + ParentOutputT + >( + workflow: Workflow<*, ChildOutputT, *>, + handler: (ChildOutputT) -> WorkflowAction, + workflowNode: WorkflowNode +) : WorkflowChildNode< + ChildPropsT, + ChildOutputT, + ParentPropsT, + ParentStateT, + ParentOutputT + >( + workflow, handler, workflowNode +) { + + @Composable + public fun Rendering( + workflow: StatefulComposeWorkflow<*, *, *, *>, + props: Any? + ): R { + val rendering = remember { mutableStateOf(null) } + @Suppress("UNCHECKED_CAST") + (workflowNode as WorkflowComposeNode).Rendering( + workflow as StatefulComposeWorkflow, + props as ChildPropsT, + rendering, + ) + return rendering.value!! + } +} diff --git a/workflow-core-compose/src/commonMain/kotlin/com/squareup/workflow1/compose/WorkflowComposeNode.kt b/workflow-core-compose/src/commonMain/kotlin/com/squareup/workflow1/compose/WorkflowComposeNode.kt new file mode 100644 index 0000000000..2f07f67da1 --- /dev/null +++ b/workflow-core-compose/src/commonMain/kotlin/com/squareup/workflow1/compose/WorkflowComposeNode.kt @@ -0,0 +1,140 @@ +package com.squareup.workflow1.compose + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.key +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import com.squareup.workflow1.IdCounter +import com.squareup.workflow1.TreeSnapshot +import com.squareup.workflow1.WorkflowExperimentalRuntime +import com.squareup.workflow1.WorkflowInterceptor.WorkflowSession +import com.squareup.workflow1.WorkflowNode +import com.squareup.workflow1.WorkflowNodeId +import com.squareup.workflow1.WorkflowOutput +import kotlin.coroutines.CoroutineContext + +/** + * @see [WorkflowNode]. This version extends that to support Compose runtime optimizations. + */ +@WorkflowExperimentalRuntime +public class WorkflowComposeNode( + id: WorkflowNodeId, + workflow: StatefulComposeWorkflow, + initialProps: PropsT, + snapshot: TreeSnapshot?, + baseContext: CoroutineContext, + emitOutputToParent: (OutputT) -> Any? = { WorkflowOutput(it) }, + parent: WorkflowSession? = null, + interceptor: ComposeWorkflowInterceptor = NoopComposeWorkflowInterceptor, + idCounter: IdCounter? = null +) : WorkflowNode( + id, + workflow, + initialProps, + snapshot, + baseContext, + emitOutputToParent, + parent, + interceptor, + idCounter, +) { + + /** + * Back [state] with a [MutableState] so the Compose runtime can track changes. + */ + private lateinit var backingMutableState: MutableState + + override var state: StateT + get() { + return backingMutableState.value + } + set(value) { + if (!this::backingMutableState.isInitialized) { + backingMutableState = mutableStateOf(value) + } + backingMutableState.value = value + } + + override val subtreeManager: ComposeSubtreeManager = + ComposeSubtreeManager( + snapshotCache = snapshot?.childTreeSnapshots, + contextForChildren = coroutineContext, + emitActionToParent = ::applyAction, + workflowSession = this, + interceptor = interceptor, + idCounter = idCounter + ) + + /** + * This returns Unit so that the Recomposer will consider this a separate Recompose scope that + * can be independently recomposed. + * + * We pass in the MutableState directly rather than setRendering() to save Compose + * having to memoize the lambda for such a frequent call. + */ + @Suppress("UNCHECKED_CAST") + @Composable + public fun Rendering( + workflow: StatefulComposeWorkflow, + input: PropsT, + rendering: MutableState + ) { + RenderingWithStateType( + workflow as StatefulComposeWorkflow, + input, + rendering + ) + } + + @Composable + private fun RenderingWithStateType( + workflow: StatefulComposeWorkflow, + props: PropsT, + rendering: MutableState + ) { + UpdatePropsAndState(workflow, props) + + val (baseRenderContext, renderContext) = remember( + state, + props, + workflow, + rendering.value + ) { + // Use the RenderContext once. After rendering successfully it is frozen until new state. + val base = RealComposeRenderContext( + renderer = subtreeManager, + sideEffectRunner = this, + eventActionsChannel = eventActionsChannel + ) + base to ComposeRenderContext(workflow = workflow, baseContext = base) + } + + rendering.value = interceptor.asComposeWorkflowInterceptor().intercept(workflow, this) + .Rendering(props, state, renderContext) + + SideEffect { + baseRenderContext.freeze() + commitAndUpdateScopes() + } + } + + @Composable + private fun UpdatePropsAndState( + workflow: StatefulComposeWorkflow, + newProps: PropsT + ) { + key(newProps) { + if (newProps != lastProps) { + state = interceptor + .asComposeWorkflowInterceptor() + .intercept(workflow, this@WorkflowComposeNode) + .onPropsChanged(lastProps, newProps, state) + } + } + SideEffect { + lastProps = newProps + } + } +} diff --git a/workflow-core-compose/src/commonMain/kotlin/com/squareup/workflow1/compose/WorkflowComposeRunner.kt b/workflow-core-compose/src/commonMain/kotlin/com/squareup/workflow1/compose/WorkflowComposeRunner.kt new file mode 100644 index 0000000000..fbbfff7b05 --- /dev/null +++ b/workflow-core-compose/src/commonMain/kotlin/com/squareup/workflow1/compose/WorkflowComposeRunner.kt @@ -0,0 +1,66 @@ +package com.squareup.workflow1.compose + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import com.squareup.workflow1.RenderingAndSnapshot +import com.squareup.workflow1.RuntimeConfig +import com.squareup.workflow1.TreeSnapshot +import com.squareup.workflow1.Workflow +import com.squareup.workflow1.WorkflowExperimentalRuntime +import com.squareup.workflow1.WorkflowRunner +import com.squareup.workflow1.id +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.StateFlow + +/** + * @see [WorkflowRunner]. This version supports running with the Compose runtime optimizations. + */ +@WorkflowExperimentalRuntime +public class WorkflowComposeRunner( + scope: CoroutineScope, + protoWorkflow: Workflow, + props: StateFlow, + snapshot: TreeSnapshot?, + interceptor: ComposeWorkflowInterceptor, + runtimeConfig: RuntimeConfig +) : WorkflowRunner( + scope, + protoWorkflow, + props, + snapshot, + interceptor, + runtimeConfig, +) { + + override var currentProps: PropsT by mutableStateOf(props.value) + + override val rootNode: WorkflowComposeNode = + WorkflowComposeNode( + id = workflow.id(), + workflow = workflow.asComposeWorkflow(), + initialProps = currentProps, + snapshot = snapshot, + baseContext = scope.coroutineContext, + interceptor = interceptor, + idCounter = idCounter + ).apply { + startSession() + } + + @Composable + public fun nextComposedRendering(): RenderingAndSnapshot { + val rendering = remember { mutableStateOf(null) } + + rootNode.Rendering(workflow as StatefulComposeWorkflow, currentProps, rendering) + + val snapshot = remember { + // need to key this on state inside WorkflowNode. Likely have a Compose version. + rootNode.snapshot(workflow) + } + + return RenderingAndSnapshot(rendering.value!!, snapshot) + } +} diff --git a/workflow-core/api/workflow-core.api b/workflow-core/api/workflow-core.api index 16d38b86ec..b12e71f1f1 100644 --- a/workflow-core/api/workflow-core.api +++ b/workflow-core/api/workflow-core.api @@ -2,7 +2,6 @@ public abstract interface class com/squareup/workflow1/ActionProcessingResult { } public abstract interface class com/squareup/workflow1/BaseRenderContext { - public abstract fun ChildRendering (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; public abstract fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function10;)Lkotlin/jvm/functions/Function9; public abstract fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function11;)Lkotlin/jvm/functions/Function10; public abstract fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;)Lkotlin/jvm/functions/Function0; @@ -55,7 +54,6 @@ public final class com/squareup/workflow1/ImpostorWorkflow$DefaultImpls { } public abstract class com/squareup/workflow1/LifecycleWorker : com/squareup/workflow1/Worker { - public static final field $stable I public fun ()V public fun doesSameWorkAs (Lcom/squareup/workflow1/Worker;)Z public fun onStarted ()V @@ -64,7 +62,6 @@ public abstract class com/squareup/workflow1/LifecycleWorker : com/squareup/work } public final class com/squareup/workflow1/PropsUpdated : com/squareup/workflow1/ActionProcessingResult { - public static final field $stable I public static final field INSTANCE Lcom/squareup/workflow1/PropsUpdated; } @@ -73,7 +70,6 @@ public abstract interface class com/squareup/workflow1/Sink { } public final class com/squareup/workflow1/Snapshot { - public static final field $stable I public static final field Companion Lcom/squareup/workflow1/Snapshot$Companion; public synthetic fun (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun bytes ()Lokio/ByteString; @@ -118,9 +114,7 @@ public final class com/squareup/workflow1/Snapshots { } public abstract class com/squareup/workflow1/StatefulWorkflow : com/squareup/workflow1/Workflow { - public static final field $stable I public fun ()V - public fun Rendering (Ljava/lang/Object;Ljava/lang/Object;Lcom/squareup/workflow1/StatefulWorkflow$RenderContext;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; public final fun asStatefulWorkflow ()Lcom/squareup/workflow1/StatefulWorkflow; public abstract fun initialState (Ljava/lang/Object;Lcom/squareup/workflow1/Snapshot;)Ljava/lang/Object; public fun onPropsChanged (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; @@ -128,9 +122,8 @@ public abstract class com/squareup/workflow1/StatefulWorkflow : com/squareup/wor public abstract fun snapshotState (Ljava/lang/Object;)Lcom/squareup/workflow1/Snapshot; } -public final class com/squareup/workflow1/StatefulWorkflow$RenderContext : com/squareup/workflow1/BaseRenderContext { +public class com/squareup/workflow1/StatefulWorkflow$RenderContext : com/squareup/workflow1/BaseRenderContext { public fun (Lcom/squareup/workflow1/StatefulWorkflow;Lcom/squareup/workflow1/BaseRenderContext;)V - public fun ChildRendering (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function10;)Lkotlin/jvm/functions/Function9; public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function11;)Lkotlin/jvm/functions/Function10; public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;)Lkotlin/jvm/functions/Function0; @@ -148,16 +141,14 @@ public final class com/squareup/workflow1/StatefulWorkflow$RenderContext : com/s } public abstract class com/squareup/workflow1/StatelessWorkflow : com/squareup/workflow1/Workflow { - public static final field $stable I public fun ()V - public fun Rendering (Ljava/lang/Object;Lcom/squareup/workflow1/StatelessWorkflow$RenderContext;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; public final fun asStatefulWorkflow ()Lcom/squareup/workflow1/StatefulWorkflow; + protected fun getStatefulWorkflow ()Lcom/squareup/workflow1/StatefulWorkflow; public abstract fun render (Ljava/lang/Object;Lcom/squareup/workflow1/StatelessWorkflow$RenderContext;)Ljava/lang/Object; } -public final class com/squareup/workflow1/StatelessWorkflow$RenderContext : com/squareup/workflow1/BaseRenderContext { +public class com/squareup/workflow1/StatelessWorkflow$RenderContext : com/squareup/workflow1/BaseRenderContext { public fun (Lcom/squareup/workflow1/StatelessWorkflow;Lcom/squareup/workflow1/BaseRenderContext;)V - public fun ChildRendering (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function10;)Lkotlin/jvm/functions/Function9; public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function11;)Lkotlin/jvm/functions/Function10; public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;)Lkotlin/jvm/functions/Function0; @@ -175,7 +166,6 @@ public final class com/squareup/workflow1/StatelessWorkflow$RenderContext : com/ } public final class com/squareup/workflow1/TimeoutForFrame : com/squareup/workflow1/ActionProcessingResult { - public static final field $stable I public static final field INSTANCE Lcom/squareup/workflow1/TimeoutForFrame; } @@ -211,8 +201,6 @@ public final class com/squareup/workflow1/Worker$DefaultImpls { public final class com/squareup/workflow1/WorkerWorkflow : com/squareup/workflow1/StatefulWorkflow, com/squareup/workflow1/ImpostorWorkflow { public fun (Lkotlin/reflect/KType;Ljava/lang/String;)V - public fun Rendering (Lcom/squareup/workflow1/Worker;ILcom/squareup/workflow1/StatefulWorkflow$RenderContext;Landroidx/compose/runtime/Composer;I)V - public synthetic fun Rendering (Ljava/lang/Object;Ljava/lang/Object;Lcom/squareup/workflow1/StatefulWorkflow$RenderContext;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; public fun describeRealIdentifier ()Ljava/lang/String; public fun getRealIdentifier ()Lcom/squareup/workflow1/WorkflowIdentifier; public final fun getWorkerType ()Lkotlin/reflect/KType; @@ -235,7 +223,6 @@ public final class com/squareup/workflow1/Workflow$Companion { } public abstract class com/squareup/workflow1/WorkflowAction { - public static final field $stable I public static final field Companion Lcom/squareup/workflow1/WorkflowAction$Companion; public fun ()V public abstract fun apply (Lcom/squareup/workflow1/WorkflowAction$Updater;)V @@ -255,7 +242,6 @@ public final class com/squareup/workflow1/WorkflowAction$Updater { } public final class com/squareup/workflow1/WorkflowIdentifier { - public static final field $stable I public static final field Companion Lcom/squareup/workflow1/WorkflowIdentifier$Companion; public fun (Lcom/squareup/workflow1/WorkflowIdentifierType;Lcom/squareup/workflow1/WorkflowIdentifier;Lkotlin/jvm/functions/Function0;)V public synthetic fun (Lcom/squareup/workflow1/WorkflowIdentifierType;Lcom/squareup/workflow1/WorkflowIdentifier;Lkotlin/jvm/functions/Function0;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -275,12 +261,10 @@ public final class com/squareup/workflow1/WorkflowIdentifierExKt { } public abstract class com/squareup/workflow1/WorkflowIdentifierType { - public static final field $stable I public abstract fun getTypeName ()Ljava/lang/String; } public final class com/squareup/workflow1/WorkflowIdentifierType$Snapshottable : com/squareup/workflow1/WorkflowIdentifierType { - public static final field $stable I public fun (Ljava/lang/String;Lkotlin/reflect/KClass;)V public synthetic fun (Ljava/lang/String;Lkotlin/reflect/KClass;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun (Lkotlin/reflect/KClass;)V @@ -296,7 +280,6 @@ public final class com/squareup/workflow1/WorkflowIdentifierType$Snapshottable : } public final class com/squareup/workflow1/WorkflowIdentifierType$Unsnapshottable : com/squareup/workflow1/WorkflowIdentifierType { - public static final field $stable I public fun (Lkotlin/reflect/KType;)V public final fun component1 ()Lkotlin/reflect/KType; public final fun copy (Lkotlin/reflect/KType;)Lcom/squareup/workflow1/WorkflowIdentifierType$Unsnapshottable; @@ -309,7 +292,6 @@ public final class com/squareup/workflow1/WorkflowIdentifierType$Unsnapshottable } public final class com/squareup/workflow1/WorkflowOutput : com/squareup/workflow1/ActionProcessingResult { - public static final field $stable I public fun (Ljava/lang/Object;)V public fun equals (Ljava/lang/Object;)Z public final fun getValue ()Ljava/lang/Object; @@ -318,9 +300,6 @@ public final class com/squareup/workflow1/WorkflowOutput : com/squareup/workflow } public final class com/squareup/workflow1/Workflows { - public static final fun ChildRendering (Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Landroidx/compose/runtime/Composer;II)Ljava/lang/Object; - public static final fun ChildRendering (Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/Workflow;Ljava/lang/String;Landroidx/compose/runtime/Composer;II)Ljava/lang/Object; - public static final fun ChildRendering (Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/Workflow;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)Ljava/lang/Object; public static final fun RenderContext (Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/StatefulWorkflow;)Lcom/squareup/workflow1/StatefulWorkflow$RenderContext; public static final fun RenderContext (Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/StatelessWorkflow;)Lcom/squareup/workflow1/StatelessWorkflow$RenderContext; public static final fun action (Lcom/squareup/workflow1/StatefulWorkflow;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/WorkflowAction; @@ -354,19 +333,12 @@ public final class com/squareup/workflow1/Workflows { public static synthetic fun runningWorker$default (Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/Worker;Lkotlin/reflect/KType;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V public static final fun sendAndAwaitApplication (Lcom/squareup/workflow1/Sink;Lcom/squareup/workflow1/WorkflowAction;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun stateful (Lcom/squareup/workflow1/Workflow$Companion;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Lcom/squareup/workflow1/StatefulWorkflow; - public static final fun stateful (Lcom/squareup/workflow1/Workflow$Companion;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function4;)Lcom/squareup/workflow1/StatefulWorkflow; public static final fun stateful (Lcom/squareup/workflow1/Workflow$Companion;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/StatefulWorkflow; - public static final fun stateful (Lcom/squareup/workflow1/Workflow$Companion;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/StatefulWorkflow; public static final fun stateful (Lcom/squareup/workflow1/Workflow$Companion;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;)Lcom/squareup/workflow1/StatefulWorkflow; - public static final fun stateful (Lcom/squareup/workflow1/Workflow$Companion;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function5;Lkotlin/jvm/functions/Function3;)Lcom/squareup/workflow1/StatefulWorkflow; public static final fun stateful (Lcom/squareup/workflow1/Workflow$Companion;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;)Lcom/squareup/workflow1/StatefulWorkflow; - public static final fun stateful (Lcom/squareup/workflow1/Workflow$Companion;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function5;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;)Lcom/squareup/workflow1/StatefulWorkflow; public static synthetic fun stateful$default (Lcom/squareup/workflow1/Workflow$Companion;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lcom/squareup/workflow1/StatefulWorkflow; - public static synthetic fun stateful$default (Lcom/squareup/workflow1/Workflow$Companion;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function5;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lcom/squareup/workflow1/StatefulWorkflow; public static synthetic fun stateful$default (Lcom/squareup/workflow1/Workflow$Companion;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lcom/squareup/workflow1/StatefulWorkflow; - public static synthetic fun stateful$default (Lcom/squareup/workflow1/Workflow$Companion;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function5;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lcom/squareup/workflow1/StatefulWorkflow; public static final fun stateless (Lcom/squareup/workflow1/Workflow$Companion;Lkotlin/jvm/functions/Function2;)Lcom/squareup/workflow1/Workflow; - public static final fun stateless (Lcom/squareup/workflow1/Workflow$Companion;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function2;)Lcom/squareup/workflow1/Workflow; public static final fun transform (Lcom/squareup/workflow1/Worker;Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/Worker; public static final fun unsnapshottableIdentifier (Lkotlin/reflect/KType;)Lcom/squareup/workflow1/WorkflowIdentifier; } diff --git a/workflow-core/build.gradle.kts b/workflow-core/build.gradle.kts index d597d5fdb8..ed9b091fef 100644 --- a/workflow-core/build.gradle.kts +++ b/workflow-core/build.gradle.kts @@ -1,13 +1,11 @@ plugins { `kotlin-multiplatform` published - id("app.cash.molecule") } kotlin { jvm { withJava() } - // TODO: No native targets yet for Molecule until Compose 1.2.0 available in JB KMP runtime. - // ios() + ios() sourceSets { all { @@ -18,11 +16,9 @@ kotlin { val commonMain by getting { dependencies { api(libs.kotlin.jdk6) - api(libs.compose.runtime) api(libs.kotlinx.coroutines.core) // For Snapshot. api(libs.squareup.okio) - implementation(libs.molecule.runtime) } } val commonTest by getting { diff --git a/workflow-core/dependencies/jvmRuntimeClasspath.txt b/workflow-core/dependencies/jvmRuntimeClasspath.txt index f2b5104fb7..94a9cb2bd9 100644 --- a/workflow-core/dependencies/jvmRuntimeClasspath.txt +++ b/workflow-core/dependencies/jvmRuntimeClasspath.txt @@ -1,9 +1,5 @@ -app.cash.molecule:molecule-runtime-jvm:0.3.0-SNAPSHOT -app.cash.molecule:molecule-runtime:0.3.0-SNAPSHOT com.squareup.okio:okio-jvm:3.0.0 com.squareup.okio:okio:3.0.0 -org.jetbrains.compose.runtime:runtime-desktop:1.1.0 -org.jetbrains.compose.runtime:runtime:1.1.0 org.jetbrains.kotlin:kotlin-bom:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.10 diff --git a/workflow-core/dependencies/runtimeClasspath.txt b/workflow-core/dependencies/runtimeClasspath.txt index d8576d6ee2..6a9b6ed46f 100644 --- a/workflow-core/dependencies/runtimeClasspath.txt +++ b/workflow-core/dependencies/runtimeClasspath.txt @@ -1,9 +1,5 @@ -app.cash.molecule:molecule-runtime-jvm:0.3.0-SNAPSHOT -app.cash.molecule:molecule-runtime:0.3.0-SNAPSHOT com.squareup.okio:okio-jvm:3.0.0 com.squareup.okio:okio:3.0.0 -org.jetbrains.compose.runtime:runtime-desktop:1.1.0 -org.jetbrains.compose.runtime:runtime:1.1.0 org.jetbrains.kotlin:kotlin-bom:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-common:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10 diff --git a/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/BaseRenderContext.kt b/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/BaseRenderContext.kt index 61a11d62d6..670578a47e 100644 --- a/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/BaseRenderContext.kt +++ b/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/BaseRenderContext.kt @@ -4,7 +4,6 @@ package com.squareup.workflow1 -import androidx.compose.runtime.Composable import com.squareup.workflow1.WorkflowAction.Companion.noAction import kotlinx.coroutines.CoroutineScope import kotlin.jvm.JvmMultifileClass @@ -79,14 +78,6 @@ public interface BaseRenderContext { handler: (ChildOutputT) -> WorkflowAction ): ChildRenderingT - @Composable - public fun ChildRendering( - child: Workflow, - props: ChildPropsT, - key: String, - handler: (ChildOutputT) -> WorkflowAction - ): ChildRenderingT - /** * Ensures [sideEffect] is running with the given [key]. * @@ -251,37 +242,6 @@ BaseRenderContext.renderChild( key: String = "" ): ChildRenderingT = renderChild(child, Unit, key) { noAction() } -/** - * Convenience alias of [BaseRenderContext.ChildRendering] for workflows that don't take props. - */ -@Composable -public fun -BaseRenderContext.ChildRendering( - child: Workflow, - key: String = "", - handler: (ChildOutputT) -> WorkflowAction -): ChildRenderingT = ChildRendering(child, Unit, key, handler) -/** - * Convenience alias of [BaseRenderContext.ChildRendering] for workflows that don't emit output. - */ -@Composable -public fun -BaseRenderContext.ChildRendering( - child: Workflow, - props: ChildPropsT, - key: String = "", -): ChildRenderingT = ChildRendering(child, props, key) { noAction() } -/** - * Convenience alias of [BaseRenderContext.ChildRendering] for children that don't take props or emit - * output. - */ -@Composable -public fun -BaseRenderContext.ChildRendering( - child: Workflow, - key: String = "", -): ChildRenderingT = ChildRendering(child, Unit, key) { noAction() } - /** * Ensures a [Worker] that never emits anything is running. Since [worker] can't emit anything, * it can't trigger any [WorkflowAction]s. diff --git a/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/StatefulWorkflow.kt b/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/StatefulWorkflow.kt index 124bc4d11a..640e774454 100644 --- a/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/StatefulWorkflow.kt +++ b/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/StatefulWorkflow.kt @@ -4,7 +4,6 @@ package com.squareup.workflow1 -import androidx.compose.runtime.Composable import com.squareup.workflow1.StatefulWorkflow.RenderContext import com.squareup.workflow1.WorkflowAction.Companion.toString import kotlin.jvm.JvmMultifileClass @@ -72,7 +71,7 @@ public abstract class StatefulWorkflow< out RenderingT > : Workflow { - public inner class RenderContext internal constructor( + public open inner class RenderContext constructor( baseContext: BaseRenderContext ) : BaseRenderContext<@UnsafeVariance PropsT, StateT, @UnsafeVariance OutputT> by baseContext @@ -146,15 +145,6 @@ public abstract class StatefulWorkflow< context: RenderContext ): RenderingT - @Composable - public open fun Rendering( - renderProps: PropsT, - renderState: StateT, - context: RenderContext, - ): RenderingT { - return render(renderProps, renderState, context) - } - /** * Satisfies the [Workflow] interface by returning `this`. */ @@ -188,35 +178,6 @@ public inline fun Workflow.Companion.state new: PropsT, state: StateT ) -> StateT = { _, _, state -> state } -): StatefulWorkflow = - stateful( - initialState = initialState, - render = render, - Rendering = { props, state -> - render(props, state) - }, - snapshot = snapshot, - onPropsChanged = onPropsChanged - ) - -public inline fun Workflow.Companion.stateful( - crossinline initialState: (PropsT, Snapshot?) -> StateT, - crossinline render: BaseRenderContext.( - props: PropsT, - state: StateT - ) -> RenderingT, - noinline Rendering: @Composable BaseRenderContext.( - props: PropsT, - state: StateT - ) -> RenderingT = { props, state -> - render(props, state) - }, - crossinline snapshot: (StateT) -> Snapshot?, - crossinline onPropsChanged: ( - old: PropsT, - new: PropsT, - state: StateT - ) -> StateT = { _, _, state -> state } ): StatefulWorkflow = object : StatefulWorkflow() { override fun initialState( @@ -233,17 +194,10 @@ public inline fun Workflow.Companion.state override fun render( renderProps: PropsT, renderState: StateT, - context: RenderContext + context: StatefulWorkflow.RenderContext ): RenderingT = render(context, renderProps, renderState) override fun snapshotState(state: StateT) = snapshot(state) - - @Composable - override fun Rendering( - renderProps: PropsT, - renderState: StateT, - context: RenderContext, - ): RenderingT = Rendering(context, renderProps, renderState) } /** @@ -260,32 +214,6 @@ public inline fun Workflow.Companion.stateful( snapshot = snapshot ) -/** - * Version of the above supporting [Rendering] for Composed Workflows. - */ -public fun Workflow.Companion.stateful( - initialState: (Snapshot?) -> StateT, - render: BaseRenderContext.(state: StateT) -> RenderingT, - Rendering: @Composable BaseRenderContext.( - state: StateT - ) -> RenderingT, - snapshot: (StateT) -> Snapshot? -): StatefulWorkflow { - @Suppress("LocalVariableName") - val RenderingWithProps: @Composable BaseRenderContext.( - props: Unit, - state: StateT - ) -> RenderingT = @Composable { _: Unit, state: StateT -> - Rendering(state) - } - return stateful( - initialState = { _: Unit, initialSnapshot: Snapshot? -> initialState(initialSnapshot) }, - render = { _: Unit, state: StateT -> render(state) }, - Rendering = RenderingWithProps, - snapshot = snapshot - ) -} - /** * Returns a stateful [Workflow] implemented via the given functions. * @@ -309,32 +237,6 @@ public inline fun Workflow.Companion.state onPropsChanged = onPropsChanged ) -/** - * Version of the above supporting [Rendering] for Composed Workflows. - */ -public inline fun Workflow.Companion.stateful( - crossinline initialState: (PropsT) -> StateT, - crossinline render: BaseRenderContext.( - props: PropsT, - state: StateT - ) -> RenderingT, - noinline Rendering: @Composable BaseRenderContext.( - props: PropsT, - state: StateT - ) -> RenderingT, - crossinline onPropsChanged: ( - old: PropsT, - new: PropsT, - state: StateT - ) -> StateT = { _, _, state -> state } -): StatefulWorkflow = stateful( - initialState = { props: PropsT, _ -> initialState(props) }, - render = render, - Rendering = Rendering, - snapshot = { null }, - onPropsChanged = onPropsChanged -) - /** * Returns a stateful [Workflow], with no props, implemented via the given function. * @@ -348,30 +250,6 @@ public inline fun Workflow.Companion.stateful( render = { _, state -> render(state) } ) -/** - * Version of the above supporting [Rendering] for Compose Workflows - */ -public fun Workflow.Companion.stateful( - initialState: StateT, - render: BaseRenderContext.(state: StateT) -> RenderingT, - Rendering: @Composable BaseRenderContext.( - state: StateT - ) -> RenderingT, -): StatefulWorkflow { - @Suppress("LocalVariableName") - val RenderWithProps: @Composable BaseRenderContext.( - props: Unit, - state: StateT - ) -> RenderingT = @Composable { _: Unit, state: StateT -> - Rendering(state) - } - return stateful( - initialState = { initialState }, - render = { _, state -> render(state) }, - Rendering = RenderWithProps, - ) -} - /** * Convenience to create a [WorkflowAction] with parameter types matching those * of the receiving [StatefulWorkflow]. The action will invoke the given [lambda][update] diff --git a/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/StatelessWorkflow.kt b/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/StatelessWorkflow.kt index 8748b90ae5..acb32a3ac0 100644 --- a/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/StatelessWorkflow.kt +++ b/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/StatelessWorkflow.kt @@ -3,8 +3,6 @@ package com.squareup.workflow1 -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName @@ -29,29 +27,16 @@ public abstract class StatelessWorkflow Workflow { @Suppress("UNCHECKED_CAST") - public inner class RenderContext internal constructor( + public open inner class RenderContext constructor( baseContext: BaseRenderContext ) : BaseRenderContext<@UnsafeVariance PropsT, Nothing, @UnsafeVariance OutputT> by baseContext as BaseRenderContext - @Suppress("UNCHECKED_CAST") - private val statefulWorkflow: StatefulWorkflow - get() { - @Suppress("LocalVariableName") - val Rendering: @Composable BaseRenderContext - .(props: PropsT, _: Unit) -> RenderingT = - @Composable { props, _ -> - val context = remember(this, this@StatelessWorkflow) { - RenderContext(this, this@StatelessWorkflow) - } - (this@StatelessWorkflow).Rendering(props, context) - } - return Workflow.stateful( - initialState = { Unit }, - render = { props, _ -> render(props, RenderContext(this, this@StatelessWorkflow)) }, - Rendering = Rendering - ) - } + protected open val statefulWorkflow: StatefulWorkflow = + Workflow.stateful( + initialState = { Unit }, + render = { props, _ -> render(props, RenderContext(this, this@StatelessWorkflow)) } + ) /** * Called at least once any time one of the following things happens: @@ -72,12 +57,6 @@ public abstract class StatelessWorkflow context: RenderContext ): RenderingT - @Composable - public open fun Rendering( - renderProps: PropsT, - context: RenderContext, - ): RenderingT = render(renderProps, context) - /** * Satisfies the [Workflow] interface by wrapping `this` in a [StatefulWorkflow] with `Unit` * state. @@ -109,30 +88,12 @@ public fun RenderContext( */ public inline fun Workflow.Companion.stateless( crossinline render: BaseRenderContext.(props: PropsT) -> RenderingT, -): Workflow = stateless( - Rendering = { props: PropsT -> - render(props) - }, - render = render -) - -public inline fun Workflow.Companion.stateless( - noinline Rendering: @Composable BaseRenderContext.( - props: PropsT - ) -> RenderingT, - crossinline render: BaseRenderContext.(props: PropsT) -> RenderingT, ): Workflow = object : StatelessWorkflow() { override fun render( renderProps: PropsT, context: RenderContext ): RenderingT = render(context, renderProps) - - @Composable - override fun Rendering( - renderProps: PropsT, - context: RenderContext - ): RenderingT = Rendering(context, renderProps) } /** 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 a1d9f7b5f1..c5ec0bba90 100644 --- a/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/WorkerWorkflow.kt +++ b/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/WorkerWorkflow.kt @@ -3,7 +3,6 @@ package com.squareup.workflow1 -import androidx.compose.runtime.Composable import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.withContext @@ -62,15 +61,6 @@ internal class WorkerWorkflow( } override fun snapshotState(state: Int): Snapshot? = null - - @Composable - override fun Rendering( - renderProps: Worker, - renderState: Int, - context: RenderContext, - ) { - render(renderProps, renderState, context) - } } /** diff --git a/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/Workflow.kt b/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/Workflow.kt index 172629fb67..ce2aaf685d 100644 --- a/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/Workflow.kt +++ b/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/Workflow.kt @@ -3,7 +3,6 @@ package com.squareup.workflow1 -import androidx.compose.runtime.Composable import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName @@ -136,12 +135,4 @@ Workflow.mapRendering( override fun describeRealIdentifier(): String = "${this@mapRendering.identifier}.mapRendering()" override fun toString(): String = "${this@mapRendering}.mapRendering()" - - @Composable - override fun Rendering( - renderProps: PropsT, - context: RenderContext - ): ToRenderingT { - TODO("Not yet implemented") - } } diff --git a/workflow-runtime/api/workflow-runtime.api b/workflow-runtime/api/workflow-runtime.api index 40342760dc..2daa133019 100644 --- a/workflow-runtime/api/workflow-runtime.api +++ b/workflow-runtime/api/workflow-runtime.api @@ -1,16 +1,81 @@ -public final class com/squareup/workflow1/Latch { +public final class com/squareup/workflow1/ActiveStagingList { public fun ()V - public final fun await (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun closeLatch ()V - public final fun isOpen ()Z - public final fun openLatch ()V - public final fun withClosed (Lkotlin/jvm/functions/Function0;)Ljava/lang/Object; + public final fun commitStaging (Lkotlin/jvm/functions/Function1;)V + public final fun firstActiveOrNull (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/InlineLinkedList$InlineListNode; + public final fun forEachActive$wf1_workflow_runtime (Lkotlin/jvm/functions/Function1;)V + public final fun forEachStaging (Lkotlin/jvm/functions/Function1;)V + public final fun getActive ()Lcom/squareup/workflow1/InlineLinkedList; + public final fun getStaging ()Lcom/squareup/workflow1/InlineLinkedList; + public final fun removeAndStage (Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/InlineLinkedList$InlineListNode;)V + public final fun retainOrCreate$wf1_workflow_runtime (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;)Lcom/squareup/workflow1/InlineLinkedList$InlineListNode; + public final fun setActive (Lcom/squareup/workflow1/InlineLinkedList;)V + public final fun setStaging (Lcom/squareup/workflow1/InlineLinkedList;)V +} + +public class com/squareup/workflow1/ChainedWorkflowInterceptor : com/squareup/workflow1/WorkflowInterceptor { + public fun (Ljava/util/List;)V + protected fun getInterceptors ()Ljava/util/List; + 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 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; + protected final fun wrap (Lcom/squareup/workflow1/WorkflowInterceptor$RenderContextInterceptor;Lcom/squareup/workflow1/WorkflowInterceptor$RenderContextInterceptor;)Lcom/squareup/workflow1/WorkflowInterceptor$RenderContextInterceptor; +} + +public final class com/squareup/workflow1/ChainedWorkflowInterceptorKt { + public static final fun chained (Ljava/util/List;)Lcom/squareup/workflow1/WorkflowInterceptor; +} + +public final class com/squareup/workflow1/IdCounter { + public fun ()V + public final fun createId ()J +} + +public final class com/squareup/workflow1/IdCounterKt { + public static final fun createId (Lcom/squareup/workflow1/IdCounter;)J +} + +public final class com/squareup/workflow1/InlineLinkedList { + public fun ()V + public final fun clear ()V + public final fun firstOrNull (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/InlineLinkedList$InlineListNode; + public final fun forEach (Lkotlin/jvm/functions/Function1;)V + public final fun getHead ()Lcom/squareup/workflow1/InlineLinkedList$InlineListNode; + public final fun getTail ()Lcom/squareup/workflow1/InlineLinkedList$InlineListNode; + public final fun plusAssign (Lcom/squareup/workflow1/InlineLinkedList$InlineListNode;)V + public final fun removeFirst (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/InlineLinkedList$InlineListNode; + public final fun setHead (Lcom/squareup/workflow1/InlineLinkedList$InlineListNode;)V + public final fun setTail (Lcom/squareup/workflow1/InlineLinkedList$InlineListNode;)V +} + +public abstract interface class com/squareup/workflow1/InlineLinkedList$InlineListNode { + public abstract fun getNextListNode ()Lcom/squareup/workflow1/InlineLinkedList$InlineListNode; + public abstract fun setNextListNode (Lcom/squareup/workflow1/InlineLinkedList$InlineListNode;)V +} + +public class com/squareup/workflow1/InterceptedRenderContext : com/squareup/workflow1/BaseRenderContext, com/squareup/workflow1/Sink { + public fun (Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/WorkflowInterceptor$RenderContextInterceptor;)V + public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function10;)Lkotlin/jvm/functions/Function9; + public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function11;)Lkotlin/jvm/functions/Function10; + public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;)Lkotlin/jvm/functions/Function0; + public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function2;)Lkotlin/jvm/functions/Function1; + public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function3;)Lkotlin/jvm/functions/Function2; + public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function4;)Lkotlin/jvm/functions/Function3; + public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function5;)Lkotlin/jvm/functions/Function4; + public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function6;)Lkotlin/jvm/functions/Function5; + public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function7;)Lkotlin/jvm/functions/Function6; + public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function8;)Lkotlin/jvm/functions/Function7; + public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function9;)Lkotlin/jvm/functions/Function8; + public fun getActionSink ()Lcom/squareup/workflow1/Sink; + public fun renderChild (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; + public fun runningSideEffect (Ljava/lang/String;Lkotlin/jvm/functions/Function2;)V + public fun send (Lcom/squareup/workflow1/WorkflowAction;)V + public synthetic fun send (Ljava/lang/Object;)V } public final class com/squareup/workflow1/NoopWorkflowInterceptor : com/squareup/workflow1/WorkflowInterceptor { - public static final field $stable I public static final field INSTANCE Lcom/squareup/workflow1/NoopWorkflowInterceptor; - public fun Rendering (Ljava/lang/Object;Ljava/lang/Object;Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lkotlin/jvm/functions/Function5;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; 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; @@ -18,13 +83,43 @@ public final class com/squareup/workflow1/NoopWorkflowInterceptor : com/squareup public fun onSnapshotState (Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Lcom/squareup/workflow1/Snapshot; } +public class com/squareup/workflow1/RealRenderContext : com/squareup/workflow1/BaseRenderContext, com/squareup/workflow1/Sink { + public fun (Lcom/squareup/workflow1/RealRenderContext$Renderer;Lcom/squareup/workflow1/RealRenderContext$SideEffectRunner;Lkotlinx/coroutines/channels/SendChannel;)V + protected final fun checkNotFrozen ()V + public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function10;)Lkotlin/jvm/functions/Function9; + public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function11;)Lkotlin/jvm/functions/Function10; + public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;)Lkotlin/jvm/functions/Function0; + public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function2;)Lkotlin/jvm/functions/Function1; + public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function3;)Lkotlin/jvm/functions/Function2; + public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function4;)Lkotlin/jvm/functions/Function3; + public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function5;)Lkotlin/jvm/functions/Function4; + public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function6;)Lkotlin/jvm/functions/Function5; + public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function7;)Lkotlin/jvm/functions/Function6; + public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function8;)Lkotlin/jvm/functions/Function7; + public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function9;)Lkotlin/jvm/functions/Function8; + public final fun freeze ()V + public fun getActionSink ()Lcom/squareup/workflow1/Sink; + protected fun getRenderer ()Lcom/squareup/workflow1/RealRenderContext$Renderer; + public fun renderChild (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; + public fun runningSideEffect (Ljava/lang/String;Lkotlin/jvm/functions/Function2;)V + public fun send (Lcom/squareup/workflow1/WorkflowAction;)V + public synthetic fun send (Ljava/lang/Object;)V +} + +public abstract interface class com/squareup/workflow1/RealRenderContext$Renderer { + public abstract fun render (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; +} + +public abstract interface class com/squareup/workflow1/RealRenderContext$SideEffectRunner { + public abstract fun runningSideEffect (Ljava/lang/String;Lkotlin/jvm/functions/Function2;)V +} + public final class com/squareup/workflow1/RenderWorkflowKt { - public static final fun renderWorkflowIn (Lcom/squareup/workflow1/Workflow;Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/flow/StateFlow;Lcom/squareup/workflow1/TreeSnapshot;Ljava/util/List;Lcom/squareup/workflow1/RuntimeConfig;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/StateFlow; - public static synthetic fun renderWorkflowIn$default (Lcom/squareup/workflow1/Workflow;Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/flow/StateFlow;Lcom/squareup/workflow1/TreeSnapshot;Ljava/util/List;Lcom/squareup/workflow1/RuntimeConfig;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/flow/StateFlow; + public static final fun renderWorkflowIn (Lcom/squareup/workflow1/Workflow;Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/flow/StateFlow;Lcom/squareup/workflow1/TreeSnapshot;Ljava/util/List;Lcom/squareup/workflow1/RuntimeConfig;Lcom/squareup/workflow1/WorkflowRuntimePlugin;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/StateFlow; + public static synthetic fun renderWorkflowIn$default (Lcom/squareup/workflow1/Workflow;Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/flow/StateFlow;Lcom/squareup/workflow1/TreeSnapshot;Ljava/util/List;Lcom/squareup/workflow1/RuntimeConfig;Lcom/squareup/workflow1/WorkflowRuntimePlugin;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/flow/StateFlow; } public final class com/squareup/workflow1/RenderingAndSnapshot { - public static final field $stable I public fun (Ljava/lang/Object;Lcom/squareup/workflow1/TreeSnapshot;)V public final fun component1 ()Ljava/lang/Object; public final fun component2 ()Lcom/squareup/workflow1/TreeSnapshot; @@ -42,7 +137,6 @@ public final class com/squareup/workflow1/RuntimeConfig$Companion { } public final class com/squareup/workflow1/RuntimeConfig$FrameTimeout : com/squareup/workflow1/RuntimeConfig { - public static final field $stable I public fun ()V public fun (JZ)V public synthetic fun (JZILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -58,15 +152,12 @@ public final class com/squareup/workflow1/RuntimeConfig$FrameTimeout : com/squar } public final class com/squareup/workflow1/RuntimeConfig$RenderPerAction : com/squareup/workflow1/RuntimeConfig { - public static final field $stable I public static final field INSTANCE Lcom/squareup/workflow1/RuntimeConfig$RenderPerAction; public fun getUseComposeInRuntime ()Z } public class com/squareup/workflow1/SimpleLoggingWorkflowInterceptor : com/squareup/workflow1/WorkflowInterceptor { - public static final field $stable I public fun ()V - public fun Rendering (Ljava/lang/Object;Ljava/lang/Object;Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lkotlin/jvm/functions/Function5;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; protected fun log (Ljava/lang/String;)V protected fun logAfterMethod (Ljava/lang/String;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;[Lkotlin/Pair;)V protected fun logBeforeMethod (Ljava/lang/String;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;[Lkotlin/Pair;)V @@ -78,13 +169,31 @@ public class com/squareup/workflow1/SimpleLoggingWorkflowInterceptor : com/squar public fun onSnapshotState (Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Lcom/squareup/workflow1/Snapshot; } +public class com/squareup/workflow1/SubtreeManager : com/squareup/workflow1/RealRenderContext$Renderer { + public fun (Ljava/util/Map;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lcom/squareup/workflow1/WorkflowInterceptor;Lcom/squareup/workflow1/IdCounter;)V + public synthetic fun (Ljava/util/Map;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lcom/squareup/workflow1/WorkflowInterceptor;Lcom/squareup/workflow1/IdCounter;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun commitRenderedChildren ()V + protected fun createChildNode (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/WorkflowChildNode; + public final fun createChildSnapshots ()Ljava/util/Map; + protected final fun getChildren ()Lcom/squareup/workflow1/ActiveStagingList; + protected final fun getContextForChildren ()Lkotlin/coroutines/CoroutineContext; + protected final fun getEmitActionToParent ()Lkotlin/jvm/functions/Function1; + protected final fun getIdCounter ()Lcom/squareup/workflow1/IdCounter; + protected fun getInterceptor ()Lcom/squareup/workflow1/WorkflowInterceptor; + protected final fun getSnapshotCache ()Ljava/util/Map; + protected final fun getWorkflowSession ()Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession; + public fun render (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; + protected final fun setChildren (Lcom/squareup/workflow1/ActiveStagingList;)V + protected final fun setSnapshotCache (Ljava/util/Map;)V + public final fun tickChildren$wf1_workflow_runtime (Lkotlinx/coroutines/selects/SelectBuilder;)V +} + public final class com/squareup/workflow1/TreeSnapshot { - public static final field $stable I public static final field Companion Lcom/squareup/workflow1/TreeSnapshot$Companion; public fun (Lcom/squareup/workflow1/Snapshot;Lkotlin/jvm/functions/Function0;)V public fun equals (Ljava/lang/Object;)Z - public final fun getChildTreeSnapshots$wf1_workflow_runtime ()Ljava/util/Map; - public final fun getWorkflowSnapshot$wf1_workflow_runtime ()Lcom/squareup/workflow1/Snapshot; + public final fun getChildTreeSnapshots ()Ljava/util/Map; + public final fun getWorkflowSnapshot ()Lcom/squareup/workflow1/Snapshot; public fun hashCode ()I public final fun toByteString ()Lokio/ByteString; } @@ -94,11 +203,25 @@ public final class com/squareup/workflow1/TreeSnapshot$Companion { public final fun parse (Lokio/ByteString;)Lcom/squareup/workflow1/TreeSnapshot; } +public class com/squareup/workflow1/WorkflowChildNode : com/squareup/workflow1/InlineLinkedList$InlineListNode { + public fun (Lcom/squareup/workflow1/Workflow;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/WorkflowNode;)V + public final fun acceptChildOutput (Ljava/lang/Object;)Lcom/squareup/workflow1/WorkflowAction; + public final fun getId ()Lcom/squareup/workflow1/WorkflowNodeId; + public synthetic fun getNextListNode ()Lcom/squareup/workflow1/InlineLinkedList$InlineListNode; + public fun getNextListNode ()Lcom/squareup/workflow1/WorkflowChildNode; + public final fun getWorkflow ()Lcom/squareup/workflow1/Workflow; + public final fun getWorkflowNode ()Lcom/squareup/workflow1/WorkflowNode; + public final fun matches (Lcom/squareup/workflow1/Workflow;Ljava/lang/String;)Z + public final fun render (Lcom/squareup/workflow1/StatefulWorkflow;Ljava/lang/Object;)Ljava/lang/Object; + public final fun setHandler$wf1_workflow_runtime (Lkotlin/jvm/functions/Function1;)V + public synthetic fun setNextListNode (Lcom/squareup/workflow1/InlineLinkedList$InlineListNode;)V + public fun setNextListNode (Lcom/squareup/workflow1/WorkflowChildNode;)V +} + public abstract interface annotation class com/squareup/workflow1/WorkflowExperimentalRuntime : java/lang/annotation/Annotation { } public abstract interface class com/squareup/workflow1/WorkflowInterceptor { - public abstract fun Rendering (Ljava/lang/Object;Ljava/lang/Object;Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lkotlin/jvm/functions/Function5;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; 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; @@ -107,7 +230,6 @@ public abstract interface class com/squareup/workflow1/WorkflowInterceptor { } public final class com/squareup/workflow1/WorkflowInterceptor$DefaultImpls { - public static fun Rendering (Lcom/squareup/workflow1/WorkflowInterceptor;Ljava/lang/Object;Ljava/lang/Object;Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lkotlin/jvm/functions/Function5;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; 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; @@ -116,14 +238,12 @@ public final class com/squareup/workflow1/WorkflowInterceptor$DefaultImpls { } public abstract interface class com/squareup/workflow1/WorkflowInterceptor$RenderContextInterceptor { - public abstract fun ChildRendering (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function6;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; public abstract fun onActionSent (Lcom/squareup/workflow1/WorkflowAction;Lkotlin/jvm/functions/Function1;)V public abstract fun onRenderChild (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function4;)Ljava/lang/Object; public abstract fun onRunningSideEffect (Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)V } public final class com/squareup/workflow1/WorkflowInterceptor$RenderContextInterceptor$DefaultImpls { - public static fun ChildRendering (Lcom/squareup/workflow1/WorkflowInterceptor$RenderContextInterceptor;Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function6;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; public static fun onActionSent (Lcom/squareup/workflow1/WorkflowInterceptor$RenderContextInterceptor;Lcom/squareup/workflow1/WorkflowAction;Lkotlin/jvm/functions/Function1;)V public static fun onRenderChild (Lcom/squareup/workflow1/WorkflowInterceptor$RenderContextInterceptor;Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function4;)Ljava/lang/Object; public static fun onRunningSideEffect (Lcom/squareup/workflow1/WorkflowInterceptor$RenderContextInterceptor;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)V @@ -140,196 +260,102 @@ public final class com/squareup/workflow1/WorkflowInterceptorKt { public static final fun intercept (Lcom/squareup/workflow1/WorkflowInterceptor;Lcom/squareup/workflow1/StatefulWorkflow;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Lcom/squareup/workflow1/StatefulWorkflow; } -public final class com/squareup/workflow1/WorkflowRuntimeClock : androidx/compose/runtime/MonotonicFrameClock { - public fun (Lcom/squareup/workflow1/Latch;)V - public fun fold (Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object; - public fun get (Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element; - public fun getKey ()Lkotlin/coroutines/CoroutineContext$Key; - public fun minusKey (Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext; - public fun plus (Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext; - public fun withFrameNanos (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - -public final class com/squareup/workflow1/internal/ActiveStagingList { - public fun ()V - public final fun commitStaging (Lkotlin/jvm/functions/Function1;)V - public final fun firstActiveOrNull (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/internal/InlineLinkedList$InlineListNode; - public final fun forEachActive (Lkotlin/jvm/functions/Function1;)V - public final fun forEachStaging (Lkotlin/jvm/functions/Function1;)V - public final fun removeAndStage (Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/internal/InlineLinkedList$InlineListNode;)V - public final fun retainOrCreate (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;)Lcom/squareup/workflow1/internal/InlineLinkedList$InlineListNode; -} - -public final class com/squareup/workflow1/internal/ChainedWorkflowInterceptor : com/squareup/workflow1/WorkflowInterceptor { - public fun (Ljava/util/List;)V - public fun Rendering (Ljava/lang/Object;Ljava/lang/Object;Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lkotlin/jvm/functions/Function5;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; - 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 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 final class com/squareup/workflow1/internal/ChainedWorkflowInterceptorKt { - public static final fun chained (Ljava/util/List;)Lcom/squareup/workflow1/WorkflowInterceptor; -} - -public final class com/squareup/workflow1/internal/IdCounter { - public fun ()V - public final fun createId ()J -} - -public final class com/squareup/workflow1/internal/IdCounterKt { - public static final fun createId (Lcom/squareup/workflow1/internal/IdCounter;)J -} - -public final class com/squareup/workflow1/internal/InlineLinkedList { - public fun ()V - public final fun clear ()V - public final fun firstOrNull (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/internal/InlineLinkedList$InlineListNode; - public final fun forEach (Lkotlin/jvm/functions/Function1;)V - public final fun getHead ()Lcom/squareup/workflow1/internal/InlineLinkedList$InlineListNode; - public final fun getTail ()Lcom/squareup/workflow1/internal/InlineLinkedList$InlineListNode; - public final fun plusAssign (Lcom/squareup/workflow1/internal/InlineLinkedList$InlineListNode;)V - public final fun removeFirst (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/internal/InlineLinkedList$InlineListNode; - public final fun setHead (Lcom/squareup/workflow1/internal/InlineLinkedList$InlineListNode;)V - public final fun setTail (Lcom/squareup/workflow1/internal/InlineLinkedList$InlineListNode;)V -} - -public abstract interface class com/squareup/workflow1/internal/InlineLinkedList$InlineListNode { - public abstract fun getNextListNode ()Lcom/squareup/workflow1/internal/InlineLinkedList$InlineListNode; - public abstract fun setNextListNode (Lcom/squareup/workflow1/internal/InlineLinkedList$InlineListNode;)V -} - -public final class com/squareup/workflow1/internal/RealRenderContext : com/squareup/workflow1/BaseRenderContext, com/squareup/workflow1/Sink { - public fun (Lcom/squareup/workflow1/internal/RealRenderContext$Renderer;Lcom/squareup/workflow1/internal/RealRenderContext$SideEffectRunner;Lkotlinx/coroutines/channels/SendChannel;)V - public fun ChildRendering (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; - public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function10;)Lkotlin/jvm/functions/Function9; - public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function11;)Lkotlin/jvm/functions/Function10; - public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;)Lkotlin/jvm/functions/Function0; - public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function2;)Lkotlin/jvm/functions/Function1; - public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function3;)Lkotlin/jvm/functions/Function2; - public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function4;)Lkotlin/jvm/functions/Function3; - public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function5;)Lkotlin/jvm/functions/Function4; - public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function6;)Lkotlin/jvm/functions/Function5; - public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function7;)Lkotlin/jvm/functions/Function6; - public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function8;)Lkotlin/jvm/functions/Function7; - public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function9;)Lkotlin/jvm/functions/Function8; - public final fun freeze ()V - public fun getActionSink ()Lcom/squareup/workflow1/Sink; - public fun renderChild (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; - public fun runningSideEffect (Ljava/lang/String;Lkotlin/jvm/functions/Function2;)V - public fun send (Lcom/squareup/workflow1/WorkflowAction;)V - public synthetic fun send (Ljava/lang/Object;)V -} - -public abstract interface class com/squareup/workflow1/internal/RealRenderContext$Renderer { - public abstract fun Rendering (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; - public abstract fun render (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; -} - -public abstract interface class com/squareup/workflow1/internal/RealRenderContext$SideEffectRunner { - public abstract fun runningSideEffect (Ljava/lang/String;Lkotlin/jvm/functions/Function2;)V -} - -public final class com/squareup/workflow1/internal/SideEffectNode : com/squareup/workflow1/internal/InlineLinkedList$InlineListNode { - public fun (Ljava/lang/String;Lkotlinx/coroutines/Job;)V - public final fun getJob ()Lkotlinx/coroutines/Job; - public final fun getKey ()Ljava/lang/String; - public synthetic fun getNextListNode ()Lcom/squareup/workflow1/internal/InlineLinkedList$InlineListNode; - public fun getNextListNode ()Lcom/squareup/workflow1/internal/SideEffectNode; - public synthetic fun setNextListNode (Lcom/squareup/workflow1/internal/InlineLinkedList$InlineListNode;)V - public fun setNextListNode (Lcom/squareup/workflow1/internal/SideEffectNode;)V -} - -public final class com/squareup/workflow1/internal/SubtreeManager : com/squareup/workflow1/internal/RealRenderContext$Renderer { - public fun (Ljava/util/Map;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lcom/squareup/workflow1/WorkflowInterceptor;Lcom/squareup/workflow1/internal/IdCounter;)V - public synthetic fun (Ljava/util/Map;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lcom/squareup/workflow1/WorkflowInterceptor;Lcom/squareup/workflow1/internal/IdCounter;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun Rendering (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; - public final fun commitRenderedChildren ()V - public final fun createChildSnapshots ()Ljava/util/Map; - public fun render (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; - public final fun tickChildren (Lkotlinx/coroutines/selects/SelectBuilder;)V -} - -public final class com/squareup/workflow1/internal/SystemUtilsKt { - public static final fun currentTimeMillis ()J - public static final fun nanoTime ()J -} - -public final class com/squareup/workflow1/internal/ThrowablesKt { - public static final fun unwrapCancellationCause (Ljava/lang/Throwable;)Ljava/lang/Throwable; -} - -public final class com/squareup/workflow1/internal/WorkflowChildNode : com/squareup/workflow1/internal/InlineLinkedList$InlineListNode { - public fun (Lcom/squareup/workflow1/Workflow;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/internal/WorkflowNode;)V - public final fun Rendering (Lcom/squareup/workflow1/StatefulWorkflow;Ljava/lang/Object;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; - public final fun acceptChildOutput (Ljava/lang/Object;)Lcom/squareup/workflow1/WorkflowAction; - public final fun getId ()Lcom/squareup/workflow1/internal/WorkflowNodeId; - public synthetic fun getNextListNode ()Lcom/squareup/workflow1/internal/InlineLinkedList$InlineListNode; - public fun getNextListNode ()Lcom/squareup/workflow1/internal/WorkflowChildNode; - public final fun getWorkflow ()Lcom/squareup/workflow1/Workflow; - public final fun getWorkflowNode ()Lcom/squareup/workflow1/internal/WorkflowNode; - public final fun matches (Lcom/squareup/workflow1/Workflow;Ljava/lang/String;)Z - public final fun render (Lcom/squareup/workflow1/StatefulWorkflow;Ljava/lang/Object;)Ljava/lang/Object; - public final fun setHandler (Lkotlin/jvm/functions/Function1;)V - public synthetic fun setNextListNode (Lcom/squareup/workflow1/internal/InlineLinkedList$InlineListNode;)V - public fun setNextListNode (Lcom/squareup/workflow1/internal/WorkflowChildNode;)V -} - -public final class com/squareup/workflow1/internal/WorkflowNode : com/squareup/workflow1/WorkflowInterceptor$WorkflowSession, com/squareup/workflow1/internal/RealRenderContext$SideEffectRunner, kotlinx/coroutines/CoroutineScope { - public fun (Lcom/squareup/workflow1/internal/WorkflowNodeId;Lcom/squareup/workflow1/StatefulWorkflow;Ljava/lang/Object;Lcom/squareup/workflow1/TreeSnapshot;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lcom/squareup/workflow1/WorkflowInterceptor;Lcom/squareup/workflow1/internal/IdCounter;)V - public synthetic fun (Lcom/squareup/workflow1/internal/WorkflowNodeId;Lcom/squareup/workflow1/StatefulWorkflow;Ljava/lang/Object;Lcom/squareup/workflow1/TreeSnapshot;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lcom/squareup/workflow1/WorkflowInterceptor;Lcom/squareup/workflow1/internal/IdCounter;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun Rendering (Lcom/squareup/workflow1/StatefulWorkflow;Ljava/lang/Object;Landroidx/compose/runtime/MutableState;Landroidx/compose/runtime/Composer;I)V - public final fun cancel (Ljava/util/concurrent/CancellationException;)V - public static synthetic fun cancel$default (Lcom/squareup/workflow1/internal/WorkflowNode;Ljava/util/concurrent/CancellationException;ILjava/lang/Object;)V +public class com/squareup/workflow1/WorkflowNode : com/squareup/workflow1/RealRenderContext$SideEffectRunner, com/squareup/workflow1/WorkflowInterceptor$WorkflowSession, kotlinx/coroutines/CoroutineScope { + public fun (Lcom/squareup/workflow1/WorkflowNodeId;Lcom/squareup/workflow1/StatefulWorkflow;Ljava/lang/Object;Lcom/squareup/workflow1/TreeSnapshot;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lcom/squareup/workflow1/WorkflowInterceptor;Lcom/squareup/workflow1/IdCounter;)V + public synthetic fun (Lcom/squareup/workflow1/WorkflowNodeId;Lcom/squareup/workflow1/StatefulWorkflow;Ljava/lang/Object;Lcom/squareup/workflow1/TreeSnapshot;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lcom/squareup/workflow1/WorkflowInterceptor;Lcom/squareup/workflow1/IdCounter;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + protected final fun applyAction (Lcom/squareup/workflow1/WorkflowAction;)Ljava/lang/Object; + public final fun cancel$wf1_workflow_runtime (Ljava/util/concurrent/CancellationException;)V + public static synthetic fun cancel$wf1_workflow_runtime$default (Lcom/squareup/workflow1/WorkflowNode;Ljava/util/concurrent/CancellationException;ILjava/lang/Object;)V + protected final fun commitAndUpdateScopes ()V public fun getCoroutineContext ()Lkotlin/coroutines/CoroutineContext; - public final fun getId ()Lcom/squareup/workflow1/internal/WorkflowNodeId; + protected final fun getEmitOutputToParent ()Lkotlin/jvm/functions/Function1; + protected final fun getEventActionsChannel ()Lkotlinx/coroutines/channels/Channel; + public final fun getId ()Lcom/squareup/workflow1/WorkflowNodeId; public fun getIdentifier ()Lcom/squareup/workflow1/WorkflowIdentifier; + protected final fun getInterceptor ()Lcom/squareup/workflow1/WorkflowInterceptor; + protected final fun getLastProps ()Ljava/lang/Object; public fun getParent ()Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession; public fun getRenderKey ()Ljava/lang/String; public fun getSessionId ()J + protected fun getState ()Ljava/lang/Object; + protected fun getSubtreeManager ()Lcom/squareup/workflow1/SubtreeManager; + protected final fun getWorkflow ()Lcom/squareup/workflow1/StatefulWorkflow; public final fun render (Lcom/squareup/workflow1/StatefulWorkflow;Ljava/lang/Object;)Ljava/lang/Object; public fun runningSideEffect (Ljava/lang/String;Lkotlin/jvm/functions/Function2;)V + protected final fun setLastProps (Ljava/lang/Object;)V + protected fun setState (Ljava/lang/Object;)V public final fun snapshot (Lcom/squareup/workflow1/StatefulWorkflow;)Lcom/squareup/workflow1/TreeSnapshot; - public final fun tick (Lkotlinx/coroutines/selects/SelectBuilder;)V + public final fun startSession ()V + public final fun tick$wf1_workflow_runtime (Lkotlinx/coroutines/selects/SelectBuilder;)V public fun toString ()Ljava/lang/String; } -public final class com/squareup/workflow1/internal/WorkflowNodeId { - public static final field Companion Lcom/squareup/workflow1/internal/WorkflowNodeId$Companion; +public final class com/squareup/workflow1/WorkflowNodeId { + public static final field Companion Lcom/squareup/workflow1/WorkflowNodeId$Companion; public fun (Lcom/squareup/workflow1/Workflow;Ljava/lang/String;)V public synthetic fun (Lcom/squareup/workflow1/Workflow;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun (Lcom/squareup/workflow1/WorkflowIdentifier;Ljava/lang/String;)V public synthetic fun (Lcom/squareup/workflow1/WorkflowIdentifier;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1$wf1_workflow_runtime ()Lcom/squareup/workflow1/WorkflowIdentifier; public final fun component2$wf1_workflow_runtime ()Ljava/lang/String; - public final fun copy (Lcom/squareup/workflow1/WorkflowIdentifier;Ljava/lang/String;)Lcom/squareup/workflow1/internal/WorkflowNodeId; - public static synthetic fun copy$default (Lcom/squareup/workflow1/internal/WorkflowNodeId;Lcom/squareup/workflow1/WorkflowIdentifier;Ljava/lang/String;ILjava/lang/Object;)Lcom/squareup/workflow1/internal/WorkflowNodeId; + public final fun copy (Lcom/squareup/workflow1/WorkflowIdentifier;Ljava/lang/String;)Lcom/squareup/workflow1/WorkflowNodeId; + public static synthetic fun copy$default (Lcom/squareup/workflow1/WorkflowNodeId;Lcom/squareup/workflow1/WorkflowIdentifier;Ljava/lang/String;ILjava/lang/Object;)Lcom/squareup/workflow1/WorkflowNodeId; public fun equals (Ljava/lang/Object;)Z public final fun getIdentifier$wf1_workflow_runtime ()Lcom/squareup/workflow1/WorkflowIdentifier; public final fun getName$wf1_workflow_runtime ()Ljava/lang/String; public fun hashCode ()I - public final fun matches (Lcom/squareup/workflow1/Workflow;Ljava/lang/String;)Z + public final fun matches$wf1_workflow_runtime (Lcom/squareup/workflow1/Workflow;Ljava/lang/String;)Z public final fun toByteStringOrNull$wf1_workflow_runtime ()Lokio/ByteString; public fun toString ()Ljava/lang/String; } -public final class com/squareup/workflow1/internal/WorkflowNodeId$Companion { - public final fun parse (Lokio/ByteString;)Lcom/squareup/workflow1/internal/WorkflowNodeId; +public final class com/squareup/workflow1/WorkflowNodeId$Companion { + public final fun parse (Lokio/ByteString;)Lcom/squareup/workflow1/WorkflowNodeId; } -public final class com/squareup/workflow1/internal/WorkflowNodeIdKt { - public static final fun id (Lcom/squareup/workflow1/Workflow;Ljava/lang/String;)Lcom/squareup/workflow1/internal/WorkflowNodeId; - public static synthetic fun id$default (Lcom/squareup/workflow1/Workflow;Ljava/lang/String;ILjava/lang/Object;)Lcom/squareup/workflow1/internal/WorkflowNodeId; +public final class com/squareup/workflow1/WorkflowNodeIdKt { + public static final fun id (Lcom/squareup/workflow1/Workflow;Ljava/lang/String;)Lcom/squareup/workflow1/WorkflowNodeId; + public static synthetic fun id$default (Lcom/squareup/workflow1/Workflow;Ljava/lang/String;ILjava/lang/Object;)Lcom/squareup/workflow1/WorkflowNodeId; } -public final class com/squareup/workflow1/internal/WorkflowRunner { +public class com/squareup/workflow1/WorkflowRunner { public fun (Lkotlinx/coroutines/CoroutineScope;Lcom/squareup/workflow1/Workflow;Lkotlinx/coroutines/flow/StateFlow;Lcom/squareup/workflow1/TreeSnapshot;Lcom/squareup/workflow1/WorkflowInterceptor;Lcom/squareup/workflow1/RuntimeConfig;)V - public final fun cancelRuntime (Ljava/util/concurrent/CancellationException;)V - public static synthetic fun cancelRuntime$default (Lcom/squareup/workflow1/internal/WorkflowRunner;Ljava/util/concurrent/CancellationException;ILjava/lang/Object;)V - public final fun nextComposedRendering (Landroidx/compose/runtime/Composer;I)Lcom/squareup/workflow1/RenderingAndSnapshot; - public final fun nextRendering ()Lcom/squareup/workflow1/RenderingAndSnapshot; - public final fun processActions (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun cancelRuntime$wf1_workflow_runtime (Ljava/util/concurrent/CancellationException;)V + public static synthetic fun cancelRuntime$wf1_workflow_runtime$default (Lcom/squareup/workflow1/WorkflowRunner;Ljava/util/concurrent/CancellationException;ILjava/lang/Object;)V + protected fun getCurrentProps ()Ljava/lang/Object; + protected final fun getIdCounter ()Lcom/squareup/workflow1/IdCounter; + protected final fun getPropsChannel ()Lkotlinx/coroutines/channels/ReceiveChannel; + protected fun getRootNode ()Lcom/squareup/workflow1/WorkflowNode; + protected final fun getRuntimeConfig ()Lcom/squareup/workflow1/RuntimeConfig; + protected final fun getWorkflow ()Lcom/squareup/workflow1/StatefulWorkflow; + public final fun nextRendering$wf1_workflow_runtime ()Lcom/squareup/workflow1/RenderingAndSnapshot; + public final fun processActions$wf1_workflow_runtime (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + protected fun setCurrentProps (Ljava/lang/Object;)V +} + +public abstract interface class com/squareup/workflow1/WorkflowRuntimePlugin { + public abstract fun chainedInterceptors (Ljava/util/List;)Lcom/squareup/workflow1/WorkflowInterceptor; + public abstract fun createWorkflowRunner (Lkotlinx/coroutines/CoroutineScope;Lcom/squareup/workflow1/Workflow;Lkotlinx/coroutines/flow/StateFlow;Lcom/squareup/workflow1/TreeSnapshot;Lcom/squareup/workflow1/WorkflowInterceptor;Lcom/squareup/workflow1/RuntimeConfig;)Lcom/squareup/workflow1/WorkflowRunner; + public abstract fun initializeRenderingStream (Lcom/squareup/workflow1/WorkflowRunner;Lkotlinx/coroutines/CoroutineScope;)Lkotlinx/coroutines/flow/StateFlow; + public abstract fun nextRendering (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/squareup/workflow1/internal/SideEffectNode : com/squareup/workflow1/InlineLinkedList$InlineListNode { + public fun (Ljava/lang/String;Lkotlinx/coroutines/Job;)V + public final fun getJob ()Lkotlinx/coroutines/Job; + public final fun getKey ()Ljava/lang/String; + public synthetic fun getNextListNode ()Lcom/squareup/workflow1/InlineLinkedList$InlineListNode; + public fun getNextListNode ()Lcom/squareup/workflow1/internal/SideEffectNode; + public synthetic fun setNextListNode (Lcom/squareup/workflow1/InlineLinkedList$InlineListNode;)V + public fun setNextListNode (Lcom/squareup/workflow1/internal/SideEffectNode;)V +} + +public final class com/squareup/workflow1/internal/SystemUtilsKt { + public static final fun currentTimeMillis ()J + public static final fun nanoTime ()J +} + +public final class com/squareup/workflow1/internal/ThrowablesKt { + public static final fun unwrapCancellationCause (Ljava/lang/Throwable;)Ljava/lang/Throwable; } diff --git a/workflow-runtime/build.gradle.kts b/workflow-runtime/build.gradle.kts index 2370ac86e6..9433f1c302 100644 --- a/workflow-runtime/build.gradle.kts +++ b/workflow-runtime/build.gradle.kts @@ -5,7 +5,6 @@ plugins { `kotlin-multiplatform` published id("org.jetbrains.kotlinx.benchmark") - id("app.cash.molecule") } kotlin { @@ -38,9 +37,7 @@ kotlin { val commonMain by getting { dependencies { api(project(":workflow-core")) - api(libs.compose.runtime) api(libs.kotlinx.coroutines.core) - implementation(libs.molecule.runtime) } } val commonTest by getting { diff --git a/workflow-runtime/dependencies/jvmRuntimeClasspath.txt b/workflow-runtime/dependencies/jvmRuntimeClasspath.txt index ae2a254c1f..6b1f421e4c 100644 --- a/workflow-runtime/dependencies/jvmRuntimeClasspath.txt +++ b/workflow-runtime/dependencies/jvmRuntimeClasspath.txt @@ -1,10 +1,6 @@ :workflow-core -app.cash.molecule:molecule-runtime-jvm:0.3.0-SNAPSHOT -app.cash.molecule:molecule-runtime:0.3.0-SNAPSHOT com.squareup.okio:okio-jvm:3.0.0 com.squareup.okio:okio:3.0.0 -org.jetbrains.compose.runtime:runtime-desktop:1.1.0 -org.jetbrains.compose.runtime:runtime:1.1.0 org.jetbrains.kotlin:kotlin-bom:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.10 diff --git a/workflow-runtime/dependencies/jvmWorkflowNodeRuntimeClasspath.txt b/workflow-runtime/dependencies/jvmWorkflowNodeRuntimeClasspath.txt index 4c79eb0088..34e2961192 100644 --- a/workflow-runtime/dependencies/jvmWorkflowNodeRuntimeClasspath.txt +++ b/workflow-runtime/dependencies/jvmWorkflowNodeRuntimeClasspath.txt @@ -1,15 +1,9 @@ -app.cash.molecule:molecule-runtime-jvm:0.3.0-SNAPSHOT -app.cash.molecule:molecule-runtime:0.3.0-SNAPSHOT net.sf.jopt-simple:jopt-simple:5.0.4 org.apache.commons:commons-math3:3.2 -org.jetbrains.compose.runtime:runtime-desktop:1.1.0 -org.jetbrains.compose.runtime:runtime:1.1.0 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.10 org.jetbrains.kotlin:kotlin-stdlib:1.6.10 org.jetbrains.kotlinx:kotlinx-benchmark-runtime-jvm:0.4.2 org.jetbrains.kotlinx:kotlinx-benchmark-runtime:0.4.2 -org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.2 -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2 org.jetbrains:annotations:13.0 org.openjdk.jmh:jmh-core:1.34 diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/ActiveStagingList.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/ActiveStagingList.kt similarity index 81% rename from workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/ActiveStagingList.kt rename to workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/ActiveStagingList.kt index 97101d9886..30a0ac30e4 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/ActiveStagingList.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/ActiveStagingList.kt @@ -1,6 +1,6 @@ -package com.squareup.workflow1.internal +package com.squareup.workflow1 -import com.squareup.workflow1.internal.InlineLinkedList.InlineListNode +import com.squareup.workflow1.InlineLinkedList.InlineListNode /** * Switches between two lists and provides certain lookup and swapping operations. @@ -14,7 +14,7 @@ import com.squareup.workflow1.internal.InlineLinkedList.InlineListNode * to swap the lists and clear the old active list. On commit, all items in the old active list will * be passed to the lambda passed to [commitStaging]. */ -internal class ActiveStagingList> { +public class ActiveStagingList> { /** * When not in the middle of a render pass, this list represents the active child workflows. @@ -24,7 +24,7 @@ internal class ActiveStagingList> { * During rendering, when a child is rendered, if it exists in this list it is removed from here * and added to [staging]. */ - private var active = InlineLinkedList() + public var active: InlineLinkedList = InlineLinkedList() /** * When not in the middle of a render pass, this list is empty. @@ -33,13 +33,13 @@ internal class ActiveStagingList> { * When [commitStaging] is called, this list is swapped with [active] and the old active list is * cleared. */ - private var staging = InlineLinkedList() + public var staging: InlineLinkedList = InlineLinkedList() /** * Looks for the first item matching [predicate] in the active list and moves it to the staging * list if found, else creates and appends a new item. */ - inline fun retainOrCreate( + internal inline fun retainOrCreate( predicate: (T) -> Boolean, create: () -> T ): T { @@ -52,7 +52,7 @@ internal class ActiveStagingList> { * Looks for the first item matching [predicate] in the active list and removes it from the active * list. Then puts [child] into the staging list. */ - inline fun removeAndStage( + public inline fun removeAndStage( predicate: (T) -> Boolean, child: T? ) { @@ -66,7 +66,7 @@ internal class ActiveStagingList> { * Returns a reference to the first item matching [predicate] in the active list, or null if * not found. */ - inline fun firstActiveOrNull( + public inline fun firstActiveOrNull( predicate: (T) -> Boolean ): T? = active.firstOrNull(predicate) @@ -74,7 +74,7 @@ internal class ActiveStagingList> { * Swaps the active and staging list and clears the old active list, passing items in the * old active list to [onRemove]. */ - inline fun commitStaging(onRemove: (T) -> Unit) { + public inline fun commitStaging(onRemove: (T) -> Unit) { // Any children left in the previous active list after the render finishes were not re-rendered // and must be torn down. active.forEach(onRemove) @@ -89,10 +89,10 @@ internal class ActiveStagingList> { /** * Iterates over the active list. */ - inline fun forEachActive(block: (T) -> Unit) = active.forEach(block) + internal inline fun forEachActive(block: (T) -> Unit): Unit = active.forEach(block) /** * Iterates over the staging list. */ - inline fun forEachStaging(block: (T) -> Unit) = staging.forEach(block) + public inline fun forEachStaging(block: (T) -> Unit): Unit = staging.forEach(block) } diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/ChainedWorkflowInterceptor.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/ChainedWorkflowInterceptor.kt similarity index 62% rename from workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/ChainedWorkflowInterceptor.kt rename to workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/ChainedWorkflowInterceptor.kt index ff3ae288b1..b89ac00e12 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/ChainedWorkflowInterceptor.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/ChainedWorkflowInterceptor.kt @@ -1,13 +1,5 @@ -package com.squareup.workflow1.internal +package com.squareup.workflow1 -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import com.squareup.workflow1.BaseRenderContext -import com.squareup.workflow1.NoopWorkflowInterceptor -import com.squareup.workflow1.Snapshot -import com.squareup.workflow1.Workflow -import com.squareup.workflow1.WorkflowAction -import com.squareup.workflow1.WorkflowInterceptor import com.squareup.workflow1.WorkflowInterceptor.RenderContextInterceptor import com.squareup.workflow1.WorkflowInterceptor.WorkflowSession import kotlinx.coroutines.CoroutineScope @@ -19,8 +11,8 @@ internal fun List.chained(): WorkflowInterceptor = else -> ChainedWorkflowInterceptor(this) } -internal class ChainedWorkflowInterceptor( - private val interceptors: List +public open class ChainedWorkflowInterceptor( + protected open val interceptors: List ) : WorkflowInterceptor { override fun onSessionStarted( @@ -83,44 +75,6 @@ internal class ChainedWorkflowInterceptor( return chainedProceed(renderProps, renderState, null) } - @Composable - override fun Rendering( - renderProps: P, - renderState: S, - context: BaseRenderContext, - session: WorkflowSession, - proceed: @Composable (P, S, RenderContextInterceptor?) -> R - ): R { - val chainedProceed = remember(session) { - interceptors.foldRight(proceed) { workflowInterceptor, proceedAcc -> - { props, state, outerContextInterceptor -> - // Holding compiler's hand for function type. - val proceedInternal = - remember<@Composable (P, S, RenderContextInterceptor?) -> R>( - outerContextInterceptor - ) { - @Composable { p: P, - s: S, - innerContextInterceptor: RenderContextInterceptor? -> - val contextInterceptor = remember(innerContextInterceptor) { - outerContextInterceptor.wrap(innerContextInterceptor) - } - proceedAcc(p, s, contextInterceptor) - } - } - workflowInterceptor.Rendering( - props, - state, - context, - proceed = proceedInternal, - session = session, - ) - } - } - } - return chainedProceed(renderProps, renderState, null) - } - override fun onSnapshotState( state: S, proceed: (S) -> Snapshot?, @@ -134,9 +88,9 @@ internal class ChainedWorkflowInterceptor( return chainedProceed(state) } - private fun RenderContextInterceptor?.wrap( + protected fun RenderContextInterceptor?.wrap( inner: RenderContextInterceptor? - ) = when { + ): RenderContextInterceptor? = when { this == null && inner == null -> null this == null -> inner inner == null -> this @@ -169,28 +123,6 @@ internal class ChainedWorkflowInterceptor( inner.onRenderChild(c, p, k, h, proceed) } - @Composable - override fun ChildRendering( - child: Workflow, - childProps: CP, - key: String, - handler: (CO) -> WorkflowAction, - proceed: @Composable ( - child: Workflow, - childProps: CP, - key: String, - handler: (CO) -> WorkflowAction - ) -> CR - ): CR = - outer.ChildRendering( - child, - childProps, - key, - handler - ) @Composable { c, p, k, h -> - inner.ChildRendering(c, p, k, h, proceed) - } - override fun onRunningSideEffect( key: String, sideEffect: suspend () -> Unit, diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/IdCounter.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/IdCounter.kt similarity index 74% rename from workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/IdCounter.kt rename to workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/IdCounter.kt index f3eb10be7d..2e45b2de3c 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/IdCounter.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/IdCounter.kt @@ -1,12 +1,12 @@ -package com.squareup.workflow1.internal +package com.squareup.workflow1 /** * Monotonically-increasing counter that produces longs, used to assign * [com.squareup.workflow1.WorkflowInterceptor.WorkflowSession.sessionId]. */ -internal class IdCounter { +public class IdCounter { private var nextId = 0L - fun createId(): Long = nextId++ + public fun createId(): Long = nextId++ } @Suppress("NOTHING_TO_INLINE") diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/InlineLinkedList.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/InlineLinkedList.kt similarity index 82% rename from workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/InlineLinkedList.kt rename to workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/InlineLinkedList.kt index b156dcc44a..907594550d 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/InlineLinkedList.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/InlineLinkedList.kt @@ -1,6 +1,6 @@ -package com.squareup.workflow1.internal +package com.squareup.workflow1 -import com.squareup.workflow1.internal.InlineLinkedList.InlineListNode +import com.squareup.workflow1.InlineLinkedList.InlineListNode /** * A simple singly-linked list that uses the list elements themselves to store the links. @@ -15,7 +15,7 @@ import com.squareup.workflow1.internal.InlineLinkedList.InlineListNode * - [plusAssign] * - [clear] */ -internal class InlineLinkedList> { +public class InlineLinkedList> { /** * Interface to be implemented by something that can be stored in an [InlineLinkedList]. @@ -23,19 +23,19 @@ internal class InlineLinkedList> { * @property nextListNode For use by [InlineLinkedList] only – implementors should never mutate * this property. It's default value should be null. */ - interface InlineListNode> { - var nextListNode: T? + public interface InlineListNode> { + public var nextListNode: T? } - var head: T? = null - var tail: T? = null + public var head: T? = null + public var tail: T? = null /** * Append an element to the end of the list. * * @throws IllegalArgumentException If node is already linked in another list. */ - operator fun plusAssign(node: T) { + public operator fun plusAssign(node: T) { require(node.nextListNode == null) { "Expected node to not be linked." } tail?.let { oldTail -> @@ -57,7 +57,7 @@ internal class InlineLinkedList> { * * @return The matching element, or null if not found. */ - inline fun removeFirst(predicate: (T) -> Boolean): T? { + public inline fun removeFirst(predicate: (T) -> Boolean): T? { var currentNode: T? = head var previousNode: T? = null @@ -87,7 +87,7 @@ internal class InlineLinkedList> { /** * Iterates over the list. */ - inline fun forEach(block: (T) -> Unit) { + public inline fun forEach(block: (T) -> Unit) { var currentNode = head while (currentNode != null) { block(currentNode) @@ -98,7 +98,7 @@ internal class InlineLinkedList> { /** * Returns the first item matching [predicate] in the list, or null. */ - inline fun firstOrNull(predicate: (T) -> Boolean): T? { + public inline fun firstOrNull(predicate: (T) -> Boolean): T? { var currentNode = head while (currentNode != null) { if (predicate(currentNode)) { @@ -112,7 +112,7 @@ internal class InlineLinkedList> { /** * Removes all elements from the list. */ - fun clear() { + public fun clear() { head = null tail = null } diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/RealRenderContext.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/RealRenderContext.kt similarity index 61% rename from workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/RealRenderContext.kt rename to workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/RealRenderContext.kt index 872cf04912..a1abc595fd 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/RealRenderContext.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/RealRenderContext.kt @@ -1,32 +1,18 @@ @file:Suppress("DEPRECATION") -package com.squareup.workflow1.internal +package com.squareup.workflow1 -import androidx.compose.runtime.Composable -import androidx.compose.runtime.key -import com.squareup.workflow1.BaseRenderContext -import com.squareup.workflow1.Sink -import com.squareup.workflow1.Workflow -import com.squareup.workflow1.WorkflowAction import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.SendChannel -internal class RealRenderContext( - private val renderer: Renderer, +public open class RealRenderContext( + protected open val renderer: Renderer, private val sideEffectRunner: SideEffectRunner, private val eventActionsChannel: SendChannel> ) : BaseRenderContext, Sink> { - interface Renderer { - fun render( - child: Workflow, - props: ChildPropsT, - key: String, - handler: (ChildOutputT) -> WorkflowAction - ): ChildRenderingT - - @Composable - fun Rendering( + public interface Renderer { + public fun render( child: Workflow, props: ChildPropsT, key: String, @@ -34,8 +20,8 @@ internal class RealRenderContext( ): ChildRenderingT } - interface SideEffectRunner { - fun runningSideEffect( + public interface SideEffectRunner { + public fun runningSideEffect( key: String, sideEffect: suspend CoroutineScope.() -> Unit ) @@ -71,16 +57,6 @@ internal class RealRenderContext( return renderer.render(child, props, key, handler) } - @Composable - override fun ChildRendering( - child: Workflow, - props: ChildPropsT, - key: String, - handler: (ChildOutputT) -> WorkflowAction - ): ChildRenderingT { - return renderer.Rendering(child, props, key, handler) - } - override fun runningSideEffect( key: String, sideEffect: suspend CoroutineScope.() -> Unit @@ -92,12 +68,12 @@ internal class RealRenderContext( /** * Freezes this context so that any further calls to this context will throw. */ - fun freeze() { + public fun freeze() { checkNotFrozen() frozen = true } - private fun checkNotFrozen() = check(!frozen) { + protected fun checkNotFrozen(): Unit = check(!frozen) { "RenderContext cannot be used after render method returns." } } diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/RenderWorkflow.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/RenderWorkflow.kt index d3913ae038..767f37660d 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/RenderWorkflow.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/RenderWorkflow.kt @@ -1,9 +1,5 @@ package com.squareup.workflow1 -import androidx.compose.runtime.BroadcastFrameClock -import app.cash.molecule.launchMolecule -import com.squareup.workflow1.internal.WorkflowRunner -import com.squareup.workflow1.internal.chained import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -13,8 +9,6 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.isActive import kotlinx.coroutines.launch -import kotlinx.coroutines.plus -import kotlinx.coroutines.yield /** * Launches the [workflow] in a new coroutine in [scope] and returns a [StateFlow] of its @@ -101,6 +95,9 @@ import kotlinx.coroutines.yield * @param runtimeConfig * Configuration parameters for the Workflow Runtime. * + * @param workflowRuntimePlugin + * This is used to plug in Runtime functionality that lives in other modules. + * * @return * A [StateFlow] of [RenderingAndSnapshot]s that will emit any time the root workflow creates a new * rendering. @@ -113,26 +110,28 @@ public fun renderWorkflowIn( initialSnapshot: TreeSnapshot? = null, interceptors: List = emptyList(), runtimeConfig: RuntimeConfig = RuntimeConfig.DEFAULT_CONFIG, + workflowRuntimePlugin: WorkflowRuntimePlugin? = null, onOutput: suspend (OutputT) -> Unit ): StateFlow> { - val chainedInterceptor = interceptors.chained() - - val runner = - WorkflowRunner(scope, workflow, props, initialSnapshot, chainedInterceptor, runtimeConfig) + val chainedInterceptor = workflowRuntimePlugin?.chainedInterceptors(interceptors) + ?: interceptors.chained() - var composeWaitingForFrame = false - val composeRuntimeClock = BroadcastFrameClock { - composeWaitingForFrame = true - } + val runner = workflowRuntimePlugin?.createWorkflowRunner( + scope, workflow, props, initialSnapshot, chainedInterceptor, runtimeConfig + ) + ?: WorkflowRunner(scope, workflow, props, initialSnapshot, chainedInterceptor, runtimeConfig) // Rendering is synchronous, so we can run the first render pass before launching the runtime // coroutine to calculate the initial rendering. val renderingsAndSnapshots = if (runtimeConfig.useComposeInRuntime) { - val clockedScope = scope + composeRuntimeClock - - clockedScope.launchMolecule { - runner.nextComposedRendering() + require(workflowRuntimePlugin != null) { + "Cannot use compose without plugging in" + + " the workflow-compose-core module." } + workflowRuntimePlugin.initializeRenderingStream( + runner, + runtimeScope = scope + ) } else { MutableStateFlow( try { @@ -164,21 +163,9 @@ public fun renderWorkflowIn( // After receiving an output, the next render pass must be done before emitting that output, // so that the workflow states appear consistent to observers of the outputs and renderings. if (runtimeConfig.useComposeInRuntime) { - // TODO: Figure out how to handle the case where the state changes on the first action - // from the first rendering? - e.g. with a Worker.from { Unit } initializing state. - // if (!composeWaitingForFrame) { - // // We want to make sure Compose is waiting for a frame in case action processing finished - // // before Compose was ready to recompose. - // while (!composeWaitingForFrame) { - // delay(20) - // } - // yield() - // } - if (composeWaitingForFrame) { - composeWaitingForFrame = false - composeRuntimeClock.sendFrame(0L) - yield() - } + // TODO (https://github.com/square/workflow-kotlin/issues/835): Figure out how to handle + // the case where the state changes on the first action as this is broken now. + workflowRuntimePlugin?.nextRendering() } else { (renderingsAndSnapshots as MutableStateFlow).value = runner.nextRendering() } diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/SimpleLoggingWorkflowInterceptor.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/SimpleLoggingWorkflowInterceptor.kt index 659229b2b8..2b6e624338 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/SimpleLoggingWorkflowInterceptor.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/SimpleLoggingWorkflowInterceptor.kt @@ -1,11 +1,9 @@ package com.squareup.workflow1 -import androidx.compose.runtime.Composable import com.squareup.workflow1.WorkflowInterceptor.RenderContextInterceptor import com.squareup.workflow1.WorkflowInterceptor.WorkflowSession import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job -import java.lang.IllegalStateException /** * A [WorkflowInterceptor] that just prints all method calls using [log]. @@ -50,17 +48,6 @@ public open class SimpleLoggingWorkflowInterceptor : WorkflowInterceptor { proceed(renderProps, renderState, SimpleLoggingContextInterceptor(session)) } - @Composable - override fun Rendering( - renderProps: P, - renderState: S, - context: BaseRenderContext, - session: WorkflowSession, - proceed: @Composable (P, S, RenderContextInterceptor?) -> R - ): R { - throw IllegalStateException("Do not use this interceptor with Compose runtime yet.") - } - override fun onSnapshotState( state: S, proceed: (S) -> Snapshot?, diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/SubtreeManager.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/SubtreeManager.kt similarity index 68% rename from workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/SubtreeManager.kt rename to workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/SubtreeManager.kt index d7675b6180..f4858c2fa6 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/SubtreeManager.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/SubtreeManager.kt @@ -1,17 +1,6 @@ -package com.squareup.workflow1.internal +package com.squareup.workflow1 -import androidx.compose.runtime.Composable -import androidx.compose.runtime.SideEffect -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import com.squareup.workflow1.ActionProcessingResult -import com.squareup.workflow1.NoopWorkflowInterceptor -import com.squareup.workflow1.TreeSnapshot -import com.squareup.workflow1.Workflow -import com.squareup.workflow1.WorkflowAction -import com.squareup.workflow1.WorkflowInterceptor import com.squareup.workflow1.WorkflowInterceptor.WorkflowSession -import com.squareup.workflow1.identifier import kotlinx.coroutines.selects.SelectBuilder import kotlin.coroutines.CoroutineContext @@ -85,15 +74,15 @@ import kotlin.coroutines.CoroutineContext * snapshots are extracted into this cache. Then, when those children are started for the * first time, they are also restored from their snapshots. */ -internal class SubtreeManager( - private var snapshotCache: Map?, - private val contextForChildren: CoroutineContext, - private val emitActionToParent: (WorkflowAction) -> Any?, - private val workflowSession: WorkflowSession? = null, - private val interceptor: WorkflowInterceptor = NoopWorkflowInterceptor, - private val idCounter: IdCounter? = null +public open class SubtreeManager( + protected var snapshotCache: Map?, + protected val contextForChildren: CoroutineContext, + protected val emitActionToParent: (WorkflowAction) -> Any?, + protected val workflowSession: WorkflowSession? = null, + protected open val interceptor: WorkflowInterceptor = NoopWorkflowInterceptor, + protected val idCounter: IdCounter? = null ) : RealRenderContext.Renderer { - private var children = ActiveStagingList>() + protected var children: ActiveStagingList> = ActiveStagingList() /** * Moves all the nodes that have been accumulated in the staging list to the active list, making @@ -101,7 +90,7 @@ internal class SubtreeManager( * * This should be called after this node's render method returns. */ - fun commitRenderedChildren() { + public fun commitRenderedChildren() { // Any children left in the previous active list after the render finishes were not re-rendered // and must be torn down. children.commitStaging { child -> @@ -127,24 +116,6 @@ internal class SubtreeManager( return stagedChild.render(child.asStatefulWorkflow(), props) } - @Composable - override fun Rendering( - child: Workflow, - props: ChildPropsT, - key: String, - handler: (ChildOutputT) -> WorkflowAction - ): ChildRenderingT { - val stagedChild = - StagedChild( - child, - props, - key, - handler - ) - val statefulChild = remember(child) { child.asStatefulWorkflow() } - return stagedChild.Rendering(statefulChild, props) - } - private fun prepareStagedChild( child: Workflow, props: ChildPropsT, @@ -167,51 +138,17 @@ internal class SubtreeManager( return stagedChild } - /** - * Prepare the staged child while only modifying [children] in a SideEffect. This will ensure - * that we do not inappropriately modify non-snapshot state. - */ - @Composable - private fun StagedChild( - child: Workflow, - props: ChildPropsT, - key: String, - handler: (ChildOutputT) -> WorkflowAction - ): WorkflowChildNode<*, *, *, *, *> { - val childState = remember(child, key, props, handler) { - children.forEachStaging { - require(!(it.matches(child, key))) { - "Expected keys to be unique for ${child.identifier}: key=\"$key\"" - } - } - mutableStateOf( - children.firstActiveOrNull { - it.matches(child, key) - } ?: createChildNode(child, props, key, handler) - ) - } - - SideEffect { - // Modify the [children] lists in a side-effect when composition is committed. - children.removeAndStage( - predicate = { it.matches(child, key) }, - child = childState.value - ) - } - return childState.value - } - /** * Uses [selector] to invoke [WorkflowNode.tick] for every running child workflow this instance * is managing. */ - fun tickChildren(selector: SelectBuilder) { + internal fun tickChildren(selector: SelectBuilder) { children.forEachActive { child -> child.workflowNode.tick(selector) } } - fun createChildSnapshots(): Map { + public fun createChildSnapshots(): Map { val snapshots = mutableMapOf() children.forEachActive { child -> val childWorkflow = child.workflow.asStatefulWorkflow() @@ -220,7 +157,7 @@ internal class SubtreeManager( return snapshots } - private fun createChildNode( + protected open fun createChildNode( child: Workflow, initialProps: ChildPropsT, key: String, @@ -246,7 +183,9 @@ internal class SubtreeManager( workflowSession, interceptor, idCounter = idCounter - ) + ).apply { + startSession() + } return WorkflowChildNode(child, handler, workflowNode) .also { node = it } } diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/TreeSnapshot.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/TreeSnapshot.kt index 0ee986a1aa..9700c2ed52 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/TreeSnapshot.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/TreeSnapshot.kt @@ -2,7 +2,6 @@ package com.squareup.workflow1 import com.squareup.workflow1.TreeSnapshot.Companion.forRootOnly import com.squareup.workflow1.TreeSnapshot.Companion.parse -import com.squareup.workflow1.internal.WorkflowNodeId import okio.Buffer import okio.ByteString import kotlin.LazyThreadSafetyMode.NONE @@ -27,7 +26,7 @@ public class TreeSnapshot internal constructor( * The [Snapshot] for the root workflow, or null if that snapshot was empty or unspecified. * Computed lazily to avoid serializing the snapshot until necessary. */ - internal val workflowSnapshot: Snapshot? by lazy(NONE) { + public val workflowSnapshot: Snapshot? by lazy(NONE) { workflowSnapshot?.takeUnless { it.bytes.size == 0 } } @@ -35,7 +34,7 @@ public class TreeSnapshot internal constructor( * The map of child snapshots by child [WorkflowNodeId]. Computed lazily so the entire snapshot * tree isn't parsed upfront. */ - internal val childTreeSnapshots: Map + public val childTreeSnapshots: Map by lazy(NONE, childTreeSnapshots) /** diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowChildNode.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/WorkflowChildNode.kt similarity index 56% rename from workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowChildNode.kt rename to workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/WorkflowChildNode.kt index 9cef8334c3..b093267a12 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowChildNode.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/WorkflowChildNode.kt @@ -1,12 +1,6 @@ -package com.squareup.workflow1.internal +package com.squareup.workflow1 -import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import com.squareup.workflow1.StatefulWorkflow -import com.squareup.workflow1.Workflow -import com.squareup.workflow1.WorkflowAction -import com.squareup.workflow1.internal.InlineLinkedList.InlineListNode +import com.squareup.workflow1.InlineLinkedList.InlineListNode /** * Representation of a child workflow that has been rendered by another workflow. @@ -14,26 +8,26 @@ import com.squareup.workflow1.internal.InlineLinkedList.InlineListNode * Associates the child's [WorkflowNode] (which includes the key passed to `renderChild`) with the * output handler function that was passed to `renderChild`. */ -internal class WorkflowChildNode< +public open class WorkflowChildNode< ChildPropsT, ChildOutputT, ParentPropsT, ParentStateT, ParentOutputT >( - val workflow: Workflow<*, ChildOutputT, *>, + public val workflow: Workflow<*, ChildOutputT, *>, private var handler: (ChildOutputT) -> WorkflowAction, - val workflowNode: WorkflowNode + public val workflowNode: WorkflowNode ) : InlineListNode> { override var nextListNode: WorkflowChildNode<*, *, *, *, *>? = null /** The [WorkflowNode]'s [WorkflowNodeId]. */ - val id get() = workflowNode.id + public val id: WorkflowNodeId get() = workflowNode.id /** * Returns true if this child has the same type as [otherWorkflow] and key as [key]. */ - fun matches( + public fun matches( otherWorkflow: Workflow<*, *, *>, key: String ): Boolean = id.matches(otherWorkflow, key) @@ -41,7 +35,7 @@ internal class WorkflowChildNode< /** * Updates the handler function that will be invoked by [acceptChildOutput]. */ - fun setHandler(newHandler: (CO) -> WorkflowAction) { + internal fun setHandler(newHandler: (CO) -> WorkflowAction) { @Suppress("UNCHECKED_CAST") handler = newHandler as (ChildOutputT) -> WorkflowAction @@ -50,7 +44,7 @@ internal class WorkflowChildNode< /** * Wrapper around [WorkflowNode.render] that allows calling it with erased types. */ - fun render( + public fun render( workflow: StatefulWorkflow<*, *, *, *>, props: Any? ): R { @@ -61,25 +55,11 @@ internal class WorkflowChildNode< ) as R } - @Composable - fun Rendering( - workflow: StatefulWorkflow<*, *, *, *>, - props: Any? - ): R { - val rendering = remember { mutableStateOf(null) } - @Suppress("UNCHECKED_CAST") - (workflowNode as WorkflowNode).Rendering( - workflow as StatefulWorkflow, - props as ChildPropsT, - rendering, - ) - return rendering.value!! - } - /** * Wrapper around [handler] that allows calling it with erased types. */ @Suppress("UNCHECKED_CAST") - fun acceptChildOutput(output: Any?): WorkflowAction = + public fun acceptChildOutput(output: Any?): + WorkflowAction = handler(output as ChildOutputT) } 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 3f8d571bb3..c2da75efe6 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/WorkflowInterceptor.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/WorkflowInterceptor.kt @@ -1,6 +1,5 @@ package com.squareup.workflow1 -import androidx.compose.runtime.Composable import com.squareup.workflow1.WorkflowInterceptor.RenderContextInterceptor import com.squareup.workflow1.WorkflowInterceptor.WorkflowSession import kotlinx.coroutines.CoroutineScope @@ -99,15 +98,6 @@ public interface WorkflowInterceptor { session: WorkflowSession ): R = proceed(renderProps, renderState, null) - @Composable - public fun Rendering( - renderProps: P, - renderState: S, - context: BaseRenderContext, - session: WorkflowSession, - proceed: @Composable (P, S, RenderContextInterceptor?) -> R - ): R = proceed(renderProps, renderState, null) - /** * Intercepts calls to [StatefulWorkflow.snapshotState]. */ @@ -236,20 +226,6 @@ public interface WorkflowInterceptor { handler: (CO) -> WorkflowAction ) -> CR ): CR = proceed(child, childProps, key, handler) - - @Composable - public fun ChildRendering( - child: Workflow, - childProps: CP, - key: String, - handler: (CO) -> WorkflowAction, - proceed: @Composable ( - child: Workflow, - childProps: CP, - key: String, - handler: (CO) -> WorkflowAction - ) -> CR - ): CR = proceed(child, childProps, key, handler) } } @@ -260,7 +236,7 @@ public object NoopWorkflowInterceptor : WorkflowInterceptor * Returns a [StatefulWorkflow] that will intercept all calls to [workflow] via this * [WorkflowInterceptor]. */ -internal fun WorkflowInterceptor.intercept( +public fun WorkflowInterceptor.intercept( workflow: StatefulWorkflow, workflowSession: WorkflowSession ): StatefulWorkflow = if (this === NoopWorkflowInterceptor) { @@ -294,36 +270,6 @@ internal fun WorkflowInterceptor.intercept( session = workflowSession, ) - @Composable - override fun Rendering( - renderProps: P, - renderState: S, - context: RenderContext, - ): R { - // Cannot annotate anonymous functions with @Composable and cannot infer type of - // this when a lambda. So need this variable to make it explicit. - val reifiedProceed: @Composable (P, S, RenderContextInterceptor?) -> R = - @Composable { props: P, - state: S, - interceptor: RenderContextInterceptor? -> - val interceptedContext = interceptor?.let { InterceptedRenderContext(context, it) } - ?: context - val renderContext = RenderContext(interceptedContext, this) - workflow.Rendering( - props, - state, - renderContext - ) - } - return Rendering( - renderProps = renderProps, - renderState = renderState, - context = context, - session = workflowSession, - proceed = reifiedProceed - ) - } - override fun snapshotState(state: S) = onSnapshotState(state, workflow::snapshotState, workflowSession) @@ -331,7 +277,7 @@ internal fun WorkflowInterceptor.intercept( } } -private class InterceptedRenderContext( +public open class InterceptedRenderContext( private val baseRenderContext: BaseRenderContext, private val interceptor: RenderContextInterceptor ) : BaseRenderContext, Sink> { @@ -353,22 +299,6 @@ private class InterceptedRenderContext( baseRenderContext.renderChild(iChild, iProps, iKey, iHandler) } - @Composable - override fun ChildRendering( - child: Workflow, - props: ChildPropsT, - key: String, - handler: (ChildOutputT) -> WorkflowAction - ): ChildRenderingT = - interceptor.ChildRendering( - child, - props, - key, - handler - ) @Composable { iChild, iProps, iKey, iHandler -> - baseRenderContext.ChildRendering(iChild, iProps, iKey, iHandler) - } - override fun runningSideEffect( key: String, sideEffect: suspend CoroutineScope.() -> Unit diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowNode.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/WorkflowNode.kt similarity index 59% rename from workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowNode.kt rename to workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/WorkflowNode.kt index 5a3c220b47..75c3ec10f7 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowNode.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/WorkflowNode.kt @@ -1,25 +1,8 @@ -package com.squareup.workflow1.internal +package com.squareup.workflow1 -import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.SideEffect -import androidx.compose.runtime.key -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import com.squareup.workflow1.ActionProcessingResult -import com.squareup.workflow1.NoopWorkflowInterceptor -import com.squareup.workflow1.RenderContext -import com.squareup.workflow1.StatefulWorkflow -import com.squareup.workflow1.TreeSnapshot -import com.squareup.workflow1.Workflow -import com.squareup.workflow1.WorkflowAction -import com.squareup.workflow1.WorkflowIdentifier -import com.squareup.workflow1.WorkflowInterceptor +import com.squareup.workflow1.RealRenderContext.SideEffectRunner import com.squareup.workflow1.WorkflowInterceptor.WorkflowSession -import com.squareup.workflow1.WorkflowOutput -import com.squareup.workflow1.applyTo -import com.squareup.workflow1.intercept -import com.squareup.workflow1.internal.RealRenderContext.SideEffectRunner +import com.squareup.workflow1.internal.SideEffectNode import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope @@ -43,15 +26,15 @@ import kotlin.coroutines.CoroutineContext * hard-coded values added to worker contexts. It must not contain a [Job] element (it would violate * structured concurrency). */ -internal class WorkflowNode( - val id: WorkflowNodeId, - workflow: StatefulWorkflow, - initialProps: PropsT, - snapshot: TreeSnapshot?, +public open class WorkflowNode( + public val id: WorkflowNodeId, + protected val workflow: StatefulWorkflow, + private val initialProps: PropsT, + private val initialSnapshot: TreeSnapshot?, baseContext: CoroutineContext, - private val emitOutputToParent: (OutputT) -> Any? = { WorkflowOutput(it) }, + protected val emitOutputToParent: (OutputT) -> Any? = { WorkflowOutput(it) }, override val parent: WorkflowSession? = null, - private val interceptor: WorkflowInterceptor = NoopWorkflowInterceptor, + protected val interceptor: WorkflowInterceptor = NoopWorkflowInterceptor, idCounter: IdCounter? = null ) : CoroutineScope, SideEffectRunner, WorkflowSession { @@ -59,34 +42,50 @@ internal class WorkflowNode( * Context that has a job that will live as long as this node. * Also adds a debug name to this coroutine based on its ID. */ - override val coroutineContext = baseContext + Job(baseContext[Job]) + CoroutineName(id.toString()) + override val coroutineContext: CoroutineContext = + baseContext + Job(baseContext[Job]) + CoroutineName(id.toString()) // WorkflowInstance properties override val identifier: WorkflowIdentifier get() = id.identifier override val renderKey: String get() = id.name override val sessionId: Long = idCounter.createId() - private val subtreeManager = SubtreeManager( - snapshotCache = snapshot?.childTreeSnapshots, - contextForChildren = coroutineContext, - emitActionToParent = ::applyAction, - workflowSession = this, - interceptor = interceptor, - idCounter = idCounter - ) - private val sideEffects = ActiveStagingList() - private var lastProps: PropsT = initialProps - private val eventActionsChannel = - Channel>(capacity = UNLIMITED) - private val state: MutableState + protected open val subtreeManager: SubtreeManager by lazy { + SubtreeManager( + snapshotCache = initialSnapshot?.childTreeSnapshots, + contextForChildren = coroutineContext, + emitActionToParent = ::applyAction, + workflowSession = this, + interceptor = interceptor, + idCounter = idCounter + ) + } - init { - interceptor.onSessionStarted(this, this) + private val sideEffects: ActiveStagingList = ActiveStagingList() + protected var lastProps: PropsT = initialProps + protected val eventActionsChannel: Channel> = + Channel(capacity = UNLIMITED) - state = mutableStateOf( - interceptor.intercept(workflow = workflow, workflowSession = this) - .initialState(initialProps, snapshot?.workflowSnapshot) - ) + private var backingState: StateT? = null + + protected open var state: StateT + get() { + requireNotNull(backingState) + return backingState!! + } + set(value) { + backingState = value + } + + /** + * Initialize the session to handle polymorphic class creation. + * + * TODO: Handle this better as this is a very dangerous implicit API connection. + */ + public fun startSession() { + interceptor.onSessionStarted(workflowScope = this, session = this) + state = interceptor.intercept(workflow = workflow, workflowSession = this) + .initialState(initialProps, initialSnapshot?.workflowSnapshot) } override fun toString(): String { @@ -105,44 +104,23 @@ internal class WorkflowNode( * render themselves and aggregate those child renderings. */ @Suppress("UNCHECKED_CAST") - fun render( + public fun render( workflow: StatefulWorkflow, input: PropsT ): RenderingT = renderWithStateType(workflow as StatefulWorkflow, input) - /** - * This returns Unit so that the Recomposer will consider this a separate Recompose scope that - * can be independently recomposed. - * - * We pass in the MutableState directly rather than setRendering() to save Compose - * having to memoize the lambda for such a frequenct call. - */ - @Suppress("UNCHECKED_CAST") - @Composable - fun Rendering( - workflow: StatefulWorkflow, - input: PropsT, - rendering: MutableState - ) { - RenderingWithStateType( - workflow as StatefulWorkflow, - input, - rendering - ) - } - /** * Walk the tree of state machines again, this time gathering snapshots and aggregating them * automatically. */ - fun snapshot(workflow: StatefulWorkflow<*, *, *, *>): TreeSnapshot { + public fun snapshot(workflow: StatefulWorkflow<*, *, *, *>): TreeSnapshot { // TODO: Figure out how to use `rememberSaveable` for Compose runtime here. @Suppress("UNCHECKED_CAST") val typedWorkflow = workflow as StatefulWorkflow val childSnapshots = subtreeManager.createChildSnapshots() val rootSnapshot = interceptor.intercept(typedWorkflow, this) - .snapshotState(state.value) + .snapshotState(state) return TreeSnapshot( workflowSnapshot = rootSnapshot, // Create the snapshots eagerly since subtreeManager is mutable. @@ -174,7 +152,7 @@ internal class WorkflowNode( * * It is an error to call this method after calling [cancel]. */ - fun tick(selector: SelectBuilder) { + internal fun tick(selector: SelectBuilder) { // Listen for any child workflow updates. subtreeManager.tickChildren(selector) @@ -192,7 +170,7 @@ internal class WorkflowNode( * This must be called when the caller will no longer call [tick]. It is an error to call [tick] * after calling this method. */ - fun cancel(cause: CancellationException? = null) { + internal fun cancel(cause: CancellationException? = null) { // No other cleanup work should be done in this function, since it will only be invoked when // this workflow is *directly* discarded by its parent (or the host). // If you need to do something whenever this workflow is torn down, add it to the @@ -216,7 +194,7 @@ internal class WorkflowNode( eventActionsChannel = eventActionsChannel ) val rendering = interceptor.intercept(workflow, this) - .render(props, state.value, RenderContext(context, workflow)) + .render(props, state, RenderContext(context, workflow)) context.freeze() commitAndUpdateScopes() @@ -224,39 +202,7 @@ internal class WorkflowNode( return rendering } - @Composable - private fun RenderingWithStateType( - workflow: StatefulWorkflow, - props: PropsT, - rendering: MutableState - ) { - UpdatePropsAndState(workflow, props) - - val (baseRenderContext, renderContext) = remember( - state.value, - props, - workflow, - rendering.value - ) { - // Use the RenderContext once. After rendering successfully it is frozen until new state. - val base = RealRenderContext( - renderer = subtreeManager, - sideEffectRunner = this, - eventActionsChannel = eventActionsChannel - ) - base to RenderContext(workflow = workflow, baseContext = base) - } - - rendering.value = interceptor.intercept(workflow, this) - .Rendering(props, state.value, renderContext) - - SideEffect { - baseRenderContext.freeze() - commitAndUpdateScopes() - } - } - - private fun commitAndUpdateScopes() { + protected fun commitAndUpdateScopes() { // Tear down workflows and workers that are obsolete. subtreeManager.commitRenderedChildren() // Side effect jobs are launched lazily, since they can send actions to the sink, and can only @@ -271,35 +217,19 @@ internal class WorkflowNode( ) { if (newProps != lastProps) { val newState = interceptor.intercept(workflow, this) - .onPropsChanged(lastProps, newProps, state.value) - state.value = newState + .onPropsChanged(lastProps, newProps, state) + state = newState } lastProps = newProps } - @Composable - private fun UpdatePropsAndState( - workflow: StatefulWorkflow, - newProps: PropsT - ) { - key(newProps) { - if (newProps != lastProps) { - state.value = interceptor.intercept(workflow, this@WorkflowNode) - .onPropsChanged(lastProps, newProps, state.value) - } - } - SideEffect { - lastProps = newProps - } - } - /** * Applies [action] to this workflow's [state] and * [emits an output to its parent][emitOutputToParent] if necessary. */ - private fun applyAction(action: WorkflowAction): T? { - val (newState, tickResult) = action.applyTo(lastProps, state.value) - state.value = newState + protected fun applyAction(action: WorkflowAction): T? { + val (newState, tickResult) = action.applyTo(lastProps, state) + state = newState @Suppress("UNCHECKED_CAST") return tickResult?.let { emitOutputToParent(it.value) } as T? } diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowNodeId.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/WorkflowNodeId.kt similarity index 72% rename from workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowNodeId.kt rename to workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/WorkflowNodeId.kt index e8b765c2df..e538c3f283 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowNodeId.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/WorkflowNodeId.kt @@ -1,12 +1,5 @@ -package com.squareup.workflow1.internal +package com.squareup.workflow1 -import com.squareup.workflow1.Workflow -import com.squareup.workflow1.WorkflowIdentifier -import com.squareup.workflow1.identifier -import com.squareup.workflow1.readByteStringWithLength -import com.squareup.workflow1.readUtf8WithLength -import com.squareup.workflow1.writeByteStringWithLength -import com.squareup.workflow1.writeUtf8WithLength import okio.Buffer import okio.ByteString @@ -14,16 +7,16 @@ import okio.ByteString * Value type that can be used to distinguish between different workflows of different types or * the same type (in that case using a [name]). */ -internal data class WorkflowNodeId( +public data class WorkflowNodeId( internal val identifier: WorkflowIdentifier, internal val name: String = "" ) { - constructor( + public constructor( workflow: Workflow<*, *, *>, name: String = "" ) : this(workflow.identifier, name) - fun matches( + internal fun matches( otherWorkflow: Workflow<*, *, *>, otherName: String ): Boolean = identifier == otherWorkflow.identifier && name == otherName @@ -50,5 +43,5 @@ internal data class WorkflowNodeId( } } -internal fun , I, O, R> +public fun , I, O, R> W.id(key: String = ""): WorkflowNodeId = WorkflowNodeId(this, key) diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowRunner.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/WorkflowRunner.kt similarity index 71% rename from workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowRunner.kt rename to workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/WorkflowRunner.kt index 609e6788b2..f0ea590409 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowRunner.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/WorkflowRunner.kt @@ -1,25 +1,12 @@ -package com.squareup.workflow1.internal +package com.squareup.workflow1 -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import com.squareup.workflow1.ActionProcessingResult -import com.squareup.workflow1.PropsUpdated -import com.squareup.workflow1.RenderingAndSnapshot -import com.squareup.workflow1.RuntimeConfig import com.squareup.workflow1.RuntimeConfig.FrameTimeout -import com.squareup.workflow1.TimeoutForFrame -import com.squareup.workflow1.TreeSnapshot -import com.squareup.workflow1.Workflow -import com.squareup.workflow1.WorkflowExperimentalRuntime -import com.squareup.workflow1.WorkflowInterceptor -import com.squareup.workflow1.WorkflowOutput +import com.squareup.workflow1.internal.currentTimeMillis import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.channels.ReceiveChannel import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.dropWhile import kotlinx.coroutines.flow.produceIn @@ -27,17 +14,18 @@ import kotlinx.coroutines.selects.SelectBuilder import kotlinx.coroutines.selects.select @OptIn(ExperimentalCoroutinesApi::class) -internal class WorkflowRunner( +public open class WorkflowRunner( scope: CoroutineScope, protoWorkflow: Workflow, props: StateFlow, snapshot: TreeSnapshot?, interceptor: WorkflowInterceptor, - private val runtimeConfig: RuntimeConfig + protected val runtimeConfig: RuntimeConfig ) { - private val workflow = protoWorkflow.asStatefulWorkflow() - private val idCounter = IdCounter() - private var currentProps: PropsT by mutableStateOf(props.value) + protected val workflow: StatefulWorkflow = + protoWorkflow.asStatefulWorkflow() + protected val idCounter: IdCounter = IdCounter() + protected open var currentProps: PropsT = props.value // Props is a StateFlow, it will immediately produce an item. Without additional handling, the // first call to processActions will see that new props value and trigger another render pass, @@ -51,18 +39,23 @@ internal class WorkflowRunner( // which can't happen until the dropWhile predicate evaluates to false, after which the dropWhile // predicate will never be invoked again, so it's fine to read the mutable value here. @OptIn(FlowPreview::class) - private val propsChannel = props.dropWhile { it == currentProps } + protected val propsChannel: ReceiveChannel = props.dropWhile { it == currentProps } .produceIn(scope) - private val rootNode = WorkflowNode( - id = workflow.id(), - workflow = workflow, - initialProps = currentProps, - snapshot = snapshot, - baseContext = scope.coroutineContext, - interceptor = interceptor, - idCounter = idCounter - ) + // Lazy because child class could override currentProps which is an input here. + protected open val rootNode: WorkflowNode by lazy { + WorkflowNode( + id = workflow.id(), + workflow = workflow, + initialProps = currentProps, + initialSnapshot = snapshot, + baseContext = scope.coroutineContext, + interceptor = interceptor, + idCounter = idCounter + ).apply { + startSession() + } + } /** * Perform a render pass and a snapshot pass and return the results. @@ -70,26 +63,12 @@ internal class WorkflowRunner( * This method must be called before the first call to [processActions], and must be called again * between every subsequent call to [processActions]. */ - fun nextRendering(): RenderingAndSnapshot { + internal fun nextRendering(): RenderingAndSnapshot { val rendering = rootNode.render(workflow, currentProps) val snapshot = rootNode.snapshot(workflow) return RenderingAndSnapshot(rendering, snapshot) } - @Composable - fun nextComposedRendering(): RenderingAndSnapshot { - val rendering = remember { mutableStateOf(null) } - - rootNode.Rendering(workflow, currentProps, rendering) - - val snapshot = remember { - // need to key this on state inside WorkflowNode. LIkely have a Compose version. - rootNode.snapshot(workflow) - } - - return RenderingAndSnapshot(rendering.value!!, snapshot) - } - /** * Stop processing and go to render on 1 of 3 conditions: * 1. Props have changed. @@ -101,7 +80,7 @@ internal class WorkflowRunner( * In those cases we return null. */ @OptIn(WorkflowExperimentalRuntime::class) - suspend fun processActions(): WorkflowOutput? { + internal suspend fun processActions(): WorkflowOutput? { // First we block and wait until there is an action to process. var processingResult: ActionProcessingResult? = select { onPropsUpdated() @@ -158,7 +137,7 @@ internal class WorkflowRunner( } } - fun cancelRuntime(cause: CancellationException? = null) { + internal fun cancelRuntime(cause: CancellationException? = null) { rootNode.cancel(cause) } } diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/WorkflowRuntimeClock.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/WorkflowRuntimeClock.kt deleted file mode 100644 index f5145a1198..0000000000 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/WorkflowRuntimeClock.kt +++ /dev/null @@ -1,81 +0,0 @@ -package com.squareup.workflow1 - -import androidx.compose.runtime.MonotonicFrameClock -import kotlinx.coroutines.suspendCancellableCoroutine -import kotlin.coroutines.Continuation -import kotlin.coroutines.resume - -/** - * Could use [PausableMonotonicFrameClock] but we'd need to wrap that around something. - */ -internal class WorkflowRuntimeClock( - private var workflowFrameLatch: Latch -) : MonotonicFrameClock { - override suspend fun withFrameNanos(onFrame: (frameTimeNanos: Long) -> R): R { - workflowFrameLatch.await() - return onFrame(0L) // frame time not used in Compose runtime. - } -} - -/** - * Class internal to androidx.compose.runtime. Useful here! - */ -internal class Latch { - - private val lock = Any() - private var awaiters = mutableListOf>() - private var spareList = mutableListOf>() - - private var _isOpen = true - val isOpen get() = synchronized(lock) { _isOpen } - - inline fun withClosed(block: () -> R): R { - closeLatch() - return try { - block() - } finally { - openLatch() - } - } - - fun closeLatch() { - synchronized(lock) { - _isOpen = false - } - } - - fun openLatch() { - synchronized(lock) { - if (isOpen) return - - // Rotate the lists so that if a resumed continuation on an immediate dispatcher - // bound to the thread calling openLatch immediately awaits again we don't disrupt - // iteration of resuming the rest. This is also why we set isClosed before resuming. - val toResume = awaiters - awaiters = spareList - spareList = toResume - _isOpen = true - - for (i in 0 until toResume.size) { - toResume[i].resume(Unit) - } - toResume.clear() - } - } - - suspend fun await() { - if (isOpen) return - - suspendCancellableCoroutine { co -> - synchronized(lock) { - awaiters.add(co) - } - - co.invokeOnCancellation { - synchronized(lock) { - awaiters.remove(co) - } - } - } - } -} diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/WorkflowRuntimePlugin.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/WorkflowRuntimePlugin.kt new file mode 100644 index 0000000000..5a0886390d --- /dev/null +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/WorkflowRuntimePlugin.kt @@ -0,0 +1,41 @@ +package com.squareup.workflow1 + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.StateFlow + +/** + * A plugin mechanism to provide a way to define runtime optimization behaviour that requires + * Compiler optimizations and extensive dependencies (such as Compose) in a separate module. + */ +public interface WorkflowRuntimePlugin { + + /** + * Initialize the stream of [RenderingAndSnapshot] that the UI layer will receive. + */ + public fun initializeRenderingStream( + workflowRunner: WorkflowRunner, + runtimeScope: CoroutineScope + ): StateFlow> + + /** + * Create a [WorkflowRunner] to drive the root [WorkflowNode]. + */ + public fun createWorkflowRunner( + scope: CoroutineScope, + protoWorkflow: Workflow, + props: StateFlow, + snapshot: TreeSnapshot?, + interceptor: WorkflowInterceptor, + runtimeConfig: RuntimeConfig + ): WorkflowRunner + + /** + * Trigger the next rendering in the runtime. + */ + public suspend fun nextRendering() + + /** + * Create a chain of interceptors for all that are passed in to [renderWorkflowIn] + */ + public fun chainedInterceptors(interceptors: List): WorkflowInterceptor +} diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/SideEffectNode.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/SideEffectNode.kt index 0370ebb7fb..8ea641400a 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/SideEffectNode.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/SideEffectNode.kt @@ -1,6 +1,6 @@ package com.squareup.workflow1.internal -import com.squareup.workflow1.internal.InlineLinkedList.InlineListNode +import com.squareup.workflow1.InlineLinkedList.InlineListNode import kotlinx.coroutines.Job /** diff --git a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/ActiveStagingListTest.kt b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/ActiveStagingListTest.kt similarity index 95% rename from workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/ActiveStagingListTest.kt rename to workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/ActiveStagingListTest.kt index ddaebfbbbd..4869a4dc93 100644 --- a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/ActiveStagingListTest.kt +++ b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/ActiveStagingListTest.kt @@ -1,6 +1,6 @@ -package com.squareup.workflow1.internal +package com.squareup.workflow1 -import com.squareup.workflow1.internal.InlineLinkedList.InlineListNode +import com.squareup.workflow1.InlineLinkedList.InlineListNode import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.fail diff --git a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/ChainedWorkflowInterceptorTest.kt b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/ChainedWorkflowInterceptorTest.kt similarity index 92% rename from workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/ChainedWorkflowInterceptorTest.kt rename to workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/ChainedWorkflowInterceptorTest.kt index 321fd5a222..ffc9814f54 100644 --- a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/ChainedWorkflowInterceptorTest.kt +++ b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/ChainedWorkflowInterceptorTest.kt @@ -1,21 +1,9 @@ @file:Suppress("UNCHECKED_CAST") -package com.squareup.workflow1.internal - -import androidx.compose.runtime.Composable -import com.squareup.workflow1.BaseRenderContext -import com.squareup.workflow1.NoopWorkflowInterceptor -import com.squareup.workflow1.Sink -import com.squareup.workflow1.Snapshot -import com.squareup.workflow1.Workflow -import com.squareup.workflow1.WorkflowAction -import com.squareup.workflow1.WorkflowIdentifier -import com.squareup.workflow1.WorkflowInterceptor +package com.squareup.workflow1 + import com.squareup.workflow1.WorkflowInterceptor.RenderContextInterceptor import com.squareup.workflow1.WorkflowInterceptor.WorkflowSession -import com.squareup.workflow1.identifier -import com.squareup.workflow1.parse -import com.squareup.workflow1.rendering import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job @@ -309,16 +297,6 @@ internal class ChainedWorkflowInterceptorTest { fail() } - @Composable - override fun ChildRendering( - child: Workflow, - props: ChildPropsT, - key: String, - handler: (ChildOutputT) -> WorkflowAction - ): ChildRenderingT { - fail() - } - override fun runningSideEffect( key: String, sideEffect: suspend CoroutineScope.() -> Unit diff --git a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/InlineLinkedListTest.kt b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/InlineLinkedListTest.kt similarity index 98% rename from workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/InlineLinkedListTest.kt rename to workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/InlineLinkedListTest.kt index 14e6cf9d6b..d7339867cc 100644 --- a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/InlineLinkedListTest.kt +++ b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/InlineLinkedListTest.kt @@ -1,6 +1,6 @@ -package com.squareup.workflow1.internal +package com.squareup.workflow1 -import com.squareup.workflow1.internal.InlineLinkedList.InlineListNode +import com.squareup.workflow1.InlineLinkedList.InlineListNode import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNull diff --git a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/RealRenderContextTest.kt b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/RealRenderContextTest.kt similarity index 88% rename from workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/RealRenderContextTest.kt rename to workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/RealRenderContextTest.kt index 5c6ef6d5e7..fbb3fa51bb 100644 --- a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/RealRenderContextTest.kt +++ b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/RealRenderContextTest.kt @@ -1,19 +1,10 @@ @file:Suppress("EXPERIMENTAL_API_USAGE", "OverridingDeprecatedMember") -package com.squareup.workflow1.internal - -import androidx.compose.runtime.Composable -import com.squareup.workflow1.Snapshot -import com.squareup.workflow1.StatefulWorkflow -import com.squareup.workflow1.Workflow -import com.squareup.workflow1.WorkflowAction -import com.squareup.workflow1.action -import com.squareup.workflow1.applyTo -import com.squareup.workflow1.internal.RealRenderContext.Renderer -import com.squareup.workflow1.internal.RealRenderContext.SideEffectRunner -import com.squareup.workflow1.internal.RealRenderContextTest.TestRenderer.Rendering -import com.squareup.workflow1.renderChild -import com.squareup.workflow1.stateless +package com.squareup.workflow1 + +import com.squareup.workflow1.RealRenderContext.Renderer +import com.squareup.workflow1.RealRenderContext.SideEffectRunner +import com.squareup.workflow1.RealRenderContextTest.TestRenderer.Rendering import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED @@ -48,20 +39,6 @@ internal class RealRenderContextTest { key, handler as (Any) -> WorkflowAction ) as ChildRenderingT - - @Suppress("UNCHECKED_CAST") - @Composable - override fun Rendering( - child: Workflow, - props: ChildPropsT, - key: String, - handler: (ChildOutputT) -> WorkflowAction - ): ChildRenderingT = Rendering( - child, - props, - key, - handler as (Any) -> WorkflowAction - ) as ChildRenderingT } private class TestRunner : SideEffectRunner { @@ -97,14 +74,6 @@ internal class RealRenderContextTest { key: String, handler: (ChildOutputT) -> WorkflowAction ): ChildRenderingT = fail() - - @Composable - override fun Rendering( - child: Workflow, - props: ChildPropsT, - key: String, - handler: (ChildOutputT) -> WorkflowAction - ): ChildRenderingT = fail() } private class PoisonRunner : SideEffectRunner { diff --git a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/SimpleLoggingWorkflowInterceptorTest.kt b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/SimpleLoggingWorkflowInterceptorTest.kt index e707cde7c7..eafd9cb171 100644 --- a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/SimpleLoggingWorkflowInterceptorTest.kt +++ b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/SimpleLoggingWorkflowInterceptorTest.kt @@ -1,6 +1,5 @@ package com.squareup.workflow1 -import androidx.compose.runtime.Composable import com.squareup.workflow1.WorkflowInterceptor.WorkflowSession import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.cancel @@ -98,16 +97,6 @@ internal class SimpleLoggingWorkflowInterceptorTest { fail() } - @Composable - override fun ChildRendering( - child: Workflow, - props: ChildPropsT, - key: String, - handler: (ChildOutputT) -> WorkflowAction - ): ChildRenderingT { - fail() - } - override fun runningSideEffect( key: String, sideEffect: suspend CoroutineScope.() -> Unit diff --git a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/SubtreeManagerTest.kt b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/SubtreeManagerTest.kt similarity index 94% rename from workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/SubtreeManagerTest.kt rename to workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/SubtreeManagerTest.kt index 8bf5d26ef8..b60e22c3d5 100644 --- a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/SubtreeManagerTest.kt +++ b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/SubtreeManagerTest.kt @@ -1,17 +1,8 @@ @file:Suppress("EXPERIMENTAL_API_USAGE") -package com.squareup.workflow1.internal - -import com.squareup.workflow1.ActionProcessingResult -import com.squareup.workflow1.Snapshot -import com.squareup.workflow1.StatefulWorkflow -import com.squareup.workflow1.TreeSnapshot -import com.squareup.workflow1.WorkflowAction -import com.squareup.workflow1.WorkflowOutput -import com.squareup.workflow1.action -import com.squareup.workflow1.applyTo -import com.squareup.workflow1.identifier -import com.squareup.workflow1.internal.SubtreeManagerTest.TestWorkflow.Rendering +package com.squareup.workflow1 + +import com.squareup.workflow1.SubtreeManagerTest.TestWorkflow.Rendering import kotlinx.coroutines.Dispatchers.Unconfined import kotlinx.coroutines.async import kotlinx.coroutines.runBlocking diff --git a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/TreeSnapshotTest.kt b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/TreeSnapshotTest.kt index 5f56255224..b5d4e25eb9 100644 --- a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/TreeSnapshotTest.kt +++ b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/TreeSnapshotTest.kt @@ -1,7 +1,5 @@ package com.squareup.workflow1 -import com.squareup.workflow1.internal.WorkflowNodeId -import com.squareup.workflow1.internal.id import okio.ByteString import kotlin.reflect.typeOf import kotlin.test.Test diff --git a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/WorkflowInterceptorTest.kt b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/WorkflowInterceptorTest.kt index 55c827ff56..f53335429c 100644 --- a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/WorkflowInterceptorTest.kt +++ b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/WorkflowInterceptorTest.kt @@ -2,7 +2,6 @@ package com.squareup.workflow1 -import androidx.compose.runtime.Composable import com.squareup.workflow1.WorkflowInterceptor.RenderContextInterceptor import com.squareup.workflow1.WorkflowInterceptor.WorkflowSession import kotlinx.coroutines.CoroutineScope @@ -63,16 +62,6 @@ internal class WorkflowInterceptorTest { handler: (ChildOutputT) -> WorkflowAction ): ChildRenderingT = fail() - @Composable - override fun ChildRendering( - child: Workflow, - props: ChildPropsT, - key: String, - handler: (ChildOutputT) -> WorkflowAction - ): ChildRenderingT { - fail() - } - override fun runningSideEffect( key: String, sideEffect: suspend CoroutineScope.() -> Unit @@ -113,16 +102,6 @@ internal class WorkflowInterceptorTest { handler: (ChildOutputT) -> WorkflowAction ): ChildRenderingT = fail() - @Composable - override fun ChildRendering( - child: Workflow, - props: ChildPropsT, - key: String, - handler: (ChildOutputT) -> WorkflowAction - ): ChildRenderingT { - fail() - } - override fun runningSideEffect( key: String, sideEffect: suspend CoroutineScope.() -> Unit @@ -156,16 +135,6 @@ internal class WorkflowInterceptorTest { handler: (ChildOutputT) -> WorkflowAction ): ChildRenderingT = fail() - @Composable - override fun ChildRendering( - child: Workflow, - props: ChildPropsT, - key: String, - handler: (ChildOutputT) -> WorkflowAction - ): ChildRenderingT { - fail() - } - override fun runningSideEffect( key: String, sideEffect: suspend CoroutineScope.() -> Unit @@ -225,16 +194,6 @@ internal class WorkflowInterceptorTest { handler: (ChildOutputT) -> WorkflowAction ): ChildRenderingT = fail() - @Composable - override fun ChildRendering( - child: Workflow, - props: ChildPropsT, - key: String, - handler: (ChildOutputT) -> WorkflowAction - ): ChildRenderingT { - fail() - } - override fun runningSideEffect( key: String, sideEffect: suspend CoroutineScope.() -> Unit diff --git a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/WorkflowNodeTest.kt b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/WorkflowNodeTest.kt similarity index 93% rename from workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/WorkflowNodeTest.kt rename to workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/WorkflowNodeTest.kt index c526c90358..ba74a4707b 100644 --- a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/WorkflowNodeTest.kt +++ b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/WorkflowNodeTest.kt @@ -1,30 +1,9 @@ @file:Suppress("EXPERIMENTAL_API_USAGE", "DEPRECATION") -package com.squareup.workflow1.internal - -import com.squareup.workflow1.ActionProcessingResult -import com.squareup.workflow1.BaseRenderContext -import com.squareup.workflow1.Sink -import com.squareup.workflow1.Snapshot -import com.squareup.workflow1.StatefulWorkflow -import com.squareup.workflow1.TreeSnapshot -import com.squareup.workflow1.Workflow -import com.squareup.workflow1.WorkflowAction -import com.squareup.workflow1.WorkflowIdentifier -import com.squareup.workflow1.WorkflowInterceptor +package com.squareup.workflow1 + import com.squareup.workflow1.WorkflowInterceptor.RenderContextInterceptor import com.squareup.workflow1.WorkflowInterceptor.WorkflowSession -import com.squareup.workflow1.WorkflowOutput -import com.squareup.workflow1.action -import com.squareup.workflow1.contraMap -import com.squareup.workflow1.identifier -import com.squareup.workflow1.parse -import com.squareup.workflow1.readUtf8WithLength -import com.squareup.workflow1.renderChild -import com.squareup.workflow1.rendering -import com.squareup.workflow1.stateful -import com.squareup.workflow1.stateless -import com.squareup.workflow1.writeUtf8WithLength import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope @@ -103,6 +82,7 @@ internal class WorkflowNodeTest { return@PropsRenderingWorkflow state } val node = WorkflowNode(workflow.id(), workflow, "old", null, context) + .apply { startSession() } node.render(workflow, "new") @@ -116,6 +96,7 @@ internal class WorkflowNodeTest { return@PropsRenderingWorkflow state } val node = WorkflowNode(workflow.id(), workflow, "old", null, context) + .apply { startSession() } node.render(workflow, "old") @@ -127,6 +108,7 @@ internal class WorkflowNodeTest { "$old->$new" } val node = WorkflowNode(workflow.id(), workflow, "foo", null, context) + .apply { startSession() } val rendering = node.render(workflow, "foo2") @@ -170,7 +152,7 @@ internal class WorkflowNodeTest { val node = WorkflowNode( workflow.id(), workflow, "", null, context, emitOutputToParent = { WorkflowOutput("tick:$it") } - ) + ).apply { startSession() } node.render(workflow, "")("event") val result = runBlocking { @@ -204,7 +186,7 @@ internal class WorkflowNodeTest { val node = WorkflowNode( workflow.id(), workflow, "", null, context, emitOutputToParent = { WorkflowOutput("tick:$it") } - ) + ).apply { startSession() } val sink = node.render(workflow, "") sink("event") @@ -243,6 +225,7 @@ internal class WorkflowNodeTest { } } val node = WorkflowNode(workflow.id(), workflow, "", null, context) + .apply { startSession() } node.render(workflow, "") sink.send(action { setOutput("event") }) @@ -261,8 +244,8 @@ internal class WorkflowNodeTest { } val node = WorkflowNode( workflow.id(), workflow.asStatefulWorkflow(), initialProps = Unit, - snapshot = null, baseContext = context - ) + initialSnapshot = null, baseContext = context + ).apply { startSession() } runBlocking { node.render(workflow.asStatefulWorkflow(), Unit) @@ -279,8 +262,8 @@ internal class WorkflowNodeTest { } val node = WorkflowNode( workflow.id(), workflow.asStatefulWorkflow(), initialProps = Unit, - snapshot = null, baseContext = context - ) + initialSnapshot = null, baseContext = context + ).apply { startSession() } node.render(workflow.asStatefulWorkflow(), Unit) assertEquals(WorkflowNodeId(workflow).toString(), node.coroutineContext[CoroutineName]!!.name) @@ -298,8 +281,8 @@ internal class WorkflowNodeTest { } val node = WorkflowNode( workflow.id(), workflow.asStatefulWorkflow(), initialProps = Unit, - snapshot = null, baseContext = context - ) + initialSnapshot = null, baseContext = context + ).apply { startSession() } node.render(workflow.asStatefulWorkflow(), Unit) val result = runBlocking { @@ -328,8 +311,8 @@ internal class WorkflowNodeTest { } val node = WorkflowNode( workflow.id(), workflow.asStatefulWorkflow(), initialProps = true, - snapshot = null, baseContext = context - ) + initialSnapshot = null, baseContext = context + ).apply { startSession() } runBlocking { node.render(workflow.asStatefulWorkflow(), true) @@ -354,8 +337,8 @@ internal class WorkflowNodeTest { } val node = WorkflowNode( workflow.id(), workflow.asStatefulWorkflow(), initialProps = Unit, - snapshot = null, baseContext = context - ) + initialSnapshot = null, baseContext = context + ).apply { startSession() } runBlocking { node.render(workflow.asStatefulWorkflow(), Unit) @@ -380,8 +363,8 @@ internal class WorkflowNodeTest { } val node = WorkflowNode( workflow.id(), workflow.asStatefulWorkflow(), initialProps = 0, - snapshot = null, baseContext = context - ) + initialSnapshot = null, baseContext = context + ).apply { startSession() } runBlocking { node.render(workflow.asStatefulWorkflow(), 0) @@ -405,8 +388,8 @@ internal class WorkflowNodeTest { } val node = WorkflowNode( workflow.id(), workflow.asStatefulWorkflow(), initialProps = 0, - snapshot = null, baseContext = context - ) + initialSnapshot = null, baseContext = context + ).apply { startSession() } runBlocking { node.render(workflow.asStatefulWorkflow(), 0) @@ -426,8 +409,8 @@ internal class WorkflowNodeTest { } val node = WorkflowNode( workflow.id(), workflow.asStatefulWorkflow(), initialProps = Unit, - snapshot = null, baseContext = context - ) + initialSnapshot = null, baseContext = context + ).apply { startSession() } val error = assertFailsWith { node.render(workflow.asStatefulWorkflow(), Unit) @@ -453,9 +436,9 @@ internal class WorkflowNodeTest { } .asStatefulWorkflow() val node = WorkflowNode( - workflow.id(), workflow, initialProps = 0, snapshot = null, + workflow.id(), workflow, initialProps = 0, initialSnapshot = null, baseContext = context - ) + ).apply { startSession() } node.render(workflow, 0) assertEquals(listOf("started"), events1) @@ -487,8 +470,8 @@ internal class WorkflowNodeTest { } val node = WorkflowNode( workflow.id(), workflow.asStatefulWorkflow(), initialProps = Unit, - snapshot = null, baseContext = context - ) + initialSnapshot = null, baseContext = context + ).apply { startSession() } assertFalse(started1) assertFalse(started2) @@ -516,9 +499,9 @@ internal class WorkflowNodeTest { workflow.id(), workflow, initialProps = "initial props", - snapshot = null, + initialSnapshot = null, baseContext = Unconfined - ) + ).apply { startSession() } assertEquals("initial props", originalNode.render(workflow, "foo")) val snapshot = originalNode.snapshot(workflow) @@ -529,9 +512,9 @@ internal class WorkflowNodeTest { workflow, // These props should be ignored, since snapshot is non-null. initialProps = "new props", - snapshot = snapshot, + initialSnapshot = snapshot, baseContext = Unconfined - ) + ).apply { startSession() } assertEquals("initial props", restoredNode.render(workflow, "foo")) } @@ -545,9 +528,9 @@ internal class WorkflowNodeTest { workflow.id(), workflow, initialProps = "initial props", - snapshot = null, + initialSnapshot = null, baseContext = Unconfined - ) + ).apply { startSession() } assertEquals("initial props", originalNode.render(workflow, "foo")) val snapshot = originalNode.snapshot(workflow) @@ -558,9 +541,9 @@ internal class WorkflowNodeTest { workflow, // These props should be ignored, since snapshot is non-null. initialProps = "new props", - snapshot = snapshot, + initialSnapshot = snapshot, baseContext = Unconfined - ) + ).apply { startSession() } assertEquals("restored", restoredNode.render(workflow, "foo")) } @@ -602,9 +585,9 @@ internal class WorkflowNodeTest { parentWorkflow.id(), parentWorkflow, initialProps = "initial props", - snapshot = null, + initialSnapshot = null, baseContext = Unconfined - ) + ).apply { startSession() } assertEquals("initial props|child props", originalNode.render(parentWorkflow, "foo")) val snapshot = originalNode.snapshot(parentWorkflow) @@ -615,9 +598,9 @@ internal class WorkflowNodeTest { parentWorkflow, // These props should be ignored, since snapshot is non-null. initialProps = "new props", - snapshot = snapshot, + initialSnapshot = snapshot, baseContext = Unconfined - ) + ).apply { startSession() } assertEquals("initial props|child props", restoredNode.render(parentWorkflow, "foo")) assertEquals("child props", restoredChildState) assertEquals("initial props", restoredParentState) @@ -642,6 +625,7 @@ internal class WorkflowNodeTest { } ) val node = WorkflowNode(workflow.id(), workflow, Unit, null, Unconfined) + .apply { startSession() } assertEquals(0, snapshotCalls) assertEquals(0, snapshotWrites) @@ -659,7 +643,7 @@ internal class WorkflowNodeTest { assertEquals(1, snapshotWrites) assertEquals(0, restoreCalls) - WorkflowNode(workflow.id(), workflow, Unit, snapshot, Unconfined) + WorkflowNode(workflow.id(), workflow, Unit, snapshot, Unconfined).apply { startSession() } assertEquals(1, snapshotCalls) assertEquals(1, snapshotWrites) @@ -682,9 +666,9 @@ internal class WorkflowNodeTest { workflow.id(), workflow, initialProps = "initial props", - snapshot = null, + initialSnapshot = null, baseContext = Unconfined - ) + ).apply { startSession() } assertEquals("initial props", originalNode.render(workflow, "foo")) val snapshot = originalNode.snapshot(workflow) @@ -694,9 +678,9 @@ internal class WorkflowNodeTest { workflow.id(), workflow, initialProps = "new props", - snapshot = snapshot, + initialSnapshot = snapshot, baseContext = Unconfined - ) + ).apply { startSession() } assertEquals("props:new props|state:initial props", restoredNode.render(workflow, "foo")) } @@ -706,10 +690,10 @@ internal class WorkflowNodeTest { id = workflow.id(key = "foo"), workflow = workflow.asStatefulWorkflow(), initialProps = Unit, - snapshot = null, + initialSnapshot = null, baseContext = Unconfined, parent = null - ) + ).apply { startSession() } assertEquals( "WorkflowInstance(identifier=${workflow.identifier}, renderKey=foo, " + @@ -724,10 +708,10 @@ internal class WorkflowNodeTest { id = workflow.id(key = "foo"), workflow = workflow.asStatefulWorkflow(), initialProps = Unit, - snapshot = null, + initialSnapshot = null, baseContext = Unconfined, parent = TestSession(42) - ) + ).apply { startSession() } assertEquals( "WorkflowInstance(identifier=${workflow.identifier}, renderKey=foo, " + @@ -757,11 +741,11 @@ internal class WorkflowNodeTest { id = workflow.id(key = "foo"), workflow = workflow.asStatefulWorkflow(), initialProps = Unit, - snapshot = null, + initialSnapshot = null, interceptor = interceptor, baseContext = Unconfined, parent = TestSession(42) - ) + ).apply { startSession() } assertSame(node.coroutineContext, interceptedScope.coroutineContext) assertEquals(workflow.identifier, interceptedSession.identifier) @@ -801,11 +785,11 @@ internal class WorkflowNodeTest { id = workflow.id(key = "foo"), workflow = workflow.asStatefulWorkflow(), initialProps = "props", - snapshot = TreeSnapshot.forRootOnly(Snapshot.of("snapshot")), + initialSnapshot = TreeSnapshot.forRootOnly(Snapshot.of("snapshot")), interceptor = interceptor, baseContext = Unconfined, parent = TestSession(42) - ) + ).apply { startSession() } assertEquals("props", interceptedProps) assertEquals(Snapshot.of("snapshot"), interceptedSnapshot) @@ -847,11 +831,11 @@ internal class WorkflowNodeTest { id = workflow.id(key = "foo"), workflow = workflow.asStatefulWorkflow(), initialProps = "old", - snapshot = null, + initialSnapshot = null, interceptor = interceptor, baseContext = Unconfined, parent = TestSession(42) - ) + ).apply { startSession() } val rendering = node.render(workflow, "new") assertEquals("old", interceptedOld) @@ -893,11 +877,11 @@ internal class WorkflowNodeTest { id = workflow.id(key = "foo"), workflow = workflow.asStatefulWorkflow(), initialProps = "props", - snapshot = null, + initialSnapshot = null, interceptor = interceptor, baseContext = Unconfined, parent = TestSession(42) - ) + ).apply { startSession() } val rendering = node.render(workflow, "props") assertEquals("props", interceptedProps) @@ -935,11 +919,11 @@ internal class WorkflowNodeTest { id = workflow.id(key = "foo"), workflow = workflow.asStatefulWorkflow(), initialProps = "old", - snapshot = null, + initialSnapshot = null, interceptor = interceptor, baseContext = Unconfined, parent = TestSession(42) - ) + ).apply { startSession() } val snapshot = node.snapshot(workflow) assertEquals("state", interceptedState) @@ -976,11 +960,11 @@ internal class WorkflowNodeTest { id = workflow.id(key = "foo"), workflow = workflow.asStatefulWorkflow(), initialProps = "old", - snapshot = null, + initialSnapshot = null, interceptor = interceptor, baseContext = Unconfined, parent = TestSession(42) - ) + ).apply { startSession() } val snapshot = node.snapshot(workflow) assertEquals("state", interceptedState) @@ -1017,12 +1001,12 @@ internal class WorkflowNodeTest { id = rootWorkflow.id(key = "foo"), workflow = rootWorkflow.asStatefulWorkflow(), initialProps = "props", - snapshot = null, + initialSnapshot = null, interceptor = interceptor, baseContext = Unconfined, parent = TestSession(42), idCounter = IdCounter() - ) + ).apply { startSession() } val rendering = node.render(rootWorkflow.asStatefulWorkflow(), "props") assertEquals("[root([leaf([[props]], [[props]])])]", rendering) @@ -1037,9 +1021,9 @@ internal class WorkflowNodeTest { workflow.id(), workflow.asStatefulWorkflow(), initialProps = Unit, - snapshot = null, + initialSnapshot = null, baseContext = Unconfined - ) + ).apply { startSession() } val error = assertFailsWith { node.render(workflow.asStatefulWorkflow(), Unit) @@ -1065,9 +1049,9 @@ internal class WorkflowNodeTest { workflow.id(), workflow.asStatefulWorkflow(), initialProps = Unit, - snapshot = null, + initialSnapshot = null, baseContext = Unconfined - ) + ).apply { startSession() } val error = assertFailsWith { node.render(workflow.asStatefulWorkflow(), Unit) @@ -1092,9 +1076,9 @@ internal class WorkflowNodeTest { workflow.id(), workflow.asStatefulWorkflow(), initialProps = Unit, - snapshot = null, + initialSnapshot = null, baseContext = Unconfined - ) + ).apply { startSession() } val (_, sink) = node.render(workflow.asStatefulWorkflow(), Unit) sink.send("hello") @@ -1117,10 +1101,10 @@ internal class WorkflowNodeTest { workflow.id(), workflow.asStatefulWorkflow(), initialProps = Unit, - snapshot = null, + initialSnapshot = null, baseContext = Unconfined, emitOutputToParent = { WorkflowOutput("output:$it") } - ) + ).apply { startSession() } val rendering = node.render(workflow.asStatefulWorkflow(), Unit) rendering.send("hello") @@ -1142,10 +1126,10 @@ internal class WorkflowNodeTest { workflow.id(), workflow.asStatefulWorkflow(), initialProps = Unit, - snapshot = null, + initialSnapshot = null, baseContext = Unconfined, emitOutputToParent = { WorkflowOutput(it) } - ) + ).apply { startSession() } val rendering = node.render(workflow.asStatefulWorkflow(), Unit) rendering.send("hello") @@ -1173,9 +1157,9 @@ internal class WorkflowNodeTest { workflow.id(), workflow.asStatefulWorkflow(), initialProps = Unit, - snapshot = null, + initialSnapshot = null, baseContext = Unconfined - ) + ).apply { startSession() } node.render(workflow.asStatefulWorkflow(), Unit) runBlocking { @@ -1198,10 +1182,10 @@ internal class WorkflowNodeTest { workflow.id(), workflow.asStatefulWorkflow(), initialProps = Unit, - snapshot = null, + initialSnapshot = null, baseContext = Unconfined, emitOutputToParent = { WorkflowOutput("output:$it") } - ) + ).apply { startSession() } node.render(workflow.asStatefulWorkflow(), Unit) val output = runBlocking { @@ -1223,10 +1207,10 @@ internal class WorkflowNodeTest { workflow.id(), workflow.asStatefulWorkflow(), initialProps = Unit, - snapshot = null, + initialSnapshot = null, baseContext = Unconfined, emitOutputToParent = { WorkflowOutput(it) } - ) + ).apply { startSession() } node.render(workflow.asStatefulWorkflow(), Unit) val output = runBlocking { diff --git a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/WorkflowRunnerTest.kt b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/WorkflowRunnerTest.kt similarity index 95% rename from workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/WorkflowRunnerTest.kt rename to workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/WorkflowRunnerTest.kt index 21c2d3a0b3..faa7a984c3 100644 --- a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/WorkflowRunnerTest.kt +++ b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/WorkflowRunnerTest.kt @@ -1,18 +1,9 @@ -package com.squareup.workflow1.internal +package com.squareup.workflow1 -import com.squareup.workflow1.NoopWorkflowInterceptor -import com.squareup.workflow1.RuntimeConfig import com.squareup.workflow1.RuntimeConfig.Companion import com.squareup.workflow1.RuntimeConfig.FrameTimeout import com.squareup.workflow1.RuntimeConfig.RenderPerAction -import com.squareup.workflow1.Worker -import com.squareup.workflow1.Workflow -import com.squareup.workflow1.WorkflowExperimentalRuntime -import com.squareup.workflow1.WorkflowOutput -import com.squareup.workflow1.action -import com.squareup.workflow1.runningWorker -import com.squareup.workflow1.stateful -import com.squareup.workflow1.stateless +import com.squareup.workflow1.internal.ParameterizedTestRunner import kotlinx.coroutines.CancellationException import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.async diff --git a/workflow-runtime/src/jvmWorkflowNode/kotlin/com/squareup/workflow1/WorkflowNodeBenchmark.kt b/workflow-runtime/src/jvmWorkflowNode/kotlin/com/squareup/workflow1/WorkflowNodeBenchmark.kt index bcbdf0ef4a..8e618bd8e8 100644 --- a/workflow-runtime/src/jvmWorkflowNode/kotlin/com/squareup/workflow1/WorkflowNodeBenchmark.kt +++ b/workflow-runtime/src/jvmWorkflowNode/kotlin/com/squareup/workflow1/WorkflowNodeBenchmark.kt @@ -6,8 +6,6 @@ import com.squareup.workflow1.FractalWorkflow.Props.RENDER_LEAVES import com.squareup.workflow1.FractalWorkflow.Props.RUN_WORKERS import com.squareup.workflow1.FractalWorkflow.Props.SKIP_FIRST_LEAF import com.squareup.workflow1.WorkflowAction.Companion.noAction -import com.squareup.workflow1.internal.WorkflowNode -import com.squareup.workflow1.internal.id import kotlinx.coroutines.Dispatchers.Unconfined import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow @@ -108,9 +106,9 @@ internal open class WorkflowNodeBenchmark { id = this.id(), workflow = this, initialProps = RENDER_LEAVES, - snapshot = null, + initialSnapshot = null, baseContext = context - ) + ).apply { startSession() } } /** diff --git a/workflow-rx2/dependencies/runtimeClasspath.txt b/workflow-rx2/dependencies/runtimeClasspath.txt index 98e3c603de..65a33a3381 100644 --- a/workflow-rx2/dependencies/runtimeClasspath.txt +++ b/workflow-rx2/dependencies/runtimeClasspath.txt @@ -1,11 +1,7 @@ :workflow-core -app.cash.molecule:molecule-runtime-jvm:0.3.0-SNAPSHOT -app.cash.molecule:molecule-runtime:0.3.0-SNAPSHOT com.squareup.okio:okio-jvm:3.0.0 com.squareup.okio:okio:3.0.0 io.reactivex.rxjava2:rxjava:2.2.21 -org.jetbrains.compose.runtime:runtime-desktop:1.1.0 -org.jetbrains.compose.runtime:runtime:1.1.0 org.jetbrains.kotlin:kotlin-bom:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-common:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10 diff --git a/workflow-testing/api/workflow-testing.api b/workflow-testing/api/workflow-testing.api index 028dc77e6f..940ad14b6b 100644 --- a/workflow-testing/api/workflow-testing.api +++ b/workflow-testing/api/workflow-testing.api @@ -1,7 +1,6 @@ public final class com/squareup/workflow1/testing/RealRenderTester : com/squareup/workflow1/testing/RenderTester, com/squareup/workflow1/BaseRenderContext, com/squareup/workflow1/Sink, com/squareup/workflow1/testing/RenderTestResult { public fun (Lcom/squareup/workflow1/StatefulWorkflow;Ljava/lang/Object;Ljava/lang/Object;Ljava/util/List;Ljava/util/List;ZLcom/squareup/workflow1/WorkflowAction;Ljava/util/List;Ljava/util/List;)V public synthetic fun (Lcom/squareup/workflow1/StatefulWorkflow;Ljava/lang/Object;Ljava/lang/Object;Ljava/util/List;Ljava/util/List;ZLcom/squareup/workflow1/WorkflowAction;Ljava/util/List;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun ChildRendering (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function10;)Lkotlin/jvm/functions/Function9; public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function11;)Lkotlin/jvm/functions/Function10; public fun eventHandler (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;)Lkotlin/jvm/functions/Function0; @@ -92,7 +91,6 @@ public final class com/squareup/workflow1/testing/RealRenderTesterKt { public final class com/squareup/workflow1/testing/RenderIdempotencyChecker : com/squareup/workflow1/WorkflowInterceptor { public static final field INSTANCE Lcom/squareup/workflow1/testing/RenderIdempotencyChecker; - public fun Rendering (Ljava/lang/Object;Ljava/lang/Object;Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; 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; diff --git a/workflow-testing/dependencies/runtimeClasspath.txt b/workflow-testing/dependencies/runtimeClasspath.txt index 9093652080..48fa2bae36 100644 --- a/workflow-testing/dependencies/runtimeClasspath.txt +++ b/workflow-testing/dependencies/runtimeClasspath.txt @@ -2,14 +2,10 @@ :workflow-config:config-jvm :workflow-core :workflow-runtime -app.cash.molecule:molecule-runtime-jvm:0.3.0-SNAPSHOT -app.cash.molecule:molecule-runtime:0.3.0-SNAPSHOT com.squareup.okio:okio-jvm:3.0.0 com.squareup.okio:okio:3.0.0 net.java.dev.jna:jna-platform:5.5.0 net.java.dev.jna:jna:5.5.0 -org.jetbrains.compose.runtime:runtime-desktop:1.1.0 -org.jetbrains.compose.runtime:runtime:1.1.0 org.jetbrains.kotlin:kotlin-bom:1.6.10 org.jetbrains.kotlin:kotlin-reflect:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-common:1.6.10 diff --git a/workflow-testing/src/main/java/com/squareup/workflow1/testing/RealRenderTester.kt b/workflow-testing/src/main/java/com/squareup/workflow1/testing/RealRenderTester.kt index 6c40356468..4e230c57ae 100644 --- a/workflow-testing/src/main/java/com/squareup/workflow1/testing/RealRenderTester.kt +++ b/workflow-testing/src/main/java/com/squareup/workflow1/testing/RealRenderTester.kt @@ -1,6 +1,5 @@ package com.squareup.workflow1.testing -import androidx.compose.runtime.Composable import com.squareup.workflow1.BaseRenderContext import com.squareup.workflow1.RenderContext import com.squareup.workflow1.Sink @@ -222,68 +221,6 @@ internal class RealRenderTester( return match.childRendering as ChildRenderingT } - @Composable - override fun ChildRendering( - child: Workflow, - props: ChildPropsT, - key: String, - handler: (ChildOutputT) -> WorkflowAction - ): ChildRenderingT { - val identifierPair = Pair(child.identifier, key) - require(identifierPair !in renderedChildren) { - "Expected keys to be unique for ${child.identifier}: key=\"$key\"" - } - renderedChildren += identifierPair - - val description = buildString { - append("child ") - append(child.identifier) - if (key.isNotEmpty()) { - append(" with key \"$key\"") - } - } - val invocation = createRenderChildInvocation(child, props, key) - val matches = expectations.filterIsInstance() - .mapNotNull { - val matchResult = it.matcher(invocation) - if (matchResult is Matched) Pair(it, matchResult) else null - } - if (matches.isEmpty()) { - throw AssertionError("Tried to render unexpected $description") - } - - val exactMatches = matches.filter { it.first.exactMatch } - val (_, match) = when { - exactMatches.size == 1 -> { - exactMatches.single() - .also { (expected, _) -> - expectations -= expected - consumedExpectations += expected - } - } - exactMatches.size > 1 -> { - throw AssertionError( - "Multiple expectations matched $description:\n" + - exactMatches.joinToString(separator = "\n") { " ${it.first.describe()}" } - ) - } - // Inexact matches are not consumable. - else -> matches.first() - } - - if (match.output != null) { - check(processedAction == null) { - "Expected only one output to be expected: $description expected to emit " + - "${match.output.value} but $processedAction was already processed." - } - @Suppress("UNCHECKED_CAST") - processedAction = handler(match.output.value as ChildOutputT) - } - - @Suppress("UNCHECKED_CAST") - return match.childRendering as ChildRenderingT - } - override fun runningSideEffect( key: String, sideEffect: suspend CoroutineScope.() -> Unit diff --git a/workflow-tracing/api/workflow-tracing.api b/workflow-tracing/api/workflow-tracing.api index e29127d49b..bdce09a3c4 100644 --- a/workflow-tracing/api/workflow-tracing.api +++ b/workflow-tracing/api/workflow-tracing.api @@ -21,7 +21,6 @@ public final class com/squareup/workflow1/diagnostic/tracing/TracingWorkflowInte public fun (Lcom/squareup/workflow1/diagnostic/tracing/MemoryStats;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)V public fun (Lcom/squareup/workflow1/diagnostic/tracing/MemoryStats;Lkotlin/jvm/functions/Function2;)V public synthetic fun (Lcom/squareup/workflow1/diagnostic/tracing/MemoryStats;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun Rendering (Ljava/lang/Object;Ljava/lang/Object;Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; 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; diff --git a/workflow-tracing/dependencies/runtimeClasspath.txt b/workflow-tracing/dependencies/runtimeClasspath.txt index 6853129da0..f32d25e615 100644 --- a/workflow-tracing/dependencies/runtimeClasspath.txt +++ b/workflow-tracing/dependencies/runtimeClasspath.txt @@ -1,14 +1,10 @@ :trace-encoder :workflow-core :workflow-runtime -app.cash.molecule:molecule-runtime-jvm:0.3.0-SNAPSHOT -app.cash.molecule:molecule-runtime:0.3.0-SNAPSHOT com.squareup.moshi:moshi-adapters:1.13.0 com.squareup.moshi:moshi:1.13.0 com.squareup.okio:okio-jvm:3.0.0 com.squareup.okio:okio:3.0.0 -org.jetbrains.compose.runtime:runtime-desktop:1.1.0 -org.jetbrains.compose.runtime:runtime:1.1.0 org.jetbrains.kotlin:kotlin-bom:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-common:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10 diff --git a/workflow-ui/compose-tooling/dependencies/releaseRuntimeClasspath.txt b/workflow-ui/compose-tooling/dependencies/releaseRuntimeClasspath.txt index a45789daa1..1e41658f8d 100644 --- a/workflow-ui/compose-tooling/dependencies/releaseRuntimeClasspath.txt +++ b/workflow-ui/compose-tooling/dependencies/releaseRuntimeClasspath.txt @@ -61,11 +61,8 @@ androidx.vectordrawable:vectordrawable-animated:1.1.0 androidx.vectordrawable:vectordrawable:1.1.0 androidx.versionedparcelable:versionedparcelable:1.1.1 androidx.viewpager:viewpager:1.0.0 -app.cash.molecule:molecule-runtime-android:0.3.0-SNAPSHOT -app.cash.molecule:molecule-runtime:0.3.0-SNAPSHOT com.squareup.okio:okio-jvm:3.0.0 com.squareup.okio:okio:3.0.0 -org.jetbrains.compose.runtime:runtime:1.1.0 org.jetbrains.kotlin:kotlin-bom:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-common:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10 diff --git a/workflow-ui/compose/dependencies/releaseRuntimeClasspath.txt b/workflow-ui/compose/dependencies/releaseRuntimeClasspath.txt index 451d58e91b..77eba98621 100644 --- a/workflow-ui/compose/dependencies/releaseRuntimeClasspath.txt +++ b/workflow-ui/compose/dependencies/releaseRuntimeClasspath.txt @@ -51,11 +51,8 @@ androidx.vectordrawable:vectordrawable-animated:1.1.0 androidx.vectordrawable:vectordrawable:1.1.0 androidx.versionedparcelable:versionedparcelable:1.1.1 androidx.viewpager:viewpager:1.0.0 -app.cash.molecule:molecule-runtime-android:0.3.0-SNAPSHOT -app.cash.molecule:molecule-runtime:0.3.0-SNAPSHOT com.squareup.okio:okio-jvm:3.0.0 com.squareup.okio:okio:3.0.0 -org.jetbrains.compose.runtime:runtime:1.1.0 org.jetbrains.kotlin:kotlin-bom:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-common:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10 diff --git a/workflow-ui/container-android/dependencies/releaseRuntimeClasspath.txt b/workflow-ui/container-android/dependencies/releaseRuntimeClasspath.txt index 9a66deff2b..75b032cd51 100644 --- a/workflow-ui/container-android/dependencies/releaseRuntimeClasspath.txt +++ b/workflow-ui/container-android/dependencies/releaseRuntimeClasspath.txt @@ -11,7 +11,6 @@ androidx.appcompat:appcompat:1.3.1 androidx.arch.core:core-common:2.1.0 androidx.arch.core:core-runtime:2.1.0 androidx.collection:collection:1.1.0 -androidx.compose.runtime:runtime:1.1.0 androidx.core:core-ktx:1.6.0 androidx.core:core:1.6.0 androidx.cursoradapter:cursoradapter:1.0.0 @@ -35,11 +34,8 @@ androidx.vectordrawable:vectordrawable-animated:1.1.0 androidx.vectordrawable:vectordrawable:1.1.0 androidx.versionedparcelable:versionedparcelable:1.1.1 androidx.viewpager:viewpager:1.0.0 -app.cash.molecule:molecule-runtime-android:0.3.0-SNAPSHOT -app.cash.molecule:molecule-runtime:0.3.0-SNAPSHOT com.squareup.okio:okio-jvm:3.0.0 com.squareup.okio:okio:3.0.0 -org.jetbrains.compose.runtime:runtime:1.1.0 org.jetbrains.kotlin:kotlin-bom:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-common:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10 diff --git a/workflow-ui/core-android/api/core-android.api b/workflow-ui/core-android/api/core-android.api index db003b6291..fa39e523f2 100644 --- a/workflow-ui/core-android/api/core-android.api +++ b/workflow-ui/core-android/api/core-android.api @@ -1,10 +1,10 @@ public final class com/squareup/workflow1/ui/AndroidRenderWorkflowKt { - public static final fun renderWorkflowIn (Lcom/squareup/workflow1/Workflow;Lkotlinx/coroutines/CoroutineScope;Landroidx/lifecycle/SavedStateHandle;Ljava/util/List;Lcom/squareup/workflow1/RuntimeConfig;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/StateFlow; - public static final fun renderWorkflowIn (Lcom/squareup/workflow1/Workflow;Lkotlinx/coroutines/CoroutineScope;Ljava/lang/Object;Landroidx/lifecycle/SavedStateHandle;Ljava/util/List;Lcom/squareup/workflow1/RuntimeConfig;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/StateFlow; - public static final fun renderWorkflowIn (Lcom/squareup/workflow1/Workflow;Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/flow/StateFlow;Landroidx/lifecycle/SavedStateHandle;Ljava/util/List;Lcom/squareup/workflow1/RuntimeConfig;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/StateFlow; - public static synthetic fun renderWorkflowIn$default (Lcom/squareup/workflow1/Workflow;Lkotlinx/coroutines/CoroutineScope;Landroidx/lifecycle/SavedStateHandle;Ljava/util/List;Lcom/squareup/workflow1/RuntimeConfig;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/flow/StateFlow; - public static synthetic fun renderWorkflowIn$default (Lcom/squareup/workflow1/Workflow;Lkotlinx/coroutines/CoroutineScope;Ljava/lang/Object;Landroidx/lifecycle/SavedStateHandle;Ljava/util/List;Lcom/squareup/workflow1/RuntimeConfig;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/flow/StateFlow; - public static synthetic fun renderWorkflowIn$default (Lcom/squareup/workflow1/Workflow;Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/flow/StateFlow;Landroidx/lifecycle/SavedStateHandle;Ljava/util/List;Lcom/squareup/workflow1/RuntimeConfig;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/flow/StateFlow; + public static final fun renderWorkflowIn (Lcom/squareup/workflow1/Workflow;Lkotlinx/coroutines/CoroutineScope;Landroidx/lifecycle/SavedStateHandle;Ljava/util/List;Lcom/squareup/workflow1/RuntimeConfig;Lcom/squareup/workflow1/WorkflowRuntimePlugin;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/StateFlow; + public static final fun renderWorkflowIn (Lcom/squareup/workflow1/Workflow;Lkotlinx/coroutines/CoroutineScope;Ljava/lang/Object;Landroidx/lifecycle/SavedStateHandle;Ljava/util/List;Lcom/squareup/workflow1/RuntimeConfig;Lcom/squareup/workflow1/WorkflowRuntimePlugin;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/StateFlow; + public static final fun renderWorkflowIn (Lcom/squareup/workflow1/Workflow;Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/flow/StateFlow;Landroidx/lifecycle/SavedStateHandle;Ljava/util/List;Lcom/squareup/workflow1/RuntimeConfig;Lcom/squareup/workflow1/WorkflowRuntimePlugin;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/StateFlow; + public static synthetic fun renderWorkflowIn$default (Lcom/squareup/workflow1/Workflow;Lkotlinx/coroutines/CoroutineScope;Landroidx/lifecycle/SavedStateHandle;Ljava/util/List;Lcom/squareup/workflow1/RuntimeConfig;Lcom/squareup/workflow1/WorkflowRuntimePlugin;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/flow/StateFlow; + public static synthetic fun renderWorkflowIn$default (Lcom/squareup/workflow1/Workflow;Lkotlinx/coroutines/CoroutineScope;Ljava/lang/Object;Landroidx/lifecycle/SavedStateHandle;Ljava/util/List;Lcom/squareup/workflow1/RuntimeConfig;Lcom/squareup/workflow1/WorkflowRuntimePlugin;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/flow/StateFlow; + public static synthetic fun renderWorkflowIn$default (Lcom/squareup/workflow1/Workflow;Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/flow/StateFlow;Landroidx/lifecycle/SavedStateHandle;Ljava/util/List;Lcom/squareup/workflow1/RuntimeConfig;Lcom/squareup/workflow1/WorkflowRuntimePlugin;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/flow/StateFlow; } public abstract interface class com/squareup/workflow1/ui/AndroidScreen : com/squareup/workflow1/ui/Screen { diff --git a/workflow-ui/core-android/dependencies/releaseRuntimeClasspath.txt b/workflow-ui/core-android/dependencies/releaseRuntimeClasspath.txt index 5a69c38578..c53b3611c9 100644 --- a/workflow-ui/core-android/dependencies/releaseRuntimeClasspath.txt +++ b/workflow-ui/core-android/dependencies/releaseRuntimeClasspath.txt @@ -7,7 +7,6 @@ androidx.annotation:annotation:1.2.0 androidx.arch.core:core-common:2.1.0 androidx.arch.core:core-runtime:2.1.0 androidx.collection:collection:1.1.0 -androidx.compose.runtime:runtime:1.1.0 androidx.core:core-ktx:1.6.0 androidx.core:core:1.6.0 androidx.customview:customview:1.0.0 @@ -26,11 +25,8 @@ androidx.tracing:tracing:1.0.0 androidx.transition:transition:1.4.1 androidx.versionedparcelable:versionedparcelable:1.1.1 androidx.viewpager:viewpager:1.0.0 -app.cash.molecule:molecule-runtime-android:0.3.0-SNAPSHOT -app.cash.molecule:molecule-runtime:0.3.0-SNAPSHOT com.squareup.okio:okio-jvm:3.0.0 com.squareup.okio:okio:3.0.0 -org.jetbrains.compose.runtime:runtime:1.1.0 org.jetbrains.kotlin:kotlin-bom:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-common:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10 diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/AndroidRenderWorkflow.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/AndroidRenderWorkflow.kt index ab32271c5a..5acbaab41d 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/AndroidRenderWorkflow.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/AndroidRenderWorkflow.kt @@ -4,6 +4,7 @@ import androidx.lifecycle.SavedStateHandle import com.squareup.workflow1.RuntimeConfig import com.squareup.workflow1.Workflow import com.squareup.workflow1.WorkflowInterceptor +import com.squareup.workflow1.WorkflowRuntimePlugin import com.squareup.workflow1.renderWorkflowIn import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -81,6 +82,7 @@ public fun renderWorkflowIn( savedStateHandle: SavedStateHandle? = null, interceptors: List = emptyList(), runtimeConfig: RuntimeConfig = RuntimeConfig.DEFAULT_CONFIG, + workflowRuntimePlugin: WorkflowRuntimePlugin? = null, onOutput: suspend (OutputT) -> Unit = {} ): StateFlow { return renderWorkflowIn( @@ -90,6 +92,7 @@ public fun renderWorkflowIn( savedStateHandle = savedStateHandle, interceptors = interceptors, runtimeConfig = runtimeConfig, + workflowRuntimePlugin = workflowRuntimePlugin, onOutput = onOutput ) } @@ -155,6 +158,9 @@ public fun renderWorkflowIn( * @param runtimeConfig * Configuration for the Workflow Runtime. * + * @param workflowRuntimePlugin + * This is used to plug in Runtime functionality that lives in other modules. + * * @return * A [StateFlow] of [RenderingT]s that will emit any time the root workflow creates a new * rendering. @@ -168,9 +174,17 @@ public fun renderWorkflowIn( savedStateHandle: SavedStateHandle? = null, interceptors: List = emptyList(), runtimeConfig: RuntimeConfig = RuntimeConfig.DEFAULT_CONFIG, + workflowRuntimePlugin: WorkflowRuntimePlugin? = null, onOutput: suspend (OutputT) -> Unit = {} ): StateFlow = renderWorkflowIn( - workflow, scope, MutableStateFlow(prop), savedStateHandle, interceptors, runtimeConfig, onOutput + workflow = workflow, + scope = scope, + props = MutableStateFlow(prop), + savedStateHandle = savedStateHandle, + interceptors = interceptors, + runtimeConfig = runtimeConfig, + workflowRuntimePlugin = workflowRuntimePlugin, + onOutput = onOutput ) /** @@ -250,6 +264,9 @@ public fun renderWorkflowIn( * @param runtimeConfig * Configuration for the Workflow Runtime. * + * @param workflowRuntimePlugin + * This is used to plug in Runtime functionality that lives in other modules. + * * @return * A [StateFlow] of [RenderingT]s that will emit any time the root workflow creates a new * rendering. @@ -263,11 +280,19 @@ public fun renderWorkflowIn( savedStateHandle: SavedStateHandle? = null, interceptors: List = emptyList(), runtimeConfig: RuntimeConfig = RuntimeConfig.DEFAULT_CONFIG, + workflowRuntimePlugin: WorkflowRuntimePlugin? = null, onOutput: suspend (OutputT) -> Unit = {} ): StateFlow { val restoredSnap = savedStateHandle?.get(KEY)?.snapshot val renderingsAndSnapshots = renderWorkflowIn( - workflow, scope, props, restoredSnap, interceptors, runtimeConfig, onOutput + workflow = workflow, + scope = scope, + props = props, + initialSnapshot = restoredSnap, + interceptors = interceptors, + runtimeConfig = runtimeConfig, + workflowRuntimePlugin = workflowRuntimePlugin, + onOutput = onOutput ) return renderingsAndSnapshots diff --git a/workflow-ui/radiography/dependencies/releaseRuntimeClasspath.txt b/workflow-ui/radiography/dependencies/releaseRuntimeClasspath.txt index 1378806cc1..7781d360b5 100644 --- a/workflow-ui/radiography/dependencies/releaseRuntimeClasspath.txt +++ b/workflow-ui/radiography/dependencies/releaseRuntimeClasspath.txt @@ -8,7 +8,6 @@ androidx.annotation:annotation:1.2.0 androidx.arch.core:core-common:2.1.0 androidx.arch.core:core-runtime:2.1.0 androidx.collection:collection:1.1.0 -androidx.compose.runtime:runtime:1.1.0 androidx.core:core-ktx:1.6.0 androidx.core:core:1.6.0 androidx.customview:customview:1.0.0 @@ -27,13 +26,10 @@ androidx.tracing:tracing:1.0.0 androidx.transition:transition:1.4.1 androidx.versionedparcelable:versionedparcelable:1.1.1 androidx.viewpager:viewpager:1.0.0 -app.cash.molecule:molecule-runtime-android:0.3.0-SNAPSHOT -app.cash.molecule:molecule-runtime:0.3.0-SNAPSHOT com.squareup.curtains:curtains:1.2.1 com.squareup.okio:okio-jvm:3.0.0 com.squareup.okio:okio:3.0.0 com.squareup.radiography:radiography:2.4.0 -org.jetbrains.compose.runtime:runtime:1.1.0 org.jetbrains.kotlin:kotlin-bom:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-common:1.6.10 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10