random_snippets/using_xpcall.lua
2025-03-25 02:34:05 +03:00

199 lines
5.7 KiB
Lua

--- This part is unrelated to pcall/xpcall usage. Scroll to "Usage
--- example" below to see the relevant parts.
-- 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 = 100
-- 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 = table.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(
"log_show",
{
privs = { server = true },
func = function(playername, _params)
formspec_log_show(playername)
return true
end,
}
)
core.register_chatcommand(
"log_clear",
{
privs = { server = true },
func = function(playername, _params)
formspec_log_clear(playername)
return true
end,
}
)
--------------------------------------------------------------------------------
--- Usage example
--------------------------------------------------------------------------------
local function bugged(playername, params)
for _=1,tonumber(params) do
core.chat_send_all("Hello!")
end
return true, playername
end
-- this command will crash if you pass it wrong arguments
core.register_chatcommand(
"bugged",
{
privs = { },
func = function(playername, params)
return bugged(playername, params)
end,
}
)
-- This is the simplest safe version using `pcall`, it will return
-- status and error message on crash.
-- This command definition does not require any of the error handling
-- code above. The limitation is that this will only give us the error
-- message.
core.register_chatcommand(
"bugged_safe",
{
privs = { },
func = function(playername, params)
local status, ret1, ret2 = pcall(bugged, playername, params)
-- `status` will be `false` if function call raised an error
-- `ret1` will contain the error message on error, or first returned value on no errors
-- `ret2` will contain second returned value on no errors
-- etc.
return status, string.format("%s | %s", ret1, ret2)
end,
}
)
-- This version allows us to get the error stack trace. We get the
-- trace by passing error handler function `error_printer` to xpcall.
-- (our helper funciton, `error_printer` will be called on error
-- without unwinding the stack, so we can call `debug` functions to
-- examine the error).
core.register_chatcommand(
"bugged_safe2",
{
privs = { },
func = function(playername, params)
-- NOTE: if this does not work, you may need to make sure you're using luajit or newer lua
local status, ret1, ret2 = xpcall(bugged, error_printer, playername, params)
-- `status` will be `false` if function call raised an error
-- `ret1` will contain first returned value in case of no
-- errors, or, in case of error, the value returned by the
-- error handler function.
-- `ret2` will contain second returned value on no errors
-- etc.
return status, string.format("%s | %s", ret1, ret2)
end,
}
)
-- This is equivalent to `xpcall` safe2 version, but uses generic
-- wrapper function we defined above.
core.register_chatcommand(
"bugged_safe3",
{
privs = { },
func = function(playername, params)
local safe_bugged = safe_wrap(bugged)
return safe_bugged(playername, params)
end,
}
)