Skip to content

Commit 2793d47

Browse files
committed
feat(change_detection): change proto change detectors to coalesce records
1 parent 5367749 commit 2793d47

File tree

7 files changed

+206
-9
lines changed

7 files changed

+206
-9
lines changed

modules/change_detection/src/change_detection_jit_generator.es6

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ export class ChangeDetectorJITGenerator {
296296

297297
switch (r.mode) {
298298
case RECORD_TYPE_SELF:
299-
throw new BaseException("Cannot evaluate self");
299+
return assignmentTemplate(newValue, context);
300300

301301
case RECORD_TYPE_CONST:
302302
return `${newValue} = ${this.genLiteral(r.funcOrValue)}`;
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import {isPresent} from 'facade/lang';
2+
import {List, ListWrapper, Map, MapWrapper} from 'facade/collection';
3+
import {RECORD_TYPE_SELF, ProtoRecord} from './proto_change_detector';
4+
5+
/**
6+
* Removes "duplicate" records. It assuming that record evaluation does not
7+
* have side-effects.
8+
*
9+
* Records that are not last in bindings are removed and all the indices
10+
* of the records that depend on them are updated.
11+
*
12+
* Records that are last in bindings CANNOT be removed, and instead are
13+
* replaced with very cheap SELF records.
14+
*/
15+
export function coalesce(records:List<ProtoRecord>):List<ProtoRecord> {
16+
var res = ListWrapper.create();
17+
var indexMap = MapWrapper.create();
18+
19+
for (var i = 0; i < records.length; ++i) {
20+
var r = records[i];
21+
var record = _replaceIndices(r, res.length + 1, indexMap);
22+
var matchingRecord = _findMatching(record, res);
23+
24+
if (isPresent(matchingRecord) && record.lastInBinding) {
25+
ListWrapper.push(res, _selfRecord(record, matchingRecord.selfIndex, res.length + 1));
26+
MapWrapper.set(indexMap, r.selfIndex, matchingRecord.selfIndex);
27+
28+
} else if (isPresent(matchingRecord) && !record.lastInBinding) {
29+
MapWrapper.set(indexMap, r.selfIndex, matchingRecord.selfIndex);
30+
31+
} else {
32+
ListWrapper.push(res, record);
33+
MapWrapper.set(indexMap, r.selfIndex, record.selfIndex);
34+
}
35+
}
36+
37+
return res;
38+
}
39+
40+
function _selfRecord(r:ProtoRecord, contextIndex:number, selfIndex:number):ProtoRecord {
41+
return new ProtoRecord(
42+
RECORD_TYPE_SELF,
43+
"self",
44+
null,
45+
[],
46+
r.fixedArgs,
47+
contextIndex,
48+
selfIndex,
49+
r.bindingMemento,
50+
r.groupMemento,
51+
r.expressionAsString,
52+
r.lastInBinding,
53+
r.lastInGroup
54+
);
55+
}
56+
57+
function _findMatching(r:ProtoRecord, rs:List<ProtoRecord>){
58+
return ListWrapper.find(rs, (rr) =>
59+
rr.mode === r.mode &&
60+
rr.funcOrValue === r.funcOrValue &&
61+
rr.contextIndex === r.contextIndex &&
62+
ListWrapper.equals(rr.args, r.args)
63+
);
64+
}
65+
66+
function _replaceIndices(r:ProtoRecord, selfIndex:number, indexMap:Map) {
67+
var args = ListWrapper.map(r.args, (a) => _map(indexMap, a));
68+
var contextIndex = _map(indexMap, r.contextIndex);
69+
return new ProtoRecord(
70+
r.mode,
71+
r.name,
72+
r.funcOrValue,
73+
args,
74+
r.fixedArgs,
75+
contextIndex,
76+
selfIndex,
77+
r.bindingMemento,
78+
r.groupMemento,
79+
r.expressionAsString,
80+
r.lastInBinding,
81+
r.lastInGroup
82+
);
83+
}
84+
85+
function _map(indexMap:Map, value:number) {
86+
var r = MapWrapper.get(indexMap, value)
87+
return isPresent(r) ? r : value;
88+
}

modules/change_detection/src/dynamic_change_detector.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
100100
_calculateCurrValue(proto:ProtoRecord) {
101101
switch (proto.mode) {
102102
case RECORD_TYPE_SELF:
103-
throw new BaseException("Cannot evaluate self");
103+
return this._readContext(proto);
104104

105105
case RECORD_TYPE_CONST:
106106
return proto.funcOrValue;

modules/change_detection/src/proto_change_detector.js

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {ChangeDetectorJITGenerator} from './change_detection_jit_generator';
3131

3232
import {ArrayChanges} from './array_changes';
3333
import {KeyValueChanges} from './keyvalue_changes';
34+
import {coalesce} from './coalesce';
3435

3536
export const RECORD_TYPE_SELF = 0;
3637
export const RECORD_TYPE_CONST = 1;
@@ -66,7 +67,9 @@ export class ProtoRecord {
6667
selfIndex:number,
6768
bindingMemento:any,
6869
groupMemento:any,
69-
expressionAsString:string) {
70+
expressionAsString:string,
71+
lastInBinding:boolean,
72+
lastInGroup:boolean) {
7073

7174
this.mode = mode;
7275
this.name = name;
@@ -77,8 +80,8 @@ export class ProtoRecord {
7780
this.selfIndex = selfIndex;
7881
this.bindingMemento = bindingMemento;
7982
this.groupMemento = groupMemento;
80-
this.lastInBinding = false;
81-
this.lastInGroup = false;
83+
this.lastInBinding = lastInBinding;
84+
this.lastInGroup = lastInGroup;
8285
this.expressionAsString = expressionAsString;
8386
}
8487
}
@@ -91,9 +94,11 @@ export class ProtoChangeDetector {
9194
}
9295

9396
export class DynamicProtoChangeDetector extends ProtoChangeDetector {
97+
_records:List<ProtoRecord>;
9498
_recordBuilder:ProtoRecordBuilder;
9599

96100
constructor() {
101+
this._records = null;
97102
this._recordBuilder = new ProtoRecordBuilder();
98103
}
99104

@@ -102,8 +107,15 @@ export class DynamicProtoChangeDetector extends ProtoChangeDetector {
102107
}
103108

104109
instantiate(dispatcher:any, formatters:Map) {
105-
var records = this._recordBuilder.records;
106-
return new DynamicChangeDetector(dispatcher, formatters, records);
110+
this._createRecordsIfNecessary();
111+
return new DynamicChangeDetector(dispatcher, formatters, this._records);
112+
}
113+
114+
_createRecordsIfNecessary() {
115+
if (isBlank(this._records)) {
116+
var records = this._recordBuilder.records;
117+
this._records = coalesce(records);
118+
}
107119
}
108120
}
109121

@@ -113,6 +125,7 @@ export class JitProtoChangeDetector extends ProtoChangeDetector {
113125
_recordBuilder:ProtoRecordBuilder;
114126

115127
constructor() {
128+
this._factory = null;
116129
this._recordBuilder = new ProtoRecordBuilder();
117130
}
118131

@@ -128,7 +141,7 @@ export class JitProtoChangeDetector extends ProtoChangeDetector {
128141
_createFactoryIfNecessary() {
129142
if (isBlank(this._factory)) {
130143
var c = _jitProtoChangeDetectorClassCounter++;
131-
var records = this._recordBuilder.records;
144+
var records = coalesce(this._recordBuilder.records);
132145
var typeName = `ChangeDetector${c}`;
133146
this._factory = new ChangeDetectorJITGenerator(typeName, records).generate();
134147
}
@@ -273,7 +286,7 @@ class _ConvertAstIntoProtoRecords {
273286
var selfIndex = ++ this.contextIndex;
274287
ListWrapper.push(this.protoRecords,
275288
new ProtoRecord(type, name, funcOrValue, args, fixedArgs, context, selfIndex,
276-
this.bindingMemento, this.groupMemento, this.expressionAsString));
289+
this.bindingMemento, this.groupMemento, this.expressionAsString, false, false));
277290
return selfIndex;
278291
}
279292
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach} from 'test_lib/test_lib';
2+
3+
import {coalesce} from 'change_detection/coalesce';
4+
import {RECORD_TYPE_SELF, ProtoRecord} from 'change_detection/proto_change_detector';
5+
6+
export function main() {
7+
function r(funcOrValue, args, contextIndex, selfIndex, lastInBinding = false) {
8+
return new ProtoRecord(99, "name", funcOrValue, args, null, contextIndex, selfIndex,
9+
null, null, null, lastInBinding, false);
10+
}
11+
12+
describe("change detection - coalesce", () => {
13+
it("should work with an empty list", () => {
14+
expect(coalesce([])).toEqual([]);
15+
});
16+
17+
it("should remove non-terminal duplicate records" +
18+
" and update the context indices referencing them", () => {
19+
var rs = coalesce([
20+
r("user", [], 0, 1),
21+
r("first", [], 1, 2),
22+
r("user", [], 0, 3),
23+
r("last", [], 3, 4)
24+
]);
25+
26+
expect(rs).toEqual([
27+
r("user", [], 0, 1),
28+
r("first", [], 1, 2),
29+
r("last", [], 1, 3)
30+
]);
31+
});
32+
33+
it("should update indices of other records", () => {
34+
var rs = coalesce([
35+
r("dup", [], 0, 1),
36+
r("dup", [], 0, 2),
37+
r("user", [], 0, 3),
38+
r("first", [3], 3, 4)
39+
]);
40+
41+
expect(rs).toEqual([
42+
r("dup", [], 0, 1),
43+
r("user", [], 0, 2),
44+
r("first", [2], 2, 3)
45+
]);
46+
});
47+
48+
it("should remove non-terminal duplicate records" +
49+
" and update the args indices referencing them", () => {
50+
var rs = coalesce([
51+
r("user1", [], 0, 1),
52+
r("user2", [], 0, 2),
53+
r("hi", [1], 0, 3),
54+
r("hi", [1], 0, 4),
55+
r("hi", [2], 0, 5)
56+
]);
57+
58+
expect(rs).toEqual([
59+
r("user1", [], 0, 1),
60+
r("user2", [], 0, 2),
61+
r("hi", [1], 0, 3),
62+
r("hi", [2], 0, 4)
63+
]);
64+
});
65+
66+
it("should replace duplicate terminal records with" +
67+
" self records", () => {
68+
69+
var rs = coalesce([
70+
r("user", [], 0, 1, true),
71+
r("user", [], 0, 2, true)
72+
]);
73+
74+
expect(rs[1]).toEqual(new ProtoRecord(
75+
RECORD_TYPE_SELF, "self", null,
76+
[], null, 1, 2,
77+
null, null, null,
78+
true, false)
79+
);
80+
});
81+
});
82+
}

modules/facade/src/collection.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,13 @@ class ListWrapper {
118118
}
119119
l.fillRange(start, end, value);
120120
}
121+
static bool equals(List a, List b){
122+
if(a.length != b.length) return false;
123+
for (var i = 0; i < a.length; ++i) {
124+
if (a[i] != b[i]) return false;
125+
}
126+
return true;
127+
}
121128
}
122129

123130
bool isListLikeIterable(obj) => obj is Iterable;

modules/facade/src/collection.es6

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,13 @@ export class ListWrapper {
169169
static fill(list:List, value, start:int = 0, end:int = undefined) {
170170
list.fill(value, start, end);
171171
}
172+
static equals(a:List, b:List):boolean {
173+
if(a.length != b.length) return false;
174+
for (var i = 0; i < a.length; ++i) {
175+
if (a[i] !== b[i]) return false;
176+
}
177+
return true;
178+
}
172179
}
173180

174181
export function isListLikeIterable(obj):boolean {

0 commit comments

Comments
 (0)