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

Commit 8583d08

Browse files
committed
fix(change-detection): corrected adding group to sibling which had children
1 parent 65213c3 commit 8583d08

File tree

2 files changed

+124
-52
lines changed

2 files changed

+124
-52
lines changed

lib/change_detection/dirty_checking_change_detector.dart

Lines changed: 117 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,32 @@ class DirtyCheckingChangeDetectorGroup<H> implements ChangeDetectorGroup<H> {
5858

5959
/**
6060
* All records for group are kept together and are denoted by head/tail.
61+
* The [_recordHead]-[_recordTail] only include our own records. If you want
62+
* to see our childGroup records as well use
63+
* [_head]-[_childInclRecordTail].
6164
*/
62-
DirtyCheckingRecord _head, _tail;
65+
DirtyCheckingRecord _recordHead, _recordTail;
66+
67+
/**
68+
* Same as [_tail] but includes child-group records as well.
69+
*/
70+
DirtyCheckingRecord get _childInclRecordTail {
71+
DirtyCheckingChangeDetectorGroup tail = this, nextTail;
72+
while ((nextTail = tail._childTail) != null) {
73+
tail = nextTail;
74+
}
75+
return tail._recordTail;
76+
}
77+
78+
79+
DirtyCheckingChangeDetector get _root {
80+
var root = this;
81+
var next;
82+
while((next = root._parent) != null) {
83+
root = next;
84+
}
85+
return root is DirtyCheckingChangeDetector ? root : null;
86+
}
6387

6488
/**
6589
* ChangeDetectorGroup is organized hierarchically, a root group can have
@@ -71,16 +95,12 @@ class DirtyCheckingChangeDetectorGroup<H> implements ChangeDetectorGroup<H> {
7195
DirtyCheckingChangeDetectorGroup(this._parent, this._getterCache) {
7296
// we need to insert the marker record at the beginning.
7397
if (_parent == null) {
74-
_head = _marker;
75-
_tail = _marker;
98+
_recordHead = _marker;
99+
_recordTail = _marker;
76100
} else {
77-
// we need to find the tail of previous record
78-
// If we are first then it is the tail of the parent group
79-
// otherwise it is the tail of the previous group
80-
DirtyCheckingChangeDetectorGroup tail = _parent._childTail;
81-
_tail = (tail == null ? _parent : tail)._tail;
82-
// _recordAdd uses _tail from above.
83-
_head = _tail = _recordAdd(_marker);
101+
_recordTail = _parent._childInclRecordTail;
102+
// _recordAdd uses _recordTail from above.
103+
_recordHead = _recordTail = _recordAdd(_marker);
84104
}
85105
}
86106

@@ -89,17 +109,20 @@ class DirtyCheckingChangeDetectorGroup<H> implements ChangeDetectorGroup<H> {
89109
*/
90110
get count {
91111
int count = 0;
92-
DirtyCheckingRecord cursor = _head == _marker ?
93-
_head._nextWatch :
94-
_head;
95-
while (cursor != null) {
96-
count++;
97-
cursor = cursor._nextWatch;
112+
DirtyCheckingRecord cursor = _recordHead;
113+
DirtyCheckingRecord end = _childInclRecordTail;
114+
while(cursor != null) {
115+
if (cursor._mode != DirtyCheckingRecord._MODE_MARKER_) {
116+
count++;
117+
}
118+
if (cursor == end) break;
119+
cursor = cursor._nextRecord;
98120
}
99121
return count;
100122
}
101123

102124
WatchRecord<H> watch(Object object, String field, H handler) {
125+
assert(_root != null); // prove that we are not deleted connected;
103126
var getter = field == null ? null : _getterCache(field);
104127
return _recordAdd(new DirtyCheckingRecord(this, object, field, getter,
105128
handler));
@@ -109,6 +132,7 @@ class DirtyCheckingChangeDetectorGroup<H> implements ChangeDetectorGroup<H> {
109132
* Create a child [ChangeDetector] group.
110133
*/
111134
DirtyCheckingChangeDetectorGroup<H> newGroup() {
135+
assert(_root._assertRecordsOk());
112136
var child = new DirtyCheckingChangeDetectorGroup(this, _getterCache);
113137
if (_childHead == null) {
114138
_childHead = _childTail = child;
@@ -117,19 +141,27 @@ class DirtyCheckingChangeDetectorGroup<H> implements ChangeDetectorGroup<H> {
117141
_childTail._next = child;
118142
_childTail = child;
119143
}
144+
assert(_root._assertRecordsOk());
120145
return child;
121146
}
122147

123148
/**
124149
* Bulk remove all records.
125150
*/
126151
void remove() {
127-
DirtyCheckingRecord previousRecord = _head._prevWatch;
128-
var childTail = _childTail == null ? this : _childTail;
129-
DirtyCheckingRecord nextRecord = childTail._tail._nextWatch;
130-
131-
if (previousRecord != null) previousRecord._nextWatch = nextRecord;
132-
if (nextRecord != null) nextRecord._prevWatch = previousRecord;
152+
var root;
153+
assert((root = _root) != null);
154+
assert(root._assertRecordsOk());
155+
DirtyCheckingRecord prevRecord = _recordHead._prevRecord;
156+
DirtyCheckingRecord nextRecord = _childInclRecordTail._nextRecord;
157+
158+
if (prevRecord != null) prevRecord._nextRecord = nextRecord;
159+
if (nextRecord != null) nextRecord._prevRecord = prevRecord;
160+
161+
var cursor = _recordHead;
162+
while(cursor != nextRecord) {
163+
cursor = cursor._nextRecord;
164+
}
133165

134166
var prevGroup = _prev;
135167
var nextGroup = _next;
@@ -144,61 +176,65 @@ class DirtyCheckingChangeDetectorGroup<H> implements ChangeDetectorGroup<H> {
144176
} else {
145177
nextGroup._prev = prevGroup;
146178
}
179+
_parent = null;
180+
assert(root._assertRecordsOk());
147181
}
148182

149183
DirtyCheckingRecord _recordAdd(DirtyCheckingRecord record) {
150-
DirtyCheckingRecord previous = _tail;
151-
DirtyCheckingRecord next = previous == null ? null : previous._nextWatch;
184+
DirtyCheckingRecord previous = _recordTail;
185+
DirtyCheckingRecord next = previous == null ? null : previous._nextRecord;
152186

153-
record._nextWatch = next;
154-
record._prevWatch = previous;
187+
record._nextRecord = next;
188+
record._prevRecord = previous;
155189

156-
if (previous != null) previous._nextWatch = record;
157-
if (next != null) next._prevWatch = record;
190+
if (previous != null) previous._nextRecord = record;
191+
if (next != null) next._prevRecord = record;
158192

159-
_tail = record;
193+
_recordTail = record;
160194

161195
if (previous == _marker) _recordRemove(_marker);
162196

163197
return record;
164198
}
165199

166200
void _recordRemove(DirtyCheckingRecord record) {
167-
DirtyCheckingRecord previous = record._prevWatch;
168-
DirtyCheckingRecord next = record._nextWatch;
201+
DirtyCheckingRecord previous = record._prevRecord;
202+
DirtyCheckingRecord next = record._nextRecord;
169203

170-
if (record == _head && record == _tail) {
204+
if (record == _recordHead && record == _recordTail) {
171205
// we are the last one, must leave marker behind.
172-
_head = _tail = _marker;
173-
_marker._nextWatch = next;
174-
_marker._prevWatch = previous;
175-
if (previous != null) previous._nextWatch = _marker;
176-
if (next != null) next._prevWatch = _marker;
206+
_recordHead = _recordTail = _marker;
207+
_marker._nextRecord = next;
208+
_marker._prevRecord = previous;
209+
if (previous != null) previous._nextRecord = _marker;
210+
if (next != null) next._prevRecord = _marker;
177211
} else {
178-
if (record == _tail) _tail = previous;
179-
if (record == _head) _head = next;
180-
if (previous != null) previous._nextWatch = next;
181-
if (next != null) next._prevWatch = previous;
212+
if (record == _recordTail) _recordTail = previous;
213+
if (record == _recordHead) _recordHead = next;
214+
if (previous != null) previous._nextRecord = next;
215+
if (next != null) next._prevRecord = previous;
182216
}
183217
}
184218

185219
String toString() {
186220
var lines = [];
187221
if (_parent == null) {
188222
var allRecords = [];
189-
DirtyCheckingRecord record = _head;
190-
while (record != null) {
223+
DirtyCheckingRecord record = _recordHead;
224+
var includeChildrenTail = _childInclRecordTail;
225+
do {
191226
allRecords.add(record.toString());
192-
record = record._nextWatch;
227+
record = record._nextRecord;
193228
}
229+
while (record != includeChildrenTail);
194230
lines.add('FIELDS: ${allRecords.join(', ')}');
195231
}
196232

197233
var records = [];
198-
DirtyCheckingRecord record = _head;
199-
while (record != _tail) {
234+
DirtyCheckingRecord record = _recordHead;
235+
while (record != _recordTail) {
200236
records.add(record.toString());
201-
record = record._nextWatch;
237+
record = record._nextRecord;
202238
}
203239
records.add(record.toString());
204240

@@ -216,10 +252,39 @@ class DirtyCheckingChangeDetector<H> extends DirtyCheckingChangeDetectorGroup<H>
216252
implements ChangeDetector<H> {
217253
DirtyCheckingChangeDetector(GetterCache getterCache): super(null, getterCache);
218254

255+
DirtyCheckingChangeDetector get _root => this;
256+
257+
_assertRecordsOk() {
258+
var record = this._recordHead;
259+
var groups = [this];
260+
DirtyCheckingChangeDetectorGroup group;
261+
while(groups.isNotEmpty) {
262+
group = groups.removeAt(0);
263+
DirtyCheckingChangeDetectorGroup childGroup = group._childTail;
264+
while(childGroup != null) {
265+
groups.insert(0, childGroup);
266+
childGroup = childGroup._prev;
267+
}
268+
var groupRecord = group._recordHead;
269+
var groupLast = group._recordTail;
270+
while(true) {
271+
if (groupRecord == record) {
272+
record = record._nextRecord;
273+
} else {
274+
throw 'lost: $record found $groupRecord\n$this';
275+
}
276+
277+
if (groupRecord == groupLast) break;
278+
groupRecord = groupRecord._nextRecord;
279+
}
280+
}
281+
return true;
282+
}
283+
219284
DirtyCheckingRecord<H> collectChanges([EvalExceptionHandler exceptionHandler]) {
220285
DirtyCheckingRecord changeHead = null;
221286
DirtyCheckingRecord changeTail = null;
222-
DirtyCheckingRecord current = _head; // current index
287+
DirtyCheckingRecord current = _recordHead; // current index
223288

224289
while (current != null) {
225290
try {
@@ -237,7 +302,7 @@ class DirtyCheckingChangeDetector<H> extends DirtyCheckingChangeDetectorGroup<H>
237302
exceptionHandler(e, s);
238303
}
239304
}
240-
current = current._nextWatch;
305+
current = current._nextRecord;
241306
}
242307
if (changeTail != null) changeTail.nextChange = null;
243308
return changeHead;
@@ -278,8 +343,8 @@ class DirtyCheckingRecord<H> implements ChangeRecord<H>, WatchRecord<H> {
278343

279344
var previousValue;
280345
var currentValue;
281-
DirtyCheckingRecord<H> _nextWatch;
282-
DirtyCheckingRecord<H> _prevWatch;
346+
DirtyCheckingRecord<H> _nextRecord;
347+
DirtyCheckingRecord<H> _prevRecord;
283348
ChangeRecord<H> nextChange;
284349
var _object;
285350
InstanceMirror _instanceMirror;
@@ -395,7 +460,7 @@ class DirtyCheckingRecord<H> implements ChangeRecord<H>, WatchRecord<H> {
395460
_group._recordRemove(this);
396461
}
397462

398-
String toString() => '${_MODE_NAMES[_mode]}[$field]';
463+
String toString() => '${_MODE_NAMES[_mode]}[$field]{$hashCode}';
399464
}
400465

401466
final Object _INITIAL_ = new Object();

test/change_detection/dirty_checking_change_detector_spec.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,13 @@ main() => describe('DirtyCheckingChangeDetector', () {
152152
obj['a'] = obj['b'] = 4;
153153
expect(detector.collectChanges(), toEqualChanges(['a', 'b']));
154154
});
155+
156+
it('should properly add children', () {
157+
var a = detector.newGroup();
158+
var aChild = a.newGroup();
159+
var b = detector.newGroup();
160+
expect(detector.collectChanges).not.toThrow();
161+
});
155162
});
156163

157164
describe('list watching', () {

0 commit comments

Comments
 (0)