Align frame snapshot/invalidation with Android's model#3096
Align frame snapshot/invalidation with Android's model#3096Ivan Matkov (MatkovIvan) wants to merge 2 commits into
Conversation
e44c7d8 to
c175afb
Compare
|
Is there a reason to bundle all these small changes into one PR? |
Initially, there was only one change + a couple of fixups to make CI green again. |
|
Alexander Maryanovsky (@m-sasha) I've extracted a few independent parts into separate PRs: #3106 #3107 #3108 I'll rebase this one on top once they are merged |
| // (applies happen once per frame on the main looper, not between phases). | ||
| // This between-phase apply is a temporary workaround kept only to preserve current | ||
| // behavior for OffsetToFocusedRect (iOS FocusableAboveKeyboard). | ||
| Snapshot.sendApplyNotifications() |
There was a problem hiding this comment.
If possible - I would make it iOS-only call to be able to eliminate it safely later.
There was a problem hiding this comment.
I thought about it, and decided not to introduce such diffs between platforms.
Also, OffsetToFocusedRect is in skikoMain
| @@ -200,11 +156,23 @@ internal abstract class BaseComposeScene( | |||
| if (isClosed) return | |||
|
|
|||
| postponeInvalidation("BaseComposeScene:draw") { | |||
There was a problem hiding this comment.
I had no chance to check the implementation of the SingleComposeSceneRenderingScope - having two embedded postponeInvalidation calls here and on the platform level - is a an overkill. That must be simplified.
There was a problem hiding this comment.
SingleComposeSceneRenderingScope is just for migration, it should go away there (on platform).
Here is just de-dup/optimization logic.
Probably we can drop this one too at some point, but it's out of the scope for now
There was a problem hiding this comment.
SingleComposeSceneRenderingScope moved postpone logic on the platform level - which mean, the same logic is no longer needed in the compose scene - at least for the rendering-related methods.
There was a problem hiding this comment.
SingleComposeSceneRenderingScope is just one of the usages that does it for cross-phase invalidations (to catch if needDraw was raised after layout and down back during draw).
Scene itself has this logic within one phase only.
Both are required for now
There was a problem hiding this comment.
Well... Still do not like this approach, but currently I cannot say if it's correct or not.
What I see here: if we want to unlock an ability for the platforms to write an optimized code - the postponeInvalidation logic must be moved the the platform level and platforms must decide how to arrange invalidation, snapshot adjustments and other stuff in the most optimal way.
Personally I cannot say if these changes are what I really need to make the rendering properly optimized on iOS without making bunch of experiments. That's why I'm thinking that current API isn't flexible enough.
There was a problem hiding this comment.
I don't see how this postponeInvalidation conflicts with "an optimized code" and why "current API isn't flexible enough".
There was a problem hiding this comment.
Explained in DM: Current API tries to be universal for all platforms, which is not optimal in terms of a specific platform. Let's keep as it is and back to the discussion when face real cases.
…ead invalidation Removes SnapshotInvalidationTracker and its CommandList queue. Owner snapshot observers now run directly, mirroring Android's AndroidComposeView: callbacks execute inline when already on the compose thread, otherwise they are posted to the host's shared trampoline queue (FrameRecomposer.runOnComposeThread). - RootNodeOwner takes `invalidate` and `onChangedExecutor` directly, owns its own hasPendingMeasureOrLayout / hasPendingDraw flags (now @volatile, since the ComposeScene contract allows reading them from any thread), and observes via OwnerSnapshotObserver(onChangedExecutor). - FrameRecomposer gains runOnComposeThread + composeThreadId (recorded where the recomposer runs on the host thread) and requires a single-thread ContinuationInterceptor in its context. - BaseComposeScene drops the tracker; invokeInvalidationCallbacks() forwards the owners' pending flags to the platform invalidate callbacks. - ComposeScene drops hasPendingSnapshotCommands (no longer a separate queue); hasInvalidations() = hasPendingMeasureOrLayout || hasPendingDraw. - Scenes pass their owners ::invokeInvalidationCallbacks + ::runOnComposeThread. - The GlobalSnapshotManager race that forced ImageComposeScene tests onto the UI thread is gone, so useInUiThread {} reverts to use {}. Tests that depend on the new invalidation model land here so this commit stays green: RenderPhasesTest's scroll/pan cases move to runSkikoComposeUiTest (they fail under runInternalSkikoComposeUiTest's StandardTestDispatcher now), and ComposeSceneTest gets a Timeout rule.
Replaces the un-Android between-phase Snapshot.sendApplyNotifications() that postponeInvalidation ran around every scene operation with the apply model Android actually uses. - postponeInvalidation no longer applies between the measure/layout and draw sub-phases; Android's Choreographer runs measure -> layout -> draw in one traversal and never applies between phases. - BaseComposeScene.draw() advances the global snapshot via Snapshot.notifyObjectsInitialized() right before drawing, mirroring AndroidComposeView.dispatchDraw (post-385d71d1ec5): lighter than sendApplyNotifications and coalesces a placement-write cascade into one frame. - FrameRecomposer.performFrame drives applies via its pump + the per-context GlobalSnapshotManager, so deferred owner-invalidation lands on frame/input boundaries. - OffsetToFocusedRect: document the remaining between-phase-apply dependency (iOS FocusableAboveKeyboard) as a FIXME. Also tidies FrameRecomposer's two-queue naming to match AndroidUiDispatcher: effectDispatcher/recomposeDispatcher -> trampolineDispatcher/frameDispatcher and performScheduledEffects/performScheduledRecomposerTasks -> performTrampolineDispatch/performFrameDispatch, with KDoc stating each rolls its loop synchronously.
c175afb to
0664c6a
Compare
There was a problem hiding this comment.
Let's Alexander Maryanovsky (@m-sasha) also look if we have time, he worked on this before
Summary
The non-Android frame pipeline accumulated several workarounds around snapshot apply, owner invalidation, that diverge from what
AndroidComposeView/Choreographeractually do. This PR removes those workarounds and aligns the pipeline to Android's canonical model.Reference model (Android):
Choreographer.doFrame()runs the whole sequence recompose → layout → drawwithout yielding to the
Looper: no queued task (includingGlobalSnapshotManager's apply post) runs between phases. Compose Multiplatform was applying snapshots between phases and routing owner invalidations through per-scene async queues - neither has an Android counterpart.What changes
The branch is structured as reviewable commits:
Replace per-scene
SnapshotInvalidationTrackerwith inline compose-thread invalidationDeletes
SnapshotInvalidationTracker+ itsCommandListqueue.Owner snapshot observers now run inline when already on the compose thread, otherwise post to the host's shared trampoline (
FrameRecomposer.runOnComposeThread), mirroringAndroidComposeView.Removes the off-thread queues whose were the root of the CMP-7838 / CMP-7067 deadlocks;
useInUiThread {}reverts touse {}.Align scene snapshot-apply to Android; tidy
FrameRecomposerqueue namingpostponeInvalidationno longer applies between measure/layout and draw.draw()advances the snapshot viaSnapshot.notifyObjectsInitialized()right before drawing - mirroringAndroidComposeView.dispatchDraw, lighter thansendApplyNotificationsand coalescing a placement-write cascade into one frame.Renames the two
FrameRecomposerqueues to matchAndroidUiDispatcher(trampolineDispatcher/frameDispatcher).Fixes
Known follow-up
CMP-10291
OffsetToFocusedRectrelies on inter-phaseSnapshotapply between layout and drawRelease Notes
N/A