Skip to content

Commit

Permalink
feat(change_detection): do not reparse AST when using generated detec…
Browse files Browse the repository at this point in the history
…tors
  • Loading branch information
vsavkin committed Aug 21, 2015
1 parent b986c54 commit d2d0715
Show file tree
Hide file tree
Showing 31 changed files with 426 additions and 282 deletions.
60 changes: 24 additions & 36 deletions modules/angular2/src/change_detection/abstract_change_detector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,15 @@ import {isPresent, isBlank, BaseException, StringWrapper} from 'angular2/src/fac
import {List, ListWrapper} from 'angular2/src/facade/collection';
import {ChangeDetectionUtil} from './change_detection_util';
import {ChangeDetectorRef} from './change_detector_ref';
import {DirectiveRecord} from './directive_record';
import {DirectiveIndex} from './directive_record';
import {ChangeDetector, ChangeDispatcher} from './interfaces';
import {Pipes} from './pipes';
import {
ChangeDetectionError,
ExpressionChangedAfterItHasBeenCheckedException,
DehydratedException
} from './exceptions';
import {ProtoRecord} from './proto_record';
import {BindingRecord} from './binding_record';
import {BindingTarget} from './binding_record';
import {Locals} from './parser/locals';
import {CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED} from './constants';
import {wtfCreateScope, wtfLeave, WtfScopeFn} from '../profile/profile';
Expand All @@ -20,9 +19,8 @@ import {isObservable} from './observable_facade';
var _scope_check: WtfScopeFn = wtfCreateScope(`ChangeDetector#check(ascii id, bool throwOnChange)`);

class _Context {
constructor(public element: any, public componentElement: any, public instance: any,
public context: any, public locals: any, public injector: any,
public expression: any) {}
constructor(public element: any, public componentElement: any, public context: any,
public locals: any, public injector: any, public expression: any) {}
}

export class AbstractChangeDetector<T> implements ChangeDetector {
Expand All @@ -35,24 +33,19 @@ export class AbstractChangeDetector<T> implements ChangeDetector {
// change detection will fail.
alreadyChecked: any = false;
context: T;
directiveRecords: List<DirectiveRecord>;
dispatcher: ChangeDispatcher;
locals: Locals = null;
mode: string = null;
pipes: Pipes = null;
firstProtoInCurrentBinding: number;
protos: List<ProtoRecord>;
propertyBindingIndex: number;

// This is an experimental feature. Works only in Dart.
subscriptions: any[];
streams: any[];

constructor(public id: string, dispatcher: ChangeDispatcher, protos: List<ProtoRecord>,
directiveRecords: List<DirectiveRecord>, public modeOnHydrate: string) {
constructor(public id: string, public dispatcher: ChangeDispatcher,
public numberOfPropertyProtoRecords: number, public bindingTargets: BindingTarget[],
public directiveIndices: DirectiveIndex[], public modeOnHydrate: string) {
this.ref = new ChangeDetectorRef(this);
this.directiveRecords = directiveRecords;
this.dispatcher = dispatcher;
this.protos = protos;
}

addChild(cd: ChangeDetector): void {
Expand Down Expand Up @@ -99,7 +92,7 @@ export class AbstractChangeDetector<T> implements ChangeDetector {
// implementation of `detectChangesInRecordsInternal` which does the work of detecting changes
// and which this method will call.
// This method expects that `detectChangesInRecordsInternal` will set the property
// `this.firstProtoInCurrentBinding` to the selfIndex of the first proto record. This is to
// `this.propertyBindingIndex` to the propertyBindingIndex of the first proto record. This is to
// facilitate error reporting.
detectChangesInRecords(throwOnChange: boolean): void {
if (!this.hydrated()) {
Expand All @@ -115,9 +108,9 @@ export class AbstractChangeDetector<T> implements ChangeDetector {
// Subclasses should override this method to perform any work necessary to detect and report
// changes. For example, changes should be reported via `ChangeDetectionUtil.addChange`, lifecycle
// methods should be called, etc.
// This implementation should also set `this.firstProtoInCurrentBinding` to the selfIndex of the
// first proto record
// to facilitate error reporting. See {@link #detectChangesInRecords}.
// This implementation should also set `this.propertyBindingIndex` to the propertyBindingIndex of
// the
// first proto record to facilitate error reporting. See {@link #detectChangesInRecords}.
detectChangesInRecordsInternal(throwOnChange: boolean): void {}

// This method is not intended to be overridden. Subclasses should instead provide an
Expand Down Expand Up @@ -195,8 +188,8 @@ export class AbstractChangeDetector<T> implements ChangeDetector {
protected observe(value: any, index: number): any {
if (isObservable(value)) {
if (isBlank(this.subscriptions)) {
this.subscriptions = ListWrapper.createFixedSize(this.protos.length + 1);
this.streams = ListWrapper.createFixedSize(this.protos.length + 1);
this.subscriptions = ListWrapper.createFixedSize(this.numberOfPropertyProtoRecords + 1);
this.streams = ListWrapper.createFixedSize(this.numberOfPropertyProtoRecords + 1);
}
if (isBlank(this.subscriptions[index])) {
this.streams[index] = value.changes;
Expand All @@ -211,7 +204,7 @@ export class AbstractChangeDetector<T> implements ChangeDetector {
}

protected getDetectorFor(directives: any, index: number): ChangeDetector {
return directives.getDetectorFor(this.directiveRecords[index].directiveIndex);
return directives.getDetectorFor(this.directiveIndices[index]);
}

protected notifyDispatcher(value: any): void {
Expand All @@ -223,31 +216,26 @@ export class AbstractChangeDetector<T> implements ChangeDetector {
if (isBlank(changes)) {
changes = {};
}
changes[this._currentBinding().propertyName] =
ChangeDetectionUtil.simpleChange(oldValue, newValue);
changes[this._currentBinding().name] = ChangeDetectionUtil.simpleChange(oldValue, newValue);
return changes;
}

private _throwError(exception: any, stack: any): void {
var proto = this._currentBindingProto();
var c = this.dispatcher.getDebugContext(proto.bindingRecord.elementIndex, proto.directiveIndex);
var context = isPresent(c) ? new _Context(c.element, c.componentElement, c.directive, c.context,
c.locals, c.injector, proto.expressionAsString) :
var c = this.dispatcher.getDebugContext(this._currentBinding().elementIndex, null);
var context = isPresent(c) ? new _Context(c.element, c.componentElement, c.context, c.locals,
c.injector, this._currentBinding().debug) :
null;
throw new ChangeDetectionError(proto, exception, stack, context);
throw new ChangeDetectionError(this._currentBinding().debug, exception, stack, context);
}

protected throwOnChangeError(oldValue: any, newValue: any): void {
var change = ChangeDetectionUtil.simpleChange(oldValue, newValue);
throw new ExpressionChangedAfterItHasBeenCheckedException(this._currentBindingProto(), change,
null);
throw new ExpressionChangedAfterItHasBeenCheckedException(this._currentBinding().debug,
oldValue, newValue, null);
}

protected throwDehydratedError(): void { throw new DehydratedException(); }

private _currentBinding(): BindingRecord { return this._currentBindingProto().bindingRecord; }

private _currentBindingProto(): ProtoRecord {
return ChangeDetectionUtil.protoByIndex(this.protos, this.firstProtoInCurrentBinding);
private _currentBinding(): BindingTarget {
return this.bindingTargets[this.propertyBindingIndex];
}
}
113 changes: 67 additions & 46 deletions modules/angular2/src/change_detection/binding_record.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import {SetterFn} from 'angular2/src/reflection/types';
import {AST} from './parser/ast';
import {DirectiveIndex, DirectiveRecord} from './directive_record';

const DIRECTIVE = "directive";
const DIRECTIVE_LIFECYCLE = "directiveLifecycle";
const BINDING = "native";

const DIRECTIVE = "directive";
const ELEMENT_PROPERTY = "elementProperty";
const ELEMENT_ATTRIBUTE = "elementAttribute";
const ELEMENT_CLASS = "elementClass";
Expand All @@ -13,24 +15,12 @@ const TEXT_NODE = "textNode";
const EVENT = "event";
const HOST_EVENT = "hostEvent";

export class BindingRecord {
constructor(public mode: string, public implicitReceiver: any, public ast: AST,
public elementIndex: number, public propertyName: string, public propertyUnit: string,
public eventName: string, public setter: SetterFn, public lifecycleEvent: string,
public directiveRecord: DirectiveRecord) {}

callOnChange(): boolean {
return isPresent(this.directiveRecord) && this.directiveRecord.callOnChange;
}

isDefaultChangeDetection(): boolean {
return isBlank(this.directiveRecord) || this.directiveRecord.isDefaultChangeDetection();
}
export class BindingTarget {
constructor(public mode: string, public elementIndex: number, public name: string,
public unit: string, public debug: string) {}

isDirective(): boolean { return this.mode === DIRECTIVE; }

isDirectiveLifecycle(): boolean { return this.mode === DIRECTIVE_LIFECYCLE; }

isElementProperty(): boolean { return this.mode === ELEMENT_PROPERTY; }

isElementAttribute(): boolean { return this.mode === ELEMENT_ATTRIBUTE; }
Expand All @@ -40,87 +30,118 @@ export class BindingRecord {
isElementStyle(): boolean { return this.mode === ELEMENT_STYLE; }

isTextNode(): boolean { return this.mode === TEXT_NODE; }
}

static createForDirective(ast: AST, propertyName: string, setter: SetterFn,
directiveRecord: DirectiveRecord): BindingRecord {
return new BindingRecord(DIRECTIVE, 0, ast, 0, propertyName, null, null, setter, null,
directiveRecord);
export class BindingRecord {
constructor(public mode: string, public target: BindingTarget, public implicitReceiver: any,
public ast: AST, public setter: SetterFn, public lifecycleEvent: string,
public directiveRecord: DirectiveRecord) {}

isDirectiveLifecycle(): boolean { return this.mode === DIRECTIVE_LIFECYCLE; }

callOnChange(): boolean {
return isPresent(this.directiveRecord) && this.directiveRecord.callOnChange;
}

isDefaultChangeDetection(): boolean {
return isBlank(this.directiveRecord) || this.directiveRecord.isDefaultChangeDetection();
}


static createDirectiveOnCheck(directiveRecord: DirectiveRecord): BindingRecord {
return new BindingRecord(DIRECTIVE_LIFECYCLE, 0, null, 0, null, null, null, null, "onCheck",
directiveRecord);
return new BindingRecord(DIRECTIVE_LIFECYCLE, null, 0, null, null, "onCheck", directiveRecord);
}

static createDirectiveOnInit(directiveRecord: DirectiveRecord): BindingRecord {
return new BindingRecord(DIRECTIVE_LIFECYCLE, 0, null, 0, null, null, null, null, "onInit",
directiveRecord);
return new BindingRecord(DIRECTIVE_LIFECYCLE, null, 0, null, null, "onInit", directiveRecord);
}

static createDirectiveOnChange(directiveRecord: DirectiveRecord): BindingRecord {
return new BindingRecord(DIRECTIVE_LIFECYCLE, 0, null, 0, null, null, null, null, "onChange",
directiveRecord);
return new BindingRecord(DIRECTIVE_LIFECYCLE, null, 0, null, null, "onChange", directiveRecord);
}



static createForDirective(ast: AST, propertyName: string, setter: SetterFn,
directiveRecord: DirectiveRecord): BindingRecord {
var t = new BindingTarget(DIRECTIVE, null, propertyName, null, ast.toString());
return new BindingRecord(DIRECTIVE, t, 0, ast, setter, null, directiveRecord);
}



static createForElementProperty(ast: AST, elementIndex: number,
propertyName: string): BindingRecord {
return new BindingRecord(ELEMENT_PROPERTY, 0, ast, elementIndex, propertyName, null, null, null,
null, null);
var t = new BindingTarget(ELEMENT_PROPERTY, elementIndex, propertyName, null, ast.toString());
return new BindingRecord(BINDING, t, 0, ast, null, null, null);
}

static createForElementAttribute(ast: AST, elementIndex: number,
attributeName: string): BindingRecord {
return new BindingRecord(ELEMENT_ATTRIBUTE, 0, ast, elementIndex, attributeName, null, null,
null, null, null);
var t = new BindingTarget(ELEMENT_ATTRIBUTE, elementIndex, attributeName, null, ast.toString());
return new BindingRecord(BINDING, t, 0, ast, null, null, null);
}

static createForElementClass(ast: AST, elementIndex: number, className: string): BindingRecord {
return new BindingRecord(ELEMENT_CLASS, 0, ast, elementIndex, className, null, null, null, null,
null);
var t = new BindingTarget(ELEMENT_CLASS, elementIndex, className, null, ast.toString());
return new BindingRecord(BINDING, t, 0, ast, null, null, null);
}

static createForElementStyle(ast: AST, elementIndex: number, styleName: string,
unit: string): BindingRecord {
return new BindingRecord(ELEMENT_STYLE, 0, ast, elementIndex, styleName, unit, null, null, null,
null);
var t = new BindingTarget(ELEMENT_STYLE, elementIndex, styleName, unit, ast.toString());
return new BindingRecord(BINDING, t, 0, ast, null, null, null);
}



static createForHostProperty(directiveIndex: DirectiveIndex, ast: AST,
propertyName: string): BindingRecord {
return new BindingRecord(ELEMENT_PROPERTY, directiveIndex, ast, directiveIndex.elementIndex,
propertyName, null, null, null, null, null);
var t = new BindingTarget(ELEMENT_PROPERTY, directiveIndex.elementIndex, propertyName, null,
ast.toString());
return new BindingRecord(BINDING, t, directiveIndex, ast, null, null, null);
}

static createForHostAttribute(directiveIndex: DirectiveIndex, ast: AST,
attributeName: string): BindingRecord {
return new BindingRecord(ELEMENT_ATTRIBUTE, directiveIndex, ast, directiveIndex.elementIndex,
attributeName, null, null, null, null, null);
var t = new BindingTarget(ELEMENT_ATTRIBUTE, directiveIndex.elementIndex, attributeName, null,
ast.toString());
return new BindingRecord(BINDING, t, directiveIndex, ast, null, null, null);
}

static createForHostClass(directiveIndex: DirectiveIndex, ast: AST,
className: string): BindingRecord {
return new BindingRecord(ELEMENT_CLASS, directiveIndex, ast, directiveIndex.elementIndex,
className, null, null, null, null, null);
var t = new BindingTarget(ELEMENT_CLASS, directiveIndex.elementIndex, className, null,
ast.toString());
return new BindingRecord(BINDING, t, directiveIndex, ast, null, null, null);
}

static createForHostStyle(directiveIndex: DirectiveIndex, ast: AST, styleName: string,
unit: string): BindingRecord {
return new BindingRecord(ELEMENT_STYLE, directiveIndex, ast, directiveIndex.elementIndex,
styleName, unit, null, null, null, null);
var t = new BindingTarget(ELEMENT_STYLE, directiveIndex.elementIndex, styleName, unit,
ast.toString());
return new BindingRecord(BINDING, t, directiveIndex, ast, null, null, null);
}



static createForTextNode(ast: AST, elementIndex: number): BindingRecord {
return new BindingRecord(TEXT_NODE, 0, ast, elementIndex, null, null, null, null, null, null);
var t = new BindingTarget(TEXT_NODE, elementIndex, null, null, ast.toString());
return new BindingRecord(BINDING, t, 0, ast, null, null, null);
}



static createForEvent(ast: AST, eventName: string, elementIndex: number): BindingRecord {
return new BindingRecord(EVENT, 0, ast, elementIndex, null, null, eventName, null, null, null);
var t = new BindingTarget(EVENT, elementIndex, eventName, null, ast.toString());
return new BindingRecord(EVENT, t, 0, ast, null, null, null);
}

static createForHostEvent(ast: AST, eventName: string,
directiveRecord: DirectiveRecord): BindingRecord {
var directiveIndex = directiveRecord.directiveIndex;
return new BindingRecord(EVENT, directiveIndex, ast, directiveIndex.elementIndex, null, null,
eventName, null, null, directiveRecord);
var t =
new BindingTarget(HOST_EVENT, directiveIndex.elementIndex, eventName, null, ast.toString());
return new BindingRecord(HOST_EVENT, t, directiveIndex, ast, null, null, directiveRecord);
}
}
17 changes: 11 additions & 6 deletions modules/angular2/src/change_detection/change_detection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export {
} from './interfaces';
export {CHECK_ONCE, CHECK_ALWAYS, DETACHED, CHECKED, ON_PUSH, DEFAULT} from './constants';
export {DynamicProtoChangeDetector} from './proto_change_detector';
export {BindingRecord} from './binding_record';
export {BindingRecord, BindingTarget} from './binding_record';
export {DirectiveIndex, DirectiveRecord} from './directive_record';
export {DynamicChangeDetector} from './dynamic_change_detector';
export {ChangeDetectorRef} from './change_detector_ref';
Expand Down Expand Up @@ -93,13 +93,14 @@ export class PreGeneratedChangeDetection extends ChangeDetection {

static isSupported(): boolean { return PregenProtoChangeDetector.isSupported(); }

createProtoChangeDetector(definition: ChangeDetectorDefinition): ProtoChangeDetector {
var id = definition.id;
getProtoChangeDetector(id: string, definition: ChangeDetectorDefinition): ProtoChangeDetector {
if (StringMapWrapper.contains(this._protoChangeDetectorFactories, id)) {
return StringMapWrapper.get(this._protoChangeDetectorFactories, id)(definition);
}
return this._dynamicChangeDetection.createProtoChangeDetector(definition);
return this._dynamicChangeDetection.getProtoChangeDetector(id, definition);
}

get generateDetectors(): boolean { return true; }
}


Expand All @@ -110,9 +111,11 @@ export class PreGeneratedChangeDetection extends ChangeDetection {
*/
@Injectable()
export class DynamicChangeDetection extends ChangeDetection {
createProtoChangeDetector(definition: ChangeDetectorDefinition): ProtoChangeDetector {
getProtoChangeDetector(id: string, definition: ChangeDetectorDefinition): ProtoChangeDetector {
return new DynamicProtoChangeDetector(definition);
}

get generateDetectors(): boolean { return true; }
}

/**
Expand All @@ -126,7 +129,9 @@ export class DynamicChangeDetection extends ChangeDetection {
export class JitChangeDetection extends ChangeDetection {
static isSupported(): boolean { return JitProtoChangeDetector.isSupported(); }

createProtoChangeDetector(definition: ChangeDetectorDefinition): ProtoChangeDetector {
getProtoChangeDetector(id: string, definition: ChangeDetectorDefinition): ProtoChangeDetector {
return new JitProtoChangeDetector(definition);
}

get generateDetectors(): boolean { return true; }
}
Loading

0 comments on commit d2d0715

Please sign in to comment.