Created
February 5, 2020 01:07
-
-
Save Earu/ce2672a4575d09253950bbcce084a2cb to your computer and use it in GitHub Desktop.
Garry's Mod image networking (proof of concept)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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