From 4cf899021ad7ddbcd0dc926e3de02bb8f3a5cb5a Mon Sep 17 00:00:00 2001 From: _AMD_ Date: Sun, 22 Dec 2024 23:51:44 +0300 Subject: [PATCH] Merge gmod client part with the main client file --- examples/garrysmod-client.lua | 65 -------------------------- lua/long-polling/client.lua | 87 +++++++++++++++++++++++++++-------- 2 files changed, 68 insertions(+), 84 deletions(-) delete mode 100644 examples/garrysmod-client.lua diff --git a/examples/garrysmod-client.lua b/examples/garrysmod-client.lua deleted file mode 100644 index c55ff42..0000000 --- a/examples/garrysmod-client.lua +++ /dev/null @@ -1,65 +0,0 @@ ---[[ -This example shows how to modify client to use it in a Garry's Mod environment - where there are no copas or lua-cjson. - -When used in pure lua where copas is installed, - it is not necessary to overwrite the functions kupol.http_* and kupol.thread_* -]] - - -local kupol = include("path/to/kupol.lua") -- https://github.com/TRIGONIM/lua-long-polling/blob/main/lua/long-polling/client.lua - -kupol.json_encode = util.TableToJSON -kupol.json_decode = util.JSONToTable - -function kupol.thread_new(f, ...) - local co = coroutine.create(f) - local function cont(...) - local ok, callback = coroutine.resume(co, ...) - if not ok then error( debug.traceback(co, callback) ) end - if coroutine.status(co) ~= "dead" then callback(cont) end - end - cont(...) -end - -function kupol.thread_pause(seconds) - coroutine.yield(function(cont) timer.Simple(seconds, cont) end) -end - -function kupol.http_get(url) - return coroutine.yield(function(cont) - http.Fetch(url, function(body, _, _, code) cont(body, code) end, - function(err) cont(false, err) end) - end) -end - -function kupol.http_post(url, data) - return coroutine.yield(function(cont) - local ok = HTTP({ url = url, method = "POST", - body = data, type = "application/json", - success = function(code, body) cont(body, code) end, - failed = function(err) cont(false, err) end - }) - if not ok then cont(false, "HTTP() failed") end - end) -end - - -local LP = kupol.new("https://poll.def.pm/test") -- personal server for testing. dont use it in production - -local i = 1 -local publish = function() - local ok, err = LP:publish({i = i}) - print(i, ok and "published" or "pub failed: " .. tostring(err)) - i = i + 1 -end - --- spam publishing -kupol.thread_new(function() - for _ = 1, 10 do publish() kupol.thread_pause(.05) end -end) - --- one time subscribing for receiving continious updates -LP:subscribe(function(upd, ts) - print("subscribe callback. upd received. Remote ts, incr:", ts, upd.i) -end, nil, 5) -- 5 seconds timeout diff --git a/lua/long-polling/client.lua b/lua/long-polling/client.lua index 9397ef5..a8188cc 100644 --- a/lua/long-polling/client.lua +++ b/lua/long-polling/client.lua @@ -1,7 +1,8 @@ +-- source: https://github.com/TRIGONIM/lua-long-polling/blob/e28fff8141c7d1a22e030c9d6132ef853032da84/lua/long-polling/client.lua -- local kupol = require("long-polling.client") --- local client = kupol.new("https://lp.example.com/channel") +-- local Client = kupol.new("https://lp.example.com/channel") -- By default, client use copas and lua-cjson, but you can use your own functions --- See how to use it in Garry's Mod (without copas and cjson) in examples/ +-- from Garry's Mod for example local kupol = {} @@ -21,8 +22,8 @@ function MT:publish(tData) return code == 201 or res == "OK", code end -function MT:get(last_id, timeout) - local paramstr = "?ts=" .. (last_id or "") .. "&sleep=" .. (timeout or "") +function MT:get(offset, timeout) + local paramstr = "?ts=" .. (offset or "") .. "&sleep=" .. (timeout or "") local body, code_or_err = kupol.http_get(self.url .. paramstr) if not body then return false, code_or_err end @@ -40,23 +41,30 @@ function MT:handle_error(err) self:log("🆘 Error\n\t%s", err) end -function MT:subscribe(fHandler, last_id, timeout) - kupol.thread_new(function() while true do - local to = last_id and timeout or 0 -- it's better if first request will be fast if last_id not provided - local tData, body = self:get(last_id, to) +function MT:subscribe(fHandler, requested_ts, timeout) + self.thread = kupol.thread_new(function() repeat + local tmt = requested_ts and timeout or 0 -- it's better if first request will be fast if requested_ts not provided + local tData, body = self:get(requested_ts, tmt) if tData then - if (last_id or 0) > tData.ts then - self:log("🚧 ts on server is less than requested (%d < %d)", tData.ts, last_id) - end + local ts_diff = tData.ts - (requested_ts or 0) + + local REM = (requested_ts or 0) > tData.ts -- REMOTE FUCKUP. e.g. remote ts 0, local ts 1000 + local LOC = ts_diff > #tData.updates -- LOCAL FUCKUP. e.g. remote ts 100, local ts 0, but 30 updates instead of 100 + if REM or LOC then + if REM then + self:log("🚧 ts on server is less than requested (%d < %d)", tData.ts, requested_ts) + end + + if LOC then + self:log("🚧 updates lost: %d (got %d, expected %d)", ts_diff - #tData.updates, #tData.updates, ts_diff) -- too long time haven't requested them + end - local updates_should_be = tData.ts - (last_id or 0) - if updates_should_be > #tData.updates then - local updates_lost = updates_should_be - #tData.updates - self:log("🚧 updates lost: %d (got %d, expected %d)", updates_lost, #tData.updates, updates_should_be) -- too long haven't requested them + requested_ts = tData.ts -- emerg reset + else + requested_ts = requested_ts and (requested_ts + #tData.updates) or tData.ts + -- requested_ts = tData.ts end - -- last_id = last_id and (last_id + #tData.updates) or tData.ts - last_id = tData.ts for _, update in ipairs(tData.updates) do local pcallok, res = pcall(fHandler, update, tData.ts) if not pcallok then @@ -67,9 +75,11 @@ function MT:subscribe(fHandler, last_id, timeout) self:handle_error(body) kupol.thread_pause(10) end - end end) -- while true, thread + until (not self.thread) end) end +local IS_GARRYSMOD = (GM or GAMEMODE) and RunString and hook + local copas_ok, copas = pcall(require, "copas") -- should be loaded before http_v2 if copas_ok then local http = require("http_v2") -- https://github.com/TRIGONIM/lua-requests-async/blob/main/lua/http_v2.lua @@ -87,8 +97,42 @@ if copas_ok then kupol.thread_new = copas.addthread kupol.thread_pause = copas.sleep + +elseif IS_GARRYSMOD then + function kupol.thread_new(f, ...) + local co = coroutine.create(f) + local function cont(...) + local ok, callback = coroutine.resume(co, ...) + if not ok then error( debug.traceback(co, callback) ) end + if coroutine.status(co) ~= "dead" then callback(cont) end + end + cont(...) + return co + end + + function kupol.thread_pause(seconds) + coroutine.yield(function(cont) timer.Simple(seconds, cont) end) + end + + function kupol.http_get(url) + return coroutine.yield(function(cont) + http.Fetch(url, function(body, _, _, code) cont(body, code) end, + function(err) cont(false, err) end) + end) + end + + function kupol.http_post(url, data) + return coroutine.yield(function(cont) + local ok = HTTP({ url = url, method = "POST", + body = data, type = "application/json", + success = function(code, body) cont(body, code) end, + failed = function(err) cont(false, err) end + }) + if not ok then cont(false, "HTTP() failed") end + end) + end + else - -- e.g. in Garry's Mod print("Kupol: looks like copas is not installed. So you should provide own kupol.http_* and kupol.thread_* functions") end @@ -96,6 +140,11 @@ local cjson_ok, cjson = pcall(require, "cjson.safe") if cjson_ok then kupol.json_encode = cjson.encode kupol.json_decode = cjson.decode + +elseif IS_GARRYSMOD then + kupol.json_encode = util.TableToJSON + kupol.json_decode = util.JSONToTable + else print("Kupol: looks like lua-cjson is not installed. So you should provide own kupol.json_encode and kupol.json_decode functions") end