Skip to content

Commit

Permalink
Bumps auxlua to 1.2.1 (+ several other ui fixes and qol tweaks) (tgst…
Browse files Browse the repository at this point in the history
…ation#69271)

* preparations for self-referential list conversion

* additional changes for the auxlua 1.1.1 update

* fixed a type in `SS13.await`

* bumps auxlua to 1.2.0

* bumps auxlua to 1.2.1
  • Loading branch information
Y0SH1M4S73R authored Aug 24, 2022
1 parent 5eefe15 commit 57b10fc
Show file tree
Hide file tree
Showing 8 changed files with 189 additions and 96 deletions.
Binary file modified auxlua.dll
Binary file not shown.
88 changes: 79 additions & 9 deletions code/__HELPERS/_lists.dm
Original file line number Diff line number Diff line change
Expand Up @@ -813,8 +813,11 @@
return element

/// 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)
/proc/refify_list(list/target_list, list/visited, path_accumulator = "list")
if(!visited)
visited = list()
var/list/ret = list()
visited[target_list] = path_accumulator
for(var/i in 1 to target_list.len)
var/key = target_list[i]
var/new_key = key
Expand All @@ -830,7 +833,10 @@
else if(key == world)
new_key = "world [REF(world)]"
else if(islist(key))
new_key = refify_list(key)
if(visited.Find(key))
new_key = visited[key]
else
new_key = refify_list(key, visited, path_accumulator + "\[[i]\]")
var/value
if(istext(key) || islist(key) || ispath(key) || isdatum(key) || key == world)
value = target_list[key]
Expand All @@ -846,7 +852,10 @@
else if(value == world)
value = "world [REF(world)]"
else if(islist(value))
value = refify_list(value)
if(visited.Find(value))
value = visited[value]
else
value = refify_list(value, visited, path_accumulator + "\[[key]\]")
var/list/to_add = list(new_key)
if(value)
to_add[new_key] = value
Expand All @@ -859,18 +868,27 @@
* 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)
/proc/kvpify_list(list/target_list, depth = INFINITY, list/visited, path_accumulator = "list")
if(!visited)
visited = list()
var/list/ret = list()
visited[target_list] = path_accumulator
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)
if(visited.Find(key))
new_key = visited[key]
else
new_key = kvpify_list(key, depth-1, visited, path_accumulator + "\[[i]\]")
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(visited.Find(value))
value = visited[value]
else
value = kvpify_list(value, depth-1, visited, path_accumulator + "\[[key]\]")
if(value)
ret += list(list("key" = new_key, "value" = value))
else
Expand Down Expand Up @@ -909,22 +927,74 @@
return TRUE

/// Returns a copy of the list where any element that is a datum is converted into a weakref
/proc/weakrefify_list(list/target_list)
/proc/weakrefify_list(list/target_list, list/visited, path_accumulator = "list")
if(!visited)
visited = list()
var/list/ret = list()
visited[target_list] = path_accumulator
for(var/i in 1 to target_list.len)
var/key = target_list[i]
var/new_key = key
if(isdatum(key))
new_key = WEAKREF(key)
else if(islist(key))
new_key = weakrefify_list(key)
if(visited.Find(key))
new_key = visited[key]
else
new_key = weakrefify_list(key, visited, path_accumulator + "\[[i]\]")
var/value
if(istext(key) || islist(key) || ispath(key) || isdatum(key) || key == world)
value = target_list[key]
if(isdatum(value))
value = WEAKREF(value)
else if(islist(value))
value = weakrefify_list(value)
if(visited.Find(value))
value = visited[value]
else
value = weakrefify_list(value, visited, path_accumulator + "\[[key]\]")
var/list/to_add = list(new_key)
if(value)
to_add[new_key] = value
ret += to_add
if(i < target_list.len)
CHECK_TICK
return ret

/// Returns a copy of a list where text values (except assoc-keys and string representations of lua-only values) are
/// wrapped in quotes and existing quote marks are escaped,
/// and nulls are replaced with the string "null"
/proc/encode_text_and_nulls(list/target_list, list/visited)
var/static/regex/lua_reference_regex
if(!lua_reference_regex)
lua_reference_regex = regex(@"^((function)|(table)|(thread)|(userdata)): 0x[0-9a-fA-F]+$")
if(!visited)
visited = list()
var/list/ret = list()
visited[target_list] = TRUE
for(var/i in 1 to target_list.len)
var/key = target_list[i]
var/new_key = key
if(istext(key) && !target_list[key] && !lua_reference_regex.Find(key))
new_key = "\"[replacetext(key, "\"", "\\\"")]\""
else if(islist(key))
var/found_index = visited.Find(key)
if(found_index)
new_key = visited[found_index]
else
new_key = encode_text_and_nulls(key, visited)
else if(isnull(key))
new_key = "null"
var/value
if(istext(key) || islist(key) || ispath(key) || isdatum(key) || key == world)
value = target_list[key]
if(istext(value) && !lua_reference_regex.Find(value))
value = "\"[replacetext(value, "\"", "\\\"")]\""
else if(islist(value))
var/found_index = visited.Find(value)
if(found_index)
value = visited[found_index]
else
value = encode_text_and_nulls(value, visited)
var/list/to_add = list(new_key)
if(value)
to_add[new_key] = value
Expand Down
8 changes: 0 additions & 8 deletions code/modules/admin/verbs/SDQL2/SDQL_2_wrappers.dm
Original file line number Diff line number Diff line change
Expand Up @@ -263,14 +263,6 @@
/proc/_turn(dir, angle)
return turn(dir, angle)

/// For some reason, an atom's contents are a kind of list that auxtools can't work with as a list
/// This proc returns a copy of contents that is an ordinary list
/proc/_contents(atom/thing)
var/list/ret = list()
if(istype(thing))
ret += thing.contents
return ret

/// Auxtools REALLY doesn't know how to handle filters as values;
/// when passed as arguments to auxtools-called procs, they aren't simply treated as nulls -
/// they don't even count towards the length of args.
Expand Down
71 changes: 51 additions & 20 deletions code/modules/admin/verbs/lua/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,57 +3,92 @@
---

## Datums
DM datums are treated as Lua userdata, and can be stored in fields. Regular datums are referenced weakly, so if a datum has been deleted, the corresponding userdata will evaluate to `nil` when used in comparisons or functions.

Keep in mind that BYOND can't see that a datum is referenced in a Lua field, and will garbage collect it if it is not referenced anywhere in DM.
DM datums are treated as lua userdata, and can be stored in fields. Due to fundamental limitations in lua, userdata is inherently truthy. Since datum userdata can correspond to a deleted datum, which would evaluate to `null` in DM, the function [`datum:is_null()`](#datumisnull) is provided to offer a truthiness test consistent with DM.

Keep in mind that BYOND can't see that a datum is referenced in a lua field, and will garbage collect it if it is not referenced anywhere in DM.

### datum:get_var(var)

Equivalent to DM's `datum.var`

### datum:set_var(var, value)

Equivalent to DM's `datum.var = value`

### datum:call_proc(procName, ...)

Equivalent to DM's `datum.procName(...)`

### datum:is_null()

This function is used to evaluate the truthiness of a DM var. The lua statement `if datum:is_null() then` is equivalent to the DM statement `if(datum)`.

### datum.vars

Returns a userdatum that allows you to access and modifiy the vars of a DM datum by index. `datum.vars.foo` is equivalent to `datum:get_var("foo")`, while `datum.vars.foo = bar` is equivalent to `datum:set_var("foo", bar)`

---

## Lists
In order to allow lists to be modified in-place across the DM-to-Lua language barrier, lists are treated as userdata. Whenever running code that expects a DM value, auxlua will attempt to convert tables into lists.

List references are subject to the same limitations as datum userdata, but you are less likely to encounter these limitations.
In order to allow lists to be modified in-place across the DM-to-lua language barrier, lists are treated as userdata. Whenever running code that expects a DM value, auxlua will attempt to convert tables into lists.

List references are subject to the same limitations as datum userdata, but you are less likely to encounter these limitations for regular lists.

Some lists (`vars`, `contents`, `overlays`, `underlays`, `vis_contents`, and `vis_locs`) are inherently attached to datums, and as such, their corresponding userdata contains a weak reference to the containing datum. Use [`list:is_null`](#listisnull) to validate these types of lists.

### list.len

Equivalent to DM's `list.len`

### list:get(index)

Equivalent to DM's `list[index]`

### list:set(index, value)

Equivalent to DM's `list[index] = value`

### list:add(value)

Equivalent to DM's `list.Add(value)`

### list:remove(value)

Equivalent to DM's `list.Remove(value)`

### list:to_table()

Converts a DM list into a lua table.

### list:of_type(type_path)

Will extract only values of type `type_path`.

### list:is_null()

A similar truthiness test to [`datum:is_null()`](#datumisnull). This function only has the possibility of returning `false` for lists that are inherently attached to a datum (`vars`, `contents`, `overlays`, `underlays`, `vis_contents`, and `vis_locs`).

### list.entries

Returns a userdatum that allows you to access and modifiy the entries of the list by index. `list.entries.foo` is equivalent to `list:get("foo")`, while `list.entries.foo = bar` is equivalent to `list:set("foo", bar)`

---

## The dm table

The `dm` table consists of the basic hooks into the DM language.

### dm.state_id
The address of the Lua state in memory. This is a copy of the internal value used by auxlua to locate the Lua state in a global hash map.

The address of the lua state in memory. This is a copy of the internal value used by auxlua to locate the lua state in a global hash map. `state_id` is a registry value that is indirectly obtained using the `dm` table's `__index` metamethod.

### dm.global_proc(proc, ...)
Calls the global proc `/proc/[proc]` with `...` as its arguments.

### dm.world
A reference to DM's `world`, in the form of datum userdata. This reference will never evaluate to `nil`, since `world` always exists.
A reference to DM's `world`, in the form of datum userdata. This reference is always valid, since `world` always exists.

Due to limitations inherent in the wrapper functions used on tgstation, `world:set_var` and `world:call_proc` will raise an error.

Expand All @@ -78,7 +113,7 @@ The Lua Scripting subsystem manages the execution of tasks for each Lua state. A
### sleep()
Yields the current thread, scheduling it to be resumed during the next fire of SSlua. Use this function to prevent your Lua code from exceeding its allowed execution duration. Under the hood, `sleep` performs the following:

- Sets the global flag `__sleep_flag`
- Sets the [`sleep_flag`](#sleep_flag)
- Calls `coroutine.yield()`
- Clears the sleep flag when determining whether the task slept or yielded
- Ignores the return values of `coroutine.yield()` once resumed
Expand Down Expand Up @@ -156,29 +191,25 @@ end)
---

## Internal globals
Auxlua defines several globals for internal use. These are read-only.

### __sleep_flag
This flag is used to designate that a yielding task should be put in the sleep queue instead of the yield table. Once auxlua determines that a task should sleep, `__sleep_flag` is cleared.
Auxlua defines several registry values for each state. Note that there is no way to access registry values from lua code.

### __set_sleep_flag(value)
### sleep_flag

A function that sets `__sleep_flag` to `value`. Calling this directly is not recommended, as doing so muddies the distinction between sleeps and yields.
This flag is used to designate that a yielding task should be put in the sleep queue instead of the yield table. Once auxlua determines that a task should sleep, `sleep_flag` is cleared.

### __sleep_queue
### sleep_queue

A sequence of threads, each corresponding to a task that has slept. When calling `/proc/__lua_awaken`, auxlua will dequeue the first thread from the sequence and resume it. Threads in this queue can be resumed from Lua code, but doing so is heavily advised against.
A sequence of threads, each corresponding to a task that has slept. When calling `/proc/__lua_awaken`, auxlua will dequeue the first thread from the sequence and resume it.

### __yield_table
### yield_table

A table of threads, each corresponding to a coroutine that has yielded. When calling `/proc/__lua_resume`, auxlua will look for a thread at the index specified in the `index` argument, and resume it with the arguments specified in the `arguments` argument. Threads in this table can be resumed from Lua code, but doing so is heavily advised against.
A table of threads, each corresponding to a coroutine that has yielded. When calling `/proc/__lua_resume`, auxlua will look for a thread at the index specified in the `index` argument, and resume it with the arguments specified in the `arguments` argument.

### __task_info
### task_info

A table of key-value-pairs, where the keys are threads, and the values are tables consisting of the following fields:

- name: A string containing the name of the task
- status: A string, either "sleep" or "yield"
- index: The task's index in `__sleep_queue` or `__yield_table`

The threads constituting this table's keys can be resumed from Lua code, but doing so is heavily advised against.
- index: The task's index in `sleep_queue` or `yield_table`
8 changes: 3 additions & 5 deletions code/modules/admin/verbs/lua/lua_editor.dm
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
/datum/lua_editor
var/datum/lua_state/current_state

/// Code imported from the user's system
var/imported_code

/// Arguments for a function call or coroutine resume
var/list/arguments = list()

/// If set, the global table will not be shown in the lua editor
/// 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
Expand Down Expand Up @@ -125,7 +122,8 @@
if("runCode")
var/code = params["code"]
var/result = current_state.load_script(code)
current_state.log_result(result)
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"]
Expand Down
11 changes: 8 additions & 3 deletions code/modules/admin/verbs/lua/lua_state.dm
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ GLOBAL_PROTECT(lua_usr)
&& !(result["name"] == "input" && (result["status"] == "finished" || length(result["param"]))))
return
var/append_to_log = TRUE
var/index_of_log
if(log.len)
for(var/index in log.len to max(log.len - MAX_LOG_REPEAT_LOOKBACK, 1) step -1)
var/list/entry = log[index]
Expand All @@ -53,12 +54,17 @@ GLOBAL_PROTECT(lua_usr)
&& ((entry["param"] == result["param"]) || deep_compare_list(entry["param"], result["param"])))
if(!entry["repeats"])
entry["repeats"] = 0
index_of_log = index
entry["repeats"]++
append_to_log = FALSE
break
if(append_to_log)
log += list(weakrefify_list(result))
if(islist(result["param"]))
result["param"] = weakrefify_list(encode_text_and_nulls(result["param"]))
log += list(result)
index_of_log = log.len
INVOKE_ASYNC(src, /datum/lua_state.proc/update_editors)
return index_of_log

/datum/lua_state/proc/load_script(script)
GLOB.IsLuaCall = TRUE
Expand All @@ -76,7 +82,6 @@ GLOBAL_PROTECT(lua_usr)
result["chunk"] = script
check_if_slept(result)

message_admins("[key_name(usr)] executed [length(script)] bytes of lua code. [ADMIN_LUAVIEW_CHUNK(src, log.len)]")
log_lua("[key_name(usr)] executed the following lua code:\n<code>[script]</code>")

return result
Expand Down Expand Up @@ -137,7 +142,7 @@ GLOBAL_PROTECT(lua_usr)
return result

/datum/lua_state/proc/get_globals()
globals = weakrefify_list(__lua_get_globals(internal_id))
globals = weakrefify_list(encode_text_and_nulls(__lua_get_globals(internal_id)))

/datum/lua_state/proc/get_tasks()
return __lua_get_tasks(internal_id)
Expand Down
2 changes: 1 addition & 1 deletion dependencies.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@ export PYTHON_VERSION=3.7.9
export AUXLUA_REPO=tgstation/auxlua

#auxlua git tag
export AUXLUA_VERSION=1.0.0
export AUXLUA_VERSION=1.1.1
Loading

0 comments on commit 57b10fc

Please sign in to comment.