Browse Source

switch node changes over to events

webworkersNetwork^2^2
Eric VanDever 9 years ago
parent
commit
ea2a4951e5
6 changed files with 175 additions and 136 deletions
  1. +1
    -1
      lib/network/Network.js
  2. +2
    -4
      lib/network/modules/InteractionHandler.js
  3. +2
    -2
      lib/network/modules/LayoutEngine.js
  4. +43
    -85
      lib/network/modules/PhysicsEngine.js
  5. +101
    -37
      lib/network/modules/PhysicsWorker.js
  6. +26
    -7
      lib/network/modules/components/Node.js

+ 1
- 1
lib/network/Network.js View File

@ -270,7 +270,7 @@ Network.prototype.bindEventListeners = function () {
this.body.emitter.on("_dataChanged", () => { this.body.emitter.on("_dataChanged", () => {
// update shortcut lists // update shortcut lists
this._updateVisibleIndices(); this._updateVisibleIndices();
this.physics.updatePhysicsData();
this.physics.initPhysicsData();
this.body.emitter.emit("_requestRedraw"); this.body.emitter.emit("_requestRedraw");
// call the dataUpdated event because the only difference between the two is the updating of the indices // call the dataUpdated event because the only difference between the two is the updating of the indices
this.body.emitter.emit("_dataUpdated"); this.body.emitter.emit("_dataUpdated");

+ 2
- 4
lib/network/modules/InteractionHandler.js View File

@ -313,8 +313,7 @@ class InteractionHandler {
yFixed: object.options.fixed.y yFixed: object.options.fixed.y
}; };
object.options.fixed.x = true;
object.options.fixed.y = true;
object.setFixed(true);
this.drag.selection.push(s); this.drag.selection.push(s);
} }
@ -395,8 +394,7 @@ class InteractionHandler {
if (selection && selection.length) { if (selection && selection.length) {
selection.forEach(function (s) { selection.forEach(function (s) {
// restore original xFixed and yFixed // restore original xFixed and yFixed
s.node.options.fixed.x = s.xFixed;
s.node.options.fixed.y = s.yFixed;
s.node.setFixed({x: s.xFixed, y: s.yFixed});
}); });
this.selectionHandler._generateClickEvent('dragEnd', event, this.getPointer(event.center)); this.selectionHandler._generateClickEvent('dragEnd', event, this.getPointer(event.center));
this.body.emitter.emit('startSimulation'); this.body.emitter.emit('startSimulation');

+ 2
- 2
lib/network/modules/LayoutEngine.js View File

@ -384,11 +384,11 @@ class LayoutEngine {
let level = this.hierarchicalLevels[nodeId] === undefined ? 0 : this.hierarchicalLevels[nodeId]; let level = this.hierarchicalLevels[nodeId] === undefined ? 0 : this.hierarchicalLevels[nodeId];
if (this.options.hierarchical.direction === 'UD' || this.options.hierarchical.direction === 'DU') { if (this.options.hierarchical.direction === 'UD' || this.options.hierarchical.direction === 'DU') {
node.y = this.options.hierarchical.levelSeparation * level; node.y = this.options.hierarchical.levelSeparation * level;
node.options.fixed.y = true;
node.setFixed({y: true});
} }
else { else {
node.x = this.options.hierarchical.levelSeparation * level; node.x = this.options.hierarchical.levelSeparation * level;
node.options.fixed.x = true;
node.setFixed({x: true});
} }
if (distribution[level] === undefined) { if (distribution[level] === undefined) {
distribution[level] = {amount: 0, nodes: {}, distance: 0}; distribution[level] = {amount: 0, nodes: {}, distance: 0};

+ 43
- 85
lib/network/modules/PhysicsEngine.js View File

@ -120,15 +120,13 @@ class PhysicsEngine {
this.body.emitter.on('_positionUpdate', (properties) => this.positionUpdateHandler(properties)); this.body.emitter.on('_positionUpdate', (properties) => this.positionUpdateHandler(properties));
this.body.emitter.on('_physicsUpdate', (properties) => this.physicsUpdateHandler(properties)); this.body.emitter.on('_physicsUpdate', (properties) => this.physicsUpdateHandler(properties));
// For identifying which nodes to send to worker thread // For identifying which nodes to send to worker thread
this.body.emitter.on('dragStart', (properties) => {this.draggingNodes = properties.nodes;});
this.body.emitter.on('dragEnd', () => {
// need one last update to handle the case where a drag happens
// and the user holds the node clicked at the final position
// for a time prior to releasing
this.updateWorkerPositions();
this.body.emitter.on('dragStart', (properties) => {
this.draggingNodes = properties.nodes;
});
this.body.emitter.on('dragEnd', () => {
this.draggingNodes = []; this.draggingNodes = [];
}); });
this.body.emitter.on('destroy', () => {
this.body.emitter.on('destroy', () => {
if (this.physicsWorker) { if (this.physicsWorker) {
this.physicsWorker.terminate(); this.physicsWorker.terminate();
this.physicsWorker = undefined; this.physicsWorker = undefined;
@ -185,7 +183,7 @@ class PhysicsEngine {
this.options.useWorker = false; this.options.useWorker = false;
this.physicsWorker.terminate(); this.physicsWorker.terminate();
this.physicsWorker = undefined; this.physicsWorker = undefined;
this.updatePhysicsData();
this.initPhysicsData();
} }
var options; var options;
if (this.options.solver === 'forceAtlas2Based') { if (this.options.solver === 'forceAtlas2Based') {
@ -253,12 +251,18 @@ class PhysicsEngine {
this.physicsUpdateHandler = (properties) => { this.physicsUpdateHandler = (properties) => {
if (properties.options.physics !== undefined) { if (properties.options.physics !== undefined) {
if (properties.options.physics) { if (properties.options.physics) {
this.physicsWorker.postMessage({type: 'addElements', data: 'TODO: createNode'});
this.physicsWorker.postMessage({
type: 'addElements',
data: this.createPhysicsNode(properties.id)
});
} else { } else {
this.physicsWorker.postMessage({type: 'removeElements', data: properties.id});
this.physicsWorker.postMessage({type: 'removeElements', data: {
nodes: [properties.id.toString()],
edges: []
}});
} }
} else { } else {
this.physicsWorker.postMessage({type: 'updateProperty', data: properties});
this.physicsWorker.postMessage({type: 'updateProperties', data: properties});
} }
}; };
} }
@ -315,7 +319,6 @@ class PhysicsEngine {
*/ */
startSimulation() { startSimulation() {
if (this.physicsEnabled === true && this.options.enabled === true) { if (this.physicsEnabled === true && this.options.enabled === true) {
this.updateWorkerPositions();
this.stabilized = false; this.stabilized = false;
// when visible, adaptivity is disabled. // when visible, adaptivity is disabled.
@ -404,7 +407,6 @@ class PhysicsEngine {
} }
if (this.stabilized === false) { if (this.stabilized === false) {
this.updateWorkerFixed();
// adaptivity means the timestep adapts to the situation, only applicable for stabilization // adaptivity means the timestep adapts to the situation, only applicable for stabilization
if (this.adaptiveTimestep === true && this.adaptiveTimestepEnabled === true) { if (this.adaptiveTimestep === true && this.adaptiveTimestepEnabled === true) {
// this is the factor for increasing the timestep on success. // this is the factor for increasing the timestep on success.
@ -460,7 +462,7 @@ class PhysicsEngine {
this.timestep = this.options.timestep; this.timestep = this.options.timestep;
if (this.physicsWorker) { if (this.physicsWorker) {
// console.log('asking working to do a physics iteration'); // console.log('asking working to do a physics iteration');
this.physicsWorker.postMessage({type: 'calculateForces'});
this.physicsWorker.postMessage({type: 'physicsTick'});
} else { } else {
this.calculateForces(); this.calculateForces();
this.moveNodes(); this.moveNodes();
@ -476,12 +478,32 @@ class PhysicsEngine {
} }
} }
createPhysicsNode(nodeId) {
let node = this.body.nodes[nodeId];
if (node && node.options.physics === true) {
// for updating fixed later
this.physicsBody.physicsNodeIndices.push(nodeId);
return {
id: node.id.toString(),
x: node.x,
y: node.y,
options: {
fixed: {
x: node.options.fixed.x,
y: node.options.fixed.y
},
mass: node.options.mass
}
}
}
}
/** /**
* Nodes and edges can have the physics toggles on or off. A collection of indices is created here so we can skip the check all the time. * Nodes and edges can have the physics toggles on or off. A collection of indices is created here so we can skip the check all the time.
* *
* @private * @private
*/ */
updatePhysicsData() {
initPhysicsData() {
let nodes = this.body.nodes; let nodes = this.body.nodes;
let edges = this.body.edges; let edges = this.body.edges;
@ -490,28 +512,12 @@ class PhysicsEngine {
this.physicsBody.physicsEdgeIndices = []; this.physicsBody.physicsEdgeIndices = [];
if (this.physicsWorker) { if (this.physicsWorker) {
this.physicsWorkerNodes = {};
var physicsWorkerEdges = {};
let physicsWorkerNodes = {};
let physicsWorkerEdges = {};
for (let nodeId in nodes) { for (let nodeId in nodes) {
if (nodes.hasOwnProperty(nodeId)) { if (nodes.hasOwnProperty(nodeId)) {
let node = nodes[nodeId];
if (node.options.physics === true) {
// for updating fixed later
this.physicsBody.physicsNodeIndices.push(nodeId);
this.physicsWorkerNodes[nodeId] = {
id: node.id,
x: node.x,
y: node.y,
options: {
fixed: {
x: node.options.fixed.x,
y: node.options.fixed.y
},
mass: node.options.mass
}
}
}
physicsWorkerNodes[nodeId] = this.createPhysicsNode(nodeId);
} }
} }
@ -549,7 +555,7 @@ class PhysicsEngine {
this.physicsWorker.postMessage({ this.physicsWorker.postMessage({
type: 'physicsObjects', type: 'physicsObjects',
data: { data: {
nodes: this.physicsWorkerNodes,
nodes: physicsWorkerNodes,
edges: physicsWorkerEdges edges: physicsWorkerEdges
} }
}); });
@ -592,52 +598,6 @@ class PhysicsEngine {
} }
} }
updateWorkerPositions() {
if (this.physicsWorker) {
for (let i = 0; i < this.draggingNodes.length; i++) {
let nodeId = this.draggingNodes[i];
let node = this.body.nodes[nodeId];
this.physicsWorker.postMessage({
type: 'updatePositions',
data: {
id: nodeId,
x: node.x,
y: node.y
}
});
}
}
}
updateWorkerFixed() {
if (this.physicsWorker) {
for (let i = 0; i < this.physicsBody.physicsNodeIndices.length; i++) {
let nodeId = this.physicsBody.physicsNodeIndices[i];
let physicsNode = this.physicsWorkerNodes[nodeId];
let node = this.body.nodes[nodeId];
if (physicsNode.options.fixed.x !== node.options.fixed.x ||
physicsNode.options.fixed.y !== node.options.fixed.y)
{
let fixed = {
x: node.options.fixed.x,
y: node.options.fixed.y
};
physicsNode.options.fixed.x = fixed.x;
physicsNode.options.fixed.y = fixed.y;
this.physicsWorker.postMessage({
type: 'updateFixed',
data: {
id: nodeId,
x: node.x,
y: node.y,
fixed: fixed
}
});
}
}
}
}
/** /**
* Revert the simulation one step. This is done so after stabilization, every new start of the simulation will also say stabilized. * Revert the simulation one step. This is done so after stabilization, every new start of the simulation will also say stabilized.
*/ */
@ -786,8 +746,7 @@ class PhysicsEngine {
if (nodes.hasOwnProperty(id)) { if (nodes.hasOwnProperty(id)) {
if (nodes[id].x && nodes[id].y) { if (nodes[id].x && nodes[id].y) {
this.freezeCache[id] = {x:nodes[id].options.fixed.x,y:nodes[id].options.fixed.y}; this.freezeCache[id] = {x:nodes[id].options.fixed.x,y:nodes[id].options.fixed.y};
nodes[id].options.fixed.x = true;
nodes[id].options.fixed.y = true;
nodes[id].setFixed(true);
} }
} }
} }
@ -803,8 +762,7 @@ class PhysicsEngine {
for (var id in nodes) { for (var id in nodes) {
if (nodes.hasOwnProperty(id)) { if (nodes.hasOwnProperty(id)) {
if (this.freezeCache[id] !== undefined) { if (this.freezeCache[id] !== undefined) {
nodes[id].options.fixed.x = this.freezeCache[id].x;
nodes[id].options.fixed.y = this.freezeCache[id].y;
nodes[id].setFixed({x: this.freezeCache[id].x, y: this.freezeCache[id].y});
} }
} }
} }

+ 101
- 37
lib/network/modules/PhysicsWorker.js View File

@ -20,21 +20,17 @@ class PhysicsWorker {
this.previousStates = {}; this.previousStates = {};
this.positions = {}; this.positions = {};
this.timestep = 0.5; this.timestep = 0.5;
this.toRemove = {
nodes: [],
edges: []
};
} }
handleMessage(event) { handleMessage(event) {
var msg = event.data; var msg = event.data;
switch (msg.type) { switch (msg.type) {
case 'calculateForces':
this.calculateForces();
this.moveNodes();
this.postMessage({
type: 'positions',
data: {
positions: this.positions,
stabilized: this.stabilized
}
});
case 'physicsTick':
this.physicsTick();
break; break;
case 'updatePositions': case 'updatePositions':
let updatedNode = this.body.nodes[msg.data.id]; let updatedNode = this.body.nodes[msg.data.id];
@ -45,34 +41,22 @@ class PhysicsWorker {
this.physicsBody.velocities[updatedNode.id] = {x: 0, y: 0}; this.physicsBody.velocities[updatedNode.id] = {x: 0, y: 0};
} }
break; break;
case 'updateProperty':
let optionsNode = this.body.nodes[msg.data.id];
if (optionsNode) {
let opts = msg.data.options;
// TODO see if we need a position with fixed
if (opts.fixed) {
if (opts.fixed.x !== undefined) {
optionsNode.options.fixed.x = opts.fixed.x;
}
if (opts.fixed.y !== undefined) {
optionsNode.options.fixed.y = opts.fixed.y;
}
}
if (opts.mass !== undefined) {
optionsNode.options.mass = opts.mass;
}
}
case 'updateProperties':
this.updateProperties(msg.data);
break; break;
case 'addElements': case 'addElements':
console.log('add');
this.addElements(msg.data);
break; break;
case 'removeElements': case 'removeElements':
console.log('remove');
// schedule removal of elements on the next physicsTick
// avoids having to defensively check every node read in each physics implementation
this.toRemove.nodes.push.apply(this.toRemove.nodes, msg.data.nodes);
this.toRemove.edges.push.apply(this.toRemove.edges, msg.data.edges);
break; break;
case 'physicsObjects': case 'physicsObjects':
this.body.nodes = msg.data.nodes; this.body.nodes = msg.data.nodes;
this.body.edges = msg.data.edges; this.body.edges = msg.data.edges;
this.updatePhysicsData();
this.initPhysicsData();
break; break;
case 'options': case 'options':
this.options = msg.data; this.options = msg.data;
@ -117,12 +101,92 @@ class PhysicsWorker {
this.modelOptions = options; this.modelOptions = options;
} }
physicsTick() {
this.processRemovals();
this.calculateForces();
this.moveNodes();
for (let i = 0; i < this.toRemove.nodes.length; i++) {
delete this.positions[this.toRemove.nodes[i]];
}
this.postMessage({
type: 'positions',
data: {
positions: this.positions,
stabilized: this.stabilized
}
});
}
updateProperties(data) {
let optionsNode = this.body.nodes[data.id];
if (optionsNode) {
let opts = data.options;
if (opts.fixed) {
if (opts.fixed.x !== undefined) {
optionsNode.options.fixed.x = opts.fixed.x;
}
if (opts.fixed.y !== undefined) {
optionsNode.options.fixed.y = opts.fixed.y;
}
}
if (opts.mass !== undefined) {
optionsNode.options.mass = opts.mass;
}
} else {
console.log('sending property to unknown node');
}
}
addElements(data) {
// TODO expand to handle multiple and edges
let newNode = data;
let nodeId = newNode.id;
this.body.nodes[nodeId] = newNode;
this.positions[nodeId] = {
x: newNode.x,
y: newNode.y
};
this.physicsBody.forces[nodeId] = {x: 0, y: 0};
this.physicsBody.velocities[nodeId] = {x: 0, y: 0};
if (this.physicsBody.physicsNodeIndices.indexOf(nodeId) === -1) {
this.physicsBody.physicsNodeIndices.push(nodeId);
}
console.log('added node', nodeId);
}
processRemovals() {
while (this.toRemove.nodes.length > 0) {
let nodeId = this.toRemove.nodes.pop();
// TODO any optimization here?
let index = this.physicsBody.physicsNodeIndices.indexOf(nodeId);
if (index === -1 && typeof nodeId === 'number') {
index = this.physicsBody.physicsNodeIndices.indexOf(nodeId.toString());
}
if (index > -1) {
this.physicsBody.physicsNodeIndices.splice(index,1);
}
delete this.physicsBody.forces[nodeId];
delete this.physicsBody.velocities[nodeId];
delete this.positions[nodeId];
delete this.body.nodes[nodeId];
console.log('removed node', nodeId);
}
while (this.toRemove.edges.length > 0) {
let edgeId = this.toRemove.edges.pop();
let index = this.physicsBody.physicsEdgeIndices.indexOf(edgeId);
if (index > -1) {
this.physicsBody.physicsEdgeIndices.splice(index,1);
}
delete this.body.edges[edgeId];
}
}
/** /**
* Nodes and edges can have the physics toggles on or off. A collection of indices is created here so we can skip the check all the time. * Nodes and edges can have the physics toggles on or off. A collection of indices is created here so we can skip the check all the time.
* *
* @private * @private
*/ */
updatePhysicsData() {
initPhysicsData() {
this.physicsBody.forces = {}; this.physicsBody.forces = {};
this.physicsBody.physicsNodeIndices = []; this.physicsBody.physicsNodeIndices = [];
this.physicsBody.physicsEdgeIndices = []; this.physicsBody.physicsEdgeIndices = [];
@ -132,18 +196,18 @@ class PhysicsWorker {
// get node indices for physics // get node indices for physics
for (let nodeId in nodes) { for (let nodeId in nodes) {
if (nodes.hasOwnProperty(nodeId)) { if (nodes.hasOwnProperty(nodeId)) {
this.physicsBody.physicsNodeIndices.push(nodeId);
this.positions[nodeId] = {
x: nodes[nodeId].x,
y: nodes[nodeId].y
}
this.physicsBody.physicsNodeIndices.push(nodeId);
this.positions[nodeId] = {
x: nodes[nodeId].x,
y: nodes[nodeId].y
}
} }
} }
// get edge indices for physics // get edge indices for physics
for (let edgeId in edges) { for (let edgeId in edges) {
if (edges.hasOwnProperty(edgeId)) { if (edges.hasOwnProperty(edgeId)) {
this.physicsBody.physicsEdgeIndices.push(edgeId);
this.physicsBody.physicsEdgeIndices.push(edgeId);
} }
} }

+ 26
- 7
lib/network/modules/components/Node.js View File

@ -97,6 +97,19 @@ class Node {
this.body.emitter.emit('_positionUpdate', {id: this.id, x: this._x, y: this._y}); this.body.emitter.emit('_positionUpdate', {id: this.id, x: this._x, y: this._y});
} }
/**
* Emitting version
*
* @param newFixed
*/
setFixed(newFixed) {
// TODO split out fixed portion?
let physOpts = Node.parseOptions(this.options, {fixed: newFixed});
if (Object.keys(physOpts).length > 0) {
this.body.emitter.emit('_physicsUpdate', {id: this.id, options: physOpts});
}
}
/** /**
* Non emitting version for use by physics engine so we don't create infinite loops. * Non emitting version for use by physics engine so we don't create infinite loops.
* @param newY * @param newY
@ -169,7 +182,7 @@ class Node {
} }
// this transforms all shorthands into fully defined options // this transforms all shorthands into fully defined options
this.parseOptions(this.options, options, true, this.globalOptions);
let physOpts = Node.parseOptions(this.options, options, true, this.globalOptions);
// load the images // load the images
if (this.options.image !== undefined) { if (this.options.image !== undefined) {
@ -186,11 +199,15 @@ class Node {
if (options.mass !== undefined) { if (options.mass !== undefined) {
this.options.mass = options.mass; this.options.mass = options.mass;
this.body.emitter.emit('_physicsUpdate', {id: this.id, options: {mass: options.mass}});
physOpts.mass = options.mass;
} }
if (options.physics !== undefined) { if (options.physics !== undefined) {
this.options.physics = options.physics; this.options.physics = options.physics;
this.body.emitter.emit('_physicsUpdate', {id: this.id, options: {physics: options.physics}});
physOpts.physics = options.physics;
}
if (Object.keys(physOpts).length > 0) {
this.body.emitter.emit('_physicsUpdate', {id: this.id, options: physOpts});
} }
if (options.hidden !== undefined) { if (options.hidden !== undefined) {
@ -206,13 +223,14 @@ class Node {
* @param parentOptions * @param parentOptions
* @param newOptions * @param newOptions
*/ */
parseOptions(parentOptions, newOptions, allowDeletion = false, globalOptions = {}) {
static parseOptions(parentOptions, newOptions, allowDeletion = false, globalOptions = {}) {
var fields = [ var fields = [
'color', 'color',
'font', 'font',
'fixed', 'fixed',
'shadow' 'shadow'
]; ];
var changedPhysicsOptions = {};
util.selectiveNotDeepExtend(fields, parentOptions, newOptions, allowDeletion); util.selectiveNotDeepExtend(fields, parentOptions, newOptions, allowDeletion);
// merge the shadow options into the parent. // merge the shadow options into the parent.
@ -233,7 +251,7 @@ class Node {
if (parentOptions.fixed.x !== newOptions.fixed || parentOptions.fixed.y !== newOptions.fixed) { if (parentOptions.fixed.x !== newOptions.fixed || parentOptions.fixed.y !== newOptions.fixed) {
parentOptions.fixed.x = newOptions.fixed; parentOptions.fixed.x = newOptions.fixed;
parentOptions.fixed.y = newOptions.fixed; parentOptions.fixed.y = newOptions.fixed;
this.body.emitter.emit('_physicsUpdate', {id: this.id, options: {fixed: {x: newOptions.fixed, y: newOptions.fixed}}});
changedPhysicsOptions.fixed = {x: newOptions.fixed, y: newOptions.fixed};
} }
} }
else { else {
@ -242,14 +260,14 @@ class Node {
parentOptions.fixed.x !== newOptions.fixed.x) parentOptions.fixed.x !== newOptions.fixed.x)
{ {
parentOptions.fixed.x = newOptions.fixed.x; parentOptions.fixed.x = newOptions.fixed.x;
this.body.emitter.emit('_physicsUpdate', {id: this.id, options: {fixed: {x: newOptions.fixed.x}}});
util.deepExtend(changedPhysicsOptions, {fixed: {x: newOptions.fixed.x}});
} }
if (newOptions.fixed.y !== undefined && if (newOptions.fixed.y !== undefined &&
typeof newOptions.fixed.y === 'boolean' && typeof newOptions.fixed.y === 'boolean' &&
parentOptions.fixed.y !== newOptions.fixed.y) parentOptions.fixed.y !== newOptions.fixed.y)
{ {
parentOptions.fixed.y = newOptions.fixed.y; parentOptions.fixed.y = newOptions.fixed.y;
this.body.emitter.emit('_physicsUpdate', {id: this.id, options: {fixed: {y: newOptions.fixed.y}}});
util.deepExtend(changedPhysicsOptions, {fixed: {y: newOptions.fixed.y}});
} }
} }
} }
@ -266,6 +284,7 @@ class Node {
if (newOptions.scaling !== undefined) { if (newOptions.scaling !== undefined) {
util.mergeOptions(parentOptions.scaling, newOptions.scaling, 'label', allowDeletion, globalOptions.scaling); util.mergeOptions(parentOptions.scaling, newOptions.scaling, 'label', allowDeletion, globalOptions.scaling);
} }
return changedPhysicsOptions;
} }
updateLabelModule() { updateLabelModule() {

Loading…
Cancel
Save