support more+empty args in hooks

This commit is contained in:
Redo
2025-10-13 12:28:16 -05:00
parent 7232ede09d
commit b71bfdb73e
4 changed files with 77 additions and 36 deletions

Binary file not shown.

View File

@@ -143,7 +143,9 @@ WIP
`string.trim(str, charsToTrim=' \t\r\n')` `string.trim(str, charsToTrim=' \t\r\n')`
`table.empty` `table.empty`
`table.map(func, ...)` `table.map(func, ...)`
`table.mapk(func, ...)`
`table.map_list(func, ...)` `table.map_list(func, ...)`
`table.mapi_list(func, ...)`
`table.swap(tbl)` `table.swap(tbl)`
`table.reverse(list)` `table.reverse(list)`
`table.islist(list)` `table.islist(list)`
@@ -173,22 +175,23 @@ TorqueScript stores no type information; all values in TorqueScript are strings.
### From Lua to TorqueScript ### From Lua to TorqueScript
- `nil` becomes the empty string "" - `nil` becomes the empty string ""
- `true` and `false` become "1" and "0" respectively - `true` and `false` become "1" and "0" respectively
- Torque containers become their object ID - A Torque object container becomes its object ID
- A `vector` becomes a string containing three numbers separated by spaces - A `vector` becomes a string containing three numbers separated by spaces
- A table of two vectors becomes a string containing six numbers separated by spaces - A table of two `vector`s becomes a string containing six numbers separated by spaces
- (WIP) A `matrix` is converted into an axis-angle (a "transform"), a string containing seven numbers separated by spaces
- Any `string` is passed directly as a string - Any `string` is passed directly as a string
- Tables cannot be passed and will throw an error - Tables cannot be passed and will throw an error
### From TorqueScript to Lua ### From TorqueScript to Lua
- Any numeric value becomes a Lua `number`, except as specified with `bl.type`, which may convert a value into a `boolean` or a Torque object container.
- The empty string "" becomes `nil` - The empty string "" becomes `nil`
- Any numeric value becomes a Lua `number`, except as specified with `bl.type`, which may convert a value into a `boolean` or a Torque object container.
- A string containing two or three numbers separated by single spaces becomes a `vector` - A string containing two or three numbers separated by single spaces becomes a `vector`
- A string containing six numbers separated by single spaces becomes a table of two vectors, usually defining the corners a bounding box - A string containing six numbers separated by single spaces becomes a table of two vectors, usually defining the corners a bounding box
- (WIP) A string containing seven numbers separated by single spaces is treated as an axis-angle (a "transform" in TorqueScript parlance), and is converted into a `matrix` representing the translation and rotation. - (WIP) A string containing seven numbers separated by single spaces is treated as an axis-angle (a "transform"), and is converted into a `matrix` representing the translation and rotation
- Any other string is passed directly as a `string` - Any other string is passed directly as a `string`
For scenarios where the automatic TorqueScript->Lua conversion rules are insufficient or incorrect, use `bl.type`. For scenarios where the automatic TorqueScript->Lua conversion rules are insufficient or incorrect, use `bl.type`.
To convert objects by hand, use `bl.object`, `bl.boolean`, or `bl.string`. To convert things by hand, use `bl.object`, `bl.boolean`, or `bl.string`.
## I/O and Safety ## I/O and Safety
All Lua code is sandboxed, and file access is confined to the default directories in the same way TorqueScript is. All Lua code is sandboxed, and file access is confined to the default directories in the same way TorqueScript is.

View File

@@ -6,15 +6,30 @@ local _bllua_ts = ts
bl = bl or {} bl = bl or {}
-- Config
local tsMaxArgs = 16
local tsArgsLocal = '%a,%b,%c,%d,%e,%f,%g,%h,%i,%j,%k,%l,%m,%n,%o,%p'
local tsArgsGlobal =
'$_bllua_hook_arg1,$_bllua_hook_arg2,$_bllua_hook_arg3,$_bllua_hook_arg4,'..
'$_bllua_hook_arg5,$_bllua_hook_arg6,$_bllua_hook_arg7,$_bllua_hook_arg8,'..
'$_bllua_hook_arg9,$_bllua_hook_arg10,$_bllua_hook_arg11,$_bllua_hook_arg12,'..
'$_bllua_hook_arg13,$_bllua_hook_arg14,$_bllua_hook_arg15,$_bllua_hook_arg16'
-- Misc -- Misc
-- Apply a function to each element in a list, building a new list from the returns local function ipairsNilable(t)
local function map(t,f) local maxk = 0
local u = {} for k,_ in pairs(t) do
for i,v in ipairs(t) do if k>maxk then maxk = k end
u[i] = f(v)
end end
return u local i = 0
return function()
i = i+1
if i>maxk then return nil
else return i, t[i] end
end end
end
-- Validation
local function isValidFuncName(name) local function isValidFuncName(name)
return type(name)=='string' and name:find('^[a-zA-Z_][a-zA-Z0-9_]*$') return type(name)=='string' and name:find('^[a-zA-Z_][a-zA-Z0-9_]*$')
end end
@@ -184,13 +199,17 @@ local function valFromTs(val, name, name2)
end end
local function arglistFromTs(name, argsS) local function arglistFromTs(name, argsS)
local args = {} local args = {}
for i,arg in ipairs(argsS) do for i,arg in ipairsNilable(argsS) do
args[i] = valFromTs(arg, name..':'..i) args[i] = valFromTs(arg, name..':'..i)
end end
return args return args
end end
local function arglistToTs(args) local function arglistToTs(args)
return map(args, valToTs) local argsS = {}
for i,v in ipairsNilable(args) do
table.insert(argsS, valToTs(v))
end
return argsS
end end
local function classFromForceTypeStr(name) local function classFromForceTypeStr(name)
local class, rest = name:match('^([a-zA-Z0-9_]+)(::.+)$') local class, rest = name:match('^([a-zA-Z0-9_]+)(::.+)$')
@@ -549,14 +568,13 @@ end
local function safeNamespaceName(name) local function safeNamespaceName(name)
return tostring(name:gsub(':', '_')) return tostring(name:gsub(':', '_'))
end end
local nscallArgStr = '%a,%b,%c,%d,%e,%f,%g,%h'
bl._cachedNamespaceCalls = {} bl._cachedNamespaceCalls = {}
local function tsNamespacedCallTfname(name) local function tsNamespacedCallTfname(name)
local tfname = bl._cachedNamespaceCalls[name] local tfname = bl._cachedNamespaceCalls[name]
if not tfname then if not tfname then
tfname = '_bllua_nscall_'..safeNamespaceName(name) tfname = '_bllua_nscall_'..safeNamespaceName(name)
local tfcode = 'function '..tfname..'('..nscallArgStr..'){'.. local tfcode = 'function '..tfname..'('..tsArgsLocal..'){'..
name..'('..nscallArgStr..');}' name..'('..tsArgsLocal..');}'
_bllua_ts.eval(tfcode) _bllua_ts.eval(tfcode)
bl._cachedNamespaceCalls[name] = tfname bl._cachedNamespaceCalls[name] = tfname
end end
@@ -753,9 +771,8 @@ local function addCmd(cmd, func)
if not isValidFuncName(cmd) then if not isValidFuncName(cmd) then
error('addCmd: invalid function name \''..tostring(cmd)..'\'') end error('addCmd: invalid function name \''..tostring(cmd)..'\'') end
bl._cmds[cmd] = func bl._cmds[cmd] = func
local arglist = '%a,%b,%c,%d,%e,%f,%g,%h,%i,%j,%k,%l,%m,%n,%o,%p' _bllua_ts.eval('function '..cmd..'('..tsArgsLocal..'){'..
_bllua_ts.eval('function '..cmd..'('..arglist..'){'.. '_bllua_luacall(_bllua_process_cmd,"'..cmd..'",'..tsArgsLocal..');}')
'_bllua_luacall(_bllua_process_cmd,"'..cmd..'",'..arglist..');}')
end end
function bl.addServerCmd(name, func) function bl.addServerCmd(name, func)
name = name:lower() name = name:lower()
@@ -803,9 +820,6 @@ local function deactivatePackage(pkg)
_bllua_ts.call('deactivatePackage', pkg) _bllua_ts.call('deactivatePackage', pkg)
end end
end end
local hookNargs = 8
local hookArglistLocal = '%a,%b,%c,%d,%e,%f,%g,%h'
local hookArglistGlobal = '$_bllua_hook_arg1,$_bllua_hook_arg2,$_bllua_hook_arg3,$_bllua_hook_arg4,$_bllua_hook_arg5,$_bllua_hook_arg6,$_bllua_hook_arg7,$_bllua_hook_arg8'
bl._hooks = bl._hooks or {} bl._hooks = bl._hooks or {}
function _bllua_process_hook_before(pkgS, nameS, ...) function _bllua_process_hook_before(pkgS, nameS, ...)
local args = arglistFromTs(nameS, {...}) local args = arglistFromTs(nameS, {...})
@@ -819,7 +833,7 @@ function _bllua_process_hook_before(pkgS, nameS, ...)
_bllua_ts.setvar('_bllua_hook_abort', '1') _bllua_ts.setvar('_bllua_hook_abort', '1')
_bllua_ts.setvar('_bllua_hook_return', valToTs(args._return)) _bllua_ts.setvar('_bllua_hook_return', valToTs(args._return))
end end
for i=1,hookNargs do for i=1,tsMaxArgs do
_bllua_ts.setvar('_bllua_hook_arg'..i, valToTs(args[i])) _bllua_ts.setvar('_bllua_hook_arg'..i, valToTs(args[i]))
end end
end end
@@ -837,8 +851,8 @@ end
local function updateHook(pkg, name, hk) local function updateHook(pkg, name, hk)
local beforeCode = hk.before and local beforeCode = hk.before and
('_bllua_luacall("_bllua_process_hook_before","'..pkg..'","'..name.. ('_bllua_luacall("_bllua_process_hook_before","'..pkg..'","'..name..
'",'..hookArglistLocal..');') or '' '",'..tsArgsLocal..');') or ''
local arglist = (hk.before and hookArglistGlobal or hookArglistLocal) local arglist = (hk.before and tsArgsGlobal or tsArgsLocal)
local parentCode = local parentCode =
tsIsFunctionNsname(name) and -- only call parent if it exists tsIsFunctionNsname(name) and -- only call parent if it exists
(hk.before and (hk.before and
@@ -851,10 +865,11 @@ local function updateHook(pkg, name, hk)
arglist..');') or '' arglist..');') or ''
local code = local code =
'package '..pkg..'{'.. 'package '..pkg..'{'..
'function '..name..'('..hookArglistLocal..'){'.. 'function '..name..'('..tsArgsLocal..'){'..
beforeCode..parentCode..afterCode.. beforeCode..parentCode..afterCode..
'}'.. '}'..
'};' '};'
print('bl.hook eval output: [['..code..']]')
_bllua_ts.eval(code) _bllua_ts.eval(code)
end end
function bl.hook(pkg, name, time, func) function bl.hook(pkg, name, time, func)
@@ -880,9 +895,9 @@ end
function bl.unhook(pkg, name, time) function bl.unhook(pkg, name, time)
if not isValidFuncName(pkg) then if not isValidFuncName(pkg) then
error('bl.unhook: argument #1: invalid package name \''..tostring(pkg)..'\'', 2) end error('bl.unhook: argument #1: invalid package name \''..tostring(pkg)..'\'', 2) end
if not isValidFuncNameNs(name) then if name and not isValidFuncNameNs(name) then
error('bl.unhook: argument #2: invalid function name \''..tostring(name)..'\'', 2) end error('bl.unhook: argument #2: invalid function name \''..tostring(name)..'\'', 2) end
if time~='before' and time~='after' then if time and time~='before' and time~='after' then
error('bl.unhook: argument #3: time must be \'before\' or \'after\'', 2) end error('bl.unhook: argument #3: time must be \'before\' or \'after\'', 2) end
if not name then if not name then
@@ -909,10 +924,13 @@ function bl.unhook(pkg, name, time)
if time~='before' and time~='after' then if time~='before' and time~='after' then
error('bl.unhook: argument #3: time must be nil, \'before\', or \'after\'', 2) end error('bl.unhook: argument #3: time must be nil, \'before\', or \'after\'', 2) end
bl._hooks[pkg][name][time] = nil bl._hooks[pkg][name][time] = nil
if tableEmpty(bl._hooks[pkg][name]) and tableEmpty(bl._hooks[pkg]) then if tableEmpty(bl._hooks[pkg][name]) then
bl._hooks[pkg][name] = nil
end
if tableEmpty(bl._hooks[pkg]) then
bl._hooks[pkg] = nil bl._hooks[pkg] = nil
deactivatePackage(pkg)
updateHook(pkg, name, {}) updateHook(pkg, name, {})
deactivatePackage(pkg)
else else
updateHook(pkg, name, bl._hooks[pkg][name]) updateHook(pkg, name, bl._hooks[pkg][name])
end end
@@ -968,7 +986,7 @@ function bl.raycast(start, stop, mask, ignores)
local stopS = vecToTs(start) local stopS = vecToTs(start)
local maskS = maskToTs(mask) local maskS = maskToTs(mask)
local ignoresS = {} local ignoresS = {}
for _,v in ipairs(ignores) do for _,v in ipairsNilable(ignores) do
table.insert(ignoresS, objToTs(v)) table.insert(ignoresS, objToTs(v))
end end
@@ -1017,7 +1035,7 @@ end
local maxTsArgLen = 8192 local maxTsArgLen = 8192
local function valsToString(vals) local function valsToString(vals)
local strs = {} local strs = {}
for i,v in ipairs(vals) do for i,v in ipairsNilable(vals) do
local tstr = table.tostring(v) local tstr = table.tostring(v)
if #tstr>maxTsArgLen then if #tstr>maxTsArgLen then
tstr = tostring(v) tstr = tostring(v)

View File

@@ -13,8 +13,18 @@ function table.map(f, ...)
local u = {} local u = {}
for k,_ in pairs(ts[1]) do for k,_ in pairs(ts[1]) do
local args = {} local args = {}
for j=1,#ts do args[j] = ts[j][i] end for j=1,#ts do args[j] = ts[j][k] end
u[i] = f(unpack(args)) u[k] = f(unpack(args))
end
return u
end
function table.mapk(f, ...)
local ts = {...}
local u = {}
for k,_ in pairs(ts[1]) do
local args = {}
for j=1,#ts do args[j] = ts[j][k] end
u[k] = f(k, unpack(args))
end end
return u return u
end end
@@ -28,6 +38,16 @@ function table.map_list(f, ...)
end end
return u return u
end end
function table.mapi_list(f, ...)
local ts = {...}
local u = {}
for i=1,#ts[1] do
local args = {}
for j=1,#ts do args[j] = ts[j][i] end
u[i] = f(i, unpack(args))
end
return u
end
-- Swap keys/values -- Swap keys/values
function table.swap(t) function table.swap(t)
local u = {} local u = {}