/* The Lunatic Interpreter, part of EDuke32. Game-side stuff. */ #include #include // getenv #include #include // strerror #ifdef USE_LUAJIT_2_1 # include # include #else # include # include #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 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"); } ////// LUNATIC_CB int32_t (*El_RestoreGamevars)(const char *savecode); 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; istrength = 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; }