perf(change_detection): do not check intermediate results
This commit is contained in:
parent
f7d7789915
commit
c1ee943533
|
@ -71,7 +71,6 @@ export class ChangeDetectorJITGenerator {
|
||||||
return new ${this._typeName}(dispatcher, protos, directiveRecords);
|
return new ${this._typeName}(dispatcher, protos, directiveRecords);
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
return new Function('AbstractChangeDetector', 'ChangeDetectionUtil', 'protos',
|
return new Function('AbstractChangeDetector', 'ChangeDetectionUtil', 'protos',
|
||||||
'directiveRecords', classDefinition)(
|
'directiveRecords', classDefinition)(
|
||||||
AbstractChangeDetector, ChangeDetectionUtil, this.records, this.directiveRecords);
|
AbstractChangeDetector, ChangeDetectionUtil, this.records, this.directiveRecords);
|
||||||
|
@ -171,7 +170,6 @@ export class ChangeDetectorJITGenerator {
|
||||||
|
|
||||||
var oldValue = this._names.getFieldName(r.selfIndex);
|
var oldValue = this._names.getFieldName(r.selfIndex);
|
||||||
var newValue = this._names.getLocalName(r.selfIndex);
|
var newValue = this._names.getLocalName(r.selfIndex);
|
||||||
var change = this._names.getChangeName(r.selfIndex);
|
|
||||||
|
|
||||||
var pipe = this._names.getPipeName(r.selfIndex);
|
var pipe = this._names.getPipeName(r.selfIndex);
|
||||||
var cdRef = "this.ref";
|
var cdRef = "this.ref";
|
||||||
|
@ -179,7 +177,7 @@ export class ChangeDetectorJITGenerator {
|
||||||
var protoIndex = r.selfIndex - 1;
|
var protoIndex = r.selfIndex - 1;
|
||||||
var pipeType = r.name;
|
var pipeType = r.name;
|
||||||
|
|
||||||
return `
|
var read = `
|
||||||
${this._names.getCurrentProtoName()} = ${this._names.getProtosName()}[${protoIndex}];
|
${this._names.getCurrentProtoName()} = ${this._names.getProtosName()}[${protoIndex}];
|
||||||
if (${pipe} === ${UTIL}.uninitialized) {
|
if (${pipe} === ${UTIL}.uninitialized) {
|
||||||
${pipe} = ${this._names.getPipesAccessorName()}.get('${pipeType}', ${context}, ${cdRef});
|
${pipe} = ${this._names.getPipesAccessorName()}.get('${pipeType}', ${context}, ${cdRef});
|
||||||
|
@ -187,16 +185,20 @@ export class ChangeDetectorJITGenerator {
|
||||||
${pipe}.onDestroy();
|
${pipe}.onDestroy();
|
||||||
${pipe} = ${this._names.getPipesAccessorName()}.get('${pipeType}', ${context}, ${cdRef});
|
${pipe} = ${this._names.getPipesAccessorName()}.get('${pipeType}', ${context}, ${cdRef});
|
||||||
}
|
}
|
||||||
|
|
||||||
${newValue} = ${pipe}.transform(${context}, [${argString}]);
|
${newValue} = ${pipe}.transform(${context}, [${argString}]);
|
||||||
|
`;
|
||||||
|
|
||||||
|
var check = `
|
||||||
if (${oldValue} !== ${newValue}) {
|
if (${oldValue} !== ${newValue}) {
|
||||||
${newValue} = ${UTIL}.unwrapValue(${newValue});
|
${newValue} = ${UTIL}.unwrapValue(${newValue})
|
||||||
${change} = true;
|
${this._genChangeMarker(r)}
|
||||||
${this._genUpdateDirectiveOrElement(r)}
|
${this._genUpdateDirectiveOrElement(r)}
|
||||||
${this._genAddToChanges(r)}
|
${this._genAddToChanges(r)}
|
||||||
${oldValue} = ${newValue};
|
${oldValue} = ${newValue};
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
return r.shouldBeChecked() ? `${read}${check}` : read;
|
||||||
}
|
}
|
||||||
|
|
||||||
_genReferenceCheck(r: ProtoRecord): string {
|
_genReferenceCheck(r: ProtoRecord): string {
|
||||||
|
@ -204,22 +206,32 @@ export class ChangeDetectorJITGenerator {
|
||||||
var newValue = this._names.getLocalName(r.selfIndex);
|
var newValue = this._names.getLocalName(r.selfIndex);
|
||||||
|
|
||||||
var protoIndex = r.selfIndex - 1;
|
var protoIndex = r.selfIndex - 1;
|
||||||
var check = `
|
|
||||||
|
var read = `
|
||||||
${this._names.getCurrentProtoName()} = ${this._names.getProtosName()}[${protoIndex}];
|
${this._names.getCurrentProtoName()} = ${this._names.getProtosName()}[${protoIndex}];
|
||||||
${this._genUpdateCurrentValue(r)}
|
${this._genUpdateCurrentValue(r)}
|
||||||
|
`;
|
||||||
|
|
||||||
|
var check = `
|
||||||
if (${newValue} !== ${oldValue}) {
|
if (${newValue} !== ${oldValue}) {
|
||||||
${this._names.getChangeName(r.selfIndex)} = true;
|
${this._genChangeMarker(r)}
|
||||||
${this._genUpdateDirectiveOrElement(r)}
|
${this._genUpdateDirectiveOrElement(r)}
|
||||||
${this._genAddToChanges(r)}
|
${this._genAddToChanges(r)}
|
||||||
${oldValue} = ${newValue};
|
${oldValue} = ${newValue};
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
var genCode = r.shouldBeChecked() ? `${read}${check}` : read;
|
||||||
|
|
||||||
if (r.isPureFunction()) {
|
if (r.isPureFunction()) {
|
||||||
var condition = r.args.map((a) => this._names.getChangeName(a)).join(" || ");
|
var condition = r.args.map((a) => this._names.getChangeName(a)).join(" || ");
|
||||||
return `if (${condition}) { ${check} } else { ${newValue} = ${oldValue}; }`;
|
if (r.isUsedByOtherRecord()) {
|
||||||
|
return `if (${condition}) { ${genCode} } else { ${newValue} = ${oldValue}; }`;
|
||||||
|
} else {
|
||||||
|
return `if (${condition}) { ${genCode} }`;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return check;
|
return genCode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -267,6 +279,10 @@ export class ChangeDetectorJITGenerator {
|
||||||
rhs = `${UTIL}.${r.name}(${argString})`;
|
rhs = `${UTIL}.${r.name}(${argString})`;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case RecordType.COLLECTION_LITERAL:
|
||||||
|
rhs = `${UTIL}.${r.name}(${argString})`;
|
||||||
|
break;
|
||||||
|
|
||||||
case RecordType.INTERPOLATE:
|
case RecordType.INTERPOLATE:
|
||||||
rhs = this._genInterpolation(r);
|
rhs = this._genInterpolation(r);
|
||||||
break;
|
break;
|
||||||
|
@ -293,6 +309,10 @@ export class ChangeDetectorJITGenerator {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_genChangeMarker(r: ProtoRecord): string {
|
||||||
|
return r.argumentToPureFunction ? `${this._names.getChangeName(r.selfIndex)} = true` : ``;
|
||||||
|
}
|
||||||
|
|
||||||
_genUpdateDirectiveOrElement(r: ProtoRecord): string {
|
_genUpdateDirectiveOrElement(r: ProtoRecord): string {
|
||||||
if (!r.lastInBinding) return "";
|
if (!r.lastInBinding) return "";
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ import {RecordType, ProtoRecord} from './proto_record';
|
||||||
* Records that are last in bindings CANNOT be removed, and instead are
|
* Records that are last in bindings CANNOT be removed, and instead are
|
||||||
* replaced with very cheap SELF records.
|
* replaced with very cheap SELF records.
|
||||||
*/
|
*/
|
||||||
export function coalesce(records: List<ProtoRecord>): List<ProtoRecord> {
|
export function coalesce(records: ProtoRecord[]): ProtoRecord[] {
|
||||||
var res: List<ProtoRecord> = [];
|
var res: List<ProtoRecord> = [];
|
||||||
var indexMap: Map<number, number> = new Map<number, number>();
|
var indexMap: Map<number, number> = new Map<number, number>();
|
||||||
|
|
||||||
|
@ -24,8 +24,13 @@ export function coalesce(records: List<ProtoRecord>): List<ProtoRecord> {
|
||||||
if (isPresent(matchingRecord) && record.lastInBinding) {
|
if (isPresent(matchingRecord) && record.lastInBinding) {
|
||||||
res.push(_selfRecord(record, matchingRecord.selfIndex, res.length + 1));
|
res.push(_selfRecord(record, matchingRecord.selfIndex, res.length + 1));
|
||||||
indexMap.set(r.selfIndex, matchingRecord.selfIndex);
|
indexMap.set(r.selfIndex, matchingRecord.selfIndex);
|
||||||
|
matchingRecord.referencedBySelf = true;
|
||||||
|
|
||||||
} else if (isPresent(matchingRecord) && !record.lastInBinding) {
|
} else if (isPresent(matchingRecord) && !record.lastInBinding) {
|
||||||
|
if (record.argumentToPureFunction) {
|
||||||
|
matchingRecord.argumentToPureFunction = true;
|
||||||
|
}
|
||||||
|
|
||||||
indexMap.set(r.selfIndex, matchingRecord.selfIndex);
|
indexMap.set(r.selfIndex, matchingRecord.selfIndex);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
@ -40,7 +45,7 @@ export function coalesce(records: List<ProtoRecord>): List<ProtoRecord> {
|
||||||
function _selfRecord(r: ProtoRecord, contextIndex: number, selfIndex: number): ProtoRecord {
|
function _selfRecord(r: ProtoRecord, contextIndex: number, selfIndex: number): ProtoRecord {
|
||||||
return new ProtoRecord(RecordType.SELF, "self", null, [], r.fixedArgs, contextIndex,
|
return new ProtoRecord(RecordType.SELF, "self", null, [], r.fixedArgs, contextIndex,
|
||||||
r.directiveIndex, selfIndex, r.bindingRecord, r.expressionAsString,
|
r.directiveIndex, selfIndex, r.bindingRecord, r.expressionAsString,
|
||||||
r.lastInBinding, r.lastInDirective);
|
r.lastInBinding, r.lastInDirective, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _findMatching(r: ProtoRecord, rs: List<ProtoRecord>) {
|
function _findMatching(r: ProtoRecord, rs: List<ProtoRecord>) {
|
||||||
|
@ -66,7 +71,8 @@ function _replaceIndices(r: ProtoRecord, selfIndex: number, indexMap: Map<any, a
|
||||||
var contextIndex = _map(indexMap, r.contextIndex);
|
var contextIndex = _map(indexMap, r.contextIndex);
|
||||||
return new ProtoRecord(r.mode, r.name, r.funcOrValue, args, r.fixedArgs, contextIndex,
|
return new ProtoRecord(r.mode, r.name, r.funcOrValue, args, r.fixedArgs, contextIndex,
|
||||||
r.directiveIndex, selfIndex, r.bindingRecord, r.expressionAsString,
|
r.directiveIndex, selfIndex, r.bindingRecord, r.expressionAsString,
|
||||||
r.lastInBinding, r.lastInDirective);
|
r.lastInBinding, r.lastInDirective, r.argumentToPureFunction,
|
||||||
|
r.referencedBySelf);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _map(indexMap: Map<any, any>, value: number) {
|
function _map(indexMap: Map<any, any>, value: number) {
|
||||||
|
|
|
@ -83,9 +83,14 @@ export class CodegenNameUtil {
|
||||||
if (i == CONTEXT_INDEX) {
|
if (i == CONTEXT_INDEX) {
|
||||||
declarations.push(`${this.getLocalName(i)} = ${this.getFieldName(i)}`);
|
declarations.push(`${this.getLocalName(i)} = ${this.getFieldName(i)}`);
|
||||||
} else {
|
} else {
|
||||||
var changeName = this.getChangeName(i);
|
var rec = this.records[i - 1];
|
||||||
declarations.push(`${this.getLocalName(i)},${changeName}`);
|
if (rec.argumentToPureFunction) {
|
||||||
assignments.push(changeName);
|
var changeName = this.getChangeName(i);
|
||||||
|
declarations.push(`${this.getLocalName(i)},${changeName}`);
|
||||||
|
assignments.push(changeName);
|
||||||
|
} else {
|
||||||
|
declarations.push(`${this.getLocalName(i)}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var assignmentsCode =
|
var assignmentsCode =
|
||||||
|
@ -100,14 +105,18 @@ export class CodegenNameUtil {
|
||||||
getAllFieldNames(): List<string> {
|
getAllFieldNames(): List<string> {
|
||||||
var fieldList = [];
|
var fieldList = [];
|
||||||
for (var k = 0, kLen = this.getFieldCount(); k < kLen; ++k) {
|
for (var k = 0, kLen = this.getFieldCount(); k < kLen; ++k) {
|
||||||
fieldList.push(this.getFieldName(k));
|
if (k === 0 || this.records[k - 1].shouldBeChecked()) {
|
||||||
|
fieldList.push(this.getFieldName(k));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0, iLen = this.records.length; i < iLen; ++i) {
|
for (var i = 0, iLen = this.records.length; i < iLen; ++i) {
|
||||||
var rec = this.records[i];
|
var rec = this.records[i];
|
||||||
if (rec.isPipeRecord()) {
|
if (rec.isPipeRecord()) {
|
||||||
fieldList.push(this.getPipeName(rec.selfIndex));
|
fieldList.push(this.getPipeName(rec.selfIndex));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var j = 0, jLen = this.directiveRecords.length; j < jLen; ++j) {
|
for (var j = 0, jLen = this.directiveRecords.length; j < jLen; ++j) {
|
||||||
var dRec = this.directiveRecords[j];
|
var dRec = this.directiveRecords[j];
|
||||||
fieldList.push(this.getDirectiveName(dRec.directiveIndex));
|
fieldList.push(this.getDirectiveName(dRec.directiveIndex));
|
||||||
|
|
|
@ -150,26 +150,30 @@ export class DynamicChangeDetector extends AbstractChangeDetector<any> {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var prevValue = this._readSelf(proto);
|
|
||||||
var currValue = this._calculateCurrValue(proto);
|
var currValue = this._calculateCurrValue(proto);
|
||||||
|
if (proto.shouldBeChecked()) {
|
||||||
|
var prevValue = this._readSelf(proto);
|
||||||
|
if (!isSame(prevValue, currValue)) {
|
||||||
|
if (proto.lastInBinding) {
|
||||||
|
var change = ChangeDetectionUtil.simpleChange(prevValue, currValue);
|
||||||
|
if (throwOnChange) ChangeDetectionUtil.throwOnChange(proto, change);
|
||||||
|
|
||||||
if (!isSame(prevValue, currValue)) {
|
this._writeSelf(proto, currValue);
|
||||||
if (proto.lastInBinding) {
|
this._setChanged(proto, true);
|
||||||
var change = ChangeDetectionUtil.simpleChange(prevValue, currValue);
|
return change;
|
||||||
if (throwOnChange) ChangeDetectionUtil.throwOnChange(proto, change);
|
} else {
|
||||||
|
this._writeSelf(proto, currValue);
|
||||||
this._writeSelf(proto, currValue);
|
this._setChanged(proto, true);
|
||||||
this._setChanged(proto, true);
|
return null;
|
||||||
|
}
|
||||||
return change;
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
this._writeSelf(proto, currValue);
|
this._setChanged(proto, false);
|
||||||
this._setChanged(proto, true);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
this._setChanged(proto, false);
|
this._writeSelf(proto, currValue);
|
||||||
|
this._setChanged(proto, true);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -215,6 +219,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector<any> {
|
||||||
|
|
||||||
case RecordType.INTERPOLATE:
|
case RecordType.INTERPOLATE:
|
||||||
case RecordType.PRIMITIVE_OP:
|
case RecordType.PRIMITIVE_OP:
|
||||||
|
case RecordType.COLLECTION_LITERAL:
|
||||||
return FunctionWrapper.apply(proto.funcOrValue, this._readArgs(proto));
|
return FunctionWrapper.apply(proto.funcOrValue, this._readArgs(proto));
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -227,28 +232,34 @@ export class DynamicChangeDetector extends AbstractChangeDetector<any> {
|
||||||
var args = this._readArgs(proto);
|
var args = this._readArgs(proto);
|
||||||
|
|
||||||
var pipe = this._pipeFor(proto, context);
|
var pipe = this._pipeFor(proto, context);
|
||||||
var prevValue = this._readSelf(proto);
|
|
||||||
var currValue = pipe.transform(context, args);
|
var currValue = pipe.transform(context, args);
|
||||||
|
|
||||||
if (!isSame(prevValue, currValue)) {
|
if (proto.shouldBeChecked()) {
|
||||||
currValue = ChangeDetectionUtil.unwrapValue(currValue);
|
var prevValue = this._readSelf(proto);
|
||||||
|
if (!isSame(prevValue, currValue)) {
|
||||||
|
currValue = ChangeDetectionUtil.unwrapValue(currValue);
|
||||||
|
|
||||||
if (proto.lastInBinding) {
|
if (proto.lastInBinding) {
|
||||||
var change = ChangeDetectionUtil.simpleChange(prevValue, currValue);
|
var change = ChangeDetectionUtil.simpleChange(prevValue, currValue);
|
||||||
if (throwOnChange) ChangeDetectionUtil.throwOnChange(proto, change);
|
if (throwOnChange) ChangeDetectionUtil.throwOnChange(proto, change);
|
||||||
|
|
||||||
this._writeSelf(proto, currValue);
|
this._writeSelf(proto, currValue);
|
||||||
this._setChanged(proto, true);
|
this._setChanged(proto, true);
|
||||||
|
|
||||||
return change;
|
return change;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
this._writeSelf(proto, currValue);
|
||||||
|
this._setChanged(proto, true);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this._writeSelf(proto, currValue);
|
this._setChanged(proto, false);
|
||||||
this._setChanged(proto, true);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this._setChanged(proto, false);
|
this._writeSelf(proto, currValue);
|
||||||
|
this._setChanged(proto, true);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -284,7 +295,9 @@ export class DynamicChangeDetector extends AbstractChangeDetector<any> {
|
||||||
|
|
||||||
_writePipe(proto: ProtoRecord, value) { this.localPipes[proto.selfIndex] = value; }
|
_writePipe(proto: ProtoRecord, value) { this.localPipes[proto.selfIndex] = value; }
|
||||||
|
|
||||||
_setChanged(proto: ProtoRecord, value: boolean) { this.changes[proto.selfIndex] = value; }
|
_setChanged(proto: ProtoRecord, value: boolean) {
|
||||||
|
if (proto.argumentToPureFunction) this.changes[proto.selfIndex] = value;
|
||||||
|
}
|
||||||
|
|
||||||
_pureFuncAndArgsDidNotChange(proto: ProtoRecord): boolean {
|
_pureFuncAndArgsDidNotChange(proto: ProtoRecord): boolean {
|
||||||
return proto.isPureFunction() && !this._argsChanged(proto);
|
return proto.isPureFunction() && !this._argsChanged(proto);
|
||||||
|
|
|
@ -65,11 +65,23 @@ export class ProtoRecordBuilder {
|
||||||
if (isPresent(oldLast) && oldLast.bindingRecord.directiveRecord == b.directiveRecord) {
|
if (isPresent(oldLast) && oldLast.bindingRecord.directiveRecord == b.directiveRecord) {
|
||||||
oldLast.lastInDirective = false;
|
oldLast.lastInDirective = false;
|
||||||
}
|
}
|
||||||
|
var numberOfRecordsBefore = this.records.length;
|
||||||
this._appendRecords(b, variableNames);
|
this._appendRecords(b, variableNames);
|
||||||
var newLast = ListWrapper.last(this.records);
|
var newLast = ListWrapper.last(this.records);
|
||||||
if (isPresent(newLast) && newLast !== oldLast) {
|
if (isPresent(newLast) && newLast !== oldLast) {
|
||||||
newLast.lastInBinding = true;
|
newLast.lastInBinding = true;
|
||||||
newLast.lastInDirective = true;
|
newLast.lastInDirective = true;
|
||||||
|
this._setArgumentToPureFunction(numberOfRecordsBefore);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_setArgumentToPureFunction(startIndex: number): void {
|
||||||
|
for (var i = startIndex; i < this.records.length; ++i) {
|
||||||
|
var rec = this.records[i];
|
||||||
|
if (rec.isPureFunction()) {
|
||||||
|
rec.args.forEach(recordIndex => this.records[recordIndex - 1].argumentToPureFunction =
|
||||||
|
true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,7 +89,7 @@ export class ProtoRecordBuilder {
|
||||||
if (b.isDirectiveLifecycle()) {
|
if (b.isDirectiveLifecycle()) {
|
||||||
this.records.push(new ProtoRecord(RecordType.DIRECTIVE_LIFECYCLE, b.lifecycleEvent, null, [],
|
this.records.push(new ProtoRecord(RecordType.DIRECTIVE_LIFECYCLE, b.lifecycleEvent, null, [],
|
||||||
[], -1, null, this.records.length + 1, b, null, false,
|
[], -1, null, this.records.length + 1, b, null, false,
|
||||||
false));
|
false, false, false));
|
||||||
} else {
|
} else {
|
||||||
_ConvertAstIntoProtoRecords.append(this.records, b, variableNames);
|
_ConvertAstIntoProtoRecords.append(this.records, b, variableNames);
|
||||||
}
|
}
|
||||||
|
@ -145,12 +157,13 @@ class _ConvertAstIntoProtoRecords implements AstVisitor {
|
||||||
|
|
||||||
visitLiteralArray(ast: LiteralArray): number {
|
visitLiteralArray(ast: LiteralArray): number {
|
||||||
var primitiveName = `arrayFn${ast.expressions.length}`;
|
var primitiveName = `arrayFn${ast.expressions.length}`;
|
||||||
return this._addRecord(RecordType.PRIMITIVE_OP, primitiveName, _arrayFn(ast.expressions.length),
|
return this._addRecord(RecordType.COLLECTION_LITERAL, primitiveName,
|
||||||
this._visitAll(ast.expressions), null, 0);
|
_arrayFn(ast.expressions.length), this._visitAll(ast.expressions), null,
|
||||||
|
0);
|
||||||
}
|
}
|
||||||
|
|
||||||
visitLiteralMap(ast: LiteralMap): number {
|
visitLiteralMap(ast: LiteralMap): number {
|
||||||
return this._addRecord(RecordType.PRIMITIVE_OP, _mapPrimitiveName(ast.keys),
|
return this._addRecord(RecordType.COLLECTION_LITERAL, _mapPrimitiveName(ast.keys),
|
||||||
ChangeDetectionUtil.mapFn(ast.keys), this._visitAll(ast.values), null,
|
ChangeDetectionUtil.mapFn(ast.keys), this._visitAll(ast.values), null,
|
||||||
0);
|
0);
|
||||||
}
|
}
|
||||||
|
@ -208,11 +221,11 @@ class _ConvertAstIntoProtoRecords implements AstVisitor {
|
||||||
if (context instanceof DirectiveIndex) {
|
if (context instanceof DirectiveIndex) {
|
||||||
this._records.push(new ProtoRecord(type, name, funcOrValue, args, fixedArgs, -1, context,
|
this._records.push(new ProtoRecord(type, name, funcOrValue, args, fixedArgs, -1, context,
|
||||||
selfIndex, this._bindingRecord, this._expressionAsString,
|
selfIndex, this._bindingRecord, this._expressionAsString,
|
||||||
false, false));
|
false, false, false, false));
|
||||||
} else {
|
} else {
|
||||||
this._records.push(new ProtoRecord(type, name, funcOrValue, args, fixedArgs, context, null,
|
this._records.push(new ProtoRecord(type, name, funcOrValue, args, fixedArgs, context, null,
|
||||||
selfIndex, this._bindingRecord, this._expressionAsString,
|
selfIndex, this._bindingRecord, this._expressionAsString,
|
||||||
false, false));
|
false, false, false, false));
|
||||||
}
|
}
|
||||||
return selfIndex;
|
return selfIndex;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ export enum RecordType {
|
||||||
PIPE,
|
PIPE,
|
||||||
INTERPOLATE,
|
INTERPOLATE,
|
||||||
SAFE_PROPERTY,
|
SAFE_PROPERTY,
|
||||||
|
COLLECTION_LITERAL,
|
||||||
SAFE_INVOKE_METHOD,
|
SAFE_INVOKE_METHOD,
|
||||||
DIRECTIVE_LIFECYCLE
|
DIRECTIVE_LIFECYCLE
|
||||||
}
|
}
|
||||||
|
@ -23,10 +24,17 @@ export class ProtoRecord {
|
||||||
public args: List<any>, public fixedArgs: List<any>, public contextIndex: number,
|
public args: List<any>, public fixedArgs: List<any>, public contextIndex: number,
|
||||||
public directiveIndex: DirectiveIndex, public selfIndex: number,
|
public directiveIndex: DirectiveIndex, public selfIndex: number,
|
||||||
public bindingRecord: BindingRecord, public expressionAsString: string,
|
public bindingRecord: BindingRecord, public expressionAsString: string,
|
||||||
public lastInBinding: boolean, public lastInDirective: boolean) {}
|
public lastInBinding: boolean, public lastInDirective: boolean,
|
||||||
|
public argumentToPureFunction: boolean, public referencedBySelf: boolean) {}
|
||||||
|
|
||||||
isPureFunction(): boolean {
|
isPureFunction(): boolean {
|
||||||
return this.mode === RecordType.INTERPOLATE || this.mode === RecordType.PRIMITIVE_OP;
|
return this.mode === RecordType.INTERPOLATE || this.mode === RecordType.COLLECTION_LITERAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
isUsedByOtherRecord(): boolean { return !this.lastInBinding || this.referencedBySelf; }
|
||||||
|
|
||||||
|
shouldBeChecked(): boolean {
|
||||||
|
return this.argumentToPureFunction || this.lastInBinding || this.isPureFunction();
|
||||||
}
|
}
|
||||||
|
|
||||||
isPipeRecord(): boolean { return this.mode === RecordType.PIPE; }
|
isPipeRecord(): boolean { return this.mode === RecordType.PIPE; }
|
||||||
|
|
|
@ -246,14 +246,14 @@ class _CodegenState {
|
||||||
|
|
||||||
var oldValue = _names.getFieldName(r.selfIndex);
|
var oldValue = _names.getFieldName(r.selfIndex);
|
||||||
var newValue = _names.getLocalName(r.selfIndex);
|
var newValue = _names.getLocalName(r.selfIndex);
|
||||||
var change = _names.getChangeName(r.selfIndex);
|
|
||||||
|
|
||||||
var pipe = _names.getPipeName(r.selfIndex);
|
var pipe = _names.getPipeName(r.selfIndex);
|
||||||
var cdRef = 'this.ref';
|
var cdRef = 'this.ref';
|
||||||
|
|
||||||
var protoIndex = r.selfIndex - 1;
|
var protoIndex = r.selfIndex - 1;
|
||||||
var pipeType = r.name;
|
var pipeType = r.name;
|
||||||
return '''
|
|
||||||
|
var read = '''
|
||||||
${_names.getCurrentProtoName()} = ${_names.getProtosName()}[$protoIndex];
|
${_names.getCurrentProtoName()} = ${_names.getProtosName()}[$protoIndex];
|
||||||
if ($_IDENTICAL_CHECK_FN($pipe, $_UTIL.uninitialized)) {
|
if ($_IDENTICAL_CHECK_FN($pipe, $_UTIL.uninitialized)) {
|
||||||
$pipe = ${_names.getPipesAccessorName()}.get('$pipeType', $context, $cdRef);
|
$pipe = ${_names.getPipesAccessorName()}.get('$pipeType', $context, $cdRef);
|
||||||
|
@ -261,16 +261,20 @@ class _CodegenState {
|
||||||
$pipe.onDestroy();
|
$pipe.onDestroy();
|
||||||
$pipe = ${_names.getPipesAccessorName()}.get('$pipeType', $context, $cdRef);
|
$pipe = ${_names.getPipesAccessorName()}.get('$pipeType', $context, $cdRef);
|
||||||
}
|
}
|
||||||
|
|
||||||
$newValue = $pipe.transform($context, [$argString]);
|
$newValue = $pipe.transform($context, [$argString]);
|
||||||
|
''';
|
||||||
|
|
||||||
|
var check = '''
|
||||||
if ($_NOT_IDENTICAL_CHECK_FN($oldValue, $newValue)) {
|
if ($_NOT_IDENTICAL_CHECK_FN($oldValue, $newValue)) {
|
||||||
$newValue = $_UTIL.unwrapValue($newValue);
|
$newValue = $_UTIL.unwrapValue($newValue);
|
||||||
$change = true;
|
${_genChangeMarker(r)}
|
||||||
${_genUpdateDirectiveOrElement(r)}
|
${_genUpdateDirectiveOrElement(r)}
|
||||||
${_genAddToChanges(r)}
|
${_genAddToChanges(r)}
|
||||||
$oldValue = $newValue;
|
$oldValue = $newValue;
|
||||||
}
|
}
|
||||||
''';
|
''';
|
||||||
|
|
||||||
|
return r.shouldBeChecked() ? "${read}${check}" : read;
|
||||||
}
|
}
|
||||||
|
|
||||||
String _genReferenceCheck(ProtoRecord r) {
|
String _genReferenceCheck(ProtoRecord r) {
|
||||||
|
@ -278,22 +282,33 @@ class _CodegenState {
|
||||||
var newValue = _names.getLocalName(r.selfIndex);
|
var newValue = _names.getLocalName(r.selfIndex);
|
||||||
|
|
||||||
var protoIndex = r.selfIndex - 1;
|
var protoIndex = r.selfIndex - 1;
|
||||||
var check = '''
|
|
||||||
|
var read = '''
|
||||||
${_names.getCurrentProtoName()} = ${_names.getProtosName()}[$protoIndex];
|
${_names.getCurrentProtoName()} = ${_names.getProtosName()}[$protoIndex];
|
||||||
${_genUpdateCurrentValue(r)}
|
${_genUpdateCurrentValue(r)}
|
||||||
|
''';
|
||||||
|
|
||||||
|
var check = '''
|
||||||
if ($_NOT_IDENTICAL_CHECK_FN($newValue, $oldValue)) {
|
if ($_NOT_IDENTICAL_CHECK_FN($newValue, $oldValue)) {
|
||||||
${_names.getChangeName(r.selfIndex)} = true;
|
${_genChangeMarker(r)}
|
||||||
${_genUpdateDirectiveOrElement(r)}
|
${_genUpdateDirectiveOrElement(r)}
|
||||||
${_genAddToChanges(r)}
|
${_genAddToChanges(r)}
|
||||||
$oldValue = $newValue;
|
$oldValue = $newValue;
|
||||||
}
|
}
|
||||||
''';
|
''';
|
||||||
|
|
||||||
|
var genCode = r.shouldBeChecked() ? "${read}${check}" : read;
|
||||||
|
|
||||||
if (r.isPureFunction()) {
|
if (r.isPureFunction()) {
|
||||||
// Add an "if changed guard"
|
// Add an "if changed guard"
|
||||||
var condition = r.args.map((a) => _names.getChangeName(a)).join(' || ');
|
var condition = r.args.map((a) => _names.getChangeName(a)).join(' || ');
|
||||||
return 'if ($condition) { $check } else { $newValue = $oldValue; }';
|
if (r.isUsedByOtherRecord()) {
|
||||||
|
return 'if ($condition) { $genCode } else { $newValue = $oldValue; }';
|
||||||
|
} else {
|
||||||
|
return 'if ($condition) { $genCode }';
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return check;
|
return genCode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -344,6 +359,10 @@ class _CodegenState {
|
||||||
rhs = '$_UTIL.${r.name}($argString)';
|
rhs = '$_UTIL.${r.name}($argString)';
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case RecordType.COLLECTION_LITERAL:
|
||||||
|
rhs = '$_UTIL.${r.name}($argString)';
|
||||||
|
break;
|
||||||
|
|
||||||
case RecordType.INTERPOLATE:
|
case RecordType.INTERPOLATE:
|
||||||
rhs = _genInterpolation(r);
|
rhs = _genInterpolation(r);
|
||||||
break;
|
break;
|
||||||
|
@ -370,6 +389,10 @@ class _CodegenState {
|
||||||
return '$res';
|
return '$res';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _genChangeMarker(ProtoRecord r) {
|
||||||
|
return r.argumentToPureFunction ? "${this._names.getChangeName(r.selfIndex)} = true;" : "";
|
||||||
|
}
|
||||||
|
|
||||||
String _genUpdateDirectiveOrElement(ProtoRecord r) {
|
String _genUpdateDirectiveOrElement(ProtoRecord r) {
|
||||||
if (!r.lastInBinding) return '';
|
if (!r.lastInBinding) return '';
|
||||||
|
|
||||||
|
|
|
@ -304,6 +304,7 @@ var _availableDefinitions = [
|
||||||
'{z: 1}',
|
'{z: 1}',
|
||||||
'{z: a}',
|
'{z: a}',
|
||||||
'name | pipe',
|
'name | pipe',
|
||||||
|
'(name | pipe).length',
|
||||||
"name | pipe:'one':address.city",
|
"name | pipe:'one':address.city",
|
||||||
'value',
|
'value',
|
||||||
'a',
|
'a',
|
||||||
|
|
|
@ -351,6 +351,14 @@ export function main() {
|
||||||
val.changeDetector.detectChanges();
|
val.changeDetector.detectChanges();
|
||||||
expect(val.dispatcher.loggedValues).toEqual(['value one two default']);
|
expect(val.dispatcher.loggedValues).toEqual(['value one two default']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should support pipes as arguments to pure functions', () => {
|
||||||
|
var registry = new FakePipes('pipe', () => new IdentityPipe());
|
||||||
|
var person = new Person('bob');
|
||||||
|
var val = _createChangeDetector('(name | pipe).length', person, registry);
|
||||||
|
val.changeDetector.detectChanges();
|
||||||
|
expect(val.dispatcher.loggedValues).toEqual([3]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should notify the dispatcher on all changes done', () => {
|
it('should notify the dispatcher on all changes done', () => {
|
||||||
|
|
|
@ -7,14 +7,22 @@ import {DirectiveIndex} from 'angular2/src/change_detection/directive_record';
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
function r(funcOrValue, args, contextIndex, selfIndex,
|
function r(funcOrValue, args, contextIndex, selfIndex,
|
||||||
{lastInBinding, mode, name, directiveIndex}:
|
{lastInBinding, mode, name, directiveIndex, argumentToPureFunction}: {
|
||||||
{lastInBinding?: any, mode?: any, name?: any, directiveIndex?: any} = {}) {
|
lastInBinding?: any,
|
||||||
|
mode?: any,
|
||||||
|
name?: any,
|
||||||
|
directiveIndex?: any,
|
||||||
|
argumentToPureFunction?: boolean
|
||||||
|
} = {}) {
|
||||||
if (isBlank(lastInBinding)) lastInBinding = false;
|
if (isBlank(lastInBinding)) lastInBinding = false;
|
||||||
if (isBlank(mode)) mode = RecordType.PROPERTY;
|
if (isBlank(mode)) mode = RecordType.PROPERTY;
|
||||||
if (isBlank(name)) name = "name";
|
if (isBlank(name)) name = "name";
|
||||||
if (isBlank(directiveIndex)) directiveIndex = null;
|
if (isBlank(directiveIndex)) directiveIndex = null;
|
||||||
|
if (isBlank(argumentToPureFunction)) argumentToPureFunction = false;
|
||||||
|
|
||||||
return new ProtoRecord(mode, name, funcOrValue, args, null, contextIndex, directiveIndex,
|
return new ProtoRecord(mode, name, funcOrValue, args, null, contextIndex, directiveIndex,
|
||||||
selfIndex, null, null, lastInBinding, false);
|
selfIndex, null, null, lastInBinding, false, argumentToPureFunction,
|
||||||
|
false);
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("change detection - coalesce", () => {
|
describe("change detection - coalesce", () => {
|
||||||
|
@ -56,7 +64,14 @@ export function main() {
|
||||||
[r("user", [], 0, 1, {lastInBinding: true}), r("user", [], 0, 2, {lastInBinding: true})]);
|
[r("user", [], 0, 1, {lastInBinding: true}), r("user", [], 0, 2, {lastInBinding: true})]);
|
||||||
|
|
||||||
expect(rs[1]).toEqual(new ProtoRecord(RecordType.SELF, "self", null, [], null, 1, null, 2,
|
expect(rs[1]).toEqual(new ProtoRecord(RecordType.SELF, "self", null, [], null, 1, null, 2,
|
||||||
null, null, true, false));
|
null, null, true, false, false, false));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should set referencedBySelf", () => {
|
||||||
|
var rs = coalesce(
|
||||||
|
[r("user", [], 0, 1, {lastInBinding: true}), r("user", [], 0, 2, {lastInBinding: true})]);
|
||||||
|
|
||||||
|
expect(rs[0].referencedBySelf).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not coalesce directive lifecycle records", () => {
|
it("should not coalesce directive lifecycle records", () => {
|
||||||
|
@ -88,5 +103,26 @@ export function main() {
|
||||||
]);
|
]);
|
||||||
expect(rs.length).toEqual(4);
|
expect(rs.length).toEqual(4);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should preserve the argumentToPureFunction property', () => {
|
||||||
|
var rs = coalesce([
|
||||||
|
r("user", [], 0, 1),
|
||||||
|
r("user", [], 0, 2, {argumentToPureFunction: true}),
|
||||||
|
r("user", [], 0, 3),
|
||||||
|
r("name", [], 3, 4)
|
||||||
|
]);
|
||||||
|
expect(rs)
|
||||||
|
.toEqual([r("user", [], 0, 1, {argumentToPureFunction: true}), r("name", [], 1, 2)]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should preserve the argumentToPureFunction property (the original record)', () => {
|
||||||
|
var rs = coalesce([
|
||||||
|
r("user", [], 0, 1, {argumentToPureFunction: true}),
|
||||||
|
r("user", [], 0, 2),
|
||||||
|
r("name", [], 2, 3)
|
||||||
|
]);
|
||||||
|
expect(rs)
|
||||||
|
.toEqual([r("user", [], 0, 1, {argumentToPureFunction: true}), r("name", [], 1, 2)]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
import {
|
||||||
|
ddescribe,
|
||||||
|
describe,
|
||||||
|
it,
|
||||||
|
iit,
|
||||||
|
xit,
|
||||||
|
expect,
|
||||||
|
beforeEach,
|
||||||
|
afterEach,
|
||||||
|
inject
|
||||||
|
} from 'angular2/test_lib';
|
||||||
|
|
||||||
|
import {ProtoRecordBuilder} from 'angular2/src/change_detection/proto_change_detector';
|
||||||
|
import {BindingRecord} from 'angular2/src/change_detection/binding_record';
|
||||||
|
import {Parser} from 'angular2/src/change_detection/parser/parser';
|
||||||
|
|
||||||
|
export function main() {
|
||||||
|
describe("ProtoRecordBuilder", () => {
|
||||||
|
it('should set argumentToPureFunction flag', inject([Parser], (p: Parser) => {
|
||||||
|
var builder = new ProtoRecordBuilder();
|
||||||
|
var ast = p.parseBinding("[1,2]", "location"); // collection literal is a pure function
|
||||||
|
builder.add(BindingRecord.createForElementProperty(ast, 0, "property"), []);
|
||||||
|
|
||||||
|
var isPureFunc = builder.records.map(r => r.argumentToPureFunction);
|
||||||
|
expect(isPureFunc).toEqual([true, true, false]);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should not set argumentToPureFunction flag when not needed',
|
||||||
|
inject([Parser], (p: Parser) => {
|
||||||
|
var builder = new ProtoRecordBuilder();
|
||||||
|
var ast = p.parseBinding("f(1,2)", "location");
|
||||||
|
builder.add(BindingRecord.createForElementProperty(ast, 0, "property"), []);
|
||||||
|
|
||||||
|
var isPureFunc = builder.records.map(r => r.argumentToPureFunction);
|
||||||
|
expect(isPureFunc).toEqual([false, false, false]);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach} from 'angular2/test_lib';
|
||||||
|
import {isBlank} from 'angular2/src/facade/lang';
|
||||||
|
|
||||||
|
import {RecordType, ProtoRecord} from 'angular2/src/change_detection/proto_record';
|
||||||
|
|
||||||
|
export function main() {
|
||||||
|
function r({lastInBinding, mode, name, directiveIndex, argumentToPureFunction, referencedBySelf}:
|
||||||
|
{
|
||||||
|
lastInBinding?: any,
|
||||||
|
mode?: any,
|
||||||
|
name?: any,
|
||||||
|
directiveIndex?: any,
|
||||||
|
argumentToPureFunction?: boolean,
|
||||||
|
referencedBySelf?: boolean
|
||||||
|
} = {}) {
|
||||||
|
if (isBlank(lastInBinding)) lastInBinding = false;
|
||||||
|
if (isBlank(mode)) mode = RecordType.PROPERTY;
|
||||||
|
if (isBlank(name)) name = "name";
|
||||||
|
if (isBlank(directiveIndex)) directiveIndex = null;
|
||||||
|
if (isBlank(argumentToPureFunction)) argumentToPureFunction = false;
|
||||||
|
if (isBlank(referencedBySelf)) referencedBySelf = false;
|
||||||
|
|
||||||
|
return new ProtoRecord(mode, name, null, [], null, 0, directiveIndex, 0, null, null,
|
||||||
|
lastInBinding, false, argumentToPureFunction, referencedBySelf);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("ProtoRecord", () => {
|
||||||
|
describe('shouldBeChecked', () => {
|
||||||
|
it('should be true for pure functions', () => {
|
||||||
|
expect(r({mode: RecordType.COLLECTION_LITERAL}).shouldBeChecked()).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be true for args of pure functions', () => {
|
||||||
|
expect(r({mode: RecordType.CONST, argumentToPureFunction: true}).shouldBeChecked())
|
||||||
|
.toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be true for last in binding records', () => {
|
||||||
|
expect(r({mode: RecordType.CONST, lastInBinding: true}).shouldBeChecked()).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be false otherwise',
|
||||||
|
() => { expect(r({mode: RecordType.CONST}).shouldBeChecked()).toBeFalsy(); });
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isUsedByOtherRecord', () => {
|
||||||
|
it('should be false for lastInBinding records',
|
||||||
|
() => { expect(r({lastInBinding: true}).isUsedByOtherRecord()).toBeFalsy(); });
|
||||||
|
|
||||||
|
it('should be true for lastInBinding records that are referenced by self records', () => {
|
||||||
|
expect(r({lastInBinding: true, referencedBySelf: true}).isUsedByOtherRecord()).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be true for non lastInBinding records',
|
||||||
|
() => { expect(r({lastInBinding: false}).isUsedByOtherRecord()).toBeTruthy(); });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
|
@ -38,9 +38,8 @@ class _MyComponent_ChangeDetector0
|
||||||
var l_context = this.context,
|
var l_context = this.context,
|
||||||
l_myNum0,
|
l_myNum0,
|
||||||
c_myNum0,
|
c_myNum0,
|
||||||
l_interpolate1,
|
l_interpolate1;
|
||||||
c_interpolate1;
|
c_myNum0 = false;
|
||||||
c_myNum0 = c_interpolate1 = false;
|
|
||||||
var isChanged = false;
|
var isChanged = false;
|
||||||
var changes = null;
|
var changes = null;
|
||||||
|
|
||||||
|
@ -56,7 +55,6 @@ class _MyComponent_ChangeDetector0
|
||||||
l_interpolate1 =
|
l_interpolate1 =
|
||||||
"Salad: " "${l_myNum0 == null ? "" : l_myNum0}" " is awesome";
|
"Salad: " "${l_myNum0 == null ? "" : l_myNum0}" " is awesome";
|
||||||
if (_gen.looseNotIdentical(l_interpolate1, this.interpolate1)) {
|
if (_gen.looseNotIdentical(l_interpolate1, this.interpolate1)) {
|
||||||
c_interpolate1 = true;
|
|
||||||
if (throwOnChange) {
|
if (throwOnChange) {
|
||||||
_gen.ChangeDetectionUtil.throwOnChange(this.currentProto,
|
_gen.ChangeDetectionUtil.throwOnChange(this.currentProto,
|
||||||
_gen.ChangeDetectionUtil.simpleChange(
|
_gen.ChangeDetectionUtil.simpleChange(
|
||||||
|
@ -68,8 +66,6 @@ class _MyComponent_ChangeDetector0
|
||||||
|
|
||||||
this.interpolate1 = l_interpolate1;
|
this.interpolate1 = l_interpolate1;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
l_interpolate1 = this.interpolate1;
|
|
||||||
}
|
}
|
||||||
changes = null;
|
changes = null;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue