250 lines
4.5 KiB
Lua
250 lines
4.5 KiB
Lua
local MSG = "<<~"
|
|
local CHN = "~>>"
|
|
|
|
local PACKET_MAX_SIZE = 200
|
|
local MAX_TICS = TICRATE * 2
|
|
|
|
local function encode(data)
|
|
data = string.gsub(data, "\n", "\\n")
|
|
data = string.gsub(data, "\t", "\\t")
|
|
return data
|
|
end
|
|
|
|
local function decode(data)
|
|
data = string.gsub(data, "\\n", "\n")
|
|
data = string.gsub(data, "\\t", "\t")
|
|
return data
|
|
end
|
|
|
|
local transmitters = {}
|
|
|
|
local function Transmitter(channel, opts)
|
|
assert(channel, "Transmitter: channel is required")
|
|
opts = $ or {}
|
|
|
|
return {
|
|
packets = {},
|
|
|
|
push = function(this, packet)
|
|
table.insert(this.packets, MSG..channel..CHN..packet)
|
|
end,
|
|
|
|
pop = function(this)
|
|
return table.remove(this.packets)
|
|
end,
|
|
|
|
sendPacket = function(this)
|
|
local sender = consoleplayer or server
|
|
COM_BufInsertText(sender, "say \""..this:pop().."\"")
|
|
return not #this.packets
|
|
end,
|
|
|
|
writeHeader = function(this)
|
|
this:push(#this.packets)
|
|
end,
|
|
|
|
close = function(this)
|
|
for i, tr in ipairs(transmitters) do
|
|
if tr == this then
|
|
table.remove(transmitters, i)
|
|
break
|
|
end
|
|
end
|
|
|
|
if opts.free then
|
|
opts.free(this, opts.handle)
|
|
end
|
|
end,
|
|
|
|
enqueue = function(this)
|
|
table.insert(transmitters, this)
|
|
end,
|
|
|
|
transmit = function(this, data)
|
|
assert(data, "Transmitter: nil data")
|
|
|
|
data = encode(data)
|
|
|
|
if opts.stream then
|
|
assert(
|
|
#data < PACKET_MAX_SIZE,
|
|
"Transmitter: data packet too large for stream"
|
|
)
|
|
|
|
this:push(data)
|
|
this:enqueue()
|
|
return
|
|
end
|
|
|
|
local sub
|
|
for i = 1, #data, PACKET_MAX_SIZE do
|
|
sub = data:sub(i, min(#data, i + PACKET_MAX_SIZE-1))
|
|
this:push(sub)
|
|
end
|
|
|
|
this:writeHeader()
|
|
this:enqueue()
|
|
end
|
|
}
|
|
end
|
|
rawset(_G, "lb_transmitter", Transmitter)
|
|
|
|
addHook("ThinkFrame", function()
|
|
if not (#transmitters and leveltime) then return end
|
|
|
|
local index = (leveltime % #transmitters) + 1
|
|
local transmitter = transmitters[index]
|
|
|
|
if transmitter:sendPacket() then
|
|
transmitter:close()
|
|
end
|
|
end)
|
|
|
|
local Channels = {
|
|
channel = {},
|
|
|
|
add = function(this, ch, reciever)
|
|
this.channel[ch] = $ or {}
|
|
table.insert(this.channel[ch], reciever)
|
|
end,
|
|
|
|
chan = function(this, ch)
|
|
local c = this.channel[ch]
|
|
local i = c and #c + 1 or 0
|
|
return function()
|
|
if i > 1 then
|
|
i = i - 1
|
|
return i, c[i]
|
|
end
|
|
end
|
|
end,
|
|
|
|
remove = function(this, ch, index)
|
|
table.remove(this.channel[ch], index)
|
|
end,
|
|
|
|
recieve = function(this)
|
|
return function(packet, ch)
|
|
for i, reciever in this:chan(ch) do
|
|
if reciever:push(packet) then
|
|
reciever:close()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
}
|
|
|
|
addHook("ThinkFrame", function()
|
|
for _, ch in pairs(Channels.channel) do
|
|
for _, rec in pairs(ch) do
|
|
if rec:tick() then
|
|
rec:close()
|
|
end
|
|
end
|
|
end
|
|
end)
|
|
|
|
local function Reciever(channel, callback, opts)
|
|
assert(callback, "Reciever: callback is required")
|
|
opts = $ or {}
|
|
|
|
local ticker, pusher
|
|
local MAX_TICS = MAX_TICS
|
|
if opts.stream then
|
|
ticker = function(this) end
|
|
|
|
pusher = function(this, packet)
|
|
callback(decode(packet), opts.handle)
|
|
end
|
|
else
|
|
ticker = function(this)
|
|
this.tics = $ + 1
|
|
return this.tics > MAX_TICS
|
|
end
|
|
|
|
pusher = function(this, packet)
|
|
if not this.len then
|
|
this:recieveHeader(packet)
|
|
return
|
|
end
|
|
|
|
table.insert(this.packets, packet)
|
|
|
|
if opts.progress then
|
|
opts.progress(#this.packets, this.len, opts.handle)
|
|
end
|
|
|
|
if #this.packets >= this.len then
|
|
this:finish()
|
|
return true
|
|
end
|
|
|
|
this.tics = 0
|
|
end
|
|
end
|
|
|
|
return {
|
|
len,
|
|
packets = {},
|
|
tics = 0,
|
|
tick = ticker,
|
|
|
|
close = function(this)
|
|
for i, rec in Channels:chan(channel) do
|
|
if rec == this then
|
|
Channels:remove(channel, i)
|
|
if opts.free then
|
|
opts.free(this, opts.handle)
|
|
end
|
|
return
|
|
end
|
|
end
|
|
end,
|
|
|
|
push = pusher,
|
|
|
|
recieveHeader = function(this, header)
|
|
local len = tonumber(header)
|
|
assert(len ~= nil,
|
|
"Reciever: invalid header '"..(header or "nil").."'")
|
|
this.len = len
|
|
end,
|
|
|
|
listen = function(this)
|
|
Channels:add(channel, this)
|
|
end,
|
|
|
|
pop = function(this)
|
|
return table.remove(this.packets)
|
|
end,
|
|
|
|
finish = function(this)
|
|
local data = ""
|
|
|
|
local s = this:pop()
|
|
while s do
|
|
data = $..s
|
|
s = this:pop()
|
|
end
|
|
|
|
callback(decode(data), opts.handle)
|
|
end
|
|
}
|
|
end
|
|
rawset(_G, "lb_reciever", Reciever)
|
|
|
|
local function scan(sym, msg, fn)
|
|
if msg:find(sym) == 1 then
|
|
msg = msg:sub(#sym+1)
|
|
local chi = msg:find(CHN)
|
|
local ch = msg:sub(1, chi-1)
|
|
msg = msg:sub(chi+#CHN)
|
|
fn(msg, ch)
|
|
return true
|
|
end
|
|
end
|
|
|
|
addHook("PlayerMsg", function(source, type, target, msg)
|
|
return scan(MSG, msg, Channels:recieve())
|
|
end)
|