@@ -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 = true ; // see also 'enableProfilingLoop' argument to runApp()
2323
2424typedef Widget Builder ();
2525typedef void WidgetTreeWalker (Widget );
@@ -1007,8 +1007,9 @@ abstract class OneChildRenderObjectWrapper extends RenderObjectWrapper {
10071007
10081008abstract class MultiChildRenderObjectWrapper extends RenderObjectWrapper {
10091009
1010- // In MultiChildRenderObjectWrapper subclasses, slots are RenderObject nodes
1011- // to use as the "insert before" sibling in ContainerRenderObjectMixin.add() calls
1010+ // In MultiChildRenderObjectWrapper subclasses, slots are the Widget
1011+ // nodes whose RenderObjects are to be used as the "insert before"
1012+ // sibling in ContainerRenderObjectMixin.add() calls
10121013
10131014 MultiChildRenderObjectWrapper ({ Key key, List <Widget > children })
10141015 : this .children = children == null ? const [] : children,
@@ -1023,11 +1024,12 @@ abstract class MultiChildRenderObjectWrapper extends RenderObjectWrapper {
10231024 walker (child);
10241025 }
10251026
1026- void insertChildRenderObject (RenderObjectWrapper child, dynamic slot) {
1027+ void insertChildRenderObject (RenderObjectWrapper child, Widget slot) {
10271028 final renderObject = this .renderObject; // TODO(ianh): Remove this once the analyzer is cleverer
1028- assert (slot == null || slot is RenderObject );
1029+ RenderObject nextSibling = slot is Widget ? slot.renderObject : null ;
1030+ assert (nextSibling == null || nextSibling is RenderObject );
10291031 assert (renderObject is ContainerRenderObjectMixin );
1030- renderObject.add (child.renderObject, before: slot );
1032+ renderObject.add (child.renderObject, before: nextSibling );
10311033 assert (renderObject == this .renderObject); // TODO(ianh): Remove this once the analyzer is cleverer
10321034 }
10331035
@@ -1053,128 +1055,145 @@ abstract class MultiChildRenderObjectWrapper extends RenderObjectWrapper {
10531055 return false ;
10541056 }
10551057
1058+ bool _canSync (Widget a, Widget b) {
1059+ return (a.runtimeType == b.runtimeType) && (a.key == b.key);
1060+ }
1061+
10561062 void syncRenderObject (MultiChildRenderObjectWrapper old) {
10571063 super .syncRenderObject (old);
10581064
10591065 final ContainerRenderObjectMixin renderObject = this .renderObject; // TODO(ianh): Remove this once the analyzer is cleverer
10601066 assert (renderObject is ContainerRenderObjectMixin );
10611067 assert (old == null || old.renderObject == renderObject);
10621068
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) {
1069+ // This attempts to diff the new child list (this.children) with
1070+ // the old child list (old.children), and update our renderObject
1071+ // accordingly.
1072+
1073+ // The cases it tries to optimise for are:
1074+ // - the old list is empty
1075+ // - the lists are identical
1076+ // - there is an insertion or removal of one or more widgets in
1077+ // only one place in the list
1078+ // If a widget with a key is in both lists, it will be synced.
1079+ // Widgets without keys might be synced but there is no guarantee.
1080+
1081+ // The general approach is to sync the entire new list backwards, as follows:
1082+ // 1. Walk the lists from the top until you no longer have
1083+ // matching nodes. We don't sync these yet, but we now know to
1084+ // skip them below. We do this because at each sync we need to
1085+ // pass the pointer to the new next widget as the slot, which
1086+ // we can't do until we've synced the next child.
1087+ // 2. Walk the lists from the bottom, syncing nodes, until you no
1088+ // longer have matching nodes.
1089+ // At this point we narrowed the old and new lists to the point
1090+ // where the nodes no longer match.
1091+ // 3. Walk the narrowed part of the old list to get the list of
1092+ // keys and sync null with non-keyed items.
1093+ // 4. Walk the narrowed part of the new list backwards:
1094+ // * Sync unkeyed items with null
1095+ // * Sync keyed items with the source if it exists, else with null.
1096+ // 5. Walk the top list again but backwards, syncing the nodes.
1097+ // 6. Sync null with any items in the list of keys that are still
1098+ // mounted.
1099+
1100+ final List <Widget > newChildren = children;
1101+ final List <Widget > oldChildren = old == null ? const < Widget > [] : old.children;
1102+ int childrenTop = 0 ;
1103+ int newChildrenBottom = newChildren.length- 1 ;
1104+ int oldChildrenBottom = oldChildren.length- 1 ;
1105+
1106+ // top of the lists
1107+ while ((childrenTop <= oldChildrenBottom) && (childrenTop <= newChildrenBottom)) {
1108+ Widget oldChild = oldChildren[childrenTop];
1109+ assert (oldChild.mounted);
1110+ Widget newChild = newChildren[childrenTop];
1111+ assert (newChild == oldChild || ! newChild.mounted);
1112+ if (! _canSync (oldChild, newChild))
10891113 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 ;
1114+ childrenTop += 1 ;
11041115 }
11051116
1106- void advanceOldStartIndex () {
1107- oldStartIndex++ ;
1108- while (oldStartIndex < oldEndIndex &&
1109- oldNodeReordered (oldChildren[oldStartIndex].key)) {
1110- oldStartIndex++ ;
1111- }
1112- }
1117+ Widget nextSibling;
11131118
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- }
1119+ // bottom of the lists
1120+ while ((childrenTop <= oldChildrenBottom) && (childrenTop <= newChildrenBottom)) {
1121+ Widget oldChild = oldChildren[oldChildrenBottom];
1122+ assert (oldChild.mounted);
1123+ Widget newChild = newChildren[newChildrenBottom];
1124+ assert (newChild == oldChild || ! newChild.mounted);
1125+ if (! _canSync (oldChild, newChild))
1126+ break ;
1127+ newChild = syncChild (newChild, oldChild, nextSibling);
1128+ assert (newChild.mounted);
1129+ assert (oldChild == newChild || ! oldChild.mounted);
1130+ newChildren[newChildrenBottom] = newChild;
1131+ nextSibling = newChild;
1132+ oldChildrenBottom -= 1 ;
1133+ newChildrenBottom -= 1 ;
11241134 }
11251135
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);
1136+ // middle of the lists - old list
1137+ bool haveOldNodes = childrenTop <= oldChildrenBottom;
1138+ Map < Key , Widget > oldKeyedChildren;
1139+ if (haveOldNodes) {
1140+ oldKeyedChildren = new Map < Key , Widget > ();
1141+ while (childrenTop <= oldChildrenBottom) {
1142+ Widget oldChild = oldChildren[oldChildrenBottom];
1143+ assert (oldChild.mounted) ;
1144+ if (oldChild.key != null ) {
1145+ oldKeyedChildren[oldChild .key] = oldChild;
1146+ } else {
1147+ syncChild ( null , oldChild, null );
1148+ }
1149+ oldChildrenBottom -= 1 ;
1150+ }
11411151 }
11421152
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 ;
1153+ // middle of the lists - new list
1154+ while (childrenTop <= newChildrenBottom) {
1155+ Widget oldChild;
1156+ Widget newChild = newChildren[newChildrenBottom];
1157+ if (haveOldNodes) {
1158+ Key key = newChild.key;
1159+ if (key != null ) {
1160+ oldChild = oldKeyedChildren[newChild.key];
1161+ if (oldChild != null ) {
1162+ if (oldChild.runtimeType != newChild.runtimeType)
1163+ oldChild = null ;
1164+ oldKeyedChildren.remove (key);
1165+ }
1166+ }
11551167 }
1156-
1157- oldNode = null ;
1158- searchForOldNode ();
1159- sync (startIndex);
1160- startIndex++ ;
1168+ assert (newChild == oldChild || ! newChild.mounted);
1169+ newChild = syncChild (newChild, oldChild, nextSibling);
1170+ assert (newChild.mounted);
1171+ assert (oldChild == newChild || oldChild == null || ! oldChild.mounted);
1172+ newChildren[newChildrenBottom] = newChild;
1173+ nextSibling = newChild;
1174+ newChildrenBottom -= 1 ;
11611175 }
1162-
1163- // New insertions
1164- oldNode = null ;
1165- while (startIndex < endIndex) {
1166- currentNode = children[startIndex];
1167- sync (startIndex);
1168- startIndex++ ;
1176+ assert (oldChildrenBottom == newChildrenBottom);
1177+ assert (childrenTop == newChildrenBottom+ 1 );
1178+
1179+ // now sync the top of the list
1180+ while (childrenTop > 0 ) {
1181+ childrenTop -= 1 ;
1182+ Widget oldChild = oldChildren[childrenTop];
1183+ assert (oldChild.mounted);
1184+ Widget newChild = newChildren[childrenTop];
1185+ assert (newChild == oldChild || ! newChild.mounted);
1186+ assert (_canSync (oldChild, newChild));
1187+ newChild = syncChild (newChild, oldChild, nextSibling);
1188+ assert (newChild.mounted);
1189+ assert (oldChild == newChild || oldChild == null || ! oldChild.mounted);
1190+ newChildren[childrenTop] = newChild;
1191+ nextSibling = newChild;
11691192 }
11701193
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 ();
1194+ if (haveOldNodes && ! oldKeyedChildren.isEmpty) {
1195+ for (Widget oldChild in oldKeyedChildren.values)
1196+ syncChild (null , oldChild, null );
11781197 }
11791198
11801199 assert (renderObject == this .renderObject); // TODO(ianh): Remove this once the analyzer is cleverer
0 commit comments