Files
Bubberstation/code/modules/admin/verbs/lua/lua_state.dm
SkyratBot 7d0d702ec7 [MIRROR] Fixes SS13.register_signal throwing unclear errors when called on deleted datums (#27293)
* Cleans up the SS13_base lua file and adds a new lua file for easily handling multiple signals on different objects. (#82458)

## About The Pull Request
Cleaned up the SS13.register_signal and SS13.unregister_signal, removing
the weird list shifting.
Also adds a new lua file that can be included for the use of registering
different signals on various datums and being able to clear them all in
1 function.
Removed the make_easy_clear_function option when registering a signal
via lua because I don't think it's used by anyone and it lacks any sort
of versatility. Users can just create their own function for clearing
signals from a datum.

Also updates the documentation for HARDDELETES.md as
COMSIG_PARENT_QDELETING was renamed to COMSIG_QDELETING

## Why It's Good For The Game
New handler file makes registering signals in batches a lot easier if
you want to clear them in one go without clearing unrelated callbacks on
the same datum. The list shifting in SS13.register_signal had pretty
significant performance problems, so removing that will make registering
and unregistering signals faster.

## Changelog
🆑
admin: LUA - Adds a new library called handler_group. Include it in your
files by doing require('handler_group')
/🆑

---------

Co-authored-by: Watermelon914 <3052169-Watermelon914@users.noreply.gitlab.com>

* Fixes SS13.register_signal throwing unclear errors when called on deleted datums (#82597)

## About The Pull Request
See title

## Why It's Good For The Game
More descriptive error message

## Changelog
🆑
fix: LUA: Registering a signal on a deleted datum will throw a more
descriptive error message.
/🆑

---------

Co-authored-by: Watermelon914 <3052169-Watermelon914@users.noreply.gitlab.com>

---------

Co-authored-by: Watermelon914 <37270891+Watermelon914@users.noreply.github.com>
Co-authored-by: Watermelon914 <3052169-Watermelon914@users.noreply.gitlab.com>
2024-04-18 10:21:35 +02:00

197 lines
6.7 KiB
Plaintext

#define MAX_LOG_REPEAT_LOOKBACK 5
GLOBAL_VAR_INIT(IsLuaCall, FALSE)
GLOBAL_PROTECT(IsLuaCall)
GLOBAL_DATUM(lua_usr, /mob)
GLOBAL_PROTECT(lua_usr)
/datum/lua_state
var/name
/// The internal ID of the lua state stored in auxlua's global map
var/internal_id
/// A log of every return, yield, and error for each chunk execution and function call
var/list/log = list()
/// A list of all the variables in the state's environment
var/list/globals = list()
/// A list in which to store datums and lists instantiated in lua, ensuring that they don't get garbage collected
var/list/references = list()
/// Ckey of the last user who ran a script on this lua state.
var/ckey_last_runner = ""
/// Whether the timer.lua script has been included into this lua context state.
var/timer_enabled = FALSE
/// Callbacks that need to be ran on next tick
var/list/functions_to_execute = list()
/datum/lua_state/vv_edit_var(var_name, var_value)
. = ..()
if(var_name == NAMEOF(src, internal_id))
return FALSE
/datum/lua_state/New(_name)
if(SSlua.initialized != TRUE)
qdel(src)
return
name = _name
internal_id = __lua_new_state()
/datum/lua_state/proc/check_if_slept(result)
if(result["status"] == "sleeping")
SSlua.sleeps += src
/datum/lua_state/proc/log_result(result, verbose = TRUE)
if(!islist(result))
return
if(!verbose && result["status"] != "errored" && result["status"] != "bad return" \
&& !(result["name"] == "input" && (result["status"] == "finished" || length(result["param"]))))
return
var/append_to_log = TRUE
var/index_of_log
if(log.len)
for(var/index in log.len to max(log.len - MAX_LOG_REPEAT_LOOKBACK, 1) step -1)
var/list/entry = log[index]
if(entry["status"] == result["status"] \
&& entry["chunk"] == result["chunk"] \
&& entry["name"] == result["name"] \
&& ((entry["param"] == result["param"]) || deep_compare_list(entry["param"], result["param"])))
if(!entry["repeats"])
entry["repeats"] = 0
index_of_log = index
entry["repeats"]++
append_to_log = FALSE
break
if(append_to_log)
if(islist(result["param"]))
result["param"] = weakrefify_list(encode_text_and_nulls(result["param"]))
log += list(result)
index_of_log = log.len
INVOKE_ASYNC(src, TYPE_PROC_REF(/datum/lua_state, update_editors))
return index_of_log
/datum/lua_state/proc/load_script(script)
GLOB.IsLuaCall = TRUE
var/tmp_usr = GLOB.lua_usr
GLOB.lua_usr = usr
var/result = __lua_load(internal_id, script)
GLOB.IsLuaCall = FALSE
GLOB.lua_usr = tmp_usr
// Internal errors unrelated to the code being executed are returned as text rather than lists
if(isnull(result))
result = list("status" = "errored", "param" = "__lua_load returned null (it may have runtimed - check the runtime logs)", "name" = "input")
if(istext(result))
result = list("status" = "errored", "param" = result, "name" = "input")
result["chunk"] = script
check_if_slept(result)
log_lua("[key_name(usr)] executed the following lua code:\n<code>[script]</code>")
return result
/datum/lua_state/process(seconds_per_tick)
if(timer_enabled)
var/result = call_function("__Timer_timer_process", seconds_per_tick)
log_result(result, verbose = FALSE)
for(var/function as anything in functions_to_execute)
result = call_function(list("__Timer_callbacks", function))
log_result(result, verbose = FALSE)
functions_to_execute.Cut()
/datum/lua_state/proc/call_function(function, ...)
var/call_args = length(args) > 1 ? args.Copy(2) : list()
if(islist(function))
var/list/new_function_path = list()
for(var/path_element in function)
new_function_path += path_element
function = new_function_path
var/msg = "[key_name(usr)] called the lua function \"[function]\" with arguments: [english_list(call_args)]"
log_lua(msg)
var/tmp_usr = GLOB.lua_usr
GLOB.lua_usr = usr
GLOB.IsLuaCall = TRUE
var/result = __lua_call(internal_id, function, call_args)
GLOB.IsLuaCall = FALSE
GLOB.lua_usr = tmp_usr
if(isnull(result))
result = list("status" = "errored", "param" = "__lua_call returned null (it may have runtimed - check the runtime logs)", "name" = islist(function) ? jointext(function, ".") : function)
if(istext(result))
result = list("status" = "errored", "param" = result, "name" = islist(function) ? jointext(function, ".") : function)
check_if_slept(result)
return result
/datum/lua_state/proc/call_function_return_first(function, ...)
var/list/result = call_function(arglist(args))
log_result(result, verbose = FALSE)
if(length(result))
if(islist(result["param"]) && length(result["param"]))
return result["param"][1]
/datum/lua_state/proc/awaken()
GLOB.IsLuaCall = TRUE
var/result = __lua_awaken(internal_id)
GLOB.IsLuaCall = FALSE
if(isnull(result))
result = list("status" = "errored", "param" = "__lua_awaken returned null (it may have runtimed - check the runtime logs)", "name" = "An attempted awaken")
if(istext(result))
result = list("status" = "errored", "param" = result, "name" = "An attempted awaken")
check_if_slept(result)
return result
/// Prefer calling SSlua.queue_resume over directly calling this
/datum/lua_state/proc/resume(index, ...)
var/call_args = length(args) > 1 ? args.Copy(2) : list()
var/msg = "[key_name(usr)] resumed a lua coroutine with arguments: [english_list(call_args)]"
log_lua(msg)
GLOB.IsLuaCall = TRUE
var/result = __lua_resume(internal_id, index, call_args)
GLOB.IsLuaCall = FALSE
if(isnull(result))
result = list("status" = "errored", "param" = "__lua_resume returned null (it may have runtimed - check the runtime logs)", "name" = "An attempted resume")
if(istext(result))
result = list("status" = "errored", "param" = result, "name" = "An attempted resume")
check_if_slept(result)
return result
/datum/lua_state/proc/get_globals()
globals = weakrefify_list(encode_text_and_nulls(__lua_get_globals(internal_id)))
/datum/lua_state/proc/get_tasks()
return __lua_get_tasks(internal_id)
/datum/lua_state/proc/kill_task(task_info)
__lua_kill_task(internal_id, task_info)
/datum/lua_state/proc/update_editors()
var/list/editor_list = LAZYACCESS(SSlua.editors, text_ref(src))
if(editor_list)
for(var/datum/lua_editor/editor as anything in editor_list)
SStgui.update_uis(editor)
/// Called by lua scripts when they add an atom to var/list/references so that it gets cleared up on delete.
/datum/lua_state/proc/clear_on_delete(datum/to_clear)
RegisterSignal(to_clear, COMSIG_QDELETING, PROC_REF(on_delete))
/// Called by lua scripts when an atom they've added should soft delete and this state should stop tracking it.
/// Needs to unregister all signals.
/datum/lua_state/proc/let_soft_delete(datum/to_clear)
UnregisterSignal(to_clear, COMSIG_QDELETING, PROC_REF(on_delete))
references -= to_clear
/datum/lua_state/proc/on_delete(datum/to_clear)
SIGNAL_HANDLER
references -= to_clear
#undef MAX_LOG_REPEAT_LOOKBACK