diff --git a/code/modules/admin/verbs/lua/lua_state.dm b/code/modules/admin/verbs/lua/lua_state.dm index ee7d6953e12..27994d966a7 100644 --- a/code/modules/admin/verbs/lua/lua_state.dm +++ b/code/modules/admin/verbs/lua/lua_state.dm @@ -109,14 +109,7 @@ GLOBAL_PROTECT(lua_usr) if(islist(function)) var/list/new_function_path = list() for(var/path_element in function) - if(isweakref(path_element)) - var/datum/weakref/weak_ref = path_element - var/resolved = weak_ref.hard_resolve() - if(!resolved) - return list("status" = "errored", "param" = "Weakref in function path ([weak_ref] [text_ref(weak_ref)]) resolved to null.", "name" = jointext(function, ".")) - new_function_path += resolved - else - new_function_path += path_element + 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) diff --git a/lua/SS13_base.lua b/lua/SS13_base.lua index ddacb345fd5..4bdd49d5d90 100644 --- a/lua/SS13_base.lua +++ b/lua/SS13_base.lua @@ -1,3 +1,6 @@ +local timer = require("timer") +local state = require("state") + local SS13 = {} __SS13_signal_handlers = __SS13_signal_handlers or {} @@ -6,12 +9,7 @@ SS13.SSlua = dm.global_vars.vars.SSlua SS13.global_proc = "some_magic_bullshit" -for _, state in SS13.SSlua.vars.states do - if state.vars.internal_id == dm.state_id then - SS13.state = state - break - end -end +SS13.state = state.state function SS13.get_runner_ckey() return SS13.state:get_var("ckey_last_runner") @@ -25,12 +23,16 @@ function SS13.istype(thing, type) return dm.global_proc("_istype", thing, dm.global_proc("_text2path", type)) == 1 end +function SS13.start_tracking(datum) + local references = SS13.state.vars.references + references:add(datum) + SS13.state:call_proc("clear_on_delete", datum) +end + function SS13.new(type, ...) - local datum = SS13.new_untracked(type, table.unpack({...})) + local datum = SS13.new_untracked(type, ...) if datum then - local references = SS13.state.vars.references - references:add(datum) - SS13.state:call_proc("clear_on_delete", datum) + SS13.start_tracking(datum) return datum end end @@ -75,58 +77,40 @@ function SS13.await(thing_to_call, proc_to_call, ...) return return_value, runtime_message end -function SS13.register_signal(datum, signal, func, make_easy_clear_function) +function SS13.register_signal(datum, signal, func) if not SS13.istype(datum, "/datum") then return end - if not __SS13_signal_handlers[datum] then - __SS13_signal_handlers[datum] = {} + local datumWeakRef = dm.global_proc("WEAKREF", datum) + if not __SS13_signal_handlers[datumWeakRef] then + __SS13_signal_handlers[datumWeakRef] = {} end if signal == "_cleanup" then return end - if not __SS13_signal_handlers[datum][signal] then - __SS13_signal_handlers[datum][signal] = {} + if not __SS13_signal_handlers[datumWeakRef][signal] then + __SS13_signal_handlers[datumWeakRef][signal] = {} end local callback = SS13.new("/datum/callback", SS13.state, "call_function_return_first") + local callbackWeakRef = dm.global_proc("WEAKREF", callback) callback:call_proc("RegisterSignal", datum, signal, "Invoke") - local path = { "__SS13_signal_handlers", dm.global_proc("WEAKREF", datum), signal, dm.global_proc("WEAKREF", callback), "func" } + local path = { "__SS13_signal_handlers", datumWeakRef, signal, callbackWeakRef, "func" } callback.vars.arguments = { path } - if not __SS13_signal_handlers[datum]["_cleanup"] then - local cleanup_path = { "__SS13_signal_handlers", dm.global_proc("WEAKREF", datum), "_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[datum]["_cleanup"] = { - func = function(datum) - SS13.signal_handler_cleanup(datum) - SS13.stop_tracking(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.vars._listen_lookup - if comp_lookup then - local lookup_for_signal = comp_lookup.entries.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[datum]["_cleanup"].callback) - if cleanup_callback_index ~= 0 and cleanup_callback_index ~= #comp_lookup then - dm.global_proc("_list_swap", lookup_for_signal, cleanup_callback_index, #lookup_for_signal) - end - end - end - end - __SS13_signal_handlers[datum][signal][callback] = { func = func, callback = callback } - if make_easy_clear_function then - local clear_function_name = "clear_signal_" .. tostring(datum) .. "_" .. signal .. "_" .. tostring(callback) - SS13[clear_function_name] = function() - if callback then - SS13.unregister_signal(datum, signal, callback) - end - SS13[clear_function_name] = nil + if not __SS13_signal_handlers[datumWeakRef]._cleanup then + local cleanupCallback = SS13.new("/datum/callback", SS13.state, "call_function_return_first") + local cleanupPath = { "__SS13_signal_handlers", datumWeakRef, "_cleanup"} + cleanupCallback.vars.arguments = { cleanupPath } + cleanupCallback:call_proc("RegisterSignal", datum, "parent_qdeleting", "Invoke") + __SS13_signal_handlers[datumWeakRef]._cleanup = function(datum) + SS13.start_tracking(datumWeakRef) + timer.set_timeout(0, function() + SS13.signal_handler_cleanup(datumWeakRef) + SS13.stop_tracking(cleanupCallback) + SS13.stop_tracking(datumWeakRef) + end) end end + __SS13_signal_handlers[datumWeakRef][signal][callbackWeakRef] = { func = func, callback = callback } return callback end @@ -143,51 +127,53 @@ function SS13.unregister_signal(datum, signal, callback) return end local handler_callback = handler_info.callback - handler_callback:call_proc("UnregisterSignal", datum, signal) + local callbackWeakRef = dm.global_proc("WEAKREF", handler_callback) + if not SS13.istype(datum, "/datum/weakref") then + handler_callback:call_proc("UnregisterSignal", datum, signal) + end SS13.stop_tracking(handler_callback) end - local function clear_easy_clear_function(callback_to_clear) - local clear_function_name = "clear_signal_" .. tostring(datum) .. "_" .. signal .. "_" .. tostring(callback_to_clear) - SS13[clear_function_name] = nil + local datumWeakRef = datum + if not SS13.istype(datum, "/datum/weakref") then + datumWeakRef = dm.global_proc("WEAKREF", datum) end - - if not __SS13_signal_handlers[datum] then + if not __SS13_signal_handlers[datumWeakRef] then return end + if signal == "_cleanup" then return end - if not __SS13_signal_handlers[datum][signal] then + + if not __SS13_signal_handlers[datumWeakRef][signal] then return end if not callback then - for handler_key, handler_info in __SS13_signal_handlers[datum][signal] do - clear_easy_clear_function(handler_key) + for handler_key, handler_info in __SS13_signal_handlers[datumWeakRef][signal] do clear_handler(handler_info) end - __SS13_signal_handlers[datum][signal] = nil + __SS13_signal_handlers[datumWeakRef][signal] = nil else if not SS13.istype(callback, "/datum/callback") then return end - clear_easy_clear_function(callback) - clear_handler(__SS13_signal_handlers[datum][signal][callback]) - __SS13_signal_handlers[datum][signal][callback] = nil + local callbackWeakRef = dm.global_proc("WEAKREF", callback) + clear_handler(__SS13_signal_handlers[datumWeakRef][signal][callbackWeakRef]) + __SS13_signal_handlers[datumWeakRef][signal][callbackWeakRef] = nil end end -function SS13.signal_handler_cleanup(datum) - if not __SS13_signal_handlers[datum] then +function SS13.signal_handler_cleanup(datumWeakRef) + if not __SS13_signal_handlers[datumWeakRef] then return end - for signal, _ in __SS13_signal_handlers[datum] do - SS13.unregister_signal(datum, signal) + for signal, _ in __SS13_signal_handlers[datumWeakRef] do + SS13.unregister_signal(datumWeakRef, signal) end - - __SS13_signal_handlers[datum] = nil + __SS13_signal_handlers[datumWeakRef] = nil end return SS13 diff --git a/lua/docs/handler_group.md b/lua/docs/handler_group.md new file mode 100644 index 00000000000..7856ff58b48 --- /dev/null +++ b/lua/docs/handler_group.md @@ -0,0 +1,66 @@ +# Handler Group + +This module is for registering signals on a datum or several datums and being able to clear them all at once without having to unregister them manually. This is particularly useful if you register signals on a datum and need to clear them later without accidentally unregistering unrelated signals + +## Functions + +### HandlerGroup.new() +Creates a new handler group instance + +### HandlerGroup:register_signal(datum, signal, func) +Registers a signal on a datum, exactly the same as `SS13.register_signal` + +### HandlerGroup:clear() +Clears all registered signals that have been registered by this handler group. + +### HandlerGroup:clear_on(datum, signal, func) +Clears all registered signals that have been registered by this handler group when a signal is called on the specified datum. Additionally, a function can be ran before it is cleared + +### HandlerGroup.register_once(datum, signal func) +Identical to just creating a new HandlerGroup instance and calling `clear_on(datum, signal, func)`. + +The idea is to register a signal and clear it after it has been called once. + +## Examples + +The following examples showcase why using handler groups can make life easier in specific situations. + +### Explode when mob enters location +This function creates a 1 tile-wide explosion at the specified location if a specific mob walks over it. The explosion won't happen if the mob dies. This function should be callable on the same mob for different locations. The function should be self-contained, it should not affect other registered signals that the mob may have registered. + +#### Without Handler Groups +```lua +local function explodeAtLocation(mobVar, position) + local deathCallback + local moveCallback + local function unlinkFromMob() + SS13.unregister_signal(mobVar, "living_death", deathCallback) + SS13.unregister_signal(mobVar, "movable_moved", moveCallback) + end + deathCallback = SS13.register_signal(mobVar, "living_death", function(_, gibbed) + unlinkFromMob() + end) + moveCallback = SS13.register_signal(mobVar, "movable_moved", function(_, oldLoc) + if mobVar:get_var("loc") == position then + -- Creates a 1 tile-wide explosion at the specified position + dm.global_proc("explosion", position, 1, 0, 0) + unlinkFromMob() + end + end) +end +``` + +#### With Handler Groups +```lua +local function explodeAtLocation(mobVar, position) + local handler = handler_group.new() + handler:clear_on(mobVar, "living_death") + handler:register_signal(mobVar, "movable_moved", function(_, oldLoc) + if mobVar:get_var("loc") == position then + -- Creates a 1 tile-wide explosion at the specified position + dm.global_proc("explosion", position, 1, 0, 0) + handler:clear() + end + end) +end +``` diff --git a/lua/handler_group.lua b/lua/handler_group.lua new file mode 100644 index 00000000000..fff63ad18e4 --- /dev/null +++ b/lua/handler_group.lua @@ -0,0 +1,49 @@ +local SS13 = require('SS13') +local HandlerGroup = {} +HandlerGroup.__index = HandlerGroup + +function HandlerGroup.new() + return setmetatable({ + registered = {} + }, HandlerGroup) +end + +-- Registers a signal on a datum for this handler group instance. +function HandlerGroup:register_signal(datum, signal, func) + local callback = SS13.register_signal(datum, signal, func) + if not callback then + return + end + table.insert(self.registered, { datum = datum, signal = signal, callback = callback }) +end + +-- Clears all the signals that have been registered on this HandlerGroup +function HandlerGroup:clear() + for _, data in self.registered do + if not data.callback or not data.datum then + continue + end + SS13.unregister_signal(data.datum, data.signal, data.callback) + end + table.clear(self.registered) +end + +-- Clears all the signals that have been registered on this HandlerGroup when a specific signal is sent on a datum. +function HandlerGroup:clear_on(datum, signal, func) + SS13.register_signal(datum, signal, function(...) + if func then + func(...) + end + self:clear() + end) +end + +-- Registers a signal on a datum and clears it after it is called once. +function HandlerGroup.register_once(datum, signal, func) + local callback = HandlerGroup.new() + callback:clear_on(datum, signal, func) + return callback +end + + +return HandlerGroup diff --git a/lua/state.lua b/lua/state.lua new file mode 100644 index 00000000000..080ee9f7eb3 --- /dev/null +++ b/lua/state.lua @@ -0,0 +1,9 @@ +local SSlua = dm.global_vars:get_var("SSlua") + +for _, state in SSlua:get_var("states") do + if state:get_var("internal_id") == dm.state_id then + return { state = state } + end +end + +return { state = nil } diff --git a/lua/timer.lua b/lua/timer.lua index 605e5b98a2e..8619bbb54a2 100644 --- a/lua/timer.lua +++ b/lua/timer.lua @@ -1,6 +1,8 @@ -local SS13 = require("SS13_base") +local state = require("state") + local Timer = {} +local SSlua = dm.global_vars:get_var("SSlua") __Timer_timers = __Timer_timers or {} __Timer_callbacks = __Timer_callbacks or {} @@ -35,7 +37,7 @@ function __stop_internal_timer(func) end __Timer_timer_processing = __Timer_timer_processing or false -SS13.state:set_var("timer_enabled", 1) +state.state:set_var("timer_enabled", 1) __Timer_timer_process = function(seconds_per_tick) if __Timer_timer_processing then return 0 @@ -50,7 +52,7 @@ __Timer_timer_process = function(seconds_per_tick) sleep() end if time >= timeData.executeTime then - SS13.state:get_var("functions_to_execute"):add(func) + state.state:get_var("functions_to_execute"):add(func) timeData.executing = true end end @@ -61,7 +63,7 @@ end function Timer.wait(time) local next_yield_index = __next_yield_index __add_internal_timer(function() - SS13.SSlua:call_proc("queue_resume", SS13.state, next_yield_index) + SSlua:call_proc("queue_resume", state.state, next_yield_index) end, time * 10, false) coroutine.yield() end