Skip to content

WIP Trying an alternative Compose runtime that properly uses the Applier. - #1563

Draft
zach-klippenstein wants to merge 12 commits into
zachklipp/compose-impl-no-apifrom
zachklipp/workflow-compose-tree
Draft

WIP Trying an alternative Compose runtime that properly uses the Applier.#1563
zach-klippenstein wants to merge 12 commits into
zachklipp/compose-impl-no-apifrom
zachklipp/workflow-compose-tree

Conversation

@zach-klippenstein

Copy link
Copy Markdown
Collaborator

No description provided.

zach-klippenstein and others added 11 commits August 19, 2026 15:39
See go/compose-based-workflows.
Wrap renderWorkflow in rememberSkippableAndRestartableComposable, keyed
on (props, onOutput). When neither key changes and the producer hasn't
been invalidated by a state read, the entire render of that workflow
session is skipped and the cached rendering is returned. State-change
invalidation still flows through the producer's restart group, so
internal updates re-run the producer in the same frame.

Also switch the cache-update check in RememberComposable from equals to
identity. The pre-existing equals comparison violated the workflow
runtime contract that rendering equals/hashCode are allowed to throw,
breaking exceptions_from_renderings_equals_methods_do_not_fail_runtime.
For workflow renderings, equals-based dedup is meaningless anyway since
parent skipping already handles identity dedup; the only remaining job
of the cache check is "did the producer run? if so, take its output".

Microbenchmark deltas (Galaxy A25, ShallowBushyTree / SquareishTree):
- rerenderSingleSiblingByPropsChange:  -67% / -47%
- rerenderSingleSiblingViaStateChange: -58% / -87%
- wideSiblingKeys_rerenderSingleSiblingByPropsChange: -59% / resolved
- initialRenderNewSibling:             -55% / -40%
- tearDownSingleSibling:               -25% / resolved
With small regressions on initial-render-everything cases (around
+11% to +71%) where the wrapper's overhead can't amortize.
… param. Actually made things worse.

Might be because of all the extra vals to try to propagate the changed param.
This is faster than trampolining. Also fixed the skipping code path to actually enable skipping in production.
Replaces PeekableMutableState with WorkflowSnapshotState, a custom StateObject
that holds props, onOutput, and state in a single record. updateAndGetState
short-circuits writes when nothing changed, and applyAction only writes when
the state actually differs.

Includes unit tests covering construction, peekState, updateAndGetState
short-circuit paths, applyAction state writes and output propagation, and
snapshot isolation.

Microbenchmark deltas (ComposeSkipping vs ComposeNoSkip, SquareishTree, SM-A256U1):
- rerenderSingleSiblingViaStateChange: 14.8x faster (204us vs 3.02ms),
  34x fewer allocs (351 vs 11,943)
- rerenderSingleSiblingByPropsChange: 1.8x faster (1.82ms vs 3.33ms),
  2.5x fewer allocs
- wideSiblingKeys_rerenderSingleSiblingByPropsChange: 2.4x faster
  (504us vs 1.20ms), 4x fewer allocs
- initialRenderNewSibling: 1.7x faster, 2.4x fewer allocs
- tearDownSingleSibling: 1.6x faster, 2.4x fewer allocs

ComposeSkipping now beats the legacy AllOpt runtime on sibling-rerender
workloads where unchanged siblings are skippable
(rerenderSingleSiblingViaStateChange Squareish: 0.21x of AllOpt).
… construction.

This is the only way it was being used in production, and this saves a snapshot state.
The default 1-minute runTest timeout fires for the slowest combinations
on physical devices, surfacing as UncompletedCoroutinesError instead of
a real benchmark result. The benchmark body itself is bounded by
measureRepeated, so widening the test timeout just lets the slow cases
finish.

Without this, 14 of 84 combinations timed out on a Galaxy A25.
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
@zach-klippenstein
zach-klippenstein force-pushed the zachklipp/compose-impl-no-api branch from b08d6ef to e2bab3e Compare August 19, 2026 22:39
@zach-klippenstein
zach-klippenstein force-pushed the zachklipp/workflow-compose-tree branch from 75739c9 to 6804f54 Compare August 19, 2026 22:40
@zach-klippenstein
zach-klippenstein force-pushed the zachklipp/compose-impl-no-api branch from e2bab3e to 9f2b298 Compare August 20, 2026 00:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant