Skip to content

RA-64264 Make WorkflowLayout sample based on the frame clock #1257

Open
@rjrjr

Description

@rjrjr

The renderings: Flow<Screen> provided to WorkflowLayout.take() can fire a lot more often than is useful, especially in large apps that don't use initialState / StateFlow.value effectively. Can take buffer what it receives, keeping only the latest, and take care to call [show] either once or never for frame clock tick?

Remember that it is absolutely fine to drop renderings; each time a new rendering appears it means that the combined workflow state has changed, and the previous ones are now invalid. We're not an animation tool.

Activity

added
optimizationIssues related to benchmarking and optimization
uiRelated to UI integration
on Jan 24, 2025
changed the title [-]Make WorkflowLayout throttle itself based on the frame clock[/-] [+]Make WorkflowLayout sample based on the frame clock[/+] on Jan 24, 2025
zach-klippenstein

zach-klippenstein commented on Jan 24, 2025

@zach-klippenstein
Collaborator

That would be the default behavior if WorkflowLayout were compose (since it would presumably collectAsState or collectAsStateWithLifecycle). I think for a View we could do something like this:

suspend fun <T> Flow<T>.collectAtFrame(choreographer: Choreographer, collector: () -> Unit) {
  var frameScheduled = false
  var mostRecentValue: T? = null
  val frameCallback = FrameCallback {
    // Not worried about races since everything's on the main thread, otherwise we'd need
    // a lock probably.
    frameScheduled = false
    collector(mostRecentValue)
  }

  try {
    collect { value ->
      mostRecentValue = value
      if (!frameScheduled) {
        frameScheduled = true
        // This is a fire-once callback: It automatically gets removed after firing.
        choreographer.postFrameCallback(frameCallback)
      }
    }
  } finally {
    // Noop if no callback currently posted.
    choreographer.removeFrameCallback(frameCallback)
  }
}

Caveat is I'm not sure how this will sync with Compose's frame callback. Compose also does postFrameCallback, so it would end up being a race and if collectAtFrame lost then the new renderings wouldn't show in Compose until the next frame, I think.

zach-klippenstein

zach-klippenstein commented on Jan 24, 2025

@zach-klippenstein
Collaborator

We're not an animation tool.

If we were, we'd still want to only operate on frames 😂

zach-klippenstein

zach-klippenstein commented on Jan 24, 2025

@zach-klippenstein
Collaborator

Actually I think we should probably just make show debounce itself until frame boundary, not put the logic in take.

Image

zach-klippenstein

zach-klippenstein commented on Jan 24, 2025

@zach-klippenstein
Collaborator

I haven't thought about nesting. If show is called as a result of a parent WorkflowLayout propagating the renderings down the view tree, then it shouldn't defer ever. (Compose also automatically handles this.) I'm not sure if this is a case we need to explicitly handle for views.

Ok so maybe take is the right place to put this debouncing. Assuming, IIRC, that when propagating down the view tree we call show directly and don't use take.

rjrjr

rjrjr commented on Jan 24, 2025

@rjrjr
ContributorAuthor

In the few instances where we have nested WorkflowLayout it's due to a separate workflow runtime being spun up by a legacy presenter object and feeding it directly. There is no recursion built into WorkflowLayout itself; and I can't see a reason for feature code to ever do that beause WorkflowViewStub and @Composable WorkflowRendering() exist and are attractive.

rjrjr

rjrjr commented on Jan 24, 2025

@rjrjr
ContributorAuthor

Did you just make an argument for us to get WorkflowLayout out of the top of our apps and do this instead? https://github.com/square/workflow-kotlin/blob/main/samples/compose-samples/src/main/java/com/squareup/sample/compose/hellocompose/HelloComposeActivity.kt

changed the title [-]Make WorkflowLayout sample based on the frame clock[/-] [+]RA-64264 Make WorkflowLayout sample based on the frame clock[/+] on Feb 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    optimizationIssues related to benchmarking and optimizationuiRelated to UI integration

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

      Participants

      @zach-klippenstein@rjrjr

      Issue actions

        RA-64264 Make WorkflowLayout sample based on the frame clock · Issue #1257 · square/workflow-kotlin