diff --git a/lib/mixins/property-effects.js b/lib/mixins/property-effects.js index f946014a10..fc82d767db 100644 --- a/lib/mixins/property-effects.js +++ b/lib/mixins/property-effects.js @@ -415,18 +415,113 @@ function runReflectEffect(inst, property, props, oldProps, info) { function runComputedEffects(inst, changedProps, oldProps, hasPaths) { let computeEffects = inst[TYPES.COMPUTE]; if (computeEffects) { - let inputProps = changedProps; - while (runEffects(inst, computeEffects, inputProps, oldProps, hasPaths)) { + if (orderedComputed == 2) { + dedupeId++; + const order = orderedComputedDeps(inst); + const queue = []; + for (let p in changedProps) { + addEffectsFor(p, computeEffects, queue, order, hasPaths); + } + let info; + while ((info = queue.shift())) { + if (runComputedEffect(inst, '', changedProps, oldProps, info, hasPaths)) { + addEffectsFor(info.methodInfo, computeEffects, queue, order, hasPaths); + } + } Object.assign(/** @type {!Object} */ (oldProps), inst.__dataOld); Object.assign(/** @type {!Object} */ (changedProps), inst.__dataPending); - inputProps = inst.__dataPending; inst.__dataPending = null; - if (orderedComputed) { - // Ensure all computed properties are de-duped against the same turn - dedupeId--; + } else { + let inputProps = changedProps; + while (runEffects(inst, computeEffects, inputProps, oldProps, hasPaths)) { + Object.assign(/** @type {!Object} */ (oldProps), inst.__dataOld); + Object.assign(/** @type {!Object} */ (changedProps), inst.__dataPending); + inputProps = inst.__dataPending; + inst.__dataPending = null; + if (orderedComputed) { + // Ensure all computed properties are de-duped against the same turn + dedupeId--; + } + } + } + } +} + +const insertEffect = (info, effects, order) => { + let start = 0; + let end = effects.length - 1; + let idx = -1; + while (start <= end) { + const mid = (start + end) >> 1; + const cmp = order.get(effects[mid].methodInfo) - order.get(info.methodInfo); + if (cmp < 0) { + start = mid + 1; + } else if (cmp > 0) { + end = mid - 1; + } else { + idx = mid; + break; + } + } + if (idx < 0) { + idx = end + 1; + } + effects.splice(idx, 0, info); +}; + +const addEffectsFor = (prop, computeEffects, queue, order, hasPaths) => { + const rootProperty = hasPaths ? root(prop) : prop; + const fxs = computeEffects[rootProperty]; + if (fxs) { + for (let i=0; i { + const p = fx.info.methodInfo; + if (--counts[p] === 0) { + next.push(p); + } + }); } } + inst.constructor.__orderedComputedDeps = ordered; + } + return ordered; +} + +function edgeCounts(inst) { + const props = inst.constructor._properties; + const depsForComputed = inst[COMPUTE_INFO]; + const counts = {}; + const next = []; + for (let p in props) { + const deps = depsForComputed[p]; + if (deps) { + counts[p] = deps.args.length; + } else { + next.push(p); + } } + return {counts, next}; } const TRANSITIVE_DEPENDENCY = '~transitive~dependency~'; @@ -442,11 +537,11 @@ const TRANSITIVE_DEPENDENCY = '~transitive~dependency~'; * @param {?Object} oldProps Bag of previous values for changed properties * @param {?} info Effect metadata * @param {boolean} hasPaths True with `changedProps` contains one or more paths - * @return {void} + * @return {boolean} True when the property being computed changed * @private */ function runComputedEffect(inst, property, changedProps, oldProps, info, hasPaths) { - if (orderedComputed) { + if (orderedComputed == 1) { // Compute any computed dependencies first; this recurses through the // dependency graph to ensure computed properties are never computed with // dependencies that may become invalidated later in this turn @@ -467,7 +562,7 @@ function runComputedEffect(inst, property, changedProps, oldProps, info, hasPath } let computedProp = info.methodInfo; if (inst.__dataHasAccessor && inst.__dataHasAccessor[computedProp]) { - inst._setPendingProperty(computedProp, result, true); + return inst._setPendingProperty(computedProp, result, true); } else { inst[computedProp] = result; } @@ -2369,11 +2464,9 @@ export const PropertyEffects = dedupingMixin(superClass => { throw new Error("Malformed computed expression '" + expression + "'"); } const info = createMethodEffect(this, sig, TYPES.COMPUTE, runComputedEffect, property, dynamicFn); - if (orderedComputed) { - // Effects are normally stored as map of dependency->effect, but for - // ordered computation, we also need tree of computedProp->dependencies - ensureOwnEffectMap(this, COMPUTE_INFO)[property] = info; - } + // Effects are normally stored as map of dependency->effect, but for + // ordered computation, we also need tree of computedProp->dependencies + ensureOwnEffectMap(this, COMPUTE_INFO)[property] = info; } /**