Skip to content
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

Adds API for retaining intermediate engine layers #9461

Merged
merged 10 commits into from
Jun 28, 2019

Conversation

yjbanov
Copy link
Contributor

@yjbanov yjbanov commented Jun 24, 2019

Add new optional named oldLayer arguments to all push* methods of the SceneBuilder class.

The Web changes are temporary. We will get those from package:flutter_web_ui separately.

When not null oldLayer signals to the engine that the intent is to update a layer rendered in a previous frame. The engine may optionally use that signal to reuse the resources allocated for that layer in the previous frame. For example, on the Web we can reuse existing DOM nodes and some of their properties and move fewer nodes around the tree.

The return type of each push method has been tightened up. Instead of having all methods return the same EngineLayer type, each method has its own unique layer type, e.g. OffsetEngineLayer. oldLayer parameters match the returned type. This prevents the framework (and other developers using dart:ui directly) from accidentally supplying an engine layer of the wrong type.

Here are some pointers to Flutter for web where oldLayer is used to reuse DOM nodes:

Breaking changes:

  • Mock implementations of SceneBuilder must add the new oldLayer parameter and tighten the return types of their push method.

@yjbanov yjbanov requested review from Hixie and liyuqian June 24, 2019 20:10
@yjbanov
Copy link
Contributor Author

yjbanov commented Jun 24, 2019

/cc @jonahwilliams

Copy link
Contributor

@liyuqian liyuqian left a comment

Choose a reason for hiding this comment

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

Can you please also put a link to the corresponding change in package:flutter_web_ui that actually uses the oldLayer?

}
builder2.build();

// Test: first addRetained then attempt illegal push
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe also test two pushes with the same oldLayer?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good idea! Done. Also added two addRetaineds.

//
// The key is the layer used. The value is the description of what the layer
// is used for, e.g. "pushOpacity" or "addRetained".
Map<EngineLayer, String> _usedLayers = <EngineLayer, String>{};
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we also catch the case where a layer is marked an oldLayer, and thus freed, but then it's reused in another scene builder?

Copy link
Contributor Author

@yjbanov yjbanov Jun 26, 2019

Choose a reason for hiding this comment

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

That's actually a supported case in Flutter for web. The old layer becomes "released" and when you use it again the engine code will "revive" it. Other than the DOM nodes the information stored on the layers is immutable and so we can always re-inflate a DOM tree from discarded layers.

The usage you describe was not why we implemented it though. We implemented it because you can push a new layer in builder1, then not use that layer in builder2 (thus another layer could "adopt" its DOM nodes), and call addRetained or pass it as oldLayer in builder3. We even have a test for this.

Copy link
Contributor

Choose a reason for hiding this comment

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

The test/use case you referred seems reasonable. But is it legal to do the following?

  1. Create a layer1 in frame/builder 1,
  2. Call pushLayer(..., oldLayer: layer1) in frame/builder 2,
  3. Call addRetained(layer1) in frame/builder 3

That sounds a little counter-intuitive and some engine implementation may view that as illegal. If we want to make this kind of usages legal, maybe it's worth to document it, and put a unit test here to ensure that all engine implementations can support it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Intuitively you can think of it as either of the following two ways, depending on whether the layers are immutable or mutable:

  1. Immutable: an EngineLayer is simply a description of what should appear in a given subtree of a scene. It should be OK to go back and forth between two descriptions, all while updating existing underlying resources.
  2. Mutable: pushLayer may actually return the same object as oldLayer. Since the object is the same, you'd have to pass it as oldLayer multiple times to communicate an "update".

Copy link
Contributor Author

Choose a reason for hiding this comment

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

However, I don't mind putting a restriction in place. I don't think we are using layers in a way where this matters today. LMK which way you prefer.

Copy link
Contributor

Choose a reason for hiding this comment

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

Let's restrict it now as I think it's easier to add capabilities than to remove capabilities that are being used :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

SGTM. Done.

@yjbanov
Copy link
Contributor Author

yjbanov commented Jun 26, 2019

@liyuqian

Can you please also put a link to the corresponding change in package:flutter_web_ui that actually uses the oldLayer?

Done (added links to the PR description)

@yjbanov
Copy link
Contributor Author

yjbanov commented Jun 26, 2019

PTAL.

@yjbanov yjbanov requested a review from liyuqian June 26, 2019 17:58
'A layer may only be used once in a given scene.'
);

_usedLayers[layer] = usage;
Copy link
Contributor

Choose a reason for hiding this comment

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

this whole method should be in an assert, to make sure we really don't use it in release

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

void addRetained(EngineLayer retainedLayer) native 'SceneBuilder_addRetained';
void addRetained(EngineLayer retainedLayer) {
assert(retainedLayer is _EngineLayerWrapper);
assert(_debugCheckUsedOnce(retainedLayer, 'addRetained'));
Copy link
Contributor

Choose a reason for hiding this comment

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

this also needs to add all the descendants of the retained layer

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, you're right. Done.

Copy link
Contributor

@liyuqian liyuqian left a comment

Choose a reason for hiding this comment

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

Newly added tests look good! Left additional comments on the addRetained after oldLayer recycle case.

@yjbanov
Copy link
Contributor Author

yjbanov commented Jun 27, 2019

I added more tests and the "oldLayers can't be reused" restriction that @liyuqian asked for. PTAL.

Copy link
Contributor

@liyuqian liyuqian left a comment

Choose a reason for hiding this comment

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

LGTM for the non-API part. Thanks for the comprehensive tests!

@@ -249,6 +249,23 @@ class SceneBuilder extends NativeFieldWrapperClass2 {
/// an optimization. It has no effect on the correctness of rendering.
/// {@endtemplate}
///
/// {@template dart.ui.sceneBuilder.oldLayerVsRetained}
/// Passing a layer to [addRetained] or as `oldLayer` argument to a push
/// method counts as _usage_. A layer be used no more than once in a scene.
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: "A layer be" -> "A layer can be"?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

///
/// When a layer is passed as an `oldLayer` argument to a push method, it may
/// no longer be used in subsequent frames. If you would like to continue
/// reusing the resources associated layer, store the layer object returned
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: "associated layer" -> "associated with the layer"?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 30, 2019
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 30, 2019
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 30, 2019
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 30, 2019
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 30, 2019
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 30, 2019
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 30, 2019
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 30, 2019
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 30, 2019
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 30, 2019
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 30, 2019
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 30, 2019
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 30, 2019
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 30, 2019
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 30, 2019
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 30, 2019
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jul 1, 2019
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jul 1, 2019
@yjbanov yjbanov deleted the intermediate-layers branch June 22, 2021 21:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants