mirror of
https://github.com/wisplite/BLE-mesh-sim.git
synced 2026-10-30 13:32:21 -05:00
event-driven node updates and grid-based neighbor finding!
This commit is contained in:
@@ -53,6 +53,10 @@
|
|||||||
<input type="checkbox" id="showGrid" autocomplete="off">
|
<input type="checkbox" id="showGrid" autocomplete="off">
|
||||||
<label for="showGrid">Show Grid</label>
|
<label for="showGrid">Show Grid</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="visualizationSettingsToggles">
|
||||||
|
<input type="checkbox" id="showNodeUpdates" autocomplete="off">
|
||||||
|
<label for="showNodeUpdates">Show Node Updates</label>
|
||||||
|
</div>
|
||||||
<div id="visualizationSettingsToggles">
|
<div id="visualizationSettingsToggles">
|
||||||
<input type="checkbox" id="showTTL" autocomplete="off" checked>
|
<input type="checkbox" id="showTTL" autocomplete="off" checked>
|
||||||
<label for="showTTL">Show TTL Colors</label>
|
<label for="showTTL">Show TTL Colors</label>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ var displayWarningWhenDone = false;
|
|||||||
var stillRouting = false;
|
var stillRouting = false;
|
||||||
var markNeighborInterval = null;
|
var markNeighborInterval = null;
|
||||||
var showGrid = false;
|
var showGrid = false;
|
||||||
|
var showUpdates = false;
|
||||||
// Track all active intervals for marking neighbors, and a cancel flag
|
// Track all active intervals for marking neighbors, and a cancel flag
|
||||||
var markNeighborIntervals = new Set();
|
var markNeighborIntervals = new Set();
|
||||||
var cancelMarkNeighborRequested = false;
|
var cancelMarkNeighborRequested = false;
|
||||||
@@ -388,121 +389,145 @@ const markNeighborsAsFailed = (nodeId, from, packetId, visited = new Set()) => {
|
|||||||
|
|
||||||
function recomputeNodeConnections(nodeId) {
|
function recomputeNodeConnections(nodeId) {
|
||||||
const p = network.getPosition(nodeId);
|
const p = network.getPosition(nodeId);
|
||||||
grid.update(nodeId, p.x, p.y);
|
const candidates = grid.getNeighborCandidates(p.x, p.y);
|
||||||
}
|
|
||||||
|
|
||||||
// main event loop
|
// check all nearby nodes against all other nearby nodes
|
||||||
function main() {
|
for (let a of candidates) {
|
||||||
const currentNodes = network.getPositions();
|
if (!nodeTable[a]) {
|
||||||
const currentNodesKeys = Object.keys(currentNodes);
|
|
||||||
for (let node1 of currentNodesKeys) {
|
|
||||||
let neighbors = [];
|
|
||||||
if (!nodeTable[node1]) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for (let node2 of currentNodesKeys) {
|
// in case user drags node too fast, drop far connections
|
||||||
if (!nodeTable[node2]) {
|
for (let connection of nodeTable[a].connections) {
|
||||||
continue;
|
const aPos = network.getPosition(a);
|
||||||
|
const bPos = network.getPosition(connection);
|
||||||
|
const distance = calculateDistance(aPos,bPos);
|
||||||
|
if (distance > CONNECTION_DISTANCE) {
|
||||||
|
dropConnection(a,connection);
|
||||||
}
|
}
|
||||||
if (node1 !== node2) {
|
}
|
||||||
const node1data = currentNodes[node1];
|
if (showUpdates) {
|
||||||
const node2data = currentNodes[node2];
|
console.log('updating')
|
||||||
const distance = calculateDistance(node1data, node2data);
|
let lastTime;
|
||||||
if (distance < CONNECTION_DISTANCE) {
|
let fadePerS = 10;
|
||||||
neighbors.push({'node': node2, 'distance': distanceToRSSI(distance), });
|
let currentFade = 0;
|
||||||
|
let animationId;
|
||||||
|
function animateFlash(time) {
|
||||||
|
if (!lastTime) {
|
||||||
|
lastTime = time;
|
||||||
|
}
|
||||||
|
const dTs = (time - lastTime) / 1000;
|
||||||
|
|
||||||
|
lastTime = time;
|
||||||
|
const diff = fadePerS * dTs;
|
||||||
|
currentFade += diff;
|
||||||
|
nodes.update({id: a, color: {background: smoothColorTransition('#eb4034', '#97c2fc', 0, 5, currentFade)}})
|
||||||
|
|
||||||
|
if (currentFade < 5) {
|
||||||
|
animationId = requestAnimationFrame(animateFlash);
|
||||||
} else {
|
} else {
|
||||||
edges.remove(`${node1}->${node2}`);
|
nodes.update({id: a, color: {background: '#97c2fc'}});
|
||||||
edges.remove(`${node2}->${node1}`);
|
animationId = null;
|
||||||
const node1Idx = nodeTable[node1].connections.indexOf(node2);
|
|
||||||
if (node1Idx !== -1) {
|
|
||||||
nodeTable[node1].connections.splice(node1Idx, 1);
|
|
||||||
}
|
|
||||||
const node2Idx = nodeTable[node2].connections.indexOf(node1);
|
|
||||||
if (node2Idx !== -1) {
|
|
||||||
nodeTable[node2].connections.splice(node2Idx, 1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
animationId = requestAnimationFrame(animateFlash);
|
||||||
}
|
}
|
||||||
}
|
let trueNeighbors = []
|
||||||
const scores = [];
|
for (let b of candidates) {
|
||||||
for (let neighbor of neighbors) {
|
if (a === b) {
|
||||||
if (!nodeTable[neighbor.node]) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (!nodeTable[b]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const aPos = network.getPosition(a);
|
||||||
|
const bPos = network.getPosition(b);
|
||||||
|
const distance = calculateDistance(aPos,bPos);
|
||||||
|
if (distance < CONNECTION_DISTANCE) {
|
||||||
let connectionScore = 0;
|
let connectionScore = 0;
|
||||||
let dropScore = 0;
|
let dropScore = 0;
|
||||||
connectionScore += (100 + neighbor.distance);
|
connectionScore += (100 + distanceToRSSI(distance));
|
||||||
connectionScore += 100 / (nodeTable[neighbor.node].connections.length + 1);
|
connectionScore += 100 / (nodeTable[b].connections.length + 1);
|
||||||
if (nodeTable[neighbor.node].connections.length == 0) {
|
if (nodeTable[b].connections.length == 0) {
|
||||||
connectionScore += 10000;
|
connectionScore += 100;
|
||||||
}
|
}
|
||||||
if (nodeTable[neighbor.node].connections.length >= MAX_CONNECTIONS) {
|
if (nodeTable[a].connections.includes(b) && nodeTable[b].connections.length == 1) {
|
||||||
continue;
|
// assume current node is only connection
|
||||||
|
connectionScore += 1000;
|
||||||
}
|
}
|
||||||
if (nodeTable[node1].connections.includes(neighbor.node)) {
|
if (nodeTable[a].connections.includes(b)) {
|
||||||
dropScore = connectionScore;
|
dropScore = connectionScore * DROP_PENALTY;
|
||||||
}
|
}
|
||||||
scores.push({'node': neighbor.node, 'score': connectionScore, 'dropScore': dropScore});
|
trueNeighbors.push({'nodeId': b, 'distance': distanceToRSSI(distance), 'score': connectionScore, 'dropScore': dropScore});
|
||||||
}
|
|
||||||
scores.sort((a, b) => b.score - a.score);
|
|
||||||
const neighborMap = new Map(neighbors.map(n => [n.node, n]));
|
|
||||||
const top3 = scores.slice(0, 3);
|
|
||||||
for (let score of top3) {
|
|
||||||
if (nodeTable[node1].connections.length >= MAX_CONNECTIONS) {
|
|
||||||
let dropped = false;
|
|
||||||
if (nodeTable[node1].connections.includes(score.node)) {
|
|
||||||
//console.log(`${score.node} is already connected; skipping drop attempt`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (let existingNode of [...nodeTable[node1].connections]) {
|
|
||||||
const neighborInfo = neighborMap.get(existingNode);
|
|
||||||
if (!neighborInfo) {
|
|
||||||
//console.log(`${existingNode} not a neighbor; skipping`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let existingScore = 0;
|
|
||||||
existingScore += (100 + neighborInfo.distance);
|
|
||||||
existingScore += 100 / (nodeTable[existingNode].connections.length + 1);
|
|
||||||
if (nodeTable[existingNode].connections.length == 0) {
|
|
||||||
existingScore += 10000;
|
|
||||||
}
|
|
||||||
if (existingScore * DROP_PENALTY < score.score) {
|
|
||||||
//console.log(`Dropping ${existingNode} from ${node1} for better ${score.node}`);
|
|
||||||
const node1Idx = nodeTable[node1].connections.indexOf(existingNode);
|
|
||||||
if (node1Idx !== -1) {
|
|
||||||
nodeTable[node1].connections.splice(node1Idx, 1);
|
|
||||||
}
|
|
||||||
const node2Idx = nodeTable[existingNode].connections.indexOf(node1);
|
|
||||||
if (node2Idx !== -1) {
|
|
||||||
nodeTable[existingNode].connections.splice(node2Idx, 1);
|
|
||||||
}
|
|
||||||
edges.remove(`${node1}->${existingNode}`);
|
|
||||||
edges.remove(`${existingNode}->${node1}`);
|
|
||||||
dropped = true;
|
|
||||||
break;
|
|
||||||
} else {
|
} else {
|
||||||
//console.log(`Not dropping ${existingNode} from ${node1}; score ${existingScore.toFixed(2)} >= candidate ${score.score.toFixed(2)}`);
|
if (nodeTable[a].connections.includes(b)) {
|
||||||
|
dropConnection(a,b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
trueNeighbors.sort((aS, bS) => bS.score - aS.score);
|
||||||
|
const neighborMap = new Map(trueNeighbors.map(n => [n.nodeId, n]));
|
||||||
|
const topCandidates = trueNeighbors.slice(0, MAX_CONNECTIONS);
|
||||||
|
let toDrop = new Set();
|
||||||
|
let toConnect = new Set();
|
||||||
|
for (let node of topCandidates) {
|
||||||
|
if (nodeTable[node.nodeId].connections.length >= MAX_CONNECTIONS) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
if (nodeTable[a].connections.length >= MAX_CONNECTIONS || (nodeTable[a].connections.length + toConnect.size) >= MAX_CONNECTIONS) {
|
||||||
// still at capacity and nothing was dropped — skip adding this candidate
|
if (nodeTable[a].connections.includes(node.nodeId)) {
|
||||||
if (nodeTable[node1].connections.length >= MAX_CONNECTIONS && !dropped) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
for (let activeConnection of nodeTable[a].connections) {
|
||||||
if (nodeTable[node1].connections.includes(score.node) || nodeTable[score.node].connections.includes(node1)) {
|
if (toDrop.has(activeConnection)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
nodeTable[node1].connections.push(score.node);
|
if (neighborMap.get(activeConnection) && neighborMap.get(activeConnection).dropScore < node.score) {
|
||||||
nodeTable[score.node].connections.push(node1);
|
toDrop.add(activeConnection);
|
||||||
connectNodes(node1, score.node);
|
toConnect.add(node.nodeId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
toConnect.add(node.nodeId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (toDrop.size != 0) {
|
||||||
|
for (let drop of toDrop) {
|
||||||
|
dropConnection(a, drop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (toConnect.size != 0) {
|
||||||
|
for (let connect of toConnect) {
|
||||||
|
// if already connected, ignore
|
||||||
|
if (nodeTable[a].connections.includes(connect)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
nodeTable[a].connections.push(connect);
|
||||||
|
nodeTable[connect].connections.push(a);
|
||||||
|
connectNodes(a,connect);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setTimeout(main, UPDATE_INTERVAL);
|
|
||||||
}
|
}
|
||||||
main();
|
|
||||||
|
|
||||||
function onNodeAdd() {
|
function dropConnection(nodeA, nodeB) {
|
||||||
|
edges.remove(`${nodeA}->${nodeB}`);
|
||||||
|
edges.remove(`${nodeB}->${nodeA}`);
|
||||||
|
const aIdx = nodeTable[nodeA].connections.indexOf(nodeB);
|
||||||
|
const bIdx = nodeTable[nodeB].connections.indexOf(nodeA);
|
||||||
|
if (aIdx !== -1) {
|
||||||
|
nodeTable[nodeA].connections.splice(aIdx, 1);
|
||||||
|
}
|
||||||
|
if (bIdx !== -1) {
|
||||||
|
nodeTable[nodeB].connections.splice(bIdx, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onNodeAdd(nodeId) {
|
||||||
|
// update grid
|
||||||
|
const position = network.getPosition(nodeId);
|
||||||
|
grid.add(nodeId, position.x, position.y);
|
||||||
|
recomputeNodeConnections(nodeId);
|
||||||
|
|
||||||
var nodeLength = Object.keys(nodeTable).length;
|
var nodeLength = Object.keys(nodeTable).length;
|
||||||
if (nodeLength >= 200 && !warningModalShown) {
|
if (nodeLength >= 200 && !warningModalShown) {
|
||||||
warningModalShown = true;
|
warningModalShown = true;
|
||||||
@@ -527,9 +552,13 @@ document.getElementById('addNode').addEventListener('click', function() {
|
|||||||
const nodeId = generateRealisticLabel();
|
const nodeId = generateRealisticLabel();
|
||||||
nodes.add({id: nodeId, label: nodeId});
|
nodes.add({id: nodeId, label: nodeId});
|
||||||
nodeTable[nodeId] = {'connections': [], 'packetCache': [], 'routingTable': {}};
|
nodeTable[nodeId] = {'connections': [], 'packetCache': [], 'routingTable': {}};
|
||||||
onNodeAdd();
|
onNodeAdd(nodeId);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
NETWORK EVENTS
|
||||||
|
*/
|
||||||
|
|
||||||
network.on('click', function(properties) {
|
network.on('click', function(properties) {
|
||||||
if (properties.nodes.length > 0) {
|
if (properties.nodes.length > 0) {
|
||||||
selectedNode = properties.nodes[0];
|
selectedNode = properties.nodes[0];
|
||||||
@@ -540,7 +569,7 @@ network.on('click', function(properties) {
|
|||||||
const nodeId = generateRealisticLabel();
|
const nodeId = generateRealisticLabel();
|
||||||
nodes.add({id: nodeId, label: nodeId, x: properties.pointer.canvas.x, y: properties.pointer.canvas.y});
|
nodes.add({id: nodeId, label: nodeId, x: properties.pointer.canvas.x, y: properties.pointer.canvas.y});
|
||||||
nodeTable[nodeId] = {'connections': [], 'packetCache': [], 'routingTable': {}};
|
nodeTable[nodeId] = {'connections': [], 'packetCache': [], 'routingTable': {}};
|
||||||
onNodeAdd();
|
onNodeAdd(nodeId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -556,7 +585,17 @@ network.on('doubleClick', function(properties) {
|
|||||||
const nodeId = generateRealisticLabel();
|
const nodeId = generateRealisticLabel();
|
||||||
nodes.add({id: nodeId, label: nodeId, x: properties.pointer.canvas.x, y: properties.pointer.canvas.y});
|
nodes.add({id: nodeId, label: nodeId, x: properties.pointer.canvas.x, y: properties.pointer.canvas.y});
|
||||||
nodeTable[nodeId] = {'connections': [], 'packetCache': [], 'routingTable': {}};
|
nodeTable[nodeId] = {'connections': [], 'packetCache': [], 'routingTable': {}};
|
||||||
onNodeAdd();
|
onNodeAdd(nodeId);
|
||||||
|
});
|
||||||
|
|
||||||
|
network.on('dragging', (properties) => {
|
||||||
|
if (properties.nodes.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const nodeId = properties.nodes[0];
|
||||||
|
const pos = network.getPosition(nodeId);
|
||||||
|
grid.update(nodeId, pos.x, pos.y);
|
||||||
|
recomputeNodeConnections(nodeId);
|
||||||
});
|
});
|
||||||
|
|
||||||
network.on('beforeDrawing', function(ctx) {
|
network.on('beforeDrawing', function(ctx) {
|
||||||
@@ -694,7 +733,7 @@ document.getElementById('createRandomGraphConfirm').addEventListener('click', fu
|
|||||||
const nodeId = generateRealisticLabel();
|
const nodeId = generateRealisticLabel();
|
||||||
nodes.add({id: nodeId, label: nodeId, x: Math.random() * 1000, y: Math.random() * 1000});
|
nodes.add({id: nodeId, label: nodeId, x: Math.random() * 1000, y: Math.random() * 1000});
|
||||||
nodeTable[nodeId] = {'connections': [], 'packetCache': [], 'routingTable': {}};
|
nodeTable[nodeId] = {'connections': [], 'packetCache': [], 'routingTable': {}};
|
||||||
onNodeAdd();
|
onNodeAdd(nodeId);
|
||||||
nodeCounter++;
|
nodeCounter++;
|
||||||
if (nodeCounter >= nodeCount) {
|
if (nodeCounter >= nodeCount) {
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
@@ -895,6 +934,11 @@ document.getElementById('showGrid').addEventListener('change', function() {
|
|||||||
network.redraw();
|
network.redraw();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.getElementById('showNodeUpdates').addEventListener('change', function() {
|
||||||
|
console.log(this.checked);
|
||||||
|
showUpdates = this.checked;
|
||||||
|
});
|
||||||
|
|
||||||
function consoleLog(message) {
|
function consoleLog(message) {
|
||||||
const consoleOutput = document.getElementById('consoleOutput');
|
const consoleOutput = document.getElementById('consoleOutput');
|
||||||
consoleOutput.innerHTML = `${message}`;
|
consoleOutput.innerHTML = `${message}`;
|
||||||
|
|||||||
Reference in New Issue
Block a user