diff --git a/js/CompleteMoveOnEdge.js b/js/CompleteMoveOnEdge.js index d1879ec..5583f67 100644 --- a/js/CompleteMoveOnEdge.js +++ b/js/CompleteMoveOnEdge.js @@ -12,6 +12,7 @@ class CompleteNodeOnEdgeEngine { this.nodes = nodes; this.edges = edges; this.dotNodes = dotNodes; + this.movingDots = new Map(); this.forwTable = forwTable; this.onArrival = null; @@ -115,12 +116,16 @@ class CompleteNodeOnEdgeEngine { this.network.on('beforeDrawing', (ctx) => { if (this.edgesMoved) { this.edgesMoved = false; - this.dotNodes.forEach((dotNode) => { + this.movingDots.forEach((dotNode) => { this.fixDotOnEdge(dotNode); }); } }); + this.network.on('afterDrawing', (ctx) => { + this.drawMovingDots(ctx); + }); + this.network.on('dragging', (params) => { if (params.edges.length <= 0) { return; @@ -136,8 +141,13 @@ class CompleteNodeOnEdgeEngine { this.network.on('physicsMoving', (params) => { this.edgesMoved = true; - this.edges.forEach((edge) => { - edge.pointsArr = {}; + // Only clear cache for edges that have moving dots + // instead of all edges - more efficient for large networks + this.movingDots.forEach((dotNode) => { + const edgeItem = this.edges.get(dotNode.edge); + if (edgeItem) { + edgeItem.pointsArr = {}; + } }); }); @@ -157,39 +167,6 @@ class CompleteNodeOnEdgeEngine { } }); - this.dotNodes.on('update', (event, properties, time) => {}); - - this.dotNodes.on('add', (event, properties, time) => { - properties.items.forEach((nodeId) => { - var node = this.dotNodes.get(nodeId); - if (this.traces_table[nodeId] === undefined || this.traces_table[nodeId].length == 0) { - this.traces_table[nodeId] = [node.source]; - } - this.nodes.add(node); - }); - }); - - this.dotNodes.on('remove', (event, properties, time) => { - properties.oldData.forEach((node) => { - this.nodes.remove(node.id); - if (this.ratio_inc >= 0) { - var entry = this.del_log_table[time]; - if (entry === undefined) { - this.del_log_table[time] = [[node, node.source, node.target]]; - } else { - entry.push([node, node.source, node.target]); - } - } else { - var entry = this.add_log_table[time]; - if (entry === undefined) { - this.add_log_table[time] = [[node, node.source, node.target]]; - } else { - entry.push([node, node.source, node.target]); - } - } - }); - }); - this.timer = requestAnimationFrame(this.loop); } @@ -270,30 +247,145 @@ class CompleteNodeOnEdgeEngine { * @param {number} time - The current time of the movement. */ moveDot(current_ratio, time) { - if (this.dotNodes.length <= 0) { + if (this.movingDots.size <= 0) { return; } - this.dotNodes.forEach((dotNode) => { + + const dotsToRemove = []; + + this.movingDots.forEach((dotNode) => { dotNode.ratio += current_ratio; + if (dotNode.ratio > 100) { this.traces_table[dotNode.id].push(dotNode.target); if (typeof this.onArrival === 'function') { try { this.onArrival({ from: dotNode.source, to: dotNode.target, dot: dotNode }); } catch (e) {} - this.dotNodes.remove(dotNode, time); - return; } - } else if (dotNode.ratio < 0) { + dotsToRemove.push(dotNode); + return; + } + + if (dotNode.ratio < 0) { this.traces_table[dotNode.id].pop(); - if (!this.updateDotNode(dotNode, this.traces_table[dotNode.id][this.traces_table[dotNode.id].length - 1], dotNode.source, 100 + dotNode.ratio)) { - this.dotNodes.remove(dotNode, time); + const previousHop = this.traces_table[dotNode.id][this.traces_table[dotNode.id].length - 1]; + if (!this.updateDotNode(dotNode, previousHop, dotNode.source, 100 + dotNode.ratio)) { + dotsToRemove.push(dotNode); return; } } + this.fixDotOnEdge(dotNode); }); - this.network.redraw(); + + dotsToRemove.forEach((dotNode) => { + this.removeMovingDot(dotNode, time); + }); + + // Request canvas redraw without forcing full network re-render + if (this.movingDots.size > 0) { + this.network.canvas.body.emitter.emit("_requestRedraw"); + } + } + + addMovingDot(dotNode, time = 0) { + if (!dotNode || dotNode.id === undefined) { + return; + } + + if (!Array.isArray(this.traces_table[dotNode.id]) || this.traces_table[dotNode.id].length === 0) { + this.traces_table[dotNode.id] = [dotNode.source]; + } + + this.movingDots.set(dotNode.id, dotNode); + + // Skip DataSet update - dots are drawn directly on canvas for better performance + // if (this.dotNodes && typeof this.dotNodes.update === 'function') { + // this.dotNodes.update(dotNode); + // } + } + + removeMovingDot(dotNode, time = this.currentTime) { + if (!dotNode || dotNode.id === undefined) { + return; + } + + this.movingDots.delete(dotNode.id); + + // Skip DataSet removal - dots are drawn directly on canvas for better performance + // if (this.dotNodes && typeof this.dotNodes.remove === 'function') { + // this.dotNodes.remove(dotNode.id); + // } + + const snapshot = { + ...dotNode, + color: dotNode.color ? { ...dotNode.color } : undefined, + font: dotNode.font ? { ...dotNode.font } : undefined, + }; + + if (this.ratio_inc >= 0) { + var removeEntry = this.del_log_table[time]; + if (removeEntry === undefined) { + this.del_log_table[time] = [[snapshot, dotNode.source, dotNode.target]]; + } else { + removeEntry.push([snapshot, dotNode.source, dotNode.target]); + } + } else { + var addEntry = this.add_log_table[time]; + if (addEntry === undefined) { + this.add_log_table[time] = [[snapshot, dotNode.source, dotNode.target]]; + } else { + addEntry.push([snapshot, dotNode.source, dotNode.target]); + } + } + } + + drawMovingDots(ctx) { + if (!ctx || this.movingDots.size === 0) { + return; + } + + this.movingDots.forEach((dotNode) => { + if (dotNode.x === undefined || dotNode.y === undefined) { + return; + } + + const radius = (dotNode.size || 10) / 2; + const fillColor = dotNode.color && dotNode.color.background ? dotNode.color.background : '#97c2fc'; + const strokeColor = dotNode.color && dotNode.color.border ? dotNode.color.border : '#2b7ce9'; + const lineWidth = dotNode.color && dotNode.color.borderWidth ? dotNode.color.borderWidth : 2; + + ctx.save(); + ctx.beginPath(); + ctx.lineWidth = lineWidth; + ctx.strokeStyle = strokeColor; + ctx.fillStyle = fillColor; + + if (dotNode.shape === 'box') { + const half = radius; + ctx.rect(dotNode.x - half, dotNode.y - half, half * 2, half * 2); + ctx.fill(); + ctx.stroke(); + } else { + ctx.arc(dotNode.x, dotNode.y, radius, 0, 2 * Math.PI, false); + ctx.fill(); + ctx.stroke(); + } + ctx.closePath(); + + if (dotNode.label) { + const fontSize = dotNode.font && dotNode.font.size ? dotNode.font.size : 12; + const fontFace = dotNode.font && dotNode.font.face ? dotNode.font.face : 'arial'; + ctx.font = `${fontSize}px ${fontFace}`; + ctx.fillStyle = dotNode.font && dotNode.font.color ? dotNode.font.color : '#000'; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.fillText(dotNode.label, dotNode.x, dotNode.y); + } + + ctx.restore(); + }); } /** @@ -307,7 +399,7 @@ class CompleteNodeOnEdgeEngine { var path = this.getEdgeConnectingNodes(dotNode.source, dotNode.target); if (path[0] === undefined) { // No longer a direct path; remove the moving dot gracefully - this.dotNodes.remove(dotNode); + this.removeMovingDot(dotNode); return; } dotNode.edge = path[0]; @@ -325,7 +417,7 @@ class CompleteNodeOnEdgeEngine { // Edge was removed from DataSet; attempt to retarget var retryPath = this.getEdgeConnectingNodes(dotNode.source, dotNode.target); if (retryPath[0] === undefined) { - this.dotNodes.remove(dotNode); + this.removeMovingDot(dotNode); return; } dotNode.edge = retryPath[0]; @@ -346,10 +438,8 @@ class CompleteNodeOnEdgeEngine { edgePoint = edge.edgeType.getPoint(current_ratio / 100); datasetEdge.pointsArr[current_ratio] = edgePoint; } - if (this.network.body.nodes[dotNode.id]) { - this.network.body.nodes[dotNode.id].x = edgePoint.x; - this.network.body.nodes[dotNode.id].y = edgePoint.y; - } + dotNode.x = edgePoint.x; + dotNode.y = edgePoint.y; } /** @@ -407,15 +497,20 @@ class CompleteNodeOnEdgeEngine { var edge = this.network.body.edges[newDotNode.edge]; var current_ratio = newDotNode.reversed ? 100 - newDotNode.ratio : newDotNode.ratio; - var edgePoint = this.edges.get(edge.id).pointsArr[current_ratio]; + var datasetEdge = this.edges.get(edge.id); + if (!datasetEdge.pointsArr) { + datasetEdge.pointsArr = {}; + } + var edgePoint = datasetEdge.pointsArr[current_ratio]; if (edgePoint === undefined) { edgePoint = edge.edgeType.getPoint(current_ratio / 100); - this.edges.get(edge.id).pointsArr[current_ratio] = edgePoint; + datasetEdge.pointsArr[current_ratio] = edgePoint; } newDotNode.x = edgePoint.x; newDotNode.y = edgePoint.y; - this.dotNodes.add(newDotNode, time); + this.addMovingDot(newDotNode, time); + // Canvas will redraw on next animation frame } /** diff --git a/script.js b/script.js index b653e0b..7449da6 100644 --- a/script.js +++ b/script.js @@ -120,12 +120,18 @@ onEdgeEngine.setArrivalCallback(async ({ from, to, dot }) => { await routePacket(to, dot.id.split('-')[4], dot.id); return; } + // Batch edge updates for better performance if (document.getElementById('showTTL').checked) { - edges.update({id: `${from}->${to}`, color: {color: smoothColorTransition('#eb4034', '#40eb34', 0, TTL, ttl+1), highlight: smoothColorTransition('#eb4034', '#40eb34', 0, TTL, ttl+1)}}); - edges.update({id: `${to}->${from}`, color: {color: smoothColorTransition('#eb4034', '#40eb34', 0, TTL, ttl+1), highlight: smoothColorTransition('#eb4034', '#40eb34', 0, TTL, ttl+1)}}); + const edgeColor = smoothColorTransition('#eb4034', '#40eb34', 0, TTL, ttl+1); + edges.update([ + {id: `${from}->${to}`, color: {color: edgeColor, highlight: edgeColor}}, + {id: `${to}->${from}`, color: {color: edgeColor, highlight: edgeColor}} + ]); } else { - edges.update({id: `${from}->${to}`, color: {color: '#2b7ce9', highlight: '#2b7ce9'}}); - edges.update({id: `${to}->${from}`, color: {color: '#2b7ce9', highlight: '#2b7ce9'}}); + edges.update([ + {id: `${from}->${to}`, color: {color: '#2b7ce9', highlight: '#2b7ce9'}}, + {id: `${to}->${from}`, color: {color: '#2b7ce9', highlight: '#2b7ce9'}} + ]); } if (nodeTable[to].packetCache.has(dot.id.split('-')[0])) { return; @@ -275,14 +281,20 @@ function quickRoute(startNode, packetInfo, fromNode) { nodeTable[nodeId].packetCache.add(packetId); nodeTable[nodeId].routingTable[originNode] = from; + // Batch updates for better performance if (document.getElementById('showTTL').checked) { nodes.update({id: nodeId, color: {background: smoothColorTransition('#eb4034', '#40eb34', 0, TTL, ttl)}}); - edges.update({id: `${from}->${nodeId}`, color: {color: smoothColorTransition('#eb4034', '#40eb34', 0, TTL, ttl+1), highlight: smoothColorTransition('#eb4034', '#40eb34', 0, TTL, ttl)}}); - edges.update({id: `${nodeId}->${from}`, color: {color: smoothColorTransition('#eb4034', '#40eb34', 0, TTL, ttl+1), highlight: smoothColorTransition('#eb4034', '#40eb34', 0, TTL, ttl)}}); + const edgeColor = smoothColorTransition('#eb4034', '#40eb34', 0, TTL, ttl+1); + edges.update([ + {id: `${from}->${nodeId}`, color: {color: edgeColor, highlight: edgeColor}}, + {id: `${nodeId}->${from}`, color: {color: edgeColor, highlight: edgeColor}} + ]); } else { nodes.update({id: nodeId, color: {background: '#97c2fc'}}); - edges.update({id: `${from}->${nodeId}`, color: {color: '#2b7ce9', highlight: '#2b7ce9'}}); - edges.update({id: `${nodeId}->${from}`, color: {color: '#2b7ce9', highlight: '#2b7ce9'}}); + edges.update([ + {id: `${from}->${nodeId}`, color: {color: '#2b7ce9', highlight: '#2b7ce9'}}, + {id: `${nodeId}->${from}`, color: {color: '#2b7ce9', highlight: '#2b7ce9'}} + ]); } const neighborsSnapshot = (nodeTable[nodeId]?.connections?.slice()) || []; @@ -354,12 +366,18 @@ async function routePacket(currentNode, goalNode, packetInfo, first=false) { font: {color: document.getElementById('darkMode').checked ? '#e0e0e0' : '#000'} }; onEdgeEngine.createDotNode(movingNode, currentNode, nextNode); + // Batch edge updates for better performance if (document.getElementById('showTTL').checked) { - edges.update({id: `${currentNode}->${nextNode}`, color: {color: smoothColorTransition('#eb4034', '#40eb34', 0, TTL, ttl+1), highlight: smoothColorTransition('#eb4034', '#40eb34', 0, TTL, ttl)}}); - edges.update({id: `${nextNode}->${currentNode}`, color: {color: smoothColorTransition('#eb4034', '#40eb34', 0, TTL, ttl+1), highlight: smoothColorTransition('#eb4034', '#40eb34', 0, TTL, ttl)}}); + const edgeColor = smoothColorTransition('#eb4034', '#40eb34', 0, TTL, ttl+1); + edges.update([ + {id: `${currentNode}->${nextNode}`, color: {color: edgeColor, highlight: edgeColor}}, + {id: `${nextNode}->${currentNode}`, color: {color: edgeColor, highlight: edgeColor}} + ]); } else { - edges.update({id: `${currentNode}->${nextNode}`, color: {color: 'yellow', highlight: 'yellow'}}); - edges.update({id: `${nextNode}->${currentNode}`, color: {color: 'yellow', highlight: 'yellow'}}); + edges.update([ + {id: `${currentNode}->${nextNode}`, color: {color: 'yellow', highlight: 'yellow'}}, + {id: `${nextNode}->${currentNode}`, color: {color: 'yellow', highlight: 'yellow'}} + ]); } } else { consoleLog(`No route found from ${currentNode} to ${goalNode} via ${nextNode}. Dropping packet.`);