Aaron Suen a9e99f921e Deferrable discovery and witness optimization
- Split "discovery" into 2 stages: a check (to see if
  there is even anything new to discover) and a
  commit.
- When doing witness checks, check to see if there
  is anything to be discovered first, before doing the
  more expensive visibility checks.  If the player
  already knows all the discoveries, then we don't
  need to do any of the raycasts.

This should speed up especially pathological cases
like door catapulting, which can cause witnessing to
happen at multiple points, and causes major
slowdowns when multiple players are within the
distance range and facing the correct direction due
to tons of raycasts happening.
2022-09-24 12:23:24 -04:00

169 lines
4.9 KiB
Lua

-- LUALOCALS < ---------------------------------------------------------
local ipairs, minetest, nodecore, pairs, string, table, tostring, type,
vector
= ipairs, minetest, nodecore, pairs, string, table, tostring, type,
vector
local string_format, table_concat
= string.format, table.concat
-- LUALOCALS > ---------------------------------------------------------
local modname = minetest.get_current_modname()
nodecore.register_on_discover,
nodecore.registered_on_discovers
= nodecore.mkreg()
local cache = {}
local function loaddb(p)
if nodecore.hints_disabled(p) then return end
if not (p and nodecore.interact(p)) then return end
local player
local pname
if type(p) == "string" then
player, pname = minetest.get_player_by_name(p), p
else
player, pname = p, p:get_player_name()
end
if not (player and pname) then return end
local db = cache[pname]
if not db then
local s = player:get_meta():get_string(modname) or ""
db = s and minetest.deserialize(s) or {}
cache[pname] = db
end
return db, player, pname, function()
player:get_meta():set_string(modname, minetest.serialize(db))
end
end
nodecore.get_player_discovered = loaddb
local function discover_deferred(p, keys, prefix)
local db, player, pname, save = loaddb(p)
local new = {}
if not db then return end
for k in pairs(nodecore.flatkeys(keys)) do
k = (prefix or "") .. tostring(k)
if not db[k] then
new[#new + 1] = k
end
end
if #new < 1 then return end
return function()
for i = 1, #new do db[new[i]] = true end
minetest.log("action", string_format("player %s discovers %q", pname,
table_concat(new, ", ")))
for _, cb in pairs(nodecore.registered_on_discovers) do
cb(player, new, pname, db)
end
return save()
end
end
nodecore.player_discover_deferred = discover_deferred
local function discover(...)
local deferred = discover_deferred(...)
if deferred then return deferred() end
end
nodecore.player_discover = discover
------------------------------------------------------------------------
-- PLAYER EVENTS
local function reghook(func, stat, pwhom, npos, ppos)
return func("stat hook", function(...)
local t = {...}
local whom = t[pwhom]
if not (whom and whom:is_player()) then return end
local n = npos and t[npos].name or nil
if ppos then
local pos = t[ppos]
local stack = pos and nodecore.stack_get(pos)
if stack and not stack:is_empty() then
discover(whom, stat .. ":" .. stack:get_name())
end
end
return discover(whom, n and (stat .. ":" .. n) or stat)
end)
end
reghook(nodecore.register_on_punchnode, "punch", 3, 2, 1)
reghook(nodecore.register_on_dignode, "dig", 3, 2)
reghook(nodecore.register_on_placenode, "place", 3, 2)
reghook(nodecore.register_on_dieplayer, "die", 1)
reghook(nodecore.register_on_respawnplayer, "spawn", 1)
reghook(nodecore.register_on_joinplayer, "join", 1)
local function unpackreason(reason)
if type(reason) ~= "table" then return reason or "?" end
if reason.nc_type then return "nc", reason.nc_type end
if reason.from then return reason.from, reason.type or nil end
return reason.type or "?"
end
nodecore.register_on_player_hpchange("hurt/heal stats", function(whom, change, reason)
if change < 0 then
return discover(whom, "hurt:" .. unpackreason(reason))
else
return discover(whom, "heal:" .. unpackreason(reason))
end
end)
nodecore.register_on_cheat("cheat stats", function(player, reason)
discover(player, "cheat: " .. unpackreason(reason))
end)
nodecore.register_on_chat_message("chat message stats", function(name, msg)
discover(name, "chat:" .. ((msg:sub(1, 1) == "/") and "command" or "message"))
end)
------------------------------------------------------------------------
-- PLAYER SCAN
nodecore.register_on_node_stare,
nodecore.registered_on_node_stares = nodecore.mkreg()
nodecore.register_playerstep({
label = "inv",
action = function(player, data, dtime)
-- inventory
local inv = player:get_inventory()
local t = {}
for i = 1, inv:get_size("main") do
local stack = inv:get_stack("main", i)
if not stack:is_empty() then
t[stack:get_name()] = true
end
end
for k in pairs(t) do
discover(player, "inv:" .. k)
end
-- looking at
local pt = data.raycast()
if pt and pt.type == "node" and data.staring_pos
and vector.equals(data.staring_pos, pt.under) then
data.staring_time = data.staring_time + dtime
if data.staring_time >= 0.8 then
data.staring_time = 0
for _, f in ipairs(nodecore.registered_on_node_stares) do
f(player, pt, data)
end
end
else
data.staring_pos = pt and pt.under
data.staring_time = dtime
end
end
})
nodecore.register_on_node_stare(function(player, pt)
local nn = minetest.get_node(pt.under).name
discover(player, "look:" .. nn)
local stack = nodecore.stack_get(pt.under)
if stack and not stack:is_empty() then
discover(player, "look:" .. stack:get_name())
end
end)