Rev 4766 |
Blame |
Compare with Previous |
Last modification |
View Log
| RSS feed
/* The Lunatic Interpreter, part of EDuke32. Game-side stuff. */
#include <stdint.h>
#include <stdlib.h> // getenv
#include <errno.h>
#include <string.h> // strerror
#ifdef __cplusplus
extern "C" {
#endif
#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
#ifdef __cplusplus
}
#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
;
// Will be set to 0 after the first time that user Lua modules are run.
int32_t g_elFirstTime
= 1;
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
);
#ifdef __cplusplus
extern "C" {
#endif
// in lpeg.o
extern int luaopen_lpeg
(lua_State
*L
);
#ifdef __cplusplus
}
#endif
// 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
= (char *)Xmalloc
(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;
int32_t haveev
=0, haveac
=0;
for (i
=0; i
<MAXEVENTS
; i
++)
{
int32_t len
= Bstrlen
(EventNames
[i
]+nn
);
Bassert
(len
< (int32_t)sizeof(buf
));
maxlen
= max
(len
, maxlen
);
}
for (i
=0; i
<MAXEVENTS
; i
++)
if (g_eventCalls
[i
])
{
int32_t n
=Bsprintf
(buf
, "%s", EventNames
[i
]+nn
);
if (!haveev
)
{
haveev
= 1;
OSD_Printf
("\n -- event times: [event]={ total calls, total time [ms], mean time/call [us] }\n");
}
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
]);
}
for (i
=0; i
<MAXTILES
; i
++)
if (g_actorCalls
[i
])
{
if (!haveac
)
{
haveac
= 1;
OSD_Printf
("\n -- actor times: [tile]={ total calls, total time [ms], {min,mean,max} time/call [us] }\n");
}
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
]);
}
}
}
////////// ERROR REPORTING //////////
#define EL_MAXERRORS 20
static int32_t el_numErrors
=0, el_tooMuchErrors
;
static char *el_errorMsgs
[EL_MAXERRORS
];
int8_t el_addNewErrors
= 1; // add new errors to display?
// 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;
}
LUNATIC_EXTERN
void El_OnError
(const char *str
)
{
if (el_addNewErrors
&& !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
= (char *)Xmalloc
(nl
-str
+1);
Bmemcpy
(errstr
, str
, nl
-str
);
errstr
[nl
-str
] = 0;
}
else
{
errstr
= Xstrdup
(str
);
}
// ...and save it:
el_errorMsgs
[el_numErrors
++] = errstr
;
}
}
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:
#ifdef __cplusplus
extern "C" {
#endif
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
, int32_t flags
);
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
);
extern void G_GameExit
(const char *msg
);
#ifdef __cplusplus
}
#endif
#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
, TWO_ARGS
)
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 }
static 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
);
}
static void El_OnOutOfMem
(void)
{
G_GameExit
("Out of memory in Lunatic.");
}
// 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;
}