Fixes hanging edges caused by a race condition of sorts

New nodes were arriving before they had been laid out.
This commit is contained in:
Simon Howe
2016-09-14 11:32:38 +02:00
parent 85e63ac786
commit eec54415ec
2 changed files with 72 additions and 37 deletions

View File

@@ -43,9 +43,6 @@ const LABEL_PREFIXES = _.range('A'.charCodeAt(), 'Z'.charCodeAt() + 1)
.map(n => String.fromCharCode(n));
// const randomLetter = () => _.sample(LABEL_PREFIXES);
const deltaAdd = (
name, adjacency = [], shape = 'circle', stack = false, nodeCount = 1,
networks = NETWORKS
@@ -71,7 +68,7 @@ function addMetrics(availableMetrics, node, v) {
]);
return Object.assign({}, node, {
metrics: metrics.map(m => Object.assign({}, m, {max: 100, value: v}))
metrics: metrics.map(m => Object.assign({}, m, {label: 'zing', max: 100, value: v})).toJS()
});
}
@@ -94,14 +91,16 @@ function addAllVariants(dispatch) {
}
function addAllMetricVariants(availableMetrics, dispatch) {
function addAllMetricVariants(availableMetrics) {
const newNodes = _.flattenDeep(METRIC_FILLS.map((v, i) => (
SHAPES.map(s => [addMetrics(availableMetrics, deltaAdd(label(s) + i, [], s), v)])
)));
dispatch(receiveNodesDelta({
add: newNodes
}));
return (dispatch) => {
dispatch(receiveNodesDelta({
add: newNodes
}));
};
}
@@ -177,11 +176,28 @@ class DebugToolbar extends React.Component {
});
}
setLoading(loading) {
this.props.dispatch(setAppState(state => state.set('topologiesLoaded', !loading)));
asyncDispatch(v) {
setTimeout(() => this.props.dispatch(v), 0);
}
addNodes(n, prefix = 'zing') {
setLoading(loading) {
this.asyncDispatch(setAppState(state => state.set('topologiesLoaded', !loading)));
}
updateAdjacencies() {
const ns = this.props.nodes;
const nodeNames = ns.keySeq().toJS();
this.asyncDispatch(receiveNodesDelta({
add: this._addNodes(7),
update: sample(nodeNames).map(n => ({
id: n,
adjacency: sample(nodeNames),
}), nodeNames.length),
remove: this._removeNode(),
}));
}
_addNodes(n, prefix = 'zing') {
const ns = this.props.nodes;
const nodeNames = ns.keySeq().toJS();
const newNodeNames = _.range(ns.size, ns.size + n).map(i => (
@@ -189,26 +205,34 @@ class DebugToolbar extends React.Component {
`${prefix}${i}`
));
const allNodes = _(nodeNames).concat(newNodeNames).value();
return newNodeNames.map((name) => deltaAdd(
name,
sample(allNodes),
_.sample(SHAPES),
_.sample(STACK_VARIANTS),
_.sample(NODE_COUNTS),
sample(NETWORKS, 10)
));
}
this.props.dispatch(receiveNodesDelta({
add: newNodeNames.map((name) => deltaAdd(
name,
sample(allNodes),
_.sample(SHAPES),
_.sample(STACK_VARIANTS),
_.sample(NODE_COUNTS),
sample(NETWORKS, 10)
))
}));
addNodes(n, prefix = 'zing') {
setTimeout(() => {
this.asyncDispatch(receiveNodesDelta({
add: this._addNodes(n, prefix)
}));
log('added nodes', n);
}, 0);
}
log('added nodes', n);
_removeNode() {
const ns = this.props.nodes;
const nodeNames = ns.keySeq().toJS();
return [nodeNames[_.random(nodeNames.length - 1)]];
}
removeNode() {
const ns = this.props.nodes;
const nodeNames = ns.keySeq().toJS();
this.props.dispatch(receiveNodesDelta({
remove: [nodeNames[_.random(nodeNames.length - 1)]]
this.asyncDispatch(receiveNodesDelta({
remove: this._removeNode()
}));
}
@@ -223,12 +247,13 @@ class DebugToolbar extends React.Component {
<button onClick={() => this.addNodes(10)}>+10</button>
<input type="number" onChange={this.onChange} value={this.state.nodesToAdd} />
<button onClick={() => this.addNodes(this.state.nodesToAdd)}>+</button>
<button onClick={() => addAllVariants(this.props.dispatch)}>Variants</button>
<button onClick={() => addAllMetricVariants(availableCanvasMetrics, this.props.dispatch)}>
<button onClick={() => this.asyncDispatch(addAllVariants)}>Variants</button>
<button onClick={() => this.asyncDispatch(addAllMetricVariants(availableCanvasMetrics))}>
Metric Variants
</button>
<button onClick={() => this.addNodes(1, LOREM)}>Long name</button>
<button onClick={() => this.removeNode()}>Remove node</button>
<button onClick={() => this.updateAdjacencies()}>Update adj.</button>
</div>
<div>

View File

@@ -1,9 +1,13 @@
import debug from 'debug';
import { createSelector, createSelectorCreator, defaultMemoize } from 'reselect';
import { Map as makeMap, is } from 'immutable';
import { Map as makeMap, is, Set } from 'immutable';
import { getAdjacentNodes } from '../utils/topology-utils';
const log = debug('scope:selectors');
//
// "immutable" createSelector
//
@@ -82,15 +86,18 @@ export const dataNodesSelector = createSelector(
);
//
// FIXME: this is a bit of a hack...
//
export const layoutNodesSelector = (_, props) => props.layoutNodes || makeMap();
function mergeDeepIfExists(mapA, mapB) {
function mergeDeepKeyIntersection(mapA, mapB) {
//
// Does a deep merge on any key that exists in the first map
// Does a deep merge on keys that exists in both maps
//
return mapA.map((v, k) => v.mergeDeep(mapB.get(k)));
const commonKeys = Set.fromKeys(mapA).intersect(mapB.keySeq());
return makeMap(commonKeys.map(k => [k, mapA.get(k).mergeDeep(mapB.get(k))]));
}
@@ -98,12 +105,15 @@ const _completeNodesSelector = createSelector(
layoutNodesSelector,
dataNodesSelector,
(layoutNodes, dataNodes) => {
if (layoutNodes.size === 0 || dataNodes.size === 0) {
return makeMap();
//
// There are no guarantees whether this selector will be computed first (when
// node-chart-elements.mapStateToProps is called by store.subscribe before
// nodes-chart.mapStateToProps is called), and component render batching and yadada.
//
if (layoutNodes.size !== dataNodes.size) {
log('Obviously mismatched node data', layoutNodes.size, dataNodes.size);
}
// dataNodes might get updated before layoutNodes when a node is removed from the topo.
return mergeDeepIfExists(dataNodes, layoutNodes);
return mergeDeepKeyIntersection(dataNodes, layoutNodes);
}
);