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", () => {
// update shortcut lists
this._updateVisibleIndices();
this.physics.updatePhysicsData();
this.physics.initPhysicsData();
this.body.emitter.emit("_requestRedraw");
// call the dataUpdated event because the only difference between the two is the updating of the indices
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
};
object.options.fixed.x = true;
object.options.fixed.y = true;
object.setFixed(true);
this.drag.selection.push(s);
}
@ -395,8 +394,7 @@ class InteractionHandler {
if (selection && selection.length) {
selection.forEach(function (s) {
// 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.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];
if (this.options.hierarchical.direction === 'UD' || this.options.hierarchical.direction === 'DU') {
node.y = this.options.hierarchical.levelSeparation * level;
node.options.fixed.y = true;
node.setFixed({y: true});
}
else {
node.x = this.options.hierarchical.levelSeparation * level;
node.options.fixed.x = true;
node.setFixed({x: true});
}
if (distribution[level] === undefined) {
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('_physicsUpdate', (properties) => this.physicsUpdateHandler(properties));
// 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.body.emitter.on('destroy', () => {
this.body.emitter.on('destroy', () => {
if (this.physicsWorker) {
this.physicsWorker.terminate();
this.physicsWorker = undefined;
@ -185,7 +183,7 @@ class PhysicsEngine {
this.options.useWorker = false;
this.physicsWorker.terminate();
this.physicsWorker = undefined;
this.updatePhysicsData();
this.initPhysicsData();
}
var options;
if (this.options.solver === 'forceAtlas2Based') {
@ -253,12 +251,18 @@ class PhysicsEngine {
this.physicsUpdateHandler = (properties) => {
if (properties.options.physics !== undefined) {
if (properties.options.physics) {
this.physicsWorker.postMessage({type: 'addElements', data: 'TODO: createNode'});
this.physicsWorker.postMessage({
type: 'addElements',
data: this.createPhysicsNode(properties.id)
});
} else {
this.physicsWorker.postMessage({type: 'removeElements', data: properties.id});
this.physicsWorker.postMessage({type: 'removeElements', data: {
nodes: [properties.id.toString()],
edges: []
}});
}
} else {
this.physicsWorker.postMessage({type: 'updateProperty', data: properties});
this.physicsWorker.postMessage({type: 'updateProperties', data: properties});
}
};
}
@ -315,7 +319,6 @@ class PhysicsEngine {
*/
startSimulation() {
if (this.physicsEnabled === true && this.options.enabled === true) {
this.updateWorkerPositions();
this.stabilized = false;
// when visible, adaptivity is disabled.
@ -404,7 +407,6 @@ class PhysicsEngine {
}
if (this.stabilized === false) {
this.updateWorkerFixed();
// adaptivity means the timestep adapts to the situation, only applicable for stabilization
if (this.adaptiveTimestep === true && this.adaptiveTimestepEnabled === true) {
// this is the factor for increasing the timestep on success.
@ -460,7 +462,7 @@ class PhysicsEngine {
this.timestep = this.options.timestep;
if (this.physicsWorker) {
// console.log('asking working to do a physics iteration');
this.physicsWorker.postMessage({type: 'calculateForces'});
this.physicsWorker.postMessage({type: 'physicsTick'});
} else {
this.calculateForces();
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.
*
* @private
*/
updatePhysicsData() {
initPhysicsData() {
let nodes = this.body.nodes;
let edges = this.body.edges;
@ -490,28 +512,12 @@ class PhysicsEngine {
this.physicsBody.physicsEdgeIndices = [];
if (this.physicsWorker) {
this.physicsWorkerNodes = {};
var physicsWorkerEdges = {};
let physicsWorkerNodes = {};
let physicsWorkerEdges = {};
for (let nodeId in nodes) {
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({
type: 'physicsObjects',
data: {
nodes: this.physicsWorkerNodes,
nodes: physicsWorkerNodes,
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.
*/
@ -786,8 +746,7 @@ class PhysicsEngine {
if (nodes.hasOwnProperty(id)) {
if (nodes[id].x && nodes[id].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) {
if (nodes.hasOwnProperty(id)) {
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.positions = {};
this.timestep = 0.5;
this.toRemove = {
nodes: [],
edges: []
};
}
handleMessage(event) {
var msg = event.data;
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;
case 'updatePositions':
let updatedNode = this.body.nodes[msg.data.id];
@ -45,34 +41,22 @@ class PhysicsWorker {
this.physicsBody.velocities[updatedNode.id] = {x: 0, y: 0};
}
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;
case 'addElements':
console.log('add');
this.addElements(msg.data);
break;
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;
case 'physicsObjects':
this.body.nodes = msg.data.nodes;
this.body.edges = msg.data.edges;
this.updatePhysicsData();
this.initPhysicsData();
break;
case 'options':
this.options = msg.data;
@ -117,12 +101,92 @@ class PhysicsWorker {
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.
*
* @private
*/
updatePhysicsData() {
initPhysicsData() {
this.physicsBody.forces = {};
this.physicsBody.physicsNodeIndices = [];
this.physicsBody.physicsEdgeIndices = [];
@ -132,18 +196,18 @@ class PhysicsWorker {
// get node indices for physics
for (let nodeId in nodes) {
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
for (let edgeId in edges) {
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});
}
/**
* 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.
* @param newY
@ -169,7 +182,7 @@ class Node {
}
// 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
if (this.options.image !== undefined) {
@ -186,11 +199,15 @@ class Node {
if (options.mass !== undefined) {
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) {
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) {
@ -206,13 +223,14 @@ class Node {
* @param parentOptions
* @param newOptions
*/
parseOptions(parentOptions, newOptions, allowDeletion = false, globalOptions = {}) {
static parseOptions(parentOptions, newOptions, allowDeletion = false, globalOptions = {}) {
var fields = [
'color',
'font',
'fixed',
'shadow'
];
var changedPhysicsOptions = {};
util.selectiveNotDeepExtend(fields, parentOptions, newOptions, allowDeletion);
// merge the shadow options into the parent.
@ -233,7 +251,7 @@ class Node {
if (parentOptions.fixed.x !== newOptions.fixed || parentOptions.fixed.y !== newOptions.fixed) {
parentOptions.fixed.x = 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 {
@ -242,14 +260,14 @@ class Node {
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 &&
typeof newOptions.fixed.y === 'boolean' &&
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) {
util.mergeOptions(parentOptions.scaling, newOptions.scaling, 'label', allowDeletion, globalOptions.scaling);
}
return changedPhysicsOptions;
}
updateLabelModule() {

Loading…
Cancel
Save