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(
componentView, this.componentDef, rootLView, rootContext, [LifecycleHooksFeature]);
addToViewTree(rootLView, HEADER_OFFSET, componentView);
addToViewTree(rootLView, componentView);
refreshDescendantViews(rootLView);
} finally {
leaveView(oldLView);

View File

@ -101,10 +101,8 @@ export function getViewComponent<T = {}>(element: Element | {}): T|null {
ngDevMode && assertLView(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`
if (parent) {
lView = parent;
}
}
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 {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 {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 {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';
@ -788,7 +788,6 @@ export function createTView(
viewQuery: viewQuery,
node: null !,
data: blueprint.slice().fill(null, bindingStartIndex),
childIndex: -1, // Children set in addToViewTree(), if any
bindingStartIndex: bindingStartIndex,
viewQueryStartIndex: initialViewLength,
expandoStartIndex: initialViewLength,
@ -2373,7 +2372,7 @@ export function containerRefreshEnd(): void {
* by executing an associated template function.
*/
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
// in LContainer. We can tell it's an LContainer because its length is less than the LView
// 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 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
*/
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
// the end of the queue, which means if the developer asks for the LContainers out of order, the
// change detection will run out of order.
// the end of the queue, which means if the developer retrieves the LContainers from RNodes out of
// 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]) {
lView[CHILD_TAIL] ![NEXT] = lViewOrLContainer;
} else {

View File

@ -46,7 +46,7 @@ export const CHILD_TAIL = 15;
export const CONTENT_QUERIES = 16;
export const DECLARATION_VIEW = 17;
/** 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
@ -79,12 +79,13 @@ export interface LView extends Array<any> {
[FLAGS]: LViewFlags;
/**
* The parent view 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
* This may store an {@link LView} or {@link LContainer}.
*
* `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.
*
* This is the "insertion" view for embedded views. This allows us to properly
* destroy embedded views.
* `LContainer` - The current view is part of a container, and is an embedded view.
*/
[PARENT]: LView|LContainer|null;
@ -162,6 +163,15 @@ export interface LView extends Array<any> {
/** An optional custom sanitizer. */
[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.
*
@ -393,18 +403,6 @@ export interface TView {
*/
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.
*/

View File

@ -369,12 +369,6 @@ export function removeView(lContainer: LContainer, removeIndex: number) {
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,
* conducting cleanup (e.g. removing listeners, calling onDestroys).

View File

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

View File

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