refactor(compiler): speed up proto view merging
- Don't create intermediate merge results - Only merge embedded ProtoViews that contain `<ng-content>` tags Closes #3150 Closes #3177
This commit is contained in:
@ -24,6 +24,7 @@ import {ComponentUrlMapper} from './component_url_mapper';
|
||||
import {ProtoViewFactory} from './proto_view_factory';
|
||||
import {UrlResolver} from 'angular2/src/services/url_resolver';
|
||||
import {AppRootUrl} from 'angular2/src/services/app_root_url';
|
||||
import {ElementBinder} from './element_binder';
|
||||
|
||||
import * as renderApi from 'angular2/src/render/api';
|
||||
|
||||
@ -90,7 +91,7 @@ export class Compiler {
|
||||
private _appUrl: string;
|
||||
private _render: renderApi.RenderCompiler;
|
||||
private _protoViewFactory: ProtoViewFactory;
|
||||
private _unmergedCyclicEmbeddedProtoViews: RecursiveEmbeddedProtoView[] = [];
|
||||
private _protoViewsToBeMerged: AppProtoView[] = [];
|
||||
|
||||
/**
|
||||
* @private
|
||||
@ -146,8 +147,37 @@ export class Compiler {
|
||||
return this._compileNestedProtoViews(hostRenderPv, protoView, componentType);
|
||||
});
|
||||
}
|
||||
return hostPvPromise.then(
|
||||
hostAppProtoView => this._mergeCyclicEmbeddedProtoViews().then(_ => hostAppProtoView.ref));
|
||||
return hostPvPromise.then(hostAppProtoView =>
|
||||
this._mergeUnmergedProtoViews().then(_ => hostAppProtoView.ref));
|
||||
}
|
||||
|
||||
private _mergeUnmergedProtoViews(): Promise<any> {
|
||||
var protoViewsToBeMerged = this._protoViewsToBeMerged;
|
||||
this._protoViewsToBeMerged = [];
|
||||
return PromiseWrapper.all(protoViewsToBeMerged.map((appProtoView) => {
|
||||
return this._render.mergeProtoViewsRecursively(
|
||||
this._collectMergeRenderProtoViews(appProtoView))
|
||||
.then((mergeResult: renderApi.RenderProtoViewMergeMapping) => {
|
||||
appProtoView.mergeMapping = new AppProtoViewMergeMapping(mergeResult);
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
private _collectMergeRenderProtoViews(
|
||||
appProtoView: AppProtoView): List<renderApi.RenderProtoViewRef | List<any>> {
|
||||
var result = [appProtoView.render];
|
||||
for (var i = 0; i < appProtoView.elementBinders.length; i++) {
|
||||
var binder = appProtoView.elementBinders[i];
|
||||
if (isPresent(binder.nestedProtoView)) {
|
||||
if (binder.hasStaticComponent() ||
|
||||
(binder.hasEmbeddedProtoView() && binder.nestedProtoView.isEmbeddedFragment)) {
|
||||
result.push(this._collectMergeRenderProtoViews(binder.nestedProtoView));
|
||||
} else {
|
||||
result.push(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private _compile(componentBinding: DirectiveBinding): Promise<AppProtoView>| AppProtoView {
|
||||
@ -207,7 +237,7 @@ export class Compiler {
|
||||
appProtoView: AppProtoView,
|
||||
componentType: Type): Promise<AppProtoView> {
|
||||
var nestedPVPromises = [];
|
||||
this._loopComponentElementBinders(appProtoView, (parentPv, elementBinder) => {
|
||||
this._loopComponentElementBinders(appProtoView, (parentPv, elementBinder: ElementBinder) => {
|
||||
var nestedComponent = elementBinder.componentDirective;
|
||||
var elementBinderDone =
|
||||
(nestedPv: AppProtoView) => { elementBinder.nestedProtoView = nestedPv; };
|
||||
@ -220,31 +250,40 @@ export class Compiler {
|
||||
});
|
||||
return PromiseWrapper.all(nestedPVPromises)
|
||||
.then((_) => {
|
||||
var appProtoViewsToMergeInto = [];
|
||||
var mergeRenderProtoViews = this._collectMergeRenderProtoViewsRecurse(
|
||||
renderProtoView, appProtoView, appProtoViewsToMergeInto);
|
||||
if (isBlank(mergeRenderProtoViews)) {
|
||||
throw new BaseException(`Unconditional component cycle in ${stringify(componentType)}`);
|
||||
}
|
||||
return this._mergeProtoViews(appProtoViewsToMergeInto, mergeRenderProtoViews);
|
||||
this._collectMergableProtoViews(appProtoView, componentType);
|
||||
return appProtoView;
|
||||
});
|
||||
}
|
||||
|
||||
private _mergeProtoViews(
|
||||
appProtoViewsToMergeInto: AppProtoView[],
|
||||
mergeRenderProtoViews:
|
||||
List<renderApi.RenderProtoViewRef | List<any>>): Promise<AppProtoView> {
|
||||
return this._render.mergeProtoViewsRecursively(mergeRenderProtoViews)
|
||||
.then((mergeResults: List<renderApi.RenderProtoViewMergeMapping>) => {
|
||||
// Note: We don't need to check for nulls here as we filtered them out before!
|
||||
// (in RenderCompiler.mergeProtoViewsRecursively and
|
||||
// _collectMergeRenderProtoViewsRecurse).
|
||||
for (var i = 0; i < mergeResults.length; i++) {
|
||||
appProtoViewsToMergeInto[i].mergeMapping =
|
||||
new AppProtoViewMergeMapping(mergeResults[i]);
|
||||
}
|
||||
return appProtoViewsToMergeInto[0];
|
||||
});
|
||||
private _collectMergableProtoViews(appProtoView: AppProtoView, componentType: Type) {
|
||||
var isRecursive = false;
|
||||
for (var i = 0; i < appProtoView.elementBinders.length; i++) {
|
||||
var binder = appProtoView.elementBinders[i];
|
||||
if (binder.hasStaticComponent()) {
|
||||
if (isBlank(binder.nestedProtoView.isRecursive)) {
|
||||
// cycle via a component. We are in the tail recursion,
|
||||
// so all components should have their isRecursive flag set already.
|
||||
isRecursive = true;
|
||||
break;
|
||||
}
|
||||
} else if (binder.hasEmbeddedProtoView()) {
|
||||
this._collectMergableProtoViews(binder.nestedProtoView, componentType);
|
||||
}
|
||||
}
|
||||
if (isRecursive) {
|
||||
if (appProtoView.isEmbeddedFragment) {
|
||||
throw new BaseException(
|
||||
`<ng-content> is used within the recursive path of ${stringify(componentType)}`);
|
||||
}
|
||||
if (appProtoView.type === renderApi.ViewType.COMPONENT) {
|
||||
throw new BaseException(`Unconditional component cycle in ${stringify(componentType)}`);
|
||||
}
|
||||
}
|
||||
if (appProtoView.type === renderApi.ViewType.EMBEDDED ||
|
||||
appProtoView.type === renderApi.ViewType.HOST) {
|
||||
this._protoViewsToBeMerged.push(appProtoView);
|
||||
}
|
||||
appProtoView.isRecursive = isRecursive;
|
||||
}
|
||||
|
||||
private _loopComponentElementBinders(appProtoView: AppProtoView, callback: Function) {
|
||||
@ -257,48 +296,6 @@ export class Compiler {
|
||||
});
|
||||
}
|
||||
|
||||
private _collectMergeRenderProtoViewsRecurse(
|
||||
renderProtoView: renderApi.ProtoViewDto, appProtoView: AppProtoView,
|
||||
targetAppProtoViews: AppProtoView[]): List<renderApi.RenderProtoViewRef | List<any>> {
|
||||
targetAppProtoViews.push(appProtoView);
|
||||
var result = [renderProtoView.render];
|
||||
for (var i = 0; i < appProtoView.elementBinders.length; i++) {
|
||||
var binder = appProtoView.elementBinders[i];
|
||||
if (binder.hasStaticComponent()) {
|
||||
if (isBlank(binder.nestedProtoView.mergeMapping)) {
|
||||
// cycle via an embedded ProtoView. store the AppProtoView and ProtoViewDto for later.
|
||||
this._unmergedCyclicEmbeddedProtoViews.push(
|
||||
new RecursiveEmbeddedProtoView(appProtoView, renderProtoView));
|
||||
return null;
|
||||
}
|
||||
result.push(binder.nestedProtoView.mergeMapping.renderProtoViewRef);
|
||||
} else if (binder.hasEmbeddedProtoView()) {
|
||||
result.push(this._collectMergeRenderProtoViewsRecurse(
|
||||
renderProtoView.elementBinders[i].nestedProtoView, binder.nestedProtoView,
|
||||
targetAppProtoViews));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private _mergeCyclicEmbeddedProtoViews() {
|
||||
var pvs = this._unmergedCyclicEmbeddedProtoViews;
|
||||
this._unmergedCyclicEmbeddedProtoViews = [];
|
||||
var promises = pvs.map(entry => {
|
||||
var appProtoView = entry.appProtoView;
|
||||
var mergeRenderProtoViews = [entry.renderProtoView.render];
|
||||
appProtoView.elementBinders.forEach((binder) => {
|
||||
if (binder.hasStaticComponent()) {
|
||||
mergeRenderProtoViews.push(binder.nestedProtoView.mergeMapping.renderProtoViewRef);
|
||||
} else if (binder.hasEmbeddedProtoView()) {
|
||||
mergeRenderProtoViews.push(null);
|
||||
}
|
||||
});
|
||||
return this._mergeProtoViews([appProtoView], mergeRenderProtoViews);
|
||||
});
|
||||
return PromiseWrapper.all(promises);
|
||||
}
|
||||
|
||||
private _buildRenderTemplate(component, view, directives): renderApi.ViewDefinition {
|
||||
var componentUrl =
|
||||
this._urlResolver.resolve(this._appUrl, this._componentUrlMapper.getUrl(component));
|
||||
@ -356,7 +353,3 @@ export class Compiler {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class RecursiveEmbeddedProtoView {
|
||||
constructor(public appProtoView: AppProtoView, public renderProtoView: renderApi.ProtoViewDto) {}
|
||||
}
|
||||
|
@ -254,9 +254,12 @@ function _createAppProtoView(
|
||||
renderProtoView: renderApi.ProtoViewDto, protoChangeDetector: ProtoChangeDetector,
|
||||
variableBindings: Map<string, string>, allDirectives: List<DirectiveBinding>): AppProtoView {
|
||||
var elementBinders = renderProtoView.elementBinders;
|
||||
var protoView = new AppProtoView(renderProtoView.type, protoChangeDetector, variableBindings,
|
||||
createVariableLocations(elementBinders),
|
||||
renderProtoView.textBindings.length);
|
||||
// Embedded ProtoViews that contain `<ng-content>` will be merged into their parents and use
|
||||
// a RenderFragmentRef. I.e. renderProtoView.transitiveNgContentCount > 0.
|
||||
var protoView = new AppProtoView(
|
||||
renderProtoView.type, renderProtoView.transitiveNgContentCount > 0, renderProtoView.render,
|
||||
protoChangeDetector, variableBindings, createVariableLocations(elementBinders),
|
||||
renderProtoView.textBindings.length);
|
||||
_createElementBinders(protoView, elementBinders, allDirectives);
|
||||
_bindDirectiveEvents(protoView, elementBinders);
|
||||
|
||||
|
@ -32,6 +32,7 @@ export class AppProtoViewMergeMapping {
|
||||
renderTextIndices: number[];
|
||||
nestedViewIndicesByElementIndex: number[];
|
||||
hostElementIndicesByViewIndex: number[];
|
||||
nestedViewCountByViewIndex: number[];
|
||||
constructor(renderProtoViewMergeMapping: renderApi.RenderProtoViewMergeMapping) {
|
||||
this.renderProtoViewRef = renderProtoViewMergeMapping.mergedProtoViewRef;
|
||||
this.renderFragmentCount = renderProtoViewMergeMapping.fragmentCount;
|
||||
@ -42,11 +43,8 @@ export class AppProtoViewMergeMapping {
|
||||
this.hostElementIndicesByViewIndex = renderProtoViewMergeMapping.hostElementIndicesByViewIndex;
|
||||
this.nestedViewIndicesByElementIndex =
|
||||
inverseIndexMapping(this.hostElementIndicesByViewIndex, this.renderElementIndices.length);
|
||||
this.nestedViewCountByViewIndex = renderProtoViewMergeMapping.nestedViewCountByViewIndex;
|
||||
}
|
||||
|
||||
get viewCount() { return this.hostElementIndicesByViewIndex.length; }
|
||||
|
||||
get elementCount() { return this.renderElementIndices.length; }
|
||||
}
|
||||
|
||||
function inverseIndexMapping(input: number[], resultLength: number): number[] {
|
||||
@ -215,7 +213,7 @@ export class AppView implements ChangeDispatcher, RenderEventDispatcher {
|
||||
dispatchRenderEvent(renderElementIndex: number, eventName: string,
|
||||
locals: Map<string, any>): boolean {
|
||||
var elementRef =
|
||||
this.elementRefs[this.proto.mergeMapping.renderInverseElementIndices[renderElementIndex]];
|
||||
this.elementRefs[this.mainMergeMapping.renderInverseElementIndices[renderElementIndex]];
|
||||
var view = internalView(elementRef.parentView);
|
||||
return view.dispatchEvent(elementRef.boundElementIndex, eventName, locals);
|
||||
}
|
||||
@ -258,7 +256,11 @@ export class AppProtoView {
|
||||
mergeMapping: AppProtoViewMergeMapping;
|
||||
ref: ProtoViewRef;
|
||||
|
||||
constructor(public type: renderApi.ViewType, public protoChangeDetector: ProtoChangeDetector,
|
||||
isRecursive: boolean = null;
|
||||
|
||||
constructor(public type: renderApi.ViewType, public isEmbeddedFragment: boolean,
|
||||
public render: renderApi.RenderProtoViewRef,
|
||||
public protoChangeDetector: ProtoChangeDetector,
|
||||
public variableBindings: Map<string, string>,
|
||||
public variableLocations: Map<string, number>, public textBindingCount: number) {
|
||||
this.ref = new ProtoViewRef(this);
|
||||
|
@ -339,12 +339,19 @@ export class AppViewManager {
|
||||
this._utils.dehydrateView(view);
|
||||
}
|
||||
var viewContainers = view.viewContainers;
|
||||
for (var i = view.elementOffset, ii = view.elementOffset + view.proto.mergeMapping.elementCount;
|
||||
i < ii; i++) {
|
||||
var vc = viewContainers[i];
|
||||
if (isPresent(vc)) {
|
||||
for (var j = vc.views.length - 1; j >= 0; j--) {
|
||||
this._destroyViewInContainer(view, i, j);
|
||||
var startViewOffset = view.viewOffset;
|
||||
var endViewOffset =
|
||||
view.viewOffset + view.mainMergeMapping.nestedViewCountByViewIndex[view.viewOffset];
|
||||
var elementOffset = view.elementOffset;
|
||||
for (var viewIdx = startViewOffset; viewIdx <= endViewOffset; viewIdx++) {
|
||||
var currView = view.views[viewIdx];
|
||||
for (var binderIdx = 0; binderIdx < currView.proto.elementBinders.length;
|
||||
binderIdx++, elementOffset++) {
|
||||
var vc = viewContainers[elementOffset];
|
||||
if (isPresent(vc)) {
|
||||
for (var j = vc.views.length - 1; j >= 0; j--) {
|
||||
this._destroyViewInContainer(currView, elementOffset, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,8 +26,8 @@ export class AppViewManagerUtils {
|
||||
var renderFragments = renderViewWithFragments.fragmentRefs;
|
||||
var renderView = renderViewWithFragments.viewRef;
|
||||
|
||||
var elementCount = mergedParentViewProto.mergeMapping.elementCount;
|
||||
var viewCount = mergedParentViewProto.mergeMapping.viewCount;
|
||||
var elementCount = mergedParentViewProto.mergeMapping.renderElementIndices.length;
|
||||
var viewCount = mergedParentViewProto.mergeMapping.nestedViewCountByViewIndex[0] + 1;
|
||||
var elementRefs: ElementRef[] = ListWrapper.createFixedSize(elementCount);
|
||||
var viewContainers = ListWrapper.createFixedSize(elementCount);
|
||||
var preBuiltObjects: eli.PreBuiltObjects[] = ListWrapper.createFixedSize(elementCount);
|
||||
@ -175,13 +175,13 @@ export class AppViewManagerUtils {
|
||||
_hydrateView(initView: viewModule.AppView, imperativelyCreatedInjector: Injector,
|
||||
hostElementInjector: eli.ElementInjector, context: Object, parentLocals: Locals) {
|
||||
var viewIdx = initView.viewOffset;
|
||||
var endViewOffset = viewIdx + initView.proto.mergeMapping.viewCount;
|
||||
while (viewIdx < endViewOffset) {
|
||||
var endViewOffset = viewIdx + initView.mainMergeMapping.nestedViewCountByViewIndex[viewIdx];
|
||||
while (viewIdx <= endViewOffset) {
|
||||
var currView = initView.views[viewIdx];
|
||||
var currProtoView = currView.proto;
|
||||
if (currView !== initView && currView.proto.type === ViewType.EMBEDDED) {
|
||||
// Don't hydrate components of embedded fragment views.
|
||||
viewIdx += currProtoView.mergeMapping.viewCount;
|
||||
viewIdx += initView.mainMergeMapping.nestedViewCountByViewIndex[viewIdx] + 1;
|
||||
} else {
|
||||
if (currView !== initView) {
|
||||
// hydrate a nested component view
|
||||
@ -263,9 +263,9 @@ export class AppViewManagerUtils {
|
||||
}
|
||||
|
||||
dehydrateView(initView: viewModule.AppView) {
|
||||
for (var viewIdx = initView.viewOffset,
|
||||
endViewOffset = viewIdx + initView.proto.mergeMapping.viewCount;
|
||||
viewIdx < endViewOffset; viewIdx++) {
|
||||
var endViewOffset = initView.viewOffset +
|
||||
initView.mainMergeMapping.nestedViewCountByViewIndex[initView.viewOffset];
|
||||
for (var viewIdx = initView.viewOffset; viewIdx <= endViewOffset; viewIdx++) {
|
||||
var currView = initView.views[viewIdx];
|
||||
if (currView.hydrated()) {
|
||||
if (isPresent(currView.locals)) {
|
||||
|
Reference in New Issue
Block a user