Implement idempotency TTL and enhance world stats collection

This commit is contained in:
2026-08-26 14:59:27 +02:00
parent 19376c3efa
commit d0d466881e
5 changed files with 59 additions and 14 deletions

View File

@@ -1,9 +1,10 @@
-- Registration is deferred to RDB_OpRegisterAll(), called from
-- RDB_Bootstrap's onServerStarted, rather than run at file top-level:
-- PZ's require() is best-effort (warns and continues rather than forcing a
-- synchronous load), so RDB_OpRegistry may not be defined yet if this file's
-- top-level code ran immediately at auto-load time. onServerStarted fires
-- only after every file has auto-loaded, so it's safe there.
-- Registration is deferred to register(), called from RDB_Bootstrap's
-- registerOps() (itself called from onServerStarted), rather than run at
-- file top-level: PZ's require() is best-effort (warns and continues rather
-- than forcing a synchronous load), so RDB_OpRegistry may not be defined yet
-- if this file's top-level code ran immediately at auto-load time.
-- onServerStarted fires only after every file has auto-loaded, so it's safe
-- there.
RDB_OpWorldGetStats = {}

View File

@@ -1,7 +1,15 @@
-- Processed request-id -> cached response, so a repeated request id replays
-- the original response instead of re-executing the op.
-- Persisted via ModData so it survives restarts.
--
-- Entries expire after RDB_Constants.IDEMPOTENCY_TTL_MS (60s by default):
-- a request id is only idempotent for retries within that window, not
-- forever. Without a TTL, a client that reuses a fixed id for routine
-- polling would get the first-ever response back on every call, since a
-- low-traffic server would rarely reach MAX_ENTRIES and trigger the
-- count-based eviction below.
require("RconDataBridge.RDB_Constants")
require("RconDataBridge.RDB_Log")
RDB_Idempotency = {}
@@ -18,20 +26,35 @@ local function ensureStore()
return store
end
local function isExpired(entry)
return getTimestampMs() - (entry.ts or 0) >= RDB_Constants.IDEMPOTENCY_TTL_MS
end
function RDB_Idempotency.has(id)
return ensureStore()[id] ~= nil
local entry = ensureStore()[id]
return entry ~= nil and not isExpired(entry)
end
function RDB_Idempotency.get(id)
local entry = ensureStore()[id]
if not entry then
if not entry or isExpired(entry) then
return nil
end
return entry.response
end
-- Sweeps expired entries first (the common case that keeps the store small
-- under normal traffic), then falls back to trimming the oldest entries if
-- still over MAX_ENTRIES (a backstop against a burst of distinct ids within
-- a single TTL window).
local function prune(s)
for id, entry in pairs(s) do
if isExpired(entry) then
s[id] = nil
end
end
local n = 0
for _ in pairs(s) do
n = n + 1

View File

@@ -1,4 +1,4 @@
-- World telemetry.
-- World telemetry, sourced from zombie.GameTime (getGameTime()).
require("RconDataBridge.RDB_Constants")
require("RconDataBridge.RDB_Log")
@@ -7,16 +7,30 @@ require("RconDataBridge.RDB_OptionsRegistry")
RDB_WorldStats = {}
local cached = { playerCount = 0, generatedAt = 0 }
local cached = {}
function RDB_WorldStats.collect()
return cached
end
local function refresh()
local gt = getGameTime()
cached = {
playerCount = getNumActivePlayers(),
generatedAt = getTimestampMs(),
worldAgeHours = gt:getWorldAgeHours(),
worldAgeDays = gt:getWorldAgeDaysSinceBegin(),
nightsSurvived = gt:getNightsSurvived(),
date = {
year = gt:getYear(),
month = gt:getMonth(),
day = gt:getDay(),
hour = gt:getHour(),
minute = gt:getMinutes(),
},
isNight = gt:isNight(),
isRaining = gt:isRainingToday(),
timeMultiplier = gt:getMultiplier(),
generatedAt = getTimestampMs(),
}
RDB_OptionsRegistry.set(RDB_Constants.OPT.WORLD_STATS, RDB_Json.encode(cached))