Files
Bubberstation/code/modules/admin/verbs/lua/lua_editor.dm
LemonInTheDark 85b2d5043d Optimizes qdel related things (slight init time savings) (#70729)
* Moves spawners and decals to a different init/delete scheme

Rather then fully creating and then immediately deleting these things,
we instead do the bare minimum.

This is faster, if in theory more fragile. We should be safe since any
errors should be caught in compile since this is very close to a
"static" action. It does mean these atoms cannot use signals, etc.

* Potentially saves init time, mostly cleans up a silly pattern

We use sleeps and INVOKE_ASYNC to ensure that handing back turfs doesn't
block a space reservation, but this by nature consumes up to the
threshold and a bit more of whatever working block we were in.

This is silly. Should just be a subsystem, so I made it one, with
support for awaiting its finish if you want to

* Optimizes garbage/proc/Queue slightly

Queue takes about 1.6 seconds to process 26k items right now.
The MASSIVE majority of this time is spent on using \ref
This is because \ref returns a string, and that string requires being
inserted into the global cache of strings we store

What I'm doing is caching the result of ANY \ref on the datum it's
applied to. This ensures previous uses will never decay from the string
tree.

This saves about 0.2 seconds of init
2022-10-30 00:09:15 -07:00

235 lines
7.9 KiB
Plaintext

/datum/lua_editor
var/datum/lua_state/current_state
/// Arguments for a function call or coroutine resume
var/list/arguments = list()
/// If not set, the global table will not be shown in the lua editor
var/show_global_table = FALSE
/// The log page we are currently on
var/page = 0
/// If set, we will force the editor's modal to be this
var/force_modal
/// If set, we will force the editor to look at this chunk
var/force_view_chunk
/datum/lua_editor/New(state, _quick_log_index)
. = ..()
if(state)
current_state = state
LAZYADDASSOCLIST(SSlua.editors, text_ref(current_state), src)
/datum/lua_editor/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "LuaEditor", "Lua")
ui.set_autoupdate(FALSE)
ui.open()
/datum/lua_editor/Destroy(force, ...)
. = ..()
if(current_state)
LAZYREMOVEASSOC(SSlua.editors, text_ref(current_state), src)
/datum/lua_editor/ui_state(mob/user)
return GLOB.debug_state
/datum/lua_editor/ui_static_data(mob/user)
var/list/data = list()
data["documentation"] = file2text('code/modules/admin/verbs/lua/README.md')
return data
/datum/lua_editor/ui_data(mob/user)
var/list/data = list()
data["noStateYet"] = !current_state
data["showGlobalTable"] = show_global_table
if(current_state)
if(current_state.log)
data["stateLog"] = kvpify_list(refify_list(current_state.log.Copy((page*50)+1, min((page+1)*50+1, current_state.log.len+1))))
data["page"] = page
data["pageCount"] = CEILING(current_state.log.len/50, 1)
data["tasks"] = current_state.get_tasks()
if(show_global_table)
current_state.get_globals()
data["globals"] = kvpify_list(refify_list(current_state.globals))
data["states"] = SSlua.states
data["callArguments"] = kvpify_list(refify_list(arguments))
if(force_modal)
data["forceModal"] = force_modal
force_modal = null
if(force_view_chunk)
data["forceViewChunk"] = force_view_chunk
force_view_chunk = null
return data
/datum/lua_editor/proc/traverse_list(list/path, list/root, traversal_depth_offset = 0)
var/top_affected_list_depth = LAZYLEN(path)-traversal_depth_offset // The depth of the element to get
if(top_affected_list_depth)
var/list/current_list = root
// We kvpify the list to the depth of the element to get - this allows us to reach list elements contained within a assoc list's key
var/list/path_list = kvpify_list(current_list, top_affected_list_depth-1)
while(LAZYLEN(path) > traversal_depth_offset)
// Navigate to the index of the next path element within the current path element
var/list/path_element = popleft(path)
var/list/list_element = path_list[path_element["index"]]
// Enter the next path element - be it the key or the value
switch(path_element["type"])
if("key")
path_list = list_element["key"]
if("value")
path_list = list_element["value"]
else
to_chat(usr, span_warning("invalid path element type \[[path_element["type"]]] for list traversal (expected \"key\" or \"value\""))
return
// The element we are entering SHOULD be a list, unless we're at the end of the path
if(!islist(path_list) && LAZYLEN(path))
to_chat(usr, span_warning("invalid path element \[[path_list]] for list traversal (expected a list)"))
return
current_list = path_list
return current_list
else
return root
/datum/lua_editor/ui_act(action, list/params)
. = ..()
if(.)
return
if(!check_rights_for(usr.client, R_DEBUG))
return
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, text_ref(current_state), src)
current_state = new_state
LAZYADDASSOCLIST(SSlua.editors, text_ref(current_state), src)
page = 0
return TRUE
if("switchState")
var/state_index = params["index"]
LAZYREMOVEASSOC(SSlua.editors, text_ref(current_state), src)
current_state = SSlua.states[state_index]
LAZYADDASSOCLIST(SSlua.editors, text_ref(current_state), src)
page = 0
return TRUE
if("runCode")
var/code = params["code"]
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)]")
return TRUE
if("moveArgUp")
var/list/path = params["path"]
var/list/target_list = traverse_list(path, arguments, traversal_depth_offset = 1)
var/index = popleft(path)["index"]
target_list.Swap(index-1, index)
return TRUE
if("moveArgDown")
var/list/path = params["path"]
var/list/target_list = traverse_list(path, arguments, traversal_depth_offset = 1)
var/index = popleft(path)["index"]
target_list.Swap(index, index+1)
return TRUE
if("removeArg")
var/list/path = params["path"]
var/list/target_list = traverse_list(path, arguments, traversal_depth_offset = 1)
var/index = popleft(path)["index"]
target_list.Cut(index, index+1)
return TRUE
if("addArg")
var/list/path = params["path"]
var/list/target_list = traverse_list(path, arguments)
if(target_list != arguments)
usr?.client?.mod_list_add(target_list, null, "a lua editor", "arguments")
else
var/list/vv_val = usr?.client?.vv_get_value(restricted_classes = list(VV_RESTORE_DEFAULT))
var/class = vv_val["class"]
if(!class)
return
LAZYADD(arguments, list(vv_val["value"]))
return TRUE
if("callFunction")
var/list/recursive_indices = params["indices"]
var/list/current_list = kvpify_list(current_state.globals)
var/function = list()
while(LAZYLEN(recursive_indices))
var/index = popleft(recursive_indices)
var/list/element = current_list[index]
var/key = element["key"]
var/value = element["value"]
if(!(istext(key) || isnum(key)))
to_chat(usr, span_warning("invalid key \[[key]] for function call (expected text or num)"))
return
function += key
if(islist(value))
current_list = value
else
var/regex/function_regex = regex("^function: 0x\[0-9a-fA-F]+$")
if(function_regex.Find(value))
break
to_chat(usr, span_warning("invalid path element \[[value]] for function call (expected list or text matching [function_regex])"))
return
var/result = current_state.call_function(arglist(list(function) + arguments))
current_state.log_result(result)
arguments.Cut()
return TRUE
if("resumeTask")
var/task_index = params["index"]
SSlua.queue_resume(current_state, task_index, arguments)
arguments.Cut()
return TRUE
if("killTask")
var/task_info = params["info"]
SSlua.kill_task(current_state, task_info)
return TRUE
if("vvReturnValue")
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")
arguments.Cut()
return TRUE
if("toggleShowGlobalTable")
show_global_table = !show_global_table
return TRUE
if("nextPage")
page = min(page+1, CEILING(current_state.log.len/50, 1)-1)
return TRUE
if("previousPage")
page = max(page-1, 0)
return TRUE
/datum/lua_editor/ui_close(mob/user)
. = ..()
qdel(src)
/client/proc/open_lua_editor()
set name = "Open Lua Editor"
set category = "Debug"
if(!check_rights_for(src, R_DEBUG))
return
if(SSlua.initialized != TRUE)
to_chat(usr, span_warning("SSlua is not initialized!"))
return
var/datum/lua_editor/editor = new()
editor.ui_interact(usr)