-
Notifications
You must be signed in to change notification settings - Fork 29.1k
Leader not always dirty #92598
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Leader not always dirty #92598
Conversation
/cc @dnfield |
@@ -2302,6 +2378,21 @@ class FollowerLayer extends ContainerLayer { | |||
/// * [unlinkedOffset], for when the layer is not linked. | |||
Offset? linkedOffset; | |||
|
|||
LayerLinkHandle? _leaderHandler; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: _leaderHandle
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM but I'm not too familiar with how layers work. I think @Hixie originally wrote the leader/follower layers.
@@ -2354,7 +2354,9 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, | |||
_clipRectLayer.layer = null; | |||
_paintContents(context, offset); | |||
} | |||
_paintHandleLayers(context, getEndpointsForSelection(selection!)); | |||
if (selection!.isValid) { | |||
_paintHandleLayers(context, getEndpointsForSelection(selection!)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It sounds like getEndpointForSelection
should return an empty list if the selection is not valid?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds reasonable. However, when doing that, 142 tests are starting to fail. Let's leave that refactoring to a later PR.
/// purposes. | ||
LeaderLayer? get debugLeader { | ||
LeaderLayer? result; | ||
if (kDebugMode) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question: is there a difference between this and an assert? Assert blocks don't get compiled unless it's in debug mode?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AFAIK, no. Since kDebugMode
is a const bool
the same rules regarding tree shaking should apply to this and this should not end up in a release binary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some packages, like local_hero, depends on the LayerLink.leader
field.
Can it be exposed by a getter method again?
@goderbauer
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Re-exposed with the changes in #96486.
/// | ||
/// When the [FollowerLayer] no longer wants to follow the [LeaderLayer], | ||
/// [LayerLinkHandle.dispose] must be called to disconnect the link. | ||
LayerLinkHandle registerFollower() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be private? register/dispose seems to be already managed in FollowerLayer
's lifecycle event callbacks. Do developers still have call these methods themselves at some point?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. This and the entire LayerLinkHandle class can just be private. Changed it.
/// [LeaderLayer]. | ||
/// | ||
/// If the link is no longer needed, [dispose] must be called to disconnect it. | ||
class LayerLinkHandle { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to make this private?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
@@ -9967,4 +9967,73 @@ void main() { | |||
containsPair('hintText', 'placeholder text'), | |||
); | |||
}); | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: these tests are probably better suited in editable_test.dart
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My thinking for placing them here was: I want to make sure that TextField never pushes these kind of layers. That currently Editable was the source of the bug is an implementation detail. If we reshuffle the implementation, the only thing that's important is that textfield continues to not do this.
|
||
/// {@macro flutter.rendering.FollowerLayer.alwaysNeedsAddToScene} | ||
@override | ||
bool get alwaysNeedsAddToScene => true; | ||
bool get alwaysNeedsAddToScene => _link._connectedFollowers > 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question: when alwaysNeedsAddToScene
gets called the layer tree is finalized right? So it's not possible for a FollowerLayer
to connect after this gets called and accidentally skips the add to scene step?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
alwaysNeedsAddtoScene is called while building the scene. However, this logic happens synchronously and is completed before the next frame build. So no, you can't end up adding a FollowerLayer until the next frame build, which will be working with a new scene.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, this is evaluated during renderView.compositeFrame()
in the snippet below after the layer tree has been finalized:
flutter/packages/flutter/lib/src/rendering/binding.dart
Lines 495 to 505 in 30d3866
void drawFrame() { | |
assert(renderView != null); | |
pipelineOwner.flushLayout(); | |
pipelineOwner.flushCompositingBits(); | |
pipelineOwner.flushPaint(); | |
if (sendFramesToEngine) { | |
renderView.compositeFrame(); // this sends the bits to the GPU | |
pipelineOwner.flushSemantics(); // this also sends the semantics to the OS. | |
_firstFrameSent = true; | |
} | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Layer changes LGTM
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Layer changes LGTM
118b88d
to
7c2c4f6
Compare
Internal benchmark results for this change: https://flutter-flutter-perf.skia.org/e/?begin=1635396934&end=1635464072&keys=X79fae4dec0e50b8f554a8a6f79f45812&num_commits=50&request_type=1&xbaroffset=26369 |
@goderbauer Making We have a package that depends on this currently and I am wondering how I should handle it now. If there is a motivation for the change, could you explain that - I would love to understand it. |
@goderbauer This answer seems nice on the surface and I understand - but it is not always feasible to go through with the necessary overhead. Can you explain how to handle the breaking change? |
The leader property has been exposed again as you can see in
To avoid that your package breaks on things like this in the future I highly recommend contributing your tests to https://github.com/flutter/tests. |
@creativecreatorormaybenot There shouldn't be much overhead if the package is already on GitHub... it's literally just a tiny file that specifies how to check out the package and run the tests. I'm very interested in hearing your experience if this is too much overhead, so that we can make it smoother. |
@Hixie hey, I really appreciate it. I actually started to write a full repo of public-facing tests for the company I work(ed) at, but I ended up not publishing it because it was even too much overhead to finish it, basically needing to maintain another set of tests. For packages, I fully agree. The |
Prior to this change,
LeaderLayer
s hadalwaysNeedsAddToScene
unconditionally set to true, but they only need to always be added to scene while actually connected to a FollowerLayer. This change makes it so and with that allows retained (= raster cachable) rendering of subtrees that containLeaderLayer
s, which are currently not connected to a follower.Change is motivated by customer "money": Fading in a route with a textfield was thrashing the raster cache because the textfield was adding a
LeaderLayer
that prevented retained rendering due to itsalwaysNeedsAddToScene
flag. TheLeaderLayer
is used to properly position toolbars and selection handles. However, when both are not visible (and their corresponding FollowerLayers are therefore not in the tree) there was no reason for this behavior. Raster caching the textfield (along any other content) during the fade transition is desirable for performance reason. This change also adds tests to ensure that a textfield without selection handles and toolbars visible remains raster-cachable.Helps with b/203470598
Pre-launch Checklist
///
).If you need help, consider asking for advice on the #hackers-new channel on Discord.