refactor(ivy): LView is a proper linked list (#28382)

- TView no longer stores childIndex
- LView now as CHILD_HEAD and CHILD_TAIL

TView used to store the head of the list, therefor all LViews had to have the same head, which is incorrect.

PR Close #28382
This commit is contained in:
Ben Lesh 2019-01-25 15:21:51 -08:00
parent ba6aa93aa3
commit 929fe029c2
7 changed files with 27 additions and 34 deletions

View File

@ -190,7 +190,7 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
component = createRootComponent( component = createRootComponent(
componentView, this.componentDef, rootLView, rootContext, [LifecycleHooksFeature]); componentView, this.componentDef, rootLView, rootContext, [LifecycleHooksFeature]);
addToViewTree(rootLView, HEADER_OFFSET, componentView); addToViewTree(rootLView, componentView);
refreshDescendantViews(rootLView); refreshDescendantViews(rootLView);
} finally { } finally {
leaveView(oldLView); leaveView(oldLView);

View File

@ -101,10 +101,8 @@ export function getViewComponent<T = {}>(element: Element | {}): T|null {
ngDevMode && assertLView(lView); ngDevMode && assertLView(lView);
while (lView[HOST] === null && (parent = getLViewParent(lView) !)) { while (lView[HOST] === null && (parent = getLViewParent(lView) !)) {
// As long as lView[HOST] is null we know we are part of sub-template such as `*ngIf` // As long as lView[HOST] is null we know we are part of sub-template such as `*ngIf`
if (parent) {
lView = parent; lView = parent;
} }
}
return lView[FLAGS] & LViewFlags.IsRoot ? null : lView[CONTEXT] as T; return lView[FLAGS] & LViewFlags.IsRoot ? null : lView[CONTEXT] as T;
} }

View File

@ -36,7 +36,7 @@ import {SanitizerFn} from './interfaces/sanitization';
import {StylingContext} from './interfaces/styling'; import {StylingContext} from './interfaces/styling';
import {BINDING_INDEX, CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_VIEW, ExpandoInstructions, FLAGS, HEADER_OFFSET, HOST, INJECTOR, InitPhaseState, LView, LViewFlags, NEXT, OpaqueViewState, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, TData, TVIEW, TView, T_HOST} from './interfaces/view'; import {BINDING_INDEX, CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_VIEW, ExpandoInstructions, FLAGS, HEADER_OFFSET, HOST, INJECTOR, InitPhaseState, LView, LViewFlags, NEXT, OpaqueViewState, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, TData, TVIEW, TView, T_HOST} from './interfaces/view';
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert'; import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
import {appendChild, appendProjectedNode, createTextNode, getLViewChild, insertView, removeView} from './node_manipulation'; import {appendChild, appendProjectedNode, createTextNode, insertView, removeView} from './node_manipulation';
import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher'; import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher';
import {decreaseElementDepthCount, enterView, getBindingsEnabled, getCheckNoChangesMode, getContextLView, getCurrentDirectiveDef, getElementDepthCount, getIsParent, getLView, getPreviousOrParentTNode, increaseElementDepthCount, isCreationMode, leaveView, nextContextImpl, resetComponentState, setBindingRoot, setCheckNoChangesMode, setCurrentDirectiveDef, setCurrentQueryIndex, setIsParent, setPreviousOrParentTNode,} from './state'; import {decreaseElementDepthCount, enterView, getBindingsEnabled, getCheckNoChangesMode, getContextLView, getCurrentDirectiveDef, getElementDepthCount, getIsParent, getLView, getPreviousOrParentTNode, increaseElementDepthCount, isCreationMode, leaveView, nextContextImpl, resetComponentState, setBindingRoot, setCheckNoChangesMode, setCurrentDirectiveDef, setCurrentQueryIndex, setIsParent, setPreviousOrParentTNode,} from './state';
import {getInitialClassNameValue, getInitialStyleStringValue, initializeStaticContext as initializeStaticStylingContext, patchContextWithStaticAttrs, renderInitialClasses, renderInitialStyles, renderStyling, updateClassProp as updateElementClassProp, updateContextWithBindings, updateStyleProp as updateElementStyleProp, updateStylingMap} from './styling/class_and_style_bindings'; import {getInitialClassNameValue, getInitialStyleStringValue, initializeStaticContext as initializeStaticStylingContext, patchContextWithStaticAttrs, renderInitialClasses, renderInitialStyles, renderStyling, updateClassProp as updateElementClassProp, updateContextWithBindings, updateStyleProp as updateElementStyleProp, updateStylingMap} from './styling/class_and_style_bindings';
@ -788,7 +788,6 @@ export function createTView(
viewQuery: viewQuery, viewQuery: viewQuery,
node: null !, node: null !,
data: blueprint.slice().fill(null, bindingStartIndex), data: blueprint.slice().fill(null, bindingStartIndex),
childIndex: -1, // Children set in addToViewTree(), if any
bindingStartIndex: bindingStartIndex, bindingStartIndex: bindingStartIndex,
viewQueryStartIndex: initialViewLength, viewQueryStartIndex: initialViewLength,
expandoStartIndex: initialViewLength, expandoStartIndex: initialViewLength,
@ -2373,7 +2372,7 @@ export function containerRefreshEnd(): void {
* by executing an associated template function. * by executing an associated template function.
*/ */
function refreshDynamicEmbeddedViews(lView: LView) { function refreshDynamicEmbeddedViews(lView: LView) {
for (let current = getLViewChild(lView); current !== null; current = current[NEXT]) { for (let current = lView[CHILD_HEAD]; current !== null; current = current[NEXT]) {
// Note: current can be an LView or an LContainer instance, but here we are only interested // Note: current can be an LView or an LContainer instance, but here we are only interested
// in LContainer. We can tell it's an LContainer because its length is less than the LView // in LContainer. We can tell it's an LContainer because its length is less than the LView
// header. // header.
@ -2704,13 +2703,14 @@ export function projection(nodeIndex: number, selectorIndex: number = 0, attrs?:
* *
* @param lView The view where LView or LContainer should be added * @param lView The view where LView or LContainer should be added
* @param adjustedHostIndex Index of the view's host node in LView[], adjusted for header * @param adjustedHostIndex Index of the view's host node in LView[], adjusted for header
* @param state The LView or LContainer to add to the view tree * @param lViewOrLContainer The LView or LContainer to add to the view tree
* @returns The state passed in * @returns The state passed in
*/ */
export function addToViewTree<T extends LView|LContainer>(lView: LView, lViewOrLContainer: T): T { export function addToViewTree<T extends LView|LContainer>(lView: LView, lViewOrLContainer: T): T {
// TODO(benlesh/misko): This implementation is incorrect, because it always adds the LContainer to // TODO(benlesh/misko): This implementation is incorrect, because it always adds the LContainer to
// the end of the queue, which means if the developer asks for the LContainers out of order, the // the end of the queue, which means if the developer retrieves the LContainers from RNodes out of
// change detection will run out of order. // order, the change detection will run out of order, as the act of retrieving the the LContainer
// from the RNode is what adds it to the queue.
if (lView[CHILD_HEAD]) { if (lView[CHILD_HEAD]) {
lView[CHILD_TAIL] ![NEXT] = lViewOrLContainer; lView[CHILD_TAIL] ![NEXT] = lViewOrLContainer;
} else { } else {

View File

@ -46,7 +46,7 @@ export const CHILD_TAIL = 15;
export const CONTENT_QUERIES = 16; export const CONTENT_QUERIES = 16;
export const DECLARATION_VIEW = 17; export const DECLARATION_VIEW = 17;
/** Size of LView's header. Necessary to adjust for it when setting slots. */ /** Size of LView's header. Necessary to adjust for it when setting slots. */
export const HEADER_OFFSET = 18; export const HEADER_OFFSET = 19;
// This interface replaces the real LView interface if it is an arg or a // This interface replaces the real LView interface if it is an arg or a
@ -79,12 +79,13 @@ export interface LView extends Array<any> {
[FLAGS]: LViewFlags; [FLAGS]: LViewFlags;
/** /**
* The parent view is needed when we exit the view and must restore the previous * This may store an {@link LView} or {@link LContainer}.
* `LView`. Without this, the render method would have to keep a stack of *
* `LView` - The parent view. This is needed when we exit the view and must restore the previous
* LView. Without this, the render method would have to keep a stack of
* views as it is recursively rendering templates. * views as it is recursively rendering templates.
* *
* This is the "insertion" view for embedded views. This allows us to properly * `LContainer` - The current view is part of a container, and is an embedded view.
* destroy embedded views.
*/ */
[PARENT]: LView|LContainer|null; [PARENT]: LView|LContainer|null;
@ -162,6 +163,15 @@ export interface LView extends Array<any> {
/** An optional custom sanitizer. */ /** An optional custom sanitizer. */
[SANITIZER]: Sanitizer|null; [SANITIZER]: Sanitizer|null;
/**
* Reference to the first LView or LContainer beneath this LView in
* the hierarchy.
*
* Necessary to store this so views can traverse through their nested views
* to remove listeners and call onDestroy callbacks.
*/
[CHILD_HEAD]: LView|LContainer|null;
/** /**
* The last LView or LContainer beneath this LView in the hierarchy. * The last LView or LContainer beneath this LView in the hierarchy.
* *
@ -393,18 +403,6 @@ export interface TView {
*/ */
viewQueryStartIndex: number; viewQueryStartIndex: number;
/**
* Index of the host node of the first LView or LContainer beneath this LView in
* the hierarchy.
*
* Necessary to store this so views can traverse through their nested views
* to remove listeners and call onDestroy callbacks.
*
* For embedded views, we store the index of an LContainer's host rather than the first
* LView to avoid managing splicing when views are added/removed.
*/
childIndex: number;
/** /**
* A reference to the first child node located in the view. * A reference to the first child node located in the view.
*/ */

View File

@ -369,12 +369,6 @@ export function removeView(lContainer: LContainer, removeIndex: number) {
destroyLView(view); destroyLView(view);
} }
/** Gets the child of the given LView */
export function getLViewChild(lView: LView): LView|LContainer|null {
const childIndex = lView[TVIEW].childIndex;
return childIndex === -1 ? null : lView[childIndex];
}
/** /**
* A standalone function which destroys an LView, * A standalone function which destroys an LView,
* conducting cleanup (e.g. removing listeners, calling onDestroys). * conducting cleanup (e.g. removing listeners, calling onDestroys).

View File

@ -314,7 +314,7 @@ export function createContainerRef(
hostView[hostTNode.index] = lContainer = hostView[hostTNode.index] = lContainer =
createLContainer(slotValue, hostView, commentNode, true); createLContainer(slotValue, hostView, commentNode, true);
addToViewTree(hostView, hostTNode.index as number, lContainer); addToViewTree(hostView, lContainer);
} }
return new R3ViewContainerRef(lContainer, hostTNode, hostView); return new R3ViewContainerRef(lContainer, hostTNode, hostView);

View File

@ -59,6 +59,9 @@
{ {
"name": "INJECTOR_BLOOM_PARENT_SIZE" "name": "INJECTOR_BLOOM_PARENT_SIZE"
}, },
{
"name": "LCONTAINER_LENGTH"
},
{ {
"name": "MONKEY_PATCH_KEY_NAME" "name": "MONKEY_PATCH_KEY_NAME"
}, },