refactor(core): Replace ExpandoInstructions with HostBindingOpCodes (#39301)

The `ExpandoInstructions` was unnecessarily convoluted way to solve the
problem of calling the `HostBindingFunction`s on components and
directives. The code was complicated and hard to fallow.

The replacement is a simplified way to achieve the same thing, which
is also more efficient in space and speed.

PR Close #39301
This commit is contained in:
Misko Hevery 2020-10-15 17:04:41 -07:00 committed by Alex Rickabaugh
parent 08f3d62391
commit 68d4de6770
17 changed files with 307 additions and 346 deletions

View File

@ -12,7 +12,7 @@
"master": { "master": {
"uncompressed": { "uncompressed": {
"runtime-es2015": 3037, "runtime-es2015": 3037,
"main-es2015": 448676, "main-es2015": 448085,
"polyfills-es2015": 52415 "polyfills-es2015": 52415
} }
} }

View File

@ -30,7 +30,7 @@
"master": { "master": {
"uncompressed": { "uncompressed": {
"runtime-es2015": 1485, "runtime-es2015": 1485,
"main-es2015": 136096, "main-es2015": 135506,
"polyfills-es2015": 37248 "polyfills-es2015": 37248
} }
} }
@ -39,7 +39,7 @@
"master": { "master": {
"uncompressed": { "uncompressed": {
"runtime-es2015": 2289, "runtime-es2015": 2289,
"main-es2015": 242460, "main-es2015": 241945,
"polyfills-es2015": 36938, "polyfills-es2015": 36938,
"5-es2015": 751 "5-es2015": 751
} }

View File

@ -198,57 +198,6 @@ The above will create the following layout:
| ... | ... | ... | ... | ... | ...
The `EXPANDO` section needs additional information for information stored in `TView.expandoInstructions`
| Index | `TView.expandoInstructions` | Meaning
| ----: | ---------------------------:| -------
| 0 | -10 | Negative numbers signify pointers to elements. In this case 10 (`<child>`)
| 1 | 2 | Injector size. Number of values to skip to get to Host Bindings.
| 2 | Child.ɵcmp.hostBindings | The function to call. (Only when `hostVars` is not `0`)
| 3 | Child.ɵcmp.hostVars | Number of host bindings to process. (Only when `hostVars` is not `0`)
| 4 | Tooltip.ɵdir.hostBindings | The function to call. (Only when `hostVars` is not `0`)
| 5 | Tooltip.ɵdir.hostVars | Number of host bindings to process. (Only when `hostVars` is not `0`)
The reason for this layout is to make the host binding update efficient using this pseudo code:
```typescript
let currentDirectiveIndex = -1;
let currentElementIndex = -1;
// This is global state which is used internally by hostBindings to know where the offset is
let bindingRootIndex = tView.expandoStartIndex;
for(var i = 0; i < tView.expandoInstructions.length; i++) {
let instruction = tView.expandoInstructions[i];
if (typeof instruction === 'number') {
// Numbers are used to update the indices.
if (instruction < 0) {
// Negative numbers means that we are starting new EXPANDO block and need to update current element and directive index
bindingRootIndex += BLOOM_OFFSET;
currentDirectiveIndex = bindingRootIndex;
currentElementIndex = -instruction;
} else {
bindingRootIndex += instruction;
}
} else {
// We know that we are hostBinding function so execute it.
instruction(currentDirectiveIndex, currentElementIndex);
currentDirectiveIndex++;
}
}
```
The above code should execute as:
| Instruction | `bindingRootIndex` | `currentDirectiveIndex` | `currentElementIndex`
| ----------: | -----------------: | ----------------------: | --------------------:
| (initial) | `11` | `-1` | `-1`
| `-10` | `19` | `\* new Child() *\ 19` | `\* <child> *\ 10`
| `2` | `21` | `\* new Child() *\ 19` | `\* <child> *\ 10`
| `Child.ɵcmp.hostBindings` | invoke with => | `\* new Child() *\ 19` | `\* <child> *\ 10`
| | `21` | `\* new Tooltip() *\ 20` | `\* <child> *\ 10`
| `Child.ɵcmp.hostVars` | `22` | `\* new Tooltip() *\ 20` | `\* <child> *\ 10`
| `Tooltip.ɵdir.hostBindings` | invoke with => | `\* new Tooltip() *\ 20` | `\* <child> *\ 10`
| | `22` | `21` | `\* <child> *\ 10`
| `Tooltip.ɵdir.hostVars` | `22` | `21` | `\* <child> *\ 10`
## `EXPANDO` and Injection ## `EXPANDO` and Injection
`EXPANDO` will also store the injection information for the element. `EXPANDO` will also store the injection information for the element.

View File

@ -16,9 +16,9 @@ import {assertComponentType} from './assert';
import {getComponentDef} from './definition'; import {getComponentDef} from './definition';
import {diPublicInInjector, getOrCreateNodeInjectorForNode} from './di'; import {diPublicInInjector, getOrCreateNodeInjectorForNode} from './di';
import {registerPostOrderHooks} from './hooks'; import {registerPostOrderHooks} from './hooks';
import {addHostBindingsToExpandoInstructions, addToViewTree, CLEAN_PROMISE, createLView, createTView, getOrCreateTComponentView, getOrCreateTNode, growHostVarsSpace, initTNodeFlags, instantiateRootComponent, invokeHostBindingsInCreationMode, locateHostElement, markAsComponentHost, refreshView, renderView} from './instructions/shared'; import {addToViewTree, CLEAN_PROMISE, createLView, createTView, getOrCreateTComponentView, getOrCreateTNode, initTNodeFlags, instantiateRootComponent, invokeHostBindingsInCreationMode, locateHostElement, markAsComponentHost, refreshView, registerHostBindingOpCodes, renderView} from './instructions/shared';
import {ComponentDef, ComponentType, RenderFlags} from './interfaces/definition'; import {ComponentDef, ComponentType, RenderFlags} from './interfaces/definition';
import {TElementNode, TNode, TNodeType} from './interfaces/node'; import {TElementNode, TNodeType} from './interfaces/node';
import {PlayerHandler} from './interfaces/player'; import {PlayerHandler} from './interfaces/player';
import {domRendererFactory3, RElement, Renderer3, RendererFactory3} from './interfaces/renderer'; import {domRendererFactory3, RElement, Renderer3, RendererFactory3} from './interfaces/renderer';
import {CONTEXT, HEADER_OFFSET, LView, LViewFlags, RootContext, RootContextFlags, TVIEW, TViewType} from './interfaces/view'; import {CONTEXT, HEADER_OFFSET, LView, LViewFlags, RootContext, RootContextFlags, TVIEW, TViewType} from './interfaces/view';
@ -230,7 +230,9 @@ export function createRootComponent<T>(
// We want to generate an empty QueryList for root content queries for backwards // We want to generate an empty QueryList for root content queries for backwards
// compatibility with ViewEngine. // compatibility with ViewEngine.
if (componentDef.contentQueries) { if (componentDef.contentQueries) {
componentDef.contentQueries(RenderFlags.Create, component, rootLView.length - 1); const tNode = getCurrentTNode()!;
ngDevMode && assertDefined(tNode, 'TNode expected');
componentDef.contentQueries(RenderFlags.Create, component, tNode.directiveStart);
} }
const rootTNode = getCurrentTNode()!; const rootTNode = getCurrentTNode()!;
@ -240,8 +242,9 @@ export function createRootComponent<T>(
setSelectedIndex(rootTNode.index); setSelectedIndex(rootTNode.index);
const rootTView = rootLView[TVIEW]; const rootTView = rootLView[TVIEW];
addHostBindingsToExpandoInstructions(rootTView, componentDef); registerHostBindingOpCodes(
growHostVarsSpace(rootTView, rootLView, componentDef.hostVars); rootTView, rootTNode, rootLView, rootTNode.directiveStart, rootTNode.directiveEnd,
componentDef);
invokeHostBindingsInCreationMode(componentDef, component); invokeHostBindingsInCreationMode(componentDef, component);
} }
@ -274,13 +277,12 @@ export function createRootContext(
* ``` * ```
*/ */
export function LifecycleHooksFeature(component: any, def: ComponentDef<any>): void { export function LifecycleHooksFeature(component: any, def: ComponentDef<any>): void {
const rootTView = readPatchedLView(component)![TVIEW]; const lView = readPatchedLView(component)!;
const dirIndex = rootTView.data.length - 1; ngDevMode && assertDefined(lView, 'LView is required');
const tView = lView[TVIEW];
// TODO(misko): replace `as TNode` with createTNode call. (needs refactoring to lose dep on const tNode = getCurrentTNode()!;
// LNode). ngDevMode && assertDefined(tNode, 'TNode is required');
registerPostOrderHooks( registerPostOrderHooks(tView, tNode);
rootTView, {directiveStart: dirIndex, directiveEnd: dirIndex + 1} as TNode);
} }
/** /**

View File

@ -7,7 +7,7 @@
*/ */
import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, DoCheck, OnChanges, OnDestroy, OnInit} from '../interface/lifecycle_hooks'; import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, DoCheck, OnChanges, OnDestroy, OnInit} from '../interface/lifecycle_hooks';
import {assertEqual, assertNotEqual} from '../util/assert'; import {assertDefined, assertEqual, assertNotEqual} from '../util/assert';
import {assertFirstCreatePass} from './assert'; import {assertFirstCreatePass} from './assert';
import {NgOnChangesFeatureImpl} from './features/ng_onchanges_feature'; import {NgOnChangesFeatureImpl} from './features/ng_onchanges_feature';
import {DirectiveDef} from './interfaces/definition'; import {DirectiveDef} from './interfaces/definition';
@ -77,6 +77,7 @@ export function registerPostOrderHooks(tView: TView, tNode: TNode): void {
// hooks for projected components and directives must be called *before* their hosts. // hooks for projected components and directives must be called *before* their hosts.
for (let i = tNode.directiveStart, end = tNode.directiveEnd; i < end; i++) { for (let i = tNode.directiveStart, end = tNode.directiveEnd; i < end; i++) {
const directiveDef = tView.data[i] as DirectiveDef<any>; const directiveDef = tView.data[i] as DirectiveDef<any>;
ngDevMode && assertDefined(directiveDef, 'Expecting DirectiveDef');
const lifecycleHooks: AfterContentInit&AfterContentChecked&AfterViewInit&AfterViewChecked& const lifecycleHooks: AfterContentInit&AfterContentChecked&AfterViewInit&AfterViewChecked&
OnDestroy = directiveDef.type.prototype; OnDestroy = directiveDef.type.prototype;
const { const {

View File

@ -154,7 +154,7 @@ export function i18nStartFirstCreatePass(
function createTNodeAndAddOpCode( function createTNodeAndAddOpCode(
tView: TView, rootTNode: TNode|null, existingTNodes: TNode[], lView: LView, tView: TView, rootTNode: TNode|null, existingTNodes: TNode[], lView: LView,
createOpCodes: I18nCreateOpCodes, text: string|null, isICU: boolean): TNode { createOpCodes: I18nCreateOpCodes, text: string|null, isICU: boolean): TNode {
const i18nNodeIdx = allocExpando(tView, lView, 1); const i18nNodeIdx = allocExpando(tView, lView, 1, null);
let opCode = i18nNodeIdx << I18nCreateOpCode.SHIFT; let opCode = i18nNodeIdx << I18nCreateOpCode.SHIFT;
let parentTNode = getCurrentParentTNode(); let parentTNode = getCurrentParentTNode();
@ -423,7 +423,7 @@ export function icuStart(
let bindingMask = 0; let bindingMask = 0;
const tIcu: TIcu = { const tIcu: TIcu = {
type: icuExpression.type, type: icuExpression.type,
currentCaseLViewIndex: allocExpando(tView, lView, 1), currentCaseLViewIndex: allocExpando(tView, lView, 1, null),
anchorIdx, anchorIdx,
cases: [], cases: [],
create: [], create: [],
@ -596,7 +596,7 @@ function walkIcuTree(
let bindingMask = 0; let bindingMask = 0;
let currentNode = parentNode.firstChild; let currentNode = parentNode.firstChild;
while (currentNode) { while (currentNode) {
const newIndex = allocExpando(tView, lView, 1); const newIndex = allocExpando(tView, lView, 1, null);
switch (currentNode.nodeType) { switch (currentNode.nodeType) {
case Node.ELEMENT_NODE: case Node.ELEMENT_NODE:
const element = currentNode as Element; const element = currentNode as Element;

View File

@ -22,7 +22,7 @@ import {SelectorFlags} from '../interfaces/projection';
import {LQueries, TQueries} from '../interfaces/query'; import {LQueries, TQueries} from '../interfaces/query';
import {RComment, RElement, Renderer3, RendererFactory3, RNode} from '../interfaces/renderer'; import {RComment, RElement, Renderer3, RendererFactory3, RNode} from '../interfaces/renderer';
import {getTStylingRangeNext, getTStylingRangeNextDuplicate, getTStylingRangePrev, getTStylingRangePrevDuplicate, TStylingKey, TStylingRange} from '../interfaces/styling'; import {getTStylingRangeNext, getTStylingRangeNextDuplicate, getTStylingRangePrev, getTStylingRangePrevDuplicate, TStylingKey, TStylingRange} from '../interfaces/styling';
import {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DebugNode, DECLARATION_VIEW, DestroyHookData, ExpandoInstructions, FLAGS, HEADER_OFFSET, HookData, HOST, INJECTOR, LContainerDebug as ILContainerDebug, LView, LViewDebug as ILViewDebug, LViewDebugRange, LViewDebugRangeContent, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, SANITIZER, T_HOST, TData, TView as ITView, TVIEW, TView, TViewType, TViewTypeAsString} from '../interfaces/view'; import {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DebugNode, DECLARATION_VIEW, DestroyHookData, FLAGS, HEADER_OFFSET, HookData, HOST, HostBindingOpCodes, INJECTOR, LContainerDebug as ILContainerDebug, LView, LViewDebug as ILViewDebug, LViewDebugRange, LViewDebugRangeContent, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, SANITIZER, T_HOST, TData, TView as ITView, TVIEW, TView, TViewType, TViewTypeAsString} from '../interfaces/view';
import {attachDebugObject} from '../util/debug_utils'; import {attachDebugObject} from '../util/debug_utils';
import {getParentInjectorIndex, getParentInjectorView} from '../util/injector_utils'; import {getParentInjectorIndex, getParentInjectorView} from '../util/injector_utils';
import {unwrapRNode} from '../util/view_utils'; import {unwrapRNode} from '../util/view_utils';
@ -115,38 +115,38 @@ function nameSuffix(text: string|null|undefined): string {
*/ */
export const TViewConstructor = class TView implements ITView { export const TViewConstructor = class TView implements ITView {
constructor( constructor(
public type: TViewType, // public type: TViewType,
public blueprint: LView, // public blueprint: LView,
public template: ComponentTemplate<{}>|null, // public template: ComponentTemplate<{}>|null,
public queries: TQueries|null, // public queries: TQueries|null,
public viewQuery: ViewQueriesFunction<{}>|null, // public viewQuery: ViewQueriesFunction<{}>|null,
public declTNode: ITNode|null, // public declTNode: ITNode|null,
public data: TData, // public data: TData,
public bindingStartIndex: number, // public bindingStartIndex: number,
public expandoStartIndex: number, // public expandoStartIndex: number,
public expandoInstructions: ExpandoInstructions|null, // public hostBindingOpCodes: HostBindingOpCodes|null,
public firstCreatePass: boolean, // public firstCreatePass: boolean,
public firstUpdatePass: boolean, // public firstUpdatePass: boolean,
public staticViewQueries: boolean, // public staticViewQueries: boolean,
public staticContentQueries: boolean, // public staticContentQueries: boolean,
public preOrderHooks: HookData|null, // public preOrderHooks: HookData|null,
public preOrderCheckHooks: HookData|null, // public preOrderCheckHooks: HookData|null,
public contentHooks: HookData|null, // public contentHooks: HookData|null,
public contentCheckHooks: HookData|null, // public contentCheckHooks: HookData|null,
public viewHooks: HookData|null, // public viewHooks: HookData|null,
public viewCheckHooks: HookData|null, // public viewCheckHooks: HookData|null,
public destroyHooks: DestroyHookData|null, // public destroyHooks: DestroyHookData|null,
public cleanup: any[]|null, // public cleanup: any[]|null,
public contentQueries: number[]|null, // public contentQueries: number[]|null,
public components: number[]|null, // public components: number[]|null,
public directiveRegistry: DirectiveDefList|null, // public directiveRegistry: DirectiveDefList|null,
public pipeRegistry: PipeDefList|null, // public pipeRegistry: PipeDefList|null,
public firstChild: ITNode|null, // public firstChild: ITNode|null,
public schemas: SchemaMetadata[]|null, // public schemas: SchemaMetadata[]|null,
public consts: TConstants|null, // public consts: TConstants|null,
public incompleteFirstPass: boolean, // public incompleteFirstPass: boolean,
public _decls: number, // public _decls: number,
public _vars: number, // public _vars: number,
) {} ) {}

View File

@ -12,7 +12,7 @@ import {CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, SchemaMetadata} from '../../me
import {ViewEncapsulation} from '../../metadata/view'; import {ViewEncapsulation} from '../../metadata/view';
import {validateAgainstEventAttributes, validateAgainstEventProperties} from '../../sanitization/sanitization'; import {validateAgainstEventAttributes, validateAgainstEventProperties} from '../../sanitization/sanitization';
import {Sanitizer} from '../../sanitization/sanitizer'; import {Sanitizer} from '../../sanitization/sanitizer';
import {assertDefined, assertDomNode, assertEqual, assertGreaterThan, assertGreaterThanOrEqual, assertIndexInRange, assertLessThan, assertNotEqual, assertNotSame, assertSame, assertString} from '../../util/assert'; import {assertDefined, assertDomNode, assertEqual, assertGreaterThan, assertGreaterThanOrEqual, assertIndexInRange, assertNotEqual, assertNotSame, assertSame, assertString} from '../../util/assert';
import {createNamedArrayType} from '../../util/named_array_type'; import {createNamedArrayType} from '../../util/named_array_type';
import {initNgDevMode} from '../../util/ng_dev_mode'; import {initNgDevMode} from '../../util/ng_dev_mode';
import {normalizeDebugBindingName, normalizeDebugBindingValue} from '../../util/ng_reflect'; import {normalizeDebugBindingName, normalizeDebugBindingValue} from '../../util/ng_reflect';
@ -24,13 +24,13 @@ import {diPublicInInjector, getNodeInjectable, getOrCreateNodeInjectorForNode} f
import {throwMultipleComponentError} from '../errors'; import {throwMultipleComponentError} from '../errors';
import {executeCheckHooks, executeInitAndCheckHooks, incrementInitPhaseFlags} from '../hooks'; import {executeCheckHooks, executeInitAndCheckHooks, incrementInitPhaseFlags} from '../hooks';
import {CONTAINER_HEADER_OFFSET, HAS_TRANSPLANTED_VIEWS, LContainer, MOVED_VIEWS} from '../interfaces/container'; import {CONTAINER_HEADER_OFFSET, HAS_TRANSPLANTED_VIEWS, LContainer, MOVED_VIEWS} from '../interfaces/container';
import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, PipeDefListOrFactory, RenderFlags, ViewQueriesFunction} from '../interfaces/definition'; import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, HostBindingsFunction, PipeDefListOrFactory, RenderFlags, ViewQueriesFunction} from '../interfaces/definition';
import {NodeInjectorFactory, NodeInjectorOffset} from '../interfaces/injector'; import {NodeInjectorFactory} from '../interfaces/injector';
import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, PropertyAliases, PropertyAliasValue, TAttributes, TConstantsOrFactory, TContainerNode, TDirectiveHostNode, TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TProjectionNode} from '../interfaces/node'; import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, PropertyAliases, PropertyAliasValue, TAttributes, TConstantsOrFactory, TContainerNode, TDirectiveHostNode, TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeType, TProjectionNode} from '../interfaces/node';
import {isProceduralRenderer, RComment, RElement, Renderer3, RendererFactory3, RNode, RText} from '../interfaces/renderer'; import {isProceduralRenderer, RComment, RElement, Renderer3, RendererFactory3, RNode, RText} from '../interfaces/renderer';
import {SanitizerFn} from '../interfaces/sanitization'; import {SanitizerFn} from '../interfaces/sanitization';
import {isComponentDef, isComponentHost, isContentQueryHost, isRootView} from '../interfaces/type_checks'; import {isComponentDef, isComponentHost, isContentQueryHost, isRootView} from '../interfaces/type_checks';
import {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_COMPONENT_VIEW, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, InitPhaseState, INJECTOR, LView, LViewFlags, NEXT, PARENT, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, T_HOST, TData, TRANSPLANTED_VIEWS_TO_REFRESH, TVIEW, TView, TViewType} from '../interfaces/view'; import {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_COMPONENT_VIEW, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, HostBindingOpCodes, InitPhaseState, INJECTOR, LView, LViewFlags, NEXT, PARENT, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, T_HOST, TData, TRANSPLANTED_VIEWS_TO_REFRESH, TVIEW, TView, TViewType} from '../interfaces/view';
import {assertPureTNodeType, assertTNodeType} from '../node_assert'; import {assertPureTNodeType, assertTNodeType} from '../node_assert';
import {updateTextNode} from '../node_manipulation'; import {updateTextNode} from '../node_manipulation';
import {isInlineTemplate, isNodeMatchingSelectorList} from '../node_selector_matcher'; import {isInlineTemplate, isNodeMatchingSelectorList} from '../node_selector_matcher';
@ -53,65 +53,31 @@ import {attachLContainerDebug, attachLViewDebug, cloneToLViewFromTViewBlueprint,
const _CLEAN_PROMISE = (() => Promise.resolve(null))(); const _CLEAN_PROMISE = (() => Promise.resolve(null))();
/** /**
* Process the `TView.expandoInstructions`. (Execute the `hostBindings`.) * Invoke `HostBindingsFunction`s for view.
* *
* @param tView `TView` containing the `expandoInstructions` * This methods executes `TView.hostBindingOpCodes`. It is used to execute the
* @param lView `LView` associated with the `TView` * `HostBindingsFunction`s associated with the current `LView`.
*
* @param tView Current `TView`.
* @param lView Current `LView`.
*/ */
export function setHostBindingsByExecutingExpandoInstructions(tView: TView, lView: LView): void { export function processHostBindingOpCodes(tView: TView, lView: LView): void {
ngDevMode && assertSame(tView, lView[TVIEW], '`LView` is not associated with the `TView`!'); const hostBindingOpCodes = tView.hostBindingOpCodes;
if (hostBindingOpCodes === null) return;
try { try {
const expandoInstructions = tView.expandoInstructions; for (let i = 0; i < hostBindingOpCodes.length; i++) {
if (expandoInstructions !== null) { const opCode = hostBindingOpCodes[i] as number;
let bindingRootIndex = tView.expandoStartIndex; if (opCode < 0) {
let currentDirectiveIndex = -1; // Negative numbers are element indexes.
let currentElementIndex = -1; setSelectedIndex(~opCode);
// TODO(misko): PERF It is possible to get here with `TView.expandoInstructions` containing no } else {
// functions to execute. This is wasteful as there is no work to be done, but we still need // Positive numbers are NumberTuple which store bindingRootIndex and directiveIndex.
// to iterate over the instructions. const directiveIdx = opCode;
// In example of this is in this test: `host_binding_spec.ts` const bindingRootIndx = hostBindingOpCodes[++i] as number;
// `fit('should not cause problems if detectChanges is called when a property updates', ...` const hostBindingFn = hostBindingOpCodes[++i] as HostBindingsFunction<any>;
// In the above test we get here with expando [0, 0, 1] which requires a lot of processing but setBindingRootForHostBindings(bindingRootIndx, directiveIdx);
// there is no function to execute. const context = lView[directiveIdx];
for (let i = 0; i < expandoInstructions.length; i++) { hostBindingFn(RenderFlags.Update, context);
const instruction = expandoInstructions[i];
if (typeof instruction === 'number') {
if (instruction <= 0) {
// Negative numbers mean that we are starting new EXPANDO block and need to update
// the current element and directive index.
currentElementIndex = ~instruction;
setSelectedIndex(currentElementIndex);
// Injector block and providers are taken into account.
const providerCount = (expandoInstructions[++i] as number);
bindingRootIndex += NodeInjectorOffset.SIZE + providerCount;
currentDirectiveIndex = bindingRootIndex;
} else {
// This is either the injector size (so the binding root can skip over directives
// and get to the first set of host bindings on this node) or the host var count
// (to get to the next set of host bindings on this node).
bindingRootIndex += instruction;
}
} else {
// If it's not a number, it's a host binding function that needs to be executed.
if (instruction !== null) {
ngDevMode &&
assertLessThan(
currentDirectiveIndex, TNodeProviderIndexes.CptViewProvidersCountShifter,
'Reached the max number of host bindings');
setBindingRootForHostBindings(bindingRootIndex, currentDirectiveIndex);
const hostCtx = lView[currentDirectiveIndex];
instruction(RenderFlags.Update, hostCtx);
}
// TODO(misko): PERF Relying on incrementing the `currentDirectiveIndex` here is
// sub-optimal. The implications are that if we have a lot of directives but none of them
// have host bindings we nevertheless need to iterate over the expando instructions to
// update the counter. It would be much better if we could encode the
// `currentDirectiveIndex` into the `expandoInstruction` array so that we only need to
// iterate over those directives which actually have `hostBindings`.
currentDirectiveIndex++;
}
} }
} }
} finally { } finally {
@ -119,6 +85,7 @@ export function setHostBindingsByExecutingExpandoInstructions(tView: TView, lVie
} }
} }
/** Refreshes all content queries declared by directives in a given view */ /** Refreshes all content queries declared by directives in a given view */
function refreshContentQueries(tView: TView, lView: LView): void { function refreshContentQueries(tView: TView, lView: LView): void {
const contentQueries = tView.contentQueries; const contentQueries = tView.contentQueries;
@ -128,6 +95,7 @@ function refreshContentQueries(tView: TView, lView: LView): void {
const directiveDefIdx = contentQueries[i + 1]; const directiveDefIdx = contentQueries[i + 1];
if (directiveDefIdx !== -1) { if (directiveDefIdx !== -1) {
const directiveDef = tView.data[directiveDefIdx] as DirectiveDef<any>; const directiveDef = tView.data[directiveDefIdx] as DirectiveDef<any>;
ngDevMode && assertDefined(directiveDef, 'DirectiveDef not found.');
ngDevMode && ngDevMode &&
assertDefined(directiveDef.contentQueries, 'contentQueries function should be defined'); assertDefined(directiveDef.contentQueries, 'contentQueries function should be defined');
setCurrentQueryIndex(queryStartIdx); setCurrentQueryIndex(queryStartIdx);
@ -279,10 +247,14 @@ export function createTNodeAtIndex(
* @param tView `TView` associated with `LView` * @param tView `TView` associated with `LView`
* @param lView The `LView` containing the blueprint to adjust * @param lView The `LView` containing the blueprint to adjust
* @param numSlotsToAlloc The number of slots to alloc in the LView, should be >0 * @param numSlotsToAlloc The number of slots to alloc in the LView, should be >0
* @param initialValue Initial value to store in blueprint
*/ */
export function allocExpando(tView: TView, lView: LView, numSlotsToAlloc: number): number { export function allocExpando(
tView: TView, lView: LView, numSlotsToAlloc: number, initialValue: any): number {
if (numSlotsToAlloc === 0) return -1;
if (ngDevMode) { if (ngDevMode) {
assertGreaterThan(numSlotsToAlloc, 0, 'The number of slots to alloc should be greater than 0'); assertFirstCreatePass(tView);
assertSame(tView, lView[TVIEW], '`LView` must be associated with `TView`!');
assertEqual(tView.data.length, lView.length, 'Expecting LView to be same size as TView'); assertEqual(tView.data.length, lView.length, 'Expecting LView to be same size as TView');
assertEqual( assertEqual(
tView.data.length, tView.blueprint.length, 'Expecting Blueprint to be same size as TView'); tView.data.length, tView.blueprint.length, 'Expecting Blueprint to be same size as TView');
@ -290,21 +262,9 @@ export function allocExpando(tView: TView, lView: LView, numSlotsToAlloc: number
} }
const allocIdx = lView.length; const allocIdx = lView.length;
for (let i = 0; i < numSlotsToAlloc; i++) { for (let i = 0; i < numSlotsToAlloc; i++) {
tView.blueprint.push(null); lView.push(initialValue);
tView.blueprint.push(initialValue);
tView.data.push(null); tView.data.push(null);
lView.push(null);
}
// We should only increment the expando start index if there aren't already directives
// and injectors saved in the "expando" section
if (!tView.expandoInstructions) {
tView.expandoStartIndex += numSlotsToAlloc;
} else {
// Since we're adding the dynamic nodes into the expando section, we need to let the host
// bindings know that they should skip x slots
// FIXME(misko): Refactor `expandoInstructions` so that it does not rely on relative binding
// offsets, but absolute values which Means we would not have to store it here.
tView.expandoInstructions.push(numSlotsToAlloc);
} }
return allocIdx; return allocIdx;
} }
@ -454,7 +414,7 @@ export function refreshView<T>(
} }
} }
setHostBindingsByExecutingExpandoInstructions(tView, lView); processHostBindingOpCodes(tView, lView);
// Refresh child component views. // Refresh child component views.
const components = tView.components; const components = tView.components;
@ -653,7 +613,7 @@ export function createTView(
const consts = typeof constsOrFactory === 'function' ? constsOrFactory() : constsOrFactory; const consts = typeof constsOrFactory === 'function' ? constsOrFactory() : constsOrFactory;
const tView = blueprint[TVIEW as any] = ngDevMode ? const tView = blueprint[TVIEW as any] = ngDevMode ?
new TViewConstructor( new TViewConstructor(
type, type, // type: TViewType,
blueprint, // blueprint: LView, blueprint, // blueprint: LView,
templateFn, // template: ComponentTemplate<{}>|null, templateFn, // template: ComponentTemplate<{}>|null,
null, // queries: TQueries|null null, // queries: TQueries|null
@ -662,24 +622,24 @@ export function createTView(
cloneToTViewData(blueprint).fill(null, bindingStartIndex), // data: TData, cloneToTViewData(blueprint).fill(null, bindingStartIndex), // data: TData,
bindingStartIndex, // bindingStartIndex: number, bindingStartIndex, // bindingStartIndex: number,
initialViewLength, // expandoStartIndex: number, initialViewLength, // expandoStartIndex: number,
null, // expandoInstructions: ExpandoInstructions|null, null, // hostBindingOpCodes: HostBindingOpCodes,
true, // firstCreatePass: boolean, true, // firstCreatePass: boolean,
true, // firstUpdatePass: boolean, true, // firstUpdatePass: boolean,
false, // staticViewQueries: boolean, false, // staticViewQueries: boolean,
false, // staticContentQueries: boolean, false, // staticContentQueries: boolean,
null, // preOrderHooks: HookData|null, null, // preOrderHooks: HookData|null,
null, // preOrderCheckHooks: HookData|null, null, // preOrderCheckHooks: HookData|null,
null, // contentHooks: HookData|null, null, // contentHooks: HookData|null,
null, // contentCheckHooks: HookData|null, null, // contentCheckHooks: HookData|null,
null, // viewHooks: HookData|null, null, // viewHooks: HookData|null,
null, // viewCheckHooks: HookData|null, null, // viewCheckHooks: HookData|null,
null, // destroyHooks: DestroyHookData|null, null, // destroyHooks: DestroyHookData|null,
null, // cleanup: any[]|null, null, // cleanup: any[]|null,
null, // contentQueries: number[]|null, null, // contentQueries: number[]|null,
null, // components: number[]|null, null, // components: number[]|null,
typeof directives === 'function' ? typeof directives === 'function' ? //
directives() : directives() : //
directives, // directiveRegistry: DirectiveDefList|null, directives, // directiveRegistry: DirectiveDefList|null,
typeof pipes === 'function' ? pipes() : pipes, // pipeRegistry: PipeDefList|null, typeof pipes === 'function' ? pipes() : pipes, // pipeRegistry: PipeDefList|null,
null, // firstChild: TNode|null, null, // firstChild: TNode|null,
schemas, // schemas: SchemaMetadata[]|null, schemas, // schemas: SchemaMetadata[]|null,
@ -698,7 +658,7 @@ export function createTView(
data: blueprint.slice().fill(null, bindingStartIndex), data: blueprint.slice().fill(null, bindingStartIndex),
bindingStartIndex: bindingStartIndex, bindingStartIndex: bindingStartIndex,
expandoStartIndex: initialViewLength, expandoStartIndex: initialViewLength,
expandoInstructions: null, hostBindingOpCodes: null,
firstCreatePass: true, firstCreatePass: true,
firstUpdatePass: true, firstUpdatePass: true,
staticViewQueries: false, staticViewQueries: false,
@ -945,14 +905,14 @@ function initializeInputAndOutputAliases(tView: TView, tNode: TNode): void {
const start = tNode.directiveStart; const start = tNode.directiveStart;
const end = tNode.directiveEnd; const end = tNode.directiveEnd;
const defs = tView.data; const tViewData = tView.data;
const tNodeAttrs = tNode.attrs; const tNodeAttrs = tNode.attrs;
const inputsFromAttrs: InitialInputData = ngDevMode ? new TNodeInitialInputs() : []; const inputsFromAttrs: InitialInputData = ngDevMode ? new TNodeInitialInputs() : [];
let inputsStore: PropertyAliases|null = null; let inputsStore: PropertyAliases|null = null;
let outputsStore: PropertyAliases|null = null; let outputsStore: PropertyAliases|null = null;
for (let i = start; i < end; i++) { for (let i = start; i < end; i++) {
const directiveDef = defs[i] as DirectiveDef<any>; const directiveDef = tViewData[i] as DirectiveDef<any>;
const directiveInputs = directiveDef.inputs; const directiveInputs = directiveDef.inputs;
// Do not use unbound attributes as inputs to structural directives, since structural // Do not use unbound attributes as inputs to structural directives, since structural
// directive inputs can only be set using microsyntax (e.g. `<div *dir="exp">`). // directive inputs can only be set using microsyntax (e.g. `<div *dir="exp">`).
@ -1146,10 +1106,15 @@ export function instantiateRootComponent<T>(tView: TView, lView: LView, def: Com
const rootTNode = getCurrentTNode()!; const rootTNode = getCurrentTNode()!;
if (tView.firstCreatePass) { if (tView.firstCreatePass) {
if (def.providersResolver) def.providersResolver(def); if (def.providersResolver) def.providersResolver(def);
generateExpandoInstructionBlock(tView, rootTNode, 1); const directiveIndex = allocExpando(tView, lView, 1, null);
baseResolveDirective(tView, lView, def); ngDevMode &&
assertEqual(
directiveIndex, rootTNode.directiveStart,
'Because this is a root component the allocated expando should match the TNode component.');
configureViewWithDirective(tView, rootTNode, lView, directiveIndex, def);
} }
const directive = getNodeInjectable(lView, tView, lView.length - 1, rootTNode as TElementNode); const directive =
getNodeInjectable(lView, tView, rootTNode.directiveStart, rootTNode as TElementNode);
attachPatchData(directive, lView); attachPatchData(directive, lView);
const native = getNativeByTNode(rootTNode, lView); const native = getNativeByTNode(rootTNode, lView);
if (native) { if (native) {
@ -1174,7 +1139,6 @@ export function resolveDirectives(
const exportsMap: ({[key: string]: number}|null) = localRefs === null ? null : {'': -1}; const exportsMap: ({[key: string]: number}|null) = localRefs === null ? null : {'': -1};
if (directiveDefs !== null) { if (directiveDefs !== null) {
let totalDirectiveHostVars = 0;
hasDirectives = true; hasDirectives = true;
initTNodeFlags(tNode, tView.data.length, directiveDefs.length); initTNodeFlags(tNode, tView.data.length, directiveDefs.length);
// When the same token is provided by several directives on the same node, some rules apply in // When the same token is provided by several directives on the same node, some rules apply in
@ -1187,18 +1151,22 @@ export function resolveDirectives(
const def = directiveDefs[i]; const def = directiveDefs[i];
if (def.providersResolver) def.providersResolver(def); if (def.providersResolver) def.providersResolver(def);
} }
generateExpandoInstructionBlock(tView, tNode, directiveDefs.length);
let preOrderHooksFound = false; let preOrderHooksFound = false;
let preOrderCheckHooksFound = false; let preOrderCheckHooksFound = false;
let directiveIdx = allocExpando(tView, lView, directiveDefs.length, null);
ngDevMode &&
assertSame(
directiveIdx, tNode.directiveStart,
'TNode.directiveStart should point to just allocated space');
for (let i = 0; i < directiveDefs.length; i++) { for (let i = 0; i < directiveDefs.length; i++) {
const def = directiveDefs[i]; const def = directiveDefs[i];
// Merge the attrs in the order of matches. This assumes that the first directive is the // Merge the attrs in the order of matches. This assumes that the first directive is the
// component itself, so that the component has the least priority. // component itself, so that the component has the least priority.
tNode.mergedAttrs = mergeHostAttrs(tNode.mergedAttrs, def.hostAttrs); tNode.mergedAttrs = mergeHostAttrs(tNode.mergedAttrs, def.hostAttrs);
baseResolveDirective(tView, lView, def); configureViewWithDirective(tView, tNode, lView, directiveIdx, def);
saveNameToExportMap(directiveIdx, def, exportsMap);
saveNameToExportMap(tView.data!.length - 1, def, exportsMap);
if (def.contentQueries !== null) tNode.flags |= TNodeFlags.hasContentQuery; if (def.contentQueries !== null) tNode.flags |= TNodeFlags.hasContentQuery;
if (def.hostBindings !== null || def.hostAttrs !== null || def.hostVars !== 0) if (def.hostBindings !== null || def.hostAttrs !== null || def.hostVars !== 0)
@ -1221,12 +1189,10 @@ export function resolveDirectives(
preOrderCheckHooksFound = true; preOrderCheckHooksFound = true;
} }
addHostBindingsToExpandoInstructions(tView, def); directiveIdx++;
totalDirectiveHostVars += def.hostVars;
} }
initializeInputAndOutputAliases(tView, tNode); initializeInputAndOutputAliases(tView, tNode);
growHostVarsSpace(tView, lView, totalDirectiveHostVars);
} }
if (exportsMap) cacheMatchingLocalNames(tNode, localRefs, exportsMap); if (exportsMap) cacheMatchingLocalNames(tNode, localRefs, exportsMap);
} }
@ -1236,51 +1202,57 @@ export function resolveDirectives(
} }
/** /**
* Add `hostBindings` to the `TView.expandoInstructions`. * Add `hostBindings` to the `TView.hostBindingOpCodes`.
* *
* @param tView `TView` to which the `hostBindings` should be added. * @param tView `TView` to which the `hostBindings` should be added.
* @param tNode `TNode` the element which contains the directive
* @param lView `LView` current `LView`
* @param directiveIdx Directive index in view.
* @param directiveVarsIdx Where will the directive's vars be stored
* @param def `ComponentDef`/`DirectiveDef`, which contains the `hostVars`/`hostBindings` to add. * @param def `ComponentDef`/`DirectiveDef`, which contains the `hostVars`/`hostBindings` to add.
*/ */
export function addHostBindingsToExpandoInstructions( export function registerHostBindingOpCodes(
tView: TView, def: ComponentDef<any>|DirectiveDef<any>): void { tView: TView, tNode: TNode, lView: LView, directiveIdx: number, directiveVarsIdx: number,
def: ComponentDef<any>|DirectiveDef<any>): void {
ngDevMode && assertFirstCreatePass(tView); ngDevMode && assertFirstCreatePass(tView);
const expando = tView.expandoInstructions!;
// TODO(misko): PERF we are adding `hostBindings` even if there is nothing to add! This is const hostBindings = def.hostBindings;
// suboptimal for performance. `def.hostBindings` may be null, if (hostBindings) {
// but we still need to push null to the array as a placeholder let hostBindingOpCodes = tView.hostBindingOpCodes;
// to ensure the directive counter is incremented (so host if (hostBindingOpCodes === null) {
// binding functions always line up with the corrective directive). hostBindingOpCodes = tView.hostBindingOpCodes = [] as any as HostBindingOpCodes;
// This is suboptimal for performance. See `currentDirectiveIndex` }
// comment in `setHostBindingsByExecutingExpandoInstructions` for more const elementIndx = ~tNode.index;
// details. expando.push(def.hostBindings); if (lastSelectedElementIdx(hostBindingOpCodes) != elementIndx) {
expando.push(def.hostBindings); // Conditionally add select element so that we are more efficient in execution.
const hostVars = def.hostVars; // NOTE: this is strictly not necessary and it trades code size for runtime perf.
if (hostVars !== 0) { // (We could just always add it.)
expando.push(def.hostVars); hostBindingOpCodes.push(elementIndx);
}
hostBindingOpCodes.push(directiveIdx, directiveVarsIdx, hostBindings);
} }
} }
/** /**
* Grow the `LView`, blueprint and `TView.data` to accommodate the `hostBindings`. * Returns the last selected element index in the `HostBindingOpCodes`
* *
* To support locality we don't know ahead of time how many `hostVars` of the containing directives * For perf reasons we don't need to update the selected element index in `HostBindingOpCodes` only
* we need to allocate. For this reason we allow growing these data structures as we discover more * if it changes. This method returns the last index (or '0' if not found.)
* directives to accommodate them.
* *
* @param tView `TView` which needs to be grown. * Selected element index are only the ones which are negative.
* @param lView `LView` which needs to be grown.
* @param count Size by which we need to grow the data structures.
*/ */
export function growHostVarsSpace(tView: TView, lView: LView, count: number) { function lastSelectedElementIdx(hostBindingOpCodes: HostBindingOpCodes): number {
ngDevMode && assertFirstCreatePass(tView); let i = hostBindingOpCodes.length;
ngDevMode && assertSame(tView, lView[TVIEW], '`LView` must be associated with `TView`!'); while (i > 0) {
for (let i = 0; i < count; i++) { const value = hostBindingOpCodes[--i];
lView.push(NO_CHANGE); if (typeof value === 'number' && value < 0) {
tView.blueprint.push(NO_CHANGE); return value;
tView.data.push(null); }
} }
return 0;
} }
/** /**
* Instantiate all the directives that were previously resolved on the current node. * Instantiate all the directives that were previously resolved on the current node.
*/ */
@ -1321,7 +1293,6 @@ function instantiateAllDirectives(
function invokeDirectivesHostBindings(tView: TView, lView: LView, tNode: TNode) { function invokeDirectivesHostBindings(tView: TView, lView: LView, tNode: TNode) {
const start = tNode.directiveStart; const start = tNode.directiveStart;
const end = tNode.directiveEnd; const end = tNode.directiveEnd;
const expando = tView.expandoInstructions!;
const firstCreatePass = tView.firstCreatePass; const firstCreatePass = tView.firstCreatePass;
const elementIndex = tNode.index; const elementIndex = tNode.index;
const currentDirectiveIndex = getCurrentDirectiveIndex(); const currentDirectiveIndex = getCurrentDirectiveIndex();
@ -1333,8 +1304,6 @@ function invokeDirectivesHostBindings(tView: TView, lView: LView, tNode: TNode)
setCurrentDirectiveIndex(dirIndex); setCurrentDirectiveIndex(dirIndex);
if (def.hostBindings !== null || def.hostVars !== 0 || def.hostAttrs !== null) { if (def.hostBindings !== null || def.hostVars !== 0 || def.hostAttrs !== null) {
invokeHostBindingsInCreationMode(def, directive); invokeHostBindingsInCreationMode(def, directive);
} else if (firstCreatePass) {
expando.push(null);
} }
} }
} finally { } finally {
@ -1355,26 +1324,6 @@ export function invokeHostBindingsInCreationMode(def: DirectiveDef<any>, directi
} }
} }
/**
* Generates a new block in TView.expandoInstructions for this node.
*
* Each expando block starts with the element index (turned negative so we can distinguish
* it from the hostVar count) and the directive count. See more in VIEW_DATA.md.
*/
export function generateExpandoInstructionBlock(
tView: TView, tNode: TNode, directiveCount: number): void {
ngDevMode &&
assertEqual(
tView.firstCreatePass, true,
'Expando block should only be generated on first create pass.');
const elementIndex = ~tNode.index;
const providerStartIndex = tNode.providerIndexes & TNodeProviderIndexes.ProvidersStartIndexMask;
const providerCount = tView.data.length - providerStartIndex;
(tView.expandoInstructions || (tView.expandoInstructions = []))
.push(elementIndex, providerCount, directiveCount);
}
/** /**
* Matches the current node against all available selectors. * Matches the current node against all available selectors.
* If a component is matched (at most one), it is returned in first position in the array. * If a component is matched (at most one), it is returned in first position in the array.
@ -1450,15 +1399,15 @@ function cacheMatchingLocalNames(
* to their directive instances. * to their directive instances.
*/ */
function saveNameToExportMap( function saveNameToExportMap(
index: number, def: DirectiveDef<any>|ComponentDef<any>, directiveIdx: number, def: DirectiveDef<any>|ComponentDef<any>,
exportsMap: {[key: string]: number}|null) { exportsMap: {[key: string]: number}|null) {
if (exportsMap) { if (exportsMap) {
if (def.exportAs) { if (def.exportAs) {
for (let i = 0; i < def.exportAs.length; i++) { for (let i = 0; i < def.exportAs.length; i++) {
exportsMap[def.exportAs[i]] = index; exportsMap[def.exportAs[i]] = directiveIdx;
} }
} }
if (isComponentDef(def)) exportsMap[''] = index; if (isComponentDef(def)) exportsMap[''] = directiveIdx;
} }
} }
@ -1479,13 +1428,32 @@ export function initTNodeFlags(tNode: TNode, index: number, numberOfDirectives:
tNode.providerIndexes = index; tNode.providerIndexes = index;
} }
function baseResolveDirective<T>(tView: TView, viewData: LView, def: DirectiveDef<T>) { /**
tView.data.push(def); * Setup directive for instantiation.
*
* We need to create a `NodeInjectorFactory` which is then inserted in both the `Blueprint` as well
* as `LView`. `TView` gets the `DirectiveDef`.
*
* @param tView `TView`
* @param tNode `TNode`
* @param lView `LView`
* @param directiveIndex Index where the directive will be stored in the Expando.
* @param def `DirectiveDef`
*/
function configureViewWithDirective<T>(
tView: TView, tNode: TNode, lView: LView, directiveIndex: number, def: DirectiveDef<T>): void {
ngDevMode &&
assertGreaterThanOrEqual(directiveIndex, HEADER_OFFSET, 'Must be in Expando section');
tView.data[directiveIndex] = def;
const directiveFactory = const directiveFactory =
def.factory || ((def as {factory: Function}).factory = getFactoryDef(def.type, true)); def.factory || ((def as {factory: Function}).factory = getFactoryDef(def.type, true));
const nodeInjectorFactory = new NodeInjectorFactory(directiveFactory, isComponentDef(def), null); const nodeInjectorFactory = new NodeInjectorFactory(directiveFactory, isComponentDef(def), null);
tView.blueprint.push(nodeInjectorFactory); tView.blueprint[directiveIndex] = nodeInjectorFactory;
viewData.push(nodeInjectorFactory); lView[directiveIndex] = nodeInjectorFactory;
registerHostBindingOpCodes(
tView, tNode, lView, directiveIndex, allocExpando(tView, lView, def.hostVars, NO_CHANGE),
def);
} }
function addComponentLogic<T>(lView: LView, hostTNode: TElementNode, def: ComponentDef<T>): void { function addComponentLogic<T>(lView: LView, hostTNode: TElementNode, def: ComponentDef<T>): void {

View File

@ -409,11 +409,17 @@ export interface TNode {
/** /**
* Stores starting index of the directives. * Stores starting index of the directives.
*
* NOTE: The first directive is always component (if present).
*/ */
directiveStart: number; directiveStart: number;
/** /**
* Stores final exclusive index of the directives. * Stores final exclusive index of the directives.
*
* The area right behind the `directiveStart-directiveEnd` range is used to allocate the
* `HostBindingFunction` `vars` (or null if no bindings.) Therefore `directiveEnd` is used to set
* `LFrame.bindingRootIndex` before `HostBindingFunction` is executed.
*/ */
directiveEnd: number; directiveEnd: number;

View File

@ -427,11 +427,69 @@ export const enum PreOrderHookFlags {
} }
/** /**
* Set of instructions used to process host bindings efficiently. * Stores a set of OpCodes to process `HostBindingsFunction` associated with a current view.
*
* In order to invoke `HostBindingsFunction` we need:
* 1. 'elementIdx`: Index to the element associated with the `HostBindingsFunction`.
* 2. 'directiveIdx`: Index to the directive associated with the `HostBindingsFunction`. (This will
* become the context for the `HostBindingsFunction` invocation.)
* 3. `bindingRootIdx`: Location where the bindings for the `HostBindingsFunction` start. Internally
* `HostBindingsFunction` binding indexes start from `0` so we need to add `bindingRootIdx` to
* it.
* 4. `HostBindingsFunction`: A host binding function to execute.
*
* The above information needs to be encoded into the `HostBindingOpCodes` in an efficient manner.
*
* 1. `elementIdx` is encoded into the `HostBindingOpCodes` as `~elementIdx` (so a negative number);
* 2. `directiveIdx`
* 3. `bindingRootIdx`
* 4. `HostBindingsFunction` is passed in as is.
*
* The `HostBindingOpCodes` array contains:
* - negative number to select the element index.
* - followed by 1 or more of:
* - a number to select the directive index
* - a number to select the bindingRoot index
* - and a function to invoke.
*
* ## Example
*
* ```
* const hostBindingOpCodes = [
* ~30, // Select element 30
* 40, 45, MyDir.ɵdir.hostBindings // Invoke host bindings on MyDir on element 30;
* // directiveIdx = 40; bindingRootIdx = 45;
* 50, 55, OtherDir.ɵdir.hostBindings // Invoke host bindings on OtherDire on element 30
* // directiveIdx = 50; bindingRootIdx = 55;
* ]
* ```
*
* ## Pseudocode
* ```
* const hostBindingOpCodes = tView.hostBindingOpCodes;
* if (hostBindingOpCodes === null) return;
* for (let i = 0; i < hostBindingOpCodes.length; i++) {
* const opCode = hostBindingOpCodes[i] as number;
* if (opCode < 0) {
* // Negative numbers are element indexes.
* setSelectedIndex(~opCode);
* } else {
* // Positive numbers are NumberTuple which store bindingRootIndex and directiveIndex.
* const directiveIdx = opCode;
* const bindingRootIndx = hostBindingOpCodes[++i] as number;
* const hostBindingFn = hostBindingOpCodes[++i] as HostBindingsFunction<any>;
* setBindingRootForHostBindings(bindingRootIndx, directiveIdx);
* const context = lView[directiveIdx];
* hostBindingFn(RenderFlags.Update, context);
* }
* }
* ```
* *
* See VIEW_DATA.md for more information.
*/ */
export interface ExpandoInstructions extends Array<number|HostBindingsFunction<any>|null> {} export interface HostBindingOpCodes extends Array<number|HostBindingsFunction<any>> {
__brand__: 'HostBindingOpCodes';
debug?: string[];
}
/** /**
* Explicitly marks `TView` as a specific type in `ngDevMode` * Explicitly marks `TView` as a specific type in `ngDevMode`
@ -572,13 +630,11 @@ export interface TView {
firstChild: TNode|null; firstChild: TNode|null;
/** /**
* Set of instructions used to process host bindings efficiently. * Stores the OpCodes to be replayed during change-detection to process the `HostBindings`
* *
* See VIEW_DATA.md for more information. * See `HostBindingOpCodes` for encoding details.
*/ */
// TODO(misko): `expandoInstructions` should be renamed to `hostBindingsInstructions` since they hostBindingOpCodes: HostBindingOpCodes|null;
// keep track of `hostBindings` which need to be executed.
expandoInstructions: ExpandoInstructions|null;
/** /**
* Full registry of directives and components that may be found in this view. * Full registry of directives and components that may be found in this view.

View File

@ -570,7 +570,7 @@ function createTQuery(tView: TView, metadata: TQueryMetadata, nodeIndex: number)
function saveContentQueryAndDirectiveIndex(tView: TView, directiveIndex: number) { function saveContentQueryAndDirectiveIndex(tView: TView, directiveIndex: number) {
const tViewContentQueries = tView.contentQueries || (tView.contentQueries = []); const tViewContentQueries = tView.contentQueries || (tView.contentQueries = []);
const lastSavedDirectiveIndex = const lastSavedDirectiveIndex =
tView.contentQueries.length ? tViewContentQueries[tViewContentQueries.length - 1] : -1; tViewContentQueries.length ? tViewContentQueries[tViewContentQueries.length - 1] : -1;
if (directiveIndex !== lastSavedDirectiveIndex) { if (directiveIndex !== lastSavedDirectiveIndex) {
tViewContentQueries.push(tView.queries!.length - 1, directiveIndex); tViewContentQueries.push(tView.queries!.length - 1, directiveIndex);
} }

View File

@ -69,10 +69,10 @@
"name": "addComponentLogic" "name": "addComponentLogic"
}, },
{ {
"name": "addHostBindingsToExpandoInstructions" "name": "addToViewTree"
}, },
{ {
"name": "addToViewTree" "name": "allocExpando"
}, },
{ {
"name": "allocLFrame" "name": "allocLFrame"
@ -86,9 +86,6 @@
{ {
"name": "autoRegisterModuleById" "name": "autoRegisterModuleById"
}, },
{
"name": "baseResolveDirective"
},
{ {
"name": "callHook" "name": "callHook"
}, },
@ -104,6 +101,9 @@
{ {
"name": "concatStringsWithSpace" "name": "concatStringsWithSpace"
}, },
{
"name": "configureViewWithDirective"
},
{ {
"name": "createLFrame" "name": "createLFrame"
}, },
@ -146,9 +146,6 @@
{ {
"name": "findAttrIndexInNode" "name": "findAttrIndexInNode"
}, },
{
"name": "generateExpandoInstructionBlock"
},
{ {
"name": "generateInitialInputs" "name": "generateInitialInputs"
}, },
@ -206,9 +203,6 @@
{ {
"name": "getTView" "name": "getTView"
}, },
{
"name": "growHostVarsSpace"
},
{ {
"name": "hasTagAndTypeMatch" "name": "hasTagAndTypeMatch"
}, },
@ -284,9 +278,6 @@
{ {
"name": "nativeAppendOrInsertBefore" "name": "nativeAppendOrInsertBefore"
}, },
{
"name": "nativeInsertBefore"
},
{ {
"name": "nextNgElementId" "name": "nextNgElementId"
}, },
@ -305,6 +296,9 @@
{ {
"name": "refreshView" "name": "refreshView"
}, },
{
"name": "registerHostBindingOpCodes"
},
{ {
"name": "rememberChangeHistoryAndInvokeOnChangesHook" "name": "rememberChangeHistoryAndInvokeOnChangesHook"
}, },
@ -350,9 +344,6 @@
{ {
"name": "setUpAttributes" "name": "setUpAttributes"
}, },
{
"name": "unwrapRNode"
},
{ {
"name": "updateTransplantedViewCount" "name": "updateTransplantedViewCount"
}, },

View File

@ -698,15 +698,15 @@
{ {
"name": "addComponentLogic" "name": "addComponentLogic"
}, },
{
"name": "addHostBindingsToExpandoInstructions"
},
{ {
"name": "addToArray" "name": "addToArray"
}, },
{ {
"name": "addToViewTree" "name": "addToViewTree"
}, },
{
"name": "allocExpando"
},
{ {
"name": "allocLFrame" "name": "allocLFrame"
}, },
@ -734,9 +734,6 @@
{ {
"name": "baseElement" "name": "baseElement"
}, },
{
"name": "baseResolveDirective"
},
{ {
"name": "bindingUpdated" "name": "bindingUpdated"
}, },
@ -785,6 +782,9 @@
{ {
"name": "config" "name": "config"
}, },
{
"name": "configureViewWithDirective"
},
{ {
"name": "connectableObservableDescriptor" "name": "connectableObservableDescriptor"
}, },
@ -941,9 +941,6 @@
{ {
"name": "fromArray" "name": "fromArray"
}, },
{
"name": "generateExpandoInstructionBlock"
},
{ {
"name": "generateInitialInputs" "name": "generateInitialInputs"
}, },
@ -1082,9 +1079,6 @@
{ {
"name": "getTView" "name": "getTView"
}, },
{
"name": "growHostVarsSpace"
},
{ {
"name": "handleError" "name": "handleError"
}, },
@ -1421,6 +1415,9 @@
{ {
"name": "registerDestroyHooksIfSupported" "name": "registerDestroyHooksIfSupported"
}, },
{
"name": "registerHostBindingOpCodes"
},
{ {
"name": "registerPostOrderHooks" "name": "registerPostOrderHooks"
}, },

View File

@ -56,6 +56,9 @@
{ {
"name": "_renderCompCount" "name": "_renderCompCount"
}, },
{
"name": "allocExpando"
},
{ {
"name": "allocLFrame" "name": "allocLFrame"
}, },
@ -176,9 +179,6 @@
{ {
"name": "nativeAppendOrInsertBefore" "name": "nativeAppendOrInsertBefore"
}, },
{
"name": "nativeInsertBefore"
},
{ {
"name": "nextNgElementId" "name": "nextNgElementId"
}, },
@ -194,6 +194,9 @@
{ {
"name": "refreshView" "name": "refreshView"
}, },
{
"name": "registerHostBindingOpCodes"
},
{ {
"name": "rememberChangeHistoryAndInvokeOnChangesHook" "name": "rememberChangeHistoryAndInvokeOnChangesHook"
}, },
@ -224,16 +227,10 @@
{ {
"name": "setSelectedIndex" "name": "setSelectedIndex"
}, },
{
"name": "unwrapRNode"
},
{ {
"name": "updateTransplantedViewCount" "name": "updateTransplantedViewCount"
}, },
{ {
"name": "viewAttachedToChangeDetector" "name": "viewAttachedToChangeDetector"
},
{
"name": "ɵɵtext"
} }
] ]

View File

@ -920,9 +920,6 @@
{ {
"name": "addComponentLogic" "name": "addComponentLogic"
}, },
{
"name": "addHostBindingsToExpandoInstructions"
},
{ {
"name": "addToArray" "name": "addToArray"
}, },
@ -935,6 +932,9 @@
{ {
"name": "advanceActivatedRouteNodeAndItsChildren" "name": "advanceActivatedRouteNodeAndItsChildren"
}, },
{
"name": "allocExpando"
},
{ {
"name": "allocLFrame" "name": "allocLFrame"
}, },
@ -965,9 +965,6 @@
{ {
"name": "baseElement" "name": "baseElement"
}, },
{
"name": "baseResolveDirective"
},
{ {
"name": "bindingUpdated" "name": "bindingUpdated"
}, },
@ -1016,6 +1013,9 @@
{ {
"name": "config" "name": "config"
}, },
{
"name": "configureViewWithDirective"
},
{ {
"name": "connectableObservableDescriptor" "name": "connectableObservableDescriptor"
}, },
@ -1244,9 +1244,6 @@
{ {
"name": "fromArray" "name": "fromArray"
}, },
{
"name": "generateExpandoInstructionBlock"
},
{ {
"name": "generateInitialInputs" "name": "generateInitialInputs"
}, },
@ -1427,9 +1424,6 @@
{ {
"name": "getToken" "name": "getToken"
}, },
{
"name": "growHostVarsSpace"
},
{ {
"name": "handleError" "name": "handleError"
}, },
@ -1760,6 +1754,9 @@
{ {
"name": "refreshView" "name": "refreshView"
}, },
{
"name": "registerHostBindingOpCodes"
},
{ {
"name": "registerPostOrderHooks" "name": "registerPostOrderHooks"
}, },

View File

@ -176,15 +176,15 @@
{ {
"name": "addComponentLogic" "name": "addComponentLogic"
}, },
{
"name": "addHostBindingsToExpandoInstructions"
},
{ {
"name": "addToArray" "name": "addToArray"
}, },
{ {
"name": "addToViewTree" "name": "addToViewTree"
}, },
{
"name": "allocExpando"
},
{ {
"name": "allocLFrame" "name": "allocLFrame"
}, },
@ -209,9 +209,6 @@
{ {
"name": "attachPatchData" "name": "attachPatchData"
}, },
{
"name": "baseResolveDirective"
},
{ {
"name": "bindingUpdated" "name": "bindingUpdated"
}, },
@ -242,6 +239,9 @@
{ {
"name": "concatStringsWithSpace" "name": "concatStringsWithSpace"
}, },
{
"name": "configureViewWithDirective"
},
{ {
"name": "createDirectivesInstances" "name": "createDirectivesInstances"
}, },
@ -323,9 +323,6 @@
{ {
"name": "forwardRef" "name": "forwardRef"
}, },
{
"name": "generateExpandoInstructionBlock"
},
{ {
"name": "generateInitialInputs" "name": "generateInitialInputs"
}, },
@ -431,9 +428,6 @@
{ {
"name": "getTView" "name": "getTView"
}, },
{
"name": "growHostVarsSpace"
},
{ {
"name": "handleError" "name": "handleError"
}, },
@ -605,6 +599,9 @@
{ {
"name": "refreshView" "name": "refreshView"
}, },
{
"name": "registerHostBindingOpCodes"
},
{ {
"name": "registerPostOrderHooks" "name": "registerPostOrderHooks"
}, },

View File

@ -119,7 +119,7 @@ const ShapeOfTView: ShapeOf<TView> = {
staticViewQueries: true, staticViewQueries: true,
staticContentQueries: true, staticContentQueries: true,
firstChild: true, firstChild: true,
expandoInstructions: true, hostBindingOpCodes: true,
directiveRegistry: true, directiveRegistry: true,
pipeRegistry: true, pipeRegistry: true,
preOrderHooks: true, preOrderHooks: true,