cc-rail-router/central_server.lua

331 lines
11 KiB
Lua
Raw Permalink Normal View History

--[[
A central server to coordinate a rail network. This central server keeps a
graph of the entire network, and handles requests to find paths to route
traffic through.
]]--
2023-09-14 15:09:26 +00:00
local RECEIVE_CHANNEL = 45452
2023-09-17 11:44:47 +00:00
local g = require("simple-graphics")
local W, H = term.getSize()
local console = g.createConsole(W, H-2, colors.white, colors.black, "UP")
-- ONLY FOR DEBUGGING
-- inspect = require("inspect")
2023-09-14 23:06:16 +00:00
local modem = peripheral.wrap("top") or error("Missing top modem")
modem.open(RECEIVE_CHANNEL)
2023-09-17 11:44:47 +00:00
local function logToConsole(text)
local timestamp = os.date("%F %T")
g.appendAndDrawConsole(term, console, timestamp.." "..text, 1, 3)
end
local function generateStandardNode(id, edgeIds)
local node = {id = id, connections = {}, type = "JUNCTION"}
for _, edgeId in pairs(edgeIds) do
for _, edgeId2 in pairs(edgeIds) do
if edgeId2 ~= edgeId then
table.insert(node.connections, {from = edgeId, to = edgeId2})
end
end
end
return node
end
2023-09-14 23:06:16 +00:00
local function generateStationNode(id, displayName, edgeId)
return {
id = id,
2023-09-14 23:06:16 +00:00
displayName = displayName,
connections = {
{from = nil, to = edgeId},
{from = edgeId, to = nil}
},
type = "STATION"
}
end
local function loadGraph()
-- local g = nil
-- local f = io.open("network_graph.tbl", "r")
-- g = textutils.unserialize(f:read("*a"))
-- f:close()
--return g
2023-09-14 23:06:16 +00:00
local tempGraph = {
nodes = {
generateStandardNode("Junction-HandieVale", {"handievale", "N1", "W1"}),
generateStandardNode("Junction-Middlecross", {"W1", "N2", "W2", "S1"}),
generateStandardNode("Junction-Foundry", {"E1", "N3", "W3", "N2"}),
generateStandardNode("Junction-End", {"E1", "E2", "end"}),
generateStandardNode("Junction-Klausville", {"N3", "N4", "klausville"}),
generateStandardNode("Junction-Foundry-West", {"W3", "foundry", "W4"}),
2023-09-17 11:44:47 +00:00
generateStandardNode("Junction-Cam", {"S1", "S2", "cam"}),
2023-09-14 23:06:16 +00:00
generateStationNode("station-klausville", "Klausville", "klausville"),
generateStationNode("station-handievale", "HandieVale", "handievale"),
generateStationNode("station-end", "End & Biofuel Refinery", "end"),
2023-09-17 11:44:47 +00:00
generateStationNode("station-foundry", "Jack's Foundry", "foundry"),
generateStationNode("station-cam", "Camville", "cam")
},
edges = {
{id = "handievale", length = 16},
{id = "end", length = 48},
{id = "foundry", length = 45},
{id = "klausville", length = 12},
2023-09-17 11:44:47 +00:00
{id = "cam", length = 16},
{id = "N1", length = nil},
{id = "W1", length = 300},
{id = "N2", length = 600},
{id = "E1", length = 75},
{id = "W2", length = nil},
2023-09-17 11:44:47 +00:00
{id = "S1", length = 420},
{id = "S2", length = nil},
{id = "W3", length = 50},
{id = "W4", length = nil},
{id = "N3", length = 350},
{id = "N4", length = nil}
}
}
2023-09-14 23:06:16 +00:00
return tempGraph
end
local function filterTable(arr, func)
local new_index = 1
local size_orig = #arr
for old_index, v in ipairs(arr) do
if func(v, old_index) then
arr[new_index] = v
new_index = new_index + 1
end
end
for i = new_index, size_orig do arr[i] = nil end
end
local function findNodeById(graph, nodeId)
for _, node in pairs(graph.nodes) do
if node.id == nodeId then return node end
end
return nil
end
local function findEdgeById(graph, edgeId)
for _, edge in pairs(graph.edges) do
if edge.id == edgeId then return edge end
end
return nil
end
local function findEdgeBetweenNodes(graph, fromNode, toNode)
local edgeIdsFrom = {}
for _, conn in pairs(fromNode.connections) do
if conn.to and not tableContains(edgeIdsFrom, conn.to) then
table.insert(edgeIdsFrom, conn.to)
end
end
local edgeIds = {}
for _, conn in pairs(toNode.connections) do
if conn.from and tableContains(edgeIdsFrom, conn.from) and not tableContains(edgeIds, conn.from) then
table.insert(edgeIds, conn.from)
end
end
end
local function tableContains(table, value)
for _, item in pairs(table) do
if item == value then return true end
end
return false
end
local function removeElement(table, value)
local idx = nil
for i, item in pairs(table) do
if item == value then idx = i break end
end
if idx then table.remove(table, idx) end
end
local function findNextEdges(graph, edgeId)
local edges = {}
for _, node in pairs(graph.nodes) do
for _, connection in pairs(node.connections) do
if connection.from == edgeId then
table.insert(edges, findEdgeById(connection.to))
end
end
end
return edges
end
-- Find the set of nodes directly connected to this one via some edges
local function findConnectedNodes(graph, startNode)
local edges = {}
local edgeIds = {}
for _, conn in pairs(startNode.connections) do
if conn.to ~= nil then
local edge = findEdgeById(graph, conn.to)
if edge ~= nil and edge.length ~= nil and not tableContains(edgeIds, edge.id) then
table.insert(edges, edge)
table.insert(edgeIds, edge.id)
end
end
end
local connections = {}
for _, edge in pairs(edges) do
for _, node in pairs(graph.nodes) do
if node.id ~= startNode.id then
for _, conn in pairs(node.connections) do
if conn.from == edge.id then
table.insert(connections, {node = node, distance = edge.length, via = edge.id})
break
end
end
end
end
end
return connections
end
local function findPath(graph, startNode, endNode)
local INFINITY = 1000000000
local dist = {}
local prev = {}
local queue = {}
for _, node in pairs(graph.nodes) do
dist[node.id] = INFINITY
prev[node.id] = nil
table.insert(queue, node)
end
dist[startNode.id] = 0
while #queue > 0 do
local minIdx = nil
local minDist = INFINITY + 1
for i, node in pairs(queue) do
if dist[node.id] < minDist then
minIdx = i
minDist = dist[node.id]
end
end
if minIdx == nil then return nil end
local u = table.remove(queue, minIdx)
if u.id == endNode.id and (prev[u.id] or u.id == startNode.id) then
local s = {}
while u ~= nil do
local via = nil
local distance = nil
local node = u
u = nil
if prev[node.id] then
via = prev[node.id].via
distance = prev[node.id].distance
u = prev[node.id].node
end
table.insert(s, 1, {node = node, via = via, distance = distance})
end
return s
end
for _, neighbor in pairs(findConnectedNodes(graph, u)) do
local unvisited = false
for _, node in pairs(queue) do
if node.id == neighbor.node.id then
unvisited = true
break
end
end
if unvisited then
local alt = dist[u.id] + neighbor.distance
if alt < dist[neighbor.node.id] then
dist[neighbor.node.id] = alt
prev[neighbor.node.id] = {node = u, via = neighbor.via, distance = neighbor.distance}
end
end
end
end
return nil
end
2023-09-14 15:09:26 +00:00
local function getReachableStations(graph, startNode)
local queue = findConnectedNodes(graph, startNode)
local stations = {}
local visitedNodeIds = {startNode.id}
while #queue > 0 do
local node = table.remove(queue, 1).node
if node.type == "STATION" and not tableContains(visitedNodeIds, node.id) then
table.insert(stations, node)
end
table.insert(visitedNodeIds, node.id)
for _, conn in pairs(findConnectedNodes(graph, node)) do
if not tableContains(visitedNodeIds, conn.node.id) then
table.insert(queue, conn)
end
end
end
return stations
end
local function handleRouteRequest(graph, replyChannel, msg)
if not msg.startNode or not msg.endNode then
modem.transmit(replyChannel, RECEIVE_CHANNEL, {success = false, error = "Invalid request"})
return
end
local startNode = findNodeById(graph, msg.startNode)
local endNode = findNodeById(graph, msg.endNode)
if not startNode or not endNode then
modem.transmit(replyChannel, RECEIVE_CHANNEL, {success = false, error = "Unknown node(s)"})
return
end
2023-09-17 11:44:47 +00:00
logToConsole("Finding path from "..startNode.id.." to "..endNode.id.."...")
local path = findPath(graph, startNode, endNode)
if not path then
2023-09-17 11:44:47 +00:00
logToConsole("Couldn't find a path!")
modem.transmit(replyChannel, RECEIVE_CHANNEL, {success = false, error = "No valid path"})
return
end
2023-09-17 11:44:47 +00:00
local pathStr = ""
for i, segment in pairs(path) do
pathStr = pathStr .. segment.node.id
if i < #path - 1 then pathStr = pathStr .. ", " end
end
logToConsole("Found path: "..pathStr)
modem.transmit(replyChannel, RECEIVE_CHANNEL, {success = true, route = path})
end
2023-09-14 15:09:26 +00:00
local function handleGetRoutesRequest(graph, replyChannel, msg)
if not msg.startNode then
modem.transmit(replyChannel, RECEIVE_CHANNEL, {success = false, error = "Invalid request"})
return
end
local startNode = findNodeById(graph, msg.startNode)
if not startNode then
modem.transmit(replyChannel, RECEIVE_CHANNEL, {success = false, error = "Unknown node"})
return
end
2023-09-17 11:44:47 +00:00
logToConsole("Finding reachable stations from "..startNode.id.."...")
2023-09-14 15:09:26 +00:00
local stations = getReachableStations(graph, startNode)
2023-09-17 11:44:47 +00:00
logToConsole("Found "..#stations.." results.")
2023-09-14 15:09:26 +00:00
modem.transmit(replyChannel, RECEIVE_CHANNEL, {success = true, stations = stations})
end
2023-09-17 11:44:47 +00:00
local function handleRequests(graph, console)
while true do
local event, side, channel, replyChannel, msg, dist = os.pullEvent("modem_message")
if channel == RECEIVE_CHANNEL and msg and msg.command and type(msg.command) == "string" then
2023-09-17 11:44:47 +00:00
logToConsole("Got request on CH: "..channel..", RCH: "..replyChannel..", CMD: "..msg.command)
if msg.command == "ROUTE" then
handleRouteRequest(graph, replyChannel, msg)
2023-09-14 15:09:26 +00:00
elseif msg.command == "GET_ROUTES" then
handleGetRoutesRequest(graph, replyChannel, msg)
else
modem.transmit(replyChannel, RECEIVE_CHANNEL, {success = false, error = "Invalid command"})
end
end
end
end
2023-09-17 11:44:47 +00:00
g.clear(term, colors.black)
g.drawTextCenter(term, W/2, 1, "CC-Rail Central Server", colors.yellow, colors.black)
g.drawXLine(term, 1, W, 2, colors.gray)
logToConsole("Now taking requests.")
handleRequests(loadGraph(), console)