Skip to content

Commit daf8f72

Browse files
committed
feat(ChangeDetector): implement enabling/disabling records
1 parent 8dfbc24 commit daf8f72

File tree

5 files changed

+238
-26
lines changed

5 files changed

+238
-26
lines changed

modules/change_detection/src/change_detector.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {ProtoWatchGroup, WatchGroup} from './watch_group';
22
import {ProtoRecord, Record} from './record';
3-
import {FIELD, int} from 'facade/lang';
3+
import {FIELD, int, isPresent} from 'facade/lang';
44
export * from './record';
55
export * from './watch_group'
66

@@ -12,11 +12,10 @@ export class ChangeDetector {
1212
}
1313

1414
detectChanges():int {
15-
var record:Record = this._rootWatchGroup.headRecord;
1615
var count:int = 0;
17-
for (record = this._rootWatchGroup.headRecord;
18-
record != null;
19-
record = record.next) {
16+
for (var record = this._rootWatchGroup.headEnabledRecord;
17+
isPresent(record);
18+
record = record.nextEnabled) {
2019
if (record.check()) {
2120
count++;
2221
}

modules/change_detection/src/record.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,12 @@ export class Record {
6565
@FIELD('final protoRecord:ProtoRecord')
6666
@FIELD('next:Record')
6767
@FIELD('prev:Record')
68+
69+
/// This reference can change.
70+
@FIELD('nextEnabled:Record')
71+
72+
/// This reference can change.
73+
@FIELD('prevEnabled:Record')
6874
@FIELD('dest:Record')
6975

7076
@FIELD('previousValue')
@@ -86,6 +92,9 @@ export class Record {
8692

8793
this.next = null;
8894
this.prev = null;
95+
this.nextEnabled = null;
96+
this.prevEnabled = null;
97+
this.disabled = false;
8998
this.dest = null;
9099

91100
this.previousValue = null;
@@ -163,9 +172,11 @@ export class Record {
163172
return FunctionWrapper.apply(this.context, this.args);
164173

165174
case MODE_STATE_INVOKE_PURE_FUNCTION:
175+
this.watchGroup.disableRecord(this);
166176
return FunctionWrapper.apply(this.funcOrValue, this.args);
167177

168178
case MODE_STATE_CONST:
179+
this.watchGroup.disableRecord(this);
169180
return this.funcOrValue;
170181

171182
case MODE_STATE_MARKER:
@@ -184,10 +195,12 @@ export class Record {
184195

185196
updateArg(value, position:int) {
186197
this.args[position] = value;
198+
this.watchGroup.enableRecord(this);
187199
}
188200

189201
updateContext(value) {
190202
this.context = value;
203+
this.watchGroup.enableRecord(this);
191204
}
192205
}
193206

modules/change_detection/src/watch_group.js

Lines changed: 65 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -56,21 +56,11 @@ export class ProtoWatchGroup {
5656
}
5757

5858
_createRecords(watchGroup:WatchGroup, formatters:Map) {
59-
var tail, prevRecord;
60-
watchGroup.headRecord = tail = new Record(watchGroup, this.headRecord, formatters);
61-
this.headRecord.recordInConstruction = watchGroup.headRecord;
62-
63-
for (var proto = this.headRecord.next; proto != null; proto = proto.next) {
64-
prevRecord = tail;
65-
66-
tail = new Record(watchGroup, proto, formatters);
67-
proto.recordInConstruction = tail;
68-
69-
tail.prev = prevRecord;
70-
prevRecord.next = tail;
59+
for (var proto = this.headRecord; proto != null; proto = proto.next) {
60+
var record = new Record(watchGroup, proto, formatters);
61+
proto.recordInConstruction = record;
62+
watchGroup.addRecord(record);
7163
}
72-
73-
watchGroup.tailRecord = tail;
7464
}
7565

7666
_setDestination() {
@@ -95,9 +85,70 @@ export class WatchGroup {
9585
this.dispatcher = dispatcher;
9686
this.headRecord = null;
9787
this.tailRecord = null;
88+
this.headEnabledRecord = null;
89+
this.tailEnabledRecord = null;
9890
this.context = null;
9991
}
10092

93+
addRecord(record:Record) {
94+
if (isPresent(this.tailRecord)) {
95+
this.tailRecord.next = record;
96+
this.tailRecord.nextEnabled = record;
97+
record.prev = this.tailRecord;
98+
record.prevEnabled = this.tailRecord;
99+
this.tailRecord = this.tailEnabledRecord = record;
100+
101+
} else {
102+
this.headRecord = this.tailRecord = record;
103+
this.headEnabledRecord = this.tailEnabledRecord = record;
104+
}
105+
}
106+
107+
disableRecord(record:Record) {
108+
var prev = record.prevEnabled;
109+
var next = record.nextEnabled;
110+
111+
record.disabled = true;
112+
113+
if (isPresent(prev)) {
114+
prev.nextEnabled = next;
115+
} else {
116+
this.headEnabledRecord = next;
117+
}
118+
119+
if (isPresent(next)) {
120+
next.prevEnabled = prev;
121+
} else {
122+
this.tailEnabledRecord = prev;
123+
}
124+
}
125+
126+
enableRecord(record:Record) {
127+
if (!record.disabled) return;
128+
129+
var prev = record.prev;
130+
while (prev != null && prev.disabled) prev = prev.prev;
131+
132+
var next = record.next;
133+
while (next != null && next.disabled) next = next.next;
134+
135+
record.disabled = false;
136+
record.prevEnabled = prev;
137+
record.nextEnabled = next;
138+
139+
if (isPresent(prev)) {
140+
prev.nextEnabled = record;
141+
} else {
142+
this.headEnabledRecord = record;
143+
}
144+
145+
if (isPresent(next)) {
146+
next.prevEnabled = record;
147+
} else {
148+
this.tailEnabledRecord = record;
149+
}
150+
}
151+
101152
insertChildGroup(newChild:WatchGroup, insertAfter:WatchGroup) {
102153
throw 'not implemented';
103154
}

modules/change_detection/test/change_detector_spec.js

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -152,13 +152,36 @@ export function main() {
152152
expect(executeWatch('m', '1 > 2 ? 1 : 2')).toEqual(['m=2']);
153153
});
154154

155-
it("should support formatters", () => {
156-
var formatters = MapWrapper.createFromPairs([
157-
["uppercase", (v) => v.toUpperCase()],
158-
["wrap", (v, before, after) => `${before}${v}${after}`]
159-
]);
160-
expect(executeWatch('str', '"aBc" | uppercase', null, formatters)).toEqual(['str=ABC']);
161-
expect(executeWatch('str', '"b" | wrap:"a":"c"', null, formatters)).toEqual(['str=abc']);
155+
describe("formatters", () => {
156+
it("should support formatters", () => {
157+
var formatters = MapWrapper.createFromPairs([
158+
['uppercase', (v) => v.toUpperCase()],
159+
['wrap', (v, before, after) => `${before}${v}${after}`]]);
160+
expect(executeWatch('str', '"aBc" | uppercase', null, formatters)).toEqual(['str=ABC']);
161+
expect(executeWatch('str', '"b" | wrap:"a":"c"', null, formatters)).toEqual(['str=abc']);
162+
});
163+
164+
it("should rerun formatters only when arguments change", () => {
165+
var counter = 0;
166+
var formatters = MapWrapper.createFromPairs([
167+
['formatter', (_) => {counter += 1; return 'value'}]
168+
]);
169+
170+
var person = new Person('Jim');
171+
172+
var c = createChangeDetector('formatter', 'name | formatter', person, formatters);
173+
var cd = c['changeDetector'];
174+
175+
cd.detectChanges();
176+
expect(counter).toEqual(1);
177+
178+
cd.detectChanges();
179+
expect(counter).toEqual(1);
180+
181+
person.name = 'bob';
182+
cd.detectChanges();
183+
expect(counter).toEqual(2);
184+
});
162185
});
163186
});
164187
});
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import {ddescribe, describe, it, iit, xit, expect} from 'test_lib/test_lib';
2+
3+
import {List, ListWrapper, MapWrapper} from 'facade/collection';
4+
import {Parser} from 'change_detection/parser/parser';
5+
import {Lexer} from 'change_detection/parser/lexer';
6+
import {ClosureMap} from 'change_detection/parser/closure_map';
7+
8+
import {
9+
ChangeDetector,
10+
ProtoWatchGroup,
11+
WatchGroup,
12+
WatchGroupDispatcher,
13+
ProtoRecord
14+
} from 'change_detection/change_detector';
15+
16+
import {Record} from 'change_detection/record';
17+
18+
export function main() {
19+
function createRecord(wg) {
20+
return new Record(wg, new ProtoRecord(null, null, null, null, null), null);
21+
}
22+
23+
describe('watch group', () => {
24+
describe("adding records", () => {
25+
it("should add a record", () => {
26+
var wg = new WatchGroup(null, null);
27+
var record = createRecord(wg);
28+
29+
wg.addRecord(record);
30+
31+
expect(wg.headRecord).toBe(record);
32+
expect(wg.tailRecord).toBe(record);
33+
expect(wg.headEnabledRecord).toBe(record);
34+
expect(wg.tailEnabledRecord).toBe(record);
35+
});
36+
37+
it("should add multiple records", () => {
38+
var wg = new WatchGroup(null, null);
39+
var record1 = createRecord(wg);
40+
var record2 = createRecord(wg);
41+
42+
wg.addRecord(record1);
43+
wg.addRecord(record2);
44+
45+
expect(wg.headRecord).toBe(record1);
46+
expect(wg.tailRecord).toBe(record2);
47+
48+
expect(wg.headEnabledRecord).toBe(record1);
49+
expect(wg.tailEnabledRecord).toBe(record2);
50+
51+
expect(record1.next).toBe(record2);
52+
expect(record2.prev).toBe(record1);
53+
});
54+
});
55+
56+
describe("enabling/disabling records", () => {
57+
it("should disable a single record", () => {
58+
var wg = new WatchGroup(null, null);
59+
var record = createRecord(wg);
60+
wg.addRecord(record);
61+
62+
wg.disableRecord(record);
63+
64+
expect(wg.headEnabledRecord).toBeNull();
65+
expect(wg.tailEnabledRecord).toBeNull();
66+
});
67+
68+
it("should enable a single record", () => {
69+
var wg = new WatchGroup(null, null);
70+
var record = createRecord(wg);
71+
wg.addRecord(record);
72+
wg.disableRecord(record);
73+
74+
wg.enableRecord(record);
75+
76+
expect(wg.headEnabledRecord).toBe(record);
77+
expect(wg.tailEnabledRecord).toBe(record);
78+
});
79+
80+
it("should disable a record", () => {
81+
var wg = new WatchGroup(null, null);
82+
var record1 = createRecord(wg);
83+
var record2 = createRecord(wg);
84+
var record3 = createRecord(wg);
85+
var record4 = createRecord(wg);
86+
wg.addRecord(record1);
87+
wg.addRecord(record2);
88+
wg.addRecord(record3);
89+
wg.addRecord(record4);
90+
91+
wg.disableRecord(record2);
92+
wg.disableRecord(record3);
93+
94+
expect(record2.disabled).toBeTruthy();
95+
96+
expect(wg.headEnabledRecord).toBe(record1);
97+
expect(wg.tailEnabledRecord).toBe(record4);
98+
99+
expect(record1.nextEnabled).toBe(record4);
100+
expect(record4.prevEnabled).toBe(record1);
101+
});
102+
103+
it("should enable a record", () => {
104+
var wg = new WatchGroup(null, null);
105+
var record1 = createRecord(wg);
106+
var record2 = createRecord(wg);
107+
var record3 = createRecord(wg);
108+
var record4 = createRecord(wg);
109+
wg.addRecord(record1);
110+
wg.addRecord(record2);
111+
wg.addRecord(record3);
112+
wg.addRecord(record4);
113+
wg.disableRecord(record2);
114+
wg.disableRecord(record3);
115+
116+
wg.enableRecord(record2);
117+
wg.enableRecord(record3);
118+
119+
expect(record1.nextEnabled).toBe(record2);
120+
expect(record2.nextEnabled).toBe(record3);
121+
expect(record3.nextEnabled).toBe(record4);
122+
expect(record4.prevEnabled).toBe(record3);
123+
});
124+
})
125+
});
126+
}

0 commit comments

Comments
 (0)