feat(change_detection): modify change detectors to recompute pure functions only when their args change
This commit is contained in:
parent
2793d47c67
commit
af41fa9ac4
@ -138,9 +138,10 @@ ${type}.prototype.detectChangesInRecords = function(throwOnChange) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function bodyTemplate(localDefinitions:string, records:string):string {
|
function bodyTemplate(localDefinitions:string, changeDefinitions:string, records:string):string {
|
||||||
return `
|
return `
|
||||||
${localDefinitions}
|
${localDefinitions}
|
||||||
|
${changeDefinitions}
|
||||||
var ${TEMP_LOCAL};
|
var ${TEMP_LOCAL};
|
||||||
var ${CHANGE_LOCAL};
|
var ${CHANGE_LOCAL};
|
||||||
var ${CHANGES_LOCAL} = [];
|
var ${CHANGES_LOCAL} = [];
|
||||||
@ -172,10 +173,11 @@ ${notify}
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function referenceCheckTemplate(assignment, newValue, oldValue, addRecord, notify) {
|
function referenceCheckTemplate(assignment, newValue, oldValue, change, addRecord, notify) {
|
||||||
return `
|
return `
|
||||||
${assignment}
|
${assignment}
|
||||||
if (${newValue} !== ${oldValue} || (${newValue} !== ${newValue}) && (${oldValue} !== ${oldValue})) {
|
if (${newValue} !== ${oldValue} || (${newValue} !== ${newValue}) && (${oldValue} !== ${oldValue})) {
|
||||||
|
${change} = true;
|
||||||
${addRecord}
|
${addRecord}
|
||||||
${oldValue} = ${newValue};
|
${oldValue} = ${newValue};
|
||||||
}
|
}
|
||||||
@ -202,10 +204,23 @@ function localDefinitionsTemplate(names:List):string {
|
|||||||
return names.map((n) => `var ${n};`).join("\n");
|
return names.map((n) => `var ${n};`).join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function changeDefinitionsTemplate(names:List):string {
|
||||||
|
return names.map((n) => `var ${n} = false;`).join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
function fieldDefinitionsTemplate(names:List):string {
|
function fieldDefinitionsTemplate(names:List):string {
|
||||||
return names.map((n) => `${n} = ${UTIL}.unitialized();`).join("\n");
|
return names.map((n) => `${n} = ${UTIL}.unitialized();`).join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ifChangedGuardTemplate(changeNames:List, body:string):string {
|
||||||
|
var cond = changeNames.join(" || ");
|
||||||
|
return `
|
||||||
|
if (${cond}) {
|
||||||
|
${body}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
function addSimpleChangeRecordTemplate(protoIndex:number, oldValue:string, newValue:string) {
|
function addSimpleChangeRecordTemplate(protoIndex:number, oldValue:string, newValue:string) {
|
||||||
return `${CHANGES_LOCAL}.push(${UTIL}.simpleChangeRecord(${PROTOS_ACCESSOR}[${protoIndex}].bindingMemento, ${oldValue}, ${newValue}));`;
|
return `${CHANGES_LOCAL}.push(${UTIL}.simpleChangeRecord(${PROTOS_ACCESSOR}[${protoIndex}].bindingMemento, ${oldValue}, ${newValue}));`;
|
||||||
}
|
}
|
||||||
@ -215,6 +230,7 @@ export class ChangeDetectorJITGenerator {
|
|||||||
typeName:string;
|
typeName:string;
|
||||||
records:List<ProtoRecord>;
|
records:List<ProtoRecord>;
|
||||||
localNames:List<String>;
|
localNames:List<String>;
|
||||||
|
changeNames:List<String>;
|
||||||
fieldNames:List<String>;
|
fieldNames:List<String>;
|
||||||
|
|
||||||
constructor(typeName:string, records:List<ProtoRecord>) {
|
constructor(typeName:string, records:List<ProtoRecord>) {
|
||||||
@ -222,6 +238,7 @@ export class ChangeDetectorJITGenerator {
|
|||||||
this.records = records;
|
this.records = records;
|
||||||
|
|
||||||
this.localNames = this.getLocalNames(records);
|
this.localNames = this.getLocalNames(records);
|
||||||
|
this.changeNames = this.getChangeNames(this.localNames);
|
||||||
this.fieldNames = this.getFieldNames(this.localNames);
|
this.fieldNames = this.getFieldNames(this.localNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -234,6 +251,10 @@ export class ChangeDetectorJITGenerator {
|
|||||||
return ["context"].concat(names);
|
return ["context"].concat(names);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getChangeNames(localNames:List<String>):List<String> {
|
||||||
|
return localNames.map((n) => `change_${n}`);
|
||||||
|
}
|
||||||
|
|
||||||
getFieldNames(localNames:List<String>):List<String> {
|
getFieldNames(localNames:List<String>):List<String> {
|
||||||
return localNames.map((n) => `this.${n}`);
|
return localNames.map((n) => `this.${n}`);
|
||||||
}
|
}
|
||||||
@ -259,13 +280,17 @@ export class ChangeDetectorJITGenerator {
|
|||||||
|
|
||||||
genBody():string {
|
genBody():string {
|
||||||
var rec = this.records.map((r) => this.genRecord(r)).join("\n");
|
var rec = this.records.map((r) => this.genRecord(r)).join("\n");
|
||||||
return bodyTemplate(this.genLocalDefinitions(), rec);
|
return bodyTemplate(this.genLocalDefinitions(), this.genChangeDefinitions(), rec);
|
||||||
}
|
}
|
||||||
|
|
||||||
genLocalDefinitions():string {
|
genLocalDefinitions():string {
|
||||||
return localDefinitionsTemplate(this.localNames);
|
return localDefinitionsTemplate(this.localNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
genChangeDefinitions():string {
|
||||||
|
return changeDefinitionsTemplate(this.changeNames);
|
||||||
|
}
|
||||||
|
|
||||||
genRecord(r:ProtoRecord):string {
|
genRecord(r:ProtoRecord):string {
|
||||||
if (r.mode == RECORD_TYPE_STRUCTURAL_CHECK) {
|
if (r.mode == RECORD_TYPE_STRUCTURAL_CHECK) {
|
||||||
return this.getStructuralCheck(r);
|
return this.getStructuralCheck(r);
|
||||||
@ -283,10 +308,17 @@ export class ChangeDetectorJITGenerator {
|
|||||||
genReferenceCheck(r:ProtoRecord):string {
|
genReferenceCheck(r:ProtoRecord):string {
|
||||||
var newValue = this.localNames[r.selfIndex];
|
var newValue = this.localNames[r.selfIndex];
|
||||||
var oldValue = this.fieldNames[r.selfIndex];
|
var oldValue = this.fieldNames[r.selfIndex];
|
||||||
|
var change = this.changeNames[r.selfIndex];
|
||||||
var assignment = this.genUpdateCurrentValue(r);
|
var assignment = this.genUpdateCurrentValue(r);
|
||||||
var addRecord = addSimpleChangeRecordTemplate(r.selfIndex - 1, oldValue, newValue);
|
var addRecord = addSimpleChangeRecordTemplate(r.selfIndex - 1, oldValue, newValue);
|
||||||
var notify = this.genNotify(r);
|
var notify = this.genNotify(r);
|
||||||
return referenceCheckTemplate(assignment, newValue, oldValue, r.lastInBinding ? addRecord : '', notify);
|
|
||||||
|
var check = referenceCheckTemplate(assignment, newValue, oldValue, change, r.lastInBinding ? addRecord : '', notify);;
|
||||||
|
if (r.isPureFunction()) {
|
||||||
|
return this.ifChangedGuard(r, check);
|
||||||
|
} else {
|
||||||
|
return check;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
genUpdateCurrentValue(r:ProtoRecord):string {
|
genUpdateCurrentValue(r:ProtoRecord):string {
|
||||||
@ -320,18 +352,22 @@ export class ChangeDetectorJITGenerator {
|
|||||||
case RECORD_TYPE_INTERPOLATE:
|
case RECORD_TYPE_INTERPOLATE:
|
||||||
return assignmentTemplate(newValue, this.genInterpolation(r));
|
return assignmentTemplate(newValue, this.genInterpolation(r));
|
||||||
|
|
||||||
|
case RECORD_TYPE_INVOKE_FORMATTER:
|
||||||
|
return assignmentTemplate(newValue, `${FORMATTERS_ACCESSOR}.get("${r.name}")(${args})`);
|
||||||
|
|
||||||
case RECORD_TYPE_KEYED_ACCESS:
|
case RECORD_TYPE_KEYED_ACCESS:
|
||||||
var key = this.localNames[r.args[0]];
|
var key = this.localNames[r.args[0]];
|
||||||
return assignmentTemplate(newValue, `${context}[${key}]`);
|
return assignmentTemplate(newValue, `${context}[${key}]`);
|
||||||
|
|
||||||
case RECORD_TYPE_INVOKE_FORMATTER:
|
|
||||||
return assignmentTemplate(newValue, `${FORMATTERS_ACCESSOR}.get("${r.name}")(${args})`);
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new BaseException(`Unknown operation ${r.mode}`);
|
throw new BaseException(`Unknown operation ${r.mode}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ifChangedGuard(r:ProtoRecord, body:string):string {
|
||||||
|
return ifChangedGuardTemplate(r.args.map((a) => this.changeNames[a]), body);
|
||||||
|
}
|
||||||
|
|
||||||
genInterpolation(r:ProtoRecord):string{
|
genInterpolation(r:ProtoRecord):string{
|
||||||
var res = "";
|
var res = "";
|
||||||
for (var i = 0; i < r.args.length; ++i) {
|
for (var i = 0; i < r.args.length; ++i) {
|
||||||
|
@ -30,14 +30,19 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
|
|||||||
dispatcher:any;
|
dispatcher:any;
|
||||||
formatters:Map;
|
formatters:Map;
|
||||||
values:List;
|
values:List;
|
||||||
|
changes:List;
|
||||||
protos:List<ProtoRecord>;
|
protos:List<ProtoRecord>;
|
||||||
|
|
||||||
constructor(dispatcher:any, formatters:Map, protoRecords:List<ProtoRecord>) {
|
constructor(dispatcher:any, formatters:Map, protoRecords:List<ProtoRecord>) {
|
||||||
super();
|
super();
|
||||||
this.dispatcher = dispatcher;
|
this.dispatcher = dispatcher;
|
||||||
this.formatters = formatters;
|
this.formatters = formatters;
|
||||||
|
|
||||||
this.values = ListWrapper.createFixedSize(protoRecords.length + 1);
|
this.values = ListWrapper.createFixedSize(protoRecords.length + 1);
|
||||||
ListWrapper.fill(this.values, uninitialized);
|
ListWrapper.fill(this.values, uninitialized);
|
||||||
|
|
||||||
|
this.changes = ListWrapper.createFixedSize(protoRecords.length + 1);
|
||||||
|
|
||||||
this.protos = protoRecords;
|
this.protos = protoRecords;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,17 +87,25 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_referenceCheck(proto:ProtoRecord) {
|
_referenceCheck(proto:ProtoRecord) {
|
||||||
|
if (this._pureFuncAndArgsDidNotChange(proto)) {
|
||||||
|
this._setChanged(proto, false);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
var prevValue = this._readSelf(proto);
|
var prevValue = this._readSelf(proto);
|
||||||
var currValue = this._calculateCurrValue(proto);
|
var currValue = this._calculateCurrValue(proto);
|
||||||
|
|
||||||
if (!isSame(prevValue, currValue)) {
|
if (!isSame(prevValue, currValue)) {
|
||||||
this._writeSelf(proto, currValue);
|
this._writeSelf(proto, currValue);
|
||||||
|
this._setChanged(proto, true);
|
||||||
|
|
||||||
if (proto.lastInBinding) {
|
if (proto.lastInBinding) {
|
||||||
return new SimpleChange(prevValue, currValue);
|
return new SimpleChange(prevValue, currValue);
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
this._setChanged(proto, false);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -179,6 +192,24 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
|
|||||||
this.values[proto.selfIndex] = value;
|
this.values[proto.selfIndex] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_setChanged(proto:ProtoRecord, value:boolean) {
|
||||||
|
this.changes[proto.selfIndex] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
_pureFuncAndArgsDidNotChange(proto:ProtoRecord):boolean {
|
||||||
|
return proto.isPureFunction() && !this._argsChanged(proto);
|
||||||
|
}
|
||||||
|
|
||||||
|
_argsChanged(proto:ProtoRecord):boolean {
|
||||||
|
var args = proto.args;
|
||||||
|
for(var i = 0; i < args.length; ++i) {
|
||||||
|
if (this.changes[args[i]]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
_readArgs(proto:ProtoRecord) {
|
_readArgs(proto:ProtoRecord) {
|
||||||
var res = ListWrapper.createFixedSize(proto.args.length);
|
var res = ListWrapper.createFixedSize(proto.args.length);
|
||||||
var args = proto.args;
|
var args = proto.args;
|
||||||
|
@ -84,6 +84,12 @@ export class ProtoRecord {
|
|||||||
this.lastInGroup = lastInGroup;
|
this.lastInGroup = lastInGroup;
|
||||||
this.expressionAsString = expressionAsString;
|
this.expressionAsString = expressionAsString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isPureFunction():boolean {
|
||||||
|
return this.mode === RECORD_TYPE_INTERPOLATE ||
|
||||||
|
this.mode === RECORD_TYPE_INVOKE_FORMATTER ||
|
||||||
|
this.mode === RECORD_TYPE_PRIMITIVE_OP;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ProtoChangeDetector {
|
export class ProtoChangeDetector {
|
||||||
|
@ -443,6 +443,25 @@ export function main() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("optimizations", () => {
|
||||||
|
it("should not rerun formatters when args did not change", () => {
|
||||||
|
var count = 0;
|
||||||
|
var formatters = MapWrapper.createFromPairs([
|
||||||
|
['count', (v) => {count ++; "value"}]]);
|
||||||
|
|
||||||
|
var c = createChangeDetector('a', 'a | count', new TestData(null), formatters);
|
||||||
|
var cd = c["changeDetector"];
|
||||||
|
|
||||||
|
cd.detectChanges();
|
||||||
|
|
||||||
|
expect(count).toEqual(1);
|
||||||
|
|
||||||
|
cd.detectChanges();
|
||||||
|
|
||||||
|
expect(count).toEqual(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user