random_snippets/snippet_safe_override.lua
2025-03-27 23:09:44 +03:00

141 lines
3.8 KiB
Lua

-- luacheck: globals whosit, std luanti+luajit
whosit = _G.whosit or {}
_G.whosit = whosit
-- provide table.pack if luajit is built without LUA52COMPAT
local pack = table.pack or function(...) return {n = select("#", ...), ...} end
local unpack = unpack or table.unpack
-- use efficient deque or implement our own FIFO type of thing
local formspec_log = futil and futil.Deque and futil.Deque()
if not formspec_log then
-- no deque implementation found
local deq = {}
deq.size = function(self)
return #self
end
deq.push_front = function(self, v)
table.insert(self, v)
end
deq.pop_back = function(self)
local v = self[1]
table.remove(self, 1)
return v
end
deq.iterate = function(self)
local i = 0
return function()
i = i + 1
local v = self[i]
if v then
return v
end
end
end
deq.__index = deq
formspec_log = setmetatable({}, deq)
end
local LOG_SIZE = 30
-- use deque for logging, pushing oldest log item out
local function log_add(msg)
core.log('error', type(msg) == "string" and msg or dump(msg))
formspec_log:push_front(msg)
if formspec_log:size() > LOG_SIZE then
formspec_log:pop_back()
end
end
local function formspec_log_clear()
formspec_log:clear()
end
-- passes to xpcall to be able to capture the call stack
local function error_printer(err)
log_add(err)
log_add(debug.traceback("----------------------", 2))
return "error was logged"
end
-- Takes any function and returns a function that takes and returns
-- same [multiple] values if no error happens. On error, logs it.
local function safe_wrap(func)
local function wrap_f(...)
local res = pack(xpcall(func, error_printer, ...))
if res[1] then
-- no error, just return rest of the values
return select(2, unpack(res))
else
-- error was logged by error_printer already
local msg = dump(res[2])
if msg then
log_add(msg)
end
return nil
end
end
return wrap_f
end
local function formspec_log_show(playername)
local log_items = {}
for item in formspec_log:iterate() do
table.insert(log_items, type(item) == "string" and item or dump(item))
end
local formspec_template = [[ formspec_version[6] size[16,9] textarea[0.1,0.4;15.8,8.5;log;log;%s] ]]
local log_text = core.formspec_escape(table.concat(log_items,'\n'))
core.show_formspec(playername, "yl_commons:formspec_log", string.format(formspec_template, log_text))
end
-- These commands will allow us to see the errors saved in
-- the `formspec_log` after they happen.
core.register_chatcommand(
"whosit_log_show",
{
privs = { server = true },
func = function(playername, _params)
formspec_log_show(playername)
return true
end,
}
)
core.register_chatcommand(
"whosit_log_clear",
{
privs = { server = true },
func = function(playername, _params)
formspec_log_clear(playername)
return true
end,
}
)
whosit.original_callbacks = {}
local original_callbacks = whosit.original_callbacks
function whosit.safe_override_on_use(item_name, func)
local def = core.registered_items[item_name]
if not (original_callbacks[item_name] or {}).on_use then
local orig = original_callbacks[item_name] or {}
original_callbacks[item_name] = orig
orig.on_use = def.on_use
end
def.on_use = function(itemstack, user, pointed_thing)
local status, res = safe_wrap(func)(itemstack, user, pointed_thing)
if status then
return res
else
return nil
end
end
end