95 lines
3.2 KiB
Lua
95 lines
3.2 KiB
Lua
local function SendUDP(content)
|
|
if content then
|
|
helpers.send_udp(options.udpPort, content, options.senderPlayerIndex)
|
|
else
|
|
error("Missing udpPort or Content")
|
|
end
|
|
end
|
|
|
|
---Chunks content into appropriately sized pieces, respecting header lines
|
|
---Chunks content greedily to minimize UDP calls
|
|
---Fills 200KB buffer as completely as possible, including multiple headers if they fit
|
|
---Only prepends header when chunk starts mid-section
|
|
---@return table chunks Array of {header=string|nil, content=string}---@param content string
|
|
---@param maxSizekB number|nil Default 200KB
|
|
local function ChunkContent(content, maxSizekB)
|
|
local maxSize = (maxSizekB or 200) * 1024
|
|
local len = #content
|
|
local chunks = {}
|
|
|
|
-- 1. Pre-compute headers (O(N))
|
|
local headers = {}
|
|
local hPos = 1
|
|
while true do
|
|
local hStart, hEnd, hText = content:find("%%-%-%-(.-)%%-%-%-\n", hPos)
|
|
if not hStart then break end
|
|
table.insert(headers, {startPos = hStart, text = "---" .. hText .. "---"})
|
|
hPos = hEnd + 1
|
|
end
|
|
|
|
local start = 1
|
|
local headerIdx = 1
|
|
local activeHeaderText = nil
|
|
|
|
while start <= len do
|
|
while headerIdx <= #headers and headers[headerIdx].startPos <= start do
|
|
activeHeaderText = headers[headerIdx].text
|
|
headerIdx = headerIdx + 1
|
|
end
|
|
|
|
local isAtHeaderStart = (headerIdx > 1 and headers[headerIdx-1].startPos == start)
|
|
local needsPrepend = (not isAtHeaderStart) and (activeHeaderText ~= nil)
|
|
local overhead = needsPrepend and (#activeHeaderText + 1) or 0
|
|
local availableSpace = maxSize - overhead
|
|
|
|
if availableSpace <= 0 then error("Header exceeds maxSize") end
|
|
|
|
local target = math.min(start + availableSpace - 1, len)
|
|
|
|
if target == len then
|
|
table.insert(chunks, {header = needsPrepend and activeHeaderText or nil, content = content:sub(start, len)})
|
|
break
|
|
end
|
|
|
|
-- Optimized C-level search for newline
|
|
local chunkView = content:sub(start, target)
|
|
local lastNewline = chunkView:match(".*()\n")
|
|
|
|
if not lastNewline then
|
|
error("Line too long at byte " .. start)
|
|
end
|
|
|
|
local splitPos = start + lastNewline - 1
|
|
table.insert(chunks, {header = needsPrepend and activeHeaderText or nil, content = content:sub(start, splitPos)})
|
|
start = splitPos + 1
|
|
end
|
|
return chunks
|
|
end
|
|
|
|
---Sends content in chunks with optional header
|
|
---If content starts with header (---<headerline>---), it's automatically repeated in each chunk
|
|
---@param content string
|
|
---@param maxSizekB number|nil Default 200KB
|
|
function SendChunked(content, maxSizekB)
|
|
if not content or #content == 0 then
|
|
error("Missing or empty content")
|
|
end
|
|
|
|
local maxSize = (maxSizekB or 200) * 1024
|
|
|
|
-- Content is smaller than maxSize, so send immediately
|
|
if #content <= maxSize then
|
|
SendUDP(content)
|
|
return
|
|
end
|
|
|
|
local chunks = ChunkContent(content, maxSizekB)
|
|
for _, chunk in ipairs(chunks) do
|
|
if chunk.header then
|
|
SendUDP(chunk.header .. "\n" .. chunk.content)
|
|
else
|
|
SendUDP(chunk.content)
|
|
end
|
|
end
|
|
end
|