feat(change_detection): change proto change detectors to coalesce records
This commit is contained in:
parent
5367749f54
commit
2793d47c67
|
@ -296,7 +296,7 @@ export class ChangeDetectorJITGenerator {
|
|||
|
||||
switch (r.mode) {
|
||||
case RECORD_TYPE_SELF:
|
||||
throw new BaseException("Cannot evaluate self");
|
||||
return assignmentTemplate(newValue, context);
|
||||
|
||||
case RECORD_TYPE_CONST:
|
||||
return `${newValue} = ${this.genLiteral(r.funcOrValue)}`;
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
import {isPresent} from 'facade/lang';
|
||||
import {List, ListWrapper, Map, MapWrapper} from 'facade/collection';
|
||||
import {RECORD_TYPE_SELF, ProtoRecord} from './proto_change_detector';
|
||||
|
||||
/**
|
||||
* Removes "duplicate" records. It assuming that record evaluation does not
|
||||
* have side-effects.
|
||||
*
|
||||
* Records that are not last in bindings are removed and all the indices
|
||||
* of the records that depend on them are updated.
|
||||
*
|
||||
* Records that are last in bindings CANNOT be removed, and instead are
|
||||
* replaced with very cheap SELF records.
|
||||
*/
|
||||
export function coalesce(records:List<ProtoRecord>):List<ProtoRecord> {
|
||||
var res = ListWrapper.create();
|
||||
var indexMap = MapWrapper.create();
|
||||
|
||||
for (var i = 0; i < records.length; ++i) {
|
||||
var r = records[i];
|
||||
var record = _replaceIndices(r, res.length + 1, indexMap);
|
||||
var matchingRecord = _findMatching(record, res);
|
||||
|
||||
if (isPresent(matchingRecord) && record.lastInBinding) {
|
||||
ListWrapper.push(res, _selfRecord(record, matchingRecord.selfIndex, res.length + 1));
|
||||
MapWrapper.set(indexMap, r.selfIndex, matchingRecord.selfIndex);
|
||||
|
||||
} else if (isPresent(matchingRecord) && !record.lastInBinding) {
|
||||
MapWrapper.set(indexMap, r.selfIndex, matchingRecord.selfIndex);
|
||||
|
||||
} else {
|
||||
ListWrapper.push(res, record);
|
||||
MapWrapper.set(indexMap, r.selfIndex, record.selfIndex);
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
function _selfRecord(r:ProtoRecord, contextIndex:number, selfIndex:number):ProtoRecord {
|
||||
return new ProtoRecord(
|
||||
RECORD_TYPE_SELF,
|
||||
"self",
|
||||
null,
|
||||
[],
|
||||
r.fixedArgs,
|
||||
contextIndex,
|
||||
selfIndex,
|
||||
r.bindingMemento,
|
||||
r.groupMemento,
|
||||
r.expressionAsString,
|
||||
r.lastInBinding,
|
||||
r.lastInGroup
|
||||
);
|
||||
}
|
||||
|
||||
function _findMatching(r:ProtoRecord, rs:List<ProtoRecord>){
|
||||
return ListWrapper.find(rs, (rr) =>
|
||||
rr.mode === r.mode &&
|
||||
rr.funcOrValue === r.funcOrValue &&
|
||||
rr.contextIndex === r.contextIndex &&
|
||||
ListWrapper.equals(rr.args, r.args)
|
||||
);
|
||||
}
|
||||
|
||||
function _replaceIndices(r:ProtoRecord, selfIndex:number, indexMap:Map) {
|
||||
var args = ListWrapper.map(r.args, (a) => _map(indexMap, a));
|
||||
var contextIndex = _map(indexMap, r.contextIndex);
|
||||
return new ProtoRecord(
|
||||
r.mode,
|
||||
r.name,
|
||||
r.funcOrValue,
|
||||
args,
|
||||
r.fixedArgs,
|
||||
contextIndex,
|
||||
selfIndex,
|
||||
r.bindingMemento,
|
||||
r.groupMemento,
|
||||
r.expressionAsString,
|
||||
r.lastInBinding,
|
||||
r.lastInGroup
|
||||
);
|
||||
}
|
||||
|
||||
function _map(indexMap:Map, value:number) {
|
||||
var r = MapWrapper.get(indexMap, value)
|
||||
return isPresent(r) ? r : value;
|
||||
}
|
|
@ -100,7 +100,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
|
|||
_calculateCurrValue(proto:ProtoRecord) {
|
||||
switch (proto.mode) {
|
||||
case RECORD_TYPE_SELF:
|
||||
throw new BaseException("Cannot evaluate self");
|
||||
return this._readContext(proto);
|
||||
|
||||
case RECORD_TYPE_CONST:
|
||||
return proto.funcOrValue;
|
||||
|
|
|
@ -31,6 +31,7 @@ import {ChangeDetectorJITGenerator} from './change_detection_jit_generator';
|
|||
|
||||
import {ArrayChanges} from './array_changes';
|
||||
import {KeyValueChanges} from './keyvalue_changes';
|
||||
import {coalesce} from './coalesce';
|
||||
|
||||
export const RECORD_TYPE_SELF = 0;
|
||||
export const RECORD_TYPE_CONST = 1;
|
||||
|
@ -66,7 +67,9 @@ export class ProtoRecord {
|
|||
selfIndex:number,
|
||||
bindingMemento:any,
|
||||
groupMemento:any,
|
||||
expressionAsString:string) {
|
||||
expressionAsString:string,
|
||||
lastInBinding:boolean,
|
||||
lastInGroup:boolean) {
|
||||
|
||||
this.mode = mode;
|
||||
this.name = name;
|
||||
|
@ -77,8 +80,8 @@ export class ProtoRecord {
|
|||
this.selfIndex = selfIndex;
|
||||
this.bindingMemento = bindingMemento;
|
||||
this.groupMemento = groupMemento;
|
||||
this.lastInBinding = false;
|
||||
this.lastInGroup = false;
|
||||
this.lastInBinding = lastInBinding;
|
||||
this.lastInGroup = lastInGroup;
|
||||
this.expressionAsString = expressionAsString;
|
||||
}
|
||||
}
|
||||
|
@ -91,9 +94,11 @@ export class ProtoChangeDetector {
|
|||
}
|
||||
|
||||
export class DynamicProtoChangeDetector extends ProtoChangeDetector {
|
||||
_records:List<ProtoRecord>;
|
||||
_recordBuilder:ProtoRecordBuilder;
|
||||
|
||||
constructor() {
|
||||
this._records = null;
|
||||
this._recordBuilder = new ProtoRecordBuilder();
|
||||
}
|
||||
|
||||
|
@ -102,8 +107,15 @@ export class DynamicProtoChangeDetector extends ProtoChangeDetector {
|
|||
}
|
||||
|
||||
instantiate(dispatcher:any, formatters:Map) {
|
||||
var records = this._recordBuilder.records;
|
||||
return new DynamicChangeDetector(dispatcher, formatters, records);
|
||||
this._createRecordsIfNecessary();
|
||||
return new DynamicChangeDetector(dispatcher, formatters, this._records);
|
||||
}
|
||||
|
||||
_createRecordsIfNecessary() {
|
||||
if (isBlank(this._records)) {
|
||||
var records = this._recordBuilder.records;
|
||||
this._records = coalesce(records);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -113,6 +125,7 @@ export class JitProtoChangeDetector extends ProtoChangeDetector {
|
|||
_recordBuilder:ProtoRecordBuilder;
|
||||
|
||||
constructor() {
|
||||
this._factory = null;
|
||||
this._recordBuilder = new ProtoRecordBuilder();
|
||||
}
|
||||
|
||||
|
@ -128,7 +141,7 @@ export class JitProtoChangeDetector extends ProtoChangeDetector {
|
|||
_createFactoryIfNecessary() {
|
||||
if (isBlank(this._factory)) {
|
||||
var c = _jitProtoChangeDetectorClassCounter++;
|
||||
var records = this._recordBuilder.records;
|
||||
var records = coalesce(this._recordBuilder.records);
|
||||
var typeName = `ChangeDetector${c}`;
|
||||
this._factory = new ChangeDetectorJITGenerator(typeName, records).generate();
|
||||
}
|
||||
|
@ -273,7 +286,7 @@ class _ConvertAstIntoProtoRecords {
|
|||
var selfIndex = ++ this.contextIndex;
|
||||
ListWrapper.push(this.protoRecords,
|
||||
new ProtoRecord(type, name, funcOrValue, args, fixedArgs, context, selfIndex,
|
||||
this.bindingMemento, this.groupMemento, this.expressionAsString));
|
||||
this.bindingMemento, this.groupMemento, this.expressionAsString, false, false));
|
||||
return selfIndex;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach} from 'test_lib/test_lib';
|
||||
|
||||
import {coalesce} from 'change_detection/coalesce';
|
||||
import {RECORD_TYPE_SELF, ProtoRecord} from 'change_detection/proto_change_detector';
|
||||
|
||||
export function main() {
|
||||
function r(funcOrValue, args, contextIndex, selfIndex, lastInBinding = false) {
|
||||
return new ProtoRecord(99, "name", funcOrValue, args, null, contextIndex, selfIndex,
|
||||
null, null, null, lastInBinding, false);
|
||||
}
|
||||
|
||||
describe("change detection - coalesce", () => {
|
||||
it("should work with an empty list", () => {
|
||||
expect(coalesce([])).toEqual([]);
|
||||
});
|
||||
|
||||
it("should remove non-terminal duplicate records" +
|
||||
" and update the context indices referencing them", () => {
|
||||
var rs = coalesce([
|
||||
r("user", [], 0, 1),
|
||||
r("first", [], 1, 2),
|
||||
r("user", [], 0, 3),
|
||||
r("last", [], 3, 4)
|
||||
]);
|
||||
|
||||
expect(rs).toEqual([
|
||||
r("user", [], 0, 1),
|
||||
r("first", [], 1, 2),
|
||||
r("last", [], 1, 3)
|
||||
]);
|
||||
});
|
||||
|
||||
it("should update indices of other records", () => {
|
||||
var rs = coalesce([
|
||||
r("dup", [], 0, 1),
|
||||
r("dup", [], 0, 2),
|
||||
r("user", [], 0, 3),
|
||||
r("first", [3], 3, 4)
|
||||
]);
|
||||
|
||||
expect(rs).toEqual([
|
||||
r("dup", [], 0, 1),
|
||||
r("user", [], 0, 2),
|
||||
r("first", [2], 2, 3)
|
||||
]);
|
||||
});
|
||||
|
||||
it("should remove non-terminal duplicate records" +
|
||||
" and update the args indices referencing them", () => {
|
||||
var rs = coalesce([
|
||||
r("user1", [], 0, 1),
|
||||
r("user2", [], 0, 2),
|
||||
r("hi", [1], 0, 3),
|
||||
r("hi", [1], 0, 4),
|
||||
r("hi", [2], 0, 5)
|
||||
]);
|
||||
|
||||
expect(rs).toEqual([
|
||||
r("user1", [], 0, 1),
|
||||
r("user2", [], 0, 2),
|
||||
r("hi", [1], 0, 3),
|
||||
r("hi", [2], 0, 4)
|
||||
]);
|
||||
});
|
||||
|
||||
it("should replace duplicate terminal records with" +
|
||||
" self records", () => {
|
||||
|
||||
var rs = coalesce([
|
||||
r("user", [], 0, 1, true),
|
||||
r("user", [], 0, 2, true)
|
||||
]);
|
||||
|
||||
expect(rs[1]).toEqual(new ProtoRecord(
|
||||
RECORD_TYPE_SELF, "self", null,
|
||||
[], null, 1, 2,
|
||||
null, null, null,
|
||||
true, false)
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -118,6 +118,13 @@ class ListWrapper {
|
|||
}
|
||||
l.fillRange(start, end, value);
|
||||
}
|
||||
static bool equals(List a, List b){
|
||||
if(a.length != b.length) return false;
|
||||
for (var i = 0; i < a.length; ++i) {
|
||||
if (a[i] != b[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool isListLikeIterable(obj) => obj is Iterable;
|
||||
|
|
|
@ -169,6 +169,13 @@ export class ListWrapper {
|
|||
static fill(list:List, value, start:int = 0, end:int = undefined) {
|
||||
list.fill(value, start, end);
|
||||
}
|
||||
static equals(a:List, b:List):boolean {
|
||||
if(a.length != b.length) return false;
|
||||
for (var i = 0; i < a.length; ++i) {
|
||||
if (a[i] !== b[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export function isListLikeIterable(obj):boolean {
|
||||
|
|
Loading…
Reference in New Issue