1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-10-01 08:20:47 +00:00

Make Rednet deduplication more efficient (#925)

This commit is contained in:
JackMacWindows 2021-09-26 16:15:37 -04:00 committed by GitHub
parent 297426419b
commit 7bb7b5e638
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 97 additions and 25 deletions

View File

@ -30,8 +30,8 @@ CHANNEL_REPEAT = 65533
MAX_ID_CHANNELS = 65500
local tReceivedMessages = {}
local tReceivedMessageTimeouts = {}
local tHostnames = {}
local nClearTimer
local function id_as_channel(id)
return (id or os.getComputerID()) % MAX_ID_CHANNELS
@ -138,8 +138,8 @@ function send(nRecipient, message, sProtocol)
-- 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
local nMessageID = math.random(1, 2147483647)
tReceivedMessages[nMessageID] = true
tReceivedMessageTimeouts[os.startTimer(30)] = nMessageID
tReceivedMessages[nMessageID] = os.clock() + 9.5
if not nClearTimer then nClearTimer = os.startTimer(10) end
-- Create the message
local nReplyChannel = id_as_channel()
@ -408,8 +408,8 @@ function run()
and tMessage.nMessageID == tMessage.nMessageID and not tReceivedMessages[tMessage.nMessageID]
and ((tMessage.nRecipient and tMessage.nRecipient == os.getComputerID()) or nChannel == CHANNEL_BROADCAST)
then
tReceivedMessages[tMessage.nMessageID] = true
tReceivedMessageTimeouts[os.startTimer(30)] = tMessage.nMessageID
tReceivedMessages[tMessage.nMessageID] = os.clock() + 9.5
if not nClearTimer then nClearTimer = os.startTimer(10) end
os.queueEvent("rednet_message", tMessage.nSender or nReplyChannel, tMessage.message, tMessage.sProtocol)
end
end
@ -428,14 +428,15 @@ function run()
end
end
elseif sEvent == "timer" then
elseif sEvent == "timer" and p1 == nClearTimer then
-- Got a timer event, use it to clear the event queue
local nTimer = p1
local nMessage = tReceivedMessageTimeouts[nTimer]
if nMessage then
tReceivedMessageTimeouts[nTimer] = nil
tReceivedMessages[nMessage] = nil
nClearTimer = nil
local nNow, bHasMore = os.clock(), nil
for nMessageID, nDeadline in pairs(tReceivedMessages) do
if nDeadline <= nNow then tReceivedMessages[nMessageID] = nil
else bHasMore = true end
end
nClearTimer = bHasMore and os.startTimer(10)
end
end
end

View File

@ -86,21 +86,22 @@ describe("The rednet library", function()
describe("on fake computers", function()
local fake_computer = require "support.fake_computer"
local debugx = require "support.debug_ext"
local function computer_with_rednet(id, fn, options)
local computer = fake_computer.make_computer(id, function(_ENV)
local fns = { _ENV.rednet.run }
local computer = fake_computer.make_computer(id, function(env)
local fns = { env.rednet.run }
if options and options.rep then
fns[#fns + 1] = function() _ENV.dofile("rom/programs/rednet/repeat.lua") end
fns[#fns + 1] = function() env.dofile("rom/programs/rednet/repeat.lua") end
end
if fn then
fns[#fns + 1] = function()
if options and options.open then
_ENV.rednet.open("back")
_ENV.os.queueEvent("x") _ENV.os.pullEvent("x")
env.rednet.open("back")
env.os.queueEvent("x") env.os.pullEvent("x")
end
return fn(_ENV.rednet, _ENV)
return fn(env.rednet, env)
end
end
@ -140,7 +141,7 @@ describe("The rednet library", function()
end)
it("sends and receives rednet messages", function()
local computer_1, modem_1 = computer_with_rednet(1, function(rednet, _ENV)
local computer_1, modem_1 = computer_with_rednet(1, function(rednet)
rednet.send(2, "Hello")
end, { open = true })
local computer_2, modem_2 = computer_with_rednet(2, function(rednet)
@ -154,7 +155,7 @@ describe("The rednet library", function()
end)
it("repeats messages between computers", function()
local computer_1, modem_1 = computer_with_rednet(1, function(rednet, _ENV)
local computer_1, modem_1 = computer_with_rednet(1, function(rednet)
rednet.send(3, "Hello")
end, { open = true })
local computer_2, modem_2 = computer_with_rednet(2, nil, { open = true, rep = true })
@ -171,7 +172,7 @@ describe("The rednet library", function()
it("repeats messages 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, _ENV)
local computer_1, modem_1 = computer_with_rednet(id_1, function(rednet)
rednet.send(id_3, "Hello")
local id, message = rednet.receive()
expect { id, message }:same { id_3, "World" }
@ -187,5 +188,39 @@ describe("The rednet library", function()
fake_computer.run_all({ computer_1, computer_2, computer_3 }, { computer_1, computer_3 })
end)
it("ignores duplicate messages", function()
local computer_1, modem_1 = computer_with_rednet(1, function(rednet)
rednet.send(2, "Hello")
end, { open = true })
local computer_2, modem_2 = computer_with_rednet(2, function(rednet, env)
local id, message = rednet.receive()
expect { id, message }:same { 1, "Hello" }
local id = rednet.receive(nil, 1)
expect(id):eq(nil)
env.sleep(10)
-- 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, "nClearTimer")):eq(nil)
end, { open = true })
local computer_3, modem_3 = computer_with_rednet(3, nil, { open = true, rep = true })
fake_computer.add_modem_edge(modem_1, modem_3)
fake_computer.add_modem_edge(modem_3, modem_2)
local computer_4, modem_4 = computer_with_rednet(4, nil, { open = true, rep = true })
fake_computer.add_modem_edge(modem_1, modem_4)
fake_computer.add_modem_edge(modem_4, modem_2)
local computers = { computer_1, computer_2, computer_3, computer_4 }
fake_computer.run_all(computers, false)
fake_computer.advance_all(computers, 1)
fake_computer.run_all(computers, { computer_1 })
fake_computer.advance_all(computers, 10)
fake_computer.run_all(computers, { computer_1, computer_2 })
end)
end)
end)

View File

@ -0,0 +1,9 @@
local function getupvalue(fn, name)
for i = 1, debug.getinfo(fn, "u").nups do
local up_name, value = debug.getupvalue(fn, i)
if up_name == name then return value end
end
error("Cannot find upvalue with name " .. name, 2)
end
return { getupvalue = getupvalue }

View File

@ -7,7 +7,7 @@ end
local safe_globals = {
"assert", "bit32", "coroutine", "debug", "error", "fs", "getmetatable", "io", "ipairs", "math", "next", "pairs",
"pcall", "print", "printError", "rawequal", "rawget", "rawlen", "rawset", "select", "setmetatable", "string",
"table", "term", "tonumber", "tostring", "type", "utf8", "xpcall",
"table", "term", "textutils", "tonumber", "tostring", "type", "utf8", "xpcall",
}
--- Create a fake computer.
@ -15,6 +15,7 @@ local function make_computer(id, fn)
local env = setmetatable({}, _G)
local peripherals = {}
local pending_timers, next_timer, clock = {}, 0, 0
local events = { { n = 1, env } }
local function queue_event(...) events[#events + 1] = table.pack(...) end
@ -40,9 +41,18 @@ local function make_computer(id, fn)
if event_data[1] == "terminate" then error("Terminated", 0) end
return table.unpack(event_data, 1, event_data.n)
end,
startTimer = function() return 0 end,
clock = function() return 0 end,
startTimer = function(delay)
local t = next_timer
pending_timers[t], next_timer = clock + delay, next_timer + 1
return t
end,
clock = function() return clock end,
sleep = function(time)
local timer = env.os.startTimer(time or 0)
repeat local _, id = env.os.pullEvent("timer") until id == timer
end,
}
env.sleep = env.os.sleep
env.dofile = function(path)
local fn, err = loadfile(path, nil, env)
if fn then return fn() else error(err, 2) end
@ -67,7 +77,17 @@ local function make_computer(id, fn)
end
end
return { env = env, peripherals = peripherals, queue_event = queue_event, step = step, co = co }
local function advance(dt)
clock = clock + dt
for id, clk in pairs(pending_timers) do
if clk <= clock then
queue_event("timer", id)
pending_timers[id] = nil
end
end
end
return { env = env, peripherals = peripherals, queue_event = queue_event, step = step, co = co, advance = advance }
end
local function parse_channel(c)
@ -137,17 +157,24 @@ local function run_all(computers, require_done)
for _, computer in pairs(computers) do
if coroutine.status(computer.co) ~= "dead" and (type(require_done) ~= "table" or require_done[computer]) then
error(debug.traceback(computer.co, "Computer did not shutdown"), 0)
error(debug.traceback(computer.co, ("Computer #%d did not shutdown"):format(computer.env.os.getComputerID())), 0)
end
end
end
end
--- Advance all computers by a given time.
local function advance_all(computers, dt)
for _, computer in pairs(computers) do computer.advance(dt) end
end
return {
make_computer = make_computer,
add_modem = add_modem,
add_modem_edge = add_modem_edge,
add_api = add_api,
step_all = step_all,
run_all = run_all,
advance_all = advance_all,
}