Commit 243960d7 authored by krisgiesing's avatar krisgiesing

Merge pull request #3010 from krisgiesing/offscreen_layout

Part 2 of independent layout pipelines
parents 4ff2a338 9dfd5d40
......@@ -33,6 +33,7 @@ class Rectangle extends StatelessWidget {
double value;
RenderObjectToWidgetElement<RenderBox> element;
BuildOwner owner;
void attachWidgetTreeToRenderTree(RenderProxyBox container) {
element = new RenderObjectToWidgetAdapter<RenderBox>(
container: container,
......@@ -70,7 +71,7 @@ void attachWidgetTreeToRenderTree(RenderProxyBox container) {
mainAxisAlignment: MainAxisAlignment.spaceBetween
)
)
).attachToRenderTree(element);
).attachToRenderTree(owner, element);
}
Duration timeBase;
......
......@@ -34,7 +34,7 @@ void main() {
for (int i = 0; i < _kNumberOfIterations || _kRunForever; ++i) {
appState.setState(_doNothing);
binding.buildDirtyElements();
binding.buildOwner.buildDirtyElements();
}
watch.stop();
......
......@@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:developer';
import 'dart:ui' as ui show window;
import 'dart:ui' show AppLifecycleState, Locale;
......@@ -26,6 +25,10 @@ class BindingObserver {
/// This is the glue that binds the framework to the Flutter engine.
class WidgetFlutterBinding extends BindingBase with Scheduler, Gesturer, Services, Renderer {
WidgetFlutterBinding() {
buildOwner.onBuildScheduled = ensureVisualUpdate;
}
/// Creates and initializes the WidgetFlutterBinding. This constructor is
/// idempotent; calling it a second time will just return the
/// previously-created instance.
......@@ -35,11 +38,15 @@ class WidgetFlutterBinding extends BindingBase with Scheduler, Gesturer, Service
return _instance;
}
final BuildOwner _buildOwner = new BuildOwner();
/// The [BuildOwner] in charge of executing the build pipeline for the
/// widget tree rooted at this binding.
BuildOwner get buildOwner => _buildOwner;
@override
void initInstances() {
super.initInstances();
_instance = this;
BuildableElement.scheduleBuildFor = scheduleBuildFor;
ui.window.onLocaleChanged = handleLocaleChanged;
ui.window.onPopRoute = handlePopRoute;
ui.window.onAppLifecycleStateChanged = handleAppLifecycleStateChanged;
......@@ -92,60 +99,9 @@ class WidgetFlutterBinding extends BindingBase with Scheduler, Gesturer, Service
@override
void beginFrame() {
buildDirtyElements();
buildOwner.buildDirtyElements();
super.beginFrame();
Element.finalizeTree();
}
List<BuildableElement> _dirtyElements = <BuildableElement>[];
/// Adds an element to the dirty elements list so that it will be rebuilt
/// when buildDirtyElements is called.
void scheduleBuildFor(BuildableElement element) {
assert(!_dirtyElements.contains(element));
assert(element.dirty);
if (_dirtyElements.isEmpty)
ensureVisualUpdate();
_dirtyElements.add(element);
}
static int _elementSort(BuildableElement a, BuildableElement b) {
if (a.depth < b.depth)
return -1;
if (b.depth < a.depth)
return 1;
if (b.dirty && !a.dirty)
return -1;
if (a.dirty && !b.dirty)
return 1;
return 0;
}
/// Builds all the elements that were marked as dirty using schedule(), in depth order.
/// If elements are marked as dirty while this runs, they must be deeper than the algorithm
/// has yet reached.
/// This is called by beginFrame().
void buildDirtyElements() {
if (_dirtyElements.isEmpty)
return;
Timeline.startSync('Build');
BuildableElement.lockState(() {
_dirtyElements.sort(_elementSort);
int dirtyCount = _dirtyElements.length;
int index = 0;
while (index < dirtyCount) {
_dirtyElements[index].rebuild();
index += 1;
if (dirtyCount < _dirtyElements.length) {
_dirtyElements.sort(_elementSort);
dirtyCount = _dirtyElements.length;
}
}
assert(!_dirtyElements.any((BuildableElement element) => element.dirty));
_dirtyElements.clear();
}, building: true);
assert(_dirtyElements.isEmpty);
Timeline.finishSync();
buildOwner.finalizeTree();
}
/// The [Element] that is at the root of the hierarchy (and which wraps the
......@@ -157,7 +113,7 @@ class WidgetFlutterBinding extends BindingBase with Scheduler, Gesturer, Service
container: renderView,
debugShortDescription: '[root]',
child: app
).attachToRenderTree(_renderViewElement);
).attachToRenderTree(buildOwner, _renderViewElement);
beginFrame();
}
}
......@@ -205,10 +161,11 @@ class RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWi
@override
void updateRenderObject(BuildContext context, RenderObject renderObject) { }
RenderObjectToWidgetElement<T> attachToRenderTree([RenderObjectToWidgetElement<T> element]) {
BuildableElement.lockState(() {
RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [RenderObjectToWidgetElement<T> element]) {
owner.lockState(() {
if (element == null) {
element = createElement();
element.assignOwner(owner);
element.mount(null, null);
} else {
element.update(this);
......@@ -229,7 +186,7 @@ class RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWi
/// whose container is the RenderView that connects to the Flutter engine. In
/// this usage, it is normally instantiated by the bootstrapping logic in the
/// WidgetFlutterBinding singleton created by runApp().
class RenderObjectToWidgetElement<T extends RenderObject> extends RenderObjectElement {
class RenderObjectToWidgetElement<T extends RenderObject> extends RootRenderObjectElement {
RenderObjectToWidgetElement(RenderObjectToWidgetAdapter<T> widget) : super(widget);
@override
......
......@@ -252,7 +252,7 @@ class _MixedViewportElement extends RenderObjectElement {
_resetCache();
_lastLayoutConstraints = constraints;
}
BuildableElement.lockState(() {
owner.lockState(() {
_doLayout(constraints);
}, building: true);
}
......
......@@ -157,7 +157,7 @@ abstract class VirtualViewportElement extends RenderObjectElement {
assert(startOffsetBase != null);
assert(startOffsetLimit != null);
_updatePaintOffset();
BuildableElement.lockState(_materializeChildren, building: true);
owner.lockState(_materializeChildren, building: true);
}
void _materializeChildren() {
......
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:test/test.dart';
const Size _kTestViewSize = const Size(800.0, 600.0);
class OffscreenRenderView extends RenderView {
OffscreenRenderView() {
configuration = new ViewConfiguration(size: _kTestViewSize);
}
@override
void scheduleInitialFrame() {
scheduleInitialLayout();
scheduleInitialPaint(new TransformLayer(transform: new Matrix4.identity()));
// Don't call Scheduler.instance.ensureVisualUpdate()
}
@override
void compositeFrame() {
// Don't draw to ui.window
}
}
class OffscreenWidgetTree {
OffscreenWidgetTree() {
renderView.attach(pipelineOwner);
renderView.scheduleInitialFrame();
}
final RenderView renderView = new OffscreenRenderView();
final BuildOwner buildOwner = new BuildOwner();
final PipelineOwner pipelineOwner = new PipelineOwner();
RenderObjectToWidgetElement<RenderBox> root;
void pumpWidget(Widget app) {
root = new RenderObjectToWidgetAdapter<RenderBox>(
container: renderView,
debugShortDescription: '[root]',
child: app
).attachToRenderTree(buildOwner, root);
pumpFrame();
}
void pumpFrame() {
buildOwner.buildDirtyElements();
pipelineOwner.flushLayout();
pipelineOwner.flushCompositingBits();
pipelineOwner.flushPaint();
renderView.compositeFrame();
if (SemanticsNode.hasListeners) {
pipelineOwner.flushSemantics();
SemanticsNode.sendSemanticsTree();
}
buildOwner.finalizeTree();
}
}
class Counter {
int count = 0;
}
class Trigger {
VoidCallback callback;
void fire() {
if (callback != null)
callback();
}
}
class TriggerableWidget extends StatefulWidget {
TriggerableWidget({ this.trigger, this.counter });
final Trigger trigger;
final Counter counter;
@override
TriggerableState createState() => new TriggerableState();
}
class TriggerableState extends State<TriggerableWidget> {
@override
void initState() {
super.initState();
config.trigger.callback = this.fire;
}
@override
void didUpdateConfig(TriggerableWidget oldConfig) {
config.trigger.callback = this.fire;
}
int _count = 0;
void fire() {
setState(() {
_count++;
});
}
@override
Widget build(BuildContext context) {
config.counter.count++;
return new Text("Bang $_count!");
}
}
void main() {
test('no crosstalk between widget build owners', () {
testWidgets((WidgetTester tester) {
Trigger trigger1 = new Trigger();
Counter counter1 = new Counter();
Trigger trigger2 = new Trigger();
Counter counter2 = new Counter();
OffscreenWidgetTree tree = new OffscreenWidgetTree();
// Both counts should start at zero
expect(counter1.count, equals(0));
expect(counter2.count, equals(0));
// Lay out the "onscreen" in the default test binding
tester.pumpWidget(new TriggerableWidget(trigger: trigger1, counter: counter1));
// Only the "onscreen" widget should have built
expect(counter1.count, equals(1));
expect(counter2.count, equals(0));
// Lay out the "offscreen" in a separate tree
tree.pumpWidget(new TriggerableWidget(trigger: trigger2, counter: counter2));
// Now both widgets should have built
expect(counter1.count, equals(1));
expect(counter2.count, equals(1));
// Mark both as needing layout
trigger1.fire();
trigger2.fire();
// Marking as needing layout shouldn't immediately build anything
expect(counter1.count, equals(1));
expect(counter2.count, equals(1));
// Pump the "onscreen" layout
tester.pump();
// Only the "onscreen" widget should have rebuilt
expect(counter1.count, equals(2));
expect(counter2.count, equals(1));
// Pump the "offscreen" layout
tree.pumpFrame();
// Now both widgets should have rebuilt
expect(counter1.count, equals(2));
expect(counter2.count, equals(2));
// Mark both as needing layout, again
trigger1.fire();
trigger2.fire();
// Now pump the "offscreen" layout first
tree.pumpFrame();
// Only the "offscreen" widget should have rebuilt
expect(counter1.count, equals(2));
expect(counter2.count, equals(3));
// Pump the "onscreen" layout
tester.pump();
// Now both widgets should have rebuilt
expect(counter1.count, equals(3));
expect(counter2.count, equals(3));
});
});
}
......@@ -38,9 +38,9 @@ class _SteppedWidgetFlutterBinding extends WidgetFlutterBinding {
// Pump the rendering pipeline up to the given phase.
@override
void beginFrame() {
buildDirtyElements();
buildOwner.buildDirtyElements();
_beginFrame();
Element.finalizeTree();
buildOwner.finalizeTree();
}
// Cloned from Renderer.beginFrame() but with early-exit semantics.
......@@ -84,7 +84,7 @@ class WidgetTester extends Instrumentation {
final FakeAsync async;
final Clock clock;
/// Calls [runApp()] with the given widget, then triggers a frame sequent and
/// Calls [runApp()] with the given widget, then triggers a frame sequence and
/// flushes microtasks, by calling [pump()] with the same duration (if any).
/// The supplied EnginePhase is the final phase reached during the pump pass;
/// if not supplied, the whole pass is executed.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment