Commit 27a73a58 authored by Adam Barth's avatar Adam Barth

Merge pull request #1400 from abarth/reorder_update_children

updateChildren() needs to walk the list forward
parents 5cfccb91 05839e51
...@@ -1294,59 +1294,62 @@ abstract class ContainerRenderObjectMixin<ChildType extends RenderObject, Parent ...@@ -1294,59 +1294,62 @@ abstract class ContainerRenderObjectMixin<ChildType extends RenderObject, Parent
ChildType _firstChild; ChildType _firstChild;
ChildType _lastChild; ChildType _lastChild;
void _addToChildList(ChildType child, { ChildType before }) { void _insertIntoChildList(ChildType child, { ChildType after }) {
final ParentDataType childParentData = child.parentData; final ParentDataType childParentData = child.parentData;
assert(childParentData.nextSibling == null); assert(childParentData.nextSibling == null);
assert(childParentData.previousSibling == null); assert(childParentData.previousSibling == null);
_childCount += 1; _childCount += 1;
assert(_childCount > 0); assert(_childCount > 0);
if (before == null) { if (after == null) {
// append at the end (_lastChild) // insert at the start (_firstChild)
childParentData.previousSibling = _lastChild; childParentData.nextSibling = _firstChild;
if (_lastChild != null) { if (_firstChild != null) {
final ParentDataType _lastChildParentData = _lastChild.parentData; final ParentDataType _firstChildParentData = _firstChild.parentData;
_lastChildParentData.nextSibling = child; _firstChildParentData.previousSibling = child;
} }
_lastChild = child;
if (_firstChild == null)
_firstChild = child; _firstChild = child;
if (_lastChild == null)
_lastChild = child;
} else { } else {
assert(_firstChild != null); assert(_firstChild != null);
assert(_lastChild != null); assert(_lastChild != null);
assert(_debugUltimatePreviousSiblingOf(before, equals: _firstChild)); assert(_debugUltimatePreviousSiblingOf(after, equals: _firstChild));
assert(_debugUltimateNextSiblingOf(before, equals: _lastChild)); assert(_debugUltimateNextSiblingOf(after, equals: _lastChild));
final ParentDataType beforeParentData = before.parentData; final ParentDataType afterParentData = after.parentData;
if (beforeParentData.previousSibling == null) { if (afterParentData.nextSibling == null) {
// insert at the start (_firstChild); we'll end up with two or more children // insert at the end (_lastChild); we'll end up with two or more children
assert(before == _firstChild); assert(after == _lastChild);
childParentData.nextSibling = before; childParentData.previousSibling = after;
beforeParentData.previousSibling = child; afterParentData.nextSibling = child;
_firstChild = child; _lastChild = child;
} else { } else {
// insert in the middle; we'll end up with three or more children // insert in the middle; we'll end up with three or more children
// set up links from child to siblings // set up links from child to siblings
childParentData.previousSibling = beforeParentData.previousSibling; childParentData.nextSibling = afterParentData.nextSibling;
childParentData.nextSibling = before; childParentData.previousSibling = after;
// set up links from siblings to child // set up links from siblings to child
final ParentDataType childPreviousSiblingParentData = childParentData.previousSibling.parentData; final ParentDataType childPreviousSiblingParentData = childParentData.previousSibling.parentData;
final ParentDataType childNextSiblingParentData = childParentData.nextSibling.parentData; final ParentDataType childNextSiblingParentData = childParentData.nextSibling.parentData;
childPreviousSiblingParentData.nextSibling = child; childPreviousSiblingParentData.nextSibling = child;
childNextSiblingParentData.previousSibling = child; childNextSiblingParentData.previousSibling = child;
assert(beforeParentData.previousSibling == child); assert(afterParentData.nextSibling == child);
} }
} }
} }
/// Insert child into this render object's child list before the given child. /// Insert child into this render object's child list after the given child.
/// void insert(ChildType child, { ChildType after }) {
/// To insert a child at the end of the child list, omit the before parameter.
void add(ChildType child, { ChildType before }) {
assert(child != this); assert(child != this);
assert(before != this); assert(after != this);
assert(child != before); assert(child != after);
assert(child != _firstChild); assert(child != _firstChild);
assert(child != _lastChild); assert(child != _lastChild);
adoptChild(child); adoptChild(child);
_addToChildList(child, before: before); _insertIntoChildList(child, after: after);
}
/// Append child to the end of this render object's child list.
void add(ChildType child) {
insert(child, after: _lastChild);
} }
/// Add all the children to the end of this render object's child list. /// Add all the children to the end of this render object's child list.
...@@ -1411,16 +1414,16 @@ abstract class ContainerRenderObjectMixin<ChildType extends RenderObject, Parent ...@@ -1411,16 +1414,16 @@ abstract class ContainerRenderObjectMixin<ChildType extends RenderObject, Parent
/// More efficient than removing and re-adding the child. Requires the child /// More efficient than removing and re-adding the child. Requires the child
/// to already be in the child list at some position. Pass null for before to /// to already be in the child list at some position. Pass null for before to
/// move the child to the end of the child list. /// move the child to the end of the child list.
void move(ChildType child, { ChildType before }) { void move(ChildType child, { ChildType after }) {
assert(child != this); assert(child != this);
assert(before != this); assert(after != this);
assert(child != before); assert(child != after);
assert(child.parent == this); assert(child.parent == this);
final ParentDataType childParentData = child.parentData; final ParentDataType childParentData = child.parentData;
if (childParentData.nextSibling == before) if (childParentData.previousSibling == after)
return; return;
_removeFromChildList(child); _removeFromChildList(child);
_addToChildList(child, before: before); _insertIntoChildList(child, after: after);
} }
void attach() { void attach() {
......
...@@ -1507,79 +1507,80 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Buildab ...@@ -1507,79 +1507,80 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Buildab
// Widgets without keys might be synced but there is no guarantee. // Widgets without keys might be synced but there is no guarantee.
// The general approach is to sync the entire new list backwards, as follows: // The general approach is to sync the entire new list backwards, as follows:
// 1. Walk the lists from the top until you no longer have // 1. Walk the lists from the top, syncing nodes, until you no longer have
// matching nodes. We don't sync these yet, but we now know to // matching nodes.
// skip them below. We do this because at each sync we need to // 2. Walk the lists from the bottom, without syncing nodes, until you no
// pass the pointer to the new next widget as the slot, which // longer have matching nodes. We'll sync these nodes at the end. We
// we can't do until we've synced the next child. // don't sync them now because we want to sync all the nodes in order
// 2. Walk the lists from the bottom, syncing nodes, until you no // from beginning ot end.
// longer have matching nodes.
// At this point we narrowed the old and new lists to the point // At this point we narrowed the old and new lists to the point
// where the nodes no longer match. // where the nodes no longer match.
// 3. Walk the narrowed part of the old list to get the list of // 3. Walk the narrowed part of the old list to get the list of
// keys and sync null with non-keyed items. // keys and sync null with non-keyed items.
// 4. Walk the narrowed part of the new list backwards: // 4. Walk the narrowed part of the new list forwards:
// * Sync unkeyed items with null // * Sync unkeyed items with null
// * Sync keyed items with the source if it exists, else with null. // * Sync keyed items with the source if it exists, else with null.
// 5. Walk the top list again but backwards, syncing the nodes. // 5. Walk the bottom of the list again, syncing the nodes.
// 6. Sync null with any items in the list of keys that are still // 6. Sync null with any items in the list of keys that are still
// mounted. // mounted.
int childrenTop = 0; int newChildrenTop = 0;
int oldChildrenTop = 0;
int newChildrenBottom = newWidgets.length - 1; int newChildrenBottom = newWidgets.length - 1;
int oldChildrenBottom = oldChildren.length - 1; int oldChildrenBottom = oldChildren.length - 1;
// top of the lists List<Element> newChildren = oldChildren.length == newWidgets.length ?
while ((childrenTop <= oldChildrenBottom) && (childrenTop <= newChildrenBottom)) { oldChildren : new List<Element>(newWidgets.length);
Element oldChild = oldChildren[childrenTop];
Widget newWidget = newWidgets[childrenTop]; Element previousChild;
// Update the top of the list.
while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
Element oldChild = oldChildren[oldChildrenTop];
Widget newWidget = newWidgets[newChildrenTop];
assert(oldChild._debugLifecycleState == _ElementLifecycle.active); assert(oldChild._debugLifecycleState == _ElementLifecycle.active);
if (!Widget.canUpdate(oldChild.widget, newWidget)) if (!Widget.canUpdate(oldChild.widget, newWidget))
break; break;
childrenTop += 1; Element newChild = updateChild(oldChild, newWidget, previousChild);
assert(newChild._debugLifecycleState == _ElementLifecycle.active);
newChildren[newChildrenTop] = newChild;
previousChild = newChild;
newChildrenTop += 1;
oldChildrenTop += 1;
} }
List<Element> newChildren = oldChildren.length == newWidgets.length ? // Scan the bottom of the list.
oldChildren : new List<Element>(newWidgets.length); while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
Element nextSibling;
// bottom of the lists
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.active); assert(oldChild._debugLifecycleState == _ElementLifecycle.active);
if (!Widget.canUpdate(oldChild.widget, newWidget)) if (!Widget.canUpdate(oldChild.widget, newWidget))
break; break;
Element newChild = updateChild(oldChild, newWidget, nextSibling);
assert(newChild._debugLifecycleState == _ElementLifecycle.active);
newChildren[newChildrenBottom] = newChild;
nextSibling = newChild;
oldChildrenBottom -= 1; oldChildrenBottom -= 1;
newChildrenBottom -= 1; newChildrenBottom -= 1;
} }
// middle of the lists - old list // Scan the old children in the middle of the list.
bool haveOldNodes = childrenTop <= oldChildrenBottom; bool haveOldChildren = oldChildrenTop <= oldChildrenBottom;
Map<Key, Element> oldKeyedChildren; Map<Key, Element> oldKeyedChildren;
if (haveOldNodes) { if (haveOldChildren) {
oldKeyedChildren = new Map<Key, Element>(); oldKeyedChildren = new Map<Key, Element>();
while (childrenTop <= oldChildrenBottom) { while (oldChildrenTop <= oldChildrenBottom) {
Element oldChild = oldChildren[oldChildrenBottom]; Element oldChild = oldChildren[oldChildrenTop];
assert(oldChild._debugLifecycleState == _ElementLifecycle.active); 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
_deactivateChild(oldChild); _deactivateChild(oldChild);
oldChildrenBottom -= 1; oldChildrenTop += 1;
} }
} }
// middle of the lists - new list // Update the middle of the list.
while (childrenTop <= newChildrenBottom) { while (newChildrenTop <= newChildrenBottom) {
Element oldChild; Element oldChild;
Widget newWidget = newWidgets[newChildrenBottom]; Widget newWidget = newWidgets[newChildrenTop];
if (haveOldNodes) { if (haveOldChildren) {
Key key = newWidget.key; Key key = newWidget.key;
if (key != null) { if (key != null) {
oldChild = oldKeyedChildren[newWidget.key]; oldChild = oldKeyedChildren[newWidget.key];
...@@ -1596,32 +1597,38 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Buildab ...@@ -1596,32 +1597,38 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Buildab
} }
} }
assert(oldChild == null || Widget.canUpdate(oldChild.widget, newWidget)); assert(oldChild == null || Widget.canUpdate(oldChild.widget, newWidget));
Element newChild = updateChild(oldChild, newWidget, nextSibling); Element newChild = updateChild(oldChild, newWidget, previousChild);
assert(newChild._debugLifecycleState == _ElementLifecycle.active); assert(newChild._debugLifecycleState == _ElementLifecycle.active);
assert(oldChild == newChild || oldChild == null || oldChild._debugLifecycleState != _ElementLifecycle.active); assert(oldChild == newChild || oldChild == null || oldChild._debugLifecycleState != _ElementLifecycle.active);
newChildren[newChildrenBottom] = newChild; newChildren[newChildrenTop] = newChild;
nextSibling = newChild; previousChild = newChild;
newChildrenBottom -= 1; newChildrenTop += 1;
} }
assert(oldChildrenBottom == newChildrenBottom);
assert(childrenTop == newChildrenBottom + 1);
// now sync the top of the list // We've scaned the whole list.
while (childrenTop > 0) { assert(oldChildrenTop == oldChildrenBottom + 1);
childrenTop -= 1; assert(newChildrenTop == newChildrenBottom + 1);
Element oldChild = oldChildren[childrenTop]; assert(newWidgets.length - newChildrenTop == oldChildren.length - oldChildrenTop);
newChildrenBottom = newWidgets.length - 1;
oldChildrenBottom = oldChildren.length - 1;
// Update the bottom of the list.
while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
Element oldChild = oldChildren[oldChildrenTop];
assert(oldChild._debugLifecycleState == _ElementLifecycle.active); assert(oldChild._debugLifecycleState == _ElementLifecycle.active);
Widget newWidget = newWidgets[childrenTop]; Widget newWidget = newWidgets[newChildrenTop];
assert(Widget.canUpdate(oldChild.widget, newWidget)); assert(Widget.canUpdate(oldChild.widget, newWidget));
Element newChild = updateChild(oldChild, newWidget, nextSibling); Element newChild = updateChild(oldChild, newWidget, previousChild);
assert(newChild._debugLifecycleState == _ElementLifecycle.active); assert(newChild._debugLifecycleState == _ElementLifecycle.active);
assert(oldChild == newChild || oldChild == null || oldChild._debugLifecycleState != _ElementLifecycle.active); assert(oldChild == newChild || oldChild == null || oldChild._debugLifecycleState != _ElementLifecycle.active);
newChildren[childrenTop] = newChild; newChildren[newChildrenTop] = newChild;
nextSibling = newChild; previousChild = newChild;
newChildrenTop += 1;
oldChildrenTop += 1;
} }
// clean up any of the remaining middle nodes from the old list // clean up any of the remaining middle nodes from the old list
if (haveOldNodes && !oldKeyedChildren.isEmpty) { if (haveOldChildren && !oldKeyedChildren.isEmpty) {
for (Element oldChild in oldKeyedChildren.values) for (Element oldChild in oldKeyedChildren.values)
_deactivateChild(oldChild); _deactivateChild(oldChild);
} }
...@@ -1755,15 +1762,13 @@ class MultiChildRenderObjectElement<T extends MultiChildRenderObjectWidget> exte ...@@ -1755,15 +1762,13 @@ class MultiChildRenderObjectElement<T extends MultiChildRenderObjectWidget> exte
void insertChildRenderObject(RenderObject child, Element slot) { void insertChildRenderObject(RenderObject child, Element slot) {
final ContainerRenderObjectMixin renderObject = this.renderObject; final ContainerRenderObjectMixin renderObject = this.renderObject;
final RenderObject nextSibling = slot?.renderObject; renderObject.insert(child, after: slot?.renderObject);
renderObject.add(child, before: nextSibling);
assert(renderObject == this.renderObject); assert(renderObject == this.renderObject);
} }
void moveChildRenderObject(RenderObject child, dynamic slot) { void moveChildRenderObject(RenderObject child, dynamic slot) {
final ContainerRenderObjectMixin renderObject = this.renderObject; final ContainerRenderObjectMixin renderObject = this.renderObject;
final RenderObject nextSibling = slot?.renderObject; renderObject.move(child, after: slot?.renderObject);
renderObject.move(child, before: nextSibling);
assert(renderObject == this.renderObject); assert(renderObject == this.renderObject);
} }
...@@ -1797,7 +1802,7 @@ class MultiChildRenderObjectElement<T extends MultiChildRenderObjectWidget> exte ...@@ -1797,7 +1802,7 @@ class MultiChildRenderObjectElement<T extends MultiChildRenderObjectWidget> exte
super.mount(parent, newSlot); super.mount(parent, newSlot);
_children = new List<Element>(widget.children.length); _children = new List<Element>(widget.children.length);
Element previousChild; Element previousChild;
for (int i = _children.length - 1; i >= 0; --i) { for (int i = 0; i < _children.length; ++i) {
Element newChild = _inflateWidget(widget.children[i], previousChild); Element newChild = _inflateWidget(widget.children[i], previousChild);
_children[i] = newChild; _children[i] = newChild;
previousChild = newChild; previousChild = newChild;
......
...@@ -118,14 +118,12 @@ abstract class _ViewportBaseElement<T extends _ViewportBase> extends RenderObjec ...@@ -118,14 +118,12 @@ abstract class _ViewportBaseElement<T extends _ViewportBase> extends RenderObjec
} }
void insertChildRenderObject(RenderObject child, Element slot) { void insertChildRenderObject(RenderObject child, Element slot) {
RenderObject nextSibling = slot?.renderObject; renderObject.insert(child, after: slot?.renderObject);
renderObject.add(child, before: nextSibling);
} }
void moveChildRenderObject(RenderObject child, Element slot) { void moveChildRenderObject(RenderObject child, Element slot) {
assert(child.parent == renderObject); assert(child.parent == renderObject);
RenderObject nextSibling = slot?.renderObject; renderObject.move(child, after: slot?.renderObject);
renderObject.move(child, before: nextSibling);
} }
void removeChildRenderObject(RenderObject child) { void removeChildRenderObject(RenderObject child) {
......
...@@ -552,14 +552,12 @@ class _MixedViewportElement extends RenderObjectElement<MixedViewport> { ...@@ -552,14 +552,12 @@ class _MixedViewportElement extends RenderObjectElement<MixedViewport> {
if (haveChildren) { if (haveChildren) {
// Place all our children in our RenderObject. // Place all our children in our RenderObject.
// All the children we are placing are in builtChildren and newChildren. // All the children we are placing are in builtChildren and newChildren.
// We will walk them backwards so we can set the slots at the same time. Element previousChild = null;
Element nextSibling = null; for (int i = startIndex; i < index; ++i) {
while (index > startIndex) { final Element element = builtChildren[i];
index -= 1; if (element.slot != previousChild)
final Element element = builtChildren[index]; updateSlotForChild(element, previousChild);
if (element.slot != nextSibling) previousChild = element;
updateSlotForChild(element, nextSibling);
nextSibling = element;
} }
} }
...@@ -577,20 +575,19 @@ class _MixedViewportElement extends RenderObjectElement<MixedViewport> { ...@@ -577,20 +575,19 @@ class _MixedViewportElement extends RenderObjectElement<MixedViewport> {
if (slot == _omit) if (slot == _omit)
return; return;
assert(slot == null || slot is Element); assert(slot == null || slot is Element);
RenderObject nextSibling = slot?.renderObject; renderObject.insert(child, after: slot?.renderObject);
renderObject.add(child, before: nextSibling);
} }
void moveChildRenderObject(RenderObject child, dynamic slot) { void moveChildRenderObject(RenderObject child, dynamic slot) {
if (slot == _omit) if (slot == _omit)
return; return;
assert(slot == null || slot is Element); assert(slot == null || slot is Element);
RenderObject nextSibling = slot?.renderObject; RenderObject previousSibling = slot?.renderObject;
assert(nextSibling == null || nextSibling.parent == renderObject); assert(previousSibling == null || previousSibling.parent == renderObject);
if (child.parent == renderObject) if (child.parent == renderObject)
renderObject.move(child, before: nextSibling); renderObject.move(child, after: previousSibling);
else else
renderObject.add(child, before: nextSibling); renderObject.insert(child, after: previousSibling);
} }
void removeChildRenderObject(RenderObject child) { void removeChildRenderObject(RenderObject child) {
......
...@@ -149,14 +149,12 @@ abstract class VirtualViewportElement<T extends VirtualViewport> extends RenderO ...@@ -149,14 +149,12 @@ abstract class VirtualViewportElement<T extends VirtualViewport> extends RenderO
} }
void insertChildRenderObject(RenderObject child, Element slot) { void insertChildRenderObject(RenderObject child, Element slot) {
RenderObject nextSibling = slot?.renderObject; renderObject.insert(child, after: slot?.renderObject);
renderObject.add(child, before: nextSibling);
} }
void moveChildRenderObject(RenderObject child, Element slot) { void moveChildRenderObject(RenderObject child, Element slot) {
assert(child.parent == renderObject); assert(child.parent == renderObject);
RenderObject nextSibling = slot?.renderObject; renderObject.move(child, after: slot?.renderObject);
renderObject.move(child, before: nextSibling);
} }
void removeChildRenderObject(RenderObject child) { void removeChildRenderObject(RenderObject child) {
......
...@@ -38,17 +38,16 @@ void main() { ...@@ -38,17 +38,16 @@ void main() {
key: keyFocus, key: keyFocus,
child: new Column( child: new Column(
children: <Widget>[ children: <Widget>[
// reverse these when you fix https://github.com/flutter/engine/issues/1495
new TestFocusable(
key: keyB,
no: 'b',
yes: 'B FOCUSED'
),
new TestFocusable( new TestFocusable(
key: keyA, key: keyA,
no: 'a', no: 'a',
yes: 'A FOCUSED' yes: 'A FOCUSED'
), ),
new TestFocusable(
key: keyB,
no: 'b',
yes: 'B FOCUSED'
),
] ]
) )
) )
......
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