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) {
|
switch (r.mode) {
|
||||||
case RECORD_TYPE_SELF:
|
case RECORD_TYPE_SELF:
|
||||||
throw new BaseException("Cannot evaluate self");
|
return assignmentTemplate(newValue, context);
|
||||||
|
|
||||||
case RECORD_TYPE_CONST:
|
case RECORD_TYPE_CONST:
|
||||||
return `${newValue} = ${this.genLiteral(r.funcOrValue)}`;
|
return `${newValue} = ${this.genLiteral(r.funcOrValue)}`;
|
||||||
|
88
modules/change_detection/src/coalesce.js
Normal file
88
modules/change_detection/src/coalesce.js
Normal 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;
|
||||||
|
}
|
@ -100,7 +100,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
|
|||||||
_calculateCurrValue(proto:ProtoRecord) {
|
_calculateCurrValue(proto:ProtoRecord) {
|
||||||
switch (proto.mode) {
|
switch (proto.mode) {
|
||||||
case RECORD_TYPE_SELF:
|
case RECORD_TYPE_SELF:
|
||||||
throw new BaseException("Cannot evaluate self");
|
return this._readContext(proto);
|
||||||
|
|
||||||
case RECORD_TYPE_CONST:
|
case RECORD_TYPE_CONST:
|
||||||
return proto.funcOrValue;
|
return proto.funcOrValue;
|
||||||
|
@ -31,6 +31,7 @@ import {ChangeDetectorJITGenerator} from './change_detection_jit_generator';
|
|||||||
|
|
||||||
import {ArrayChanges} from './array_changes';
|
import {ArrayChanges} from './array_changes';
|
||||||
import {KeyValueChanges} from './keyvalue_changes';
|
import {KeyValueChanges} from './keyvalue_changes';
|
||||||
|
import {coalesce} from './coalesce';
|
||||||
|
|
||||||
export const RECORD_TYPE_SELF = 0;
|
export const RECORD_TYPE_SELF = 0;
|
||||||
export const RECORD_TYPE_CONST = 1;
|
export const RECORD_TYPE_CONST = 1;
|
||||||
@ -66,7 +67,9 @@ export class ProtoRecord {
|
|||||||
selfIndex:number,
|
selfIndex:number,
|
||||||
bindingMemento:any,
|
bindingMemento:any,
|
||||||
groupMemento:any,
|
groupMemento:any,
|
||||||
expressionAsString:string) {
|
expressionAsString:string,
|
||||||
|
lastInBinding:boolean,
|
||||||
|
lastInGroup:boolean) {
|
||||||
|
|
||||||
this.mode = mode;
|
this.mode = mode;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
@ -77,8 +80,8 @@ export class ProtoRecord {
|
|||||||
this.selfIndex = selfIndex;
|
this.selfIndex = selfIndex;
|
||||||
this.bindingMemento = bindingMemento;
|
this.bindingMemento = bindingMemento;
|
||||||
this.groupMemento = groupMemento;
|
this.groupMemento = groupMemento;
|
||||||
this.lastInBinding = false;
|
this.lastInBinding = lastInBinding;
|
||||||
this.lastInGroup = false;
|
this.lastInGroup = lastInGroup;
|
||||||
this.expressionAsString = expressionAsString;
|
this.expressionAsString = expressionAsString;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -91,9 +94,11 @@ export class ProtoChangeDetector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class DynamicProtoChangeDetector extends ProtoChangeDetector {
|
export class DynamicProtoChangeDetector extends ProtoChangeDetector {
|
||||||
|
_records:List<ProtoRecord>;
|
||||||
_recordBuilder:ProtoRecordBuilder;
|
_recordBuilder:ProtoRecordBuilder;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
this._records = null;
|
||||||
this._recordBuilder = new ProtoRecordBuilder();
|
this._recordBuilder = new ProtoRecordBuilder();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,8 +107,15 @@ export class DynamicProtoChangeDetector extends ProtoChangeDetector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
instantiate(dispatcher:any, formatters:Map) {
|
instantiate(dispatcher:any, formatters:Map) {
|
||||||
var records = this._recordBuilder.records;
|
this._createRecordsIfNecessary();
|
||||||
return new DynamicChangeDetector(dispatcher, formatters, records);
|
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;
|
_recordBuilder:ProtoRecordBuilder;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
this._factory = null;
|
||||||
this._recordBuilder = new ProtoRecordBuilder();
|
this._recordBuilder = new ProtoRecordBuilder();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,7 +141,7 @@ export class JitProtoChangeDetector extends ProtoChangeDetector {
|
|||||||
_createFactoryIfNecessary() {
|
_createFactoryIfNecessary() {
|
||||||
if (isBlank(this._factory)) {
|
if (isBlank(this._factory)) {
|
||||||
var c = _jitProtoChangeDetectorClassCounter++;
|
var c = _jitProtoChangeDetectorClassCounter++;
|
||||||
var records = this._recordBuilder.records;
|
var records = coalesce(this._recordBuilder.records);
|
||||||
var typeName = `ChangeDetector${c}`;
|
var typeName = `ChangeDetector${c}`;
|
||||||
this._factory = new ChangeDetectorJITGenerator(typeName, records).generate();
|
this._factory = new ChangeDetectorJITGenerator(typeName, records).generate();
|
||||||
}
|
}
|
||||||
@ -273,7 +286,7 @@ class _ConvertAstIntoProtoRecords {
|
|||||||
var selfIndex = ++ this.contextIndex;
|
var selfIndex = ++ this.contextIndex;
|
||||||
ListWrapper.push(this.protoRecords,
|
ListWrapper.push(this.protoRecords,
|
||||||
new ProtoRecord(type, name, funcOrValue, args, fixedArgs, context, selfIndex,
|
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;
|
return selfIndex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
82
modules/change_detection/test/coalesce_spec.js
Normal file
82
modules/change_detection/test/coalesce_spec.js
Normal 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)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -118,6 +118,13 @@ class ListWrapper {
|
|||||||
}
|
}
|
||||||
l.fillRange(start, end, value);
|
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;
|
bool isListLikeIterable(obj) => obj is Iterable;
|
||||||
|
@ -169,6 +169,13 @@ export class ListWrapper {
|
|||||||
static fill(list:List, value, start:int = 0, end:int = undefined) {
|
static fill(list:List, value, start:int = 0, end:int = undefined) {
|
||||||
list.fill(value, start, end);
|
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 {
|
export function isListLikeIterable(obj):boolean {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user