diff --git a/code/modules/admin/verbs/lua/README.md b/code/modules/admin/verbs/lua/README.md index 17138c95fde..707184d4d77 100644 --- a/code/modules/admin/verbs/lua/README.md +++ b/code/modules/admin/verbs/lua/README.md @@ -138,6 +138,12 @@ The `SS13` package contains various helper functions that use code specific to t ### SS13.state A reference to the state datum (`/datum/lua_state`) handling this Lua state. +### SS13.get_runner_ckey() +The ckey of the user who ran the lua script in the current context. Can be unreliable if accessed after sleeping. + +### SS13.get_runner_client() +Returns the client of the user who ran the lua script in the current context. Can be unreliable if accessed after sleeping. + ### SS13.global_proc A wrapper for the magic string used to tell `WrapAdminProcCall` to call a global proc. For instance, `/datum/callback` must be instantiated with `SS13.global_proc` as its first argument to specify that it will be invoking a global proc. @@ -224,6 +230,42 @@ SS13.set_timeout(5, function() end) ``` +### SS13.start_loop(time, amount, func) +Creates a timer which will execute `func` after `time` **seconds**. `func` should not expect to be passed any arguments, as it will not be passed any. Works exactly the same as `SS13.set_timeout` except it will loop the timer `amount` times. If `amount` is set to -1, it will loop indefinitely. Returns a number value, which represents the timer's id. Can be stopped with `SS13.end_loop` +Returns a number, the timer id, which is needed to stop indefinite timers. +The following example will output a message to chat every 5 seconds, repeating 10 times: +```lua +SS13.start_loop(5, 10, function() + dm.global_proc("to_chat", dm.world, "Hello World!") +end) +``` +The following example will output a message to chat every 5 seconds, until `SS13.end_loop(timerid)` is called: +```lua +local timerid = SS13.start_loop(5, -1, function() + dm.global_proc("to_chat", dm.world, "Hello World!") +end) +``` + +### SS13.end_loop(id) +Prematurely ends a loop that hasn't ended yet, created with `SS13.start_loop`. Silently fails if there is no started loop with the specified id. +The following example will output a message to chat every 5 seconds and delete it after it has repeated 20 times: +```lua +local repeated_amount = 0 +-- timerid won't be in the looping function's scope if declared before the function is declared. +local timerid +timerid = SS13.start_loop(5, -1, function() + dm.global_proc("to_chat", dm.world, "Hello World!") + repeated_amount += 1 + if repeated_amount >= 20 then + SS13.end_loop(timerid) + end +end) +``` + +### SS13.stop_all_loops() +Stops all current running loops that haven't ended yet. +Useful in case you accidentally left a indefinite loop running without storing the id anywhere. + ### SS13.stop_tracking(datum) Stops tracking a datum that was created via `SS13.new` so that it can be garbage collected and deleted without having to qdel. Should be used for things like callbacks and other such datums where the reference to the variable is no longer needed. diff --git a/code/modules/admin/verbs/lua/lua_editor.dm b/code/modules/admin/verbs/lua/lua_editor.dm index d8a414f4685..ea052509779 100644 --- a/code/modules/admin/verbs/lua/lua_editor.dm +++ b/code/modules/admin/verbs/lua/lua_editor.dm @@ -126,6 +126,7 @@ return TRUE if("runCode") var/code = params["code"] + current_state.ckey_last_runner = usr.ckey var/result = current_state.load_script(code) var/index_with_result = current_state.log_result(result) message_admins("[key_name(usr)] executed [length(code)] bytes of lua code. [ADMIN_LUAVIEW_CHUNK(current_state, index_with_result)]") diff --git a/code/modules/admin/verbs/lua/lua_state.dm b/code/modules/admin/verbs/lua/lua_state.dm index b90344d6333..d5b2c6de65d 100644 --- a/code/modules/admin/verbs/lua/lua_state.dm +++ b/code/modules/admin/verbs/lua/lua_state.dm @@ -21,6 +21,9 @@ GLOBAL_PROTECT(lua_usr) /// 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 = "" + /datum/lua_state/vv_edit_var(var_name, var_value) . = ..() if(var_name == NAMEOF(src, internal_id)) diff --git a/lua/SS13.lua b/lua/SS13.lua index a17d5b50577..076dc9ee72e 100644 --- a/lua/SS13.lua +++ b/lua/SS13.lua @@ -1,5 +1,9 @@ local SS13 = {} +__SS13_signal_handlers = __SS13_signal_handlers or {} +__SS13_timeouts = __SS13_timeouts or {} +__SS13_timeouts_id_mapping = __SS13_timeouts_id_mapping or {} + SS13.SSlua = dm.global_vars.vars.SSlua SS13.global_proc = "some_magic_bullshit" @@ -11,6 +15,14 @@ for _, state in SS13.SSlua.vars.states do end end +function SS13.get_runner_ckey() + return SS13.state:get_var("ckey_last_runner") +end + +function SS13.get_runner_client() + return dm.global_vars:get_var("GLOB"):get_var("directory"):get(SS13.get_runner_ckey()) +end + function SS13.istype(thing, type) return dm.global_proc("_istype", thing, dm.global_proc("_text2path", type)) == 1 end @@ -65,39 +77,36 @@ function SS13.await(thing_to_call, proc_to_call, ...) return return_value, runtime_message end -function SS13.wait(time, timer) +function SS13.wait(time) local callback = SS13.new("/datum/callback", SS13.SSlua, "queue_resume", SS13.state, __next_yield_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, nil, debug.info(1, "sl")) coroutine.yield() - dm.global_proc("deltimer", timedevent, timer) + dm.global_proc("deltimer", timedevent) SS13.stop_tracking(callback) 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.signal_handlers[datum] then - SS13.signal_handlers[datum] = {} + if not __SS13_signal_handlers[datum] then + __SS13_signal_handlers[datum] = {} 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[datum][signal] then + __SS13_signal_handlers[datum][signal] = {} end local callback = SS13.new("/datum/callback", SS13.state, "call_function_return_first") 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", dm.global_proc("WEAKREF", datum), signal, dm.global_proc("WEAKREF", callback), "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" } + 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"] = { + __SS13_signal_handlers[datum]["_cleanup"] = { func = function(datum) SS13.signal_handler_cleanup(datum) SS13.stop_tracking(cleanup_callback) @@ -111,14 +120,14 @@ function SS13.register_signal(datum, signal, func, make_easy_clear_function) 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) + 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 } + __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() @@ -148,70 +157,93 @@ function SS13.unregister_signal(datum, signal, callback) SS13.stop_tracking(handler_callback) end - if not SS13.signal_handlers then - return - 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 end - if not SS13.signal_handlers[datum] then + if not __SS13_signal_handlers[datum] then return end if signal == "_cleanup" then return end - if not SS13.signal_handlers[datum][signal] then + if not __SS13_signal_handlers[datum][signal] then return end if not callback then - for handler_key, handler_info in SS13.signal_handlers[datum][signal] do + for handler_key, handler_info in __SS13_signal_handlers[datum][signal] do clear_easy_clear_function(handler_key) clear_handler(handler_info) end - SS13.signal_handlers[datum][signal] = nil + __SS13_signal_handlers[datum][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 + clear_handler(__SS13_signal_handlers[datum][signal][callback]) + __SS13_signal_handlers[datum][signal][callback] = nil end end function SS13.signal_handler_cleanup(datum) - if not SS13.signal_handlers then - return - end - if not SS13.signal_handlers[datum] then + if not __SS13_signal_handlers[datum] then return end - for signal, _ in SS13.signal_handlers[datum] do + for signal, _ in __SS13_signal_handlers[datum] do SS13.unregister_signal(datum, signal) end - SS13.signal_handlers[datum] = nil + __SS13_signal_handlers[datum] = nil end -function SS13.set_timeout(time, func, timer) - if not SS13.timeouts then - SS13.timeouts = {} +function SS13.set_timeout(time, func) + SS13.start_loop(time, 1, func) +end + +function SS13.start_loop(time, amount, func) + if not amount or amount == 0 then + return end local callback = SS13.new("/datum/callback", SS13.state, "call_function") - local timedevent = dm.global_proc("_addtimer", callback, time * 10, 8, timer, debug.info(1, "sl")) - SS13.timeouts[callback] = function() - SS13.timeouts[callback] = nil - dm.global_proc("deltimer", timedevent, timer) - SS13.stop_tracking(callback) + local timedevent = dm.global_proc("_addtimer", callback, time * 10, 40, nil, debug.info(1, "sl")) + local doneAmount = 0 + __SS13_timeouts[callback] = function() + doneAmount += 1 + if amount ~= -1 and doneAmount >= amount then + SS13.end_loop(timedevent) + end func() end - local path = { "SS13", "timeouts", dm.global_proc("WEAKREF", callback) } + local loop_data = { + callback = callback, + loop_amount = amount, + } + __SS13_timeouts_id_mapping[timedevent] = loop_data + local path = { "__SS13_timeouts", dm.global_proc("WEAKREF", callback) } callback.vars.arguments = { path } + return timedevent +end + +function SS13.end_loop(id) + local data = __SS13_timeouts_id_mapping[id] + if data then + __SS13_timeouts_id_mapping[id] = nil + __SS13_timeouts[data.callback] = nil + SS13.stop_tracking(data.callback) + dm.global_proc("deltimer", id) + end +end + +function SS13.stop_all_loops() + for id, data in __SS13_timeouts_id_mapping do + if data.amount ~= 1 then + SS13.end_loop(id) + end + end end return SS13