From c8bbb5935687796ee1ba85cb865a919dc32afe08 Mon Sep 17 00:00:00 2001 From: florinm03 Date: Wed, 1 Apr 2026 17:17:07 +0200 Subject: [PATCH 1/6] show download speed --- .../lua/ge/extensions/kissmp/ui/download.lua | 14 ++++++++++---- KISSMultiplayer/lua/ge/extensions/network.lua | 8 ++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/KISSMultiplayer/lua/ge/extensions/kissmp/ui/download.lua b/KISSMultiplayer/lua/ge/extensions/kissmp/ui/download.lua index d5872448..44a32448 100644 --- a/KISSMultiplayer/lua/ge/extensions/kissmp/ui/download.lua +++ b/KISSMultiplayer/lua/ge/extensions/kissmp/ui/download.lua @@ -37,19 +37,25 @@ local function draw(gui) imgui.ProgressBar(download_status.progress, imgui.ImVec2(split_width, 0)) local mod = kissmods.mods[download_status.name] - total_size = total_size + mod.size - downloaded_size = downloaded_size + (mod.size * download_status.progress) + local mod_size = (mod and mod.size) or 0 + total_size = total_size + mod_size + downloaded_size = downloaded_size + (mod_size * download_status.progress) end end imgui.EndChild() total_size = bytes_to_mb(total_size) downloaded_size = bytes_to_mb(downloaded_size) - local progress = downloaded_size / total_size local progress_text = tostring(math.floor(downloaded_size)) .. "MB / " .. tostring(math.floor(total_size)) .. "MB" + local elapsed = socket.gettime() - (network.download_start_time or 0) + if elapsed <= 0 then elapsed = 0.001 end + local progress_speed = downloaded_size / elapsed + local speed_text = tostring(math.floor(progress_speed)) .. "MB/s" + content_width = imgui.GetWindowContentRegionWidth() - split_width = content_width * 0.495 + split_width = content_width * 0.450 + progress_text = progress_text .. " (" .. speed_text .. ")" local text_size = imgui.CalcTextSize(progress_text) local extra_size = split_width - text_size.x diff --git a/KISSMultiplayer/lua/ge/extensions/network.lua b/KISSMultiplayer/lua/ge/extensions/network.lua index b4ccb728..ef2559df 100644 --- a/KISSMultiplayer/lua/ge/extensions/network.lua +++ b/KISSMultiplayer/lua/ge/extensions/network.lua @@ -7,6 +7,7 @@ M.downloads = {} M.downloading = false M.downloads_status = {} M.downloads_received = {} +M.download_start_time = 0 local current_download = nil @@ -281,6 +282,7 @@ local function connect(addr, player_name, is_public) disconnect() end M.players = {} + M.download_start_time = 0 print("Connecting...") addr = sanitize_addr(addr) @@ -389,6 +391,7 @@ local function send_messagepack(data_type, reliable, data) end local function on_finished_download() + M.download_start_time = 0 vehiclemanager.loading_map = true change_map(M.connection.server_info.map) end @@ -417,6 +420,7 @@ local function cancel_download() M.downloads_status = {} M.downloads_received = {} M.downloading = false + M.download_start_time = 0 end local function onUpdate(dt) @@ -447,6 +451,9 @@ local function onUpdate(dt) end end elseif string.byte(msg_type) == 0 then -- Binary data + if M.download_start_time == 0 then + M.download_start_time = socket.gettime() + end local name_b = M.connection.tcp:receive(4) if not name_b then break end @@ -508,6 +515,7 @@ local function onUpdate(dt) local len = bytesToU32(len_b) local reason, _, _ = M.connection.tcp:receive(len) disconnect(reason) + break end end end From bcdd2053104afa3307f1dc04339687fd7bd96011 Mon Sep 17 00:00:00 2001 From: florinm03 Date: Wed, 1 Apr 2026 21:41:10 +0200 Subject: [PATCH 2/6] fix(download): stabilize speed/progress metrics and improve packet processing --- .../lua/ge/extensions/kissmp/ui/download.lua | 16 +++- KISSMultiplayer/lua/ge/extensions/network.lua | 86 +++++++++++++++---- 2 files changed, 84 insertions(+), 18 deletions(-) diff --git a/KISSMultiplayer/lua/ge/extensions/kissmp/ui/download.lua b/KISSMultiplayer/lua/ge/extensions/kissmp/ui/download.lua index 44a32448..cb53448d 100644 --- a/KISSMultiplayer/lua/ge/extensions/kissmp/ui/download.lua +++ b/KISSMultiplayer/lua/ge/extensions/kissmp/ui/download.lua @@ -44,11 +44,21 @@ local function draw(gui) end imgui.EndChild() - total_size = bytes_to_mb(total_size) - downloaded_size = bytes_to_mb(downloaded_size) + local total_size_bytes = network.download_total_bytes or 0 + local downloaded_size_bytes = network.downloaded_bytes or 0 + if total_size_bytes <= 0 then + total_size_bytes = total_size + downloaded_size_bytes = downloaded_size + end + + total_size = bytes_to_mb(total_size_bytes) + downloaded_size = bytes_to_mb(downloaded_size_bytes) local progress_text = tostring(math.floor(downloaded_size)) .. "MB / " .. tostring(math.floor(total_size)) .. "MB" - local elapsed = socket.gettime() - (network.download_start_time or 0) + local elapsed = 0 + if (network.download_start_time or 0) > 0 then + elapsed = socket.gettime() - network.download_start_time + end if elapsed <= 0 then elapsed = 0.001 end local progress_speed = downloaded_size / elapsed local speed_text = tostring(math.floor(progress_speed)) .. "MB/s" diff --git a/KISSMultiplayer/lua/ge/extensions/network.lua b/KISSMultiplayer/lua/ge/extensions/network.lua index ef2559df..6bb26bef 100644 --- a/KISSMultiplayer/lua/ge/extensions/network.lua +++ b/KISSMultiplayer/lua/ge/extensions/network.lua @@ -8,6 +8,8 @@ M.downloading = false M.downloads_status = {} M.downloads_received = {} M.download_start_time = 0 +M.download_total_bytes = 0 +M.downloaded_bytes = 0 local current_download = nil @@ -74,7 +76,13 @@ local function disconnect(data) end kissui.chat.add_message(text) M.connection.connected = false - M.connection.tcp:close() + if M.connection.tcp then + M.connection.tcp:close() + M.connection.tcp = nil + end + M.download_start_time = 0 + M.download_total_bytes = 0 + M.downloaded_bytes = 0 M.players = {} kissplayers.players = {} kissplayers.player_transforms = {} @@ -283,6 +291,8 @@ local function connect(addr, player_name, is_public) end M.players = {} M.download_start_time = 0 + M.download_total_bytes = 0 + M.downloaded_bytes = 0 print("Connecting...") addr = sanitize_addr(addr) @@ -349,14 +359,18 @@ local function connect(addr, player_name, is_public) local missing_mods = {} local mod_names = {} + local total_missing_bytes = 0 for _, mod in pairs(kissmods.mods) do table.insert(mod_names, mod.name) if mod.status ~= "ok" then table.insert(missing_mods, mod.name) M.downloads_status[mod.name] = {name = mod.name, progress = 0} + total_missing_bytes = total_missing_bytes + (mod.size or 0) end end M.connection.mods_left = #missing_mods + M.download_total_bytes = total_missing_bytes + M.downloaded_bytes = 0 kissmods.deactivate_all_mods() for k, v in pairs(missing_mods) do @@ -421,6 +435,8 @@ local function cancel_download() M.downloads_received = {} M.downloading = false M.download_start_time = 0 + M.download_total_bytes = 0 + M.downloaded_bytes = 0 end local function onUpdate(dt) @@ -432,48 +448,79 @@ local function onUpdate(dt) send_ping() end - while true do + local packets_processed = 0 + local max_packets_per_update = 64 + + while packets_processed < max_packets_per_update do local msg_type = M.connection.tcp:receive(1) if not msg_type then break end - --print("msg_t"..string.byte(msg_type)) + packets_processed = packets_processed + 1 + M.connection.tcp:settimeout(5.0) - -- JSON data + if string.byte(msg_type) == 1 then - local data = M.connection.tcp:receive(4) - if not data then break end - local len = bytesToU32(data) + local len_b = M.connection.tcp:receive(4) + if not len_b then + M.connection.tcp:settimeout(0.0) + break + end + + local len = bytesToU32(len_b) local data, _, _ = M.connection.tcp:receive(len) M.connection.tcp:settimeout(0.0) + if not data then break end + local data_decoded = jsonDecode(data) - for k, v in pairs(data_decoded) do - if message_handlers[k] then - message_handlers[k](v) + if data_decoded then + for k, v in pairs(data_decoded) do + if message_handlers[k] then + message_handlers[k](v) + end end end + elseif string.byte(msg_type) == 0 then -- Binary data if M.download_start_time == 0 then M.download_start_time = socket.gettime() end + local name_b = M.connection.tcp:receive(4) - if not name_b then break end + if not name_b then + M.connection.tcp:settimeout(0.0) + break + end M.downloading = true kissui.show_download = true + local len_n = bytesToU32(name_b) local name, _, _ = M.connection.tcp:receive(len_n) local chunk_n_b = M.connection.tcp:receive(4) local chunk_a_b = M.connection.tcp:receive(4) local read_size_b = M.connection.tcp:receive(4) + + if not name or not chunk_n_b or not chunk_a_b or not read_size_b then + M.connection.tcp:settimeout(0.0) + break + end + local chunk_n = bytesToU32(chunk_n_b) local chunk_a = bytesToU32(chunk_a_b) local read_size = bytesToU32(read_size_b) local file_length = chunk_a local file_data, _, _ = M.connection.tcp:receive(read_size) + M.connection.tcp:settimeout(0.0) + if not file_data then break end + if not M.downloads_received[name] then M.downloads_received[name] = 0 end M.downloads_received[name] = M.downloads_received[name] + read_size + M.downloaded_bytes = M.downloaded_bytes + read_size + if M.download_total_bytes > 0 and M.downloaded_bytes > M.download_total_bytes then + M.downloaded_bytes = M.download_total_bytes + end M.downloads_status[name] = { name = name, @@ -492,12 +539,12 @@ local function onUpdate(dt) end if M.downloads_received[name] >= file_length then - kissmods.mount_mod(name) - if M.downloads[name] then M.downloads[name]:close() M.downloads[name] = nil end + + kissmods.mount_mod(name) M.downloads_status[name] = nil M.downloads_received[name] = nil M.connection.mods_left = M.connection.mods_left - 1 @@ -506,16 +553,25 @@ local function onUpdate(dt) if M.connection.mods_left <= 0 then M.downloading = false kissui.show_download = false + M.downloaded_bytes = M.download_total_bytes on_finished_download() end - M.connection.tcp:settimeout(0.0) - break + elseif string.byte(msg_type) == 2 then local len_b = M.connection.tcp:receive(4) + if not len_b then + M.connection.tcp:settimeout(0.0) + break + end + local len = bytesToU32(len_b) local reason, _, _ = M.connection.tcp:receive(len) + M.connection.tcp:settimeout(0.0) disconnect(reason) break + + else + M.connection.tcp:settimeout(0.0) end end end From 13a29ddedf6e3b3f6e713bd249557f73cc705bf7 Mon Sep 17 00:00:00 2001 From: florinm03 Date: Wed, 1 Apr 2026 22:41:35 +0200 Subject: [PATCH 3/6] add ETA --- .../lua/ge/extensions/kissmp/ui/download.lua | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/KISSMultiplayer/lua/ge/extensions/kissmp/ui/download.lua b/KISSMultiplayer/lua/ge/extensions/kissmp/ui/download.lua index cb53448d..e5ac4962 100644 --- a/KISSMultiplayer/lua/ge/extensions/kissmp/ui/download.lua +++ b/KISSMultiplayer/lua/ge/extensions/kissmp/ui/download.lua @@ -5,6 +5,18 @@ local function bytes_to_mb(bytes) return (bytes / 1024) / 1024 end +local function format_eta(seconds) + if seconds < 0 then seconds = 0 end + local hours = math.floor(seconds / 3600) + local minutes = math.floor((seconds % 3600) / 60) + local secs = math.floor(seconds % 60) + + if hours > 0 then + return string.format("%02d:%02d:%02d", hours, minutes, secs) + end + return string.format("%02d:%02d", minutes, secs) +end + local function draw(gui) if not kissui.show_download then return end @@ -63,9 +75,15 @@ local function draw(gui) local progress_speed = downloaded_size / elapsed local speed_text = tostring(math.floor(progress_speed)) .. "MB/s" + local eta_text = "--:--" + if progress_speed > 0 and downloaded_size < total_size then + local eta_seconds = (total_size - downloaded_size) / progress_speed + eta_text = format_eta(eta_seconds) + end + content_width = imgui.GetWindowContentRegionWidth() split_width = content_width * 0.450 - progress_text = progress_text .. " (" .. speed_text .. ")" + progress_text = progress_text .. " (" .. speed_text .. ", ETA " .. eta_text .. ")" local text_size = imgui.CalcTextSize(progress_text) local extra_size = split_width - text_size.x From d19f0d6664d2b914bd033a90d38d509afa5df61e Mon Sep 17 00:00:00 2001 From: florinm03 Date: Wed, 1 Apr 2026 22:49:05 +0200 Subject: [PATCH 4/6] keep track of already downloaded mods --- KISSMultiplayer/lua/ge/extensions/network.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/KISSMultiplayer/lua/ge/extensions/network.lua b/KISSMultiplayer/lua/ge/extensions/network.lua index 6bb26bef..adcac440 100644 --- a/KISSMultiplayer/lua/ge/extensions/network.lua +++ b/KISSMultiplayer/lua/ge/extensions/network.lua @@ -359,6 +359,7 @@ local function connect(addr, player_name, is_public) local missing_mods = {} local mod_names = {} + local available_mods = {} local total_missing_bytes = 0 for _, mod in pairs(kissmods.mods) do table.insert(mod_names, mod.name) @@ -366,6 +367,8 @@ local function connect(addr, player_name, is_public) table.insert(missing_mods, mod.name) M.downloads_status[mod.name] = {name = mod.name, progress = 0} total_missing_bytes = total_missing_bytes + (mod.size or 0) + else + table.insert(available_mods, mod.name) end end M.connection.mods_left = #missing_mods @@ -373,6 +376,9 @@ local function connect(addr, player_name, is_public) M.downloaded_bytes = 0 kissmods.deactivate_all_mods() + if #available_mods > 0 then + kissmods.mount_mods(available_mods) + end for k, v in pairs(missing_mods) do print(k.." "..v) end From e2fdbcde4004df9b4d879e37c44cced24ea44ddb Mon Sep 17 00:00:00 2001 From: florinm03 Date: Thu, 2 Apr 2026 16:43:42 +0200 Subject: [PATCH 5/6] queue for one mod at a time --- KISSMultiplayer/lua/ge/extensions/network.lua | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/KISSMultiplayer/lua/ge/extensions/network.lua b/KISSMultiplayer/lua/ge/extensions/network.lua index 9df534e2..eb3617d8 100644 --- a/KISSMultiplayer/lua/ge/extensions/network.lua +++ b/KISSMultiplayer/lua/ge/extensions/network.lua @@ -10,6 +10,7 @@ M.downloads_received = {} M.download_start_time = 0 M.download_total_bytes = 0 M.downloaded_bytes = 0 +M.download_queue = {} local current_download = nil @@ -83,6 +84,7 @@ local function disconnect(data) M.download_start_time = 0 M.download_total_bytes = 0 M.downloaded_bytes = 0 + M.download_queue = {} M.players = {} kissplayers.players = {} kissplayers.player_transforms = {} @@ -291,6 +293,7 @@ local function connect(addr, player_name, is_public) end M.players = {} M.download_start_time = 0 + M.download_queue = {} M.download_total_bytes = 0 M.downloaded_bytes = 0 @@ -389,7 +392,11 @@ local function connect(addr, player_name, is_public) disconnect() return else - send_data({ RequestMods = missing_mods }, true) + M.download_queue = missing_mods + local next_mod = table.remove(M.download_queue, 1) + if next_mod then + send_data({ RequestMods = { next_mod } }, true) + end end end vehiclemanager.loading_map = true @@ -443,6 +450,7 @@ local function cancel_download() M.download_start_time = 0 M.download_total_bytes = 0 M.downloaded_bytes = 0 + M.download_queue = {} end local function onUpdate(dt) @@ -560,6 +568,13 @@ local function onUpdate(dt) M.downloads_status[name] = nil M.downloads_received[name] = nil M.connection.mods_left = M.connection.mods_left - 1 + + if M.connection.mods_left > 0 then + local next_mod = table.remove(M.download_queue, 1) + if next_mod then + send_data({ RequestMods = { next_mod } }, true) + end + end end if M.connection.mods_left <= 0 then From 42f5df7b89298e13709adae37ca641ae4769b315 Mon Sep 17 00:00:00 2001 From: Vlad118 Date: Sat, 4 Apr 2026 22:09:59 +0100 Subject: [PATCH 6/6] remove unused and tidied code; delete incomplete mod download; added catches for more cases --- .../lua/ge/extensions/kissmp/ui/download.lua | 1 - KISSMultiplayer/lua/ge/extensions/network.lua | 92 ++++++++----------- 2 files changed, 36 insertions(+), 57 deletions(-) diff --git a/KISSMultiplayer/lua/ge/extensions/kissmp/ui/download.lua b/KISSMultiplayer/lua/ge/extensions/kissmp/ui/download.lua index e5ac4962..bb2332ca 100644 --- a/KISSMultiplayer/lua/ge/extensions/kissmp/ui/download.lua +++ b/KISSMultiplayer/lua/ge/extensions/kissmp/ui/download.lua @@ -94,7 +94,6 @@ local function draw(gui) end imgui.SameLine() if imgui.Button("Cancel###cancel_download", imgui.ImVec2(split_width, -1)) then - network.cancel_download() kissui.show_download = false network.disconnect() end diff --git a/KISSMultiplayer/lua/ge/extensions/network.lua b/KISSMultiplayer/lua/ge/extensions/network.lua index eb3617d8..0f3e6947 100644 --- a/KISSMultiplayer/lua/ge/extensions/network.lua +++ b/KISSMultiplayer/lua/ge/extensions/network.lua @@ -12,8 +12,6 @@ M.download_total_bytes = 0 M.downloaded_bytes = 0 M.download_queue = {} -local current_download = nil - local socket = require("socket") local messagepack = require("lua/common/libs/Lua-MessagePack/MessagePack") local ping_send_time = 0 @@ -28,12 +26,10 @@ M.connection = { heartbeat_time = 1, timer = 0, tickrate = 33, - mods_left = 0, ping = 0, time_offset = 0 } -local FILE_TRANSFER_CHUNK_SIZE = 16384; local CHUNK_SIZE = 65000 -- Safe size under 65536 limit local message_handlers = {} @@ -70,6 +66,28 @@ local function bytesToU32(str) ) end +local function cancel_download() +for name, file_handle in pairs(M.downloads) do + file_handle:close() + + -- Delete partial file + local file_path = "/kissmp_mods/" .. name + if FS:fileExists(file_path) then + FS:removeFile(file_path) + end + end + + -- Clear the tables so dead file handles aren't reused + M.downloads = {} + M.downloads_status = {} + M.downloads_received = {} + M.downloading = false + M.download_start_time = 0 + M.download_total_bytes = 0 + M.downloaded_bytes = 0 + M.download_queue = {} +end + local function disconnect(data) local text = "Disconnected!" if data then @@ -81,10 +99,9 @@ local function disconnect(data) M.connection.tcp:close() M.connection.tcp = nil end - M.download_start_time = 0 - M.download_total_bytes = 0 - M.downloaded_bytes = 0 - M.download_queue = {} + + cancel_download() + M.players = {} kissplayers.players = {} kissplayers.player_transforms = {} @@ -106,24 +123,6 @@ local function handle_disconnected(data) disconnect(data) end -local function handle_file_transfer(data) - kissui.show_download = true - -- local file_len = ffi.cast("uint32_t*", ffi.new("char[?]", 5, data:sub(1, 4)))[0] - local file_len = bytesToU32(data:sub(1, 4)) - local file_name = data:sub(5, #data) - local chunks = math.floor(file_len / FILE_TRANSFER_CHUNK_SIZE) - - current_download = { - file_len = file_len, - file_name = file_name, - chunks = chunks, - last_chunk = file_len - chunks * FILE_TRANSFER_CHUNK_SIZE, - current_chunk = 0, - file = kissmods.open_file(file_name) - } - M.downloading = true -end - local function handle_player_info(player_info) M.players[player_info.id] = player_info end @@ -374,7 +373,7 @@ local function connect(addr, player_name, is_public) table.insert(available_mods, mod.name) end end - M.connection.mods_left = #missing_mods + M.download_total_bytes = total_missing_bytes M.downloaded_bytes = 0 @@ -433,26 +432,6 @@ local function send_ping() ) end -local function cancel_download() - --[[if not current_download then return end - io.close(current_download.file) - current_download = nil - M.downloading = false]]-- - for k, v in pairs(M.downloads) do - M.downloads[k]:close() - end - - -- Clear the tables so dead file handles aren't reused - M.downloads = {} - M.downloads_status = {} - M.downloads_received = {} - M.downloading = false - M.download_start_time = 0 - M.download_total_bytes = 0 - M.downloaded_bytes = 0 - M.download_queue = {} -end - local function onUpdate(dt) if not M.connection.connected then return end if M.connection.timer < M.connection.heartbeat_time then @@ -556,6 +535,10 @@ local function onUpdate(dt) if file and file_data then file:write(file_data) + else + kissui.chat.add_message("Error: Could not write file to disk. Check permissions or disk space.", kissui.COLOR_RED) + disconnect("File write error") + return end if M.downloads_received[name] >= file_length then @@ -567,22 +550,19 @@ local function onUpdate(dt) kissmods.mount_mod(name) M.downloads_status[name] = nil M.downloads_received[name] = nil - M.connection.mods_left = M.connection.mods_left - 1 - if M.connection.mods_left > 0 then + if #M.download_queue > 0 then local next_mod = table.remove(M.download_queue, 1) if next_mod then send_data({ RequestMods = { next_mod } }, true) end + else + M.downloading = false + kissui.show_download = false + M.downloaded_bytes = M.download_total_bytes + on_finished_download() end end - - if M.connection.mods_left <= 0 then - M.downloading = false - kissui.show_download = false - M.downloaded_bytes = M.download_total_bytes - on_finished_download() - end elseif string.byte(msg_type) == 2 then local len_b = M.connection.tcp:receive(4)