[go: nahoru, domu]

Improve Magnifier performance

Current Magnifier implementation unnecessarily consumes resources when there's no Magnifier shown.

When a MagnifierNode is attached to a modifier hierarchy, it receives global position updates even when the magnifier is not showing. Always calculating the `positionInRoot` becomes wasteful. Instead the new solution signals a change to coordinates by keeping it in a Snapshot state wrapper while setting mutation policy to never. The actual `positionInRoot` calculation only happens when necessary.

Similarly, every draw call starts a coroutine which is already wasteful even when magnifier is showing. The new approach uses a channel to send draw signals to a consumer that starts when the node is attached.

Before this CL;

```
158,269   ns         275 allocs    Trace    BasicTextField2ToggleTextBenchmark.layout[length=32]
4,976   ns           0 allocs    Trace    BasicTextField2ToggleTextBenchmark.first_draw[length=32]
3,507,578   ns         605 allocs    Trace    BasicTextField2ToggleTextBenchmark.first_layout[length=32]
6,700   ns           0 allocs    Trace    BasicTextField2ToggleTextBenchmark.toggleText_measure[length=32]
518,848   ns         247 allocs    Trace    BasicTextField2ToggleTextBenchmark.toggleText_draw[length=32]
1,988,724   ns        1221 allocs    Trace    BasicTextField2ToggleTextBenchmark.first_measure[length=32]
5,346,187   ns        8072 allocs    Trace    BasicTextField2ToggleTextBenchmark.first_compose[length=32]
3,823   ns           0 allocs    Trace    BasicTextField2ToggleTextBenchmark.toggleText_layout[length=32]
60,488   ns          51 allocs    Trace    BasicTextField2ToggleTextBenchmark.toggleText_recompose[length=32]
```

After this CL;

```
112,220   ns         144 allocs    Trace    BasicTextField2ToggleTextBenchmark.layout[length=32]
4,801   ns           0 allocs    Trace    BasicTextField2ToggleTextBenchmark.first_draw[length=32]
2,840,854   ns         475 allocs    Trace    BasicTextField2ToggleTextBenchmark.first_layout[length=32]
6,585   ns           0 allocs    Trace    BasicTextField2ToggleTextBenchmark.toggleText_measure[length=32]
395,176   ns          97 allocs    Trace    BasicTextField2ToggleTextBenchmark.toggleText_draw[length=32]
1,938,467   ns        1221 allocs    Trace    BasicTextField2ToggleTextBenchmark.first_measure[length=32]
4,558,038   ns        7919 allocs    Trace    BasicTextField2ToggleTextBenchmark.first_compose[length=32]
3,805   ns           0 allocs    Trace    BasicTextField2ToggleTextBenchmark.toggleText_layout[length=32]
65,024   ns          51 allocs    Trace    BasicTextField2ToggleTextBenchmark.toggleText_recompose[length=32]
```

Test: MagnifierTest
Test: gradle :compose:foundation:foundation:cAT
Test: manually tested with BTF1 and BTF2
Test: New benchmark
Change-Id: I546b9dd1bb20e0e71d19b1da513b564f73b93c3a
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt
index 4cb66ac..465fc46 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt
@@ -1034,7 +1034,14 @@
     // Hide the magnifier when dragged too far (outside the horizontal bounds of how big the
     // magnifier actually is). See
     // https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/widget/Editor.java;l=5228-5231;drc=2fdb6bd709be078b72f011334362456bb758922c
-    if ((dragX - textConstrainedX).absoluteValue > magnifierSize.width / 2) {
+    // Also check whether magnifierSize is calculated. A platform magnifier instance is not
+    // created until it's requested for the first time. So the size will only be calculated after we
+    // return a specified offset from this function.
+    // It is very unlikely that this behavior would cause a flicker since magnifier immediately
+    // shows up where the pointer is being dragged. The pointer needs to drag further than the half
+    // of magnifier's width to hide by the following logic.
+    if (magnifierSize != IntSize.Zero &&
+        (dragX - textConstrainedX).absoluteValue > magnifierSize.width / 2) {
         return Offset.Unspecified
     }