Skip to content

Commit 0a766f4

Browse files
vicbrkirov
authored andcommitted
feat(Change Detection): Implement map changes
1 parent 1bd304e commit 0a766f4

File tree

6 files changed

+509
-4
lines changed

6 files changed

+509
-4
lines changed

modules/change_detection/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ dependencies:
77
dev_dependencies:
88
test_lib:
99
path: ../test_lib
10-
guinness: ">=0.1.16 <0.2.0"
10+
guinness: ">=0.1.16 <0.2.0"

modules/change_detection/src/collection_changes.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export class CollectionChanges {
5454

5555
forEachItem(fn:Function) {
5656
var record:CollectionChangeRecord;
57-
for (record = this._itHead; record !== null; record = record.next) {
57+
for (record = this._itHead; record !== null; record = record._next) {
5858
fn(record);
5959
}
6060
}
@@ -348,7 +348,7 @@ export class CollectionChanges {
348348
}
349349

350350
_remove(record:CollectionChangeRecord):CollectionChangeRecord {
351-
this._addToRemovals(this._unlink(record));
351+
return this._addToRemovals(this._unlink(record));
352352
}
353353

354354
_unlink(record:CollectionChangeRecord):CollectionChangeRecord {
Lines changed: 353 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,353 @@
1+
import {ListWrapper, MapWrapper} from 'facade/collection';
2+
3+
import {stringify, looseIdentical} from 'facade/lang';
4+
5+
export class MapChanges {
6+
// todo(vicb) add as fields when supported
7+
/*
8+
final _records = new HashMap<dynamic, MapKeyValue>();
9+
Map _map;
10+
11+
Map get map => _map;
12+
13+
MapKeyValue<K, V> _mapHead;
14+
MapKeyValue<K, V> _previousMapHead;
15+
MapKeyValue<K, V> _changesHead, _changesTail;
16+
MapKeyValue<K, V> _additionsHead, _additionsTail;
17+
MapKeyValue<K, V> _removalsHead, _removalsTail;
18+
*/
19+
20+
constructor() {
21+
this._records = MapWrapper.create();
22+
this._map = null;
23+
this._mapHead = null;
24+
this._previousMapHead = null;
25+
this._changesHead = null;
26+
this._changesTail = null;
27+
this._additionsHead = null;
28+
this._additionsTail = null;
29+
this._removalsHead = null;
30+
this._removalsTail = null;
31+
}
32+
33+
get isDirty():boolean {
34+
return this._additionsHead !== null ||
35+
this._changesHead !== null ||
36+
this._removalsHead !== null;
37+
}
38+
39+
forEachItem(fn:Function) {
40+
var record:MapChangeRecord;
41+
for (record = this._mapHead; record !== null; record = record._next) {
42+
fn(record);
43+
}
44+
}
45+
46+
forEachPreviousItem(fn:Function) {
47+
var record:MapChangeRecord;
48+
for (record = this._previousMapHead; record !== null; record = record._nextPrevious) {
49+
fn(record);
50+
}
51+
}
52+
53+
forEachChangedItem(fn:Function) {
54+
var record:MapChangeRecord;
55+
for (record = this._changesHead; record !== null; record = record._nextChanged) {
56+
fn(record);
57+
}
58+
}
59+
60+
forEachAddedItem(fn:Function){
61+
var record:MapChangeRecord;
62+
for (record = this._additionsHead; record !== null; record = record._nextAdded) {
63+
fn(record);
64+
}
65+
}
66+
67+
forEachRemovedItem(fn:Function){
68+
var record:MapChangeRecord;
69+
for (record = this._removalsHead; record !== null; record = record._nextRemoved) {
70+
fn(record);
71+
}
72+
}
73+
74+
check(map):boolean {
75+
this._reset();
76+
this._map = map;
77+
var records = this._records;
78+
var oldSeqRecord:MapChangeRecord = this._mapHead;
79+
var lastOldSeqRecord:MapChangeRecord = null;
80+
var lastNewSeqRecord:MapChangeRecord = null;
81+
var seqChanged:boolean = false;
82+
83+
MapWrapper.forEach(map, (value, key) => {
84+
var newSeqRecord;
85+
if (oldSeqRecord !== null && key === oldSeqRecord.key) {
86+
newSeqRecord = oldSeqRecord;
87+
if (!looseIdentical(value, oldSeqRecord._currentValue)) {
88+
oldSeqRecord._previousValue = oldSeqRecord._currentValue;
89+
oldSeqRecord._currentValue = value;
90+
this._addToChanges(oldSeqRecord);
91+
}
92+
} else {
93+
seqChanged = true;
94+
if (oldSeqRecord !== null) {
95+
oldSeqRecord._next = null;
96+
this._removeFromSeq(lastOldSeqRecord, oldSeqRecord);
97+
this._addToRemovals(oldSeqRecord);
98+
}
99+
if (MapWrapper.contains(records, key)) {
100+
newSeqRecord = MapWrapper.get(records, key);
101+
} else {
102+
newSeqRecord = new MapChangeRecord(key);
103+
MapWrapper.set(records, key, newSeqRecord);
104+
newSeqRecord._currentValue = value;
105+
this._addToAdditions(newSeqRecord);
106+
}
107+
}
108+
109+
if (seqChanged) {
110+
if (this._isInRemovals(newSeqRecord)) {
111+
this._removeFromRemovals(newSeqRecord);
112+
}
113+
if (lastNewSeqRecord == null) {
114+
this._mapHead = newSeqRecord;
115+
} else {
116+
lastNewSeqRecord._next = newSeqRecord;
117+
}
118+
}
119+
lastOldSeqRecord = oldSeqRecord;
120+
lastNewSeqRecord = newSeqRecord;
121+
oldSeqRecord = oldSeqRecord === null ? null : oldSeqRecord._next;
122+
});
123+
this._truncate(lastOldSeqRecord, oldSeqRecord);
124+
return this.isDirty;
125+
}
126+
127+
_reset() {
128+
if (this.isDirty) {
129+
var record:MapChangeRecord;
130+
// Record the state of the mapping
131+
for (record = this._previousMapHead = this._mapHead;
132+
record !== null;
133+
record = record._next) {
134+
record._nextPrevious = record._next;
135+
}
136+
137+
for (record = this._changesHead; record !== null; record = record._nextChanged) {
138+
record._previousValue = record._currentValue;
139+
}
140+
141+
for (record = this._additionsHead; record != null; record = record._nextAdded) {
142+
record._previousValue = record._currentValue;
143+
}
144+
145+
// todo(vicb) once assert is supported
146+
//assert(() {
147+
// var r = _changesHead;
148+
// while (r != null) {
149+
// var nextRecord = r._nextChanged;
150+
// r._nextChanged = null;
151+
// r = nextRecord;
152+
// }
153+
//
154+
// r = _additionsHead;
155+
// while (r != null) {
156+
// var nextRecord = r._nextAdded;
157+
// r._nextAdded = null;
158+
// r = nextRecord;
159+
// }
160+
//
161+
// r = _removalsHead;
162+
// while (r != null) {
163+
// var nextRecord = r._nextRemoved;
164+
// r._nextRemoved = null;
165+
// r = nextRecord;
166+
// }
167+
//
168+
// return true;
169+
//});
170+
this._changesHead = this._changesTail = null;
171+
this._additionsHead = this._additionsTail = null;
172+
this._removalsHead = this._removalsTail = null;
173+
}
174+
}
175+
176+
_truncate(lastRecord:MapChangeRecord, record:MapChangeRecord) {
177+
while (record !== null) {
178+
if (lastRecord === null) {
179+
this._mapHead = null;
180+
} else {
181+
lastRecord._next = null;
182+
}
183+
var nextRecord = record._next;
184+
// todo(vicb) assert
185+
//assert((() {
186+
// record._next = null;
187+
// return true;
188+
//}));
189+
this._addToRemovals(record);
190+
lastRecord = record;
191+
record = nextRecord;
192+
}
193+
194+
for (var rec:MapChangeRecord = this._removalsHead; rec !== null; rec = rec._nextRemoved) {
195+
rec._previousValue = rec._currentValue;
196+
rec._currentValue = null;
197+
MapWrapper.delete(this._records, rec.key);
198+
}
199+
}
200+
201+
_isInRemovals(record:MapChangeRecord) {
202+
return record === this._removalsHead ||
203+
record._nextRemoved !== null ||
204+
record._prevRemoved !== null;
205+
}
206+
207+
_addToRemovals(record:MapChangeRecord) {
208+
// todo(vicb) assert
209+
//assert(record._next == null);
210+
//assert(record._nextAdded == null);
211+
//assert(record._nextChanged == null);
212+
//assert(record._nextRemoved == null);
213+
//assert(record._prevRemoved == null);
214+
if (this._removalsHead === null) {
215+
this._removalsHead = this._removalsTail = record;
216+
} else {
217+
this._removalsTail._nextRemoved = record;
218+
record._prevRemoved = this._removalsTail;
219+
this._removalsTail = record;
220+
}
221+
}
222+
223+
_removeFromSeq(prev:MapChangeRecord, record:MapChangeRecord) {
224+
var next = record._next;
225+
if (prev === null) {
226+
this._mapHead = next;
227+
} else {
228+
prev._next = next;
229+
}
230+
// todo(vicb) assert
231+
//assert((() {
232+
// record._next = null;
233+
// return true;
234+
//})());
235+
}
236+
237+
_removeFromRemovals(record:MapChangeRecord) {
238+
// todo(vicb) assert
239+
//assert(record._next == null);
240+
//assert(record._nextAdded == null);
241+
//assert(record._nextChanged == null);
242+
243+
var prev = record._prevRemoved;
244+
var next = record._nextRemoved;
245+
if (prev === null) {
246+
this._removalsHead = next;
247+
} else {
248+
prev._nextRemoved = next;
249+
}
250+
if (next === null) {
251+
this._removalsTail = prev;
252+
} else {
253+
next._prevRemoved = prev;
254+
}
255+
record._prevRemoved = record._nextRemoved = null;
256+
}
257+
258+
_addToAdditions(record:MapChangeRecord) {
259+
// todo(vicb): assert
260+
//assert(record._next == null);
261+
//assert(record._nextAdded == null);
262+
//assert(record._nextChanged == null);
263+
//assert(record._nextRemoved == null);
264+
//assert(record._prevRemoved == null);
265+
if (this._additionsHead === null) {
266+
this._additionsHead = this._additionsTail = record;
267+
} else {
268+
this._additionsTail._nextAdded = record;
269+
this._additionsTail = record;
270+
}
271+
}
272+
273+
_addToChanges(record:MapChangeRecord) {
274+
// todo(vicb) assert
275+
//assert(record._nextAdded == null);
276+
//assert(record._nextChanged == null);
277+
//assert(record._nextRemoved == null);
278+
//assert(record._prevRemoved == null);
279+
if (this._changesHead === null) {
280+
this._changesHead = this._changesTail = record;
281+
} else {
282+
this._changesTail._nextChanged = record;
283+
this._changesTail = record;
284+
}
285+
}
286+
287+
toString():string {
288+
var items = [];
289+
var previous = [];
290+
var changes = [];
291+
var additions = [];
292+
var removals = [];
293+
var record:MapChangeRecord;
294+
295+
for (record = this._mapHead; record !== null; record = record._next) {
296+
ListWrapper.push(items, stringify(record));
297+
}
298+
for (record = this._previousMapHead; record !== null; record = record._nextPrevious) {
299+
ListWrapper.push(previous, stringify(record));
300+
}
301+
for (record = this._changesHead; record !== null; record = record._nextChanged) {
302+
ListWrapper.push(changes, stringify(record));
303+
}
304+
for (record = this._additionsHead; record !== null; record = record._nextAdded) {
305+
ListWrapper.push(additions, stringify(record));
306+
}
307+
for (record = this._removalsHead; record !== null; record = record._nextRemoved) {
308+
ListWrapper.push(removals, stringify(record));
309+
}
310+
311+
return "map: " + items.join(', ') + "\n" +
312+
"previous: " + previous.join(', ') + "\n" +
313+
"additions: " + additions.join(', ') + "\n" +
314+
"changes: " + changes.join(', ') + "\n" +
315+
"removals: " + removals.join(', ') + "\n";
316+
}
317+
}
318+
319+
export class MapChangeRecord {
320+
// todo(vicb) add as fields
321+
//final K key;
322+
//V _previousValue, _currentValue;
323+
//
324+
//V get previousValue => _previousValue;
325+
//V get currentValue => _currentValue;
326+
//
327+
//MapKeyValue<K, V> _nextPrevious;
328+
//MapKeyValue<K, V> _next;
329+
//MapKeyValue<K, V> _nextAdded;
330+
//MapKeyValue<K, V> _nextRemoved, _prevRemoved;
331+
//MapKeyValue<K, V> _nextChanged;
332+
333+
constructor(key) {
334+
this.key = key;
335+
this._previousValue = null;
336+
this._currentValue = null;
337+
338+
this._nextPrevious = null;
339+
this._next = null;
340+
this._nextAdded = null;
341+
this._nextRemoved = null;
342+
this._prevRemoved = null;
343+
this._nextChanged = null;
344+
}
345+
346+
toString():string {
347+
return looseIdentical(this._previousValue, this._currentValue) ?
348+
stringify(this.key) :
349+
(stringify(this.key) + '[' + stringify(this._previousValue) + '->' +
350+
stringify(this._currentValue) + ']');
351+
}
352+
353+
}

0 commit comments

Comments
 (0)