X Tutup
Skip to content

Commit 7e92d2e

Browse files
committed
feat(ChangeDetector): Add support for short-circuiting
1 parent b913514 commit 7e92d2e

File tree

10 files changed

+579
-110
lines changed

10 files changed

+579
-110
lines changed

modules/angular2/src/core/change_detection/change_detection_jit_generator.ts

Lines changed: 82 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const CHANGES_LOCAL = "changes";
3131
export class ChangeDetectorJITGenerator {
3232
private _logic: CodegenLogicUtil;
3333
private _names: CodegenNameUtil;
34+
private _endOfBlockIdxs: number[];
3435
private id: string;
3536
private changeDetectionStrategy: ChangeDetectionStrategy;
3637
private records: ProtoRecord[];
@@ -91,7 +92,7 @@ export class ChangeDetectorJITGenerator {
9192
var ${IS_CHANGED_LOCAL} = false;
9293
var ${CHANGES_LOCAL} = null;
9394
94-
${this.records.map((r) => this._genRecord(r)).join("\n")}
95+
${this._genAllRecords(this.records)}
9596
}
9697
9798
${this._maybeGenHandleEventInternal()}
@@ -144,10 +145,28 @@ export class ChangeDetectorJITGenerator {
144145

145146
/** @internal */
146147
_genEventBinding(eb: EventBinding): string {
147-
var recs = eb.records.map(r => this._genEventBindingEval(eb, r)).join("\n");
148+
let codes: String[] = [];
149+
this._endOfBlockIdxs = [];
150+
151+
ListWrapper.forEachWithIndex(eb.records, (r, i) => {
152+
let code;
153+
154+
if (r.isConditionalSkipRecord()) {
155+
code = this._genConditionalSkip(r, this._names.getEventLocalName(eb, i));
156+
} else if (r.isUnconditionalSkipRecord()) {
157+
code = this._genUnconditionalSkip(r);
158+
} else {
159+
code = this._genEventBindingEval(eb, r);
160+
}
161+
162+
code += this._genEndOfSkipBlock(i);
163+
164+
codes.push(code);
165+
});
166+
148167
return `
149168
if (eventName === "${eb.eventName}" && elIndex === ${eb.elIndex}) {
150-
${recs}
169+
${codes.join("\n")}
151170
}`;
152171
}
153172

@@ -235,20 +254,65 @@ export class ChangeDetectorJITGenerator {
235254
}
236255

237256
/** @internal */
238-
_genRecord(r: ProtoRecord): string {
239-
var rec;
240-
if (r.isLifeCycleRecord()) {
241-
rec = this._genDirectiveLifecycle(r);
242-
} else if (r.isPipeRecord()) {
243-
rec = this._genPipeCheck(r);
244-
} else {
245-
rec = this._genReferenceCheck(r);
257+
_genAllRecords(rs: ProtoRecord[]): string {
258+
var codes: String[] = [];
259+
this._endOfBlockIdxs = [];
260+
261+
for (let i = 0; i < rs.length; i++) {
262+
let code;
263+
let r = rs[i];
264+
265+
if (r.isLifeCycleRecord()) {
266+
code = this._genDirectiveLifecycle(r);
267+
} else if (r.isPipeRecord()) {
268+
code = this._genPipeCheck(r);
269+
} else if (r.isConditionalSkipRecord()) {
270+
code = this._genConditionalSkip(r, this._names.getLocalName(r.contextIndex));
271+
} else if (r.isUnconditionalSkipRecord()) {
272+
code = this._genUnconditionalSkip(r);
273+
} else {
274+
code = this._genReferenceCheck(r);
275+
}
276+
277+
code = `
278+
${this._maybeFirstInBinding(r)}
279+
${code}
280+
${this._maybeGenLastInDirective(r)}
281+
${this._genEndOfSkipBlock(i)}
282+
`;
283+
284+
codes.push(code);
246285
}
247-
return `
248-
${this._maybeFirstInBinding(r)}
249-
${rec}
250-
${this._maybeGenLastInDirective(r)}
251-
`;
286+
287+
return codes.join("\n");
288+
}
289+
290+
/** @internal */
291+
_genConditionalSkip(r: ProtoRecord, condition: string): string {
292+
let maybeNegate = r.mode === RecordType.SkipRecordsIf ? '!' : '';
293+
this._endOfBlockIdxs.push(r.fixedArgs[0] - 1);
294+
295+
return `if (${maybeNegate}${condition}) {`;
296+
}
297+
298+
/** @internal */
299+
_genUnconditionalSkip(r: ProtoRecord): string {
300+
this._endOfBlockIdxs.pop();
301+
this._endOfBlockIdxs.push(r.fixedArgs[0] - 1);
302+
return `} else {`;
303+
}
304+
305+
/** @internal */
306+
_genEndOfSkipBlock(protoIndex: number): string {
307+
if (!ListWrapper.isEmpty(this._endOfBlockIdxs)) {
308+
let endOfBlock = ListWrapper.last(this._endOfBlockIdxs);
309+
if (protoIndex === endOfBlock) {
310+
this._endOfBlockIdxs.pop();
311+
return '}';
312+
}
313+
}
314+
315+
return '';
252316
}
253317

254318
/** @internal */
@@ -401,8 +465,8 @@ export class ChangeDetectorJITGenerator {
401465
/** @internal */
402466
_maybeFirstInBinding(r: ProtoRecord): string {
403467
var prev = ChangeDetectionUtil.protoByIndex(this.records, r.selfIndex - 1);
404-
var firstInBindng = isBlank(prev) || prev.bindingRecord !== r.bindingRecord;
405-
return firstInBindng && !r.bindingRecord.isDirectiveLifecycle() ?
468+
var firstInBinding = isBlank(prev) || prev.bindingRecord !== r.bindingRecord;
469+
return firstInBinding && !r.bindingRecord.isDirectiveLifecycle() ?
406470
`${this._names.getPropertyBindingIndex()} = ${r.propertyBindingIndex};` :
407471
'';
408472
}

modules/angular2/src/core/change_detection/change_detection_util.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,6 @@ export class ChangeDetectionUtil {
126126
static operation_greater_then(left, right): any { return left > right; }
127127
static operation_less_or_equals_then(left, right): any { return left <= right; }
128128
static operation_greater_or_equals_then(left, right): any { return left >= right; }
129-
static operation_logical_and(left, right): any { return left && right; }
130-
static operation_logical_or(left, right): any { return left || right; }
131129
static cond(cond, trueVal, falseVal): any { return cond ? trueVal : falseVal; }
132130

133131
static mapFn(keys: any[]): any {
Lines changed: 136 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {isPresent, isBlank, looseIdentical, StringWrapper} from 'angular2/src/core/facade/lang';
1+
import {isPresent, isBlank, looseIdentical} from 'angular2/src/core/facade/lang';
22
import {ListWrapper, Map} from 'angular2/src/core/facade/collection';
33
import {RecordType, ProtoRecord} from './proto_record';
44

@@ -13,70 +13,163 @@ import {RecordType, ProtoRecord} from './proto_record';
1313
*
1414
* @internal
1515
*/
16-
export function coalesce(records: ProtoRecord[]): ProtoRecord[] {
17-
var res: ProtoRecord[] = [];
18-
var indexMap: Map<number, number> = new Map<number, number>();
16+
export function coalesce(srcRecords: ProtoRecord[]): ProtoRecord[] {
17+
let dstRecords = [];
18+
let excludedIdxs = [];
19+
let indexMap: Map<number, number> = new Map<number, number>();
20+
let skipDepth = 0;
21+
let skipSources: ProtoRecord[] = ListWrapper.createFixedSize(srcRecords.length);
1922

20-
for (var i = 0; i < records.length; ++i) {
21-
var r = records[i];
22-
var record = _replaceIndices(r, res.length + 1, indexMap);
23-
var matchingRecord = _findMatching(record, res);
23+
for (let protoIndex = 0; protoIndex < srcRecords.length; protoIndex++) {
24+
let skipRecord = skipSources[protoIndex];
25+
if (isPresent(skipRecord)) {
26+
skipDepth--;
27+
skipRecord.fixedArgs[0] = dstRecords.length;
28+
}
2429

25-
if (isPresent(matchingRecord) && record.lastInBinding) {
26-
res.push(_selfRecord(record, matchingRecord.selfIndex, res.length + 1));
27-
indexMap.set(r.selfIndex, matchingRecord.selfIndex);
28-
matchingRecord.referencedBySelf = true;
30+
let src = srcRecords[protoIndex];
31+
let dst = _cloneAndUpdateIndexes(src, dstRecords, indexMap);
2932

30-
} else if (isPresent(matchingRecord) && !record.lastInBinding) {
31-
if (record.argumentToPureFunction) {
32-
matchingRecord.argumentToPureFunction = true;
33+
if (dst.isSkipRecord()) {
34+
dstRecords.push(dst);
35+
skipDepth++;
36+
skipSources[dst.fixedArgs[0]] = dst;
37+
} else {
38+
let record = _mayBeAddRecord(dst, dstRecords, excludedIdxs, skipDepth > 0);
39+
indexMap.set(src.selfIndex, record.selfIndex);
40+
}
41+
}
42+
43+
return _optimizeSkips(dstRecords);
44+
}
45+
46+
/**
47+
* - Conditional skip of 1 record followed by an unconditional skip of N are replaced by a
48+
* conditional skip of N with the negated condition,
49+
* - Skips of 0 records are removed
50+
*/
51+
function _optimizeSkips(srcRecords: ProtoRecord[]): ProtoRecord[] {
52+
let dstRecords = [];
53+
let skipSources = ListWrapper.createFixedSize(srcRecords.length);
54+
let indexMap: Map<number, number> = new Map<number, number>();
55+
56+
for (let protoIndex = 0; protoIndex < srcRecords.length; protoIndex++) {
57+
let skipRecord = skipSources[protoIndex];
58+
if (isPresent(skipRecord)) {
59+
skipRecord.fixedArgs[0] = dstRecords.length;
60+
}
61+
62+
let src = srcRecords[protoIndex];
63+
64+
if (src.isSkipRecord()) {
65+
if (src.isConditionalSkipRecord() && src.fixedArgs[0] === protoIndex + 2 &&
66+
protoIndex < srcRecords.length - 1 &&
67+
srcRecords[protoIndex + 1].mode === RecordType.SkipRecords) {
68+
src.mode = src.mode === RecordType.SkipRecordsIf ? RecordType.SkipRecordsIfNot :
69+
RecordType.SkipRecordsIf;
70+
src.fixedArgs[0] = srcRecords[protoIndex + 1].fixedArgs[0];
71+
protoIndex++;
3372
}
3473

35-
indexMap.set(r.selfIndex, matchingRecord.selfIndex);
74+
if (src.fixedArgs[0] > protoIndex + 1) {
75+
let dst = _cloneAndUpdateIndexes(src, dstRecords, indexMap);
76+
dstRecords.push(dst);
77+
skipSources[dst.fixedArgs[0]] = dst;
78+
}
3679

3780
} else {
38-
res.push(record);
39-
indexMap.set(r.selfIndex, record.selfIndex);
81+
let dst = _cloneAndUpdateIndexes(src, dstRecords, indexMap);
82+
dstRecords.push(dst);
83+
indexMap.set(src.selfIndex, dst.selfIndex);
4084
}
4185
}
4286

43-
return res;
87+
return dstRecords;
4488
}
4589

46-
function _selfRecord(r: ProtoRecord, contextIndex: number, selfIndex: number): ProtoRecord {
47-
return new ProtoRecord(RecordType.Self, "self", null, [], r.fixedArgs, contextIndex,
48-
r.directiveIndex, selfIndex, r.bindingRecord, r.lastInBinding,
49-
r.lastInDirective, false, false, r.propertyBindingIndex);
90+
/**
91+
* Add a new record or re-use one of the existing records.
92+
*/
93+
function _mayBeAddRecord(record: ProtoRecord, dstRecords: ProtoRecord[], excludedIdxs: number[],
94+
excluded: boolean): ProtoRecord {
95+
let match = _findFirstMatch(record, dstRecords, excludedIdxs);
96+
97+
if (isPresent(match)) {
98+
if (record.lastInBinding) {
99+
dstRecords.push(_createSelfRecord(record, match.selfIndex, dstRecords.length + 1));
100+
match.referencedBySelf = true;
101+
} else {
102+
if (record.argumentToPureFunction) {
103+
match.argumentToPureFunction = true;
104+
}
105+
}
106+
107+
return match;
108+
}
109+
110+
if (excluded) {
111+
excludedIdxs.push(record.selfIndex);
112+
}
113+
114+
dstRecords.push(record);
115+
return record;
50116
}
51117

52-
function _findMatching(r: ProtoRecord, rs: ProtoRecord[]) {
118+
/**
119+
* Returns the first `ProtoRecord` that matches the record.
120+
*/
121+
function _findFirstMatch(record: ProtoRecord, dstRecords: ProtoRecord[],
122+
excludedIdxs: number[]): ProtoRecord {
53123
return ListWrapper.find(
54-
rs, (rr) => rr.mode !== RecordType.DirectiveLifecycle && _sameDirIndex(rr, r) &&
55-
rr.mode === r.mode && looseIdentical(rr.funcOrValue, r.funcOrValue) &&
56-
rr.contextIndex === r.contextIndex && StringWrapper.equals(rr.name, r.name) &&
57-
ListWrapper.equals(rr.args, r.args));
124+
dstRecords,
125+
// TODO(vicb): optimize notReusableIndexes.indexOf (sorted array)
126+
rr => excludedIdxs.indexOf(rr.selfIndex) == -1 && rr.mode !== RecordType.DirectiveLifecycle &&
127+
_haveSameDirIndex(rr, record) && rr.mode === record.mode &&
128+
looseIdentical(rr.funcOrValue, record.funcOrValue) &&
129+
rr.contextIndex === record.contextIndex && looseIdentical(rr.name, record.name) &&
130+
ListWrapper.equals(rr.args, record.args));
58131
}
59132

60-
function _sameDirIndex(a: ProtoRecord, b: ProtoRecord): boolean {
61-
var di1 = isBlank(a.directiveIndex) ? null : a.directiveIndex.directiveIndex;
62-
var ei1 = isBlank(a.directiveIndex) ? null : a.directiveIndex.elementIndex;
133+
/**
134+
* Clone the `ProtoRecord` and changes the indexes for the ones in the destination array for:
135+
* - the arguments,
136+
* - the context,
137+
* - self
138+
*/
139+
function _cloneAndUpdateIndexes(record: ProtoRecord, dstRecords: ProtoRecord[],
140+
indexMap: Map<number, number>): ProtoRecord {
141+
let args = record.args.map(src => _srcToDstSelfIndex(indexMap, src));
142+
let contextIndex = _srcToDstSelfIndex(indexMap, record.contextIndex);
143+
let selfIndex = dstRecords.length + 1;
63144

64-
var di2 = isBlank(b.directiveIndex) ? null : b.directiveIndex.directiveIndex;
65-
var ei2 = isBlank(b.directiveIndex) ? null : b.directiveIndex.elementIndex;
145+
return new ProtoRecord(record.mode, record.name, record.funcOrValue, args, record.fixedArgs,
146+
contextIndex, record.directiveIndex, selfIndex, record.bindingRecord,
147+
record.lastInBinding, record.lastInDirective,
148+
record.argumentToPureFunction, record.referencedBySelf,
149+
record.propertyBindingIndex);
150+
}
66151

67-
return di1 === di2 && ei1 === ei2;
152+
/**
153+
* Returns the index in the destination array corresponding to the index in the src array.
154+
* When the element is not present in the destination array, return the source index.
155+
*/
156+
function _srcToDstSelfIndex(indexMap: Map<number, number>, srcIdx: number): number {
157+
var dstIdx = indexMap.get(srcIdx);
158+
return isPresent(dstIdx) ? dstIdx : srcIdx;
68159
}
69160

70-
function _replaceIndices(r: ProtoRecord, selfIndex: number, indexMap: Map<any, any>) {
71-
var args = r.args.map(a => _map(indexMap, a));
72-
var contextIndex = _map(indexMap, r.contextIndex);
73-
return new ProtoRecord(r.mode, r.name, r.funcOrValue, args, r.fixedArgs, contextIndex,
161+
function _createSelfRecord(r: ProtoRecord, contextIndex: number, selfIndex: number): ProtoRecord {
162+
return new ProtoRecord(RecordType.Self, "self", null, [], r.fixedArgs, contextIndex,
74163
r.directiveIndex, selfIndex, r.bindingRecord, r.lastInBinding,
75-
r.lastInDirective, r.argumentToPureFunction, r.referencedBySelf,
76-
r.propertyBindingIndex);
164+
r.lastInDirective, false, false, r.propertyBindingIndex);
77165
}
78166

79-
function _map(indexMap: Map<any, any>, value: number) {
80-
var r = indexMap.get(value);
81-
return isPresent(r) ? r : value;
167+
function _haveSameDirIndex(a: ProtoRecord, b: ProtoRecord): boolean {
168+
var di1 = isBlank(a.directiveIndex) ? null : a.directiveIndex.directiveIndex;
169+
var ei1 = isBlank(a.directiveIndex) ? null : a.directiveIndex.elementIndex;
170+
171+
var di2 = isBlank(b.directiveIndex) ? null : b.directiveIndex.directiveIndex;
172+
var ei2 = isBlank(b.directiveIndex) ? null : b.directiveIndex.elementIndex;
173+
174+
return di1 === di2 && ei1 === ei2;
82175
}

0 commit comments

Comments
 (0)
X Tutup