refactor(render): use render layer fully

Introduces angular2/src/core/compiler/ViewFactory which
extracts ProtoView.instantiate and replaces ViewPool.

Note: This is a work in progress commit to unblock other commits.
There will be follow ups to add unit tests, remove TODOs, …
This commit is contained in:
Tobias Bosch
2015-04-07 20:54:20 -07:00
parent de581ea8b3
commit 50098767fc
60 changed files with 1206 additions and 3341 deletions

View File

@ -203,7 +203,7 @@ export class Renderer {
* Sets the dispatcher for all events that have been defined in the template or in directives
* in the given view.
*/
setEventDispatcher(viewRef:ViewRef, dispatcher:EventDispatcher):void {}
setEventDispatcher(viewRef:ViewRef, dispatcher:any/*EventDispatcher*/):void {}
/**
* To be called at the end of the VmTurn so the API can buffer calls
@ -218,10 +218,10 @@ export class Renderer {
export class EventDispatcher {
/**
* Called when an event was triggered for a on-* attribute on an element.
* @param {List<any>} locals Locals to be used to evaluate the
* @param {Map<string, any>} locals Locals to be used to evaluate the
* event expressions
*/
dispatchEvent(
elementIndex:number, eventName:string, locals:List<any>
elementIndex:number, eventName:string, locals:Map<string, any>
):void {}
}

View File

@ -1,10 +1,14 @@
import {Injectable} from 'angular2/di';
import {PromiseWrapper, Promise} from 'angular2/src/facade/async';
import {BaseException} from 'angular2/src/facade/lang';
import {Template, ProtoView} from '../../api';
import {CompilePipeline} from './compile_pipeline';
import {TemplateLoader} from 'angular2/src/render/dom/compiler/template_loader';
import {CompileStepFactory} from './compile_step_factory';
import {CompileStepFactory, DefaultStepFactory} from './compile_step_factory';
import {Parser} from 'angular2/change_detection';
import {ShadowDomStrategy} from '../shadow_dom/shadow_dom_strategy';
/**
* The compiler loads and translates the html templates of components into
@ -43,4 +47,11 @@ export class Compiler {
return PromiseWrapper.resolve(protoView);
}
}
}
@Injectable()
export class DefaultCompiler extends Compiler {
constructor(parser:Parser, shadowDomStrategy:ShadowDomStrategy, templateLoader: TemplateLoader) {
super(new DefaultStepFactory(parser, shadowDomStrategy), templateLoader);
}
}

View File

@ -1,3 +1,4 @@
import {Injectable} from 'angular2/di';
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
import {List, ListWrapper} from 'angular2/src/facade/collection';
import {isBlank, isPresent} from 'angular2/src/facade/lang';
@ -14,7 +15,7 @@ function _resolveViewContainer(vc:api.ViewContainerRef) {
return _resolveView(vc.view).viewContainers[vc.elementIndex];
}
function _resolveView(viewRef:_DirectDomViewRef) {
function _resolveView(viewRef:DirectDomViewRef) {
return isPresent(viewRef) ? viewRef.delegate : null;
}
@ -23,7 +24,7 @@ function _resolveProtoView(protoViewRef:DirectDomProtoViewRef) {
}
function _wrapView(view:View) {
return new _DirectDomViewRef(view);
return new DirectDomViewRef(view);
}
function _collectComponentChildViewRefs(view, target = null) {
@ -51,7 +52,7 @@ export class DirectDomProtoViewRef extends api.ProtoViewRef {
}
}
class _DirectDomViewRef extends api.ViewRef {
export class DirectDomViewRef extends api.ViewRef {
delegate:View;
constructor(delegate:View) {
@ -60,6 +61,7 @@ class _DirectDomViewRef extends api.ViewRef {
}
}
@Injectable()
export class DirectDomRenderer extends api.Renderer {
_compiler: Compiler;
_viewFactory: ViewFactory;
@ -131,7 +133,7 @@ export class DirectDomRenderer extends api.Renderer {
_resolveView(viewRef).setText(textNodeIndex, text);
}
setEventDispatcher(viewRef:api.ViewRef, dispatcher:api.EventDispatcher) {
setEventDispatcher(viewRef:api.ViewRef, dispatcher:any/*api.EventDispatcher*/):void {
_resolveView(viewRef).setEventDispatcher(dispatcher);
}
}

View File

@ -242,19 +242,19 @@ export class EventLocalsAstSplitter extends AstTransformer {
}
splitEventAstIntoLocals(eventBindings:Map<string, ASTWithSource>):Map<string, ASTWithSource> {
// TODO(tbosch): reenable this when we are using
// the render views
return eventBindings;
// if (isPresent(eventBindings)) {
// var result = MapWrapper.create();
// MapWrapper.forEach(eventBindings, (astWithSource, eventName) => {
// var adjustedAst = astWithSource.ast.visit(this);
// MapWrapper.set(result, eventName, new ASTWithSource(adjustedAst, astWithSource.source, ''));
// ListWrapper.push(this.eventNames, eventName);
// });
// return result;
// }
// return null;
if (isPresent(eventBindings)) {
var result = MapWrapper.create();
MapWrapper.forEach(eventBindings, (astWithSource, eventName) => {
// TODO(tbosch): reenable this when we are parsing element properties
// out of action expressions
// var adjustedAst = astWithSource.ast.visit(this);
var adjustedAst = astWithSource.ast;
MapWrapper.set(result, eventName, new ASTWithSource(adjustedAst, astWithSource.source, ''));
ListWrapper.push(this.eventNames, eventName);
});
return result;
}
return null;
}
visitAccessMember(ast:AccessMember) {

View File

@ -1,7 +1,6 @@
import {DOM} from 'angular2/src/dom/dom_adapter';
import {ListWrapper, MapWrapper, Map, StringMapWrapper, List} from 'angular2/src/facade/collection';
import {int, isPresent, isBlank, BaseException} from 'angular2/src/facade/lang';
import {Locals} from 'angular2/change_detection';
import {ViewContainer} from './view_container';
import {ProtoView} from './proto_view';
@ -10,7 +9,7 @@ import {Content} from '../shadow_dom/content_tag';
import {ShadowDomStrategy} from '../shadow_dom/shadow_dom_strategy';
import {EventDispatcher} from '../../api';
// import {EventDispatcher} from '../../api';
const NG_BINDING_CLASS = 'ng-binding';
@ -31,7 +30,7 @@ export class View {
lightDoms: List<LightDom>;
proto: ProtoView;
_hydrated: boolean;
_eventDispatcher: EventDispatcher;
_eventDispatcher: any/*EventDispatcher*/;
constructor(
proto:ProtoView, rootNodes:List,
@ -43,6 +42,7 @@ export class View {
this.viewContainers = viewContainers;
this.contentTags = contentTags;
this.lightDoms = ListWrapper.createFixedSize(boundElements.length);
ListWrapper.fill(this.lightDoms, null);
this.componentChildViews = ListWrapper.createFixedSize(boundElements.length);
this._hydrated = false;
}
@ -134,7 +134,10 @@ export class View {
// componentChildViews
for (var i = 0; i < this.componentChildViews.length; i++) {
this.componentChildViews[i].dehydrate();
var cv = this.componentChildViews[i];
if (isPresent(cv)) {
cv.dehydrate();
}
}
// viewContainers and content tags
@ -150,10 +153,11 @@ export class View {
}
}
}
this._eventDispatcher = null;
this._hydrated = false;
}
setEventDispatcher(dispatcher:EventDispatcher) {
setEventDispatcher(dispatcher:any/*EventDispatcher*/) {
this._eventDispatcher = dispatcher;
}
@ -161,8 +165,11 @@ export class View {
if (isPresent(this._eventDispatcher)) {
var evalLocals = MapWrapper.create();
MapWrapper.set(evalLocals, '$event', event);
var localValues = this.proto.elementBinders[elementIndex].eventLocals.eval(null, new Locals(null, evalLocals));
this._eventDispatcher.dispatchEvent(elementIndex, eventName, localValues);
// TODO(tbosch): reenable this when we are parsing element properties
// out of action expressions
// var localValues = this.proto.elementBinders[elementIndex].eventLocals.eval(null, new Locals(null, evalLocals));
// this._eventDispatcher.dispatchEvent(elementIndex, eventName, localValues);
this._eventDispatcher.dispatchEvent(elementIndex, eventName, evalLocals);
}
}
}

View File

@ -35,7 +35,7 @@ export class ViewContainer {
if (isBlank(this._lightDom)) {
for (var i = this._views.length - 1; i >= 0; i--) {
var view = this._views[i];
ViewContainer.removeViewNodesFromParent(this.templateElement.parentNode, view);
ViewContainer.removeViewNodes(view);
this._viewFactory.returnView(view);
}
this._views = [];
@ -72,12 +72,11 @@ export class ViewContainer {
}
insert(view, atIndex=-1): viewModule.View {
this._checkHydrated();
if (atIndex == -1) atIndex = this._views.length;
ListWrapper.insert(this._views, atIndex, view);
if (!view.hydrated()) {
view.hydrate(this._hostLightDom);
}
if (atIndex == -1) atIndex = this._views.length;
ListWrapper.insert(this._views, atIndex, view);
if (isBlank(this._lightDom)) {
ViewContainer.moveViewNodesAfterSibling(this._siblingToInsertAfter(atIndex), view);
@ -100,7 +99,7 @@ export class ViewContainer {
var detachedView = this.get(atIndex);
ListWrapper.removeAt(this._views, atIndex);
if (isBlank(this._lightDom)) {
ViewContainer.removeViewNodesFromParent(this.templateElement.parentNode, detachedView);
ViewContainer.removeViewNodes(detachedView);
} else {
this._lightDom.redistribute();
}
@ -129,8 +128,11 @@ export class ViewContainer {
}
}
static removeViewNodesFromParent(parent, view) {
for (var i = view.rootNodes.length - 1; i >= 0; --i) {
static removeViewNodes(view) {
var len = view.rootNodes.length;
if (len == 0) return;
var parent = view.rootNodes[0].parentNode;
for (var i = len - 1; i >= 0; --i) {
DOM.removeChild(parent, view.rootNodes[i]);
}
}

View File

@ -1,4 +1,4 @@
import {OpaqueToken} from 'angular2/di';
import {OpaqueToken, Inject, Injectable} from 'angular2/di';
import {int, isPresent, isBlank, BaseException} from 'angular2/src/facade/lang';
import {ListWrapper, MapWrapper, Map, StringMapWrapper, List} from 'angular2/src/facade/collection';
@ -8,32 +8,33 @@ import {Content} from '../shadow_dom/content_tag';
import {ShadowDomStrategy} from '../shadow_dom/shadow_dom_strategy';
import {EventManager} from 'angular2/src/render/dom/events/event_manager';
import {ViewContainer} from './view_container';
import {ProtoView} from './proto_view';
import {View} from './view';
import * as vcModule from './view_container';
import * as pvModule from './proto_view';
import * as viewModule from './view';
import {NG_BINDING_CLASS_SELECTOR, NG_BINDING_CLASS} from '../util';
export var VIEW_POOL_CAPACITY = new OpaqueToken('ViewFactory.viewPoolCapacity');
// TODO(tbosch): Make this an OpaqueToken as soon as our transpiler supports this!
export const VIEW_POOL_CAPACITY = 'render.ViewFactory.viewPoolCapacity';
@Injectable()
export class ViewFactory {
_poolCapacity:number;
_pooledViews:List<View>;
_pooledViews:List<viewModule.View>;
_eventManager:EventManager;
_shadowDomStrategy:ShadowDomStrategy;
constructor(capacity, eventManager, shadowDomStrategy) {
constructor(@Inject(VIEW_POOL_CAPACITY) capacity, eventManager:EventManager, shadowDomStrategy:ShadowDomStrategy) {
this._poolCapacity = capacity;
this._pooledViews = ListWrapper.create();
this._eventManager = eventManager;
this._shadowDomStrategy = shadowDomStrategy;
}
getView(protoView:ProtoView):View {
getView(protoView:pvModule.ProtoView):viewModule.View {
// TODO(tbosch): benchmark this scanning of views and maybe
// replace it with a fancy LRU Map/List combination...
var view;
for (var i=0; i<this._pooledViews.length; i++) {
for (var i=this._pooledViews.length-1; i>=0; i--) {
var pooledView = this._pooledViews[i];
if (pooledView.proto === protoView) {
view = ListWrapper.removeAt(this._pooledViews, i);
@ -45,7 +46,7 @@ export class ViewFactory {
return view;
}
returnView(view:View) {
returnView(view:viewModule.View) {
if (view.hydrated()) {
view.dehydrate();
}
@ -55,7 +56,7 @@ export class ViewFactory {
}
}
_createView(protoView:ProtoView): View {
_createView(protoView:pvModule.ProtoView): viewModule.View {
var rootElementClone = protoView.isRootView ? protoView.element : DOM.importIntoDoc(protoView.element);
var elementsWithBindingsDynamic;
if (protoView.isTemplateElement) {
@ -72,7 +73,7 @@ export class ViewFactory {
var viewRootNodes;
if (protoView.isTemplateElement) {
var childNode = DOM.firstChild(DOM.content(rootElementClone));
viewRootNodes = []; // TODO(perf): Should be fixed size, since we could pre-compute in in ProtoView
viewRootNodes = []; // TODO(perf): Should be fixed size, since we could pre-compute in in pvModule.ProtoView
// Note: An explicit loop is the fastest way to convert a DOM array into a JS array!
while(childNode != null) {
ListWrapper.push(viewRootNodes, childNode);
@ -108,7 +109,7 @@ export class ViewFactory {
// viewContainers
var viewContainer = null;
if (isBlank(binder.componentId) && isPresent(binder.nestedProtoView)) {
viewContainer = new ViewContainer(this, element);
viewContainer = new vcModule.ViewContainer(this, element);
}
viewContainers[binderIdx] = viewContainer;
@ -120,7 +121,7 @@ export class ViewFactory {
contentTags[binderIdx] = contentTag;
}
var view = new View(
var view = new viewModule.View(
protoView, viewRootNodes,
boundTextNodes, boundElements, viewContainers, contentTags
);