refactor(change_detect): Move common fields to AbstractChangeDetector

Move fields common to Dynamic, Jit, and Pregen change detectors into the
`AbstractChangeDetector` superclass to save on codegen size and reduce
code duplication.

Update to #3248, closes #3243
This commit is contained in:
Tim Blasi
2015-07-28 12:43:41 -07:00
parent d894aa9101
commit 192cf9ddf5
10 changed files with 224 additions and 199 deletions

View File

@ -1,10 +1,12 @@
import {isPresent, BaseException} from 'angular2/src/facade/lang';
import {List, ListWrapper} from 'angular2/src/facade/collection';
import {ChangeDetectorRef} from './change_detector_ref';
import {ChangeDetector} from './interfaces';
import {DirectiveRecord} from './directive_record';
import {ChangeDetector, ChangeDispatcher} from './interfaces';
import {ChangeDetectionError} from './exceptions';
import {ProtoRecord} from './proto_record';
import {Locals} from './parser/locals';
import {Pipes} from './pipes/pipes';
import {CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED, ON_PUSH} from './constants';
class _Context {
@ -13,14 +15,31 @@ class _Context {
public expression: any) {}
}
export class AbstractChangeDetector implements ChangeDetector {
export class AbstractChangeDetector<T> implements ChangeDetector {
lightDomChildren: List<any> = [];
shadowDomChildren: List<any> = [];
parent: ChangeDetector;
mode: string = null;
ref: ChangeDetectorRef;
constructor(public id: string, public dispatcher: any) { this.ref = new ChangeDetectorRef(this); }
// The names of the below fields must be kept in sync with codegen_name_util.ts or
// change detection will fail.
alreadyChecked: any = false;
context: T;
currentProto: ProtoRecord = null;
directiveRecords: List<DirectiveRecord>;
dispatcher: ChangeDispatcher;
locals: Locals = null;
mode: string = null;
pipes: Pipes = null;
protos: List<ProtoRecord>;
constructor(public id: string, dispatcher: ChangeDispatcher, protos: List<ProtoRecord>,
directiveRecords: List<DirectiveRecord>) {
this.ref = new ChangeDetectorRef(this);
this.directiveRecords = directiveRecords;
this.dispatcher = dispatcher;
this.protos = protos;
}
addChild(cd: ChangeDetector): void {
this.lightDomChildren.push(cd);
@ -58,7 +77,7 @@ export class AbstractChangeDetector implements ChangeDetector {
detectChangesInRecords(throwOnChange: boolean): void {}
hydrate(context: any, locals: Locals, directives: any, pipes: any): void {}
hydrate(context: T, locals: Locals, directives: any, pipes: any): void {}
hydrateDirectives(directives: any): void {}

View File

@ -42,7 +42,8 @@ export {
ChangeDetector,
ChangeDispatcher,
ChangeDetection,
ChangeDetectorDefinition
ChangeDetectorDefinition,
DebugContext
} from './interfaces';
export {CHECK_ONCE, CHECK_ALWAYS, DETACHED, CHECKED, ON_PUSH, DEFAULT} from './constants';
export {DynamicProtoChangeDetector} from './proto_change_detector';

View File

@ -6,7 +6,7 @@ import {ChangeDetectionUtil} from './change_detection_util';
import {DirectiveIndex, DirectiveRecord} from './directive_record';
import {ProtoRecord, RecordType} from './proto_record';
import {CodegenNameUtil, sanitizeName} from './codegen_name_util';
import {CONTEXT_INDEX, CodegenNameUtil, sanitizeName} from './codegen_name_util';
/**
@ -20,16 +20,8 @@ import {CodegenNameUtil, sanitizeName} from './codegen_name_util';
*/
var ABSTRACT_CHANGE_DETECTOR = "AbstractChangeDetector";
var UTIL = "ChangeDetectionUtil";
var DISPATCHER_ACCESSOR = "this.dispatcher";
var PIPES_ACCESSOR = "this.pipes";
var PROTOS_ACCESSOR = "this.protos";
var DIRECTIVES_ACCESSOR = "this.directiveRecords";
var IS_CHANGED_LOCAL = "isChanged";
var CHANGES_LOCAL = "changes";
var LOCALS_ACCESSOR = "this.locals";
var MODE_ACCESSOR = "this.mode";
var CURRENT_PROTO = "this.currentProto";
var ALREADY_CHECKED_ACCESSOR = "this.alreadyChecked";
export class ChangeDetectorJITGenerator {
_names: CodegenNameUtil;
@ -38,20 +30,14 @@ export class ChangeDetectorJITGenerator {
constructor(public id: string, public changeDetectionStrategy: string,
public records: List<ProtoRecord>, public directiveRecords: List<any>,
private generateCheckNoChanges: boolean) {
this._names = new CodegenNameUtil(this.records, this.directiveRecords, 'this._', UTIL);
this._names = new CodegenNameUtil(this.records, this.directiveRecords, UTIL);
this._typeName = sanitizeName(`ChangeDetector_${this.id}`);
}
generate(): Function {
var classDefinition = `
var ${this._typeName} = function ${this._typeName}(dispatcher, protos, directiveRecords) {
${ABSTRACT_CHANGE_DETECTOR}.call(this, ${JSON.stringify(this.id)}, dispatcher);
${PROTOS_ACCESSOR} = protos;
${DIRECTIVES_ACCESSOR} = directiveRecords;
${LOCALS_ACCESSOR} = null;
${CURRENT_PROTO} = null;
${PIPES_ACCESSOR} = null;
${ALREADY_CHECKED_ACCESSOR} = false;
${ABSTRACT_CHANGE_DETECTOR}.call(this, ${JSON.stringify(this.id)}, dispatcher, protos, directiveRecords);
this.dehydrateDirectives(false);
}
@ -64,22 +50,20 @@ export class ChangeDetectorJITGenerator {
try {
this.__detectChangesInRecords(throwOnChange);
} catch (e) {
this.throwError(${CURRENT_PROTO}, e, e.stack);
this.throwError(${this._names.getCurrentProtoName()}, e, e.stack);
}
}
${this._typeName}.prototype.__detectChangesInRecords = function(throwOnChange) {
${CURRENT_PROTO} = null;
${this._names.getCurrentProtoName()} = null;
${this._names.genInitLocals()}
var ${IS_CHANGED_LOCAL} = false;
var ${CHANGES_LOCAL} = null;
context = ${this._names.getContextName()};
${this.records.map((r) => this._genRecord(r)).join("\n")}
${ALREADY_CHECKED_ACCESSOR} = true;
${this._names.getAlreadyCheckedName()} = true;
}
${this._genCheckNoChanges()}
@ -89,26 +73,27 @@ export class ChangeDetectorJITGenerator {
}
${this._typeName}.prototype.hydrate = function(context, locals, directives, pipes) {
${MODE_ACCESSOR} = "${ChangeDetectionUtil.changeDetectionMode(this.changeDetectionStrategy)}";
${this._names.getContextName()} = context;
${LOCALS_ACCESSOR} = locals;
${this._names.getModeName()} =
"${ChangeDetectionUtil.changeDetectionMode(this.changeDetectionStrategy)}";
${this._names.getFieldName(CONTEXT_INDEX)} = context;
${this._names.getLocalsAccessorName()} = locals;
this.hydrateDirectives(directives);
${PIPES_ACCESSOR} = pipes;
${ALREADY_CHECKED_ACCESSOR} = false;
${this._names.getPipesAccessorName()} = pipes;
${this._names.getAlreadyCheckedName()} = false;
}
${this._maybeGenHydrateDirectives()}
${this._typeName}.prototype.dehydrate = function() {
this.dehydrateDirectives(true);
${LOCALS_ACCESSOR} = null;
${PIPES_ACCESSOR} = null;
${this._names.getLocalsAccessorName()} = null;
${this._names.getPipesAccessorName()} = null;
}
${this._maybeGenDehydrateDirectives()}
${this._typeName}.prototype.hydrated = function() {
return ${this._names.getContextName()} !== null;
return ${this._names.getFieldName(CONTEXT_INDEX)} !== null;
}
return function(dispatcher) {
@ -148,8 +133,8 @@ export class ChangeDetectorJITGenerator {
var directiveFieldNames = this._names.getAllDirectiveNames();
var lines = ListWrapper.createFixedSize(directiveFieldNames.length);
for (var i = 0, iLen = directiveFieldNames.length; i < iLen; ++i) {
lines[i] =
`${directiveFieldNames[i]} = directives.getDirectiveFor(${DIRECTIVES_ACCESSOR}[${i}].directiveIndex);`;
lines[i] = `${directiveFieldNames[i]} = directives.getDirectiveFor(
${this._names.getDirectivesAccessorName()}[${i}].directiveIndex);`;
}
return lines.join('\n');
}
@ -158,8 +143,8 @@ export class ChangeDetectorJITGenerator {
var detectorFieldNames = this._names.getAllDetectorNames();
var lines = ListWrapper.createFixedSize(detectorFieldNames.length);
for (var i = 0, iLen = detectorFieldNames.length; i < iLen; ++i) {
lines[i] = `${detectorFieldNames[i]} =
directives.getDetectorFor(${DIRECTIVES_ACCESSOR}[${i}].directiveIndex);`;
lines[i] = `${detectorFieldNames[i]} = directives.getDetectorFor(
${this._names.getDirectivesAccessorName()}[${i}].directiveIndex);`;
}
return lines.join('\n');
}
@ -179,7 +164,7 @@ export class ChangeDetectorJITGenerator {
var directiveNotifications = notifications.join("\n");
return `
this.dispatcher.notifyOnAllChangesDone();
${this._names.getDispatcherName()}.notifyOnAllChangesDone();
${directiveNotifications}
`;
}
@ -223,12 +208,12 @@ export class ChangeDetectorJITGenerator {
var pipeType = r.name;
return `
${CURRENT_PROTO} = ${PROTOS_ACCESSOR}[${protoIndex}];
${this._names.getCurrentProtoName()} = ${this._names.getProtosName()}[${protoIndex}];
if (${pipe} === ${UTIL}.uninitialized) {
${pipe} = ${PIPES_ACCESSOR}.get('${pipeType}', ${context}, ${cdRef});
${pipe} = ${this._names.getPipesAccessorName()}.get('${pipeType}', ${context}, ${cdRef});
} else if (!${pipe}.supports(${context})) {
${pipe}.onDestroy();
${pipe} = ${PIPES_ACCESSOR}.get('${pipeType}', ${context}, ${cdRef});
${pipe} = ${this._names.getPipesAccessorName()}.get('${pipeType}', ${context}, ${cdRef});
}
${newValue} = ${pipe}.transform(${context}, [${argString}]);
@ -248,7 +233,7 @@ export class ChangeDetectorJITGenerator {
var protoIndex = r.selfIndex - 1;
var check = `
${CURRENT_PROTO} = ${PROTOS_ACCESSOR}[${protoIndex}];
${this._names.getCurrentProtoName()} = ${this._names.getProtosName()}[${protoIndex}];
${this._genUpdateCurrentValue(r)}
if (${newValue} !== ${oldValue}) {
${this._names.getChangeName(r.selfIndex)} = true;
@ -291,7 +276,7 @@ export class ChangeDetectorJITGenerator {
break;
case RecordType.LOCAL:
rhs = `${LOCALS_ACCESSOR}.get('${r.name}')`;
rhs = `${this._names.getLocalsAccessorName()}.get('${r.name}')`;
break;
case RecordType.INVOKE_METHOD:
@ -354,7 +339,8 @@ export class ChangeDetectorJITGenerator {
} else {
return `
${this._genThrowOnChangeCheck(oldValue, newValue)}
${DISPATCHER_ACCESSOR}.notifyOnBinding(${CURRENT_PROTO}.bindingRecord, ${newValue});
${this._names.getDispatcherName()}.notifyOnBinding(
${this._names.getCurrentProtoName()}.bindingRecord, ${newValue});
`;
}
}
@ -363,7 +349,7 @@ export class ChangeDetectorJITGenerator {
if (this.generateCheckNoChanges) {
return `
if(throwOnChange) {
${UTIL}.throwOnChange(${CURRENT_PROTO}, ${UTIL}.simpleChange(${oldValue}, ${newValue}));
${UTIL}.throwOnChange(${this._names.getCurrentProtoName()}, ${UTIL}.simpleChange(${oldValue}, ${newValue}));
}
`;
} else {
@ -385,7 +371,7 @@ export class ChangeDetectorJITGenerator {
if (!r.bindingRecord.callOnChange()) return "";
return `
${CHANGES_LOCAL} = ${UTIL}.addChange(
${CHANGES_LOCAL}, ${CURRENT_PROTO}.bindingRecord.propertyName,
${CHANGES_LOCAL}, ${this._names.getCurrentProtoName()}.bindingRecord.propertyName,
${UTIL}.simpleChange(${oldValue}, ${newValue}));
`;
}
@ -406,7 +392,7 @@ export class ChangeDetectorJITGenerator {
_genOnInit(r: ProtoRecord): string {
var br = r.bindingRecord;
return `if (!throwOnChange && !${ALREADY_CHECKED_ACCESSOR}) ${this._names.getDirectiveName(br.directiveRecord.directiveIndex)}.onInit();`;
return `if (!throwOnChange && !${this._names.getAlreadyCheckedName()}) ${this._names.getDirectiveName(br.directiveRecord.directiveIndex)}.onInit();`;
}
_genOnChange(r: ProtoRecord): string {

View File

@ -5,8 +5,21 @@ import {DirectiveIndex} from './directive_record';
import {ProtoRecord} from './proto_record';
// `context` is always the first field.
var _CONTEXT_IDX = 0;
// The names of these fields must be kept in sync with abstract_change_detector.ts or change
// detection will fail.
const _ALREADY_CHECKED_ACCESSOR = "alreadyChecked";
const _CONTEXT_ACCESSOR = "context";
const _CURRENT_PROTO = "currentProto";
const _DIRECTIVES_ACCESSOR = "directiveRecords";
const _DISPATCHER_ACCESSOR = "dispatcher";
const _LOCALS_ACCESSOR = "locals";
const _MODE_ACCESSOR = "mode";
const _PIPES_ACCESSOR = "pipes";
const _PROTOS_ACCESSOR = "protos";
// `context` is always first.
export const CONTEXT_INDEX = 0;
const _FIELD_PREFIX = 'this.';
var _whiteSpaceRegExp = RegExpWrapper.create("\\W", "g");
@ -29,18 +42,34 @@ export class CodegenNameUtil {
*/
_sanitizedNames: List<string>;
constructor(public records: List<ProtoRecord>, public directiveRecords: List<any>,
public fieldPrefix: string, public utilName: string) {
constructor(private records: List<ProtoRecord>, private directiveRecords: List<any>,
private utilName: string) {
this._sanitizedNames = ListWrapper.createFixedSize(this.records.length + 1);
this._sanitizedNames[_CONTEXT_IDX] = 'context';
this._sanitizedNames[CONTEXT_INDEX] = _CONTEXT_ACCESSOR;
for (var i = 0, iLen = this.records.length; i < iLen; ++i) {
this._sanitizedNames[i + 1] = sanitizeName(`${this.records[i].name}${i}`);
}
}
getContextName(): string { return this.getFieldName(_CONTEXT_IDX); }
_addFieldPrefix(name: string): string { return `${_FIELD_PREFIX}${name}`; }
getLocalName(idx: int): string { return this._sanitizedNames[idx]; }
getDispatcherName(): string { return this._addFieldPrefix(_DISPATCHER_ACCESSOR); }
getPipesAccessorName(): string { return this._addFieldPrefix(_PIPES_ACCESSOR); }
getProtosName(): string { return this._addFieldPrefix(_PROTOS_ACCESSOR); }
getDirectivesAccessorName(): string { return this._addFieldPrefix(_DIRECTIVES_ACCESSOR); }
getLocalsAccessorName(): string { return this._addFieldPrefix(_LOCALS_ACCESSOR); }
getAlreadyCheckedName(): string { return this._addFieldPrefix(_ALREADY_CHECKED_ACCESSOR); }
getModeName(): string { return this._addFieldPrefix(_MODE_ACCESSOR); }
getCurrentProtoName(): string { return this._addFieldPrefix(_CURRENT_PROTO); }
getLocalName(idx: int): string { return `l_${this._sanitizedNames[idx]}`; }
getChangeName(idx: int): string { return `c_${this._sanitizedNames[idx]}`; }
@ -51,17 +80,22 @@ export class CodegenNameUtil {
var declarations = [];
var assignments = [];
for (var i = 0, iLen = this.getFieldCount(); i < iLen; ++i) {
var changeName = this.getChangeName(i);
declarations.push(`${this.getLocalName(i)},${changeName}`);
assignments.push(changeName);
if (i == CONTEXT_INDEX) {
declarations.push(`${this.getLocalName(i)} = ${this.getFieldName(i)}`);
} else {
var changeName = this.getChangeName(i);
declarations.push(`${this.getLocalName(i)},${changeName}`);
assignments.push(changeName);
}
}
return `var ${ListWrapper.join(declarations, ',')};` +
`${ListWrapper.join(assignments, '=')} = false;`;
var assignmentsCode =
ListWrapper.isEmpty(assignments) ? '' : `${ListWrapper.join(assignments, '=')} = false;`;
return `var ${ListWrapper.join(declarations, ',')};${assignmentsCode}`;
}
getFieldCount(): int { return this._sanitizedNames.length; }
getFieldName(idx: int): string { return `${this.fieldPrefix}${this._sanitizedNames[idx]}`; }
getFieldName(idx: int): string { return this._addFieldPrefix(this._sanitizedNames[idx]); }
getAllFieldNames(): List<string> {
var fieldList = [];
@ -84,27 +118,17 @@ export class CodegenNameUtil {
return fieldList;
}
/**
* Generates a statement which declares all fields.
* This is only necessary for Dart change detectors.
*/
genDeclareFields(): string {
var fields = this.getAllFieldNames();
ListWrapper.removeAt(fields, _CONTEXT_IDX);
return ListWrapper.isEmpty(fields) ? '' : `var ${ListWrapper.join(fields, ', ')};`;
}
/**
* Generates statements which clear all fields so that the change detector is dehydrated.
*/
genDehydrateFields(): string {
var fields = this.getAllFieldNames();
ListWrapper.removeAt(fields, _CONTEXT_IDX);
ListWrapper.removeAt(fields, CONTEXT_INDEX);
if (!ListWrapper.isEmpty(fields)) {
// At least one assignment.
fields.push(`${this.utilName}.uninitialized;`);
}
return `${this.getContextName()} = null; ${ListWrapper.join(fields, ' = ')}`;
return `${this.getFieldName(CONTEXT_INDEX)} = null; ${ListWrapper.join(fields, ' = ')}`;
}
/**
@ -116,13 +140,17 @@ export class CodegenNameUtil {
}), (r) => { return `${this.getPipeName(r.selfIndex)}.onDestroy();`; }), '\n');
}
getPipeName(idx: int): string { return `${this.fieldPrefix}${this._sanitizedNames[idx]}_pipe`; }
getPipeName(idx: int): string {
return this._addFieldPrefix(`${this._sanitizedNames[idx]}_pipe`);
}
getAllDirectiveNames(): List<string> {
return ListWrapper.map(this.directiveRecords, d => this.getDirectiveName(d.directiveIndex));
}
getDirectiveName(d: DirectiveIndex): string { return `${this.fieldPrefix}directive_${d.name}`; }
getDirectiveName(d: DirectiveIndex): string {
return this._addFieldPrefix(`directive_${d.name}`);
}
getAllDetectorNames(): List<string> {
return ListWrapper.map(
@ -130,5 +158,5 @@ export class CodegenNameUtil {
(d) => this.getDetectorName(d.directiveIndex));
}
getDetectorName(d: DirectiveIndex): string { return `${this.fieldPrefix}detector_${d.name}`; }
getDetectorName(d: DirectiveIndex): string { return this._addFieldPrefix(`detector_${d.name}`); }
}

View File

@ -10,19 +10,16 @@ import {ChangeDetectionUtil, SimpleChange} from './change_detection_util';
import {ProtoRecord, RecordType} from './proto_record';
export class DynamicChangeDetector extends AbstractChangeDetector {
locals: Locals = null;
export class DynamicChangeDetector extends AbstractChangeDetector<any> {
values: List<any>;
changes: List<any>;
localPipes: List<any>;
prevContexts: List<any>;
directives: any = null;
alreadyChecked: boolean = false;
private pipes: Pipes = null;
constructor(id: string, private changeControlStrategy: string, dispatcher: any,
private protos: List<ProtoRecord>, private directiveRecords: List<any>) {
super(id, dispatcher);
protos: List<ProtoRecord>, directiveRecords: List<any>) {
super(id, dispatcher, protos, directiveRecords);
this.values = ListWrapper.createFixedSize(protos.length + 1);
this.localPipes = ListWrapper.createFixedSize(protos.length + 1);
this.prevContexts = ListWrapper.createFixedSize(protos.length + 1);

View File

@ -2,7 +2,7 @@ import {List} from 'angular2/src/facade/collection';
import {CONST} from 'angular2/src/facade/lang';
import {Locals} from './parser/locals';
import {BindingRecord} from './binding_record';
import {DirectiveRecord} from './directive_record';
import {DirectiveIndex, DirectiveRecord} from './directive_record';
/**
* Interface used by Angular to control the change detection strategy for an application.
@ -36,7 +36,13 @@ export class ChangeDetection {
}
}
export class DebugContext {
constructor(public element: any, public componentElement: any, public directive: any,
public context: any, public locals: any, public injector: any) {}
}
export interface ChangeDispatcher {
getDebugContext(elementIndex: number, directiveIndex: DirectiveIndex): DebugContext;
notifyOnBinding(bindingRecord: BindingRecord, value: any): void;
notifyOnAllChangesDone(): void;
}
@ -58,7 +64,7 @@ export interface ChangeDetector {
checkNoChanges(): void;
}
export interface ProtoChangeDetector { instantiate(dispatcher: any): ChangeDetector; }
export interface ProtoChangeDetector { instantiate(dispatcher: ChangeDispatcher): ChangeDetector; }
export class ChangeDetectorDefinition {
constructor(public id: string, public strategy: string, public variableNames: List<string>,