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_CACHE = -0x00010000,
SFLAG_ROTFIXED = -0x00020000, SFLAG_ROTFIXED = -0x00020000,
SFLAG_HARDCODED_BADGUY = -0x00040000, SFLAG_HARDCODED_BADGUY = -0x00040000,
-- RESERVED for actor.FLAGS.CHAIN_BEG/CHAIN_END/REPLACE:
-- 0x20000000, 0x40000000
} }
STAT = { STAT = {

View file

@ -710,6 +710,11 @@ do
our_SFLAG.enemystayput = con_lang.SFLAG.SFLAG_BADGUY + con_lang.SFLAG.SFLAG_BADGUYSTAYPUT our_SFLAG.enemystayput = con_lang.SFLAG.SFLAG_BADGUY + con_lang.SFLAG.SFLAG_BADGUYSTAYPUT
our_SFLAG.rotfixed = con_lang.SFLAG.SFLAG_ROTFIXED 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 -- XXX: CON doesn't export BADGUYSTAYPUT or ROTFIXED SFLAGs, but they are considered
-- external for Lunatic. -- external for Lunatic.
our_SFLAG.USER_MASK = bit.bor(USER_MASK, our_SFLAG.enemystayput, our_SFLAG.rotfixed) 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" -- needed by "control"
actor = setmtonce({}, defs_c.GenStructMetatable("actor", "MAXSPRITES", actor_static_members)) actor = setmtonce({}, defs_c.GenStructMetatable("actor", "MAXSPRITES", actor_static_members))
local BNOT_SFLAG_USER_MASK = bit.bnot(actor.FLAGS.USER_MASK) 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 projectile = defs_c.creategtab(ffiC.ProjectileData, ffiC.MAXTILES, "projectile[]")
local g_tile = setmtonce({}, defs_c.GenStructMetatable("g_tile", "MAXTILES", tile_static_members)) local g_tile = setmtonce({}, defs_c.GenStructMetatable("g_tile", "MAXTILES", tile_static_members))
@ -1423,6 +1429,22 @@ G_._VERSION = _VERSION
G_._G = G_ 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 local gameactor_internal = gameactor_internal -- included in lunatic.c
-- gameactor(tilenum [, flags [, strength [, act [, mov [, movflags]]]]], actor_func) -- gameactor(tilenum [, flags [, strength [, act [, mov [, movflags]]]]], actor_func)
local function our_gameactor(tilenum, ...) 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) error("invalid 'flags' argument to gameactor: must be a number", 2)
end 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) local flags_rbits = bit.band(flags, BNOT_SFLAG_USER_MASK)
if (flags_rbits ~= 0) then if (flags_rbits ~= 0) then
error("invalid 'flags' argument to gameactor: must not set reserved bits (0x" error("invalid 'flags' argument to gameactor: must not set reserved bits (0x"
..(bit.tohex(flags_rbits))..")", 2) ..(bit.tohex(flags_rbits))..")", 2)
end end
local strength = (numargs > 2) and args[2] or 0 local strength = (numargs > 2) and args[2] or (replacep and 0 or nil)
if (type(strength) ~= "number") then if (replacep or strength~=nil) then
error("invalid 'strength' argument to gameactor: must be a number", 2) if (type(strength) ~= "number") then
error("invalid 'strength' argument to gameactor: must be a number", 2)
end
end end
-- TODO: literal number action other than 0? -- TODO: literal number action other than 0?
local act = (numargs > 3) and args[3] or "NO" local act = (numargs > 3) and args[3] or (replacep and "NO" or nil)
if (type(act)=="string") then if (replacep or act ~= nil) then
act = AC[act] if (type(act)=="string") then
end act = AC[act]
if (not ffi.istype(con_action_ct, act)) then end
error("invalid 'act' argument to gameactor: must be a string or action", 2) if (not ffi.istype(con_action_ct, act)) then
error("invalid 'act' argument to gameactor: must be a string or action", 2)
end
end end
-- TODO: literal number move other than 0? -- TODO: literal number move other than 0?
local mov = (numargs > 4) and args[4] or "NO" local mov = (numargs > 4) and args[4] or (replacep and "NO" or nil)
if (type(mov)=="string") then if (replacep or mov ~= nil) then
mov = MV[mov] if (type(mov)=="string") then
end mov = MV[mov]
if (not ffi.istype(con_move_ct, mov)) then end
error("invalid 'mov' argument to gameactor: must be a string or move", 2) if (not ffi.istype(con_move_ct, mov)) then
error("invalid 'mov' argument to gameactor: must be a string or move", 2)
end
end end
local movflags = (numargs > 5) and args[5] or 0 local movflags = (numargs > 5) and args[5] or (replacep and 0 or nil)
if (type(movflags) ~= "number") then if (replacep or movflags ~= nil) then
error("invalid 'movflags' argument to gameactor: must be a number", 2) if (type(movflags) ~= "number") then
error("invalid 'movflags' argument to gameactor: must be a number", 2)
end
end end
-- All good, set the tile bits and register the actor! -- All good, set the tile bits and register the actor!
local tile = ffiC.g_tile[tilenum] 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 end
-- Event functions, saved for event chaining -- Event functions, saved for event chaining
local event_funcs = {} local event_funcs = {}
local gameevent_internal = gameevent_internal -- included in lunatic.c local gameevent_internal = gameevent_internal -- included in lunatic.c
-- gameevent(<event idx or string>, <event function>) -- gameevent(<event idx or string> [, flags], <event function>)
local function our_gameevent(event, func) local function our_gameevent(event, flags, func)
if (ffiC.g_elCallDepth > 0) then if (ffiC.g_elCallDepth > 0) then
error("Invalid use of gameevent: must be called from top level", 2) error("Invalid use of gameevent: must be called from top level", 2)
end end
@ -1516,26 +1572,39 @@ local function our_gameevent(event, func)
event = eventidx event = eventidx
end end
if (type(event) ~= "number") then 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 end
if (event >= con_lang.MAXEVENTS+0ULL) then if (event >= con_lang.MAXEVENTS+0ULL) then
error("invalid argument #1 to gameevent: must be an event number (0 .. MAXEVENTS-1)", 2) error("invalid argument #1 to gameevent: must be an event number (0 .. MAXEVENTS-1)", 2)
end end
if (type(func) ~= "function") then local AF = actor.FLAGS
error("invalid argument #2 to gameevent: must be a function", 2)
end
local newfunc = func -- Event chaining: in Lunatic, chaining at the *end* is the default.
local oldfunc = event_funcs[event] if (func==nil) then
-- event chaining: events defined later come first func = flags
if (oldfunc ~= nil) then flags = AF.chain_end
newfunc = function(aci, pli, dist) else
func(aci, pli, dist) if (type(flags) ~= "number") then
return oldfunc(aci, pli, dist) 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
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) gameevent_internal(event, newfunc)
event_funcs[event] = newfunc event_funcs[event] = newfunc
end end

View file

@ -102,7 +102,7 @@ local g_warn = { ["not-redefined"]=true, ["bad-identifier"]=false,
-- Code generation and output options. -- Code generation and output options.
local g_cgopt = { ["no"]=false, ["debug-lineinfo"]=false, ["gendir"]=nil, 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 local function csapp() return g_cgopt["cache-sap"] end
-- How many 'if' statements are following immediately each other, -- How many 'if' statements are following immediately each other,
@ -1772,8 +1772,11 @@ local handle =
state = function(statename) state = function(statename)
if (g_funcname[statename]==nil) then if (g_funcname[statename]==nil) then
errprintf("state `%s' not found.", statename) local warn = g_cgopt["no-error-nostate"]
return "_NULLSTATE()" local xprintf = warn and warnprintf or errprintf
xprintf("state `%s' not found.", statename)
return warn and "" or "_NULLSTATE()"
end end
return format("%s(_aci,_pli,_dist)", g_funcname[statename]) return format("%s(_aci,_pli,_dist)", g_funcname[statename])
end, end,
@ -3192,7 +3195,7 @@ local function handle_cmdline_arg(str)
g_warn[warnstr] = val g_warn[warnstr] = val
ok = true ok = true
end end
elseif (str:sub(2,4)=="fno") then elseif (str:sub(2)=="fno") then
-- Disable printing code. -- Disable printing code.
if (#str >= 5 and str:sub(5)=="=onlycheck") then if (#str >= 5 and str:sub(5)=="=onlycheck") then
g_cgopt["no"] = "onlycheck" g_cgopt["no"] = "onlycheck"
@ -3209,6 +3212,9 @@ local function handle_cmdline_arg(str)
elseif (str:sub(2)=="fcache-sap") then elseif (str:sub(2)=="fcache-sap") then
g_cgopt["cache-sap"] = true g_cgopt["cache-sap"] = true
ok = 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 elseif (str:sub(2)=="fdebug-lineinfo") then
g_cgopt["debug-lineinfo"] = true g_cgopt["debug-lineinfo"] = true
ok = 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) // gameactor(actortile, strength, act, mov, movflags, lua_function)
static int32_t SetActor_CF(lua_State *L) static int32_t SetActor_CF(lua_State *L)
{ {
int32_t actortile, strength, movflags; int32_t actortile;
const con_action_t *act;
const con_move_t *mov;
el_actor_t *a; el_actor_t *a;
Bassert(lua_gettop(L) == 6); Bassert(lua_gettop(L) == 6);
@ -425,22 +422,23 @@ static int32_t SetActor_CF(lua_State *L)
actortile = luaL_checkint(L, 1); actortile = luaL_checkint(L, 1);
Bassert((unsigned)actortile < MAXTILES); 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]; a = &g_elActors[actortile];
L_CheckAndRegisterFunction(L, a); 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->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; return 0;
} }

View file

@ -365,7 +365,8 @@ local TROOPSTRENGTH = 30
local AF = actor.FLAGS 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) function(i, playeri, dist)
spriteext[i]:make_animated() spriteext[i]:make_animated()