Skip to content
This repository was archived by the owner on Feb 22, 2018. It is now read-only.

Commit 28a79bc

Browse files
committed
fix(dirty-checking): handle simultaneous mutations and ref changes
This applies to both maps and lists/iterables. Consider the case when one is watching an expression, say, `items`, that evaluates a list.  Now consider the case when one both mutates that list and also updates `items` to point to a different list.  In this case, the reported changes should be between the earlier observed collection and the newly observed collection.  The previous implementation is buggy.  It first observes that the original list was mutated and then observes that the reference to the list was updated to a different list.  The changes it reports are the differences between the mutated list and the new list which is incorrect. This commit fixes the bug by noticing the case when both a mutation of the previously tracked sequence and a ref change occurs and resets the change record to undo the recording of the mutation.
1 parent 04fae51 commit 28a79bc

File tree

4 files changed

+444
-191
lines changed

4 files changed

+444
-191
lines changed

lib/change_detection/change_detection.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ abstract class MapChangeRecord<K, V> {
114114

115115
/// A list of [CollectionKeyValue]s which are in the iteration order. */
116116
KeyValue<K, V> get mapHead;
117+
PreviousKeyValue<K, V> get previousMapHead;
117118
/// A list of changed items.
118119
ChangedKeyValue<K, V> get changesHead;
119120
/// A list of new added items.
@@ -145,6 +146,10 @@ abstract class KeyValue<K, V> extends MapKeyValue<K, V> {
145146
KeyValue<K, V> get nextKeyValue;
146147
}
147148

149+
abstract class PreviousKeyValue<K, V> extends MapKeyValue<K, V> {
150+
PreviousKeyValue<K, V> get previousNextKeyValue;
151+
}
152+
148153
abstract class AddedKeyValue<K, V> extends MapKeyValue<K, V> {
149154
AddedKeyValue<K, V> get nextAddedKeyValue;
150155
}
@@ -172,6 +177,7 @@ abstract class CollectionChangeRecord<V> {
172177

173178
/** A list of [CollectionItem]s which are in the iteration order. */
174179
CollectionItem<V> get collectionHead;
180+
PreviousCollectionItem<V> get previousCollectionHead;
175181
/** A list of new [AddedItem]s. */
176182
AddedItem<V> get additionsHead;
177183
/** A list of [MovedItem]s. */
@@ -211,6 +217,10 @@ abstract class CollectionItem<V> extends CollectionChangeItem<V> {
211217
* A linked list of new items added to the collection. These items are always in
212218
* the iteration order of the collection.
213219
*/
220+
abstract class PreviousCollectionItem<V> extends CollectionChangeItem<V> {
221+
PreviousCollectionItem<V> get previousNextItem;
222+
}
223+
214224
abstract class AddedItem<V> extends CollectionChangeItem<V> {
215225
AddedItem<V> get nextAddedItem;
216226
}

lib/change_detection/dirty_checking_change_detector.dart

Lines changed: 136 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -426,16 +426,29 @@ class DirtyCheckingRecord<H> implements Record<H>, WatchRecord<H> {
426426
_getter = null;
427427
if (obj is Map) {
428428
if (_mode != _MODE_MAP_) {
429-
// Last one was collection as well, don't reset state.
430429
_mode = _MODE_MAP_;
431430
currentValue = new _MapChangeRecord();
432431
}
432+
if (currentValue.isDirty) {
433+
// We're dirty because the mapping we tracked by reference mutated.
434+
// In addition, our reference has now changed. We should compare the
435+
// previous reported value of that mapping with the one from the
436+
// new reference.
437+
currentValue._revertToPreviousState();
438+
}
439+
433440
} else if (obj is Iterable) {
434441
if (_mode != _MODE_ITERABLE_) {
435-
// Last one was collection as well, don't reset state.
436-
_mode = _MODE_ITERABLE_;
442+
_mode = _MODE_ITERABLE_;
437443
currentValue = new _CollectionChangeRecord();
438444
}
445+
if (currentValue.isDirty) {
446+
// We're dirty because the collection we tracked by reference mutated.
447+
// In addition, our reference has now changed. We should compare the
448+
// previous reported value of that collection with the one from the
449+
// new reference.
450+
currentValue._revertToPreviousState();
451+
}
439452
} else {
440453
_mode = _MODE_IDENTITY_;
441454
}
@@ -507,12 +520,14 @@ class _MapChangeRecord<K, V> implements MapChangeRecord<K, V> {
507520
final Map<dynamic, KeyValueRecord> _records = new Map<dynamic, KeyValueRecord>();
508521
Map _map;
509522
KeyValueRecord _mapHead;
523+
KeyValueRecord _previousMapHead;
510524
KeyValueRecord _changesHead, _changesTail;
511525
KeyValueRecord _additionsHead, _additionsTail;
512526
KeyValueRecord _removalsHead, _removalsTail;
513527

514528
Map get map => _map;
515529
KeyValue<K, V> get mapHead => _mapHead;
530+
PreviousKeyValue<K, V> get previousMapHead => _previousMapHead;
516531
ChangedKeyValue<K, V> get changesHead => _changesHead;
517532
AddedKeyValue<K, V> get additionsHead => _additionsHead;
518533
RemovedKeyValue<K, V> get removalsHead => _removalsHead;
@@ -521,6 +536,24 @@ class _MapChangeRecord<K, V> implements MapChangeRecord<K, V> {
521536
_changesHead != null ||
522537
_removalsHead != null;
523538

539+
_revertToPreviousState() {
540+
if (!isDirty) {
541+
return;
542+
}
543+
KeyValueRecord record, prev;
544+
int i = 0;
545+
for (record = _mapHead = _previousMapHead;
546+
record != null;
547+
prev = record, record = record._previousNextKeyValue, ++i) {
548+
record._currentValue = record._previousValue;
549+
if (prev != null) {
550+
prev._nextKeyValue = prev._previousNextKeyValue = record;
551+
}
552+
}
553+
prev._nextKeyValue = null;
554+
_undoDeltas();
555+
}
556+
524557
void forEachChange(void f(ChangedKeyValue<K, V> change)) {
525558
KeyValueRecord record = _changesHead;
526559
while (record != null) {
@@ -602,6 +635,18 @@ class _MapChangeRecord<K, V> implements MapChangeRecord<K, V> {
602635
}
603636

604637
void _reset() {
638+
if (isDirty) {
639+
// Record the state of the mapping for a possible _revertToPreviousState()
640+
for (KeyValueRecord record = _previousMapHead = _mapHead;
641+
record != null;
642+
record = record._nextKeyValue) {
643+
record._previousNextKeyValue = record._nextKeyValue;
644+
}
645+
_undoDeltas();
646+
}
647+
}
648+
649+
void _undoDeltas() {
605650
var record = _changesHead;
606651
while (record != null) {
607652
record._previousValue = record._currentValue;
@@ -748,14 +793,42 @@ class _MapChangeRecord<K, V> implements MapChangeRecord<K, V> {
748793
_changesTail = record;
749794
}
750795
}
796+
797+
String toString() {
798+
List itemsList = [], previousList = [], changesList = [], additionsList = [], removalsList = [];
799+
KeyValueRecord record;
800+
for (record = _mapHead; record != null; record = record._nextKeyValue) {
801+
itemsList.add("$record");
802+
}
803+
for (record = _previousMapHead; record != null; record = record._previousNextKeyValue) {
804+
previousList.add("$record");
805+
}
806+
for (record = _changesHead; record != null; record = record._nextChangedKeyValue) {
807+
changesList.add("$record");
808+
}
809+
for (record = _additionsHead; record != null; record = record._nextAddedKeyValue) {
810+
additionsList.add("$record");
811+
}
812+
for (record = _removalsHead; record != null; record = record._nextRemovedKeyValue) {
813+
removalsList.add("$record");
814+
}
815+
return """
816+
map: ${itemsList.join(", ")}
817+
previous: ${previousList.join(", ")}
818+
changes: ${changesList.join(", ")}
819+
additions: ${additionsList.join(", ")}
820+
removals: ${removalsList.join(", ")}
821+
""";
822+
}
751823
}
752824

753-
class KeyValueRecord<K, V> implements KeyValue<K, V>, AddedKeyValue<K, V>,
754-
RemovedKeyValue<K, V>, ChangedKeyValue<K, V> {
825+
class KeyValueRecord<K, V> implements KeyValue<K, V>, PreviousKeyValue<K, V>,
826+
AddedKeyValue<K, V>, RemovedKeyValue<K, V>, ChangedKeyValue<K, V> {
755827
final K key;
756828
V _previousValue, _currentValue;
757829

758830
KeyValueRecord<K, V> _nextKeyValue;
831+
KeyValueRecord<K, V> _previousNextKeyValue;
759832
KeyValueRecord<K, V> _nextAddedKeyValue;
760833
KeyValueRecord<K, V> _nextRemovedKeyValue, _prevRemovedKeyValue;
761834
KeyValueRecord<K, V> _nextChangedKeyValue;
@@ -765,12 +838,13 @@ class KeyValueRecord<K, V> implements KeyValue<K, V>, AddedKeyValue<K, V>,
765838
V get previousValue => _previousValue;
766839
V get currentValue => _currentValue;
767840
KeyValue<K, V> get nextKeyValue => _nextKeyValue;
841+
PreviousKeyValue<K, V> get previousNextKeyValue => _previousNextKeyValue;
768842
AddedKeyValue<K, V> get nextAddedKeyValue => _nextAddedKeyValue;
769843
RemovedKeyValue<K, V> get nextRemovedKeyValue => _nextRemovedKeyValue;
770844
ChangedKeyValue<K, V> get nextChangedKeyValue => _nextChangedKeyValue;
771845

772846
String toString() => _previousValue == _currentValue
773-
? key
847+
? "$key"
774848
: '$key[$_previousValue -> $_currentValue]';
775849
}
776850

@@ -785,16 +859,40 @@ class _CollectionChangeRecord<V> implements CollectionChangeRecord<V> {
785859
/** Used to keep track of removed items. */
786860
DuplicateMap _removedItems = new DuplicateMap();
787861

862+
ItemRecord<V> _previousCollectionHead;
788863
ItemRecord<V> _collectionHead, _collectionTail;
789864
ItemRecord<V> _additionsHead, _additionsTail;
790865
ItemRecord<V> _movesHead, _movesTail;
791866
ItemRecord<V> _removalsHead, _removalsTail;
792867

868+
CollectionChangeItem<V> get previousCollectionHead => _previousCollectionHead;
793869
CollectionChangeItem<V> get collectionHead => _collectionHead;
794870
CollectionChangeItem<V> get additionsHead => _additionsHead;
795871
CollectionChangeItem<V> get movesHead => _movesHead;
796872
CollectionChangeItem<V> get removalsHead => _removalsHead;
797873

874+
_revertToPreviousState() {
875+
if (!isDirty) {
876+
return;
877+
}
878+
_items.clear();
879+
ItemRecord<V> record, prev;
880+
int i = 0;
881+
for (record = _collectionHead = _previousCollectionHead;
882+
record != null;
883+
prev = record, record = record._previousNextRec, ++i) {
884+
record.currentIndex = record.previousIndex = i;
885+
record._prevRec = prev;
886+
if (prev != null) {
887+
prev._nextRec = prev._previousNextRec = record;
888+
}
889+
_items.put(record);
890+
}
891+
prev._nextRec = null;
892+
_collectionTail = prev;
893+
_undoDeltas();
894+
}
895+
798896
void forEachAddition(void f(AddedItem<V> addition)){
799897
ItemRecord record = _additionsHead;
800898
while (record != null) {
@@ -824,14 +922,15 @@ class _CollectionChangeRecord<V> implements CollectionChangeRecord<V> {
824922

825923
bool _check(Iterable collection) {
826924
_reset();
827-
ItemRecord record = _collectionHead;
828-
bool maybeDirty = false;
829925
if ((collection is UnmodifiableListView) &&
830-
identical(_iterable, collection)) {
926+
identical(_iterable, collection)) {
831927
// Short circuit and assume that the list has not been modified.
832928
return false;
833929
}
834930

931+
ItemRecord record = _collectionHead;
932+
bool maybeDirty = false;
933+
835934
if (collection is List) {
836935
List list = collection;
837936
_length = list.length;
@@ -873,6 +972,18 @@ class _CollectionChangeRecord<V> implements CollectionChangeRecord<V> {
873972
* removals).
874973
*/
875974
void _reset() {
975+
if (isDirty) {
976+
// Record the state of the collection for a possible _revertToPreviousState()
977+
for (ItemRecord record = _previousCollectionHead = _collectionHead;
978+
record != null;
979+
record = record._nextRec) {
980+
record._previousNextRec = record._nextRec;
981+
}
982+
_undoDeltas();
983+
}
984+
}
985+
986+
void _undoDeltas() {
876987
ItemRecord record;
877988

878989
record = _additionsHead;
@@ -1157,6 +1268,13 @@ class _CollectionChangeRecord<V> implements CollectionChangeRecord<V> {
11571268
record = record._nextRec;
11581269
}
11591270

1271+
var previous = [];
1272+
record = _previousCollectionHead;
1273+
while (record != null) {
1274+
previous.add(record);
1275+
record = record._previousNextRec;
1276+
}
1277+
11601278
var additions = [];
11611279
record = _additionsHead;
11621280
while (record != null) {
@@ -1180,24 +1298,28 @@ class _CollectionChangeRecord<V> implements CollectionChangeRecord<V> {
11801298

11811299
return """
11821300
collection: ${list.join(", ")}
1301+
previous: ${previous.join(", ")}
11831302
additions: ${additions.join(", ")}
11841303
moves: ${moves.join(", ")}
11851304
removals: ${removals.join(", ")}
11861305
""";
11871306
}
11881307
}
11891308

1190-
class ItemRecord<V> implements CollectionItem<V>, AddedItem<V>, MovedItem<V>,
1309+
class ItemRecord<V> implements PreviousCollectionItem<V>, CollectionItem<V>, AddedItem<V>, MovedItem<V>,
11911310
RemovedItem<V> {
11921311
int previousIndex = null;
11931312
int currentIndex = null;
11941313
V item = _INITIAL_;
11951314

1315+
1316+
ItemRecord<V> _previousNextRec;
11961317
ItemRecord<V> _prevRec, _nextRec;
11971318
ItemRecord<V> _prevDupRec, _nextDupRec;
11981319
ItemRecord<V> _prevRemovedRec, _nextRemovedRec;
11991320
ItemRecord<V> _nextAddedRec, _nextMovedRec;
12001321

1322+
PreviousCollectionItem<V> get previousNextItem => _previousNextRec;
12011323
CollectionItem<V> get nextCollectionItem => _nextRec;
12021324
RemovedItem<V> get nextRemovedItem => _nextRemovedRec;
12031325
AddedItem<V> get nextAddedItem => _nextAddedRec;
@@ -1314,7 +1436,11 @@ class DuplicateMap {
13141436
return record;
13151437
}
13161438

1439+
bool get isEmpty => map.isEmpty;
1440+
13171441
void clear() {
13181442
map.clear();
13191443
}
1444+
1445+
String toString() => "$runtimeType($map)";
13201446
}

0 commit comments

Comments
 (0)