Skip to content

Instantly share code, notes, and snippets.

@Earu
Created February 5, 2020 01:07
Show Gist options
  • Save Earu/ce2672a4575d09253950bbcce084a2cb to your computer and use it in GitHub Desktop.
Save Earu/ce2672a4575d09253950bbcce084a2cb to your computer and use it in GitHub Desktop.
Garry's Mod image networking (proof of concept)
local NET_SEND_OWN_IMG = "IMG_SEND_OWN_IMG"
local NET_TRANSFER_IMG = "IMG_TRANSFER_IMG"
local NET_REQ_CHECKSUM = "IMG_REQ_CHECKSUM"
local CHUNK_SIZE = 60000
local THROTTLE_DELAY = 0.05 -- in seconds
if CLIENT then
local CACHE_DIRECTORY = "img_cache"
if not file.Exists(CACHE_DIRECTORY, "DATA") then
file.CreateDir(CACHE_DIRECTORY)
end
local function send_image(ext, base64)
print(#base64)
print("--------------------------")
local hash = util.CRC(base64)
net.Start(NET_SEND_OWN_IMG)
net.WriteBool(true) -- is_start
net.WriteString(hash)
net.WriteString(ext)
net.SendToServer()
local len = #base64
local chunk_amount = math.ceil(len / CHUNK_SIZE)
local i = 0
print("CHUNKS: ", chunk_amount)
-- throttle sendings because gmod is gay
timer.Create("ECImage_" .. hash, THROTTLE_DELAY, chunk_amount, function()
local is_final = i + 1 == chunk_amount
net.Start(NET_SEND_OWN_IMG)
net.WriteBool(false)
net.WriteString(hash)
print("CLIENT CHUNK: ", i + 1)
if is_final then
local pos = (CHUNK_SIZE - 1)
net.WriteString(base64:sub(-pos))
print("CLIENT: FINAL CHUNK")
else
local offset = CHUNK_SIZE * i
net.WriteString(base64:sub(offset + 1, offset + CHUNK_SIZE))
end
net.WriteBool(is_final)
net.SendToServer()
i = i + 1
end)
end
net.Receive(NET_REQ_CHECKSUM, function()
local hash = net.ReadString()
local ext = net.ReadString()
local has_file = file.Exists(("%s/%s.%s"):format(CACHE_DIRECTORY, hash, ext), "DATA")
-- cant start another networking context, here, so yeah
timer.Simple(0, function()
net.Start(NET_REQ_CHECKSUM)
net.WriteString(hash)
net.WriteBool(has_file)
net.SendToServer()
end)
end)
local transfers = {}
net.Receive(NET_TRANSFER_IMG, function()
local hash = net.ReadString()
local ext = net.ReadString()
local chunk = net.ReadString()
local is_final = net.ReadBool()
transfers[hash] = transfers[hash] or {}
local transfer = transfers[hash]
local i = table.insert(transfer, chunk)
print("FINAL CHUNK: ", i)
if is_final then
print("FINISHED")
local file_path = ("%s/%s.%s"):format(CACHE_DIRECTORY, hash, ext)
local base64 = table.concat(transfer)
hook.Run("ReceivedImageData", file_path, base64)
transfers[hash] = nil
end
end)
return send_image
end
if SERVER then
util.AddNetworkString(NET_SEND_OWN_IMG)
util.AddNetworkString(NET_TRANSFER_IMG)
util.AddNetworkString(NET_REQ_CHECKSUM)
-- callback: function(ply, has_hash)
local checksum_reqs = {}
local function request_hash(hash, ext, callback)
for _, ply in ipairs(player.GetAll()) do
checksum_reqs[ply:AccountID() .. hash] = callback
end
net.Start(NET_REQ_CHECKSUM)
net.WriteString(hash)
net.WriteString(ext)
net.Broadcast()
end
net.Receive(NET_REQ_CHECKSUM, function(_, ply)
local hash = net.ReadString()
local has_file = net.ReadBool()
local key = ply:AccountID() .. hash
local callback = checksum_reqs[key]
if callback then
callback(ply, has_file)
checksum_reqs[key] = nil
end
end)
local in_transfers = {}
local out_transfers = {}
local function start_transfer()
local hash = net.ReadString()
local ext = net.ReadString()
in_transfers[hash] = {
Chunks = {},
Extension = ext,
Completed = false,
}
-- request hash from all connected to clients
request_hash(hash, ext, function(ply, has_hash)
if has_hash then return end
-- create an "out transfer" to keep data in the right order
-- as we network it
local key = ply:AccountID() .. hash
out_transfers[key] = 1
local timer_name = "Image_" .. key
timer.Create(timer_name, THROTTLE_DELAY, 0, function()
local in_transfer = in_transfers[hash]
if not in_transfer then return end
local chunk = in_transfer.Chunks[out_transfers[key]]
-- there are more chunks but we dont have them yet
if not chunk then return end
local data_len = #chunk
local is_final =
out_transfers[key] == #in_transfer.Chunks
and in_transfer.Completed
net.Start(NET_TRANSFER_IMG)
net.WriteString(hash)
net.WriteString(ext)
net.WriteString(chunk)
net.WriteBool(is_final)
net.Send(ply)
if is_final then
out_transfers[key] = nil
timer.Destroy(timer_name)
return
end
out_transfers[key] = out_transfers[key] + 1
end)
end)
end
local function process_transfer()
local hash = net.ReadString()
local chunk = net.ReadString()
local is_final = net.ReadBool()
-- should never happen
local in_transfer = in_transfers[hash]
if not in_transfer then return end
local i = table.insert(in_transfer.Chunks, chunk)
in_transfer.Completed = is_final
print("SERVER CHUNK: ", i)
if is_final then print("SERVER: FINAL CHUNK") end
end
net.Receive(NET_SEND_OWN_IMG, function()
local is_start = net.ReadBool()
if is_start then
start_transfer()
else
process_transfer()
end
end)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment