Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MIRROR] Expands the SS13.lua module by adding loop helpers and functions to get the script runner. #186

Merged
merged 1 commit into from
Oct 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions code/modules/admin/verbs/lua/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.

Expand Down
1 change: 1 addition & 0 deletions code/modules/admin/verbs/lua/lua_editor.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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)]")
Expand Down
3 changes: 3 additions & 0 deletions code/modules/admin/verbs/lua/lua_state.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
114 changes: 73 additions & 41 deletions lua/SS13.lua
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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()
Expand Down Expand Up @@ -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
Loading