diff --git a/code/controllers/subsystem/lua.dm b/code/controllers/subsystem/lua.dm index 641416b7243..da37dad34f5 100644 --- a/code/controllers/subsystem/lua.dm +++ b/code/controllers/subsystem/lua.dm @@ -16,6 +16,7 @@ SUBSYSTEM_DEF(lua) var/list/resumes = list() var/list/current_run = list() + var/list/current_states_run = list() /// Protects return values from getting GCed before getting converted to lua values /// Gets cleared every tick. @@ -97,6 +98,7 @@ SUBSYSTEM_DEF(lua) // then resumes every yielded task in the order their resumes were queued if(!resumed) current_run = list("sleeps" = sleeps.Copy(), "resumes" = resumes.Copy()) + current_states_run = states.Copy() sleeps.Cut() resumes.Cut() @@ -136,6 +138,13 @@ SUBSYSTEM_DEF(lua) if(MC_TICK_CHECK) break + while(length(current_states_run)) + var/datum/lua_state/state = current_states_run[current_states_run.len] + current_states_run.len-- + state.process(wait) + if(MC_TICK_CHECK) + break + // Update every lua editor TGUI open for each state that had a task awakened or resumed for(var/datum/lua_state/state in affected_states) INVOKE_ASYNC(state, TYPE_PROC_REF(/datum/lua_state, update_editors)) diff --git a/code/modules/admin/verbs/lua/lua_state.dm b/code/modules/admin/verbs/lua/lua_state.dm index d5b2c6de65d..415d9f6971a 100644 --- a/code/modules/admin/verbs/lua/lua_state.dm +++ b/code/modules/admin/verbs/lua/lua_state.dm @@ -24,6 +24,12 @@ GLOBAL_PROTECT(lua_usr) /// 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)) @@ -89,6 +95,13 @@ GLOBAL_PROTECT(lua_usr) return result +/datum/lua_state/process(seconds_per_tick) + if(timer_enabled) + call_function("__Timer_timer_process", seconds_per_tick) + for(var/function as anything in functions_to_execute) + call_function(list("__Timer_callbacks", function)) + functions_to_execute.Cut() + /datum/lua_state/proc/call_function(function, ...) var/call_args = length(args) > 1 ? args.Copy(2) : list() if(islist(function)) diff --git a/lua/SS13.lua b/lua/SS13.lua index 076dc9ee72e..a3cef0f6052 100644 --- a/lua/SS13.lua +++ b/lua/SS13.lua @@ -1,249 +1,10 @@ -local SS13 = {} +local SS13 = require("SS13_base") +local timer = require("timer") -__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" - -for _, state in SS13.SSlua.vars.states do - if state.vars.internal_id == dm.state_id then - SS13.state = state - break - 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 - -function SS13.new(type, ...) - local datum = SS13.new_untracked(type, table.unpack({...})) - if datum then - local references = SS13.state.vars.references - references:add(datum) - SS13.state:call_proc("clear_on_delete", datum) - return datum - end -end - -function SS13.type(string_type) - return dm.global_proc("_text2path", string_type) -end - -function SS13.qdel(datum) - if SS13.is_valid(datum) then - dm.global_proc("qdel", datum) - return true - end - return false -end - -function SS13.new_untracked(type, ...) - return dm.global_proc("_new", type, { ... }) -end - -function SS13.is_valid(datum) - if datum and not datum:is_null() and not datum:get_var("gc_destroyed") then - return true - end - return false -end - -function SS13.await(thing_to_call, proc_to_call, ...) - if not SS13.istype(thing_to_call, "/datum") then - thing_to_call = SS13.global_proc - end - if thing_to_call == SS13.global_proc then - proc_to_call = "/proc/" .. proc_to_call - end - local promise = SS13.new("/datum/auxtools_promise", thing_to_call, proc_to_call, ...) - local promise_vars = promise.vars - while promise_vars.status == 0 do - sleep() - end - local return_value, runtime_message = promise_vars.return_value, promise_vars.runtime_message - SS13.stop_tracking(promise) - return return_value, runtime_message -end - -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, nil, debug.info(1, "sl")) - coroutine.yield() - dm.global_proc("deltimer", timedevent) - SS13.stop_tracking(callback) -end - -function SS13.register_signal(datum, signal, func, make_easy_clear_function) - if not SS13.istype(datum, "/datum") then - return - end - 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] = {} - 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" } - 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 - end - end - return callback -end - -function SS13.stop_tracking(datum) - SS13.state:call_proc("let_soft_delete", datum) -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 - local handler_callback = handler_info.callback - handler_callback:call_proc("UnregisterSignal", datum, signal) - 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 - end - - if not __SS13_signal_handlers[datum] then - return - end - if signal == "_cleanup" then - return - end - 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 - clear_easy_clear_function(handler_key) - clear_handler(handler_info) - end - __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 - end -end - -function SS13.signal_handler_cleanup(datum) - if not __SS13_signal_handlers[datum] then - return - end - - for signal, _ in __SS13_signal_handlers[datum] do - SS13.unregister_signal(datum, signal) - end - - __SS13_signal_handlers[datum] = nil -end - -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, 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 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 +SS13.wait = timer.wait +SS13.set_timeout = timer.set_timeout +SS13.start_loop = timer.start_loop +SS13.end_loop = timer.end_loop +SS13.stop_all_loops = timer.stop_all_loops return SS13 diff --git a/lua/SS13_base.lua b/lua/SS13_base.lua new file mode 100644 index 00000000000..ddacb345fd5 --- /dev/null +++ b/lua/SS13_base.lua @@ -0,0 +1,193 @@ +local SS13 = {} + +__SS13_signal_handlers = __SS13_signal_handlers or {} + +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 + +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 + +function SS13.new(type, ...) + local datum = SS13.new_untracked(type, table.unpack({...})) + if datum then + local references = SS13.state.vars.references + references:add(datum) + SS13.state:call_proc("clear_on_delete", datum) + return datum + end +end + +function SS13.type(string_type) + return dm.global_proc("_text2path", string_type) +end + +function SS13.qdel(datum) + if SS13.is_valid(datum) then + dm.global_proc("qdel", datum) + return true + end + return false +end + +function SS13.new_untracked(type, ...) + return dm.global_proc("_new", type, { ... }) +end + +function SS13.is_valid(datum) + if datum and not datum:is_null() and not datum:get_var("gc_destroyed") then + return true + end + return false +end + +function SS13.await(thing_to_call, proc_to_call, ...) + if not SS13.istype(thing_to_call, "/datum") then + thing_to_call = SS13.global_proc + end + if thing_to_call == SS13.global_proc then + proc_to_call = "/proc/" .. proc_to_call + end + local promise = SS13.new("/datum/auxtools_promise", thing_to_call, proc_to_call, ...) + local promise_vars = promise.vars + while promise_vars.status == 0 do + sleep() + end + local return_value, runtime_message = promise_vars.return_value, promise_vars.runtime_message + SS13.stop_tracking(promise) + return return_value, runtime_message +end + +function SS13.register_signal(datum, signal, func, make_easy_clear_function) + if not SS13.istype(datum, "/datum") then + return + end + 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] = {} + 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" } + 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 + end + end + return callback +end + +function SS13.stop_tracking(datum) + SS13.state:call_proc("let_soft_delete", datum) +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 + local handler_callback = handler_info.callback + handler_callback:call_proc("UnregisterSignal", datum, signal) + 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 + end + + if not __SS13_signal_handlers[datum] then + return + end + if signal == "_cleanup" then + return + end + 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 + clear_easy_clear_function(handler_key) + clear_handler(handler_info) + end + __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 + end +end + +function SS13.signal_handler_cleanup(datum) + if not __SS13_signal_handlers[datum] then + return + end + + for signal, _ in __SS13_signal_handlers[datum] do + SS13.unregister_signal(datum, signal) + end + + __SS13_signal_handlers[datum] = nil +end + +return SS13 diff --git a/lua/timer.lua b/lua/timer.lua new file mode 100644 index 00000000000..e7829d28514 --- /dev/null +++ b/lua/timer.lua @@ -0,0 +1,106 @@ +local SS13 = require("SS13_base") +local Timer = {} + +__Timer_timers = __Timer_timers or {} +__Timer_callbacks = __Timer_callbacks or {} + +function __add_internal_timer(func, time, loop) + local timer = { + loop = loop, + executeTime = time + dm.world:get_var("time") + } + __Timer_callbacks[tostring(func)] = function() + timer.executing = false + if loop and timer.terminate ~= true then + timer.executeTime = dm.world:get_var("time") + time + else + __stop_internal_timer(tostring(func)) + end + func() + end + __Timer_timers[tostring(func)] = timer + return tostring(func) +end + +function __stop_internal_timer(func) + local timer = __Timer_timers[func] + if timer and not timer.executing then + __Timer_timers[func] = nil + __Timer_callbacks[func] = nil + else + timer.terminate = true + end +end + +__Timer_timer_processing = __Timer_timer_processing or false +SS13.state:set_var("timer_enabled", 1) +__Timer_timer_process = function(seconds_per_tick) + if __Timer_timer_processing then + return + end + __Timer_timer_processing = true + local time = dm.world:get_var("time") + for func, timeData in __Timer_timers do + if timeData.executing == true then + continue + end + if over_exec_usage(0.85) then + sleep() + end + if time >= timeData.executeTime then + SS13.state:get_var("functions_to_execute"):add(func) + timeData.executing = true + end + end + __Timer_timer_processing = false +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) + end, time * 10, false) + coroutine.yield() +end + +function Timer.set_timeout(time, func) + Timer.start_loop(time, 1, func) +end + +function Timer.start_loop(time, amount, func) + if not amount or amount == 0 then + return + end + if amount == -1 then + return __add_internal_timer(func, time * 10, true) + end + if amount == 1 then + return __add_internal_timer(func, time * 10, false) + end + local callback = SS13.new("/datum/callback", SS13.state, "call_function") + local timedevent = dm.global_proc("_addtimer", callback, time * 10, 40, nil, debug.info(1, "sl")) + local doneAmount = 0 + local newFunc = function() + func() + doneAmount += 1 + if doneAmount >= amount then + Timer.end_loop(timedevent) + end + end + __add_internal_timer(newFunc, time * 10, true) + return newFunc +end + +function Timer.end_loop(id) + __stop_internal_timer(id) +end + +function Timer.stop_all_loops() + for id, data in __Timer_timers do + if data.loop then + Timer.end_loop(id) + end + end +end + +return Timer