Files
Bubberstation/code/controllers/subsystem/lua.dm
Lucy 348413a8a6 Add a DISABLE_DREAMLUAU define, for disabling dreamluau at compile-time (#89359)
## About The Pull Request

this adds a new define, `DISABLE_DREAMLUAU` (commented out by default),
which does... exactly what it says on the tin. it fully disables any
dreamluau-related code (the "Open Lua Editor" admin verb is left in,
albeit just giving the user a warning saying Lua is disabled, just so
there's no confusion about the verb itself being missing)

when compiling with OpenDream outside of CI (so dreamluau code will
still be linted), `DISABLE_DREAMLUAU` will be defined by default, tho.

## Why It's Good For The Game

makes OpenDream testing easier

## Changelog

no user-facing changes. or even code changes for most cases.
2025-02-06 02:27:55 +01:00

161 lines
5.2 KiB
Plaintext

#ifndef DISABLE_DREAMLUAU
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()
var/list/current_states_run = list()
var/list/needs_gc_cycle = list()
/datum/controller/subsystem/lua/Initialize()
DREAMLUAU_SET_EXECUTION_LIMIT_SECS(5)
// Set wrappers to ensure that lua scripts are subject to the same safety restrictions as other admin tooling
DREAMLUAU_SET_NEW_WRAPPER("/proc/_new")
DREAMLUAU_SET_VAR_GET_WRAPPER("/proc/wrap_lua_get_var")
DREAMLUAU_SET_VAR_SET_WRAPPER("/proc/wrap_lua_set_var")
DREAMLUAU_SET_OBJECT_CALL_WRAPPER("/proc/wrap_lua_datum_proc_call")
DREAMLUAU_SET_GLOBAL_CALL_WRAPPER("/proc/wrap_lua_global_proc_call")
// Set the print wrapper, as otherwise, the print function is meaningless
DREAMLUAU_SET_PRINT_WRAPPER("/proc/wrap_lua_print")
return SS_INIT_SUCCESS
/datum/controller/subsystem/lua/OnConfigLoad()
// 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)
lua_path += path
world.SetConfig("env", "LUAU_PATH", jointext(lua_path, ";"))
/datum/controller/subsystem/lua/proc/queue_resume(datum/lua_state/state, index, arguments)
if(!initialized)
return
if(!istype(state))
return
if(!arguments)
arguments = list()
else if(!islist(arguments))
arguments = list(arguments)
else
var/list/args_list = arguments
arguments = args_list.Copy()
resumes += list(list("state" = state, "index" = index, "arguments" = arguments))
/datum/controller/subsystem/lua/proc/kill_task(datum/lua_state/state, is_sleep, index)
if(!istype(state))
return
if(is_sleep)
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 == index)
sleeps.Cut(i, i+1)
break
state_index++
else
// 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"] == index)
resumes.Cut(i, i+1)
break
state.kill_task(is_sleep, index)
/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())
current_states_run = states.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
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
while(length(needs_gc_cycle))
var/datum/lua_state/state = needs_gc_cycle[needs_gc_cycle.len]
needs_gc_cycle.len--
state.collect_garbage()
// 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))
/datum/controller/subsystem/lua/proc/log_involved_runtime(exception/runtime, list/desclines, list/lua_stacks)
var/list/json_data = list("status" = "runtime", "file" = runtime.file, "line" = runtime.line, "message" = runtime.name, "stack" = list())
var/level = 1
for(var/line in desclines)
line = copytext(line, 3)
if(starts_with_any(line, list(
"/datum/lua_state (/datum/lua_state): load script",
"/datum/lua_state (/datum/lua_state): call function",
"/datum/lua_state (/datum/lua_state): awaken",
"/datum/lua_state (/datum/lua_state): resume"
)))
json_data["stack"] += lua_stacks[level]
level++
json_data["stack"] += line
for(var/datum/weakref/state_ref as anything in GLOB.lua_state_stack)
var/datum/lua_state/state = state_ref.resolve()
if(!state)
continue
state.log_result(json_data)
return
#endif