Skip to content

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

Merged
merged 9 commits into from
Oct 28, 2021
Merged

Conversation

goderbauer
Copy link
Member

@goderbauer goderbauer commented Oct 27, 2021

Prior to this change, LeaderLayers had alwaysNeedsAddToScene 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 contain LeaderLayers, 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 its alwaysNeedsAddToScene flag. The LeaderLayer 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

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

@flutter-dashboard flutter-dashboard bot added a: text input Entering text in a text field or keyboard related problems f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels. labels Oct 27, 2021
@google-cla google-cla bot added the cla: yes label Oct 27, 2021
@goderbauer
Copy link
Member Author

/cc @dnfield

@@ -2302,6 +2378,21 @@ class FollowerLayer extends ContainerLayer {
/// * [unlinkedOffset], for when the layer is not linked.
Offset? linkedOffset;

LayerLinkHandle? _leaderHandler;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: _leaderHandle

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Copy link
Contributor

@LongCatIsLooong LongCatIsLooong left a 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!));
Copy link
Contributor

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?

Copy link
Member Author

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) {
Copy link
Contributor

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?

Copy link
Member Author

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.

Copy link

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

Copy link
Member Author

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() {
Copy link
Contributor

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?

Copy link
Member Author

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 {
Copy link
Contributor

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?

Copy link
Member Author

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'),
);
});

Copy link
Contributor

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?

Copy link
Member Author

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;
Copy link
Contributor

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?

Copy link
Contributor

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.

Copy link
Member Author

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:

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;
}
}

Copy link
Contributor

@dnfield dnfield left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Layer changes LGTM

Copy link
Contributor

@dnfield dnfield left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Layer changes LGTM

@goderbauer goderbauer force-pushed the leaderNotAlwaysDirty branch from 118b88d to 7c2c4f6 Compare October 28, 2021 16:56
@goderbauer goderbauer merged commit ca788d8 into flutter:master Oct 28, 2021
@goderbauer goderbauer deleted the leaderNotAlwaysDirty branch October 28, 2021 17:35
clocksmith pushed a commit to clocksmith/flutter that referenced this pull request Oct 29, 2021
@zanderso
Copy link
Member

zanderso commented Nov 1, 2021

@creativecreatorormaybenot
Copy link
Contributor

@goderbauer Making _leader prviate here is a breaking change - is this necessary for any reason?

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.
Otherwise, I would probably like to propose to open a PR to expose it again ~

@creativecreatorormaybenot
Copy link
Contributor

For Flutter a change is only breaking if it breaks one of the contributed tests (see also https://github.com/flutter/flutter/wiki/Tree-hygiene#1-determine-if-your-change-is-a-breaking-change). Since this change didn't require any changes to those tests it isn't considered breaking and no migration guide has been published. Please read the link above to learn how you can contribute tests to avoid being broken in the future.

@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?

@goderbauer
Copy link
Member Author

The leader property has been exposed again as you can see in

LeaderLayer? get leader => _leader;
.

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.

@Hixie
Copy link
Contributor

Hixie commented Feb 3, 2022

@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.

@creativecreatorormaybenot
Copy link
Contributor

@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 flutter_portal package in particular does not have sufficient test coverage I would say, but if I have a package that qualifies, I will certainly contribute!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
a: text input Entering text in a text field or keyboard related problems f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants