Aaron Suen d161b83a90 Increase GC intervals
We're already kicking off GC on startup, and it shouldn't be able
to get way out of hand within an hour, so we don't need to scan
quite so intensely.
2024-08-24 14:32:10 -04:00

298 lines
7.2 KiB
Lua

-- LUALOCALS < ---------------------------------------------------------
local ipairs, math, minetest, next, pairs, pcall, string, table
= ipairs, math, minetest, next, pairs, pcall, string, table
local math_random, string_format, string_gmatch, string_match,
table_concat
= math.random, string.format, string.gmatch, string.match,
table.concat
-- LUALOCALS > ---------------------------------------------------------
local modname = minetest.get_current_modname()
local modstore = minetest.get_mod_storage()
local cache = {}
for k, v in pairs(modstore:to_table().fields) do
cache[k] = minetest.string_to_pos(v)
end
minetest.register_globalstep(function()
for _, player in ipairs(minetest.get_connected_players()) do
local pname = player:get_player_name()
local pos = player:get_pos()
local opos = cache[pname]
if (not opos) or (not vector.equals(pos, opos)) then
modstore:set_string(pname, minetest.pos_to_string(pos))
cache[pname] = pos
end
end
end)
local function expire()
for k in pairs(cache) do
if not minetest.player_exists(k) then
modstore:set_string(k, "")
cache[k] = nil
end
end
minetest.after(5 + math_random() * 5, expire)
end
minetest.after(0, expire)
minetest.register_privilege(modname, {
description = "Can see position of other players",
give_to_singleplayer = false,
give_to_admin = true
})
local function matchplayers(spec)
local found = {}
for k, v in pairs(cache) do
local matched
for p in string_gmatch(spec, "[^%s]+") do
if not matched then
local ok, res = pcall(function() return string_match(k, p) end)
matched = matched or (ok and res)
end
end
if matched then
local player = minetest.get_player_by_name(k)
found[k] = {
name = k,
pos = player and player:get_pos() or v,
player = player
}
end
end
return found
end
minetest.register_chatcommand("pos", {
description = "Get current position of players",
privs = {[modname] = true},
func = function(_, param)
local rpt = {}
for k, v in pairs(matchplayers(param)) do
rpt[#rpt + 1] = k .. " at "
.. minetest.pos_to_string(vector.round(v.pos))
.. (v.player and " [online]" or "")
end
if #rpt < 1 then rpt = {"no match found"} end
return true, table_concat(rpt, "\n")
end
})
------------------------------------------------------------------------
-- Teleport to offline players
local teleport = minetest.registered_chatcommands.teleport
if teleport and teleport.func then
local oldfunc = teleport.func
teleport.func = function(caller, ...)
local oldget = minetest.get_player_by_name
local function helper(...)
minetest.get_player_by_name = oldget
return ...
end
function minetest.get_player_by_name(name, ...)
local player = oldget(name, ...)
if player then return player end
local s = cache[name]
if s then
return {
get_pos = function() return s end,
set_pos = function()
minetest.after(0, function()
return minetest.chat_send_player(caller,
"Cannot teleport an offline player.")
end)
end,
get_attach = function() end,
}
end
end
return helper(oldfunc(caller, ...))
end
end
------------------------------------------------------------------------
-- Configure position tracking filter
local trackcache = {}
local function gettracking(player, pname)
local tracking = {}
if minetest.check_player_privs(pname, modname) then
tracking = trackcache[pname]
if not tracking then
local s = player:get_meta():get_string(modname)
tracking = s and s ~= "" and minetest.deserialize(s) or {}
trackcache[pname] = tracking
end
end
return tracking
end
local function trackset(pname, param, value)
local player = minetest.get_player_by_name(pname)
if not player then return false, "Cannot track while offline" end
local set
if (not param) or (param == "") then
if value then
set = {}
for _, p in pairs(minetest.get_connected_players()) do
local n = p:get_player_name()
set[n] = {
name = n,
pos = p:get_pos(),
player = p
}
end
else
set = matchplayers(".")
end
else
set = matchplayers(param)
end
local tracking = gettracking(player, pname)
for k in pairs(set) do
if k ~= pname then tracking[k] = value end
end
for k in pairs(tracking) do
if not minetest.player_exists(k) then
tracking[k] = nil
end
end
trackcache[pname] = tracking
player:get_meta():set_string(modname, minetest.serialize(tracking))
local total = 0
for _ in pairs(tracking) do total = total + 1 end
return true, "now tracking " .. total .. " player(s)"
end
minetest.register_chatcommand("postrack", {
description = "Enable tracking position of players",
privs = {[modname] = true},
func = function(pname, param)
return trackset(pname, param, true)
end
})
minetest.register_chatcommand("posuntrack", {
description = "Disable tracking position of players",
privs = {[modname] = true},
func = function(pname, param)
return trackset(pname, param)
end
})
------------------------------------------------------------------------
-- Posiiton tracking HUDs
local huds = {}
minetest.register_on_leaveplayer(function(player)
huds[player:get_player_name()] = nil
end)
local hud_elem_type = minetest.features.hud_def_type_field and "type" or "hud_elem_type"
local function trackinghuds(player)
local pname = player:get_player_name()
local tracking = gettracking(player, pname)
local phuds = huds[pname]
if not phuds then
if not next(tracking) then return end
phuds = {}
huds[pname] = phuds
end
local show = {}
for k in pairs(tracking) do
local peer = minetest.get_player_by_name(k)
local ppos = peer and peer:get_pos() or cache[k]
if not ppos then
local s = modstore:get_string(k)
ppos = s and s ~= "" and minetest.string_to_pos(s)
cache[k] = ppos
end
if ppos then
show[k] = {
x = ppos.x,
y = ppos.y + 1.25,
z = ppos.z,
on = not not peer
}
end
end
for k, v in pairs(show) do
local old = phuds[k]
if old then
if not vector.equals(old.pos, v) then
player:hud_change(old.id, "world_pos", v)
old.pos = v
end
if old.on ~= v.on then
player:hud_change(old.id, "number", v.on
and 0xffff00 or 0x888800)
old.on = v.on
end
else
phuds[k] = {
pos = v,
on = v.on,
id = player:hud_add({
[hud_elem_type] = "waypoint",
world_pos = v,
name = k,
precision = 1,
number = v.on and 0xffff00 or 0x888800
})
}
end
end
for k, v in pairs(phuds) do
if not show[k] then
player:hud_remove(v.id)
phuds[k] = nil
end
end
if not pairs(phuds)(phuds) then
huds[pname] = nil
end
end
minetest.register_globalstep(function()
for _, player in pairs(minetest.get_connected_players()) do
trackinghuds(player)
end
end)
do
local batch = {}
local rescan = 0
minetest.register_globalstep(function(dtime)
if #batch > 0 then
local pname = batch[#batch]
batch[#batch] = nil
if not minetest.player_exists(pname) then
minetest.log("info", string_format(
"%s gc %s", modname, pname))
modstore:set_string(pname, "")
end
return
end
if rescan > 0 then
rescan = rescan - dtime
return
end
rescan = 3600 * (math_random() + 0.5)
batch = modstore:get_keys()
end)
end