Skip to content

Commit af41fa9

Browse files
committed
feat(change_detection): modify change detectors to recompute pure functions only when their args change
1 parent 2793d47 commit af41fa9

File tree

4 files changed

+99
-7
lines changed

4 files changed

+99
-7
lines changed

modules/change_detection/src/change_detection_jit_generator.es6

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -138,9 +138,10 @@ ${type}.prototype.detectChangesInRecords = function(throwOnChange) {
138138
}
139139

140140

141-
function bodyTemplate(localDefinitions:string, records:string):string {
141+
function bodyTemplate(localDefinitions:string, changeDefinitions:string, records:string):string {
142142
return `
143143
${localDefinitions}
144+
${changeDefinitions}
144145
var ${TEMP_LOCAL};
145146
var ${CHANGE_LOCAL};
146147
var ${CHANGES_LOCAL} = [];
@@ -172,10 +173,11 @@ ${notify}
172173
`;
173174
}
174175

175-
function referenceCheckTemplate(assignment, newValue, oldValue, addRecord, notify) {
176+
function referenceCheckTemplate(assignment, newValue, oldValue, change, addRecord, notify) {
176177
return `
177178
${assignment}
178179
if (${newValue} !== ${oldValue} || (${newValue} !== ${newValue}) && (${oldValue} !== ${oldValue})) {
180+
${change} = true;
179181
${addRecord}
180182
${oldValue} = ${newValue};
181183
}
@@ -202,10 +204,23 @@ function localDefinitionsTemplate(names:List):string {
202204
return names.map((n) => `var ${n};`).join("\n");
203205
}
204206

207+
function changeDefinitionsTemplate(names:List):string {
208+
return names.map((n) => `var ${n} = false;`).join("\n");
209+
}
210+
205211
function fieldDefinitionsTemplate(names:List):string {
206212
return names.map((n) => `${n} = ${UTIL}.unitialized();`).join("\n");
207213
}
208214

215+
function ifChangedGuardTemplate(changeNames:List, body:string):string {
216+
var cond = changeNames.join(" || ");
217+
return `
218+
if (${cond}) {
219+
${body}
220+
}
221+
`;
222+
}
223+
209224
function addSimpleChangeRecordTemplate(protoIndex:number, oldValue:string, newValue:string) {
210225
return `${CHANGES_LOCAL}.push(${UTIL}.simpleChangeRecord(${PROTOS_ACCESSOR}[${protoIndex}].bindingMemento, ${oldValue}, ${newValue}));`;
211226
}
@@ -215,13 +230,15 @@ export class ChangeDetectorJITGenerator {
215230
typeName:string;
216231
records:List<ProtoRecord>;
217232
localNames:List<String>;
233+
changeNames:List<String>;
218234
fieldNames:List<String>;
219235

220236
constructor(typeName:string, records:List<ProtoRecord>) {
221237
this.typeName = typeName;
222238
this.records = records;
223239

224240
this.localNames = this.getLocalNames(records);
241+
this.changeNames = this.getChangeNames(this.localNames);
225242
this.fieldNames = this.getFieldNames(this.localNames);
226243
}
227244

@@ -234,6 +251,10 @@ export class ChangeDetectorJITGenerator {
234251
return ["context"].concat(names);
235252
}
236253

254+
getChangeNames(localNames:List<String>):List<String> {
255+
return localNames.map((n) => `change_${n}`);
256+
}
257+
237258
getFieldNames(localNames:List<String>):List<String> {
238259
return localNames.map((n) => `this.${n}`);
239260
}
@@ -259,13 +280,17 @@ export class ChangeDetectorJITGenerator {
259280

260281
genBody():string {
261282
var rec = this.records.map((r) => this.genRecord(r)).join("\n");
262-
return bodyTemplate(this.genLocalDefinitions(), rec);
283+
return bodyTemplate(this.genLocalDefinitions(), this.genChangeDefinitions(), rec);
263284
}
264285

265286
genLocalDefinitions():string {
266287
return localDefinitionsTemplate(this.localNames);
267288
}
268289

290+
genChangeDefinitions():string {
291+
return changeDefinitionsTemplate(this.changeNames);
292+
}
293+
269294
genRecord(r:ProtoRecord):string {
270295
if (r.mode == RECORD_TYPE_STRUCTURAL_CHECK) {
271296
return this.getStructuralCheck(r);
@@ -283,10 +308,17 @@ export class ChangeDetectorJITGenerator {
283308
genReferenceCheck(r:ProtoRecord):string {
284309
var newValue = this.localNames[r.selfIndex];
285310
var oldValue = this.fieldNames[r.selfIndex];
311+
var change = this.changeNames[r.selfIndex];
286312
var assignment = this.genUpdateCurrentValue(r);
287313
var addRecord = addSimpleChangeRecordTemplate(r.selfIndex - 1, oldValue, newValue);
288314
var notify = this.genNotify(r);
289-
return referenceCheckTemplate(assignment, newValue, oldValue, r.lastInBinding ? addRecord : '', notify);
315+
316+
var check = referenceCheckTemplate(assignment, newValue, oldValue, change, r.lastInBinding ? addRecord : '', notify);;
317+
if (r.isPureFunction()) {
318+
return this.ifChangedGuard(r, check);
319+
} else {
320+
return check;
321+
}
290322
}
291323

292324
genUpdateCurrentValue(r:ProtoRecord):string {
@@ -320,18 +352,22 @@ export class ChangeDetectorJITGenerator {
320352
case RECORD_TYPE_INTERPOLATE:
321353
return assignmentTemplate(newValue, this.genInterpolation(r));
322354
355+
case RECORD_TYPE_INVOKE_FORMATTER:
356+
return assignmentTemplate(newValue, `${FORMATTERS_ACCESSOR}.get("${r.name}")(${args})`);
357+
323358
case RECORD_TYPE_KEYED_ACCESS:
324359
var key = this.localNames[r.args[0]];
325360
return assignmentTemplate(newValue, `${context}[${key}]`);
326361
327-
case RECORD_TYPE_INVOKE_FORMATTER:
328-
return assignmentTemplate(newValue, `${FORMATTERS_ACCESSOR}.get("${r.name}")(${args})`);
329-
330362
default:
331363
throw new BaseException(`Unknown operation ${r.mode}`);
332364
}
333365
}
334366

367+
ifChangedGuard(r:ProtoRecord, body:string):string {
368+
return ifChangedGuardTemplate(r.args.map((a) => this.changeNames[a]), body);
369+
}
370+
335371
genInterpolation(r:ProtoRecord):string{
336372
var res = "";
337373
for (var i = 0; i < r.args.length; ++i) {

modules/change_detection/src/dynamic_change_detector.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,19 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
3030
dispatcher:any;
3131
formatters:Map;
3232
values:List;
33+
changes:List;
3334
protos:List<ProtoRecord>;
3435

3536
constructor(dispatcher:any, formatters:Map, protoRecords:List<ProtoRecord>) {
3637
super();
3738
this.dispatcher = dispatcher;
3839
this.formatters = formatters;
40+
3941
this.values = ListWrapper.createFixedSize(protoRecords.length + 1);
4042
ListWrapper.fill(this.values, uninitialized);
43+
44+
this.changes = ListWrapper.createFixedSize(protoRecords.length + 1);
45+
4146
this.protos = protoRecords;
4247
}
4348

@@ -82,17 +87,25 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
8287
}
8388

8489
_referenceCheck(proto:ProtoRecord) {
90+
if (this._pureFuncAndArgsDidNotChange(proto)) {
91+
this._setChanged(proto, false);
92+
return null;
93+
}
94+
8595
var prevValue = this._readSelf(proto);
8696
var currValue = this._calculateCurrValue(proto);
8797

8898
if (!isSame(prevValue, currValue)) {
8999
this._writeSelf(proto, currValue);
100+
this._setChanged(proto, true);
101+
90102
if (proto.lastInBinding) {
91103
return new SimpleChange(prevValue, currValue);
92104
} else {
93105
return null;
94106
}
95107
} else {
108+
this._setChanged(proto, false);
96109
return null;
97110
}
98111
}
@@ -179,6 +192,24 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
179192
this.values[proto.selfIndex] = value;
180193
}
181194

195+
_setChanged(proto:ProtoRecord, value:boolean) {
196+
this.changes[proto.selfIndex] = value;
197+
}
198+
199+
_pureFuncAndArgsDidNotChange(proto:ProtoRecord):boolean {
200+
return proto.isPureFunction() && !this._argsChanged(proto);
201+
}
202+
203+
_argsChanged(proto:ProtoRecord):boolean {
204+
var args = proto.args;
205+
for(var i = 0; i < args.length; ++i) {
206+
if (this.changes[args[i]]) {
207+
return true;
208+
}
209+
}
210+
return false;
211+
}
212+
182213
_readArgs(proto:ProtoRecord) {
183214
var res = ListWrapper.createFixedSize(proto.args.length);
184215
var args = proto.args;

modules/change_detection/src/proto_change_detector.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,12 @@ export class ProtoRecord {
8484
this.lastInGroup = lastInGroup;
8585
this.expressionAsString = expressionAsString;
8686
}
87+
88+
isPureFunction():boolean {
89+
return this.mode === RECORD_TYPE_INTERPOLATE ||
90+
this.mode === RECORD_TYPE_INVOKE_FORMATTER ||
91+
this.mode === RECORD_TYPE_PRIMITIVE_OP;
92+
}
8793
}
8894

8995
export class ProtoChangeDetector {

modules/change_detection/test/change_detection_spec.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,25 @@ export function main() {
443443
});
444444
});
445445
});
446+
447+
describe("optimizations", () => {
448+
it("should not rerun formatters when args did not change", () => {
449+
var count = 0;
450+
var formatters = MapWrapper.createFromPairs([
451+
['count', (v) => {count ++; "value"}]]);
452+
453+
var c = createChangeDetector('a', 'a | count', new TestData(null), formatters);
454+
var cd = c["changeDetector"];
455+
456+
cd.detectChanges();
457+
458+
expect(count).toEqual(1);
459+
460+
cd.detectChanges();
461+
462+
expect(count).toEqual(1);
463+
});
464+
});
446465
});
447466
});
448467
}

0 commit comments

Comments
 (0)