Skip to content

Commit

Permalink
[wip] Add additional topo-sort based algorithm.
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinpschaaf committed Apr 11, 2019
1 parent dae1d73 commit 7cda770
Showing 1 changed file with 107 additions and 14 deletions.
121 changes: 107 additions & 14 deletions lib/mixins/property-effects.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<fxs.length; i++) {
const fx = fxs[i];
if ((fx.info.lastRun !== dedupeId) &&
(!hasPaths || pathMatchesTrigger(prop, fx.trigger))) {
fx.info.lastRun = dedupeId;
insertEffect(fx.info, queue, order);
}
}
}
};

function orderedComputedDeps(inst) {
let ordered = inst.constructor.__orderedComputedDeps;
if (!ordered) {
ordered = new Map();
const effects = inst[TYPES.COMPUTE];
const {counts, next} = edgeCounts(inst);
let curr;
while ((curr = next.pop())) {
ordered.set(curr, ordered.size);
const computedByCurr = effects[curr];
if (computedByCurr) {
computedByCurr.forEach(fx => {
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~';
Expand All @@ -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
Expand All @@ -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;
}
Expand Down Expand Up @@ -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;
}

/**
Expand Down

0 comments on commit 7cda770

Please sign in to comment.