@@ -19,7 +19,7 @@ export 'package:sky/rendering/box.dart' show BoxConstraints, BoxDecoration, Bord
1919export 'package:sky/rendering/flex.dart' show FlexDirection;
2020export 'package:sky/rendering/object.dart' show Point, Offset, Size, Rect, Color, Paint, Path;
2121
22- final bool _shouldLogRenderDuration = false ;
22+ final bool _shouldLogRenderDuration = false ; // see also 'enableProfilingLoop' argument to runApp()
2323
2424typedef Widget Builder ();
2525typedef void WidgetTreeWalker (Widget );
@@ -324,7 +324,7 @@ abstract class Widget {
324324 }
325325
326326 if (oldNode != null ) {
327- if (oldNode.runtimeType == newNode.runtimeType && oldNode.key == newNode.key ) {
327+ if (_canSync ( newNode, oldNode) ) {
328328 if (oldNode.retainStatefulNodeIfPossible (newNode)) {
329329 assert (oldNode.mounted);
330330 assert (! newNode.mounted);
@@ -398,6 +398,10 @@ abstract class Widget {
398398 }
399399}
400400
401+ bool _canSync (Widget a, Widget b) {
402+ return a.runtimeType == b.runtimeType && a.key == b.key;
403+ }
404+
401405
402406// Descendants of TagNode provide a way to tag RenderObjectWrapper and
403407// Component nodes with annotations, such as event listeners,
@@ -707,8 +711,7 @@ abstract class StatefulComponent extends Component {
707711 bool retainStatefulNodeIfPossible (StatefulComponent newNode) {
708712 assert (! _disqualifiedFromEverAppearingAgain);
709713 assert (newNode != null );
710- assert (runtimeType == newNode.runtimeType);
711- assert (key == newNode.key);
714+ assert (_canSync (this , newNode);
712715 assert (_child != null );
713716 newNode._disqualifiedFromEverAppearingAgain = true ;
714717
@@ -894,7 +897,7 @@ abstract class RenderObjectWrapper extends Widget {
894897 if (_ancestor is RenderObjectWrapper )
895898 _ancestor.insertChildRenderObject (this , slot);
896899 } else {
897- assert (old.runtimeType == runtimeType );
900+ assert (_canSync ( this , old) );
898901 _renderObject = old.renderObject;
899902 _ancestor = old._ancestor;
900903 assert (_renderObject != null );
@@ -1007,8 +1010,9 @@ abstract class OneChildRenderObjectWrapper extends RenderObjectWrapper {
10071010
10081011abstract class MultiChildRenderObjectWrapper extends RenderObjectWrapper {
10091012
1010- // In MultiChildRenderObjectWrapper subclasses, slots are RenderObject nodes
1011- // to use as the "insert before" sibling in ContainerRenderObjectMixin.add() calls
1013+ // In MultiChildRenderObjectWrapper subclasses, slots are the Widget
1014+ // nodes whose RenderObjects are to be used as the "insert before"
1015+ // sibling in ContainerRenderObjectMixin.add() calls
10121016
10131017 MultiChildRenderObjectWrapper ({ Key key, List <Widget > children })
10141018 : this .children = children == null ? const [] : children,
@@ -1023,11 +1027,12 @@ abstract class MultiChildRenderObjectWrapper extends RenderObjectWrapper {
10231027 walker (child);
10241028 }
10251029
1026- void insertChildRenderObject (RenderObjectWrapper child, dynamic slot) {
1030+ void insertChildRenderObject (RenderObjectWrapper child, Widget slot) {
10271031 final renderObject = this .renderObject; // TODO(ianh): Remove this once the analyzer is cleverer
1028- assert (slot == null || slot is RenderObject );
1032+ RenderObject nextSibling = slot != null ? slot.renderObject : null ;
1033+ assert (nextSibling == null || nextSibling is RenderObject );
10291034 assert (renderObject is ContainerRenderObjectMixin );
1030- renderObject.add (child.renderObject, before: slot );
1035+ renderObject.add (child.renderObject, before: nextSibling );
10311036 assert (renderObject == this .renderObject); // TODO(ianh): Remove this once the analyzer is cleverer
10321037 }
10331038
@@ -1060,121 +1065,134 @@ abstract class MultiChildRenderObjectWrapper extends RenderObjectWrapper {
10601065 assert (renderObject is ContainerRenderObjectMixin );
10611066 assert (old == null || old.renderObject == renderObject);
10621067
1063- var startIndex = 0 ;
1064- var endIndex = children.length;
1065-
1066- var oldChildren = old == null ? [] : old.children;
1067- var oldStartIndex = 0 ;
1068- var oldEndIndex = oldChildren.length;
1069-
1070- RenderObject nextSibling = null ;
1071- Widget currentNode = null ;
1072- Widget oldNode = null ;
1073-
1074- void sync (int atIndex) {
1075- children[atIndex] = syncChild (currentNode, oldNode, nextSibling);
1076- assert (children[atIndex] != null );
1077- assert (children[atIndex].parent == this );
1078- if (atIndex > 0 )
1079- children[atIndex- 1 ].updateSlot (children[atIndex].renderObject);
1080- }
1081-
1082- // Scan backwards from end of list while nodes can be directly synced
1083- // without reordering.
1084- while (endIndex > startIndex && oldEndIndex > oldStartIndex) {
1085- currentNode = children[endIndex - 1 ];
1086- oldNode = oldChildren[oldEndIndex - 1 ];
1087-
1088- if (currentNode.runtimeType != oldNode.runtimeType || currentNode.key != oldNode.key) {
1068+ // This attempts to diff the new child list (this.children) with
1069+ // the old child list (old.children), and update our renderObject
1070+ // accordingly.
1071+
1072+ // The cases it tries to optimise for are:
1073+ // - the old list is empty
1074+ // - the lists are identical
1075+ // - there is an insertion or removal of one or more widgets in
1076+ // only one place in the list
1077+ // If a widget with a key is in both lists, it will be synced.
1078+ // Widgets without keys might be synced but there is no guarantee.
1079+
1080+ // The general approach is to sync the entire new list backwards, as follows:
1081+ // 1. Walk the lists from the top until you no longer have
1082+ // matching nodes. We don't sync these yet, but we now know to
1083+ // skip them below. We do this because at each sync we need to
1084+ // pass the pointer to the new next widget as the slot, which
1085+ // we can't do until we've synced the next child.
1086+ // 2. Walk the lists from the bottom, syncing nodes, until you no
1087+ // longer have matching nodes.
1088+ // At this point we narrowed the old and new lists to the point
1089+ // where the nodes no longer match.
1090+ // 3. Walk the narrowed part of the old list to get the list of
1091+ // keys and sync null with non-keyed items.
1092+ // 4. Walk the narrowed part of the new list backwards:
1093+ // * Sync unkeyed items with null
1094+ // * Sync keyed items with the source if it exists, else with null.
1095+ // 5. Walk the top list again but backwards, syncing the nodes.
1096+ // 6. Sync null with any items in the list of keys that are still
1097+ // mounted.
1098+
1099+ final List <Widget > newChildren = children;
1100+ final List <Widget > oldChildren = old == null ? const < Widget > [] : old.children;
1101+ int childrenTop = 0 ;
1102+ int newChildrenBottom = newChildren.length- 1 ;
1103+ int oldChildrenBottom = oldChildren.length- 1 ;
1104+
1105+ // top of the lists
1106+ while ((childrenTop <= oldChildrenBottom) && (childrenTop <= newChildrenBottom)) {
1107+ Widget oldChild = oldChildren[childrenTop];
1108+ assert (oldChild.mounted);
1109+ Widget newChild = newChildren[childrenTop];
1110+ assert (newChild == oldChild || ! newChild.mounted);
1111+ if (! _canSync (oldChild, newChild))
10891112 break ;
1090- }
1091-
1092- endIndex-- ;
1093- oldEndIndex-- ;
1094- sync (endIndex);
1095- nextSibling = children[endIndex].renderObject;
1096- }
1097-
1098- HashMap <Key , Widget > oldNodeIdMap = null ;
1099-
1100- bool oldNodeReordered (Key key) {
1101- return oldNodeIdMap != null &&
1102- oldNodeIdMap.containsKey (key) &&
1103- oldNodeIdMap[key] == null ;
1113+ childrenTop += 1 ;
11041114 }
11051115
1106- void advanceOldStartIndex () {
1107- oldStartIndex++ ;
1108- while (oldStartIndex < oldEndIndex &&
1109- oldNodeReordered (oldChildren[oldStartIndex].key)) {
1110- oldStartIndex++ ;
1111- }
1112- }
1116+ Widget nextSibling;
11131117
1114- void ensureOldIdMap () {
1115- if (oldNodeIdMap != null )
1116- return ;
1117-
1118- oldNodeIdMap = new HashMap <Key , Widget >();
1119- for (int i = oldStartIndex; i < oldEndIndex; i++ ) {
1120- var node = oldChildren[i];
1121- if (node.key != null )
1122- oldNodeIdMap.putIfAbsent (node.key, () => node);
1123- }
1118+ // bottom of the lists
1119+ while ((childrenTop <= oldChildrenBottom) && (childrenTop <= newChildrenBottom)) {
1120+ Widget oldChild = oldChildren[oldChildrenBottom];
1121+ assert (oldChild.mounted);
1122+ Widget newChild = newChildren[newChildrenBottom];
1123+ assert (newChild == oldChild || ! newChild.mounted);
1124+ if (! _canSync (oldChild, newChild))
1125+ break ;
1126+ newChild = syncChild (newChild, oldChild, nextSibling);
1127+ assert (newChild.mounted);
1128+ assert (oldChild == newChild || ! oldChild.mounted);
1129+ newChildren[newChildrenBottom] = newChild;
1130+ nextSibling = newChild;
1131+ oldChildrenBottom -= 1 ;
1132+ newChildrenBottom -= 1 ;
11241133 }
11251134
1126- void searchForOldNode () {
1127- if (currentNode.key == null )
1128- return ; // never re-order these nodes
1129-
1130- ensureOldIdMap ();
1131- oldNode = oldNodeIdMap[currentNode.key];
1132- if (oldNode == null )
1133- return ;
1134-
1135- oldNodeIdMap[currentNode .key] = null ; // mark it reordered
1136- assert (renderObject is ContainerRenderObjectMixin );
1137- assert (old.renderObject is ContainerRenderObjectMixin );
1138- assert (oldNode.renderObject != null );
1139-
1140- renderObject. move (oldNode.renderObject, before : nextSibling);
1135+ // middle of the lists - old list
1136+ bool haveOldNodes = childrenTop <= oldChildrenBottom;
1137+ Map < Key , Widget > oldKeyedChildren;
1138+ if (haveOldNodes) {
1139+ oldKeyedChildren = new Map < Key , Widget > ();
1140+ while (childrenTop <= oldChildrenBottom) {
1141+ Widget oldChild = oldChildren[oldChildrenBottom];
1142+ assert (oldChild.mounted) ;
1143+ if (oldChild.key != null ) {
1144+ oldKeyedChildren[oldChild .key] = oldChild;
1145+ } else {
1146+ syncChild ( null , oldChild, null );
1147+ }
1148+ oldChildrenBottom -= 1 ;
1149+ }
11411150 }
11421151
1143- // Scan forwards, this time we may re-order;
1144- nextSibling = renderObject.firstChild;
1145- while (startIndex < endIndex && oldStartIndex < oldEndIndex) {
1146- currentNode = children[startIndex];
1147- oldNode = oldChildren[oldStartIndex];
1148-
1149- if (currentNode.runtimeType == oldNode.runtimeType && currentNode.key == oldNode.key) {
1150- nextSibling = renderObject.childAfter (nextSibling);
1151- sync (startIndex);
1152- startIndex++ ;
1153- advanceOldStartIndex ();
1154- continue ;
1152+ // middle of the lists - new list
1153+ while (childrenTop <= newChildrenBottom) {
1154+ Widget oldChild;
1155+ Widget newChild = newChildren[newChildrenBottom];
1156+ if (haveOldNodes) {
1157+ Key key = newChild.key;
1158+ if (key != null ) {
1159+ oldChild = oldKeyedChildren[newChild.key];
1160+ if (oldChild != null ) {
1161+ if (oldChild.runtimeType != newChild.runtimeType)
1162+ oldChild = null ;
1163+ oldKeyedChildren.remove (key);
1164+ }
1165+ }
11551166 }
1156-
1157- oldNode = null ;
1158- searchForOldNode ();
1159- sync (startIndex);
1160- startIndex++ ;
1167+ assert (newChild == oldChild || ! newChild.mounted);
1168+ newChild = syncChild (newChild, oldChild, nextSibling);
1169+ assert (newChild.mounted);
1170+ assert (oldChild == newChild || oldChild == null || ! oldChild.mounted);
1171+ newChildren[newChildrenBottom] = newChild;
1172+ nextSibling = newChild;
1173+ newChildrenBottom -= 1 ;
11611174 }
1162-
1163- // New insertions
1164- oldNode = null ;
1165- while (startIndex < endIndex) {
1166- currentNode = children[startIndex];
1167- sync (startIndex);
1168- startIndex++ ;
1175+ assert (oldChildrenBottom == newChildrenBottom);
1176+ assert (childrenTop == newChildrenBottom+ 1 );
1177+
1178+ // now sync the top of the list
1179+ while (childrenTop > 0 ) {
1180+ childrenTop -= 1 ;
1181+ Widget oldChild = oldChildren[childrenTop];
1182+ assert (oldChild.mounted);
1183+ Widget newChild = newChildren[childrenTop];
1184+ assert (newChild == oldChild || ! newChild.mounted);
1185+ assert (_canSync (oldChild, newChild));
1186+ newChild = syncChild (newChild, oldChild, nextSibling);
1187+ assert (newChild.mounted);
1188+ assert (oldChild == newChild || oldChild == null || ! oldChild.mounted);
1189+ newChildren[childrenTop] = newChild;
1190+ nextSibling = newChild;
11691191 }
11701192
1171- // Removals
1172- currentNode = null ;
1173- while (oldStartIndex < oldEndIndex) {
1174- oldNode = oldChildren[oldStartIndex];
1175- syncChild (null , oldNode, null );
1176- assert (oldNode.parent == null );
1177- advanceOldStartIndex ();
1193+ if (haveOldNodes && ! oldKeyedChildren.isEmpty) {
1194+ for (Widget oldChild in oldKeyedChildren.values)
1195+ syncChild (null , oldChild, null );
11781196 }
11791197
11801198 assert (renderObject == this .renderObject); // TODO(ianh): Remove this once the analyzer is cleverer
0 commit comments