diff --git a/src/component/modem.lua b/src/component/modem.lua index ab088c4..90c8e51 100644 --- a/src/component/modem.lua +++ b/src/component/modem.lua @@ -5,6 +5,24 @@ compCheckArg(1,wireless,"boolean") local socket = require("socket") local ser = require("loot.OpenOS.lib.serialization") +local function cerror(...) + local args = table.pack(...) + + local sep = '' + + for _,arg in pairs(args) do + local p; + if (type(arg) == "userdata") then p = "userdata" + elseif (type(arg) == "string") then p = arg + else p = ser.serialize(arg) end + io.stderr:write(sep .. tostring(_) .. '=' .. p) + sep = ',' + end + + io.stderr:write('\n') + io.stderr:flush() +end + -- yes, global modem_host = {} @@ -24,14 +42,14 @@ modem_host.clients = {} -- [port_number] = true when open modem_host.open_ports = {} -function modem_host.createPacketArray(t, address, port, ...) - compCheckArg(1,t,type("")) - compCheckArg(2,address,type(""),type(0)) - compCheckArg(3,port,type(0)) +function modem_host.createPacketArray(packetType, address, port, ...) + compCheckArg(1,packetType,"string") + compCheckArg(2,address,"string","number") + compCheckArg(3,port,"number") local packed = { - t, + packetType, address, modem_host.id, port, @@ -43,7 +61,7 @@ function modem_host.createPacketArray(t, address, port, ...) end function modem_host.packetArrayToPacket(packed) - compCheckArg(1,packed,type({})) + compCheckArg(1,packed,"table") assert(#packed >= 5) local packet = {} @@ -54,70 +72,75 @@ function modem_host.packetArrayToPacket(packed) packet.distance = packed[5] packet.payload = {} - for i=6,#packed do - table.insert(packet.payload, packed[i]) + -- all other keys will be index values but may skip some (nils) + for k,v in pairs(packed) do + if k > 5 then + packet.payload[k-5] = v + end end return packet end function modem_host.packetArrayToDatagram(packed) - compCheckArg(1,packed,type({})) + compCheckArg(1,packed,"table") local datagram = ser.serialize(packed) return datagram .. '\n' end function modem_host.packetToPacketArray(packet) - return + local packed = { packet.type, packet.target, packet.source, packet.port, packet.distance, - table.unpack(packet.payload or {}) } -end -function modem_host.datagramToPacketArray(datagram) - compCheckArg(1,datagram,type("")) - local packed = ser.unserialize(datagram) + if packet.payload then + for i,v in pairs(packet.payload) do + packed[i+5] = v + end + end + return packed end +function modem_host.datagramToPacketArray(datagram) + compCheckArg(1,datagram,"string") + return ser.unserialize(datagram) +end + function modem_host.datagramToPacket(datagram) - local packed = modem_host.datagramToPacketArray(datagram) - local packet = modem_host.packedToPacket(packed) - return packet + return modem_host.packedToPacket(modem_host.datagramToPacketArray(datagram)) end function modem_host.packetToDatagram(packet) - local packed = modem_host.packetToPacketArray(packet) - local datagram = modem_host.packetArrayToDatagram(packed) - return datagram + return modem_host.packetArrayToDatagram(modem_host.packetToPacketArray(packet)) end function modem_host.readDatagram(client) -- client:receive() local raw, err = client:receive() - if raw then cprint("received: " .. raw) end + if raw then cerror("readDatagram", raw) end return raw, err end function modem_host.readPacketArray(client) -- client:receive() local datagram, err = modem_host.readDatagram(client) - if not datagram then return nil, err end + if datagram == nil then return nil, err end return modem_host.datagramToPacketArray(datagram) end function modem_host.readPacket(client) -- client:receive() local packed, err = modem_host.readPacketArray(client) - if not packed then return nil, err end + if packed == nil then return nil, err end return modem_host.packetArrayToPacket(packed) end function modem_host.sendDatagram(client, datagram) - cprint("sending: " .. datagram) + cerror("sendDatagram", datagram) return client:send(datagram) end @@ -132,33 +155,37 @@ function modem_host.sendPacket(client, packet) end function modem_host.broadcast(packet) - -- only host broadcasts - -- this method will be hit for all broadcasted messages - -- but nonhosting clients will simply not repeat the broadcast + -- we assume packet.target == 0 if modem_host.hosting then - local datagram = modem_host.packetToDatagram(packet) for addr,client in pairs(modem_host.clients) do - modem_host.sendDatagram(client, datagram) + packet.target = addr + modem_host.sendPacket(client, packet) end + -- and self + packet.target = modem_host.id + modem_host.dispatchPacket(packet) + else + -- let host broadcast to all clients + modem_host.sendPacket(modem_host.socket, packet) end end function modem_host.validTarget(target) if target == 0 then - return true + return true -- broadcast end if target == modem_host.id then - return true + return true -- dispatch will add to machine signals end if not modem_host.hosting then - return false + return true -- dispatch can handle sending to host end for address,client in pairs(modem_host.clients) do if address == target then - return true + return true -- we are hosting and know about this target end end @@ -178,9 +205,9 @@ end function modem_host.dispatchPacket(packet) if packet.target == modem_host.id then - if obj.isOpen(packet.port) then - table.insert(machine.signals, modem_host.packetToPacketArray(packet)) - end + if obj.isOpen(packet.port) then + table.insert(machine.signals, modem_host.packetToPacketArray(packet)) + end elseif modem_host.hosting then -- if hosting we will route for source,client in pairs(modem_host.clients) do if source == packet.target then @@ -194,59 +221,55 @@ function modem_host.dispatchPacket(packet) end function modem_host.processPendingMessages() - -- computer address seems to be applied late - if not modem_host.id then - modem_host.id = component.list("computer",true)() - assert(modem_host.id) + -- do not try to process anything if this machine is not even connected to a message board + -- not wrong without this, this is a simple optimization + if not modem_host.connected then + return end - modem_host.recvPendingMessages() - - for _,packet in pairs(modem_host.messages) do + modem_host.acceptPendingClients() + for _,packet in modem_host.allPendingMessages() do if packet.type == 'modem_message' then -- broadcast if no target if packet.target == 0 then - modem_host.broadcast(packet) -- ignored by clients - - -- clean up for broadcasting to self - packet.target = modem_host.id + modem_host.broadcast(packet) + else + modem_host.dispatchPacket(packet) end - - modem_host.dispatchPacket(packet) + elseif packet.type == 'host_shutdown' then + modem_host.host_shutdown = true end end - - modem_host.messages = {} end -function modem_host.recvPendingMessages() +function modem_host.acceptPendingClients() if modem_host.hosting then while true do local client = modem_host.socket:accept() - if not client then + if client == nil then break; end local handshake, err = modem_host.readPacket(client) -- client:receive() - if not handshake then + if handshake == nil then client:close() else local connectionResponse local accepted = false if handshake.type ~= "handshake" then - connectionResponse = modem_host.createPacketArray("handshake", modem_host.id, -1, + connectionResponse = modem_host.createPacketArray("handshake", 0, -1, false, "unsupported message type"); elseif modem_host.validTarget(handshake.source) then -- repeated client - connectionResponse = modem_host.createPacketArray("handshake", modem_host.id, -1, + connectionResponse = modem_host.createPacketArray("handshake", 0, -1, false, "computer address conflict detected, ignoring connection"); else client:settimeout(0, 't') modem_host.clients[handshake.source] = client accepted = true - connectionResponse = modem_host.createPacketArray("handshake", modem_host.id, -1, true); + connectionResponse = modem_host.createPacketArray("handshake", 0, -1, true); end modem_host.sendPacketArray(client, connectionResponse) @@ -256,27 +279,45 @@ function modem_host.recvPendingMessages() end end end + end +end - -- recv all pending packets - for source, client in pairs(modem_host.clients) do - local packet, err = modem_host.readPacket(client) - if packet then - modem_host.pushMessage(packet) - elseif err ~= "timeout" then - client:close() - modem_host.clients[source] = nil - end +function modem_host.allPendingMessages() + local msgIt = function(...) + if #modem_host.messages > 0 then + return 0, table.remove(modem_host.messages, 1) end - elseif modem_host.socket then - while true do - local packet, err = modem_host.readPacket(modem_host.socket) - if packet then - modem_host.pushMessage(packet) - else - break + + if modem_host.hosting then + for source, client in pairs(modem_host.clients) do + local packet, err = modem_host.readPacket(client) + if packet then + return 0, packet + elseif err ~= "timeout" then + client:close() + modem_host.clients[source] = nil + end + end + elseif modem_host.socket then + while true do + local packet, err = modem_host.readPacket(modem_host.socket) + if packet then + return 0, packet + else + if err ~= "timeout" then + if not modem_host.host_shutdown then + error("modem host was unexpectedly lost") + end + modem_host.connected = false + modem_host.connectMessageBoard() + end + break + end end end end + + return msgIt, nil, 0 end function modem_host.createNewMessageBoard() @@ -316,6 +357,21 @@ function modem_host.connectMessageBoard() return true end + if modem_host.host_shutdown then + modem_host.socket:close() + end + + modem_host.socket = nil + modem_host.clients = {} + modem_host.messages = {} + modem_host.host_shutdown = nil + + -- computer address seems to be applied late + if modem_host.id == nil then + modem_host.id = component.list("computer",true)() + assert(modem_host.id) + end + local ok, info, critical = modem_host.joinExistingMessageBoard() if not ok and critical then @@ -332,12 +388,37 @@ function modem_host.connectMessageBoard() modem_host.socket:settimeout(0, 't') -- accept calls must be already pending modem_host.connected = true - modem_host.clients = {} - modem_host.messages = {} return true end +function modem_host.halt(bReboot) + compCheckArg(1,bReboot,"boolean") + obj.close() -- close all virtual ports + + -- if only rebooting, pending messages don't need to be pumped and no one needs to be notified + if modem_host.connected and not bReboot then + + if modem_host.hosting then + for addr,csocket in pairs(modem_host.clients) do + local notification = modem_host.createPacketArray("host_shutdown", addr, -1); + modem_host.sendPacketArray(csocket, notification) + end + + -- close all client connections + for _,c in pairs(modem_host.clients) do + c:close() + end + + modem_host.hosting = false + modem_host.clients = {} -- forget client socket data + end + + -- close real port + modem_host.socket:close() + end +end + local wakeMessage local strength if wireless then @@ -352,67 +433,72 @@ local function checkPort(port) end function obj.send(address, port, ...) -- Sends the specified data to the specified target. - cprint("modem.send",address, port, ...) compCheckArg(1,address,"string") compCheckArg(2,port,"number") port=checkPort(port) + -- we cannot send unless we are connected to the message board + if not modem_host.connectMessageBoard() then + return false + end + local packed = modem_host.createPacketArray("modem_message", address, port, ...) local packet = modem_host.packetArrayToPacket(packed) - return modem_host.dispatchPacket(packet) + modem_host.pushMessage(packet) + return true end function obj.getWakeMessage() -- Get the current wake-up message. - cprint("modem.getWakeMessage") return wakeMessage end function obj.setWakeMessage(message) -- Set the wake-up message. - cprint("modem.setWakeMessage",message) compCheckArg(1,message,"string","nil") wakeMessage = message end function obj.close(port) -- Closes the specified port (default: all ports). Returns true if ports were closed. - cprint("modem.close",port) compCheckArg(1,port,"number","nil") if port ~= nil then port=checkPort(port) end - if not obj.isOpen(port) then - return false; + -- nil port case + if port == nil then + if not next(modem_host.open_ports) then + return false, "no open ports" + else + modem_host.open_ports = {} -- close them all + end + elseif not obj.isOpen(port) then + return false, "port not open" + else + modem_host.open_ports[port] = nil end - modem_host.open_ports[port] = nil return true end function obj.maxPacketSize() -- Gets the maximum packet size (config setting). - cprint("modem.maxPacketSize") return settings.maxNetworkPacketSize end if wireless then function obj.getStrength() -- Get the signal strength (range) used when sending messages. - cprint("modem.getStrength") return strength end function obj.setStrength(newstrength) -- Set the signal strength (range) used when sending messages. - cprint("modem.setStrength",newstrength) compCheckArg(1,newstrength,"number") strength = newstrength end end function obj.isOpen(port) -- Whether the specified port is open. - cprint("modem.isOpen",port) compCheckArg(1,port,"number") return modem_host.open_ports[port] ~= nil end function obj.open(port) -- Opens the specified port. Returns true if the port was opened. - cprint("modem.open",port) compCheckArg(1,port,"number") port=checkPort(port) @@ -432,12 +518,10 @@ function obj.open(port) -- Opens the specified port. Returns true if the port wa end function obj.isWireless() -- Whether this is a wireless network card. - cprint("modem.isWireless") return wireless end function obj.broadcast(port, ...) -- Broadcasts the specified data on the specified port. - cprint("modem.broadcast",port, ...) compCheckArg(1,port,"number") port=checkPort(port) @@ -448,7 +532,8 @@ function obj.broadcast(port, ...) -- Broadcasts the specified data on the specif local packed = modem_host.createPacketArray("modem_message", 0, port, ...) local packet = modem_host.packetArrayToPacket(packed) - return modem_host.dispatchPacket(packet) + modem_host.pushMessage(packet) + return true end local cec = {} diff --git a/src/component/screen_sdl2.lua b/src/component/screen_sdl2.lua index 64b3d99..bdec8dd 100644 --- a/src/component/screen_sdl2.lua +++ b/src/component/screen_sdl2.lua @@ -102,6 +102,8 @@ local window, renderer, texture, copytexture local function createWindow() if not window then window = SDL.createWindow("OCEmu - screen@" .. address, SDL.WINDOWPOS_CENTERED, SDL.WINDOWPOS_CENTERED, width*8, height*16, SDL.WINDOW_SHOWN) + SDL.setWindowGrab(window, SDL.FALSE) + if window == ffi.C.NULL then error(ffi.string(SDL.getError())) end diff --git a/src/main.lua b/src/main.lua index ca8beb6..6ef0bf8 100644 --- a/src/main.lua +++ b/src/main.lua @@ -294,8 +294,10 @@ function resume_thread(...) machine.deadline = elsa.timer.getTime() + results[2] elseif type(results[2]) == "boolean" then if results[2] then + modem_host.halt(true) boot_machine() else + modem_host.halt(false) elsa.quit() error("Machine power off",0) end