Commit 4ebf26bf authored by Adam Barth's avatar Adam Barth

Merge pull request #1441 from abarth/delay_finalization

Delay unmounting elements until the end of the frame
parents 94f910d8 0b542d5c
...@@ -40,7 +40,7 @@ class WidgetFlutterBinding extends FlutterBinding { ...@@ -40,7 +40,7 @@ class WidgetFlutterBinding extends FlutterBinding {
void beginFrame(double timeStamp) { void beginFrame(double timeStamp) {
buildDirtyElements(); buildDirtyElements();
super.beginFrame(timeStamp); super.beginFrame(timeStamp);
scheduleMicrotask(GlobalKey.checkForDuplicatesAndNotifyListeners); Element.finalizeTree();
} }
List<BuildableElement> _dirtyElements = new List<BuildableElement>(); List<BuildableElement> _dirtyElements = new List<BuildableElement>();
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async';
import 'dart:collection'; import 'dart:collection';
import 'package:sky/rendering.dart'; import 'package:sky/rendering.dart';
...@@ -118,8 +119,7 @@ abstract class GlobalKey extends Key { ...@@ -118,8 +119,7 @@ abstract class GlobalKey extends Key {
assert(removed); assert(removed);
} }
static void checkForDuplicatesAndNotifyListeners() { static bool _debugCheckForDuplicates() {
assert(() {
String message = ''; String message = '';
for (GlobalKey key in _debugDuplicates.keys) { for (GlobalKey key in _debugDuplicates.keys) {
message += 'Duplicate GlobalKey found amongst mounted elements: $key (${_debugDuplicates[key]} instances)\n'; message += 'Duplicate GlobalKey found amongst mounted elements: $key (${_debugDuplicates[key]} instances)\n';
...@@ -128,7 +128,9 @@ abstract class GlobalKey extends Key { ...@@ -128,7 +128,9 @@ abstract class GlobalKey extends Key {
if (!_debugDuplicates.isEmpty) if (!_debugDuplicates.isEmpty)
throw message; throw message;
return true; return true;
}); }
static void _notifyListeners() {
if (_removedKeys.isEmpty) if (_removedKeys.isEmpty)
return; return;
try { try {
...@@ -409,10 +411,55 @@ bool _canUpdate(Widget oldWidget, Widget newWidget) { ...@@ -409,10 +411,55 @@ bool _canUpdate(Widget oldWidget, Widget newWidget) {
enum _ElementLifecycle { enum _ElementLifecycle {
initial, initial,
mounted, active,
inactive,
defunct, defunct,
} }
class _FreeElements {
bool _locked = false;
final Set<Element> _elements = new Set<Element>();
void _finalize(Element element) {
assert(element._debugLifecycleState == _ElementLifecycle.inactive);
element.unmount();
assert(element._debugLifecycleState == _ElementLifecycle.defunct);
element.visitChildren((Element child) {
assert(child._parent == element);
_finalize(child);
});
}
void finalizeAll() {
BuildableElement.lockState(() {
try {
_locked = true;
for (Element element in _elements)
_finalize(element);
} finally {
_elements.clear();
_locked = false;
}
});
}
void add(Element element) {
assert(!_locked);
assert(!_elements.contains(element));
assert(element._parent == null);
void deactivate(Element element) {
assert(element._debugLifecycleState == _ElementLifecycle.active);
element.deactivate();
assert(element._debugLifecycleState == _ElementLifecycle.inactive);
element.visitChildren(deactivate);
}
deactivate(element);
_elements.add(element);
}
}
final _FreeElements _freeElements = new _FreeElements();
typedef void ElementVisitor(Element element); typedef void ElementVisitor(Element element);
abstract class BuildContext { abstract class BuildContext {
...@@ -468,15 +515,6 @@ abstract class Element<T extends Widget> implements BuildContext { ...@@ -468,15 +515,6 @@ abstract class Element<T extends Widget> implements BuildContext {
/// Calls the argument for each child. Must be overridden by subclasses that support having children. /// Calls the argument for each child. Must be overridden by subclasses that support having children.
void visitChildren(ElementVisitor visitor) { } void visitChildren(ElementVisitor visitor) { }
/// Calls the argument for each descendant, depth-first pre-order.
void visitDescendants(ElementVisitor visitor) {
void visit(Element element) {
visitor(element);
element.visitChildren(visit);
}
visitChildren(visit);
}
/// This method is the core of the system. /// This method is the core of the system.
/// ///
/// It is called each time we are to add, update, or remove a child based on /// It is called each time we are to add, update, or remove a child based on
...@@ -523,10 +561,16 @@ abstract class Element<T extends Widget> implements BuildContext { ...@@ -523,10 +561,16 @@ abstract class Element<T extends Widget> implements BuildContext {
} }
child = newWidget.createElement(); child = newWidget.createElement();
child.mount(this, newSlot); child.mount(this, newSlot);
assert(child._debugLifecycleState == _ElementLifecycle.mounted); assert(child._debugLifecycleState == _ElementLifecycle.active);
return child; return child;
} }
static void finalizeTree() {
_freeElements.finalizeAll();
assert(GlobalKey._debugCheckForDuplicates);
scheduleMicrotask(GlobalKey._notifyListeners);
}
/// Called when an Element is given a new parent shortly after having been /// Called when an Element is given a new parent shortly after having been
/// created. Use this to initialize state that depends on having a parent. For /// created. Use this to initialize state that depends on having a parent. For
/// state that is independent of the position in the tree, it's better to just /// state that is independent of the position in the tree, it's better to just
...@@ -535,7 +579,7 @@ abstract class Element<T extends Widget> implements BuildContext { ...@@ -535,7 +579,7 @@ abstract class Element<T extends Widget> implements BuildContext {
assert(_debugLifecycleState == _ElementLifecycle.initial); assert(_debugLifecycleState == _ElementLifecycle.initial);
assert(widget != null); assert(widget != null);
assert(_parent == null); assert(_parent == null);
assert(parent == null || parent._debugLifecycleState == _ElementLifecycle.mounted); assert(parent == null || parent._debugLifecycleState == _ElementLifecycle.active);
assert(slot == null); assert(slot == null);
assert(depth == null); assert(depth == null);
_parent = parent; _parent = parent;
...@@ -545,12 +589,12 @@ abstract class Element<T extends Widget> implements BuildContext { ...@@ -545,12 +589,12 @@ abstract class Element<T extends Widget> implements BuildContext {
final GlobalKey key = widget.key; final GlobalKey key = widget.key;
key._register(this); key._register(this);
} }
assert(() { _debugLifecycleState = _ElementLifecycle.mounted; return true; }); assert(() { _debugLifecycleState = _ElementLifecycle.active; return true; });
} }
/// Called when an Element receives a new configuration widget. /// Called when an Element receives a new configuration widget.
void update(T newWidget) { void update(T newWidget) {
assert(_debugLifecycleState == _ElementLifecycle.mounted); assert(_debugLifecycleState == _ElementLifecycle.active);
assert(widget != null); assert(widget != null);
assert(newWidget != null); assert(newWidget != null);
assert(depth != null); assert(depth != null);
...@@ -562,7 +606,7 @@ abstract class Element<T extends Widget> implements BuildContext { ...@@ -562,7 +606,7 @@ abstract class Element<T extends Widget> implements BuildContext {
/// subclasses that have multiple children, to update the slot of a particular /// subclasses that have multiple children, to update the slot of a particular
/// child when the child is moved in its child list. /// child when the child is moved in its child list.
void updateSlotForChild(Element child, dynamic newSlot) { void updateSlotForChild(Element child, dynamic newSlot) {
assert(_debugLifecycleState == _ElementLifecycle.mounted); assert(_debugLifecycleState == _ElementLifecycle.active);
assert(child != null); assert(child != null);
assert(child._parent == this); assert(child._parent == this);
void visit(Element element) { void visit(Element element) {
...@@ -574,40 +618,40 @@ abstract class Element<T extends Widget> implements BuildContext { ...@@ -574,40 +618,40 @@ abstract class Element<T extends Widget> implements BuildContext {
} }
void _updateSlot(dynamic newSlot) { void _updateSlot(dynamic newSlot) {
assert(_debugLifecycleState == _ElementLifecycle.mounted); assert(_debugLifecycleState == _ElementLifecycle.active);
assert(widget != null); assert(widget != null);
assert(_parent != null); assert(_parent != null);
assert(_parent._debugLifecycleState == _ElementLifecycle.mounted); assert(_parent._debugLifecycleState == _ElementLifecycle.active);
assert(depth != null); assert(depth != null);
_slot = newSlot; _slot = newSlot;
} }
void detachRenderObject() {
visitChildren((Element child) {
child.detachRenderObject();
});
_slot = null;
}
void _detachChild(Element child) { void _detachChild(Element child) {
assert(child != null); assert(child != null);
assert(child._parent == this); assert(child._parent == this);
child._parent = null; child._parent = null;
child.detachRenderObject();
bool haveDetachedRenderObject = false; _freeElements.add(child);
void detach(Element descendant) {
if (!haveDetachedRenderObject) {
descendant._slot = null;
if (descendant is RenderObjectElement) {
descendant.detachRenderObject();
haveDetachedRenderObject = true;
}
}
descendant.unmount();
assert(descendant._debugLifecycleState == _ElementLifecycle.defunct);
} }
detach(child); void deactivate() {
child.visitDescendants(detach); assert(_debugLifecycleState == _ElementLifecycle.active);
assert(widget != null);
assert(depth != null);
assert(() { _debugLifecycleState = _ElementLifecycle.inactive; return true; });
} }
/// Called when an Element is removed from the tree. /// Called when an Element is removed from the tree.
/// Currently, an Element removed from the tree never returns. /// Currently, an Element removed from the tree never returns.
void unmount() { void unmount() {
assert(_debugLifecycleState == _ElementLifecycle.mounted); assert(_debugLifecycleState == _ElementLifecycle.inactive);
assert(widget != null); assert(widget != null);
assert(depth != null); assert(depth != null);
if (widget.key is GlobalKey) { if (widget.key is GlobalKey) {
...@@ -689,6 +733,8 @@ abstract class BuildableElement<T extends Widget> extends Element<T> { ...@@ -689,6 +733,8 @@ abstract class BuildableElement<T extends Widget> extends Element<T> {
bool get dirty => _dirty; bool get dirty => _dirty;
bool _dirty = true; bool _dirty = true;
bool _active = false;
// We to let component authors call setState from initState, didUpdateConfig, // We to let component authors call setState from initState, didUpdateConfig,
// and build even when state is locked because its convenient and a no-op // and build even when state is locked because its convenient and a no-op
// anyway. This flag ensures that this convenience is only allowed on the // anyway. This flag ensures that this convenience is only allowed on the
...@@ -703,6 +749,8 @@ abstract class BuildableElement<T extends Widget> extends Element<T> { ...@@ -703,6 +749,8 @@ abstract class BuildableElement<T extends Widget> extends Element<T> {
void mount(Element parent, dynamic newSlot) { void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot); super.mount(parent, newSlot);
assert(_child == null); assert(_child == null);
assert(!_active);
_active = true;
rebuild(); rebuild();
assert(_child != null); assert(_child != null);
} }
...@@ -716,9 +764,11 @@ abstract class BuildableElement<T extends Widget> extends Element<T> { ...@@ -716,9 +764,11 @@ abstract class BuildableElement<T extends Widget> extends Element<T> {
/// and by update() when the Widget has changed. /// and by update() when the Widget has changed.
void rebuild() { void rebuild() {
assert(_debugLifecycleState != _ElementLifecycle.initial); assert(_debugLifecycleState != _ElementLifecycle.initial);
if (!dirty) if (!_active || !_dirty) {
_dirty = false;
return; return;
assert(_debugLifecycleState == _ElementLifecycle.mounted); }
assert(_debugLifecycleState == _ElementLifecycle.active);
assert(_debugStateLocked); assert(_debugStateLocked);
assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(true)); assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(true));
Widget built; Widget built;
...@@ -778,10 +828,12 @@ abstract class BuildableElement<T extends Widget> extends Element<T> { ...@@ -778,10 +828,12 @@ abstract class BuildableElement<T extends Widget> extends Element<T> {
/// the build itself. /// the build itself.
void markNeedsBuild() { void markNeedsBuild() {
assert(_debugLifecycleState != _ElementLifecycle.defunct); assert(_debugLifecycleState != _ElementLifecycle.defunct);
if (!_active)
return;
assert(_debugLifecycleState == _ElementLifecycle.active);
assert(!_debugStateLocked || (_debugAllowIgnoredCallsToMarkNeedsBuild && dirty)); assert(!_debugStateLocked || (_debugAllowIgnoredCallsToMarkNeedsBuild && dirty));
if (dirty) if (dirty)
return; return;
assert(_debugLifecycleState == _ElementLifecycle.mounted);
assert(!_debugStateLocked); assert(!_debugStateLocked);
_dirty = true; _dirty = true;
assert(scheduleBuildFor != null); assert(scheduleBuildFor != null);
...@@ -793,9 +845,10 @@ abstract class BuildableElement<T extends Widget> extends Element<T> { ...@@ -793,9 +845,10 @@ abstract class BuildableElement<T extends Widget> extends Element<T> {
visitor(_child); visitor(_child);
} }
void unmount() { void deactivate() {
super.unmount(); super.deactivate();
_dirty = false; // so that we don't get rebuilt even if we're already marked dirty assert(_active == true);
_active = false;
} }
void dependenciesChanged() { void dependenciesChanged() {
...@@ -1051,7 +1104,7 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Element ...@@ -1051,7 +1104,7 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Element
while ((childrenTop <= oldChildrenBottom) && (childrenTop <= newChildrenBottom)) { while ((childrenTop <= oldChildrenBottom) && (childrenTop <= newChildrenBottom)) {
Element oldChild = oldChildren[childrenTop]; Element oldChild = oldChildren[childrenTop];
Widget newWidget = newWidgets[childrenTop]; Widget newWidget = newWidgets[childrenTop];
assert(oldChild._debugLifecycleState == _ElementLifecycle.mounted); assert(oldChild._debugLifecycleState == _ElementLifecycle.active);
if (!_canUpdate(oldChild.widget, newWidget)) if (!_canUpdate(oldChild.widget, newWidget))
break; break;
childrenTop += 1; childrenTop += 1;
...@@ -1066,11 +1119,11 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Element ...@@ -1066,11 +1119,11 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Element
while ((childrenTop <= oldChildrenBottom) && (childrenTop <= newChildrenBottom)) { while ((childrenTop <= oldChildrenBottom) && (childrenTop <= newChildrenBottom)) {
Element oldChild = oldChildren[oldChildrenBottom]; Element oldChild = oldChildren[oldChildrenBottom];
Widget newWidget = newWidgets[newChildrenBottom]; Widget newWidget = newWidgets[newChildrenBottom];
assert(oldChild._debugLifecycleState == _ElementLifecycle.mounted); assert(oldChild._debugLifecycleState == _ElementLifecycle.active);
if (!_canUpdate(oldChild.widget, newWidget)) if (!_canUpdate(oldChild.widget, newWidget))
break; break;
Element newChild = updateChild(oldChild, newWidget, nextSibling); Element newChild = updateChild(oldChild, newWidget, nextSibling);
assert(newChild._debugLifecycleState == _ElementLifecycle.mounted); assert(newChild._debugLifecycleState == _ElementLifecycle.active);
newChildren[newChildrenBottom] = newChild; newChildren[newChildrenBottom] = newChild;
nextSibling = newChild; nextSibling = newChild;
oldChildrenBottom -= 1; oldChildrenBottom -= 1;
...@@ -1084,7 +1137,7 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Element ...@@ -1084,7 +1137,7 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Element
oldKeyedChildren = new Map<Key, Element>(); oldKeyedChildren = new Map<Key, Element>();
while (childrenTop <= oldChildrenBottom) { while (childrenTop <= oldChildrenBottom) {
Element oldChild = oldChildren[oldChildrenBottom]; Element oldChild = oldChildren[oldChildrenBottom];
assert(oldChild._debugLifecycleState == _ElementLifecycle.mounted); assert(oldChild._debugLifecycleState == _ElementLifecycle.active);
if (oldChild.widget.key != null) if (oldChild.widget.key != null)
oldKeyedChildren[oldChild.widget.key] = oldChild; oldKeyedChildren[oldChild.widget.key] = oldChild;
else else
...@@ -1115,8 +1168,8 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Element ...@@ -1115,8 +1168,8 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Element
} }
assert(oldChild == null || _canUpdate(oldChild.widget, newWidget)); assert(oldChild == null || _canUpdate(oldChild.widget, newWidget));
Element newChild = updateChild(oldChild, newWidget, nextSibling); Element newChild = updateChild(oldChild, newWidget, nextSibling);
assert(newChild._debugLifecycleState == _ElementLifecycle.mounted); assert(newChild._debugLifecycleState == _ElementLifecycle.active);
assert(oldChild == newChild || oldChild == null || oldChild._debugLifecycleState != _ElementLifecycle.mounted); assert(oldChild == newChild || oldChild == null || oldChild._debugLifecycleState != _ElementLifecycle.active);
newChildren[newChildrenBottom] = newChild; newChildren[newChildrenBottom] = newChild;
nextSibling = newChild; nextSibling = newChild;
newChildrenBottom -= 1; newChildrenBottom -= 1;
...@@ -1128,12 +1181,12 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Element ...@@ -1128,12 +1181,12 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Element
while (childrenTop > 0) { while (childrenTop > 0) {
childrenTop -= 1; childrenTop -= 1;
Element oldChild = oldChildren[childrenTop]; Element oldChild = oldChildren[childrenTop];
assert(oldChild._debugLifecycleState == _ElementLifecycle.mounted); assert(oldChild._debugLifecycleState == _ElementLifecycle.active);
Widget newWidget = newWidgets[childrenTop]; Widget newWidget = newWidgets[childrenTop];
assert(_canUpdate(oldChild.widget, newWidget)); assert(_canUpdate(oldChild.widget, newWidget));
Element newChild = updateChild(oldChild, newWidget, nextSibling); Element newChild = updateChild(oldChild, newWidget, nextSibling);
assert(newChild._debugLifecycleState == _ElementLifecycle.mounted); assert(newChild._debugLifecycleState == _ElementLifecycle.active);
assert(oldChild == newChild || oldChild == null || oldChild._debugLifecycleState != _ElementLifecycle.mounted); assert(oldChild == newChild || oldChild == null || oldChild._debugLifecycleState != _ElementLifecycle.active);
newChildren[childrenTop] = newChild; newChildren[childrenTop] = newChild;
nextSibling = newChild; nextSibling = newChild;
} }
...@@ -1148,6 +1201,11 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Element ...@@ -1148,6 +1201,11 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Element
return newChildren; return newChildren;
} }
void deactivate() {
super.deactivate();
assert(!renderObject.attached);
}
void unmount() { void unmount() {
super.unmount(); super.unmount();
assert(!renderObject.attached); assert(!renderObject.attached);
...@@ -1170,6 +1228,7 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Element ...@@ -1170,6 +1228,7 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Element
_ancestorRenderObjectElement.removeChildRenderObject(renderObject); _ancestorRenderObjectElement.removeChildRenderObject(renderObject);
_ancestorRenderObjectElement = null; _ancestorRenderObjectElement = null;
} }
_slot = null;
} }
void insertChildRenderObject(RenderObject child, dynamic slot); void insertChildRenderObject(RenderObject child, dynamic slot);
...@@ -1301,7 +1360,7 @@ class MultiChildRenderObjectElement<T extends MultiChildRenderObjectWidget> exte ...@@ -1301,7 +1360,7 @@ class MultiChildRenderObjectElement<T extends MultiChildRenderObjectWidget> exte
for (int i = _children.length - 1; i >= 0; --i) { for (int i = _children.length - 1; i >= 0; --i) {
Element newChild = widget.children[i].createElement(); Element newChild = widget.children[i].createElement();
newChild.mount(this, previousChild); newChild.mount(this, previousChild);
assert(newChild._debugLifecycleState == _ElementLifecycle.mounted); assert(newChild._debugLifecycleState == _ElementLifecycle.active);
_children[i] = newChild; _children[i] = newChild;
previousChild = newChild; previousChild = newChild;
} }
......
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