Skip to content
Permalink
Browse files

perf(ivy): initialise TNode inputs / outputs on the first creation pa…

…ss (#32608)

This perf-focused refactoring moves the TNode's input / output initialization
logic to the first template pass - close to the place where directives are
matched and resolved.

This code change makes it possible to update-mode checks for both property
bindings and listeners registration.

PR Close #32608
  • Loading branch information...
pkozlowski-opensource authored and kara committed Sep 11, 2019
1 parent adeee0f commit ad178c55fd919f062d8c35e00246a6bf9f2122da
@@ -23,7 +23,7 @@ import {getInitialStylingValue, hasClassInput, hasStyleInput} from '../styling_n
import {setUpAttributes} from '../util/attrs_utils';
import {getNativeByTNode, getTNode} from '../util/view_utils';

import {createDirectivesInstances, elementCreate, executeContentQueries, getOrCreateTNode, initializeTNodeInputs, renderInitialStyling, resolveDirectives, saveResolvedLocalsInData, setInputsForProperty} from './shared';
import {createDirectivesInstances, elementCreate, executeContentQueries, getOrCreateTNode, renderInitialStyling, resolveDirectives, saveResolvedLocalsInData, setInputsForProperty} from './shared';



@@ -84,13 +84,14 @@ export function ɵɵelementStart(
ngDevMode && ngDevMode.firstTemplatePass++;
resolveDirectives(tView, lView, tNode, localRefs || null);

const inputData = initializeTNodeInputs(tView, tNode);
if (inputData && inputData.hasOwnProperty('class')) {
tNode.flags |= TNodeFlags.hasClassInput;
}

if (inputData && inputData.hasOwnProperty('style')) {
tNode.flags |= TNodeFlags.hasStyleInput;
const inputData = tNode.inputs;
if (inputData != null) {
if (inputData.hasOwnProperty('class')) {
tNode.flags |= TNodeFlags.hasClassInput;
}
if (inputData.hasOwnProperty('style')) {
tNode.flags |= TNodeFlags.hasStyleInput;
}
}

if (tView.queries !== null) {
@@ -17,8 +17,7 @@ import {CLEANUP, FLAGS, LView, LViewFlags, RENDERER, TVIEW} from '../interfaces/
import {assertNodeOfPossibleTypes} from '../node_assert';
import {getLView, getPreviousOrParentTNode} from '../state';
import {getComponentViewByIndex, getNativeByTNode, unwrapRNode} from '../util/view_utils';

import {BindingDirection, generatePropertyAliases, getCleanup, handleError, loadComponentRenderer, markViewDirty} from './shared';
import {getCleanup, handleError, loadComponentRenderer, markViewDirty} from './shared';

/**
* Adds an event listener to the current node.
@@ -186,36 +185,28 @@ function listenerInternal(
}

// subscribe to directive outputs
if (isTNodeDirectiveHost && processOutputs) {
let outputs = tNode.outputs;
if (outputs === undefined) {
// if we create TNode here, inputs must be undefined so we know they still need to be
// checked
outputs = tNode.outputs = generatePropertyAliases(tView, tNode, BindingDirection.Output);
}

let props: PropertyAliasValue|undefined;
if (outputs !== null && (props = outputs[eventName])) {
const propsLength = props.length;
if (propsLength) {
const lCleanup = getCleanup(lView);
for (let i = 0; i < propsLength; i += 3) {
const index = props[i] as number;
ngDevMode && assertDataInRange(lView, index);
const minifiedName = props[i + 2];
const directiveInstance = lView[index];
const output = directiveInstance[minifiedName];
const outputs = tNode.outputs;
let props: PropertyAliasValue|undefined;
if (processOutputs && outputs != null && (props = outputs[eventName])) {
const propsLength = props.length;
if (propsLength) {
const lCleanup = getCleanup(lView);
for (let i = 0; i < propsLength; i += 3) {
const index = props[i] as number;
ngDevMode && assertDataInRange(lView, index);
const minifiedName = props[i + 2];
const directiveInstance = lView[index];
const output = directiveInstance[minifiedName];

if (ngDevMode && !isObservable(output)) {
throw new Error(
`@Output ${minifiedName} not initialized in '${directiveInstance.constructor.name}'.`);
}

const subscription = output.subscribe(listenerFn);
const idx = lCleanup.length;
lCleanup.push(listenerFn, subscription);
tCleanup && tCleanup.push(eventName, tNode.index, idx, -(idx + 1));
if (ngDevMode && !isObservable(output)) {
throw new Error(
`@Output ${minifiedName} not initialized in '${directiveInstance.constructor.name}'.`);
}

const subscription = output.subscribe(listenerFn);
const idx = lCleanup.length;
lCleanup.push(listenerFn, subscription);
tCleanup && tCleanup.push(eventName, tNode.index, idx, -(idx + 1));
}
}
}
@@ -21,7 +21,7 @@ import {diPublicInInjector, getNodeInjectable, getOrCreateNodeInjectorForNode} f
import {throwMultipleComponentError} from '../errors';
import {executeCheckHooks, executeInitAndCheckHooks, incrementInitPhaseFlags, registerPreOrderHooks} from '../hooks';
import {ACTIVE_INDEX, CONTAINER_HEADER_OFFSET, LContainer} from '../interfaces/container';
import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, FactoryFn, PipeDefListOrFactory, RenderFlags, ViewQueriesFunction} from '../interfaces/definition';
import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, PipeDefListOrFactory, RenderFlags, ViewQueriesFunction} from '../interfaces/definition';
import {INJECTOR_BLOOM_PARENT_SIZE, NodeInjectorFactory} from '../interfaces/injector';
import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, PropertyAliasValue, PropertyAliases, TAttributes, TContainerNode, TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TProjectionNode, TViewNode} from '../interfaces/node';
import {RComment, RElement, RText, Renderer3, RendererFactory3, isProceduralRenderer} from '../interfaces/renderer';
@@ -49,11 +49,6 @@ import {LCleanup, LViewBlueprint, MatchesArray, TCleanup, TNodeConstructor, TNod
*/
const _CLEAN_PROMISE = (() => Promise.resolve(null))();

export const enum BindingDirection {
Input,
Output,
}

/** Sets the host bindings for the current view. */
export function setHostBindings(tView: TView, viewData: LView): void {
const selectedIndex = getSelectedIndex();
@@ -803,39 +798,45 @@ export function createTNode(
}


function generatePropertyAliases(
inputAliasMap: {[publicName: string]: string}, directiveDefIdx: number,
propStore: PropertyAliases | null): PropertyAliases|null {
for (let publicName in inputAliasMap) {
if (inputAliasMap.hasOwnProperty(publicName)) {
propStore = propStore === null ? {} : propStore;
const internalName = inputAliasMap[publicName];

if (propStore.hasOwnProperty(publicName)) {
propStore[publicName].push(directiveDefIdx, publicName, internalName);
} else {
(propStore[publicName] = [directiveDefIdx, publicName, internalName]);
}
}
}
return propStore;
}

/**
* Consolidates all inputs or outputs of all directives on this logical node.
*
* @param tNode
* @param direction whether to consider inputs or outputs
* @returns PropertyAliases|null aggregate of all properties if any, `null` otherwise
* Initializes data structures required to work with directive outputs and outputs.
* Initialization is done for all directives matched on a given TNode.
*/
export function generatePropertyAliases(
tView: TView, tNode: TNode, direction: BindingDirection): PropertyAliases|null {
let propStore: PropertyAliases|null = null;
function initializeInputAndOutputAliases(tView: TView, tNode: TNode): void {
ngDevMode && assertFirstTemplatePass(tView);

const start = tNode.directiveStart;
const end = tNode.directiveEnd;
const defs = tView.data;

if (end > start) {
const isInput = direction === BindingDirection.Input;
const defs = tView.data;

for (let i = start; i < end; i++) {
const directiveDef = defs[i] as DirectiveDef<any>;
const propertyAliasMap: {[publicName: string]: string} =
isInput ? directiveDef.inputs : directiveDef.outputs;
for (let publicName in propertyAliasMap) {
if (propertyAliasMap.hasOwnProperty(publicName)) {
propStore = propStore || {};
const internalName = propertyAliasMap[publicName];
const hasProperty = propStore.hasOwnProperty(publicName);
hasProperty ? propStore[publicName].push(i, publicName, internalName) :
(propStore[publicName] = [i, publicName, internalName]);
}
}
}
let inputsStore: PropertyAliases|null = null;
let outputsStore: PropertyAliases|null = null;
for (let i = start; i < end; i++) {
const directiveDef = defs[i] as DirectiveDef<any>;
inputsStore = generatePropertyAliases(directiveDef.inputs, i, inputsStore);
outputsStore = generatePropertyAliases(directiveDef.outputs, i, outputsStore);
}
return propStore;

tNode.inputs = inputsStore;
tNode.outputs = outputsStore;
}

/**
@@ -863,13 +864,11 @@ export function elementPropertyInternal<T>(
loadRendererFn?: ((tNode: TNode, lView: LView) => Renderer3) | null): void {
ngDevMode && assertNotSame(value, NO_CHANGE as any, 'Incoming value should never be NO_CHANGE.');
const lView = getLView();
const tView = lView[TVIEW];
const element = getNativeByIndex(index, lView) as RElement | RComment;
const tNode = getTNode(index, lView);
let inputData: PropertyAliases|null|undefined;
let inputData = tNode.inputs;
let dataValue: PropertyAliasValue|undefined;
if (!nativeOnly && (inputData = initializeTNodeInputs(tView, tNode)) &&
(dataValue = inputData[propName])) {
if (!nativeOnly && inputData != null && (dataValue = inputData[propName])) {
setInputsForProperty(lView, dataValue, value);
if (isComponentHost(tNode)) markDirtyIfOnPush(lView, index + HEADER_OFFSET);
if (ngDevMode) {
@@ -1055,6 +1054,8 @@ export function resolveDirectives(
directiveDefIdx, def, tView, nodeIndex, initialPreOrderHooksLength,
initialPreOrderCheckHooksLength);
}

initializeInputAndOutputAliases(tView, tNode);
}
if (exportsMap) cacheMatchingLocalNames(tNode, localRefs, exportsMap);
}
@@ -1773,16 +1774,6 @@ export function storePropertyBindingMetadata(

export const CLEAN_PROMISE = _CLEAN_PROMISE;

export function initializeTNodeInputs(tView: TView, tNode: TNode): PropertyAliases|null {
// If tNode.inputs is undefined, a listener has created outputs, but inputs haven't
// yet been checked.
if (tNode.inputs === undefined) {
// mark inputs as checked
tNode.inputs = generatePropertyAliases(tView, tNode, BindingDirection.Input);
}
return tNode.inputs;
}

export function getCleanup(view: LView): any[] {
// top level variables should not be exported for performance reasons (PERF_NOTES.md)
return view[CLEANUP] || (view[CLEANUP] = ngDevMode ? new LCleanup() : []);
@@ -423,7 +423,7 @@
"name": "initNodeFlags"
},
{
"name": "initializeTNodeInputs"
"name": "initializeInputAndOutputAliases"
},
{
"name": "insertBloom"
@@ -936,7 +936,7 @@
"name": "initNodeFlags"
},
{
"name": "initializeTNodeInputs"
"name": "initializeInputAndOutputAliases"
},
{
"name": "injectElementRef"

0 comments on commit ad178c5

Please sign in to comment.
You can’t perform that action at this time.