diff --git a/auxlua.dll b/auxlua.dll index 0e68d5efa87..a5f2e65e51d 100755 Binary files a/auxlua.dll and b/auxlua.dll differ diff --git a/code/__HELPERS/_lists.dm b/code/__HELPERS/_lists.dm index 3f220fd69d9..005297ede8c 100644 --- a/code/__HELPERS/_lists.dm +++ b/code/__HELPERS/_lists.dm @@ -812,17 +812,20 @@ else return element -#define REFIFY_KVPIFY_MAX_LENGTH 1000 - /// Returns a copy of the list where any element that is a datum or the world is converted into a ref /proc/refify_list(list/target_list) - if(length(target_list) > REFIFY_KVPIFY_MAX_LENGTH) - return "list\[[length(target_list)]\]" var/list/ret = list() for(var/i in 1 to target_list.len) var/key = target_list[i] var/new_key = key - if(isdatum(key)) + if(isweakref(key)) + var/datum/weakref/ref = key + var/resolved = ref.resolve() + if(resolved) + new_key = "[resolved] [REF(resolved)]" + else + new_key = "null weakref [REF(key)]" + else if(isdatum(key)) new_key = "[key] [REF(key)]" else if(key == world) new_key = "world [REF(world)]" @@ -831,7 +834,14 @@ var/value if(istext(key) || islist(key) || ispath(key) || isdatum(key) || key == world) value = target_list[key] - if(isdatum(value)) + if(isweakref(value)) + var/datum/weakref/ref = value + var/resolved = ref.resolve() + if(resolved) + value = "[resolved] [REF(resolved)]" + else + value = "null weakref [REF(key)]" + else if(isdatum(value)) value = "[value] [REF(value)]" else if(value == world) value = "world [REF(world)]" @@ -841,6 +851,7 @@ if(value) to_add[new_key] = value ret += to_add + CHECK_TICK return ret /** @@ -848,8 +859,6 @@ * so that list keys that are themselves lists can be fully json-encoded */ /proc/kvpify_list(list/target_list, depth = INFINITY) - if(length(target_list) > REFIFY_KVPIFY_MAX_LENGTH) - return "list\[[length(target_list)]\]" var/list/ret = list() for(var/i in 1 to target_list.len) var/key = target_list[i] @@ -865,10 +874,9 @@ ret += list(list("key" = new_key, "value" = value)) else ret += list(list("key" = i, "value" = new_key)) + CHECK_TICK return ret -#undef REFIFY_KVPIFY_MAX_LENGTH - /// Compares 2 lists, returns TRUE if they are the same /proc/deep_compare_list(list/list_1, list/list_2) if(!islist(list_1) || !islist(list_2)) @@ -896,5 +904,28 @@ return FALSE else if(value_1 != value_2) return FALSE - return TRUE + +/// Returns a copy of the list where any element that is a datum is converted into a weakref +/proc/weakrefify_list(list/target_list) + var/list/ret = list() + for(var/i in 1 to target_list.len) + var/key = target_list[i] + var/new_key = key + if(isdatum(key)) + new_key = WEAKREF(key) + else if(islist(key)) + new_key = weakrefify_list(key) + var/value + if(istext(key) || islist(key) || ispath(key) || isdatum(key) || key == world) + value = target_list[key] + if(isdatum(value)) + value = WEAKREF(value) + else if(islist(value)) + value = weakrefify_list(value) + var/list/to_add = list(new_key) + if(value) + to_add[new_key] = value + ret += to_add + CHECK_TICK + return ret diff --git a/code/controllers/subsystem/lua.dm b/code/controllers/subsystem/lua.dm index 86923fe23c6..826fd277d5a 100644 --- a/code/controllers/subsystem/lua.dm +++ b/code/controllers/subsystem/lua.dm @@ -1,8 +1,3 @@ -//world/proc/shelleo -#define SHELLEO_ERRORLEVEL 1 -#define SHELLEO_STDOUT 2 -#define SHELLEO_STDERR 3 - #define SSLUA_INIT_FAILED 2 SUBSYSTEM_DEF(lua) @@ -49,19 +44,10 @@ SUBSYSTEM_DEF(lua) return time /datum/controller/subsystem/lua/OnConfigLoad() - // Get the current working directory - we need it to set the LUAU_PATH environment variable - var/here = world.shelleo(world.system_type == MS_WINDOWS ? "cd" : "pwd")[SHELLEO_STDOUT] - here = replacetext(here, "\n", "") - var/last_char = copytext_char(here, -1) - if(last_char != "/" && last_char != "\\") - here += "/" - // Read the paths from the config file var/list/lua_path = list() var/list/config_paths = CONFIG_GET(str_list/lua_path) for(var/path in config_paths) - if(path[1] != "/") - path = here + path lua_path += path world.SetConfig("env", "LUAU_PATH", jointext(lua_path, ";")) @@ -158,9 +144,4 @@ SUBSYSTEM_DEF(lua) for(var/datum/lua_editor/editor in editor_list) SStgui.update_uis(editor) -//world/proc/shelleo -#undef SHELLEO_ERRORLEVEL -#undef SHELLEO_STDOUT -#undef SHELLEO_STDERR - #undef SSLUA_INIT_FAILED diff --git a/code/modules/admin/verbs/lua/_wrappers.dm b/code/modules/admin/verbs/lua/_wrappers.dm index 671feb3a514..30de6542b21 100644 --- a/code/modules/admin/verbs/lua/_wrappers.dm +++ b/code/modules/admin/verbs/lua/_wrappers.dm @@ -4,20 +4,30 @@ /proc/wrap_lua_datum_proc_call(datum/thing_to_call, proc_name, list/arguments) if(!usr) usr = GLOB.lua_usr + var/ret if(usr) - SSlua.gc_guard = WrapAdminProcCall(thing_to_call, proc_name, arguments) + ret = WrapAdminProcCall(thing_to_call, proc_name, arguments) else - SSlua.gc_guard = HandleUserlessProcCall("lua", thing_to_call, proc_name, arguments) - return SSlua.gc_guard + ret = HandleUserlessProcCall("lua", thing_to_call, proc_name, arguments) + if(isdatum(ret)) + SSlua.gc_guard = ret + var/datum/ret_datum = ret + ret_datum.RegisterSignal(ret_datum, COMSIG_PARENT_QDELETING, /datum.proc/lua_reference_cleanup, override = TRUE) + return ret /proc/wrap_lua_global_proc_call(proc_name, list/arguments) if(!usr) usr = GLOB.lua_usr + var/ret if(usr) - SSlua.gc_guard = WrapAdminProcCall(GLOBAL_PROC, proc_name, arguments) + ret = WrapAdminProcCall(GLOBAL_PROC, proc_name, arguments) else - SSlua.gc_guard = HandleUserlessProcCall("lua", GLOBAL_PROC, proc_name, arguments) - return SSlua.gc_guard + ret = HandleUserlessProcCall("lua", GLOBAL_PROC, proc_name, arguments) + if(isdatum(ret)) + SSlua.gc_guard = ret + var/datum/ret_datum = ret + ret_datum.RegisterSignal(ret_datum, COMSIG_PARENT_QDELETING, /datum.proc/lua_reference_cleanup, override = TRUE) + return ret /proc/wrap_lua_print(state_id, list/arguments) var/datum/lua_state/target_state diff --git a/code/modules/admin/verbs/lua/helpers.dm b/code/modules/admin/verbs/lua/helpers.dm index 4515bc7b3ff..fc53b587d39 100644 --- a/code/modules/admin/verbs/lua/helpers.dm +++ b/code/modules/admin/verbs/lua/helpers.dm @@ -30,3 +30,17 @@ #undef PROMISE_PENDING #undef PROMISE_RESOLVED #undef PROMISE_REJECTED + +/** + * When a datum is created from lua, it gets held in `SSlua.gc_guard`, and later, + * in the calling state datum's `var/list/references`, just in case it would be garbage + * collected due to there not being any references that BYOND recognizes. To avoid harddels, + * we register this proc as a signal handler any time a DM function called from lua returns + * a datum. + */ +/datum/proc/lua_reference_cleanup() + SIGNAL_HANDLER + if(SSlua.gc_guard == src) + SSlua.gc_guard = null + for(var/datum/lua_state/state in SSlua.states) + state.references -= src diff --git a/code/modules/admin/verbs/lua/lua_editor.dm b/code/modules/admin/verbs/lua/lua_editor.dm index 157b07b2130..526df241dbb 100644 --- a/code/modules/admin/verbs/lua/lua_editor.dm +++ b/code/modules/admin/verbs/lua/lua_editor.dm @@ -85,6 +85,8 @@ switch(action) if("newState") var/state_name = params["name"] + if(!length(state_name)) + return TRUE var/datum/lua_state/new_state = new(state_name) SSlua.states += new_state LAZYREMOVEASSOC(SSlua.editors, "\ref[current_state]", src) @@ -170,10 +172,16 @@ var/log_entry_index = params["entryIndex"] var/list/log_entry = current_state.log[log_entry_index] var/thing_to_debug = traverse_list(params["tableIndices"], log_entry["param"]) + if(isweakref(thing_to_debug)) + var/datum/weakref/ref = thing_to_debug + thing_to_debug = ref.resolve() INVOKE_ASYNC(usr.client, /client.proc/debug_variables, thing_to_debug) return FALSE if("vvGlobal") var/thing_to_debug = traverse_list(params["indices"], current_state.globals) + if(isweakref(thing_to_debug)) + var/datum/weakref/ref = thing_to_debug + thing_to_debug = ref.resolve() INVOKE_ASYNC(usr.client, /client.proc/debug_variables, thing_to_debug) return FALSE if("clearArgs") diff --git a/code/modules/admin/verbs/lua/lua_state.dm b/code/modules/admin/verbs/lua/lua_state.dm index 15c9ab9ad77..4d14837c727 100644 --- a/code/modules/admin/verbs/lua/lua_state.dm +++ b/code/modules/admin/verbs/lua/lua_state.dm @@ -38,6 +38,8 @@ GLOBAL_PROTECT(lua_usr) 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 @@ -55,7 +57,7 @@ GLOBAL_PROTECT(lua_usr) append_to_log = FALSE break if(append_to_log) - log += list(result) + log += list(weakrefify_list(result)) /datum/lua_state/proc/load_script(script) GLOB.IsLuaCall = TRUE @@ -91,6 +93,10 @@ GLOBAL_PROTECT(lua_usr) if(istext(result)) result = list("status" = "errored", "param" = result, "name" = islist(function) ? jointext(function, ".") : function) check_if_slept(result) + var/list/editor_list = LAZYACCESS(SSlua.editors, "\ref[src]") + if(editor_list) + for(var/datum/lua_editor/editor in editor_list) + SStgui.update_uis(editor) return result /datum/lua_state/proc/call_function_return_first(function, ...) @@ -126,7 +132,7 @@ GLOBAL_PROTECT(lua_usr) return result /datum/lua_state/proc/get_globals() - globals = __lua_get_globals(internal_id) + globals = weakrefify_list(__lua_get_globals(internal_id)) /datum/lua_state/proc/get_tasks() return __lua_get_tasks(internal_id) diff --git a/dependencies.sh b/dependencies.sh old mode 100755 new mode 100644 index 95faf891f8b..db37b7d2e55 --- a/dependencies.sh +++ b/dependencies.sh @@ -24,4 +24,4 @@ export PYTHON_VERSION=3.7.9 export AUXLUA_REPO=tgstation/auxlua #auxlua git tag -export AUXLUA_VERSION=0.2.0 +export AUXLUA_VERSION=0.2.1 diff --git a/lua/SS13.lua b/lua/SS13.lua index a2965a327da..ea855d7228c 100644 --- a/lua/SS13.lua +++ b/lua/SS13.lua @@ -17,7 +17,7 @@ function SS13.istype(thing, type) end function SS13.new(type, ...) - local datum = dm.global_proc("_new", type, {...}) + local datum = dm.global_proc("_new", type, { ... }) local references = SS13.state:get_var("references") references:add(datum) return datum @@ -40,40 +40,60 @@ end function SS13.wait(time, timer) local index = #__yield_table + 1 local callback = SS13.new("/datum/callback", SS13.SSlua, "queue_resume", SS13.state, index) - local timedevent = dm.global_proc("_addtimer", callback, time*10, 8, timer, debug.info(1, "sl")) + local timedevent = dm.global_proc("_addtimer", callback, time * 10, 8, timer, debug.info(1, "sl")) coroutine.yield() dm.global_proc("deltimer", timedevent, timer) end - function SS13.register_signal(datum, signal, func, make_easy_clear_function) if not SS13.signal_handlers then SS13.signal_handlers = {} end - if not SS13.istype(datum, "/datum") then return end + if not SS13.istype(datum, "/datum") then + return + end local ref = dm.global_proc("REF", datum) if not SS13.signal_handlers[ref] then SS13.signal_handlers[ref] = {} end - if signal == "_cleanup" then return end + if signal == "_cleanup" then + return + end if not SS13.signal_handlers[ref][signal] then SS13.signal_handlers[ref][signal] = {} end local callback = SS13.new("/datum/callback", SS13.state, "call_function_return_first") callback:call_proc("RegisterSignal", datum, signal, "Invoke") local callback_ref = dm.global_proc("REF", callback) - local path = {"SS13", "signal_handlers", ref, signal, callback_ref, "func"} - callback:set_var("arguments", {path}) + local path = { "SS13", "signal_handlers", ref, signal, callback_ref, "func" } + callback:set_var("arguments", { path }) if not SS13.signal_handlers[ref]["_cleanup"] then - local cleanup_path = {"SS13", "signal_handlers", ref, "_cleanup"} + local cleanup_path = { "SS13", "signal_handlers", ref, "_cleanup", "func" } local cleanup_callback = SS13.new("/datum/callback", SS13.state, "call_function_return_first", cleanup_path) cleanup_callback:call_proc("RegisterSignal", datum, "parent_qdeleting", "Invoke") - SS13.signal_handlers[ref]["_cleanup"] = function(datum) - SS13.signal_handler_cleanup(datum) - dm.global_proc("qdel", cleanup_callback) + SS13.signal_handlers[ref]["_cleanup"] = { + func = function(datum) + SS13.signal_handler_cleanup(datum) + dm.global_proc("qdel", cleanup_callback) + end, + callback = cleanup_callback, + } + end + if signal == "parent_qdeleting" then --We want to make sure that the cleanup function is the very last signal handler called. + local comp_lookup = datum:get_var("comp_lookup") + if comp_lookup then + local lookup_table = comp_lookup:to_table() + local lookup_for_signal = lookup_table.parent_qdeleting + if lookup_for_signal and not SS13.istype(lookup_for_signal, "/datum") then + local cleanup_callback_index = + dm.global_proc("_list_find", lookup_for_signal, SS13.signal_handlers[ref]["_cleanup"].callback) + if cleanup_callback_index ~= 0 then + dm.global_proc("_list_swap", lookup_for_signal, cleanup_callback_index, lookup_for_signal.len) + end + end end end - SS13.signal_handlers[ref][signal][callback_ref] = {func=func, callback=callback} + SS13.signal_handlers[ref][signal][callback_ref] = { func = func, callback = callback } if make_easy_clear_function then local clear_function_name = "clear_signal_" .. ref .. "_" .. signal .. "_" .. callback_ref SS13[clear_function_name] = function() @@ -86,17 +106,22 @@ function SS13.register_signal(datum, signal, func, make_easy_clear_function) return callback end - function SS13.unregister_signal(datum, signal, callback) local function clear_handler(handler_info) - if not handler_info then return end - if not handler_info.callback then return end + if not handler_info then + return + end + if not handler_info.callback then + return + end local handler_callback = handler_info.callback handler_callback:call_proc("UnregisterSignal", datum, signal) dm.global_proc("qdel", handler_callback) end - if not SS13.signal_handlers then return end + if not SS13.signal_handlers then + return + end local ref = dm.global_proc("REF", datum) local function clear_easy_clear_function(callback_ref) @@ -104,9 +129,15 @@ function SS13.unregister_signal(datum, signal, callback) SS13[clear_function_name] = nil end - if not SS13.signal_handlers[ref] then return end - if signal == "_cleanup" then return end - if not SS13.signal_handlers[ref][signal] then return end + if not SS13.signal_handlers[ref] then + return + end + if signal == "_cleanup" then + return + end + if not SS13.signal_handlers[ref][signal] then + return + end if not callback then for handler_key, handler_info in SS13.signal_handlers[ref][signal] do @@ -115,7 +146,9 @@ function SS13.unregister_signal(datum, signal, callback) end SS13.signal_handlers[ref][signal] = nil else - if not SS13.istype(callback, "/datum/callback") then return end + if not SS13.istype(callback, "/datum/callback") then + return + end local callback_ref = dm.global_proc("REF", callback) clear_easy_clear_function(callback_ref) clear_handler(SS13.signal_handlers[ref][signal][callback_ref]) @@ -124,9 +157,13 @@ function SS13.unregister_signal(datum, signal, callback) end function SS13.signal_handler_cleanup(datum) - if not SS13.signal_handlers then return end + if not SS13.signal_handlers then + return + end local ref = dm.global_proc("REF", datum) - if not SS13.signal_handlers[ref] then return end + if not SS13.signal_handlers[ref] then + return + end for signal, _ in SS13.signal_handlers[ref] do SS13.unregister_signal(datum, signal) @@ -145,9 +182,9 @@ function SS13.set_timeout(time, func) SS13.timeouts[callback_ref] = nil func() end - local path = {"SS13", "timeouts", callback_ref} - callback:set_var("arguments", {path}) - dm.global_proc("_addtimer", callback, time*10, 8, nil, debug.info(1, "sl")) + local path = { "SS13", "timeouts", callback_ref } + callback:set_var("arguments", { path }) + dm.global_proc("_addtimer", callback, time * 10, 8, nil, debug.info(1, "sl")) end return SS13 diff --git a/tgui/packages/tgui/interfaces/LuaEditor/index.js b/tgui/packages/tgui/interfaces/LuaEditor/index.js index 45a3929b8fa..701fee82ee1 100644 --- a/tgui/packages/tgui/interfaces/LuaEditor/index.js +++ b/tgui/packages/tgui/interfaces/LuaEditor/index.js @@ -15,7 +15,7 @@ hljs.registerLanguage('lua', lua); export const LuaEditor = (props, context) => { const { act, data } = useBackend(context); - const { noStateYet, globals, documentation } = data; + const { noStateYet, globals, documentation, tasks } = data; const [modal, setModal] = useLocalState( context, 'modal', @@ -30,18 +30,35 @@ export const LuaEditor = (props, context) => { let tabContent; switch (activeTab) { case 'globals': { - tabContent = ( - act('vvGlobal', { indices: path })} - callType="callFunction" - /> - ); + if (!globals) { + tabContent = ( +

+ Could not retrieve the global table. Was it corrupted or shadowed? +

+ ); + } else { + tabContent = ( + act('vvGlobal', { indices: path })} + callType="callFunction" + /> + ); + } break; } case 'tasks': { - tabContent = ; + if (!tasks) { + tabContent = ( +

+ Could not retrieve task info. Was the global table corrupted or + shadowed? +

+ ); + } else { + tabContent = ; + } break; } case 'log': {