Lunatic: chaining of actor callback functions.

For events and actors, a flag can be now passed whether to chain the new
function at the beginning or end of an already existing one, or to replace
it entirely.
Also, for the translator, add option -fno-error-nostate, disabled by default.

git-svn-id: https://svn.eduke32.com/eduke32@3629 1a8010ca-5511-0410-912e-c29ae57300e0
This commit is contained in:
helixhorned 2013-03-31 18:58:04 +00:00
parent fe69ee5476
commit e63874d011
5 changed files with 130 additions and 54 deletions

View file

@ -220,6 +220,8 @@ SFLAG = {
SFLAG_CACHE = -0x00010000,
SFLAG_ROTFIXED = -0x00020000,
SFLAG_HARDCODED_BADGUY = -0x00040000,
-- RESERVED for actor.FLAGS.CHAIN_BEG/CHAIN_END/REPLACE:
-- 0x20000000, 0x40000000
}
STAT = {

View file

@ -710,6 +710,11 @@ do
our_SFLAG.enemystayput = con_lang.SFLAG.SFLAG_BADGUY + con_lang.SFLAG.SFLAG_BADGUYSTAYPUT
our_SFLAG.rotfixed = con_lang.SFLAG.SFLAG_ROTFIXED
-- Callback function chaining control flags.
our_SFLAG.chain_beg = bit.tobit(0x20000000)
our_SFLAG.chain_end = bit.tobit(0x40000000)
our_SFLAG.replace = bit.bor(our_SFLAG.chain_beg, our_SFLAG.chain_end)
-- XXX: CON doesn't export BADGUYSTAYPUT or ROTFIXED SFLAGs, but they are considered
-- external for Lunatic.
our_SFLAG.USER_MASK = bit.bor(USER_MASK, our_SFLAG.enemystayput, our_SFLAG.rotfixed)
@ -729,6 +734,7 @@ player = setmtonce({}, defs_c.GenStructMetatable("g_player_ps", "playerswhenstar
-- needed by "control"
actor = setmtonce({}, defs_c.GenStructMetatable("actor", "MAXSPRITES", actor_static_members))
local BNOT_SFLAG_USER_MASK = bit.bnot(actor.FLAGS.USER_MASK)
local BNOT_CHAIN_MASK = bit.bnot(actor.FLAGS.replace)
local projectile = defs_c.creategtab(ffiC.ProjectileData, ffiC.MAXTILES, "projectile[]")
local g_tile = setmtonce({}, defs_c.GenStructMetatable("g_tile", "MAXTILES", tile_static_members))
@ -1423,6 +1429,22 @@ G_._VERSION = _VERSION
G_._G = G_
-- Chain together two functions taking 3 input args.
local function chain_func3(func1, func2)
if (func1==nil or func2==nil) then
return assert(func1 or func2)
end
-- Return a function that runs <func1> first and then tail-calls <func2>.
return function(aci, pli, dist)
func1(aci, pli, dist)
return func2(aci, pli, dist)
end
end
-- Actor functions, saved for actor chaining
local actor_funcs = {}
local gameactor_internal = gameactor_internal -- included in lunatic.c
-- gameactor(tilenum [, flags [, strength [, act [, mov [, movflags]]]]], actor_func)
local function our_gameactor(tilenum, ...)
@ -1454,54 +1476,88 @@ local function our_gameactor(tilenum, ...)
error("invalid 'flags' argument to gameactor: must be a number", 2)
end
local AF = actor.FLAGS
local chainflags = bit.band(flags, AF.replace)
flags = bit.band(flags, BNOT_CHAIN_MASK)
-- Default chaining behavior: don't, replace the old actor instead (like CON).
if (chainflags == 0) then
chainflags = AF.replace
end
local replacep = (chainflags==AF.replace)
if (not replacep and not actor_funcs[tilenum]) then
error("attempt to chain code to nonexistent actor tile "..tilenum, 2)
end
local flags_rbits = bit.band(flags, BNOT_SFLAG_USER_MASK)
if (flags_rbits ~= 0) then
error("invalid 'flags' argument to gameactor: must not set reserved bits (0x"
..(bit.tohex(flags_rbits))..")", 2)
end
local strength = (numargs > 2) and args[2] or 0
if (type(strength) ~= "number") then
error("invalid 'strength' argument to gameactor: must be a number", 2)
local strength = (numargs > 2) and args[2] or (replacep and 0 or nil)
if (replacep or strength~=nil) then
if (type(strength) ~= "number") then
error("invalid 'strength' argument to gameactor: must be a number", 2)
end
end
-- TODO: literal number action other than 0?
local act = (numargs > 3) and args[3] or "NO"
if (type(act)=="string") then
act = AC[act]
end
if (not ffi.istype(con_action_ct, act)) then
error("invalid 'act' argument to gameactor: must be a string or action", 2)
local act = (numargs > 3) and args[3] or (replacep and "NO" or nil)
if (replacep or act ~= nil) then
if (type(act)=="string") then
act = AC[act]
end
if (not ffi.istype(con_action_ct, act)) then
error("invalid 'act' argument to gameactor: must be a string or action", 2)
end
end
-- TODO: literal number move other than 0?
local mov = (numargs > 4) and args[4] or "NO"
if (type(mov)=="string") then
mov = MV[mov]
end
if (not ffi.istype(con_move_ct, mov)) then
error("invalid 'mov' argument to gameactor: must be a string or move", 2)
local mov = (numargs > 4) and args[4] or (replacep and "NO" or nil)
if (replacep or mov ~= nil) then
if (type(mov)=="string") then
mov = MV[mov]
end
if (not ffi.istype(con_move_ct, mov)) then
error("invalid 'mov' argument to gameactor: must be a string or move", 2)
end
end
local movflags = (numargs > 5) and args[5] or 0
if (type(movflags) ~= "number") then
error("invalid 'movflags' argument to gameactor: must be a number", 2)
local movflags = (numargs > 5) and args[5] or (replacep and 0 or nil)
if (replacep or movflags ~= nil) then
if (type(movflags) ~= "number") then
error("invalid 'movflags' argument to gameactor: must be a number", 2)
end
end
-- All good, set the tile bits and register the actor!
local tile = ffiC.g_tile[tilenum]
tile.flags = bit.bor(tile.flags, flags)
local func = args[numargs]
gameactor_internal(tilenum, strength, act, mov, movflags, args[numargs])
-- NOTE: when chaining, this allows passing different flags which are
-- silently ORed. This may or may not be what the user intends, but it
-- allows e.g. adding a stayput bit to an already defined enemy.
-- Modifying existing behavior is the whole point of chaining after all.
tile.flags = replacep and flags or bit.bor(tile.flags, flags)
local newfunc = replacep and func
or (chainflags==AF.chain_beg) and chain_func3(func, actor_funcs[tilenum])
or (chainflags==AF.chain_end) and chain_func3(actor_funcs[tilenum], func)
or assert(false)
gameactor_internal(tilenum, strength, act, mov, movflags, newfunc)
actor_funcs[tilenum] = newfunc
end
-- Event functions, saved for event chaining
local event_funcs = {}
local gameevent_internal = gameevent_internal -- included in lunatic.c
-- gameevent(<event idx or string>, <event function>)
local function our_gameevent(event, func)
-- gameevent(<event idx or string> [, flags], <event function>)
local function our_gameevent(event, flags, func)
if (ffiC.g_elCallDepth > 0) then
error("Invalid use of gameevent: must be called from top level", 2)
end
@ -1516,26 +1572,39 @@ local function our_gameevent(event, func)
event = eventidx
end
if (type(event) ~= "number") then
error("invalid argument #1 to gameevent: must be a number", 2)
error("invalid argument #1 to gameevent: must be a number or event label", 2)
end
if (event >= con_lang.MAXEVENTS+0ULL) then
error("invalid argument #1 to gameevent: must be an event number (0 .. MAXEVENTS-1)", 2)
end
if (type(func) ~= "function") then
error("invalid argument #2 to gameevent: must be a function", 2)
end
local AF = actor.FLAGS
local newfunc = func
local oldfunc = event_funcs[event]
-- event chaining: events defined later come first
if (oldfunc ~= nil) then
newfunc = function(aci, pli, dist)
func(aci, pli, dist)
return oldfunc(aci, pli, dist)
-- Event chaining: in Lunatic, chaining at the *end* is the default.
if (func==nil) then
func = flags
flags = AF.chain_end
else
if (type(flags) ~= "number") then
error("invalid 'flags' argument to gameevent: must be a number", 2)
end
if (bit.band(flags, BNOT_CHAIN_MASK) ~= 0) then
error("invalid 'flags' argument to gameevent: must not set reserved bits", 2)
end
if (flags == 0) then
flags = AF.chain_end
end
end
if (type(func) ~= "function") then
error("invalid last argument to gameevent: must be a function", 2)
end
local newfunc = (flags==AF.replace) and func
or (flags==AF.chain_beg) and chain_func3(func, event_funcs[event])
or (flags==AF.chain_end) and chain_func3(event_funcs[event], func)
or assert(false)
gameevent_internal(event, newfunc)
event_funcs[event] = newfunc
end

View file

@ -102,7 +102,7 @@ local g_warn = { ["not-redefined"]=true, ["bad-identifier"]=false,
-- Code generation and output options.
local g_cgopt = { ["no"]=false, ["debug-lineinfo"]=false, ["gendir"]=nil,
["cache-sap"]=false, }
["cache-sap"]=false, ["no-error-nostate"]=false, }
local function csapp() return g_cgopt["cache-sap"] end
-- How many 'if' statements are following immediately each other,
@ -1772,8 +1772,11 @@ local handle =
state = function(statename)
if (g_funcname[statename]==nil) then
errprintf("state `%s' not found.", statename)
return "_NULLSTATE()"
local warn = g_cgopt["no-error-nostate"]
local xprintf = warn and warnprintf or errprintf
xprintf("state `%s' not found.", statename)
return warn and "" or "_NULLSTATE()"
end
return format("%s(_aci,_pli,_dist)", g_funcname[statename])
end,
@ -3192,7 +3195,7 @@ local function handle_cmdline_arg(str)
g_warn[warnstr] = val
ok = true
end
elseif (str:sub(2,4)=="fno") then
elseif (str:sub(2)=="fno") then
-- Disable printing code.
if (#str >= 5 and str:sub(5)=="=onlycheck") then
g_cgopt["no"] = "onlycheck"
@ -3209,6 +3212,9 @@ local function handle_cmdline_arg(str)
elseif (str:sub(2)=="fcache-sap") then
g_cgopt["cache-sap"] = true
ok = true
elseif (str:sub(2)=="fno-error-nostate") then
g_cgopt["no-error-nostate"] = true
ok = true
elseif (str:sub(2)=="fdebug-lineinfo") then
g_cgopt["debug-lineinfo"] = true
ok = true

View file

@ -414,10 +414,7 @@ static int32_t SetEvent_CF(lua_State *L)
// gameactor(actortile, strength, act, mov, movflags, lua_function)
static int32_t SetActor_CF(lua_State *L)
{
int32_t actortile, strength, movflags;
const con_action_t *act;
const con_move_t *mov;
int32_t actortile;
el_actor_t *a;
Bassert(lua_gettop(L) == 6);
@ -425,22 +422,23 @@ static int32_t SetActor_CF(lua_State *L)
actortile = luaL_checkint(L, 1);
Bassert((unsigned)actortile < MAXTILES);
strength = luaL_checkint(L, 2);
movflags = luaL_checkint(L, 5);
act = lua_topointer(L, 3);
mov = lua_topointer(L, 4);
a = &g_elActors[actortile];
L_CheckAndRegisterFunction(L, a);
// Set actor properties. They can only be nil if we're chaining actor code.
if (!lua_isnil(L, 2))
a->strength = luaL_checkint(L, 2);
if (!lua_isnil(L, 5))
a->movflags = luaL_checkint(L, 5);
if (!lua_isnil(L, 3))
Bmemcpy(&a->act, lua_topointer(L, 3), sizeof(con_action_t));
if (!lua_isnil(L, 4))
Bmemcpy(&a->mov, lua_topointer(L, 4), sizeof(con_move_t));
a->haveit = 1;
a->strength = strength;
a->movflags = movflags;
Bmemcpy(&a->act, act, sizeof(con_action_t));
Bmemcpy(&a->mov, mov, sizeof(con_move_t));
return 0;
}

View file

@ -365,7 +365,8 @@ local TROOPSTRENGTH = 30
local AF = actor.FLAGS
gameactor(1680, AF.enemy, TROOPSTRENGTH, "TROOPSTAND", -- LIZTROOP
-- Also test actor code chaining: strength is doubled.
gameactor(1680, AF.chain_end+AF.enemy, 2*TROOPSTRENGTH, "TROOPSTAND", -- LIZTROOP
function(i, playeri, dist)
spriteext[i]:make_animated()