diff --git a/index.html b/index.html index e82ab1e..3a7d6c4 100644 --- a/index.html +++ b/index.html @@ -43,30 +43,44 @@ +
+ Visualization Settings +
+
+ + +
+
+ + +
+
+ + +
+
+

- - + +
+ +
- - + + - -
-
- - - - - - + +
@@ -78,7 +92,10 @@
- +
+ + +
@@ -88,6 +105,12 @@
+
+
+

+ +
+
diff --git a/script.js b/script.js index d05c839..8b3ca08 100644 --- a/script.js +++ b/script.js @@ -5,6 +5,8 @@ var TTL = 5; var MAX_OUTGOING_CONNECTIONS = 3; var MAX_INCOMING_CONNECTIONS = 3; var warningModalShown = false; +var displayWarningWhenDone = false; +var stillRouting = false; function smoothColorTransition(color1, color2, min, max, current) { // Clamp current between min and max @@ -101,6 +103,10 @@ onEdgeEngine.createEdgesTable(); onEdgeEngine.initMovement(); onEdgeEngine.setArrivalCallback(({ from, to, dot }) => { var ttl = parseInt(dot.id.split('-')[2]); + if (stillRouting) { + routePacket(to, dot.id.split('-')[3], dot.id); + return; + } 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)}}); } else { @@ -184,6 +190,97 @@ function quickPacket(startNode, packetInfo, fromNode) { } } +// This is a modified version of quickPacket that contains some extra logic to save routing tables. +// It also disables the TTL colors for unreachable nodes to save performance. +function quickRoute(startNode, packetInfo, fromNode) { + + const packetId = packetInfo.split('-')[0]; + const ttlStart = parseInt(packetInfo.split('-')[2]); + const originNode = packetInfo.split('-')[3]; + + const queue = [{ nodeId: startNode, ttl: ttlStart, from: fromNode }]; + + while (queue.length > 0) { + const { nodeId, ttl, from } = queue.shift(); + + if (ttl < 0) { + displayWarningWhenDone = true; + continue; + } + if (nodeTable[nodeId]?.packetCache?.includes(packetId)) { + continue; + } + + nodeTable[nodeId].packetCache.push(packetId); + nodeTable[nodeId].routingTable[originNode] = from; + + 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)}}); + } else { + nodes.update({id: nodeId, color: {background: '#97c2fc'}}); + edges.update({id: `${from}->${nodeId}`, color: {color: '#2b7ce9', highlight: '#2b7ce9'}}); + } + + const neighborsSnapshot = (nodeTable[nodeId]?.outgoing?.slice()) || []; + + for (let neighbor of neighborsSnapshot) { + if (neighbor === originNode || neighbor === from) { + continue; + } + queue.push({ nodeId: neighbor, ttl: ttl - 1, from: nodeId }); + } + } +} + +function routePacket(currentNode, goalNode, packetInfo, first=false) { + stillRouting = true; + const packetId = packetInfo.split('-')[0]; + var packetShift = generateRealisticLabel(); + let ttl = parseInt(packetInfo.split('-')[2]); + nodes.update({id: currentNode, color: {background: '#97c2fc'}}); + nodes.update({id: goalNode, color: {background: '#ff00d9'}}); + if (ttl <= 0) { + consoleLog(`Packet ${packetId} TTL expired. Dropping packet.`); + stillRouting = false; + return; + } + if (currentNode === goalNode) { + consoleLog(`Packet ${packetId} reached ${goalNode}.`); + stillRouting = false; + return; + } + if (document.getElementById('showTTL').checked) { + nodes.update({id: currentNode, color: {background: smoothColorTransition('#eb4034', '#40eb34', 0, TTL, ttl)}}); + } else { + if (first) { + nodes.update({id: currentNode, color: {background: '#ff00d9'}}); + } else { + nodes.update({id: currentNode, color: {background: 'yellow'}}); + } + } + ttl--; + const nextNode = nodeTable[currentNode].routingTable[goalNode]; + if (nextNode) { + var movingNode = { + id: `${packetId}-${packetShift}-${ttl}-${goalNode}`, + label: packetId + `-${ttl}`, + shape: 'dot', + size: 10, + color: { + background: 'white', + border: 'black' + }, + font: {color: document.getElementById('darkMode').checked ? '#e0e0e0' : '#000'} + }; + onEdgeEngine.createDotNode(movingNode, currentNode, nextNode); + } else { + consoleLog(`No route found from ${currentNode} to ${goalNode}. Dropping packet.`); + console.log(nodeTable[currentNode].routingTable); + stillRouting = false; + } +} + const markNeighborsAsFailed = (nodeId, from, packetId, visited = new Set()) => { if (visited.has(nodeId)) { return; @@ -492,7 +589,7 @@ function onNodeAdd() { document.getElementById('addNode').addEventListener('click', function() { const nodeId = generateRealisticLabel(); nodes.add({id: nodeId, label: nodeId}); - nodeTable[nodeId] = {'incoming': [], 'outgoing': [], 'packetCache': []}; + nodeTable[nodeId] = {'incoming': [], 'outgoing': [], 'packetCache': [], 'routingTable': {}}; onNodeAdd(); }); @@ -503,7 +600,7 @@ network.on('click', function(properties) { if (document.getElementById('quickPlace').checked) { const nodeId = generateRealisticLabel(); nodes.add({id: nodeId, label: nodeId, x: properties.pointer.canvas.x, y: properties.pointer.canvas.y}); - nodeTable[nodeId] = {'incoming': [], 'outgoing': [], 'packetCache': []}; + nodeTable[nodeId] = {'incoming': [], 'outgoing': [], 'packetCache': [], 'routingTable': {}}; onNodeAdd(); } }); @@ -513,11 +610,12 @@ network.on('doubleClick', function(properties) { return; } if (properties.nodes.length > 0) { + navigator.clipboard.writeText(properties.nodes[0]); return; } const nodeId = generateRealisticLabel(); nodes.add({id: nodeId, label: nodeId, x: properties.pointer.canvas.x, y: properties.pointer.canvas.y}); - nodeTable[nodeId] = {'incoming': [], 'outgoing': [], 'packetCache': []}; + nodeTable[nodeId] = {'incoming': [], 'outgoing': [], 'packetCache': [], 'routingTable': {}}; onNodeAdd(); }); @@ -607,8 +705,8 @@ document.getElementById('createRandomGraphConfirm').addEventListener('click', fu let nodeCounter = 0; const interval = setInterval(() => { const nodeId = generateRealisticLabel(); - nodes.add({id: nodeId, label: nodeId, x: Math.random() * 100, y: Math.random() * 100}); - nodeTable[nodeId] = {'incoming': [], 'outgoing': [], 'packetCache': []}; + nodes.add({id: nodeId, label: nodeId, x: Math.random() * 1000, y: Math.random() * 1000}); + nodeTable[nodeId] = {'incoming': [], 'outgoing': [], 'packetCache': [], 'routingTable': {}}; onNodeAdd(); nodeCounter++; if (nodeCounter >= nodeCount) { @@ -646,6 +744,36 @@ document.getElementById('quickPacket').addEventListener('click', async function( quickPacket(selectedNode, `${packetId}-${packetShift}-${ttl}-${selectedNode}`, selectedNode); }); +document.getElementById('createRoutingTables').addEventListener('click', function() { + let nodeCounter = 0; + document.getElementById('routingProgressContainer').style.display = 'flex'; + document.getElementById('routingProgress').value = 0; + document.getElementById('routingProgress').max = Object.keys(nodeTable).length; + const interval = setInterval(() => { + const packetId = generateRealisticLabel(); + var packetShift = generateRealisticLabel(); + let ttl = TTL; + const nodeId = Object.keys(nodeTable)[nodeCounter]; + quickRoute(nodeId, `${packetId}-${packetShift}-${ttl}-${nodeId}`, nodeId); + nodeCounter++; + document.getElementById('routingProgress').value = nodeCounter; + if (nodeCounter >= Object.keys(nodeTable).length) { + clearInterval(interval); + document.getElementById('routingProgressContainer').style.display = 'none'; + document.getElementById('routingProgress').value = 0; + if (displayWarningWhenDone) { + displayWarningWhenDone = false; + document.getElementById('warningModal').style.display = 'block'; + document.getElementById('warningModalMessage').innerHTML = 'TTL hit zero while routing, some routes may be missing. If you experience issues, try increasing the TTL and routing again.'; + document.getElementById('warningModalCustomContent').innerHTML = ''; + } else { + document.getElementById('successModal').style.display = 'block'; + document.getElementById('successModalMessage').innerHTML = 'Routing tables created successfully.'; + } + } + }, 10); +}); + window.onload = function() { if (localStorage.getItem('darkMode') === 'true') { document.getElementById('darkMode').checked = true; @@ -654,4 +782,115 @@ window.onload = function() { document.getElementById('darkMode').checked = false; document.getElementById('styleSheet').href = 'style.css'; } + + let consoleContainer = document.createElement('div'); + consoleContainer.id = 'consoleContainer'; + document.getElementById('simContainer').appendChild(consoleContainer); + + let openConsoleButton = document.createElement('button'); + openConsoleButton.innerHTML = ''; + openConsoleButton.id = 'openConsole'; + openConsoleButton.addEventListener('click', function() { + const consoleElement = document.getElementById('consoleContainer'); + const currentDisplay = window.getComputedStyle(consoleElement).display; + consoleElement.style.display = currentDisplay === 'none' ? 'flex' : 'none'; + }); + + let consoleInputContainer = document.createElement('div'); + consoleInputContainer.id = 'consoleInputContainer'; + consoleContainer.appendChild(consoleInputContainer); + + let consoleCarrot = document.createElement('pre'); + consoleCarrot.innerHTML = '>'; + consoleInputContainer.appendChild(consoleCarrot); + + let consoleInput = document.createElement('input'); + consoleInput.id = 'consoleInput'; + consoleInput.placeholder = 'Enter command'; + consoleInputContainer.appendChild(consoleInput); + let consoleInputHistory = []; + let consoleInputHistoryIndex = 0; + // Handle Enter key for command execution + consoleInput.addEventListener('keypress', function(event) { + if (event.key === 'Enter') { + const command = consoleInput.value.trim(); + if (command) { // Only add non-empty commands to history + consoleInputHistory.push(command); + } + consoleInputHistoryIndex = consoleInputHistory.length; + + const rootCommand = command.split(' ')[0]; + const args = command.split(' ')[1]; + + switch (rootCommand) { + case 'help': + switch (args) { + case 'routePacket': + consoleLog('Sends a packet from one specific node to another. Usage: routePacket [startNode]->[endNode]'); + break; + case 'exit': + consoleLog('Exits the console'); + break; + default: + consoleLog('Available commands: help, routePacket, exit'); + break; + } + break; + case 'routePacket': + if (!args) { + consoleLog('Usage: routePacket [startNode]->[endNode]'); + break; + } + document.getElementById('resetTTLColors').click(); + const packetInfo = `${generateRealisticLabel()}-${generateRealisticLabel()}-${TTL}-${args.split('->')[1]}`; + nodes.update({id: args.split('->')[0], color: {background: '#ff00d9'}}); + nodes.update({id: args.split('->')[1], color: {background: '#ff00d9'}}); + routePacket(args.split('->')[0], args.split('->')[1], packetInfo, true); + break; + case 'exit': + document.getElementById('consoleContainer').style.display = 'none'; + break; + default: + consoleLog(`Unknown command "${rootCommand}"`); + break; + } + consoleInput.value = ''; + } + }); + + // Handle arrow keys for history navigation + consoleInput.addEventListener('keydown', function(event) { + if (event.key === 'ArrowUp') { + event.preventDefault(); // Prevent cursor movement + if (consoleInputHistory.length === 0) return; + + if (consoleInputHistoryIndex > 0) { + consoleInputHistoryIndex--; + } + consoleInput.value = consoleInputHistory[consoleInputHistoryIndex] || ''; + } else if (event.key === 'ArrowDown') { + event.preventDefault(); // Prevent cursor movement + if (consoleInputHistory.length === 0) return; + + consoleInputHistoryIndex++; + if (consoleInputHistoryIndex >= consoleInputHistory.length) { + consoleInputHistoryIndex = consoleInputHistory.length; + consoleInput.value = ''; // Clear input when going beyond history + } else { + consoleInput.value = consoleInputHistory[consoleInputHistoryIndex] || ''; + } + } + }); + + let consoleOutput = document.createElement('pre'); + consoleOutput.id = 'consoleOutput'; + consoleContainer.appendChild(consoleOutput); + + document.getElementById('simContainer').appendChild(openConsoleButton); +} + +function consoleLog(message) { + const consoleOutput = document.getElementById('consoleOutput'); + consoleOutput.innerHTML = `${message}`; + consoleOutput.scrollTop = consoleOutput.scrollHeight; } \ No newline at end of file diff --git a/style-dark.css b/style-dark.css index 0c50b77..80dca4f 100644 --- a/style-dark.css +++ b/style-dark.css @@ -93,7 +93,7 @@ html, body { gap: 10px; } -#simpleSettingsContent, #engineSettingsContent, #hardwareConstraintsContent { +#simpleSettingsContent, #engineSettingsContent, #hardwareConstraintsContent, #visualizationSettingsContent { display: flex; flex-direction: column; gap: 10px; @@ -101,21 +101,21 @@ html, body { padding-top: 10px; } -#simpleSettings, #hardwareConstraints, #engineSettings { +#simpleSettings, #hardwareConstraints, #engineSettings, #visualizationSettings { margin-bottom: 10px; } -#randomGraphModal, #warningModal { +#randomGraphModal, #warningModal, #successModal { display: none; position: absolute; top: 0%; left: 0%; transform: translate(0%, 0%); background-color: rgba(0, 0, 0, 0.8); - padding: 20px; border: none; - width: 100%; - height: 100%; + width: 100vw; + height: 100vh; + z-index: 100; } #warningModalContent { @@ -131,6 +131,19 @@ html, body { box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5); } +#successModalContent { + padding: 20px; + background-color: #2d2d2d; + border: 2px solid #00ff00; + border-radius: 8px; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + color: #e0e0e0; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5); +} + #randomGraphModalContent { padding: 20px; background-color: #2d2d2d; @@ -144,6 +157,16 @@ html, body { box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5); } +#createRandomGraphCancel { + margin-top: 10px; + color: white; + border: none; + padding: 8px 16px; + border-radius: 4px; + cursor: pointer; + transition: background-color 0.2s ease; +} + #createRandomGraphConfirm { margin-top: 10px; background-color: #4a9eff; @@ -205,4 +228,77 @@ button:active { /* Checkbox and radio button styling */ input[type="checkbox"], input[type="radio"] { accent-color: #4a9eff; +} + +#routingProgressContainer { + display: none; + width: 100%; + flex-direction: column; + align-items: center; + justify-content: center; +} + +#routingProgress { + width: 90%; +} + +#openConsole { + position: absolute; + top: 10px; + right: 10px; + z-index: 10; + display: flex; + align-items: center; + justify-content: center; +} + +#consoleContainer { + position: absolute; + bottom: 0px; + left: 0px; + z-index: 10; + width: 100%; + height: 10%; + background-color: #000000; + border-top: 1px solid #ffffff; + display: none; + flex-direction: column; + align-items: center; + justify-content: center; + padding-left: 10px; + overflow: hidden; +} + +#consoleInput { + width: 100%; + height: 100%; + background-color: #000000; + color: #e0e0e0; + border: none; + padding: 10px; + box-sizing: border-box; +} + +#consoleInput:focus { + outline: none; + box-shadow: none; + border-color: transparent; +} + +#consoleInputContainer { + width: 100%; + height: 50%; + display: flex; + flex-direction: row; + align-items: center; + justify-content: start; + gap: 0px; +} + + +#consoleOutput { + width: 100%; + height: 50%; + background-color: #000000; + color: #e0e0e0; } \ No newline at end of file diff --git a/style.css b/style.css index be5d3f1..3b17058 100644 --- a/style.css +++ b/style.css @@ -52,7 +52,7 @@ html, body { position: absolute; border: 1px solid black; border-radius: 50%; - z-index: 1000; + z-index: 10; opacity: 0.2; background-color: red; pointer-events: none; @@ -73,7 +73,7 @@ html, body { gap: 10px; } -#simpleSettingsContent, #engineSettingsContent, #hardwareConstraintsContent { +#simpleSettingsContent, #engineSettingsContent, #hardwareConstraintsContent, #visualizationSettingsContent { display: flex; flex-direction: column; gap: 10px; @@ -81,21 +81,21 @@ html, body { padding-top: 10px; } -#simpleSettings, #hardwareConstraints, #engineSettings { +#simpleSettings, #hardwareConstraints, #engineSettings, #visualizationSettings { margin-bottom: 10px; } -#randomGraphModal, #warningModal { +#randomGraphModal, #warningModal, #successModal { display: none; position: absolute; top: 0%; left: 0%; transform: translate(0%, 0%); background-color: rgba(0, 0, 0, 0.5); - padding: 20px; border: 1px solid #000; - width: 100%; - height: 100%; + width: 100vw; + height: 100vh; + z-index: 100; } #warningModalContent { @@ -108,6 +108,16 @@ html, body { transform: translate(-50%, -50%); } +#successModalContent { + padding: 20px; + background-color: #fff; + border: 1px solid #00ff00; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} + #randomGraphModalContent { padding: 20px; background-color: #fff; @@ -118,6 +128,79 @@ html, body { transform: translate(-50%, -50%); } -#createRandomGraphConfirm { +#createRandomGraphConfirm, #createRandomGraphCancel { margin-top: 10px; +} + +#routingProgressContainer { + display: none; + width: 100%; + flex-direction: column; + align-items: center; + justify-content: center; +} + +#routingProgress { + width: 90%; +} + +#openConsole { + position: absolute; + top: 10px; + right: 10px; + z-index: 10; + display: flex; + align-items: center; + justify-content: center; +} + +#consoleContainer { + position: absolute; + bottom: 0px; + left: 0px; + z-index: 10; + width: 100%; + height: 10%; + background-color: #ffffff; + border-top: 1px solid #000000; + display: none; + flex-direction: column; + align-items: center; + justify-content: center; + padding-left: 10px; + overflow: hidden; +} + +#consoleInput { + width: 100%; + height: 100%; + background-color: #ffffff; + color: #000000; + border: none; + padding: 10px; + box-sizing: border-box; +} + +#consoleInput:focus { + outline: none; + box-shadow: none; + border-color: transparent; +} + +#consoleInputContainer { + width: 100%; + height: 50%; + display: flex; + flex-direction: row; + align-items: center; + justify-content: start; + gap: 0px; +} + + +#consoleOutput { + width: 100%; + height: 50%; + background-color: #ffffff; + color: #000000; } \ No newline at end of file