1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-01-15 03:35:42 +00:00

Some bits of rednet cleanup

- Remove all the hungrarian notation in variables. Currently leaving
   the format of rednet messages for now, while I work out whether this
   counts as part of the public API or not.

 - Fix the "repeat" program failing with broadcast packets. This was
   introduced in #900, but I don't think anybody noticed. Will be more
   relevant when #955 is implemented though.
This commit is contained in:
Jonathan Coates 2021-12-13 14:30:11 +00:00
parent 1cfad31a0d
commit e16f66e128
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
4 changed files with 149 additions and 127 deletions

View File

@ -29,9 +29,9 @@ CHANNEL_REPEAT = 65533
-- greater or equal to this limit wrap around to 0. -- greater or equal to this limit wrap around to 0.
MAX_ID_CHANNELS = 65500 MAX_ID_CHANNELS = 65500
local tReceivedMessages = {} local received_messages = {}
local tHostnames = {} local hostnames = {}
local nClearTimer local prune_received_timer
local function id_as_channel(id) local function id_as_channel(id)
return (id or os.getComputerID()) % MAX_ID_CHANNELS return (id or os.getComputerID()) % MAX_ID_CHANNELS
@ -115,10 +115,10 @@ be @{rednet.open|opened} before sending is possible.
Assuming the target was in range and also had a correctly opened modem, it Assuming the target was in range and also had a correctly opened modem, it
may then use @{rednet.receive} to collect the message. may then use @{rednet.receive} to collect the message.
@tparam number nRecipient The ID of the receiving computer. @tparam number recipient The ID of the receiving computer.
@param message The message to send. This should not contain coroutines or @param message The message to send. This should not contain coroutines or
functions, as they will be converted to @{nil}. functions, as they will be converted to @{nil}.
@tparam[opt] string sProtocol The "protocol" to send this message under. When @tparam[opt] string protocol The "protocol" to send this message under. When
using @{rednet.receive} one can filter to only receive messages sent under a using @{rednet.receive} one can filter to only receive messages sent under a
particular protocol. particular protocol.
@treturn boolean If this message was successfully sent (i.e. if rednet is @treturn boolean If this message was successfully sent (i.e. if rednet is
@ -131,41 +131,41 @@ actually _received_.
rednet.send(2, "Hello from rednet!") rednet.send(2, "Hello from rednet!")
]] ]]
function send(nRecipient, message, sProtocol) function send(recipient, message, protocol)
expect(1, nRecipient, "number") expect(1, recipient, "number")
expect(3, sProtocol, "string", "nil") expect(3, protocol, "string", "nil")
-- Generate a (probably) unique message ID -- Generate a (probably) unique message ID
-- We could do other things to guarantee uniqueness, but we really don't need to -- We could do other things to guarantee uniqueness, but we really don't need to
-- Store it to ensure we don't get our own messages back -- Store it to ensure we don't get our own messages back
local nMessageID = math.random(1, 2147483647) local message_id = math.random(1, 2147483647)
tReceivedMessages[nMessageID] = os.clock() + 9.5 received_messages[message_id] = os.clock() + 9.5
if not nClearTimer then nClearTimer = os.startTimer(10) end if not prune_received_timer then prune_received_timer = os.startTimer(10) end
-- Create the message -- Create the message
local nReplyChannel = id_as_channel() local reply_channel = id_as_channel()
local tMessage = { local message_wrapper = {
nMessageID = nMessageID, nMessageID = message_id,
nRecipient = nRecipient, nRecipient = recipient,
nSender = os.getComputerID(), nSender = os.getComputerID(),
message = message, message = message,
sProtocol = sProtocol, sProtocol = protocol,
} }
local sent = false local sent = false
if nRecipient == os.getComputerID() then if recipient == os.getComputerID() then
-- Loopback to ourselves -- Loopback to ourselves
os.queueEvent("rednet_message", os.getComputerID(), message, sProtocol) os.queueEvent("rednet_message", os.getComputerID(), message_wrapper, protocol)
sent = true sent = true
else else
-- Send on all open modems, to the target and to repeaters -- Send on all open modems, to the target and to repeaters
if nRecipient ~= CHANNEL_BROADCAST then if recipient ~= CHANNEL_BROADCAST then
nRecipient = id_as_channel(nRecipient) recipient = id_as_channel(recipient)
end end
for _, sModem in ipairs(peripheral.getNames()) do for _, modem in ipairs(peripheral.getNames()) do
if isOpen(sModem) then if isOpen(modem) then
peripheral.call(sModem, "transmit", nRecipient, nReplyChannel, tMessage) peripheral.call(modem, "transmit", recipient, reply_channel, message_wrapper)
peripheral.call(sModem, "transmit", CHANNEL_REPEAT, nReplyChannel, tMessage) peripheral.call(modem, "transmit", CHANNEL_REPEAT, reply_channel, message_wrapper)
sent = true sent = true
end end
end end
@ -179,23 +179,23 @@ end
-- --
-- @param message The message to send. This should not contain coroutines or -- @param message The message to send. This should not contain coroutines or
-- functions, as they will be converted to @{nil}. -- functions, as they will be converted to @{nil}.
-- @tparam[opt] string sProtocol The "protocol" to send this message under. When -- @tparam[opt] string protocol The "protocol" to send this message under. When
-- using @{rednet.receive} one can filter to only receive messages sent under a -- using @{rednet.receive} one can filter to only receive messages sent under a
-- particular protocol. -- particular protocol.
-- @see rednet.receive -- @see rednet.receive
-- @changed 1.6 Added protocol parameter. -- @changed 1.6 Added protocol parameter.
function broadcast(message, sProtocol) function broadcast(message, protocol)
expect(2, sProtocol, "string", "nil") expect(2, protocol, "string", "nil")
send(CHANNEL_BROADCAST, message, sProtocol) send(CHANNEL_BROADCAST, message, protocol)
end end
--[[- Wait for a rednet message to be received, or until `nTimeout` seconds have --[[- Wait for a rednet message to be received, or until `nTimeout` seconds have
elapsed. elapsed.
@tparam[opt] string sProtocolFilter The protocol the received message must be @tparam[opt] string protocol_filter The protocol the received message must be
sent with. If specified, any messages not sent under this protocol will be sent with. If specified, any messages not sent under this protocol will be
discarded. discarded.
@tparam[opt] number nTimeout The number of seconds to wait if no message is @tparam[opt] number timeout The number of seconds to wait if no message is
received. received.
@treturn[1] number The computer which sent this message @treturn[1] number The computer which sent this message
@return[1] The received message @return[1] The received message
@ -227,34 +227,34 @@ received.
print(message) print(message)
]] ]]
function receive(sProtocolFilter, nTimeout) function receive(protocol_filter, timeout)
-- The parameters used to be ( nTimeout ), detect this case for backwards compatibility -- The parameters used to be ( nTimeout ), detect this case for backwards compatibility
if type(sProtocolFilter) == "number" and nTimeout == nil then if type(protocol_filter) == "number" and timeout == nil then
sProtocolFilter, nTimeout = nil, sProtocolFilter protocol_filter, timeout = nil, protocol_filter
end end
expect(1, sProtocolFilter, "string", "nil") expect(1, protocol_filter, "string", "nil")
expect(2, nTimeout, "number", "nil") expect(2, timeout, "number", "nil")
-- Start the timer -- Start the timer
local timer = nil local timer = nil
local sFilter = nil local event_filter = nil
if nTimeout then if timeout then
timer = os.startTimer(nTimeout) timer = os.startTimer(timeout)
sFilter = nil event_filter = nil
else else
sFilter = "rednet_message" event_filter = "rednet_message"
end end
-- Wait for events -- Wait for events
while true do while true do
local sEvent, p1, p2, p3 = os.pullEvent(sFilter) local event, p1, p2, p3 = os.pullEvent(event_filter)
if sEvent == "rednet_message" then if event == "rednet_message" then
-- Return the first matching rednet_message -- Return the first matching rednet_message
local nSenderID, message, sProtocol = p1, p2, p3 local sender_id, message, protocol = p1, p2, p3
if sProtocolFilter == nil or sProtocol == sProtocolFilter then if protocol_filter == nil or protocol == protocol_filter then
return nSenderID, message, sProtocol return sender_id, message, protocol
end end
elseif sEvent == "timer" then elseif event == "timer" then
-- Return nil if we timeout -- Return nil if we timeout
if p1 == timer then if p1 == timer then
return nil return nil
@ -276,34 +276,34 @@ end
-- "registering" themselves before doing so (eg while offline or part of a -- "registering" themselves before doing so (eg while offline or part of a
-- different network). -- different network).
-- --
-- @tparam string sProtocol The protocol this computer provides. -- @tparam string protocol The protocol this computer provides.
-- @tparam string sHostname The name this protocol exposes for the given protocol. -- @tparam string hostname The name this protocol exposes for the given protocol.
-- @throws If trying to register a hostname which is reserved, or currently in use. -- @throws If trying to register a hostname which is reserved, or currently in use.
-- @see rednet.unhost -- @see rednet.unhost
-- @see rednet.lookup -- @see rednet.lookup
-- @since 1.6 -- @since 1.6
function host(sProtocol, sHostname) function host(protocol, hostname)
expect(1, sProtocol, "string") expect(1, protocol, "string")
expect(2, sHostname, "string") expect(2, hostname, "string")
if sHostname == "localhost" then if hostname == "localhost" then
error("Reserved hostname", 2) error("Reserved hostname", 2)
end end
if tHostnames[sProtocol] ~= sHostname then if hostnames[protocol] ~= hostname then
if lookup(sProtocol, sHostname) ~= nil then if lookup(protocol, hostname) ~= nil then
error("Hostname in use", 2) error("Hostname in use", 2)
end end
tHostnames[sProtocol] = sHostname hostnames[protocol] = hostname
end end
end end
--- Stop @{rednet.host|hosting} a specific protocol, meaning it will no longer --- Stop @{rednet.host|hosting} a specific protocol, meaning it will no longer
-- respond to @{rednet.lookup} requests. -- respond to @{rednet.lookup} requests.
-- --
-- @tparam string sProtocol The protocol to unregister your self from. -- @tparam string protocol The protocol to unregister your self from.
-- @since 1.6 -- @since 1.6
function unhost(sProtocol) function unhost(protocol)
expect(1, sProtocol, "string") expect(1, protocol, "string")
tHostnames[sProtocol] = nil hostnames[protocol] = nil
end end
--- Search the local rednet network for systems @{rednet.host|hosting} the --- Search the local rednet network for systems @{rednet.host|hosting} the
@ -313,36 +313,36 @@ end
-- If a hostname is specified, only one ID will be returned (assuming an exact -- If a hostname is specified, only one ID will be returned (assuming an exact
-- match is found). -- match is found).
-- --
-- @tparam string sProtocol The protocol to search for. -- @tparam string protocol The protocol to search for.
-- @tparam[opt] string sHostname The hostname to search for. -- @tparam[opt] string hostname The hostname to search for.
-- --
-- @treturn[1] { number }|nil A list of computer IDs hosting the given -- @treturn[1] { number }|nil A list of computer IDs hosting the given
-- protocol, or @{nil} if none exist. -- protocol, or @{nil} if none exist.
-- @treturn[2] number|nil The computer ID with the provided hostname and protocol, -- @treturn[2] number|nil The computer ID with the provided hostname and protocol,
-- or @{nil} if none exists. -- or @{nil} if none exists.
-- @since 1.6 -- @since 1.6
function lookup(sProtocol, sHostname) function lookup(protocol, hostname)
expect(1, sProtocol, "string") expect(1, protocol, "string")
expect(2, sHostname, "string", "nil") expect(2, hostname, "string", "nil")
-- Build list of host IDs -- Build list of host IDs
local tResults = nil local results = nil
if sHostname == nil then if hostname == nil then
tResults = {} results = {}
end end
-- Check localhost first -- Check localhost first
if tHostnames[sProtocol] then if hostnames[protocol] then
if sHostname == nil then if hostname == nil then
table.insert(tResults, os.getComputerID()) table.insert(results, os.getComputerID())
elseif sHostname == "localhost" or sHostname == tHostnames[sProtocol] then elseif hostname == "localhost" or hostname == hostnames[protocol] then
return os.getComputerID() return os.getComputerID()
end end
end end
if not isOpen() then if not isOpen() then
if tResults then if results then
return table.unpack(tResults) return table.unpack(results)
end end
return nil return nil
end end
@ -350,8 +350,8 @@ function lookup(sProtocol, sHostname)
-- Broadcast a lookup packet -- Broadcast a lookup packet
broadcast({ broadcast({
sType = "lookup", sType = "lookup",
sProtocol = sProtocol, sProtocol = protocol,
sHostname = sHostname, sHostname = hostname,
}, "dns") }, "dns")
-- Start a timer -- Start a timer
@ -362,30 +362,28 @@ function lookup(sProtocol, sHostname)
local event, p1, p2, p3 = os.pullEvent() local event, p1, p2, p3 = os.pullEvent()
if event == "rednet_message" then if event == "rednet_message" then
-- Got a rednet message, check if it's the response to our request -- Got a rednet message, check if it's the response to our request
local nSenderID, tMessage, sMessageProtocol = p1, p2, p3 local sender_id, message, message_protocol = p1, p2, p3
if sMessageProtocol == "dns" and type(tMessage) == "table" and tMessage.sType == "lookup response" then if message_protocol == "dns" and type(message) == "table" and message.sType == "lookup response" then
if tMessage.sProtocol == sProtocol then if message.sProtocol == protocol then
if sHostname == nil then if hostname == nil then
table.insert(tResults, nSenderID) table.insert(results, sender_id)
elseif tMessage.sHostname == sHostname then elseif message.sHostname == hostname then
return nSenderID return sender_id
end end
end end
end end
else elseif event == "timer" and p1 == timer then
-- Got a timer event, check it's the end of our timeout -- Got a timer event, check it's the end of our timeout
if p1 == timer then
break break
end end
end end
end if results then
if tResults then return table.unpack(results)
return table.unpack(tResults)
end end
return nil return nil
end end
local bRunning = false local started = false
--- Listen for modem messages and converts them into rednet messages, which may --- Listen for modem messages and converts them into rednet messages, which may
-- then be @{receive|received}. -- then be @{receive|received}.
@ -393,51 +391,51 @@ local bRunning = false
-- This is automatically started in the background on computer startup, and -- This is automatically started in the background on computer startup, and
-- should not be called manually. -- should not be called manually.
function run() function run()
if bRunning then if started then
error("rednet is already running", 2) error("rednet is already running", 2)
end end
bRunning = true started = true
while bRunning do while true do
local sEvent, p1, p2, p3, p4 = os.pullEventRaw() local event, p1, p2, p3, p4 = os.pullEventRaw()
if sEvent == "modem_message" then if event == "modem_message" then
-- Got a modem message, process it and add it to the rednet event queue -- Got a modem message, process it and add it to the rednet event queue
local sModem, nChannel, nReplyChannel, tMessage = p1, p2, p3, p4 local modem, channel, reply_channel, message = p1, p2, p3, p4
if nChannel == id_as_channel() or nChannel == CHANNEL_BROADCAST then if channel == id_as_channel() or channel == CHANNEL_BROADCAST then
if type(tMessage) == "table" and type(tMessage.nMessageID) == "number" if type(message) == "table" and type(message.nMessageID) == "number"
and tMessage.nMessageID == tMessage.nMessageID and not tReceivedMessages[tMessage.nMessageID] and message.nMessageID == message.nMessageID and not received_messages[message.nMessageID]
and ((tMessage.nRecipient and tMessage.nRecipient == os.getComputerID()) or nChannel == CHANNEL_BROADCAST) and ((message.nRecipient and message.nRecipient == os.getComputerID()) or channel == CHANNEL_BROADCAST)
and isOpen(sModem) and isOpen(modem)
then then
tReceivedMessages[tMessage.nMessageID] = os.clock() + 9.5 received_messages[message.nMessageID] = os.clock() + 9.5
if not nClearTimer then nClearTimer = os.startTimer(10) end if not prune_received_timer then prune_received_timer = os.startTimer(10) end
os.queueEvent("rednet_message", tMessage.nSender or nReplyChannel, tMessage.message, tMessage.sProtocol) os.queueEvent("rednet_message", message.nSender or reply_channel, message.message, message.sProtocol)
end end
end end
elseif sEvent == "rednet_message" then elseif event == "rednet_message" then
-- Got a rednet message (queued from above), respond to dns lookup -- Got a rednet message (queued from above), respond to dns lookup
local nSenderID, tMessage, sProtocol = p1, p2, p3 local sender, message, protocol = p1, p2, p3
if sProtocol == "dns" and type(tMessage) == "table" and tMessage.sType == "lookup" then if protocol == "dns" and type(message) == "table" and message.sType == "lookup" then
local sHostname = tHostnames[tMessage.sProtocol] local hostname = hostnames[message.sProtocol]
if sHostname ~= nil and (tMessage.sHostname == nil or tMessage.sHostname == sHostname) then if hostname ~= nil and (message.sHostname == nil or message.sHostname == hostname) then
rednet.send(nSenderID, { send(sender, {
sType = "lookup response", sType = "lookup response",
sHostname = sHostname, sHostname = hostname,
sProtocol = tMessage.sProtocol, sProtocol = message.sProtocol,
}, "dns") }, "dns")
end end
end end
elseif sEvent == "timer" and p1 == nClearTimer then elseif event == "timer" and p1 == prune_received_timer then
-- Got a timer event, use it to clear the event queue -- Got a timer event, use it to prune the set of received messages
nClearTimer = nil prune_received_timer = nil
local nNow, bHasMore = os.clock(), nil local now, has_more = os.clock(), nil
for nMessageID, nDeadline in pairs(tReceivedMessages) do for message_id, deadline in pairs(received_messages) do
if nDeadline <= nNow then tReceivedMessages[nMessageID] = nil if deadline <= now then received_messages[message_id] = nil
else bHasMore = true end else has_more = true end
end end
nClearTimer = bHasMore and os.startTimer(10) prune_received_timer = has_more and os.startTimer(10)
end end
end end
end end

View File

@ -59,7 +59,8 @@ end
-- @treturn Redirect The current terminal redirect -- @treturn Redirect The current terminal redirect
-- @since 1.6 -- @since 1.6
-- @usage -- @usage
-- Create a new @{window} which draws to the current redirect target -- Create a new @{window} which draws to the current redirect target.
--
-- window.create(term.current(), 1, 1, 10, 10) -- window.create(term.current(), 1, 1, 10, 10)
term.current = function() term.current = function()
return redirectTarget return redirectTarget

View File

@ -14,10 +14,6 @@ else
print(#tModems .. " modems found.") print(#tModems .. " modems found.")
end end
local function idAsChannel(id)
return (id or os.getComputerID()) % rednet.MAX_ID_CHANNELS
end
local function open(nChannel) local function open(nChannel)
for n = 1, #tModems do for n = 1, #tModems do
local sModem = tModems[n] local sModem = tModems[n]
@ -53,11 +49,16 @@ local ok, error = pcall(function()
tReceivedMessages[tMessage.nMessageID] = true tReceivedMessages[tMessage.nMessageID] = true
tReceivedMessageTimeouts[os.startTimer(30)] = tMessage.nMessageID tReceivedMessageTimeouts[os.startTimer(30)] = tMessage.nMessageID
local recipient_channel = tMessage.nRecipient
if tMessage.nRecipient ~= rednet.CHANNEL_BROADCAST then
recipient_channel = recipient_channel % rednet.MAX_ID_CHANNELS
end
-- Send on all other open modems, to the target and to other repeaters -- Send on all other open modems, to the target and to other repeaters
for n = 1, #tModems do for n = 1, #tModems do
local sOtherModem = tModems[n] local sOtherModem = tModems[n]
peripheral.call(sOtherModem, "transmit", rednet.CHANNEL_REPEAT, nReplyChannel, tMessage) peripheral.call(sOtherModem, "transmit", rednet.CHANNEL_REPEAT, nReplyChannel, tMessage)
peripheral.call(sOtherModem, "transmit", idAsChannel(tMessage.nRecipient), nReplyChannel, tMessage) peripheral.call(sOtherModem, "transmit", recipient_channel, nReplyChannel, tMessage)
end end
-- Log the event -- Log the event

View File

@ -88,6 +88,7 @@ describe("The rednet library", function()
local fake_computer = require "support.fake_computer" local fake_computer = require "support.fake_computer"
local debugx = require "support.debug_ext" local debugx = require "support.debug_ext"
local function dawdle() while true do coroutine.yield() end end
local function computer_with_rednet(id, fn, options) local function computer_with_rednet(id, fn, options)
local computer = fake_computer.make_computer(id, function(env) local computer = fake_computer.make_computer(id, function(env)
local fns = { env.rednet.run } local fns = { env.rednet.run }
@ -105,6 +106,10 @@ describe("The rednet library", function()
end end
end end
if options and options.host then
env.rednet.host("some_protocol", "host_" .. id)
end
return parallel.waitForAny(table.unpack(fns)) return parallel.waitForAny(table.unpack(fns))
end) end)
local modem = fake_computer.add_modem(computer, "back") local modem = fake_computer.add_modem(computer, "back")
@ -203,8 +208,8 @@ describe("The rednet library", function()
env.sleep(10) env.sleep(10)
-- Ensure our pending message store is empty. Bit ugly to prod internals, but there's no other way. -- Ensure our pending message store is empty. Bit ugly to prod internals, but there's no other way.
expect(debugx.getupvalue(rednet.run, "tReceivedMessages")):same({}) expect(debugx.getupvalue(rednet.run, "received_messages")):same({})
expect(debugx.getupvalue(rednet.run, "nClearTimer")):eq(nil) expect(debugx.getupvalue(rednet.run, "prune_received_timer")):eq(nil)
end, { open = true }) end, { open = true })
local computer_3, modem_3 = computer_with_rednet(3, nil, { open = true, rep = true }) local computer_3, modem_3 = computer_with_rednet(3, nil, { open = true, rep = true })
@ -222,5 +227,22 @@ describe("The rednet library", function()
fake_computer.advance_all(computers, 10) fake_computer.advance_all(computers, 10)
fake_computer.run_all(computers, { computer_1, computer_2 }) fake_computer.run_all(computers, { computer_1, computer_2 })
end) end)
it("handles lookups between computers with massive IDs", function()
local id_1, id_3 = 24283947, 93428798
local computer_1, modem_1 = computer_with_rednet(id_1, function(rednet)
local ids = { rednet.lookup("some_protocol") }
expect(ids):same { id_3 }
end, { open = true })
local computer_2, modem_2 = computer_with_rednet(2, nil, { open = true, rep = true })
local computer_3, modem_3 = computer_with_rednet(id_3, dawdle, { open = true, host = true })
fake_computer.add_modem_edge(modem_1, modem_2)
fake_computer.add_modem_edge(modem_2, modem_3)
local computers = { computer_1, computer_2, computer_3 }
fake_computer.run_all(computers, false)
fake_computer.advance_all(computers, 3)
fake_computer.run_all(computers, { computer_1 })
end)
end) end)
end) end)