You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

508 lines
13 KiB

local serialization = require("serialization")
local uuid = require("uuid")
local computer = require("computer")
local Version = {0, 1}
local Message = {
Hello = 0x48454c4f,
Register = 0x52454749,
Deregister = 0x44524547,
NounRequest = 0x4e524551,
NounResponse = 0x4e524553,
VerbRequest = 0x56524551,
VerbResponse = 0x56524553,
ListenRequest = 0x4c524551,
ListenNotify = 0x4c4e4f54,
ListenCancel = 0x4c535450,
}
local query_param = {
__call=function(self, v)
return {ty=self.ty, v=v}
end,
}
local Query = {
Change = {ty=0x434847},
Rising = {ty=0x524953},
Falling = {ty=0x46414c},
Equals = setmetatable({ty=0x455155, invalid=true}, query_param),
Above = setmetatable({ty=0x414256, invalid=true}, query_param),
Below = setmetatable({ty=0x424c4f, invalid=true}, query_param),
}
-- Network ---------------------------------------------------------------- {{{
local Network = {
default_port = 0xBC00 | (Version[1] << 4) | Version[2],
}
function Network:new(modem, port)
if type(modem) == "number" then
port = modem
modem = nil
end
local self = {
modem = modem or require("component").modem,
port = port or Network.default_port,
event = require("event"),
}
setmetatable(self, {__index=Network})
return self
end
setmetatable(Network, {__call=Network.new})
function Network:start(callback)
self.modem.open(self.port)
self.listener = function(_, addr_lo, addr_remote, port, _, msg)
-- Filter everything we don't care about
if addr_lo ~= self.modem.address then return end
if port ~= self.port then return end
callback(addr_remote, serialization.unserialize(msg))
end
self.event.listen("modem_message", self.listener)
end
function Network:send(addr, msg)
self.modem.send(addr, self.port, serialization.serialize(msg))
end
function Network:broadcast(msg)
self.modem.broadcast(self.port, serialization.serialize(msg))
end
function Network:pull(filter, timeout)
local last_msg
local ev = self.event.pullFiltered(timeout, function(ev, addr_lo, addr_remote, port, _, msg)
if ev ~= "modem_message" then return false end
if addr_lo ~= self.modem.address then return false end
if port ~= self.port then return false end
last_msg = serialization.unserialize(msg)
return filter(addr_remote, last_msg)
end)
if ev ~= nil then
return last_msg
else
return nil, "timeout"
end
end
function Network:stop()
self.event.ignore("modem_message", self.listener)
self.modem.close(self.port)
end
-- }}}
-- BaseControl ------------------------------------------------------------ {{{
local BaseControl = {
version = Version,
Message = Message,
Query = Query,
Network = Network,
}
-- Lifecycle {{{
function BaseControl:new(network)
local self = {
local_nouns = {},
local_verbs = {},
remote_nouns = {},
remote_verbs = {},
listeners = {},
live = false,
network = network or Network:new(),
}
setmetatable(self, {__index=BaseControl, __call=BaseControl.new})
self.network:start(function(remote, msg)
self:_network_handler(remote, msg)
end)
-- Announce own presence so registers start coming in
self.network:broadcast{ty=Message.Hello}
return self
end
function BaseControl:register(name, value)
if self.live then
error("can't register \""..name.."\" after finalizing")
end
if type(value) == "function" then
if self.local_verbs[name] ~= nil then
error("\""..name.."\" already registered")
else
self.local_verbs[name] = value
end
else
if self.local_nouns[name] ~= nil then
error("\""..name.."\" already registered")
else
self.local_nouns[name] = value
end
end
end
function BaseControl:finalize(waits, timeout)
local self = self
if self == BaseControl then
-- Called as a constructor
self = BaseControl:new()
end
-- Announce own nouns and verbs if this is the first
-- call to finalize() and we have registered names
if not self.live then
local nouns, verbs = self:nouns(true), self:verbs(true)
if #nouns > 0 or #verbs > 0 then
self.network:broadcast{
ty=Message.Register,
nouns=nouns,
verbs=verbs,
}
end
end
if #(waits or {}) ~= 0 then
-- Wait for requested names
local remaining = {} -- Table with all remaining names as keys
local num_remaining = 0 -- Number of remaining names
for _, name in ipairs(waits) do
if not self:has_noun(name) and not self:has_verb(name) then
remaining[name] = true
num_remaining = num_remaining + 1
end
end
local tstart = computer.uptime()
local timeout_remaining = timeout
while num_remaining > 0 do
if timeout ~= nil then
timeout_remaining = timeout - (computer.uptime() - tstart)
if timeout_remaining <= 0 then
error("timeout")
end
end
-- Wait until the next register arrives
local msg = self.network:pull(function(remote, msg)
return msg.ty == Message.Register
end, timeout_remaining)
if msg ~= nil then
for _, noun in ipairs(msg.nouns or {}) do
if remaining[noun] then
num_remaining = num_remaining - 1
remaining[noun] = nil
end
end
for _, verb in ipairs(msg.verbs or {}) do
if remaining[verb] then
num_remaining = num_remaining - 1
remaining[verb] = nil
end
end
end
end
end
self.live = true
return self
end
function BaseControl:close()
if self.live == true then
-- Deregister own nouns and verbs if we were live
local nouns, verbs = self:nouns(true), self:verbs(true)
if #nouns > 0 or #verbs > 0 then
self.network:broadcast{
ty=Message.Deregister,
nouns=nouns,
verbs=verbs,
}
end
end
self.network:stop()
setmetatable(self, nil)
end
-- }}}
-- Nouns {{{
function BaseControl:has_noun(noun)
return self.local_nouns[noun] ~= nil or self.remote_nouns[noun] ~= nil
end
function BaseControl:nouns(local_only)
local nouns = {}
for noun in pairs(self.local_nouns) do
table.insert(nouns, noun)
end
if not local_only then
for noun in pairs(self.remote_nouns) do
table.insert(nouns, noun)
end
end
return nouns
end
function BaseControl:get(noun, timeout)
if self.local_nouns[noun] ~= nil then
return self.local_nouns[noun]
elseif self.local_verbs[noun] ~= nil then
error("\""..noun.."\" is not a noun")
elseif self.remote_nouns[noun] ~= nil then
self.network:send(self.remote_nouns[noun], {
ty=Message.NounRequest,
noun=noun,
})
local answer = self.network:pull(function(remote, msg)
if remote ~= self.remote_nouns[noun] then return false end
if msg.ty ~= Message.NounResponse then return false end
if msg.noun ~= noun then return false end
return true
end, timeout)
if answer ~= nil then
return answer.value
else
error("timeout")
end
else
return error("unknown noun \""..noun.."\"")
end
end
function BaseControl:set(name, value)
if self.local_nouns[name] ~= nil then
if type(value) == "function" then
error("\""..name.."\" can't be cast into a verb")
else
local old = self.local_nouns[name]
self.local_nouns[name] = value
if self.listeners[name] ~= nil then
for id, l in pairs(self.listeners[name]) do
if (l.query.ty == Query.Change.ty and value ~= old)
or (l.query.ty == Query.Rising.ty and value > old)
or (l.query.ty == Query.Falling.ty and value < old)
or (l.query.ty == Query.Equals.ty and value == l.query.v)
or (l.query.ty == Query.Above.ty and value > l.query.v)
or (l.query.ty == Query.Below.ty and value < l.query.v)
then
if l.callback ~= nil then
l.callback(value)
else
self.network:send(l.addr, {
ty=Message.ListenNotify,
noun=name,
id=id,
value=value,
})
end
end
end
end
end
elseif self.local_verbs[name] ~= nil then
if type(value) ~= "function" then
error("\""..name.."\" can't be cast into a noun")
else
self.local_verbs[name] = value
end
else
error("\""..name.."\" is not a local")
end
end
-- }}}
-- Verbs {{{
function BaseControl:has_verb(verb)
return self.local_verbs[verb] ~= nil or self.remote_verbs[verb] ~= nil
end
function BaseControl:verbs(local_only)
local verbs = {}
for verb in pairs(self.local_verbs) do
table.insert(verbs, verb)
end
if not local_only then
for verb in pairs(self.remote_verbs) do
table.insert(verbs, verb)
end
end
return verbs
end
function BaseControl:call(verb, ...)
if self.local_verbs[verb] ~= nil then
self.local_verbs[verb](...)
return true
elseif self.remote_verbs[verb] ~= nil then
self.network:send(self.remote_verbs[verb], {
ty=Message.VerbRequest,
verb=verb,
param={...},
})
return true
else
return false
end
end
function BaseControl:call_sync(verb, ...)
if self.local_verbs[verb] ~= nil then
return self.local_verbs[verb](...)
elseif self.remote_verbs[verb] ~= nil then
self.network:send(self.remote_verbs[verb], {
ty=Message.VerbRequest,
verb=verb,
param={...},
sync=true,
})
local answer = self.network:pull(function(remote, msg)
if remote ~= self.remote_verbs[verb] then return false end
if msg.ty ~= Message.VerbResponse then return false end
if msg.verb ~= verb then return false end
return true
end, timeout)
if answer ~= nil then
return table.unpack(answer.ret)
else
error("timeout")
end
else
error("unknown verb \""..verb.."\"")
end
end
-- }}}
-- Listening {{{
function BaseControl:listen(noun, query, callback)
if query.invalid then
error("invalid query, forgot a parameter?")
end
if self.listeners[noun] == nil then
self.listeners[noun] = {}
end
local id = uuid.next()
self.listeners[noun][id] = {
query=query,
callback=callback,
}
if self.local_nouns[noun] ~= nil then
return id, true
elseif self.remote_nouns[noun] ~= nil then
self.network:send(self.remote_nouns[noun], {
ty=Message.ListenRequest,
noun=noun,
id=id,
query=query,
})
return id, true
else
return id, false
end
end
function BaseControl:cancel(noun, id)
if self.listeners[noun] == nil or self.listeners[noun][id] == nil then
error("can't cancel unknown listener on \""..noun.."\"")
end
if self.local_nouns[noun] == nil and self.remote_nouns[noun] ~= nil then
self.network:send(self.remote_nouns[noun], {
ty=Message.ListenCancel,
noun=noun,
id=id,
})
end
self.listeners[noun][id] = nil
end
-- }}}
-- Network Handler {{{
function BaseControl:_network_handler(remote, msg)
if msg.ty == Message.Hello then
if self.live then
-- If we are live already, answer with own nouns and verbs
self.network:send(remote, {
ty=Message.Register,
nouns=self:nouns(true),
verbs=self:verbs(true),
})
end
elseif msg.ty == Message.Register then
for _, noun in ipairs(msg.nouns or {}) do
self.remote_nouns[noun] = remote
for id, l in pairs(self.listeners[noun] or {}) do
self.network:send(remote, {
ty=Message.ListenRequest,
noun=noun,
id=id,
query=l.query,
})
end
end
for _, verb in ipairs(msg.verbs or {}) do
self.remote_verbs[verb] = remote
end
elseif msg.ty == Message.Deregister then
for _, noun in ipairs(msg.nouns or {}) do
if self.remote_nouns[noun] == remote then
self.remote_nouns[noun] = nil
end
end
for _, verb in ipairs(msg.verbs or {}) do
if self.remote_verbs[verb] == remote then
self.remote_verbs[verb] = nil
end
end
elseif msg.ty == Message.NounRequest then
self.network:send(remote, {
ty=Message.NounResponse,
noun=msg.noun,
value=self.local_nouns[msg.noun],
})
elseif msg.ty == Message.NounResponse then
-- Handled via pull
elseif msg.ty == Message.VerbRequest then
if self.local_verbs[msg.verb] ~= nil then
local ret = table.pack(self.local_verbs[msg.verb](table.unpack(msg.param)))
if msg.sync then
self.network:send(remote, {
ty=Message.VerbResponse,
verb=msg.verb,
ret=ret,
})
end
end
elseif msg.ty == Message.VerbResponse then
-- Handled via pull
elseif msg.ty == Message.ListenRequest then
if self.local_nouns[msg.noun] ~= nil then
if self.listeners[msg.noun] == nil then
self.listeners[msg.noun] = {}
end
self.listeners[msg.noun][msg.id] = {
query=msg.query,
addr=remote,
}
end
elseif msg.ty == Message.ListenNotify then
if self.listeners[msg.noun][msg.id] ~= nil then
self.listeners[msg.noun][msg.id].callback(msg.value)
end
elseif msg.ty == Message.ListenCancel then
if self.listeners[msg.noun][msg.id] ~= nil then
if self.listeners[msg.noun][msg.id].addr == remote then
self.listeners[msg.noun][msg.id] = nil
end
end
else
error("TODO: MessageType Unknown")
end
end
-- }}}
-- }}}
return setmetatable(BaseControl, {__call=BaseControl.new})