raze/polymer/eduke32/source/lunatic/lunatic_game.c

631 lines
18 KiB
C
Raw Normal View History

/* The Lunatic Interpreter, part of EDuke32. Game-side stuff. */
#include <stdint.h>
#include <stdlib.h> // getenv
#include <errno.h>
#include <string.h> // strerror
#ifdef USE_LUAJIT_2_1
# include <luajit-2.1/lualib.h>
# include <luajit-2.1/lauxlib.h>
#else
# include <luajit-2.0/lualib.h>
# include <luajit-2.0/lauxlib.h>
#endif
#include "build.h" // printext256
#include "lunatic_game.h"
#include "osd.h"
#include "gamedef.h" // EventNames[]
L_State g_ElState;
// this serves two purposes:
// the values as booleans and the addresses as keys to the Lua registry
uint8_t g_elEvents[MAXEVENTS];
// same thing for actors:
el_actor_t g_elActors[MAXTILES];
// Session variable. Never restored except by 'readgamevar'.
int32_t g_elSessionVar[8]; // MAXSESSIONVARS, KEEPINSYNC con_lang.lua
// Set to 1 on error in event.
int32_t g_elEventError;
int32_t g_elCallDepth = 0;
int32_t g_RETURN;
// for timing events and actors
static int32_t g_timingInited = 0;
uint32_t g_eventCalls[MAXEVENTS], g_actorCalls[MAXTILES];
double g_eventTotalMs[MAXEVENTS], g_actorTotalMs[MAXTILES], g_actorMinMs[MAXTILES], g_actorMaxMs[MAXTILES];
// Used as Lua registry key to the tweak_traceback_msg() function, set to 1 if
// such a function has been registered.
static uint8_t g_tweakTracebackMsg = 0;
// forward-decls...
static int32_t SetEvent_CF(lua_State *L);
static int32_t SetActor_CF(lua_State *L);
// in lpeg.o
extern int luaopen_lpeg(lua_State *L);
typedef struct {
uint32_t x, y, z, c;
} rng_jkiss_t;
// See: Good Practice in (Pseudo) Random Number Generation for
// Bioinformatics Applications, by David Jones
ATTRIBUTE_OPTIMIZE("O2")
LUNATIC_EXTERN uint32_t rand_jkiss_u32(rng_jkiss_t *s)
{
uint64_t t;
s->x = 314527869 * s->x + 1234567;
s->y ^= s->y << 5; s->y ^= s->y >> 7; s->y ^= s->y << 22;
t = 4294584393ULL * s->z + s->c; s->c = t >> 32; s->z = t;
return s->x + s->y + s->z;
}
ATTRIBUTE_OPTIMIZE("O2")
LUNATIC_EXTERN double rand_jkiss_dbl(rng_jkiss_t *s)
{
double x;
unsigned int a, b;
a = rand_jkiss_u32(s) >> 6; /* Upper 26 bits */
b = rand_jkiss_u32(s) >> 5; /* Upper 27 bits */
x = (a * 134217728.0 + b) / 9007199254740992.0;
return x;
}
void El_PrintTimes(void)
{
int32_t i;
const char nn = Bstrlen("EVENT_");
// Try environment variable specifying the base name (sans ".actors.csv" or
// ".events.csv") for a CSV file to output, for further processing in e.g.
// GSL shell: http://www.nongnu.org/gsl-shell/
const char *basefn = getenv("LUNATIC_TIMING_BASEFN");
if (basefn != NULL)
{
const int32_t baselen = Bstrlen(basefn);
const int32_t addnlen = Bstrlen(".actors.csv"); // MUST equal that of ".events.csv"
char *fullfn = Bmalloc(baselen + addnlen + 1);
BFILE *outf;
if (fullfn == NULL)
return;
Bmemcpy(fullfn, basefn, baselen);
// EVENTS
Bmemcpy(fullfn+baselen, ".events.csv", addnlen+1);
outf = Bfopen(fullfn, "w");
if (outf == NULL)
{
OSD_Printf("Couldn't open \"%s\" for writing timing data: %s", fullfn, strerror(errno));
goto finish;
}
Bfprintf(outf, "evtname,numcalls,total_ms,mean_us\n"); // times in usecs are per-call
for (i=0; i<MAXEVENTS; i++)
if (g_eventCalls[i])
Bfprintf(outf, "%s,%d,%f,%f\n", EventNames[i]+nn, g_eventCalls[i], g_eventTotalMs[i],
1000*g_eventTotalMs[i]/g_eventCalls[i]);
Bfclose(outf);
// ACTORS
Bmemcpy(fullfn+baselen, ".actors.csv", addnlen+1);
outf = Bfopen(fullfn, "w");
if (outf == NULL)
{
OSD_Printf("Couldn't open \"%s\" for writing timing data: %s", fullfn, strerror(errno));
goto finish;
}
Bfprintf(outf, "tilenum,numcalls,total_ms,min_us,mean_us,max_us\n");
for (i=0; i<MAXTILES; i++)
if (g_actorCalls[i])
Bfprintf(outf, "%d,%d,%f,%f,%f,%f\n", i, g_actorCalls[i], g_actorTotalMs[i],
1000*g_actorMinMs[i],
1000*g_actorTotalMs[i]/g_actorCalls[i],
1000*g_actorMaxMs[i]);
Bfclose(outf);
OSD_Printf("Wrote timing data to \"%s.*.csv\"\n", basefn);
finish:
Bfree(fullfn);
return;
}
else
{
// If not writing out CSV files, print timing data to log instead.
char buf[32];
int32_t maxlen = 0;
for (i=0; i<MAXEVENTS; i++)
{
int32_t len = Bstrlen(EventNames[i]+nn);
Bassert(len < (int32_t)sizeof(buf));
maxlen = max(len, maxlen);
}
OSD_Printf("{\n {\n");
OSD_Printf(" -- event times, [event]={ total calls, total time [ms], mean time/call [us] }\n");
for (i=0; i<MAXEVENTS; i++)
if (g_eventCalls[i])
{
int32_t n=Bsprintf(buf, "%s", EventNames[i]+nn);
for (; n<maxlen; n++)
buf[n] = ' ';
buf[maxlen] = 0;
OSD_Printf(" [%s]={ %8d, %10.3f, %10.3f },\n",
buf, g_eventCalls[i], g_eventTotalMs[i],
1000*g_eventTotalMs[i]/g_eventCalls[i]);
}
OSD_Printf(" },\n\n {\n");
OSD_Printf(" -- actor times, [tile]={ total calls, total time [ms], {min,mean,max} time/call [us] }\n");
for (i=0; i<MAXTILES; i++)
if (g_actorCalls[i])
OSD_Printf(" [%5d]={ %8d, %9.3f, %9.3f, %9.3f, %9.3f },\n",
i, g_actorCalls[i], g_actorTotalMs[i],
1000*g_actorMinMs[i],
1000*g_actorTotalMs[i]/g_actorCalls[i],
1000*g_actorMaxMs[i]);
OSD_Printf(" },\n}\n");
}
}
////////// ERROR REPORTING //////////
#define EL_MAXERRORS 20
static int32_t el_numErrors=0, el_tooMuchErrors;
static char *el_errorMsgs[EL_MAXERRORS];
// Compare against all other error messages.
// Strictly seen, this is quadratic-time, but EL_MAXERRORS is small and
// errors should be fixed anyway.
static int32_t cmp_against_others(const char *str, int32_t slen)
{
int32_t i;
for (i=0; i<el_numErrors; i++)
if (!Bstrncmp(str, el_errorMsgs[i], slen))
return 1;
return 0;
}
static void El_OnError(const char *str)
{
if (!el_tooMuchErrors)
{
char *errstr = NULL;
const char *nl = Bstrchr(str, '\n');
// First, check whether the error message matches an already saved one
if (nl)
{
// cut off string after the newline
if (cmp_against_others(str, nl-str))
return;
}
else
{
// save string fully
if (cmp_against_others(str, Bstrlen(str)))
return;
}
// If the (EL_MAXERRORS+1)'th distinct error appeared, we have too many.
if (el_numErrors==EL_MAXERRORS)
{
el_tooMuchErrors = 1;
return;
}
// Otherwise, allocate storage for the potentially clipped error string...
if (nl)
{
errstr = Bmalloc(nl-str+1);
if (errstr)
{
Bmemcpy(errstr, str, nl-str);
errstr[nl-str] = 0;
}
}
else
{
errstr = Bstrdup(str);
}
// ...and save it:
if (errstr)
el_errorMsgs[el_numErrors++] = errstr;
}
}
static void El_OnOutOfMem(void)
{
extern void G_GameExit(const char *msg);
G_GameExit("Out of memory in Lunatic.");
}
void El_ClearErrors(void)
{
int32_t i;
for (i=0; i<EL_MAXERRORS; i++)
{
Bfree(el_errorMsgs[i]);
el_errorMsgs[i] = NULL;
}
el_numErrors = el_tooMuchErrors = 0;
}
void El_DisplayErrors(void)
{
int32_t i;
for (i=0; i<el_numErrors; i++)
printext256(8, 8+8*i, 242, 0, el_errorMsgs[i], 0);
if (el_tooMuchErrors)
printext256(8, 8+8*EL_MAXERRORS, 242, 0, "(more distinct errors ...)", 0);
}
////////// STATE CREATION/DESTRUCTIION //////////
static int our_traceback_CF(lua_State *L)
{
Bassert(lua_gettop(L)==1);
if (lua_type(L, 1)==LUA_TBOOLEAN)
{
lua_pushvalue(L, 1); // duplicate it
return 1; // and tell Lua to return it
}
Bassert(lua_type(L, 1)==LUA_TSTRING);
// call debug.traceback with the string
L_PushDebugTraceback(L);
lua_pushvalue(L, 1);
lua_call(L, 1, 1);
Bassert(lua_gettop(L)==2); // Lua will pop off args
if (g_tweakTracebackMsg)
{
// Get tweak_traceback_msg() onto the stack.
lua_pushlightuserdata(L, &g_tweakTracebackMsg);
lua_gettable(L, LUA_REGISTRYINDEX);
lua_pushvalue(L, -2); // push copy of error message string
Bassert(lua_type(L, -1)==LUA_TSTRING);
// Call tweak_traceback_msg(). CAREFUL, it's unprotected!
lua_call(L, 1, 1);
}
return 1;
}
// Registers a function: str = tweak_traceback_msg(str)
static int32_t SetTweakTracebackMsg_CF(lua_State *L)
{
Bassert(lua_gettop(L)==1);
L_CheckAndRegisterFunction(L, &g_tweakTracebackMsg);
g_tweakTracebackMsg = 1;
return 0;
}
////// Lua C-API interfaces for C game functions that may call events.
// http://www.freelists.org/post/luajit/intermitten-lua-pcall-crash-on-x86-64-linux,1
// Some of these are duplicate declarations:
extern void P_AddWeaponMaybeSwitchI(int32_t snum, int32_t weap);
extern void P_CheckWeaponI(int32_t snum);
extern int32_t A_ShootWithZvel(int32_t i, int32_t atwith, int32_t override_zvel);
extern int32_t A_Spawn(int32_t j, int32_t pn);
extern void VM_FallSprite(int32_t i);
extern int32_t VM_ResetPlayer2(int32_t snum);
extern void A_RadiusDamage(int32_t i, int32_t r, int32_t, int32_t, int32_t, int32_t);
extern void G_OperateSectors(int32_t sn, int32_t ii);
extern void G_OperateActivators(int32_t low,int32_t snum);
extern int32_t A_InsertSprite(int32_t whatsect,int32_t s_x,int32_t s_y,int32_t s_z,int32_t s_pn,int32_t s_s,
int32_t s_xr,int32_t s_yr,int32_t s_a,int32_t s_ve,int32_t s_zv,int32_t s_ow,int32_t s_ss);
extern void A_AddToDeleteQueue(int32_t i);
extern int32_t A_PlaySound(uint32_t num, int32_t i);
extern void A_DeleteSprite(int32_t s);
extern void G_ShowView(int32_t x, int32_t y, int32_t z, int32_t a, int32_t horiz, int32_t sect,
int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t unbiasedp);
#define LARG(index) lua_tointeger(L, index)
#define ONE_ARG LARG(1)
#define TWO_ARGS LARG(1), LARG(2)
#define THREE_ARGS LARG(1), LARG(2), LARG(3)
#define CALL_WITH_RET(Name, ...) \
int32_t ret = Name(__VA_ARGS__); \
lua_pushinteger(L, ret); \
return 1
#define CALL_WITHOUT_RET(Name, ...) \
Name(__VA_ARGS__); \
return 0
#define DEFINE_RET_CFUNC(Name, ...) \
static int32_t Name##_CF(lua_State *L) \
{ \
CALL_WITH_RET(Name, __VA_ARGS__); \
}
#define DEFINE_VOID_CFUNC(Name, ...) \
static int32_t Name##_CF(lua_State *L) \
{ \
CALL_WITHOUT_RET(Name, __VA_ARGS__); \
}
// NOTE: player struct -> player index -> player struct ugliness because
// pointers to FFI cdata apparently can't be reliably passed via lua_getpointer().
// Not to mention that lua_getpointer() returns _const_ void*.
DEFINE_VOID_CFUNC(P_AddWeaponMaybeSwitchI, TWO_ARGS)
DEFINE_VOID_CFUNC(P_CheckWeaponI, ONE_ARG)
DEFINE_RET_CFUNC(A_ShootWithZvel, THREE_ARGS)
DEFINE_RET_CFUNC(A_Spawn, TWO_ARGS)
DEFINE_VOID_CFUNC(VM_FallSprite, ONE_ARG)
DEFINE_RET_CFUNC(VM_ResetPlayer2, ONE_ARG)
DEFINE_VOID_CFUNC(A_RadiusDamage, LARG(1), LARG(2), LARG(3), LARG(4), LARG(5), LARG(6))
DEFINE_VOID_CFUNC(G_OperateSectors, TWO_ARGS)
DEFINE_VOID_CFUNC(G_OperateActivators, TWO_ARGS)
DEFINE_RET_CFUNC(A_InsertSprite, LARG(1), LARG(2), LARG(3), LARG(4), LARG(5), LARG(6),
LARG(7), LARG(8), LARG(9), LARG(10), LARG(11), LARG(12), LARG(13))
DEFINE_VOID_CFUNC(A_AddToDeleteQueue, ONE_ARG)
DEFINE_RET_CFUNC(A_PlaySound, TWO_ARGS)
DEFINE_VOID_CFUNC(A_DeleteSprite, ONE_ARG)
DEFINE_VOID_CFUNC(G_ShowView, LARG(1), LARG(2), LARG(3), LARG(4), LARG(5), LARG(6),
LARG(7), LARG(8), LARG(9), LARG(10), LARG(11))
#define CFUNC_REG(Name) { #Name, Name##_CF }
struct { const char *name; lua_CFunction func; } cfuncs[] =
{
CFUNC_REG(P_AddWeaponMaybeSwitchI),
CFUNC_REG(P_CheckWeaponI),
CFUNC_REG(A_ShootWithZvel),
CFUNC_REG(A_Spawn),
CFUNC_REG(VM_FallSprite),
CFUNC_REG(VM_ResetPlayer2),
CFUNC_REG(A_RadiusDamage),
CFUNC_REG(G_OperateSectors),
CFUNC_REG(G_OperateActivators),
CFUNC_REG(A_InsertSprite),
CFUNC_REG(A_Spawn),
CFUNC_REG(A_AddToDeleteQueue),
CFUNC_REG(A_PlaySound),
CFUNC_REG(A_DeleteSprite),
CFUNC_REG(G_ShowView),
};
// Creates a global table "CF" containing the functions from cfuncs[].
static void El_PushCFunctions(lua_State *L)
{
int32_t i;
lua_newtable(L);
for (i=0; i<(signed)sizeof(cfuncs)/(signed)sizeof(cfuncs[0]); i++)
{
lua_pushstring(L, cfuncs[i].name);
lua_pushcfunction(L, cfuncs[i].func);
lua_settable(L, -3);
}
lua_setglobal(L, "CF");
}
//////
static void El_StateSetup(lua_State *L)
{
luaopen_lpeg(L);
lua_pop(L, lua_gettop(L)); // pop off whatever lpeg leaves on the stack
// create misc. global functions in the Lua state
lua_pushcfunction(L, SetEvent_CF);
lua_setglobal(L, "gameevent_internal");
lua_pushcfunction(L, SetActor_CF);
lua_setglobal(L, "gameactor_internal");
lua_pushcfunction(L, SetTweakTracebackMsg_CF);
lua_setglobal(L, "set_tweak_traceback_internal");
El_PushCFunctions(L);
Bassert(lua_gettop(L)==0);
// This is for engine-side Lua:
lua_pushcfunction(L, &our_traceback_CF);
}
// 0: success, <0: failure
int32_t El_CreateState(L_State *estate, const char *name)
{
int32_t i;
if (!g_timingInited)
{
g_timingInited = 1;
for (i=0; i<MAXTILES; i++)
g_actorMinMs[i] = 1e308;
}
L_ErrorFunc = El_OnError;
L_OutOfMemFunc = El_OnOutOfMem;
return L_CreateState(estate, name, &El_StateSetup);
}
void El_DestroyState(L_State *estate)
{
L_DestroyState(estate);
g_tweakTracebackMsg = 0;
// XXX: It would be cleaner to also clear stuff like g_elEvents[], but
// currently, when the game Lua state is recreated, the array should have
// the same values as before, so we're skipping that for now.
}
////////// Lua_CFunctions //////////
// gameevent(EVENT_..., lua_function)
static int32_t SetEvent_CF(lua_State *L)
{
int32_t eventidx;
Bassert(lua_gettop(L) == 2);
eventidx = luaL_checkint(L, 1);
Bassert((unsigned)eventidx < MAXEVENTS);
L_CheckAndRegisterFunction(L, &g_elEvents[eventidx]);
g_elEvents[eventidx] = 1;
return 0;
}
// gameactor(actortile, strength, act, mov, movflags, lua_function)
static int32_t SetActor_CF(lua_State *L)
{
int32_t actortile;
el_actor_t *a;
Bassert(lua_gettop(L) == 6);
actortile = luaL_checkint(L, 1);
Bassert((unsigned)actortile < MAXTILES);
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;
return 0;
}
//////////////////////////////
static int32_t call_regd_function3(lua_State *L, void *keyaddr,
int32_t iActor, int32_t iPlayer, int32_t lDist)
{
#if !defined NDEBUG
const int32_t top = lua_gettop(L);
#endif
lua_pushcfunction(L, &our_traceback_CF);
// get the Lua function from the registry
lua_pushlightuserdata(L, keyaddr);
lua_gettable(L, LUA_REGISTRYINDEX);
lua_pushinteger(L, iActor);
lua_pushinteger(L, iPlayer);
lua_pushinteger(L, lDist);
// -- call it! --
{
const int32_t i = lua_pcall(L, 3, 0, -5);
const int32_t haveerr = (i != 0);
Bassert(lua_iscfunction(L, -1-haveerr));
lua_remove(L, -1-haveerr);
Bassert(lua_gettop(L) == top+haveerr);
return i;
}
}
static int32_t g_eventIdx = 0;
static void El_EventErrorPrint(const char *errmsg)
{
OSD_Printf(OSD_ERROR "event \"%s\" runtime error: %s\n",
EventNames[g_eventIdx], errmsg);
}
int32_t El_CallEvent(L_State *estate, int32_t eventidx, int32_t iActor, int32_t iPlayer, int32_t lDist, int32_t *iReturn)
{
// XXX: estate must be the one where the events were registered...
// make a global?
lua_State *const L = estate->L;
int32_t i;
const int32_t o_RETURN = g_RETURN;
g_RETURN = *iReturn;
g_elCallDepth++;
i = call_regd_function3(L, &g_elEvents[eventidx], iActor, iPlayer, lDist);
g_elCallDepth--;
*iReturn = g_RETURN;
g_RETURN = o_RETURN;
if (i != 0)
{
g_elEventError = 1;
g_eventIdx = eventidx;
return L_HandleError(L, i, &El_EventErrorPrint);
}
return 0;
}
static int32_t g_actorTile, g_iActor;
static void El_ActorErrorPrint(const char *errmsg)
{
OSD_Printf(OSD_ERROR "actor %d (sprite %d) runtime error: %s\n",
g_actorTile, g_iActor, errmsg);
}
int32_t El_CallActor(L_State *estate, int32_t actortile, int32_t iActor, int32_t iPlayer, int32_t lDist)
{
lua_State *const L = estate->L;
int32_t i;
g_elCallDepth++;
i = call_regd_function3(L, &g_elActors[actortile], iActor, iPlayer, lDist);
g_elCallDepth--;
if (i != 0)
{
g_actorTile = actortile;
g_iActor = iActor;
return L_HandleError(L, i, &El_ActorErrorPrint);
}
return 0;
}