Skip to content

Instantly share code, notes, and snippets.

@AMD-NICK
Last active June 5, 2022 16:47
Show Gist options
  • Save AMD-NICK/655a2904b7cda1cc911f16df807b5a1d to your computer and use it in GitHub Desktop.
Save AMD-NICK/655a2904b7cda1cc911f16df807b5a1d to your computer and use it in GitHub Desktop.
Библиотека для работы с poll.gmod.app для Garry's Mod сервера. Полная версия + зависимости включены в сборку IGS от gm-donate.ru

Этот файл является незавершенной версией поллинг клиента для Garry's Mod серверов. Она будет расширяться и дописываться в процессе использования клиентами gm-donate.ru в сборках IGS

Выложена исключительно в ознакомительных целях для лучшего понимания работы с poll.gmod.app

Пример использования внутри IGS

IGS Polling Usage Example

gist создан специально для поста в моем блоге

-- 2021.01.18 4:16
-- Polling client for poll.gmod.app
-- Docs: https://blog.amd-nick.me/poll-gmod-app-docs
-- Author: amd-nick.me/about
-- 2 copy of this file in some cases in different places with different include methods
if not lolib then require("lolib") end
-- require bib
kupol = kupol or {
log = lolib.new()
}
local log = kupol.log
log.setFormat("{time} polling {message}")
log.setCvar("kupol_logging_level")
local function get_updates(base_url, uid, sleep, ts, fOnResponse)
local url = base_url .. uid .. "/getUpdates?sleep=" .. (sleep or "") .. "&ts=" .. (ts or "")
log.info("http.Fetch({})", url)
http.Fetch(url, function(json)
log.debug("Body: {}", json)
local t = util.JSONToTable(json)
if t and t.ok then
fOnResponse(t)
else
fOnResponse(false, t and t.description or "response is not a json")
end
end, function()
fOnResponse(false)
end)
end
function kupol.new(sUrl, uid, iTimeout)
local o = {uid = uid, url = sUrl, timeout = iTimeout, handler = false, running = false, stopping = false}
o.poll = function(ts, fOnResponse)
get_updates(o.url, o.uid, o.timeout, ts, fOnResponse)
end
local processResponse = function(requested_ts, res)
local remote_ts = res.ts
local a = remote_ts < requested_ts -- переезд, бэкап, обнуление временем
local b = #res.updates == 0 and requested_ts > remote_ts -- переход с dev на prod, где ts больше
if a or b then
local log_pattern = a and "ts сервера ({}) меньше локального ({})"
or "Похоже, что на сервере произошел баг или сервер изменился. ts {} prev {}"
log.warning(log_pattern, remote_ts, requested_ts)
bib.setNum("lp:ts:" .. o.uid, remote_ts)
requested_ts = remote_ts
end
local ts_diff = remote_ts - requested_ts
if #res.updates > 0 then
log.info("From uid {} received {} new messages. Ts diff: {} items", o.uid, #res.updates, ts_diff)
end
for _,upd in ipairs(res.updates) do
-- возможно проскальзывание https://img.qweqwe.ovh/1609615123638.png
-- bib.setNum("lp:ts", remote_ts)
local i = bib.getNum("lp:ts:" .. o.uid, 0) + 1
bib.setNum("lp:ts:" .. o.uid, i) -- increment
local ok, err = pcall(o.handler, upd)
if err then
log.error("Внутри хендлера произошла ошибка и работа чуть не прекратилась: {}", err)
end
end
-- https://t.me/c/1353676159/43747
if ts_diff > #res.updates then
log.warning("Апдейты долго не запрашивались и {} шт утеряно", ts_diff - #res.updates)
bib.setNum("lp:ts:" .. o.uid, remote_ts)
end
end
o.consume_updates = function()
local previous_ts = bib.getNum("lp:ts:" .. o.uid) or 0
-- log.info("Polling uid: {}. Timeout {} sec. Requested Ts {}", uid, sleep, previous_ts)
o.poll(previous_ts, function(res, err)
if o.checkStopping() then return end
if res then
processResponse(previous_ts, res)
o.consume_updates()
else
log.error("Error: {}. Waiting 5 sec and retrying", err)
timer.Simple(5, o.consume_updates)
end
end)
return o
end
o.start = function(fHandler)
local stopping = o.stopping
o.running = true
o.stopping = false
o.handler = fHandler
if not stopping then
o.consume_updates()
end
return o
end
o.stop = function(fOnStopped)
fOnStopped = fOnStopped or function() end
if not o.running then fOnStopped() return end
o.stopping = fOnStopped
return o
end
o.checkStopping = function()
local onStopped = o.stopping
if onStopped then
o.stopping = false
o.running = false
onStopped()
return true
end
return false
end
return o
end
local function doPoll(uid, callback, ts_)
if STOP_POLLING then print("polling stopped") return end
http.Fetch("https://poll.gmod.app/" .. uid .. "/getUpdates?sleep=30&ts=" .. (ts_ or 0), function(body)
local dat = util.JSONToTable(body)
if dat and dat.updates then
if not ts_ then return doPoll(uid, callback, dat.ts) end
ts_ = ts_ + #dat.updates
for _, upd in ipairs(dat.updates) do
local ok,err = pcall(callback, upd)
if not ok then print("Поллинг чуть не остановился: " .. tostring(err)) end
end
doPoll(uid, callback, ts_)
else -- no json or 429
PrintTable({poll_err = body})
return timer.Simple(10, fp{doPoll, uid, callback, ts_})
end
end, function()
timer.Simple(10, fp{doPoll, uid, callback, ts_})
end)
end
doPoll("secret", fp{PRINT, "NEW UPDATE"})
-- Экспериментальная реализация через корутины и цикл while
-- Интерес заключался в избавлении от бесконечной колбечной рекурсии
function async(f)
local co = coroutine.wrap(f)
co(coroutine.yield, co)
end
async(function(wait, cont)
local function foo(ts_)
local url = "https://poll.gmod.app/secret/getUpdates?sleep=5&ts=" .. (ts_ or 0)
http.Fetch(url, fc{fp{cont, true}, util.JSONToTable}, fp{cont, false,})
return wait()
end
local function sleep(sec)
timer.Simple(sec, cont)
return wait()
end
local callback = fp{PRINT, "CALLBACK"}
local ts
while not STOP_CYKA do
local ok, res = foo(ts)
if ok and res and res.updates then
if not ts then ts = res.ts return end -- first time
ts = ts + #res.updates
fl.each(fp{pcall, callback}, res.updates)
else
print("Спим 10 сек", not ok and "http_error", not res and "no_json", not res.updates and "429?")
sleep(10)
end
end
end)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment