From 730679964f964b05cfbbe71a413c9f1eadbfea0c Mon Sep 17 00:00:00 2001 From: Kara Erickson Date: Tue, 2 Oct 2018 21:12:26 -0700 Subject: [PATCH] refactor(ivy): flatten LInjector into LViewData (#26220) PR Close #26220 --- packages/core/src/render3/debug.ts | 5 +- packages/core/src/render3/di.ts | 398 ++++++++++-------- packages/core/src/render3/instructions.ts | 5 +- .../core/src/render3/interfaces/injector.ts | 146 ++++--- packages/core/src/render3/interfaces/node.ts | 2 - packages/core/src/render3/interfaces/view.ts | 12 +- packages/core/src/render3/query.ts | 3 +- .../src/render3/view_engine_compatibility.ts | 21 +- .../bundle.golden_symbols.json | 28 +- .../hello_world/bundle.golden_symbols.json | 19 +- .../bundling/todo/bundle.golden_symbols.json | 28 +- .../todo_r2/bundle.golden_symbols.json | 30 +- packages/core/test/render3/di_spec.ts | 113 ++--- 13 files changed, 457 insertions(+), 353 deletions(-) diff --git a/packages/core/src/render3/debug.ts b/packages/core/src/render3/debug.ts index 5246ab027c..a3d1240f84 100644 --- a/packages/core/src/render3/debug.ts +++ b/packages/core/src/render3/debug.ts @@ -47,10 +47,7 @@ class Render3DebugContext implements DebugContext { get injector(): Injector { if (this.nodeIndex !== null) { const tNode = this.view[TVIEW].data[this.nodeIndex]; - const nodeInjector = di.getInjector(tNode, this.view); - if (nodeInjector) { - return new di.NodeInjector(nodeInjector); - } + return new di.NodeInjector(tNode, this.view); } return Injector.NULL; } diff --git a/packages/core/src/render3/di.ts b/packages/core/src/render3/di.ts index 4c7dae2094..40a92b489a 100644 --- a/packages/core/src/render3/di.ts +++ b/packages/core/src/render3/di.ts @@ -20,10 +20,10 @@ import {getComponentDef, getDirectiveDef, getPipeDef} from './definition'; import {NG_ELEMENT_ID} from './fields'; import {_getViewData, assertPreviousIsParent, getPreviousOrParentTNode, resolveDirective, setEnvironment} from './instructions'; import {DirectiveDefInternal} from './interfaces/definition'; -import {LInjector} from './interfaces/injector'; +import {INJECTOR_SIZE, InjectorLocationFlags, PARENT_INJECTOR, TNODE,} from './interfaces/injector'; import {AttributeMarker, TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeFlags, TNodeType} from './interfaces/node'; import {isProceduralRenderer} from './interfaces/renderer'; -import {DECLARATION_VIEW, DIRECTIVES, HOST_NODE, INJECTOR, LViewData, RENDERER, TVIEW, TView} from './interfaces/view'; +import {DECLARATION_VIEW, DIRECTIVES, HOST_NODE, INJECTOR, LViewData, PARENT, RENDERER, TData, TVIEW, TView} from './interfaces/view'; import {assertNodeOfPossibleTypes} from './node_assert'; /** @@ -41,43 +41,47 @@ let nextNgElementId = 0; * Registers this directive as present in its node's injector by flipping the directive's * corresponding bit in the injector's bloom filter. * - * @param injector The node injector in which the directive should be registered - * @param type The directive to register + * @param injectorIndex The index of the node injector where this token should be registered + * @param tView The TView for the injector's bloom filters + * @param type The directive token to register */ -export function bloomAdd(injector: LInjector, type: Type): void { - let id: number|undefined = (type as any)[NG_ELEMENT_ID]; +export function bloomAdd(injectorIndex: number, tView: TView, type: Type): void { + if (tView.firstTemplatePass) { + let id: number|undefined = (type as any)[NG_ELEMENT_ID]; - // Set a unique ID on the directive type, so if something tries to inject the directive, - // we can easily retrieve the ID and hash it into the bloom bit that should be checked. - if (id == null) { - id = (type as any)[NG_ELEMENT_ID] = nextNgElementId++; - } + // Set a unique ID on the directive type, so if something tries to inject the directive, + // we can easily retrieve the ID and hash it into the bloom bit that should be checked. + if (id == null) { + id = (type as any)[NG_ELEMENT_ID] = nextNgElementId++; + } - // We only have BLOOM_SIZE (256) slots in our bloom filter (8 buckets * 32 bits each), - // so all unique IDs must be modulo-ed into a number from 0 - 255 to fit into the filter. - const bloomBit = id & BLOOM_MASK; + // We only have BLOOM_SIZE (256) slots in our bloom filter (8 buckets * 32 bits each), + // so all unique IDs must be modulo-ed into a number from 0 - 255 to fit into the filter. + const bloomBit = id & BLOOM_MASK; - // Create a mask that targets the specific bit associated with the directive. - // JS bit operations are 32 bits, so this will be a number between 2^0 and 2^31, corresponding - // to bit positions 0 - 31 in a 32 bit integer. - const mask = 1 << bloomBit; + // Create a mask that targets the specific bit associated with the directive. + // JS bit operations are 32 bits, so this will be a number between 2^0 and 2^31, corresponding + // to bit positions 0 - 31 in a 32 bit integer. + const mask = 1 << bloomBit; - // Use the raw bloomBit number to determine which bloom filter bucket we should check - // e.g: bf0 = [0 - 31], bf1 = [32 - 63], bf2 = [64 - 95], bf3 = [96 - 127], etc - const b7 = bloomBit & 0x80; - const b6 = bloomBit & 0x40; - const b5 = bloomBit & 0x20; + // Use the raw bloomBit number to determine which bloom filter bucket we should check + // e.g: bf0 = [0 - 31], bf1 = [32 - 63], bf2 = [64 - 95], bf3 = [96 - 127], etc + const b7 = bloomBit & 0x80; + const b6 = bloomBit & 0x40; + const b5 = bloomBit & 0x20; + const tData = tView.data as number[]; - if (b7) { - b6 ? (b5 ? (injector.bf7 |= mask) : (injector.bf6 |= mask)) : - (b5 ? (injector.bf5 |= mask) : (injector.bf4 |= mask)); - } else { - b6 ? (b5 ? (injector.bf3 |= mask) : (injector.bf2 |= mask)) : - (b5 ? (injector.bf1 |= mask) : (injector.bf0 |= mask)); + if (b7) { + b6 ? (b5 ? (tData[injectorIndex + 7] |= mask) : (tData[injectorIndex + 6] |= mask)) : + (b5 ? (tData[injectorIndex + 5] |= mask) : (tData[injectorIndex + 4] |= mask)); + } else { + b6 ? (b5 ? (tData[injectorIndex + 3] |= mask) : (tData[injectorIndex + 2] |= mask)) : + (b5 ? (tData[injectorIndex + 1] |= mask) : (tData[injectorIndex] |= mask)); + } } } -export function getOrCreateNodeInjector(): LInjector { +export function getOrCreateNodeInjector(): number { ngDevMode && assertPreviousIsParent(); return getOrCreateNodeInjectorForNode( getPreviousOrParentTNode() as TElementNode | TElementContainerNode | TContainerNode, @@ -92,67 +96,97 @@ export function getOrCreateNodeInjector(): LInjector { * @returns Node injector */ export function getOrCreateNodeInjectorForNode( - tNode: TElementNode | TContainerNode | TElementContainerNode, hostView: LViewData): LInjector { - const injector = getInjector(tNode, hostView); - if (injector) return injector; + tNode: TElementNode | TContainerNode | TElementContainerNode, hostView: LViewData): number { + const existingInjectorIndex = getInjectorIndex(tNode, hostView); + if (existingInjectorIndex !== -1) { + return existingInjectorIndex; + } const tView = hostView[TVIEW]; if (tView.firstTemplatePass) { // TODO(kara): Store node injector with host bindings for that node (see VIEW_DATA.md) tNode.injectorIndex = hostView.length; - tView.blueprint.push(null); - tView.hostBindingStartIndex++; + tView.blueprint.push(0, 0, 0, 0, 0, 0, 0, 0, null); // foundation for cumulative bloom + tView.data.push(0, 0, 0, 0, 0, 0, 0, 0, tNode); // foundation for node bloom + tView.hostBindingStartIndex += INJECTOR_SIZE; } - const parentInjector = getParentInjector(tNode, hostView); - return hostView[tNode.injectorIndex] = { - parent: parentInjector, - tNode: tNode, - view: hostView, - bf0: 0, - bf1: 0, - bf2: 0, - bf3: 0, - bf4: 0, - bf5: 0, - bf6: 0, - bf7: 0, - cbf0: parentInjector == null ? 0 : parentInjector.cbf0 | parentInjector.bf0, - cbf1: parentInjector == null ? 0 : parentInjector.cbf1 | parentInjector.bf1, - cbf2: parentInjector == null ? 0 : parentInjector.cbf2 | parentInjector.bf2, - cbf3: parentInjector == null ? 0 : parentInjector.cbf3 | parentInjector.bf3, - cbf4: parentInjector == null ? 0 : parentInjector.cbf4 | parentInjector.bf4, - cbf5: parentInjector == null ? 0 : parentInjector.cbf5 | parentInjector.bf5, - cbf6: parentInjector == null ? 0 : parentInjector.cbf6 | parentInjector.bf6, - cbf7: parentInjector == null ? 0 : parentInjector.cbf7 | parentInjector.bf7, - }; + const parentLoc = getParentInjectorLocation(tNode, hostView); + const parentIndex = parentLoc & InjectorLocationFlags.InjectorIndexMask; + const parentView: LViewData = getParentInjectorView(parentLoc, hostView); + + const parentData = parentView[TVIEW].data as any; + const injectorIndex = tNode.injectorIndex; + + for (let i = 0; i < PARENT_INJECTOR; i++) { + const bloomIndex = parentIndex + i; + hostView[injectorIndex + i] = + parentLoc === -1 ? 0 : parentView[bloomIndex] | parentData[bloomIndex]; + } + + hostView[injectorIndex + PARENT_INJECTOR] = parentLoc; + return injectorIndex; } -export function getInjector(tNode: TNode, view: LViewData): LInjector|null { - // If the injector index is the same as its parent's injector index, then the index has been - // copied down from the parent node. No injector has been created yet on this node. +export function getInjectorIndex(tNode: TNode, hostView: LViewData): number { if (tNode.injectorIndex === -1 || - tNode.parent && tNode.parent.injectorIndex === tNode.injectorIndex) { - return null; + // If the injector index is the same as its parent's injector index, then the index has been + // copied down from the parent node. No injector has been created yet on this node. + (tNode.parent && tNode.parent.injectorIndex === tNode.injectorIndex) || + // After the first template pass, the injector index might exist but the parent values + // might not have been calculated yet for this instance + hostView[tNode.injectorIndex + PARENT_INJECTOR] == null) { + return -1; } else { - return view[tNode.injectorIndex]; + return tNode.injectorIndex; } } -export function getParentInjector(tNode: TNode, view: LViewData): LInjector { +/** + * Finds the index of the parent injector, with a view offset if applicable. Used to set the + * parent injector initially. + */ +export function getParentInjectorLocation(tNode: TNode, view: LViewData): number { if (tNode.parent && tNode.parent.injectorIndex !== -1) { - return view[tNode.parent.injectorIndex]; + return tNode.parent.injectorIndex; // view offset is 0 } // For most cases, the parent injector index can be found on the host node (e.g. for component // or container), so this loop will be skipped, but we must keep the loop here to support - // the rarer case of deeply nested tags. + // the rarer case of deeply nested tags or inline views. let hostTNode = view[HOST_NODE]; + let viewOffset = 1; while (hostTNode && hostTNode.injectorIndex === -1) { view = view[DECLARATION_VIEW] !; hostTNode = view[HOST_NODE] !; + viewOffset++; } - return hostTNode ? view[DECLARATION_VIEW] ![hostTNode.injectorIndex] : null; + return hostTNode ? + hostTNode.injectorIndex | (viewOffset << InjectorLocationFlags.ViewOffsetShift) : + -1; +} + +/** + * Unwraps a parent injector location number to find the view offset from the current injector, + * then walks up the declaration view tree until the view is found that contains the parent + * injector. + * + * @param location The location of the parent injector, which contains the view offset + * @param startView The LViewData instance from which to start walking up the view tree + * @returns The LViewData instance that contains the parent injector + */ +export function getParentInjectorView(location: number, startView: LViewData): LViewData { + let viewOffset = location >> InjectorLocationFlags.ViewOffsetShift; + let parentView = startView; + // For most cases, the parent injector can be found on the host node (e.g. for component + // or container), but we must keep the loop here to support the rarer case of deeply nested + // tags or inline views, where the parent injector might live many views + // above the child injector. + while (viewOffset > 0) { + parentView = parentView[DECLARATION_VIEW] !; + viewOffset--; + } + return parentView; } /** @@ -161,8 +195,9 @@ export function getParentInjector(tNode: TNode, view: LViewData): LInjector { * @param di The node injector in which a directive will be added * @param def The definition of the directive to be made public */ -export function diPublicInInjector(di: LInjector, def: DirectiveDefInternal): void { - bloomAdd(di, def.type); +export function diPublicInInjector( + injectorIndex: number, view: LViewData, def: DirectiveDefInternal): void { + bloomAdd(injectorIndex, view[TVIEW], def.type); } /** @@ -171,7 +206,7 @@ export function diPublicInInjector(di: LInjector, def: DirectiveDefInternal * @param def The definition of the directive to be made public */ export function diPublic(def: DirectiveDefInternal): void { - diPublicInInjector(getOrCreateNodeInjector(), def); + diPublicInInjector(getOrCreateNodeInjector(), _getViewData(), def); } /** @@ -199,11 +234,11 @@ export function directiveInject(token: Type| InjectionToken): T; export function directiveInject(token: Type| InjectionToken, flags: InjectFlags): T; export function directiveInject( token: Type| InjectionToken, flags = InjectFlags.Default): T|null { - return getOrCreateInjectable(getOrCreateNodeInjector(), token, flags); + return getOrCreateInjectable(getOrCreateNodeInjector(), _getViewData(), token, flags); } export function injectRenderer2(): Renderer2 { - return getOrCreateRenderer2(getOrCreateNodeInjector()); + return getOrCreateRenderer2(_getViewData()); } /** * Inject static attribute value into directive constructor. @@ -254,8 +289,8 @@ export function injectAttribute(attrNameToInject: string): string|undefined { return undefined; } -function getOrCreateRenderer2(di: LInjector): Renderer2 { - const renderer = di.view[RENDERER]; +function getOrCreateRenderer2(view: LViewData): Renderer2 { + const renderer = view[RENDERER]; if (isProceduralRenderer(renderer)) { return renderer as Renderer2; } else { @@ -275,7 +310,7 @@ function getOrCreateRenderer2(di: LInjector): Renderer2 { * @returns the value from the injector or `null` when not found */ export function getOrCreateInjectable( - nodeInjector: LInjector, token: Type| InjectionToken, + startInjectorIndex: number, hostView: LViewData, token: Type| InjectionToken, flags: InjectFlags = InjectFlags.Default): T|null { const bloomHash = bloomHashBitOrFactory(token); // If the ID stored here is a function, this is a special object like ElementRef or TemplateRef @@ -285,60 +320,73 @@ export function getOrCreateInjectable( // If the token has a bloom hash, then it is a directive that is public to the injection system // (diPublic) otherwise fall back to the module injector. if (bloomHash != null) { - let injector: LInjector|null = nodeInjector; + let injectorIndex = startInjectorIndex; + let injectorView = hostView; - while (injector) { - // Get the closest potential matching injector (upwards in the injector tree) that - // *potentially* has the token. - injector = bloomFindPossibleInjector(injector, bloomHash, flags); + if (flags & InjectFlags.SkipSelf) { + const parentLocation = injectorView[injectorIndex + PARENT_INJECTOR]; + injectorIndex = parentLocation & InjectorLocationFlags.InjectorIndexMask; + injectorView = getParentInjectorView(parentLocation, injectorView); + } + + while (injectorIndex !== -1) { + // Traverse up the injector tree until we find a potential match or until we know there + // *isn't* a match. Outer loop is necessary in case we get a false positive injector. + while (injectorIndex !== -1) { + // Check the current injector. If it matches, stop searching for an injector. + if (injectorHasToken(bloomHash, injectorIndex, injectorView[TVIEW].data)) { + break; + } + + if (flags & InjectFlags.Self || + flags & InjectFlags.Host && + !sameHostView(injectorView[injectorIndex + PARENT_INJECTOR])) { + injectorIndex = -1; + break; + } + + // If the ancestor bloom filter value has the bit corresponding to the directive, traverse + // up to find the specific injector. If the ancestor bloom filter does not have the bit, we + // can abort. + if (injectorHasToken(bloomHash, injectorIndex, injectorView)) { + const parentLocation = injectorView[injectorIndex + PARENT_INJECTOR]; + injectorIndex = parentLocation & InjectorLocationFlags.InjectorIndexMask; + injectorView = getParentInjectorView(parentLocation, injectorView); + } else { + injectorIndex = -1; + break; + } + } // If no injector is found, we *know* that there is no ancestor injector that contains the // token, so we abort. - if (!injector) { + if (injectorIndex === -1) { break; } // At this point, we have an injector which *may* contain the token, so we step through the // directives associated with the injector's corresponding node to get the directive instance. - const tNode = injector.tNode; - const injectorView = injector.view; - const nodeFlags = tNode.flags; - const count = nodeFlags & TNodeFlags.DirectiveCountMask; - - if (count !== 0) { - const start = nodeFlags >> TNodeFlags.DirectiveStartingIndexShift; - const end = start + count; - const defs = injectorView[TVIEW].directives !; - - for (let i = start; i < end; i++) { - // Get the definition for the directive at this index and, if it is injectable (diPublic), - // and matches the given token, return the directive instance. - const directiveDef = defs[i] as DirectiveDefInternal; - if (directiveDef.type === token && directiveDef.diPublic) { - return injectorView[DIRECTIVES] ![i]; - } - } + let instance: T|null; + if (instance = searchDirectivesOnInjector(injectorIndex, injectorView, token)) { + return instance; } // If we *didn't* find the directive for the token and we are searching the current node's // injector, it's possible the directive is on this node and hasn't been created yet. - let instance: T|null; - if (injector === nodeInjector && + if (injectorIndex === startInjectorIndex && hostView === injectorView && (instance = searchMatchesQueuedForCreation(token, injectorView[TVIEW]))) { return instance; } // The def wasn't found anywhere on this node, so it was a false positive. - // If flags permit, traverse up the tree and continue searching. - if (flags & InjectFlags.Self || flags & InjectFlags.Host && !sameHostView(injector)) { - injector = null; - } else { - injector = injector.parent; - } + // Traverse up the tree and continue searching. + const parentLocation = injectorView[injectorIndex + PARENT_INJECTOR]; + injectorIndex = parentLocation & InjectorLocationFlags.InjectorIndexMask; + injectorView = getParentInjectorView(parentLocation, injectorView); } } - const moduleInjector = nodeInjector.view[INJECTOR]; + const moduleInjector = hostView[INJECTOR]; const formerInjector = setCurrentInjector(moduleInjector); try { return inject(token, flags); @@ -360,6 +408,29 @@ function searchMatchesQueuedForCreation(token: any, hostTView: TView): T|null return null; } +function searchDirectivesOnInjector( + injectorIndex: number, injectorView: LViewData, token: Type| InjectionToken) { + const tNode = injectorView[TVIEW].data[injectorIndex + TNODE] as TNode; + const nodeFlags = tNode.flags; + const count = nodeFlags & TNodeFlags.DirectiveCountMask; + + if (count !== 0) { + const start = nodeFlags >> TNodeFlags.DirectiveStartingIndexShift; + const end = start + count; + const defs = injectorView[TVIEW].directives !; + + for (let i = start; i < end; i++) { + // Get the definition for the directive at this index and, if it is injectable (diPublic), + // and matches the given token, return the directive instance. + const directiveDef = defs[i] as DirectiveDefInternal; + if (directiveDef.type === token && directiveDef.diPublic) { + return injectorView[DIRECTIVES] ![i]; + } + } + } + return null; +} + /** * Returns the bit in an injector's bloom filter that should be used to determine whether or not * the directive might be provided by the injector. @@ -371,108 +442,67 @@ function searchMatchesQueuedForCreation(token: any, hostTView: TView): T|null * @param token the injection token * @returns the matching bit to check in the bloom filter or `null` if the token is not known. */ -function bloomHashBitOrFactory(token: Type| InjectionToken): number|Function|undefined { - const tokenId: number|undefined = (token as any)[NG_ELEMENT_ID] || null; +export function bloomHashBitOrFactory(token: Type| InjectionToken): number|Function| + undefined { + const tokenId: number|undefined = (token as any)[NG_ELEMENT_ID]; return typeof tokenId === 'number' ? tokenId & BLOOM_MASK : tokenId; } -/** - * Finds the closest injector that might have a certain directive. - * - * Each directive corresponds to a bit in an injector's bloom filter. Given the bloom bit to - * check and a starting injector, this function traverses up injectors until it finds an - * injector that contains a 1 for that bit in its bloom filter. A 1 indicates that the - * injector may have that directive. It only *may* have the directive because directives begin - * to share bloom filter bits after the BLOOM_SIZE is reached, and it could correspond to a - * different directive sharing the bit. - * - * Note: We can skip checking further injectors up the tree if an injector's cbf structure - * has a 0 for that bloom bit. Since cbf contains the merged value of all the parent - * injectors, a 0 in the bloom bit indicates that the parents definitely do not contain - * the directive and do not need to be checked. - * - * @param injector The starting node injector to check - * @param bloomBit The bit to check in each injector's bloom filter - * @param flags The injection flags for this injection site (e.g. Optional or SkipSelf) - * @returns An injector that might have the directive - */ -export function bloomFindPossibleInjector( - startInjector: LInjector, bloomBit: number, flags: InjectFlags): LInjector|null { +export function injectorHasToken( + bloomHash: number, injectorIndex: number, injectorView: LViewData | TData) { // Create a mask that targets the specific bit associated with the directive we're looking for. // JS bit operations are 32 bits, so this will be a number between 2^0 and 2^31, corresponding // to bit positions 0 - 31 in a 32 bit integer. - const mask = 1 << bloomBit; - const b7 = bloomBit & 0x80; - const b6 = bloomBit & 0x40; - const b5 = bloomBit & 0x20; + const mask = 1 << bloomHash; + const b7 = bloomHash & 0x80; + const b6 = bloomHash & 0x40; + const b5 = bloomHash & 0x20; - // Traverse up the injector tree until we find a potential match or until we know there *isn't* a - // match. - let injector: LInjector|null = - flags & InjectFlags.SkipSelf ? startInjector.parent : startInjector; + // Our bloom filter size is 256 bits, which is eight 32-bit bloom filter buckets: + // bf0 = [0 - 31], bf1 = [32 - 63], bf2 = [64 - 95], bf3 = [96 - 127], etc. + // Get the bloom filter value from the appropriate bucket based on the directive's bloomBit. + let value: number; - while (injector) { - // Our bloom filter size is 256 bits, which is eight 32-bit bloom filter buckets: - // bf0 = [0 - 31], bf1 = [32 - 63], bf2 = [64 - 95], bf3 = [96 - 127], etc. - // Get the bloom filter value from the appropriate bucket based on the directive's bloomBit. - let value: number; - - if (b7) { - value = b6 ? (b5 ? injector.bf7 : injector.bf6) : (b5 ? injector.bf5 : injector.bf4); - } else { - value = b6 ? (b5 ? injector.bf3 : injector.bf2) : (b5 ? injector.bf1 : injector.bf0); - } - - // If the bloom filter value has the bit corresponding to the directive's bloomBit flipped on, - // this injector is a potential match. - if (value & mask) { - return injector; - } - - if (flags & InjectFlags.Self || flags & InjectFlags.Host && !sameHostView(injector)) { - return null; - } - - // If the current injector does not have the directive, check the bloom filters for the ancestor - // injectors (cbf0 - cbf7). These filters capture *all* ancestor injectors. - if (b7) { - value = b6 ? (b5 ? injector.cbf7 : injector.cbf6) : (b5 ? injector.cbf5 : injector.cbf4); - } else { - value = b6 ? (b5 ? injector.cbf3 : injector.cbf2) : (b5 ? injector.cbf1 : injector.cbf0); - } - - // If the ancestor bloom filter value has the bit corresponding to the directive, traverse up to - // find the specific injector. If the ancestor bloom filter does not have the bit, we can abort. - if (value & mask) { - injector = injector.parent; - } else { - return null; - } + if (b7) { + value = b6 ? (b5 ? injectorView[injectorIndex + 7] : injectorView[injectorIndex + 6]) : + (b5 ? injectorView[injectorIndex + 5] : injectorView[injectorIndex + 4]); + } else { + value = b6 ? (b5 ? injectorView[injectorIndex + 3] : injectorView[injectorIndex + 2]) : + (b5 ? injectorView[injectorIndex + 1] : injectorView[injectorIndex]); } - return null; + // If the bloom filter value has the bit corresponding to the directive's bloomBit flipped on, + // this injector is a potential match. + return !!(value & mask); } + /** * Checks whether the current injector and its parent are in the same host view. * * This is necessary to support @Host() decorators. If @Host() is set, we should stop searching once * the injector and its parent view don't match because it means we'd cross the view boundary. */ -function sameHostView(injector: LInjector): boolean { - return !!injector.parent && injector.parent.view === injector.view; +function sameHostView(parentLocation: number): boolean { + return !!parentLocation && (parentLocation >> InjectorLocationFlags.ViewOffsetShift) === 0; } export class NodeInjector implements Injector { - constructor(private _lInjector: LInjector) {} + private _injectorIndex: number; + + constructor( + private _tNode: TElementNode|TContainerNode|TElementContainerNode, + private _hostView: LViewData) { + this._injectorIndex = getOrCreateNodeInjectorForNode(_tNode, _hostView); + } get(token: any): any { if (token === Renderer2) { - return getOrCreateRenderer2(this._lInjector); + return getOrCreateRenderer2(this._hostView); } - setEnvironment(this._lInjector.tNode, this._lInjector.view); - return getOrCreateInjectable(this._lInjector, token); + setEnvironment(this._tNode, this._hostView); + return getOrCreateInjectable(this._injectorIndex, this._hostView, token); } } export function getFactoryOf(type: Type): ((type?: Type) => T)|null { diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index ebe7fee981..4bb658b699 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -19,7 +19,6 @@ import {throwCyclicDependencyError, throwErrorIfNoChangesMode, throwMultipleComp import {executeHooks, executeInitHooks, queueInitHooks, queueLifecycleHooks} from './hooks'; import {ACTIVE_INDEX, LContainer, RENDER_PARENT, VIEWS} from './interfaces/container'; import {ComponentDefInternal, ComponentQuery, ComponentTemplate, DirectiveDefInternal, DirectiveDefListOrFactory, InitialStylingFlags, PipeDefListOrFactory, RenderFlags} from './interfaces/definition'; -import {LInjector} from './interfaces/injector'; import {AttributeMarker, InitialInputData, InitialInputs, LContainerNode, LElementContainerNode, LElementNode, LNode, LProjectionNode, LTextNode, LViewNode, LocalRefExtractor, PropertyAliasValue, PropertyAliases, TAttributes, TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeFlags, TNodeType, TProjectionNode, TViewNode} from './interfaces/node'; import {CssSelectorList, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection'; import {LQueries} from './interfaces/query'; @@ -1106,8 +1105,8 @@ export function createTView( template: templateFn, viewQuery: viewQuery, node: null !, - data: HEADER_FILLER.slice(), // Fill in to match HEADER_OFFSET in LViewData - childIndex: -1, // Children set in addToViewTree(), if any + data: blueprint.slice(), // Fill in to match HEADER_OFFSET in LViewData + childIndex: -1, // Children set in addToViewTree(), if any bindingStartIndex: bindingStartIndex, hostBindingStartIndex: initialViewLength, directives: null, diff --git a/packages/core/src/render3/interfaces/injector.ts b/packages/core/src/render3/interfaces/injector.ts index a20fb83dad..4acf62ce36 100644 --- a/packages/core/src/render3/interfaces/injector.ts +++ b/packages/core/src/render3/interfaces/injector.ts @@ -7,71 +7,97 @@ */ -import {ChangeDetectorRef} from '../../change_detection/change_detector_ref'; -import {ElementRef} from '../../linker/element_ref'; -import {TemplateRef} from '../../linker/template_ref'; -import {ViewContainerRef} from '../../linker/view_container_ref'; - import {TContainerNode, TElementContainerNode, TElementNode,} from './node'; -import {LViewData} from './view'; -export interface LInjector { - /** - * We need to store a reference to the injector's parent so DI can keep looking up - * the injector tree until it finds the dependency it's looking for. - */ - readonly parent: LInjector|null; +export const TNODE = 8; +export const PARENT_INJECTOR = 8; +export const INJECTOR_SIZE = 9; - /** Necessary to find directive indices for a particular node and look up the LNode. */ - readonly tNode: TElementNode|TElementContainerNode|TContainerNode; - - /** - * The view where the node is stored. Necessary because as we traverse up the injector - * tree the view where we search directives may change. - */ - readonly view: LViewData; - - /** - * The following bloom filter determines whether a directive is available - * on the associated node or not. This prevents us from searching the directives - * array at this level unless it's probable the directive is in it. - * - * - bf0: Check directive IDs 0-31 (IDs are % 128) - * - bf1: Check directive IDs 32-63 - * - bf2: Check directive IDs 64-95 - * - bf3: Check directive IDs 96-127 - * - bf4: Check directive IDs 128-159 - * - bf5: Check directive IDs 160 - 191 - * - bf6: Check directive IDs 192 - 223 - * - bf7: Check directive IDs 224 - 255 - * - * See: https://en.wikipedia.org/wiki/Bloom_filter for more about bloom filters. - */ - bf0: number; - bf1: number; - bf2: number; - bf3: number; - bf4: number; - bf5: number; - bf6: number; - bf7: number; - - /** - * cbf0 - cbf7 properties determine whether a directive is available through a - * parent injector. They refer to the merged values of parent bloom filters. This - * allows us to skip looking up the chain unless it's probable that directive exists - * up the chain. - */ - cbf0: number; - cbf1: number; - cbf2: number; - cbf3: number; - cbf4: number; - cbf5: number; - cbf6: number; - cbf7: number; +export const enum InjectorLocationFlags { + InjectorIndexMask = 0b111111111111111, + ViewOffsetShift = 15 } +/** + * Each injector is saved in 9 contiguous slots in `LViewData` and 9 contiguous slots in + * `TView.data`. This allows us to store information about the current node's tokens (which + * can be shared in `TView`) as well as the tokens of its ancestor nodes (which cannot be + * shared, so they live in `LViewData`). + * + * Each of these slots (aside from the last slot) contains a bloom filter. This bloom filter + * determines whether a directive is available on the associated node or not. This prevents us + * from searching the directives array at this level unless it's probable the directive is in it. + * + * See: https://en.wikipedia.org/wiki/Bloom_filter for more about bloom filters. + * + * Because all injectors have been flattened into `LViewData` and `TViewData`, they cannot typed + * using interfaces as they were previously. The start index of each `LInjector` and `TInjector` + * will differ based on where it is flattened into the main array, so it's not possible to know + * the indices ahead of time and save their types here. The interfaces are still included here + * for documentation purposes. + * + * export interface LInjector extends Array { + * + * // Cumulative bloom for directive IDs 0-31 (IDs are % BLOOM_SIZE) + * [0]: number; + * + * // Cumulative bloom for directive IDs 32-63 + * [1]: number; + * + * // Cumulative bloom for directive IDs 64-95 + * [2]: number; + * + * // Cumulative bloom for directive IDs 96-127 + * [3]: number; + * + * // Cumulative bloom for directive IDs 128-159 + * [4]: number; + * + * // Cumulative bloom for directive IDs 160 - 191 + * [5]: number; + * + * // Cumulative bloom for directive IDs 192 - 223 + * [6]: number; + * + * // Cumulative bloom for directive IDs 224 - 255 + * [7]: number; + * + * // We need to store a reference to the injector's parent so DI can keep looking up + * // the injector tree until it finds the dependency it's looking for. + * [PARENT_INJECTOR]: number; + * } + * + * export interface TInjector extends Array { + * + * // Shared node bloom for directive IDs 0-31 (IDs are % BLOOM_SIZE) + * [0]: number; + * + * // Shared node bloom for directive IDs 32-63 + * [1]: number; + * + * // Shared node bloom for directive IDs 64-95 + * [2]: number; + * + * // Shared node bloom for directive IDs 96-127 + * [3]: number; + * + * // Shared node bloom for directive IDs 128-159 + * [4]: number; + * + * // Shared node bloom for directive IDs 160 - 191 + * [5]: number; + * + * // Shared node bloom for directive IDs 192 - 223 + * [6]: number; + * + * // Shared node bloom for directive IDs 224 - 255 + * [7]: number; + * + * // Necessary to find directive indices for a particular node. + * [TNODE]: TElementNode|TElementContainerNode|TContainerNode; + * } + */ + // Note: This hack is necessary so we don't erroneously get a circular dependency // failure based on types. export const unusedValueExportToPlacateAjd = 1; diff --git a/packages/core/src/render3/interfaces/node.ts b/packages/core/src/render3/interfaces/node.ts index 2ea0bc64a4..112572ad16 100644 --- a/packages/core/src/render3/interfaces/node.ts +++ b/packages/core/src/render3/interfaces/node.ts @@ -7,8 +7,6 @@ */ import {LContainer} from './container'; -import {LInjector} from './injector'; -import {LQueries} from './query'; import {RComment, RElement, RText} from './renderer'; import {StylingContext} from './styling'; import {LViewData, TView} from './view'; diff --git a/packages/core/src/render3/interfaces/view.ts b/packages/core/src/render3/interfaces/view.ts index d4bd36e9a8..8d1719ed87 100644 --- a/packages/core/src/render3/interfaces/view.ts +++ b/packages/core/src/render3/interfaces/view.ts @@ -550,11 +550,15 @@ export type HookData = (number | (() => void))[]; * Static data that corresponds to the instance-specific data array on an LView. * * Each node's static data is stored in tData at the same index that it's stored - * in the data array. Each pipe's definition is stored here at the same index - * as its pipe instance in the data array. Any nodes that do not have static - * data store a null value in tData to avoid a sparse array. + * in the data array. Any nodes that do not have static data store a null value in + * tData to avoid a sparse array. + * + * Each pipe's definition is stored here at the same index as its pipe instance in + * the data array. + * + * Injector bloom filters are also stored here. */ -export type TData = (TNode | PipeDefInternal| null)[]; +export type TData = (TNode | PipeDefInternal| number | null)[]; /** Type for TView.currentMatches */ export type CurrentMatchesList = [DirectiveDefInternal, (string | number | null)]; diff --git a/packages/core/src/render3/query.ts b/packages/core/src/render3/query.ts index a5715c515f..71d2379344 100644 --- a/packages/core/src/render3/query.ts +++ b/packages/core/src/render3/query.ts @@ -14,7 +14,6 @@ import {EventEmitter} from '../event_emitter'; import {ElementRef as ViewEngine_ElementRef} from '../linker/element_ref'; import {QueryList as viewEngine_QueryList} from '../linker/query_list'; import {TemplateRef as ViewEngine_TemplateRef} from '../linker/template_ref'; -import {ViewContainerRef as ViewEngine_ViewContainerRef} from '../linker/view_container_ref'; import {Type} from '../type'; import {getSymbolIterator} from '../util'; @@ -485,4 +484,4 @@ export function queryRefresh(queryList: QueryList): boolean { return true; } return false; -} \ No newline at end of file +} diff --git a/packages/core/src/render3/view_engine_compatibility.ts b/packages/core/src/render3/view_engine_compatibility.ts index a2c4d01ace..b682e7caea 100644 --- a/packages/core/src/render3/view_engine_compatibility.ts +++ b/packages/core/src/render3/view_engine_compatibility.ts @@ -16,16 +16,17 @@ import {ViewContainerRef as ViewEngine_ViewContainerRef} from '../linker/view_co import {EmbeddedViewRef as viewEngine_EmbeddedViewRef, ViewRef as viewEngine_ViewRef} from '../linker/view_ref'; import {assertDefined, assertGreaterThan, assertLessThan} from './assert'; -import {NodeInjector, getInjector, getOrCreateNodeInjectorForNode, getParentInjector} from './di'; +import {NodeInjector, getParentInjectorLocation, getParentInjectorView} from './di'; import {_getViewData, addToViewTree, createEmbeddedViewAndNode, createLContainer, createLNodeObject, createTNode, getPreviousOrParentTNode, getRenderer, renderEmbeddedTemplate} from './instructions'; import {LContainer, RENDER_PARENT, VIEWS} from './interfaces/container'; import {RenderFlags} from './interfaces/definition'; +import {InjectorLocationFlags} from './interfaces/injector'; import {LContainerNode, TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeFlags, TNodeType, TViewNode} from './interfaces/node'; import {LQueries} from './interfaces/query'; import {RComment, RElement, Renderer3} from './interfaces/renderer'; -import {CONTEXT, HOST_NODE, LViewData, QUERIES, RENDERER, TView} from './interfaces/view'; +import {CONTEXT, HOST_NODE, LViewData, QUERIES, RENDERER, TVIEW, TView} from './interfaces/view'; import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert'; -import {addRemoveViewFromContainer, appendChild, detachView, findComponentView, getBeforeNodeForView, getParentLNode, getRenderParent, insertView, removeView} from './node_manipulation'; +import {addRemoveViewFromContainer, appendChild, detachView, findComponentView, getBeforeNodeForView, getRenderParent, insertView, removeView} from './node_manipulation'; import {getLNode, isComponent} from './util'; import {ViewRef} from './view_ref'; @@ -180,15 +181,17 @@ export function createContainerRef( return createElementRef(ElementRefToken, this._hostTNode, this._hostView); } - get injector(): Injector { - const nodeInjector = getOrCreateNodeInjectorForNode(this._hostTNode, this._hostView); - return new NodeInjector(nodeInjector); - } + get injector(): Injector { return new NodeInjector(this._hostTNode, this._hostView); } /** @deprecated No replacement */ get parentInjector(): Injector { - const parentLInjector = getParentInjector(this._hostTNode, this._hostView); - return parentLInjector ? new NodeInjector(parentLInjector) : new NullInjector(); + const parentLocation = getParentInjectorLocation(this._hostTNode, this._hostView); + const parentView = getParentInjectorView(parentLocation, this._hostView); + const parentIndex = parentLocation & InjectorLocationFlags.InjectorIndexMask; + const parentTNode = parentView[TVIEW].data[parentIndex] as TElementNode | TContainerNode; + + return parentLocation === -1 ? new NullInjector() : + new NodeInjector(parentTNode, parentView); } clear(): void { diff --git a/packages/core/test/bundling/animation_world/bundle.golden_symbols.json b/packages/core/test/bundling/animation_world/bundle.golden_symbols.json index b2980a5f73..4008c148b1 100644 --- a/packages/core/test/bundling/animation_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/animation_world/bundle.golden_symbols.json @@ -68,9 +68,6 @@ { "name": "FLAGS" }, - { - "name": "HEADER_FILLER" - }, { "name": "HEADER_OFFSET" }, @@ -80,6 +77,9 @@ { "name": "INJECTOR$1" }, + { + "name": "INJECTOR_SIZE" + }, { "name": "IterableChangeRecord_" }, @@ -140,6 +140,9 @@ { "name": "PARENT" }, + { + "name": "PARENT_INJECTOR" + }, { "name": "PublicFeature" }, @@ -182,6 +185,9 @@ { "name": "TAIL" }, + { + "name": "TNODE" + }, { "name": "TVIEW" }, @@ -302,9 +308,6 @@ { "name": "bloomAdd" }, - { - "name": "bloomFindPossibleInjector" - }, { "name": "bloomHashBitOrFactory" }, @@ -582,7 +585,7 @@ "name": "getInjectableDef" }, { - "name": "getInjector$1" + "name": "getInjectorIndex" }, { "name": "getLElementFromComponent" @@ -621,7 +624,10 @@ "name": "getOrCreateTView" }, { - "name": "getParentInjector" + "name": "getParentInjectorLocation" + }, + { + "name": "getParentInjectorView" }, { "name": "getParentLNode" @@ -707,6 +713,9 @@ { "name": "injectViewContainerRef" }, + { + "name": "injectorHasToken" + }, { "name": "insertNewMultiProperty" }, @@ -911,6 +920,9 @@ { "name": "scheduleTick" }, + { + "name": "searchDirectivesOnInjector" + }, { "name": "searchMatchesQueuedForCreation" }, diff --git a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json index 51ffa47f74..8893f93e3f 100644 --- a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json @@ -38,9 +38,6 @@ { "name": "FLAGS" }, - { - "name": "HEADER_FILLER" - }, { "name": "HEADER_OFFSET" }, @@ -50,6 +47,9 @@ { "name": "INJECTOR$1" }, + { + "name": "INJECTOR_SIZE" + }, { "name": "MONKEY_PATCH_KEY_NAME" }, @@ -80,6 +80,9 @@ { "name": "PARENT" }, + { + "name": "PARENT_INJECTOR" + }, { "name": "PublicFeature" }, @@ -107,9 +110,6 @@ { "name": "ViewEncapsulation$1" }, - { - "name": "_CLEAN_PROMISE" - }, { "name": "_getViewData" }, @@ -240,7 +240,7 @@ "name": "getHostElementNode" }, { - "name": "getInjector$1" + "name": "getInjectorIndex" }, { "name": "getLNode" @@ -258,7 +258,10 @@ "name": "getOrCreateTView" }, { - "name": "getParentInjector" + "name": "getParentInjectorLocation" + }, + { + "name": "getParentInjectorView" }, { "name": "getParentLNode" diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index 6878df5edd..3a1fa34240 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -53,9 +53,6 @@ { "name": "FLAGS" }, - { - "name": "HEADER_FILLER" - }, { "name": "HEADER_OFFSET" }, @@ -65,6 +62,9 @@ { "name": "INJECTOR$1" }, + { + "name": "INJECTOR_SIZE" + }, { "name": "IterableChangeRecord_" }, @@ -131,6 +131,9 @@ { "name": "PARENT" }, + { + "name": "PARENT_INJECTOR" + }, { "name": "PublicFeature" }, @@ -170,6 +173,9 @@ { "name": "TAIL" }, + { + "name": "TNODE" + }, { "name": "TVIEW" }, @@ -368,9 +374,6 @@ { "name": "bloomAdd" }, - { - "name": "bloomFindPossibleInjector" - }, { "name": "bloomHashBitOrFactory" }, @@ -624,7 +627,7 @@ "name": "getInjectableDef" }, { - "name": "getInjector$1" + "name": "getInjectorIndex" }, { "name": "getLElementFromComponent" @@ -657,7 +660,10 @@ "name": "getOrCreateTView" }, { - "name": "getParentInjector" + "name": "getParentInjectorLocation" + }, + { + "name": "getParentInjectorView" }, { "name": "getParentLNode" @@ -737,6 +743,9 @@ { "name": "injectViewContainerRef" }, + { + "name": "injectorHasToken" + }, { "name": "insertView" }, @@ -935,6 +944,9 @@ { "name": "scheduleTick" }, + { + "name": "searchDirectivesOnInjector" + }, { "name": "searchMatchesQueuedForCreation" }, diff --git a/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json b/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json index 18679254fe..8a808af482 100644 --- a/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json @@ -320,9 +320,6 @@ { "name": "HAMMER_LOADER" }, - { - "name": "HEADER_FILLER" - }, { "name": "HEADER_OFFSET" }, @@ -353,6 +350,9 @@ { "name": "INJECTOR$1" }, + { + "name": "INJECTOR_SIZE" + }, { "name": "INSPECT_GLOBAL_NAME" }, @@ -623,6 +623,9 @@ { "name": "PARENT" }, + { + "name": "PARENT_INJECTOR" + }, { "name": "PATTERN_SEP" }, @@ -815,6 +818,9 @@ { "name": "THURSDAY" }, + { + "name": "TNODE" + }, { "name": "TRANSITION_ID" }, @@ -1202,9 +1208,6 @@ { "name": "bloomAdd" }, - { - "name": "bloomFindPossibleInjector" - }, { "name": "bloomHashBitOrFactory" }, @@ -1653,10 +1656,10 @@ "name": "getInjectableDef" }, { - "name": "getInjector$1" + "name": "getInjectorDef" }, { - "name": "getInjectorDef" + "name": "getInjectorIndex" }, { "name": "getLElementFromComponent" @@ -1752,7 +1755,10 @@ "name": "getOriginalError" }, { - "name": "getParentInjector" + "name": "getParentInjectorLocation" + }, + { + "name": "getParentInjectorView" }, { "name": "getParentLNode" @@ -1886,6 +1892,9 @@ { "name": "injectableDefRecord" }, + { + "name": "injectorHasToken" + }, { "name": "insertView" }, @@ -2282,6 +2291,9 @@ { "name": "scheduleTick" }, + { + "name": "searchDirectivesOnInjector" + }, { "name": "searchMatchesQueuedForCreation" }, diff --git a/packages/core/test/render3/di_spec.ts b/packages/core/test/render3/di_spec.ts index 89cb474285..77280629af 100644 --- a/packages/core/test/render3/di_spec.ts +++ b/packages/core/test/render3/di_spec.ts @@ -10,20 +10,20 @@ import {Attribute, ChangeDetectorRef, ElementRef, Host, InjectFlags, Injector, O import {RenderFlags} from '@angular/core/src/render3/interfaces/definition'; import {defineComponent} from '../../src/render3/definition'; -import {bloomAdd, bloomFindPossibleInjector, getInjector, getOrCreateNodeInjector, injectAttribute} from '../../src/render3/di'; -import {PublicFeature, defineDirective, directiveInject, elementProperty, getCurrentView, getRenderedText, injectRenderer2, load, templateRefExtractor} from '../../src/render3/index'; +import {bloomAdd, bloomHashBitOrFactory as bloomHash, getOrCreateInjectable, getOrCreateNodeInjector, injectAttribute, injectorHasToken} from '../../src/render3/di'; +import {PublicFeature, defineDirective, directiveInject, elementProperty, injectRenderer2, load, templateRefExtractor} from '../../src/render3/index'; import {bind, container, containerRefreshEnd, containerRefreshStart, createNodeAtIndex, createLViewData, createTView, element, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, enterView, interpolation2, leaveView, projection, projectionDef, reference, template, text, textBinding, loadDirective, elementContainerStart, elementContainerEnd, _getViewData, getTNode} from '../../src/render3/instructions'; -import {LInjector} from '../../src/render3/interfaces/injector'; import {isProceduralRenderer} from '../../src/render3/interfaces/renderer'; import {AttributeMarker, LContainerNode, LElementNode, TNodeType} from '../../src/render3/interfaces/node'; -import {HEADER_OFFSET, LViewData, LViewFlags, TVIEW, TView} from '../../src/render3/interfaces/view'; +import {LViewFlags} from '../../src/render3/interfaces/view'; import {ViewRef} from '../../src/render3/view_ref'; import {getRendererFactory2} from './imported_renderer2'; import {ComponentFixture, createComponent, createDirective, renderComponent, toHtml} from './render_util'; import {NgIf} from './common_with_def'; +import {TNODE} from '../../src/render3/interfaces/injector'; describe('di', () => { describe('no dependencies', () => { @@ -1680,72 +1680,79 @@ describe('di', () => { describe('inject', () => { describe('bloom filter', () => { - let di: LInjector; + let mockTView: any; beforeEach(() => { - di = {} as any; - di.bf0 = 0; - di.bf1 = 0; - di.bf2 = 0; - di.bf3 = 0; - di.bf4 = 0; - di.bf5 = 0; - di.bf6 = 0; - di.bf7 = 0; - di.bf3 = 0; - di.cbf0 = 0; - di.cbf1 = 0; - di.cbf2 = 0; - di.cbf3 = 0; - di.cbf4 = 0; - di.cbf5 = 0; - di.cbf6 = 0; - di.cbf7 = 0; + mockTView = {data: [0, 0, 0, 0, 0, 0, 0, 0, null], firstTemplatePass: true}; }); - function bloomState() { - return [di.bf7, di.bf6, di.bf5, di.bf4, di.bf3, di.bf2, di.bf1, di.bf0]; + function bloomState() { return mockTView.data.slice(0, TNODE).reverse(); } + + class Dir0 { + /** @internal */ static __NG_ELEMENT_ID__ = 0; + } + class Dir1 { + /** @internal */ static __NG_ELEMENT_ID__ = 1; + } + class Dir33 { + /** @internal */ static __NG_ELEMENT_ID__ = 33; + } + class Dir66 { + /** @internal */ static __NG_ELEMENT_ID__ = 66; + } + class Dir99 { + /** @internal */ static __NG_ELEMENT_ID__ = 99; + } + class Dir132 { + /** @internal */ static __NG_ELEMENT_ID__ = 132; + } + class Dir165 { + /** @internal */ static __NG_ELEMENT_ID__ = 165; + } + class Dir198 { + /** @internal */ static __NG_ELEMENT_ID__ = 198; + } + class Dir231 { + /** @internal */ static __NG_ELEMENT_ID__ = 231; } it('should add values', () => { - bloomAdd(di, { __NG_ELEMENT_ID__: 0 } as any); + bloomAdd(0, mockTView, Dir0); expect(bloomState()).toEqual([0, 0, 0, 0, 0, 0, 0, 1]); - bloomAdd(di, { __NG_ELEMENT_ID__: 32 + 1 } as any); + bloomAdd(0, mockTView, Dir33); expect(bloomState()).toEqual([0, 0, 0, 0, 0, 0, 2, 1]); - bloomAdd(di, { __NG_ELEMENT_ID__: 64 + 2 } as any); + bloomAdd(0, mockTView, Dir66); expect(bloomState()).toEqual([0, 0, 0, 0, 0, 4, 2, 1]); - bloomAdd(di, { __NG_ELEMENT_ID__: 96 + 3 } as any); + bloomAdd(0, mockTView, Dir99); expect(bloomState()).toEqual([0, 0, 0, 0, 8, 4, 2, 1]); - bloomAdd(di, { __NG_ELEMENT_ID__: 128 + 4 } as any); + bloomAdd(0, mockTView, Dir132); expect(bloomState()).toEqual([0, 0, 0, 16, 8, 4, 2, 1]); - bloomAdd(di, { __NG_ELEMENT_ID__: 160 + 5 } as any); + bloomAdd(0, mockTView, Dir165); expect(bloomState()).toEqual([0, 0, 32, 16, 8, 4, 2, 1]); - bloomAdd(di, { __NG_ELEMENT_ID__: 192 + 6 } as any); + bloomAdd(0, mockTView, Dir198); expect(bloomState()).toEqual([0, 64, 32, 16, 8, 4, 2, 1]); - bloomAdd(di, { __NG_ELEMENT_ID__: 224 + 7 } as any); + bloomAdd(0, mockTView, Dir231); expect(bloomState()).toEqual([128, 64, 32, 16, 8, 4, 2, 1]); }); it('should query values', () => { - bloomAdd(di, { __NG_ELEMENT_ID__: 0 } as any); - bloomAdd(di, { __NG_ELEMENT_ID__: 32 } as any); - bloomAdd(di, { __NG_ELEMENT_ID__: 64 } as any); - bloomAdd(di, { __NG_ELEMENT_ID__: 96 } as any); - bloomAdd(di, { __NG_ELEMENT_ID__: 127 } as any); - bloomAdd(di, { __NG_ELEMENT_ID__: 161 } as any); - bloomAdd(di, { __NG_ELEMENT_ID__: 188 } as any); - bloomAdd(di, { __NG_ELEMENT_ID__: 223 } as any); - bloomAdd(di, { __NG_ELEMENT_ID__: 255 } as any); + bloomAdd(0, mockTView, Dir0); + bloomAdd(0, mockTView, Dir33); + bloomAdd(0, mockTView, Dir66); + bloomAdd(0, mockTView, Dir99); + bloomAdd(0, mockTView, Dir132); + bloomAdd(0, mockTView, Dir165); + bloomAdd(0, mockTView, Dir198); + bloomAdd(0, mockTView, Dir231); - expect(bloomFindPossibleInjector(di, 0, InjectFlags.Default)).toEqual(di); - expect(bloomFindPossibleInjector(di, 1, InjectFlags.Default)).toEqual(null); - expect(bloomFindPossibleInjector(di, 32, InjectFlags.Default)).toEqual(di); - expect(bloomFindPossibleInjector(di, 64, InjectFlags.Default)).toEqual(di); - expect(bloomFindPossibleInjector(di, 96, InjectFlags.Default)).toEqual(di); - expect(bloomFindPossibleInjector(di, 127, InjectFlags.Default)).toEqual(di); - expect(bloomFindPossibleInjector(di, 161, InjectFlags.Default)).toEqual(di); - expect(bloomFindPossibleInjector(di, 188, InjectFlags.Default)).toEqual(di); - expect(bloomFindPossibleInjector(di, 223, InjectFlags.Default)).toEqual(di); - expect(bloomFindPossibleInjector(di, 255, InjectFlags.Default)).toEqual(di); + expect(injectorHasToken(bloomHash(Dir0) as number, 0, mockTView.data)).toEqual(true); + expect(injectorHasToken(bloomHash(Dir1) as number, 0, mockTView.data)).toEqual(false); + expect(injectorHasToken(bloomHash(Dir33) as number, 0, mockTView.data)).toEqual(true); + expect(injectorHasToken(bloomHash(Dir66) as number, 0, mockTView.data)).toEqual(true); + expect(injectorHasToken(bloomHash(Dir99) as number, 0, mockTView.data)).toEqual(true); + expect(injectorHasToken(bloomHash(Dir132) as number, 0, mockTView.data)).toEqual(true); + expect(injectorHasToken(bloomHash(Dir165) as number, 0, mockTView.data)).toEqual(true); + expect(injectorHasToken(bloomHash(Dir198) as number, 0, mockTView.data)).toEqual(true); + expect(injectorHasToken(bloomHash(Dir231) as number, 0, mockTView.data)).toEqual(true); }); }); @@ -1778,9 +1785,11 @@ describe('di', () => { /** *
+ * % if (...) { * * {{ child1.value }} - {{ child2.value }} * + * % } *
*/ const App = createComponent('app', function(rf: RenderFlags, ctx: any) {