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">
|
||||
<label for="showGrid">Show Grid</label>
|
||||
</div>
|
||||
<div id="visualizationSettingsToggles">
|
||||
<input type="checkbox" id="showNodeUpdates" autocomplete="off">
|
||||
<label for="showNodeUpdates">Show Node Updates</label>
|
||||
</div>
|
||||
<div id="visualizationSettingsToggles">
|
||||
<input type="checkbox" id="showTTL" autocomplete="off" checked>
|
||||
<label for="showTTL">Show TTL Colors</label>
|
||||
|
||||
@@ -8,6 +8,7 @@ var displayWarningWhenDone = false;
|
||||
var stillRouting = false;
|
||||
var markNeighborInterval = null;
|
||||
var showGrid = false;
|
||||
var showUpdates = false;
|
||||
// Track all active intervals for marking neighbors, and a cancel flag
|
||||
var markNeighborIntervals = new Set();
|
||||
var cancelMarkNeighborRequested = false;
|
||||
@@ -388,121 +389,145 @@ const markNeighborsAsFailed = (nodeId, from, packetId, visited = new Set()) => {
|
||||
|
||||
function recomputeNodeConnections(nodeId) {
|
||||
const p = network.getPosition(nodeId);
|
||||
grid.update(nodeId, p.x, p.y);
|
||||
}
|
||||
const candidates = grid.getNeighborCandidates(p.x, p.y);
|
||||
|
||||
// main event loop
|
||||
function main() {
|
||||
const currentNodes = network.getPositions();
|
||||
const currentNodesKeys = Object.keys(currentNodes);
|
||||
for (let node1 of currentNodesKeys) {
|
||||
let neighbors = [];
|
||||
if (!nodeTable[node1]) {
|
||||
// check all nearby nodes against all other nearby nodes
|
||||
for (let a of candidates) {
|
||||
if (!nodeTable[a]) {
|
||||
continue;
|
||||
}
|
||||
for (let node2 of currentNodesKeys) {
|
||||
if (!nodeTable[node2]) {
|
||||
continue;
|
||||
// in case user drags node too fast, drop far connections
|
||||
for (let connection of nodeTable[a].connections) {
|
||||
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];
|
||||
const node2data = currentNodes[node2];
|
||||
const distance = calculateDistance(node1data, node2data);
|
||||
if (distance < CONNECTION_DISTANCE) {
|
||||
neighbors.push({'node': node2, 'distance': distanceToRSSI(distance), });
|
||||
}
|
||||
if (showUpdates) {
|
||||
console.log('updating')
|
||||
let lastTime;
|
||||
let fadePerS = 10;
|
||||
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 {
|
||||
edges.remove(`${node1}->${node2}`);
|
||||
edges.remove(`${node2}->${node1}`);
|
||||
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);
|
||||
}
|
||||
nodes.update({id: a, color: {background: '#97c2fc'}});
|
||||
animationId = null;
|
||||
}
|
||||
}
|
||||
animationId = requestAnimationFrame(animateFlash);
|
||||
}
|
||||
let trueNeighbors = []
|
||||
for (let b of candidates) {
|
||||
if (a === b) {
|
||||
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 dropScore = 0;
|
||||
connectionScore += (100 + distanceToRSSI(distance));
|
||||
connectionScore += 100 / (nodeTable[b].connections.length + 1);
|
||||
if (nodeTable[b].connections.length == 0) {
|
||||
connectionScore += 100;
|
||||
}
|
||||
if (nodeTable[a].connections.includes(b) && nodeTable[b].connections.length == 1) {
|
||||
// assume current node is only connection
|
||||
connectionScore += 1000;
|
||||
}
|
||||
if (nodeTable[a].connections.includes(b)) {
|
||||
dropScore = connectionScore * DROP_PENALTY;
|
||||
}
|
||||
trueNeighbors.push({'nodeId': b, 'distance': distanceToRSSI(distance), 'score': connectionScore, 'dropScore': dropScore});
|
||||
} else {
|
||||
if (nodeTable[a].connections.includes(b)) {
|
||||
dropConnection(a,b);
|
||||
}
|
||||
}
|
||||
}
|
||||
const scores = [];
|
||||
for (let neighbor of neighbors) {
|
||||
if (!nodeTable[neighbor.node]) {
|
||||
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;
|
||||
}
|
||||
let connectionScore = 0;
|
||||
let dropScore = 0;
|
||||
connectionScore += (100 + neighbor.distance);
|
||||
connectionScore += 100 / (nodeTable[neighbor.node].connections.length + 1);
|
||||
if (nodeTable[neighbor.node].connections.length == 0) {
|
||||
connectionScore += 10000;
|
||||
if (nodeTable[a].connections.length >= MAX_CONNECTIONS || (nodeTable[a].connections.length + toConnect.size) >= MAX_CONNECTIONS) {
|
||||
if (nodeTable[a].connections.includes(node.nodeId)) {
|
||||
continue;
|
||||
}
|
||||
for (let activeConnection of nodeTable[a].connections) {
|
||||
if (toDrop.has(activeConnection)) {
|
||||
continue;
|
||||
}
|
||||
if (neighborMap.get(activeConnection) && neighborMap.get(activeConnection).dropScore < node.score) {
|
||||
toDrop.add(activeConnection);
|
||||
toConnect.add(node.nodeId);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
toConnect.add(node.nodeId);
|
||||
}
|
||||
if (nodeTable[neighbor.node].connections.length >= MAX_CONNECTIONS) {
|
||||
continue;
|
||||
}
|
||||
if (nodeTable[node1].connections.includes(neighbor.node)) {
|
||||
dropScore = connectionScore;
|
||||
}
|
||||
scores.push({'node': neighbor.node, '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 {
|
||||
//console.log(`Not dropping ${existingNode} from ${node1}; score ${existingScore.toFixed(2)} >= candidate ${score.score.toFixed(2)}`);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// still at capacity and nothing was dropped — skip adding this candidate
|
||||
if (nodeTable[node1].connections.length >= MAX_CONNECTIONS && !dropped) {
|
||||
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);
|
||||
}
|
||||
if (nodeTable[node1].connections.includes(score.node) || nodeTable[score.node].connections.includes(node1)) {
|
||||
continue;
|
||||
}
|
||||
nodeTable[node1].connections.push(score.node);
|
||||
nodeTable[score.node].connections.push(node1);
|
||||
connectNodes(node1, score.node);
|
||||
}
|
||||
}
|
||||
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;
|
||||
if (nodeLength >= 200 && !warningModalShown) {
|
||||
warningModalShown = true;
|
||||
@@ -527,9 +552,13 @@ document.getElementById('addNode').addEventListener('click', function() {
|
||||
const nodeId = generateRealisticLabel();
|
||||
nodes.add({id: nodeId, label: nodeId});
|
||||
nodeTable[nodeId] = {'connections': [], 'packetCache': [], 'routingTable': {}};
|
||||
onNodeAdd();
|
||||
onNodeAdd(nodeId);
|
||||
});
|
||||
|
||||
/*
|
||||
NETWORK EVENTS
|
||||
*/
|
||||
|
||||
network.on('click', function(properties) {
|
||||
if (properties.nodes.length > 0) {
|
||||
selectedNode = properties.nodes[0];
|
||||
@@ -540,7 +569,7 @@ network.on('click', function(properties) {
|
||||
const nodeId = generateRealisticLabel();
|
||||
nodes.add({id: nodeId, label: nodeId, x: properties.pointer.canvas.x, y: properties.pointer.canvas.y});
|
||||
nodeTable[nodeId] = {'connections': [], 'packetCache': [], 'routingTable': {}};
|
||||
onNodeAdd();
|
||||
onNodeAdd(nodeId);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -556,7 +585,17 @@ network.on('doubleClick', function(properties) {
|
||||
const nodeId = generateRealisticLabel();
|
||||
nodes.add({id: nodeId, label: nodeId, x: properties.pointer.canvas.x, y: properties.pointer.canvas.y});
|
||||
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) {
|
||||
@@ -694,7 +733,7 @@ document.getElementById('createRandomGraphConfirm').addEventListener('click', fu
|
||||
const nodeId = generateRealisticLabel();
|
||||
nodes.add({id: nodeId, label: nodeId, x: Math.random() * 1000, y: Math.random() * 1000});
|
||||
nodeTable[nodeId] = {'connections': [], 'packetCache': [], 'routingTable': {}};
|
||||
onNodeAdd();
|
||||
onNodeAdd(nodeId);
|
||||
nodeCounter++;
|
||||
if (nodeCounter >= nodeCount) {
|
||||
clearInterval(interval);
|
||||
@@ -895,6 +934,11 @@ document.getElementById('showGrid').addEventListener('change', function() {
|
||||
network.redraw();
|
||||
});
|
||||
|
||||
document.getElementById('showNodeUpdates').addEventListener('change', function() {
|
||||
console.log(this.checked);
|
||||
showUpdates = this.checked;
|
||||
});
|
||||
|
||||
function consoleLog(message) {
|
||||
const consoleOutput = document.getElementById('consoleOutput');
|
||||
consoleOutput.innerHTML = `${message}`;
|
||||
|
||||
Reference in New Issue
Block a user