angular/packages/core/src/render3/pure_function.ts
Kara Erickson a20b2f72f2 fix(ivy): process creation mode deeply before running update mode (#27744)
Prior to this commit, we had two different modes for change detection
execution for Ivy, depending on whether you called `bootstrap()` or
`renderComponent()`. In the former case, we would complete creation
mode for all components in the tree before beginning update mode for
any component. In the latter case, we would run creation mode and
update mode together for each component individually.

Maintaining code to support these two different execution orders was
unnecessarily complex, so this commit aligns the two bootstrapping
mechanisms to execute in the same order. Now creation mode always
runs for all components before update mode begins.

This change also simplifies our rendering logic so that we use
`LView` flags as the source of truth for rendering mode instead of
`rf` function arguments. This fixed some related bugs (e.g. calling
`ViewRef.detectChanges` synchronously after the view's creation
would create view nodes twice, view queries would execute twice, etc).

PR Close #27744
2018-12-19 15:10:16 -05:00

294 lines
12 KiB
TypeScript

/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {bindingUpdated, bindingUpdated2, bindingUpdated3, bindingUpdated4, getBinding, updateBinding} from './bindings';
import {getBindingRoot, getLView, isCreationMode} from './state';
/**
* Bindings for pure functions are stored after regular bindings.
*
* |------consts------|---------vars---------| |----- hostVars (dir1) ------|
* ------------------------------------------------------------------------------------------
* | nodes/refs/pipes | bindings | fn slots | injector | dir1 | host bindings | host slots |
* ------------------------------------------------------------------------------------------
* ^ ^
* TView.bindingStartIndex TView.expandoStartIndex
*
* Pure function instructions are given an offset from the binding root. Adding the offset to the
* binding root gives the first index where the bindings are stored. In component views, the binding
* root is the bindingStartIndex. In host bindings, the binding root is the expandoStartIndex +
* any directive instances + any hostVars in directives evaluated before it.
*
* See VIEW_DATA.md for more information about host binding resolution.
*/
/**
* If the value hasn't been saved, calls the pure function to store and return the
* value. If it has been saved, returns the saved value.
*
* @param slotOffset the offset from binding root to the reserved slot
* @param pureFn Function that returns a value
* @param thisArg Optional calling context of pureFn
* @returns value
*/
export function pureFunction0<T>(slotOffset: number, pureFn: () => T, thisArg?: any): T {
// TODO(kara): use bindingRoot instead of bindingStartIndex when implementing host bindings
const bindingIndex = getBindingRoot() + slotOffset;
const lView = getLView();
return isCreationMode() ?
updateBinding(lView, bindingIndex, thisArg ? pureFn.call(thisArg) : pureFn()) :
getBinding(lView, bindingIndex);
}
/**
* If the value of the provided exp has changed, calls the pure function to return
* an updated value. Or if the value has not changed, returns cached value.
*
* @param slotOffset the offset from binding root to the reserved slot
* @param pureFn Function that returns an updated value
* @param exp Updated expression value
* @param thisArg Optional calling context of pureFn
* @returns Updated or cached value
*/
export function pureFunction1(
slotOffset: number, pureFn: (v: any) => any, exp: any, thisArg?: any): any {
// TODO(kara): use bindingRoot instead of bindingStartIndex when implementing host bindings
const lView = getLView();
const bindingIndex = getBindingRoot() + slotOffset;
return bindingUpdated(lView, bindingIndex, exp) ?
updateBinding(lView, bindingIndex + 1, thisArg ? pureFn.call(thisArg, exp) : pureFn(exp)) :
getBinding(lView, bindingIndex + 1);
}
/**
* If the value of any provided exp has changed, calls the pure function to return
* an updated value. Or if no values have changed, returns cached value.
*
* @param slotOffset the offset from binding root to the reserved slot
* @param pureFn
* @param exp1
* @param exp2
* @param thisArg Optional calling context of pureFn
* @returns Updated or cached value
*/
export function pureFunction2(
slotOffset: number, pureFn: (v1: any, v2: any) => any, exp1: any, exp2: any,
thisArg?: any): any {
// TODO(kara): use bindingRoot instead of bindingStartIndex when implementing host bindings
const bindingIndex = getBindingRoot() + slotOffset;
const lView = getLView();
return bindingUpdated2(lView, bindingIndex, exp1, exp2) ?
updateBinding(
lView, bindingIndex + 2,
thisArg ? pureFn.call(thisArg, exp1, exp2) : pureFn(exp1, exp2)) :
getBinding(lView, bindingIndex + 2);
}
/**
* If the value of any provided exp has changed, calls the pure function to return
* an updated value. Or if no values have changed, returns cached value.
*
* @param slotOffset the offset from binding root to the reserved slot
* @param pureFn
* @param exp1
* @param exp2
* @param exp3
* @param thisArg Optional calling context of pureFn
* @returns Updated or cached value
*/
export function pureFunction3(
slotOffset: number, pureFn: (v1: any, v2: any, v3: any) => any, exp1: any, exp2: any, exp3: any,
thisArg?: any): any {
// TODO(kara): use bindingRoot instead of bindingStartIndex when implementing host bindings
const bindingIndex = getBindingRoot() + slotOffset;
const lView = getLView();
return bindingUpdated3(lView, bindingIndex, exp1, exp2, exp3) ?
updateBinding(
lView, bindingIndex + 3,
thisArg ? pureFn.call(thisArg, exp1, exp2, exp3) : pureFn(exp1, exp2, exp3)) :
getBinding(lView, bindingIndex + 3);
}
/**
* If the value of any provided exp has changed, calls the pure function to return
* an updated value. Or if no values have changed, returns cached value.
*
* @param slotOffset the offset from binding root to the reserved slot
* @param pureFn
* @param exp1
* @param exp2
* @param exp3
* @param exp4
* @param thisArg Optional calling context of pureFn
* @returns Updated or cached value
*/
export function pureFunction4(
slotOffset: number, pureFn: (v1: any, v2: any, v3: any, v4: any) => any, exp1: any, exp2: any,
exp3: any, exp4: any, thisArg?: any): any {
// TODO(kara): use bindingRoot instead of bindingStartIndex when implementing host bindings
const bindingIndex = getBindingRoot() + slotOffset;
const lView = getLView();
return bindingUpdated4(lView, bindingIndex, exp1, exp2, exp3, exp4) ?
updateBinding(
lView, bindingIndex + 4,
thisArg ? pureFn.call(thisArg, exp1, exp2, exp3, exp4) : pureFn(exp1, exp2, exp3, exp4)) :
getBinding(lView, bindingIndex + 4);
}
/**
* If the value of any provided exp has changed, calls the pure function to return
* an updated value. Or if no values have changed, returns cached value.
*
* @param slotOffset the offset from binding root to the reserved slot
* @param pureFn
* @param exp1
* @param exp2
* @param exp3
* @param exp4
* @param exp5
* @param thisArg Optional calling context of pureFn
* @returns Updated or cached value
*/
export function pureFunction5(
slotOffset: number, pureFn: (v1: any, v2: any, v3: any, v4: any, v5: any) => any, exp1: any,
exp2: any, exp3: any, exp4: any, exp5: any, thisArg?: any): any {
// TODO(kara): use bindingRoot instead of bindingStartIndex when implementing host bindings
const bindingIndex = getBindingRoot() + slotOffset;
const lView = getLView();
const different = bindingUpdated4(lView, bindingIndex, exp1, exp2, exp3, exp4);
return bindingUpdated(lView, bindingIndex + 4, exp5) || different ?
updateBinding(
lView, bindingIndex + 5, thisArg ? pureFn.call(thisArg, exp1, exp2, exp3, exp4, exp5) :
pureFn(exp1, exp2, exp3, exp4, exp5)) :
getBinding(lView, bindingIndex + 5);
}
/**
* If the value of any provided exp has changed, calls the pure function to return
* an updated value. Or if no values have changed, returns cached value.
*
* @param slotOffset the offset from binding root to the reserved slot
* @param pureFn
* @param exp1
* @param exp2
* @param exp3
* @param exp4
* @param exp5
* @param exp6
* @param thisArg Optional calling context of pureFn
* @returns Updated or cached value
*/
export function pureFunction6(
slotOffset: number, pureFn: (v1: any, v2: any, v3: any, v4: any, v5: any, v6: any) => any,
exp1: any, exp2: any, exp3: any, exp4: any, exp5: any, exp6: any, thisArg?: any): any {
// TODO(kara): use bindingRoot instead of bindingStartIndex when implementing host bindings
const bindingIndex = getBindingRoot() + slotOffset;
const lView = getLView();
const different = bindingUpdated4(lView, bindingIndex, exp1, exp2, exp3, exp4);
return bindingUpdated2(lView, bindingIndex + 4, exp5, exp6) || different ?
updateBinding(
lView, bindingIndex + 6, thisArg ?
pureFn.call(thisArg, exp1, exp2, exp3, exp4, exp5, exp6) :
pureFn(exp1, exp2, exp3, exp4, exp5, exp6)) :
getBinding(lView, bindingIndex + 6);
}
/**
* If the value of any provided exp has changed, calls the pure function to return
* an updated value. Or if no values have changed, returns cached value.
*
* @param slotOffset the offset from binding root to the reserved slot
* @param pureFn
* @param exp1
* @param exp2
* @param exp3
* @param exp4
* @param exp5
* @param exp6
* @param exp7
* @param thisArg Optional calling context of pureFn
* @returns Updated or cached value
*/
export function pureFunction7(
slotOffset: number,
pureFn: (v1: any, v2: any, v3: any, v4: any, v5: any, v6: any, v7: any) => any, exp1: any,
exp2: any, exp3: any, exp4: any, exp5: any, exp6: any, exp7: any, thisArg?: any): any {
// TODO(kara): use bindingRoot instead of bindingStartIndex when implementing host bindings
const bindingIndex = getBindingRoot() + slotOffset;
const lView = getLView();
let different = bindingUpdated4(lView, bindingIndex, exp1, exp2, exp3, exp4);
return bindingUpdated3(lView, bindingIndex + 4, exp5, exp6, exp7) || different ?
updateBinding(
lView, bindingIndex + 7, thisArg ?
pureFn.call(thisArg, exp1, exp2, exp3, exp4, exp5, exp6, exp7) :
pureFn(exp1, exp2, exp3, exp4, exp5, exp6, exp7)) :
getBinding(lView, bindingIndex + 7);
}
/**
* If the value of any provided exp has changed, calls the pure function to return
* an updated value. Or if no values have changed, returns cached value.
*
* @param slotOffset the offset from binding root to the reserved slot
* @param pureFn
* @param exp1
* @param exp2
* @param exp3
* @param exp4
* @param exp5
* @param exp6
* @param exp7
* @param exp8
* @param thisArg Optional calling context of pureFn
* @returns Updated or cached value
*/
export function pureFunction8(
slotOffset: number,
pureFn: (v1: any, v2: any, v3: any, v4: any, v5: any, v6: any, v7: any, v8: any) => any,
exp1: any, exp2: any, exp3: any, exp4: any, exp5: any, exp6: any, exp7: any, exp8: any,
thisArg?: any): any {
// TODO(kara): use bindingRoot instead of bindingStartIndex when implementing host bindings
const bindingIndex = getBindingRoot() + slotOffset;
const lView = getLView();
const different = bindingUpdated4(lView, bindingIndex, exp1, exp2, exp3, exp4);
return bindingUpdated4(lView, bindingIndex + 4, exp5, exp6, exp7, exp8) || different ?
updateBinding(
lView, bindingIndex + 8, thisArg ?
pureFn.call(thisArg, exp1, exp2, exp3, exp4, exp5, exp6, exp7, exp8) :
pureFn(exp1, exp2, exp3, exp4, exp5, exp6, exp7, exp8)) :
getBinding(lView, bindingIndex + 8);
}
/**
* pureFunction instruction that can support any number of bindings.
*
* If the value of any provided exp has changed, calls the pure function to return
* an updated value. Or if no values have changed, returns cached value.
*
* @param slotOffset the offset from binding root to the reserved slot
* @param pureFn A pure function that takes binding values and builds an object or array
* containing those values.
* @param exps An array of binding values
* @param thisArg Optional calling context of pureFn
* @returns Updated or cached value
*/
export function pureFunctionV(
slotOffset: number, pureFn: (...v: any[]) => any, exps: any[], thisArg?: any): any {
// TODO(kara): use bindingRoot instead of bindingStartIndex when implementing host bindings
let bindingIndex = getBindingRoot() + slotOffset;
let different = false;
const lView = getLView();
for (let i = 0; i < exps.length; i++) {
bindingUpdated(lView, bindingIndex++, exps[i]) && (different = true);
}
return different ? updateBinding(lView, bindingIndex, pureFn.apply(thisArg, exps)) :
getBinding(lView, bindingIndex);
}