Skip to content

Commit

Permalink
[MIRROR] Admin lua scripting (#65635) (#15118)
Browse files Browse the repository at this point in the history
[Ready for Review] Admin lua scripting (#65635)

Co-authored-by: Mothblocks <[email protected]>

Co-authored-by: Y0SH1M4S73R <[email protected]>
Co-authored-by: Mothblocks <[email protected]>
  • Loading branch information
3 people authored Jul 24, 2022
1 parent cc051b4 commit 15a223f
Show file tree
Hide file tree
Showing 46 changed files with 2,163 additions and 15 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/ci_suite.yml
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ jobs:
sudo apt update || true
sudo apt install -o APT::Immediate-Configure=false libssl1.1:i386
bash tools/ci/install_rust_g.sh
- name: Install auxlua
run: |
bash tools/ci/install_auxlua.sh
- name: Compile Tests
run: |
bash tools/ci/install_byond.sh
Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,11 @@ Temporary Items
# TGUI bundles - SKYRAT EDIT
/tgui/public/tgui-common.bundle.js

# Built auxtools libraries and intermediate files
aux*.dll
libaux*.so
aux*.pdb

# JavaScript tools
**/node_modules

Expand Down
12 changes: 12 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug External Libraries",
"type": "cppvsdbg",
"request": "launch",
"program": "${command:dreammaker.returnDreamDaemonPath}",
"cwd": "${workspaceRoot}",
"args": [
"${command:dreammaker.getFilenameDmb}",
"-trusted"
],
"preLaunchTask": "Build All"
},
{
"type": "byond",
"request": "launch",
Expand Down
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,6 @@
"editor.rulers": [80],
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
}
},
"Lua.diagnostics.enable": false
}
Binary file added auxlua.dll
Binary file not shown.
2 changes: 2 additions & 0 deletions code/__DEFINES/admin.dm
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@
#define ADMIN_VERBOSEJMP(src) "[src ? src.Admin_Coordinates_Readable(TRUE, TRUE) : "nonexistent location"]"
#define ADMIN_INDIVIDUALLOG(user) "(<a href='?_src_=holder;[HrefToken(forceGlobal = TRUE)];individuallog=[REF(user)]'>LOGS</a>)"
#define ADMIN_TAG(datum) "(<A href='?src=[REF(src)];[HrefToken(forceGlobal = TRUE)];tag_datum=[REF(datum)]'>TAG</a>)"
#define ADMIN_LUAVIEW(state) "(<a href='?_src_=holder;[HrefToken(forceGlobal = TRUE)];lua_state=[REF(state)]'>VIEW STATE</a>)"
#define ADMIN_LUAVIEW_CHUNK(state, log_index) "(<a href='?_src_=holder;[HrefToken(forceGlobal = TRUE)];lua_state=[REF(state)];log_index=[log_index]'>VIEW CODE</a>)"

/atom/proc/Admin_Coordinates_Readable(area_name, admin_jump_ref)
var/turf/T = Safe_COORD_Location()
Expand Down
5 changes: 0 additions & 5 deletions code/__DEFINES/spaceman_dmm.dm
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,3 @@
/proc/enable_debugging(mode, port)
CRASH("auxtools not loaded")

/world/Del()
var/debug_server = world.GetConfig("env", "AUXTOOLS_DEBUG_DLL")
if (debug_server)
call(debug_server, "auxtools_shutdown")()
. = ..()
30 changes: 30 additions & 0 deletions code/__HELPERS/_auxtools_api.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#define AUXTOOLS_FULL_INIT 2
#define AUXTOOLS_PARTIAL_INIT 1

GLOBAL_LIST_EMPTY(auxtools_initialized)

#define AUXTOOLS_CHECK(LIB)\
if (GLOB.auxtools_initialized[LIB] != AUXTOOLS_FULL_INIT) {\
if (fexists(LIB)) {\
var/string = call(LIB,"auxtools_init")();\
if(findtext(string, "SUCCESS")) {\
GLOB.auxtools_initialized[LIB] = AUXTOOLS_FULL_INIT;\
} else {\
CRASH(string);\
}\
} else {\
CRASH("No file named [LIB] found!")\
}\
}\

#define AUXTOOLS_SHUTDOWN(LIB)\
if (GLOB.auxtools_initialized[LIB] == AUXTOOLS_FULL_INIT && fexists(LIB)){\
call(LIB,"auxtools_shutdown")();\
GLOB.auxtools_initialized[LIB] = AUXTOOLS_PARTIAL_INIT;\
}\

#define AUXTOOLS_FULL_SHUTDOWN(LIB)\
if (GLOB.auxtools_initialized[LIB] && fexists(LIB)){\
call(LIB,"auxtools_full_shutdown")();\
GLOB.auxtools_initialized[LIB] = FALSE;\
}\
87 changes: 87 additions & 0 deletions code/__HELPERS/_lists.dm
Original file line number Diff line number Diff line change
Expand Up @@ -811,3 +811,90 @@
return ref.resolve()
else
return element

#define REFIFY_KVPIFY_MAX_LENGTH 1000

/// Returns a copy of the list where any element that is a datum or the world is converted into a ref
/proc/refify_list(list/target_list)
if(length(target_list) > REFIFY_KVPIFY_MAX_LENGTH)
return "list\[[length(target_list)]\]"
var/list/ret = list()
for(var/i in 1 to target_list.len)
var/key = target_list[i]
var/new_key = key
if(isdatum(key))
new_key = "[key] [REF(key)]"
else if(key == world)
new_key = "world [REF(world)]"
else if(islist(key))
new_key = refify_list(key)
var/value
if(istext(key) || islist(key) || ispath(key) || isdatum(key) || key == world)
value = target_list[key]
if(isdatum(value))
value = "[value] [REF(value)]"
else if(value == world)
value = "world [REF(world)]"
else if(islist(value))
value = refify_list(value)
var/list/to_add = list(new_key)
if(value)
to_add[new_key] = value
ret += to_add
return ret

/**
* Converts a list into a list of assoc lists of the form ("key" = key, "value" = value)
* so that list keys that are themselves lists can be fully json-encoded
*/
/proc/kvpify_list(list/target_list, depth = INFINITY)
if(length(target_list) > REFIFY_KVPIFY_MAX_LENGTH)
return "list\[[length(target_list)]\]"
var/list/ret = list()
for(var/i in 1 to target_list.len)
var/key = target_list[i]
var/new_key = key
if(islist(key) && depth)
new_key = kvpify_list(key, depth-1)
var/value
if(istext(key) || islist(key) || ispath(key) || isdatum(key) || key == world)
value = target_list[key]
if(islist(value) && depth)
value = kvpify_list(value, depth-1)
if(value)
ret += list(list("key" = new_key, "value" = value))
else
ret += list(list("key" = i, "value" = new_key))
return ret

#undef REFIFY_KVPIFY_MAX_LENGTH

/// Compares 2 lists, returns TRUE if they are the same
/proc/deep_compare_list(list/list_1, list/list_2)
if(!islist(list_1) || !islist(list_2))
return FALSE

if(list_1 == list_2)
return TRUE

if(list_1.len != list_2.len)
return FALSE

for(var/i in 1 to list_1.len)
var/key_1 = list_1[i]
var/key_2 = list_2[i]
if (islist(key_1) && islist(key_2))
if(!deep_compare_list(key_1, key_2))
return FALSE
else if(key_1 != key_2)
return FALSE
if(istext(key_1) || islist(key_1) || ispath(key_1) || isdatum(key_1) || key_1 == world)
var/value_1 = list_1[key_1]
var/value_2 = list_2[key_1]
if (islist(value_1) && islist(value_2))
if(!deep_compare_list(value_1, value_2))
return FALSE
else if(value_1 != value_2)
return FALSE

return TRUE
12 changes: 12 additions & 0 deletions code/__HELPERS/auxtools.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/// Macro for getting the auxtools library file
#define AUXLUA (world.system_type == MS_WINDOWS ? "auxlua.dll" : __detect_auxtools("auxlua"))

/proc/__detect_auxtools(library)
if(IsAdminAdvancedProcCall())
return
if (fexists("./lib[library].so"))
return "./lib[library].so"
else if (fexists("[world.GetConfig("env", "HOME")]/.byond/bin/lib[library].so"))
return "[world.GetConfig("env", "HOME")]/.byond/bin/lib[library].so"
else
CRASH("Could not find lib[library].so")
4 changes: 4 additions & 0 deletions code/__HELPERS/logging/debug.dm
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,7 @@
WRITE_LOG(GLOB.world_runtime_log, text)
#endif
SEND_TEXT(world.log, text)

/// Logging for lua scripting
/proc/log_lua(text)
WRITE_LOG(GLOB.lua_log, text)
3 changes: 3 additions & 0 deletions code/_globalvars/logging.dm
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ GLOBAL_PROTECT(perf_log)
GLOBAL_VAR(demo_log)
GLOBAL_PROTECT(demo_log)

GLOBAL_VAR(lua_log)
GLOBAL_PROTECT(lua_log)

GLOBAL_LIST_EMPTY(bombers)
GLOBAL_PROTECT(bombers)
GLOBAL_LIST_EMPTY(admin_log)
Expand Down
1 change: 1 addition & 0 deletions code/controllers/configuration/entries/game_options.dm
Original file line number Diff line number Diff line change
Expand Up @@ -399,3 +399,4 @@
integer = FALSE

/datum/config_entry/flag/disallow_circuit_sounds

2 changes: 2 additions & 0 deletions code/controllers/configuration/entries/lua.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/datum/config_entry/str_list/lua_path
protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN
166 changes: 166 additions & 0 deletions code/controllers/subsystem/lua.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
//world/proc/shelleo
#define SHELLEO_ERRORLEVEL 1
#define SHELLEO_STDOUT 2
#define SHELLEO_STDERR 3

#define SSLUA_INIT_FAILED 2

SUBSYSTEM_DEF(lua)
name = "Lua Scripting"
runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT
wait = 0.1 SECONDS

/// A list of all lua states
var/list/datum/lua_state/states = list()

/// A list of open editors, with each key in the list associated with a list of editors.
/// Tracks which UIs are open for each state so that they can be updated whenever
/// code is run in the state.
var/list/editors

var/list/sleeps = list()
var/list/resumes = list()

var/list/current_run = list()

/// Protects return values from getting GCed before getting converted to lua values
var/gc_guard

/datum/controller/subsystem/lua/Initialize(start_timeofday)
try

// Initialize the auxtools library
AUXTOOLS_CHECK(AUXLUA)

// Set the wrappers for setting vars and calling procs
__lua_set_set_var_wrapper("/proc/wrap_lua_set_var")
__lua_set_datum_proc_call_wrapper("/proc/wrap_lua_datum_proc_call")
__lua_set_global_proc_call_wrapper("/proc/wrap_lua_global_proc_call")
__lua_set_print_wrapper("/proc/wrap_lua_print")
return ..()
catch(var/exception/e)
// Something went wrong, best not allow the subsystem to run
initialized = SSLUA_INIT_FAILED
can_fire = FALSE
var/time = (REALTIMEOFDAY - start_timeofday) / 10
var/msg = "Failed to initialize [name] subsystem after [time] seconds!"
to_chat(world, span_boldwarning("[msg]"))
warning(e.name)
return time

/datum/controller/subsystem/lua/OnConfigLoad()
// Get the current working directory - we need it to set the LUAU_PATH environment variable
var/here = world.shelleo(world.system_type == MS_WINDOWS ? "cd" : "pwd")[SHELLEO_STDOUT]
here = replacetext(here, "\n", "")
var/last_char = copytext_char(here, -1)
if(last_char != "/" && last_char != "\\")
here += "/"

// Read the paths from the config file
var/list/lua_path = list()
var/list/config_paths = CONFIG_GET(str_list/lua_path)
for(var/path in config_paths)
if(path[1] != "/")
path = here + path
lua_path += path
world.SetConfig("env", "LUAU_PATH", jointext(lua_path, ";"))

/datum/controller/subsystem/lua/Shutdown()
AUXTOOLS_SHUTDOWN(AUXLUA)

/datum/controller/subsystem/lua/proc/queue_resume(datum/lua_state/state, index, arguments)
if(initialized != TRUE)
return
if(!istype(state))
return
if(!arguments)
arguments = list()
else if(!islist(arguments))
arguments = list(arguments)
resumes += list(list("state" = state, "index" = index, "arguments" = arguments))

/datum/controller/subsystem/lua/proc/kill_task(datum/lua_state/state, list/task_info)
if(!istype(state))
return
if(!islist(task_info))
return
if(!(istext(task_info["name"]) && istext(task_info["status"]) && isnum(task_info["index"])))
return
switch(task_info["status"])
if("sleep")
var/task_index = task_info["index"]
var/state_index = 1

// Get the nth sleep in the sleep list corresponding to the target state
for(var/i in 1 to length(sleeps))
var/datum/lua_state/sleeping_state = sleeps[i]
if(sleeping_state == state)
if(state_index == task_index)
sleeps.Cut(i, i+1)
break
state_index++
if("yield")
// Remove the resumt from the resumt list
for(var/i in 1 to length(resumes))
var/resume = resumes[i]
if(resume["state"] == state && resume["index"] == task_info["index"])
resumes.Cut(i, i+1)
break
state.kill_task(task_info)

/datum/controller/subsystem/lua/fire(resumed)
// Each fire of SSlua awakens every sleeping task in the order they slept,
// then resumes every yielded task in the order their resumes were queued
if(!resumed)
current_run = list("sleeps" = sleeps.Copy(), "resumes" = resumes.Copy())
sleeps.Cut()
resumes.Cut()

var/list/current_sleeps = current_run["sleeps"]
var/list/affected_states = list()
while(length(current_sleeps))
var/datum/lua_state/state = current_sleeps[1]
current_sleeps.Cut(1,2)
if(!istype(state))
continue
affected_states |= state
var/result = state.awaken()
state.log_result(result, verbose = FALSE)

if(MC_TICK_CHECK)
break

if(!length(current_sleeps))
var/list/current_resumes = current_run["resumes"]
while(length(current_resumes))
var/list/resume_params = current_resumes[1]
current_resumes.Cut(1,2)
var/datum/lua_state/state = resume_params["state"]
if(!istype(state))
continue
var/index = resume_params["index"]
if(isnull(index) || !isnum(index))
continue
var/arguments = resume_params["arguments"]
if(!islist(arguments))
continue
affected_states |= state
var/result = state.resume(arglist(list(index) + arguments))
state.log_result(result, verbose = FALSE)

if(MC_TICK_CHECK)
break

// Update every lua editor TGUI open for each state that had a task awakened or resumed
for(var/state in affected_states)
var/list/editor_list = LAZYACCESS(editors, "\ref[state]")
if(editor_list)
for(var/datum/lua_editor/editor in editor_list)
SStgui.update_uis(editor)

//world/proc/shelleo
#undef SHELLEO_ERRORLEVEL
#undef SHELLEO_STDOUT
#undef SHELLEO_STDERR

#undef SSLUA_INIT_FAILED
Loading

0 comments on commit 15a223f

Please sign in to comment.