perf(ivy): match query results on the TView level (#31489)

PR Close #31489
This commit is contained in:
Pawel Kozlowski
2019-06-07 10:55:48 +02:00
committed by Kara Erickson
parent 9c954ebc62
commit d52ae7cbab
20 changed files with 1001 additions and 1036 deletions

View File

@ -9,9 +9,9 @@
import {ViewRef} from '../../linker/view_ref';
import {TNode} from './node';
import {LQueries} from './query';
import {RComment, RElement} from './renderer';
import {HOST, LView, NEXT, PARENT, QUERIES, T_HOST} from './view';
import {HOST, LView, NEXT, PARENT, T_HOST} from './view';
/**
@ -26,8 +26,15 @@ export const TYPE = 1;
* Uglify will inline these when minifying so there shouldn't be a cost.
*/
export const ACTIVE_INDEX = 2;
// PARENT, NEXT, QUERIES and T_HOST are indices 3, 4, 5 and 6.
// PARENT and NEXT are indices 3 and 4
// As we already have these constants in LView, we don't need to re-create them.
export const MOVED_VIEWS = 5;
// T_HOST is index 6
// We already have this constants in LView, we don't need to re-create it.
export const NATIVE = 7;
export const VIEW_REFS = 8;
@ -84,11 +91,11 @@ export interface LContainer extends Array<any> {
[NEXT]: LView|LContainer|null;
/**
* Queries active for this container - all the views inserted to / removed from
* this container are reported to queries referenced here.
* A collection of views created based on the underlying `<ng-template>` element but inserted into
* a different `LContainer`. We need to track views created from a given declaration point since
* queries collect matches from the embedded view declaration point and _not_ the insertion point.
*/
[QUERIES]: LQueries|null; // TODO(misko): This is abuse of `LContainer` since we are storing
// `[QUERIES]` in it which are not needed for `LContainer` (only needed for Template)
[MOVED_VIEWS]: LView[]|null;
/**
* Pointer to the `TNode` which represents the host of the container.

View File

@ -9,99 +9,211 @@
import {Type} from '../../interface/type';
import {QueryList} from '../../linker';
import {TContainerNode, TElementContainerNode, TElementNode, TNode} from './node';
import {TNode} from './node';
import {TView} from './view';
/**
* An object representing query metadata extracted from query annotations.
*/
export interface TQueryMetadata {
predicate: Type<any>|string[];
descendants: boolean;
read: any;
isStatic: boolean;
}
/** Used for tracking queries (e.g. ViewChild, ContentChild). */
/**
* TQuery objects represent all the query-related data that remain the same from one view instance
* to another and can be determined on the very first template pass. Most notably TQuery holds all
* the matches for a given view.
*/
export interface TQuery {
/**
* Query metadata extracted from query annotations.
*/
metadata: TQueryMetadata;
/**
* Index of a query in a declaration view in case of queries propagated to en embedded view, -1
* for queries declared in a given view. We are storing this index so we can find a parent query
* to clone for an embedded view (when an embedded view is created).
*/
indexInDeclarationView: number;
/**
* Matches collected on the first template pass. Each match is a pair of:
* - TNode index;
* - match index;
*
* A TNode index can be either:
* - a positive number (the most common case) to indicate a matching TNode;
* - a negative number to indicate that a given query is crossing a <ng-template> element and
* results from views created based on TemplateRef should be inserted at this place.
*
* A match index is a number used to find an actual value (for a given node) when query results
* are materialized. This index can have one of the following values:
* - -2 - indicates that we need to read a special token (TemplateRef, ViewContainerRef etc.);
* - -1 - indicates that we need to read a default value based on the node type (TemplateRef for
* ng-template and ElementRef for other elements);
* - a positive number - index of an injectable to be read from the element injector.
*/
matches: number[]|null;
/**
* A flag indicating if a given query crosses an <ng-template> element. This flag exists for
* performance reasons: we can notice that queries not crossing any <ng-template> elements will
* have matches from a given view only (and adapt processing accordingly).
*/
crossesNgTemplate: boolean;
/**
* A method call when a given query is crossing an element (or element container). This is where a
* given TNode is matched against a query predicate.
* @param tView
* @param tNode
*/
elementStart(tView: TView, tNode: TNode): void;
/**
* A method called when processing the elementEnd instruction - this is mostly useful to determine
* if a given content query should match any nodes past this point.
* @param tNode
*/
elementEnd(tNode: TNode): void;
/**
* A method called when processing the template instruction. This is where a
* given TContainerNode is matched against a query predicate.
* @param tView
* @param tNode
*/
template(tView: TView, tNode: TNode): void;
/**
* A query-related method called when an embedded TView is created based on the content of a
* <ng-template> element. We call this method to determine if a given query should be propagated
* to the embedded view and if so - return a cloned TQuery for this embedded view.
* @param tNode
* @param childQueryIndex
*/
embeddedTView(tNode: TNode, childQueryIndex: number): TQuery|null;
}
/**
* TQueries represent a collection of individual TQuery objects tracked in a given view. Most of the
* methods on this interface are simple proxy methods to the corresponding functionality on TQuery.
*/
export interface TQueries {
/**
* Adds a new TQuery to a collection of queries tracked in a given view.
* @param tQuery
*/
track(tQuery: TQuery): void;
/**
* Returns a TQuery instance for at the given index in the queries array.
* @param index
*/
getByIndex(index: number): TQuery;
/**
* Returns the number of queries tracked in a given view.
*/
length: number;
/**
* A proxy method that iterates over all the TQueries in a given TView and calls the corresponding
* `elementStart` on each and every TQuery.
* @param tView
* @param tNode
*/
elementStart(tView: TView, tNode: TNode): void;
/**
* A proxy method that iterates over all the TQueries in a given TView and calls the corresponding
* `elementEnd` on each and every TQuery.
* @param tNode
*/
elementEnd(tNode: TNode): void;
/**
* A proxy method that iterates over all the TQueries in a given TView and calls the corresponding
* `template` on each and every TQuery.
* @param tView
* @param tNode
*/
template(tView: TView, tNode: TNode): void;
/**
* A proxy method that iterates over all the TQueries in a given TView and calls the corresponding
* `embeddedTView` on each and every TQuery.
* @param tNode
*/
embeddedTView(tNode: TNode): TQueries|null;
}
/**
* An interface that represents query-related information specific to a view instance. Most notably
* it contains:
* - materialized query matches;
* - a pointer to a QueryList where materialized query results should be reported.
*/
export interface LQuery<T> {
/**
* Materialized query matches for a given view only (!). Results are initialized lazily so the
* array of matches is set to `null` initially.
*/
matches: (T|null)[]|null;
/**
* A QueryList where materialized query results should be reported.
*/
queryList: QueryList<T>;
/**
* Clones an LQuery for an embedded view. A cloned query shares the same `QueryList` but has a
* separate collection of materialized matches.
*/
clone(): LQuery<T>;
/**
* Called when an embedded view, impacting results of this query, is inserted or removed.
*/
setDirty(): void;
}
/**
* lQueries represent a collection of individual LQuery objects tracked in a given view.
*/
export interface LQueries {
/**
* The parent LQueries instance.
*
* When there is a content query, a new LQueries instance is created to avoid mutating any
* existing LQueries. After we are done searching content children, the parent property allows
* us to traverse back up to the original LQueries instance to continue to search for matches
* in the main view.
* A collection of queries tracked in a given view.
*/
parent: LQueries|null;
queries: LQuery<any>[];
/**
* The index of the node on which this LQueries instance was created / cloned in a given LView.
*
* This index is stored to minimize LQueries cloning: we can observe that LQueries can be mutated
* only under 2 conditions:
* - we are crossing an element that has directives with content queries (new queries are added);
* - we are descending into element hierarchy (creating a child element of an existing element)
* and the current LQueries object is tracking shallow queries (shallow queries are removed).
*
* Since LQueries are not cloned systematically we need to know exactly where (on each element)
* cloning occurred, so we can properly restore the set of tracked queries when going up the
* elements hierarchy.
*
* Always set to -1 for view queries as view queries are created before we process any node in a
* given view.
* A method called when a new embedded view is created. As a result a set of LQueries applicable
* for a new embedded view is instantiated (cloned) from the declaration view.
* @param tView
*/
nodeIndex: number;
createEmbeddedView(tView: TView): LQueries|null;
/**
* Ask queries to prepare a copy of itself. This ensures that:
* - tracking new queries on content nodes doesn't mutate list of queries tracked on a parent
* node;
* - we don't track shallow queries when descending into elements hierarchy.
*
* We will clone LQueries before constructing content queries
* A method called when an embedded view is inserted into a container. As a result all impacted
* `LQuery` objects (and associated `QueryList`) are marked as dirty.
* @param tView
*/
clone(tNode: TNode): LQueries;
insertView(tView: TView): void;
/**
* Notify `LQueries` that a new `TNode` has been created and needs to be added to query results
* if matching query predicate.
* A method called when an embedded view is detached from a container. As a result all impacted
* `LQuery` objects (and associated `QueryList`) are marked as dirty.
* @param tView
*/
addNode(tNode: TElementNode|TContainerNode|TElementContainerNode): void;
/**
* Notify `LQueries` that a new `TNode` has been created and needs to be added to query results
* if matching query predicate. This is a special mode invoked if the query container has to
* be created out of order (e.g. view created in the constructor of a directive).
*/
insertNodeBeforeViews(tNode: TElementNode|TContainerNode|TElementContainerNode): void;
/**
* Notify `LQueries` that a new LContainer was added to ivy data structures. As a result we need
* to prepare room for views that might be inserted into this container.
*/
container(): LQueries|null;
/**
* Notify `LQueries` that a new `LView` has been created. As a result we need to prepare room
* and collect nodes that match query predicate.
*/
createView(): LQueries|null;
/**
* Notify `LQueries` that a new `LView` has been added to `LContainer`. As a result all
* the matching nodes from this view should be added to container's queries.
*/
insertView(newViewIndex: number): void;
/**
* Notify `LQueries` that an `LView` has been removed from `LContainer`. As a result all
* the matching nodes from this view should be removed from container's queries.
*/
removeView(): void;
/**
* Add additional `QueryList` to track.
*
* @param queryList `QueryList` to update with changes.
* @param predicate Either `Type` or selector array of [key, value] predicates.
* @param descend If true the query will recursively apply to the children.
* @param read Indicates which token should be read from DI for this query.
*/
track<T>(
queryList: QueryList<T>, predicate: Type<any>|string[], descend?: boolean,
read?: Type<T>): void;
detachView(tView: TView): void;
}
// Note: This hack is necessary so we don't erroneously get a circular dependency
// failure based on types.
export const unusedValueExportToPlacateAjd = 1;

View File

@ -9,7 +9,6 @@
import {InjectionToken} from '../../di/injection_token';
import {Injector} from '../../di/injector';
import {Type} from '../../interface/type';
import {QueryList} from '../../linker';
import {SchemaMetadata} from '../../metadata';
import {Sanitizer} from '../../sanitization/security';
@ -18,7 +17,7 @@ import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefList, HostBin
import {I18nUpdateOpCodes, TI18n} from './i18n';
import {TElementNode, TNode, TViewNode} from './node';
import {PlayerHandler} from './player';
import {LQueries} from './query';
import {LQueries, TQueries} from './query';
import {RElement, Renderer3, RendererFactory3} from './renderer';
@ -42,11 +41,11 @@ export const RENDERER = 12;
export const SANITIZER = 13;
export const CHILD_HEAD = 14;
export const CHILD_TAIL = 15;
export const CONTENT_QUERIES = 16;
export const DECLARATION_VIEW = 17;
export const DECLARATION_VIEW = 16;
export const DECLARATION_LCONTAINER = 17;
export const PREORDER_HOOK_FLAGS = 18;
/** Size of LView's header. Necessary to adjust for it when setting slots. */
export const HEADER_OFFSET = 20;
export const HEADER_OFFSET = 19;
// This interface replaces the real LView interface if it is an arg or a
@ -179,13 +178,6 @@ export interface LView extends Array<any> {
*/
[CHILD_TAIL]: LView|LContainer|null;
/**
* Stores QueryLists associated with content queries of a directive. This data structure is
* filled-in as part of a directive creation process and is later used to retrieve a QueryList to
* be refreshed.
*/
[CONTENT_QUERIES]: QueryList<any>[]|null;
/**
* View where this view's template was declared.
*
@ -212,6 +204,16 @@ export interface LView extends Array<any> {
*/
[DECLARATION_VIEW]: LView|null;
/**
* A declaration point of embedded views (ones instantiated based on the content of a
* <ng-template>), null for other types of views.
*
* We need to track all embedded views created from a given declaration point so we can prepare
* query matches in a proper order (query matches are ordered based on their declaration point and
* _not_ the insertion point).
*/
[DECLARATION_LCONTAINER]: LContainer|null;
/**
* More flags for this view. See PreOrderHookFlags for more info.
*/
@ -410,17 +412,6 @@ export interface TView {
*/
staticContentQueries: boolean;
/**
* The index where the viewQueries section of `LView` begins. This section contains
* view queries defined for a component/directive.
*
* We store this start index so we know where the list of view queries starts.
* This is required when we invoke view queries at runtime. We invoke queries one by one and
* increment query index after each iteration. This information helps us to reset index back to
* the beginning of view query list before we invoke view queries again.
*/
viewQueryStartIndex: number;
/**
* A reference to the first child node located in the view.
*/
@ -550,7 +541,19 @@ export interface TView {
components: number[]|null;
/**
* A list of indices for child directives that have content queries.
* A collection of queries tracked in a given view.
*/
queries: TQueries|null;
/**
* An array of indices pointing to directives with content queries alongside with the
* corresponding
* query index. Each entry in this array is a tuple of:
* - index of the first content query index declared by a given directive;
* - index of a directive.
*
* We are storing those indexes so we can refresh content queries as part of a view refresh
* process.
*/
contentQueries: number[]|null;