rework hooks, proper arg conversion for luacall from ts, fix+rename bl.addServerCmd/addClientCmd, remove 'bool' from ts.type (only use 'boolean'), more dedefault typedefs, support typedefs on named objects, typedef inheritance, reorganize libts

This commit is contained in:
Redo
2025-10-04 00:49:19 -05:00
parent 9c349a9352
commit eaafb42317
9 changed files with 1005 additions and 906 deletions

View File

@@ -106,21 +106,29 @@ local function valToTs(val)
error('valToTs: could not convert '..type(val), 3)
end
end
local function convertValFromTs(val, typ)
if typ=='boolean' then
return tsBool(val)
elseif typ=='object' then
return toTsObject(val)
else
error('valFromTs: invalid force type '..typ, 4)
end
end
bl._forceType = bl._forceType or {}
local function valFromTs(val, name)
local function valFromTs(val, name, name2) -- todo: ensure name and name2 are already lowercase
if type(val)~='string' then
error('valFromTs: expected string, got '..type(val), 3) end
if name then
local nameL = name:lower()
if bl._forceType[nameL] then
local typ = bl._forceType[nameL]
if typ=='boolean' then
return tsBool(val)
elseif typ=='object' then
return toTsObject(val)
else
error('valFromTs: invalid force type '..typ, 3)
end
name = name:lower()
if bl._forceType[name] then
return convertValFromTs(val, bl._forceType[name])
end
end
if name2 then
name2 = name2:lower()
if bl._forceType[name2] then
return convertValFromTs(val, bl._forceType[name2])
end
end
-- '' -> nil
@@ -151,22 +159,35 @@ end
local function arglistToTs(args)
return map(args, valToTs)
end
function bl.type(name, typ)
if typ~='bool' and typ~='boolean' and typ~='object' and typ~=nil then
error('bl.type: can only set type to \'bool\' or \'object\' or nil', 2) end
if not isValidFuncNameNsArgn(name) then
error('bl.type: invalid function or variable name \''..name..'\'', 2) end
if typ=='bool' then typ='boolean' end
bl._forceType[name:lower()] = typ
-- apply to children (wip)
--local class, rest = name:match('^([a-zA-Z0-9_]+)(::.+)$')
--if not class then
-- class, rest = name:match('^([a-zA-Z0-9_]+)(%..+)$') end
--if class then
--
--end
local function classFromForceTypeStr(name)
local class, rest = name:match('^([a-zA-Z0-9_]+)(::.+)$')
if not class then
class, rest = name:match('^([a-zA-Z0-9_]+)(%..+)$') end
return class,rest
end
local setForceType
setForceType = function(ftname, typ)
if typ~='boolean' and typ~='object' and typ~=nil then
error('bl.type: can only set type to \'boolean\', \'object\', or nil', 2) end
if not isValidFuncNameNsArgn(ftname) then
error('bl.type: invalid function or variable name \''..ftname..'\'', 2) end
ftname = ftname:lower()
bl._forceType[ftname] = typ
-- apply to child classes if present
local cname, rest = classFromForceTypeStr(ftname)
if cname then
local meta = bl._objectUserMetas[cname]
if meta then
for chcname,_ in pairs(meta._children) do
setForceType(chcname..rest, typ)
end
end
end
end
bl.type = setForceType
-- Value detection
@@ -210,40 +231,47 @@ end
local tsClassMeta = {
__tostring = function(t)
return 'torqueClass:'..t._name..
(t._inherit and (':'..t._inherit) or '')
(t._inherit and (':'..t._inherit._name) or '')
end,
}
bl._objectUserMetas = bl._objectUserMetas or {}
function bl.class(name, inherit)
if not ( type(name)=='string' and isValidFuncName(name) ) then
function bl.class(cname, inhname)
if not ( type(cname)=='string' and isValidFuncName(cname) ) then
error('bl.class: argument #1: invalid class name', 2) end
if not ( inherit==nil or (type(inherit)=='string' and isValidFuncName(inherit)) ) then
if not ( inhname==nil or (type(inhname)=='string' and isValidFuncName(inhname)) ) then
error('bl.class: argument #2: inherit name must be a string or nil', 2) end
name = name:lower()
cname = cname:lower()
local met = bl._objectUserMetas[name] or {
_name = name,
local met = bl._objectUserMetas[cname] or {
_name = cname,
_inherit = nil,
_children = {},
}
bl._objectUserMetas[name] = met
bl._objectUserMetas[cname] = met
setmetatable(met, tsClassMeta)
if inherit then
inherit = inherit:lower()
if inhname then
inhname = inhname:lower()
local inh = bl._objectUserMetas[inherit]
if not inh then error('bl.class: argument #2: \''..inherit..'\' is not the '..
local inh = bl._objectUserMetas[inhname]
if not inh then error('bl.class: argument #2: \''..inhname..'\' is not the '..
'name of an existing class', 2) end
inh._children[name] = true
inh._children[cname] = true
local inhI = met._inherit
if inhI and inhI~=inh then
error('bl.class: argument #2: class already exists and '..
'inherits a different parent.', 2) end
met._inherit = inh
-- apply inherited method and field types
for ftname, typ in pairs(bl._forceType) do
local cname2, rest = classFromForceTypeStr(ftname)
if cname2==inhname then
setForceType(cname..rest, typ)
end
end
end
end
local function objectInheritedMetas(name)
@@ -279,11 +307,15 @@ local tsObjectMeta = {
return function(t, ...)
local args = {...}
local argsS = arglistToTs(args)
return valFromTs(_bllua_ts.callobj(rawget(t,'_tsObjectId'), name, unpack(argsS)),
return valFromTs(
_bllua_ts.callobj(rawget(t,'_tsObjectId'), name, unpack(argsS)),
rawget(t,'_tsName') and rawget(t,'_tsName')..'::'..name,
rawget(t,'_tsNamespace')..'::'..name)
end
else
return valFromTs(_bllua_ts.getfield(rawget(t,'_tsObjectId'), name),
return valFromTs(
_bllua_ts.getfield(rawget(t,'_tsObjectId'), name),
rawget(t,'_tsName') and rawget(t,'_tsName')..'.'..name,
rawget(t,'_tsNamespace')..'.'..name)
end
end
@@ -489,7 +521,7 @@ local tsMeta = {
-- bl.set(name, value)
-- Used to set global variables
function bl.set(name, val)
_bllua_ts.call('_bllua_set_var', name, valToTs(val))
_bllua_ts.setvar(name, valToTs(val))
end
-- Utility functions
@@ -499,7 +531,7 @@ function bl.call(func, ...)
return _bllua_ts.call(func, unpack(argsS))
end
function bl.eval(code)
return valFromTs(_bllua_ts.call('eval', code))
return valFromTs(_bllua_ts.eval(code))
end
function bl.exec(file)
return valFromTs(_bllua_ts.call('exec', file))
@@ -524,8 +556,13 @@ function bl.array(name, ...)
local rest = {...}
return name..table.concat(rest, '_')
end
function _bllua_call(name, ...)
-- todo: call ts->lua using this instead of directly
function _bllua_call(fnameS, ...)
local args = arglistFromTs(fnameS:lower(), {...})
if not _G[fnameS] then
error('luacall: no global lua function named \''..fnameS..'\'') end
-- todo: library fields and object methods
local res = _G[fnameS](args)
return valToTs(res)
end
-- bl.schedule: Use TS's schedule function to schedule lua calls
@@ -544,7 +581,7 @@ function bl.schedule(time, cb, ...)
bl._scheduleNextId = bl._scheduleNextId+1
local args = {...}
local handle = tonumber(_bllua_ts.call('schedule',
time, 0, 'luacall', '_bllua_schedule_callback', id))
time, 0, '_bllua_luacall', '_bllua_schedule_callback', id))
local sch = {
callback = cb,
args = args,
@@ -554,8 +591,9 @@ function bl.schedule(time, cb, ...)
bl._scheduleTable[id] = sch
return sch
end
function _bllua_schedule_callback(id)
id = tonumber(id) or error('_bllua_schedule_callback: invalid id: '..tostring(id))
function _bllua_schedule_callback(idS)
local id = tonumber(idS) or
error('_bllua_schedule_callback: invalid id: '..tostring(idS))
local sch = bl._scheduleTable[id]
if not sch then error('_bllua_schedule_callback: no schedule with id '..id) end
bl._scheduleTable[id] = nil
@@ -563,26 +601,48 @@ function _bllua_schedule_callback(id)
end
-- serverCmd and clientCmd
-- bl.serverCmd('suicide', function(client) client.player:kill() end)
bl._cmds = bl._cmds or {}
function _bllua_process_cmd(cmdS, clientS, ...)
local client = toTsObject(clientS)
local argsS = {...}
local args = arglistFromTs(cmdS, argsS)
local func = bl._cmds[cmdS]
function _bllua_process_cmd(cmdS, ...)
local cmd = cmdS:lower()
local args = arglistFromTs(cmd, {...})
local func = bl._cmds[cmd]
if not func then error('_bllua_process_cmd: no cmd named \''..cmd..'\'') end
pcall(func, client, unpack(args))
func(unpack(args)) --pcall(func, unpack(args))
end
local function addCmd(cmd, func)
if not isValidFuncName(cmd) then
error('addCmd: invalid function name \''..tostring(cmd)..'\'') end
bl._servercmds[cmd] = func
local arglist = '%a,%b,%c,%d,%e,%f,%g,%h'
_bllua_ts.eval('function '..cmd..'(%cl,'..arglist..'){'..
'luacall(_bllua_process_cmd,"'..cmd..'",%cl,'..arglist..');}')
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..'('..arglist..'){'..
'_bllua_luacall(_bllua_process_cmd,"'..cmd..'",'..arglist..');}')
end
function bl.addServerCmd(name, func)
name = name:lower()
addCmd('servercmd'..name, func)
bl._forceType['servercmd'..name..':1'] = 'object'
end
function bl.addClientCmd(name, func)
name = name:lower()
addCmd('clientcmd'..name, func)
end
-- commandToServer and commandToClient
function bl.commandToServer(cmd, ...)
_bllua_ts.call('commandToServer',
_bllua_ts.call('addTaggedString', cmd),
unpack(arglistToTs({...})))
end
function bl.commandToClient(cmd, ...)
_bllua_ts.call('commandToClient',
_bllua_ts.call('addTaggedString', cmd),
unpack(arglistToTs({...})))
end
function bl.commandToAll(cmd, ...)
_bllua_ts.call('commandToAll',
_bllua_ts.call('addTaggedString', cmd),
unpack(arglistToTs({...})))
end
function bl.serverCmd(name, func) addCmd('serverCmd'..name, func) end
function bl.clientCmd(name, func) addCmd('clientCmd'..name, func) end
-- Hooks (using TS packages)
local function isPackageActive(pkg)
@@ -603,42 +663,67 @@ local function deactivatePackage(pkg)
_bllua_ts.call('deactivatePackage', pkg)
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 {}
function _bllua_process_hook(pkgS, nameS, timeS, ...)
local argsS = {...}
local args = arglistFromTs(nameS, argsS)
function _bllua_process_hook_before(pkgS, nameS, ...)
local args = arglistFromTs(nameS, {...})
local func = bl._hooks[pkgS] and bl._hooks[pkgS][nameS] and
bl._hooks[pkgS][nameS][timeS]
bl._hooks[pkgS][nameS].before
if not func then
error('_bllua_process_hook: no hook for '..pkgS..':'..nameS..':'..timeS) end
pcall(func, args)
error('_bllua_process_hook_before: no hook for '..pkgs..':'..nameS) end
_bllua_ts.setvar('_bllua_hook_abort', '0')
func(args) --pcall(func, args)
if args._return then
_bllua_ts.setvar('_bllua_hook_abort', '1')
_bllua_ts.setvar('_bllua_hook_return', valToTs(args._return))
end
for i=1,hookNargs do
_bllua_ts.setvar('_bllua_hook_arg'..i, valToTs(args[i]))
end
end
function _bllua_process_hook_after(pkgS, nameS, resultS, ...)
nameS = nameS:lower()
local args = arglistFromTs(nameS, {...})
args._return = valFromTs(resultS, nameS)
local func = bl._hooks[pkgS] and bl._hooks[pkgS][nameS] and
bl._hooks[pkgS][nameS].after
if not func then
error('_bllua_process_hook_after: no hook for '..pkgs..':'..nameS) end
func(args) --pcall(func, args)
return valToTs(args._return)
end
local function updateHook(pkg, name, hk)
local arglist = '%a,%b,%c,%d,%e,%f,%g,%h'
local beforeCode = hk.before and
('luacall("_bllua_process_hook", "'..pkg..'", "'..name..
'", "before", '..arglist..');') or ''
local parentCode = hk.override and
('luacall("_bllua_process_hook", "'..pkg..'", "'..name..
'", "override", '..arglist..');') or
(tsIsFunctionNsname(name) and
('parent::'..name:match('[^:]+$')..'('..arglist..');') or '')
('_bllua_luacall("_bllua_process_hook_before", "'..pkg..'","'..name..
'",'..hookArglistLocal..');') or ''
local arglist = (hk.before and hookArglistGlobal or hookArglistLocal)
local parentCode =
tsIsFunctionNsname(name) and -- only call parent if it exists
(hk.before and
'if($_bllua_hook_abort)return $_bllua_hook_return; else ' or '')..
((hk.after and '%result=' or 'return ')..
'parent::'..name:match('[^:]+$')..
'('..arglist..');') or ''
local afterCode = hk.after and
('luacall("_bllua_process_hook", "'..pkg..'", "'..name..
'", "after", '..arglist..');') or ''
bl.eval('package '..pkg..'{function '..name..'('..arglist..'){'..
beforeCode..parentCode..afterCode..'}};')
('return _bllua_luacall("_bllua_process_hook_after","'..pkg..'","'..name..'",%result,'..
arglist..');') or ''
local code =
'package '..pkg..'{'..
'function '..name..'('..hookArglistLocal..'){'..
beforeCode..parentCode..afterCode..
'}'..
'};'
_bllua_ts.eval(code)
end
function bl.hook(pkg, name, time, func)
if not isValidFuncName(pkg) then
error('bl.hook: argument #1: invalid package name \''..tostring(pkg)..'\'', 2) end
if not isValidFuncNameNs(name) then
error('bl.hook: argument #2: invalid function name \''..tostring(name)..'\'', 2) end
if time~='before' and time~='after' and time~='override' then
error('bl.hook: argument #3: time must be one of '..
'\'before\' \'after\' \'override\'', 2) end
if time~='before' and time~='after' then
error('bl.hook: argument #3: time must be \'before\' or \'after\'', 2) end
if type(func)~='function' then
error('bl.hook: argument #4: expected a function', 2) end
@@ -654,9 +739,8 @@ function bl.unhook(pkg, name, time)
error('bl.unhook: argument #1: invalid package name \''..tostring(pkg)..'\'', 2) end
if not isValidFuncNameNs(name) then
error('bl.unhook: argument #2: invalid function name \''..tostring(name)..'\'', 2) end
if time~='before' and time~='after' and time~='override' then
error('bl.unhook: argument #3: time must be one of '..
'\'before\' \'after\' \'override\'', 2) end
if time~='before' and time~='after' then
error('bl.unhook: argument #3: time must be \'before\' or \'after\'', 2) end
if not name then
if bl._hooks[pkg] then
@@ -679,15 +763,16 @@ function bl.unhook(pkg, name, time)
end
updateHook(pkg, name, {})
else
if time~='before' and time~='after' and time~='override' then
error('bl.unhook: argument #3: time must be nil or one of '..
'\'before\' \'after\' \'override\'', 2) end
if time~='before' and time~='after' then
error('bl.unhook: argument #3: time must be nil, \'before\', or \'after\'', 2) end
bl._hooks[pkg][name][time] = nil
if table.empty(bl._hooks[pkg][name]) and table.empty(bl._hooks[pkg]) then
bl._hooks[pkg] = nil
deactivatePackage(pkg)
updateHook(pkg, name, {})
else
updateHook(pkg, name, bl._hooks[pkg][name])
end
updateHook(pkg, name, bl._hooks[pkg][name])
end
else
--error('bl.unhook: no hooks registered for function \''..name..