feat(change_detection): change proto change detectors to coalesce records

This commit is contained in:
vsavkin 2015-01-25 16:59:06 -08:00
parent 5367749f54
commit 2793d47c67
7 changed files with 206 additions and 9 deletions

View File

@ -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)}`;

View File

@ -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;
}

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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)
);
});
});
}

View File

@ -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;

View File

@ -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 {