Several window ports that support perm_invent were using a call back to the core display_inventory() function. While calling from the window port back to core functions is arguably not ideal in the first place, it was recently brought to light that code NetHack-3.7 code changes to display_inventory() actually caused it to stop repopulating the perm_invent window as intended under certain circumstances. For now, provide an alternative function, repopulate_perminvent(), that hopefullshould still work the way it did previously. There will likely be some additional changes after this to further improve things, at some point. For now though, this Resolves #1454
326 lines
13 KiB
C
326 lines
13 KiB
C
/* NetHack 3.7 winshim.c $NHDT-Date: 1596498345 2020/08/03 23:45:45 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.259 $ */
|
|
/* Copyright (c) Adam Powers, 2020 */
|
|
/* NetHack may be freely redistributed. See license for details. */
|
|
|
|
/* not an actual windowing port, but a fake win port for libnethack */
|
|
|
|
#include "hack.h"
|
|
#include <string.h>
|
|
|
|
#ifdef SHIM_GRAPHICS
|
|
#include <stdarg.h>
|
|
/* for cross-compiling to WebAssembly (WASM) */
|
|
#ifdef __EMSCRIPTEN__
|
|
#include <emscripten/emscripten.h>
|
|
#endif
|
|
|
|
#undef SHIM_DEBUG
|
|
|
|
#ifdef SHIM_DEBUG
|
|
#define debugf printf
|
|
#else /* !SHIM_DEBUG */
|
|
#define debugf(...)
|
|
#endif /* SHIM_DEBUG */
|
|
|
|
|
|
/* shim_graphics_callback is the primary interface to shim graphics,
|
|
* call this function with your declared callback function
|
|
* and you will receive all the windowing calls
|
|
*/
|
|
#ifdef __EMSCRIPTEN__
|
|
/************
|
|
* WASM interface
|
|
************/
|
|
EMSCRIPTEN_KEEPALIVE
|
|
static char *shim_callback_name = NULL;
|
|
void shim_graphics_set_callback(char *cbName);
|
|
|
|
void shim_graphics_set_callback(char *cbName) {
|
|
if (shim_callback_name != NULL) free(shim_callback_name);
|
|
if(cbName && strlen(cbName) > 0) {
|
|
debugf("setting shim_callback_name: %s\n", cbName);
|
|
shim_callback_name = strdup(cbName);
|
|
} else {
|
|
debugf("un-setting shim_callback_name\n");
|
|
shim_callback_name = NULL;
|
|
}
|
|
/* TODO: free(shim_callback_name) during shutdown? */
|
|
}
|
|
void local_callback (const char *cb_name, const char *shim_name, void *ret_ptr, const char *fmt_str, void *args);
|
|
|
|
/* A2P = Argument to Pointer */
|
|
#define A2P &
|
|
/* P2V = Pointer to Void */
|
|
#define P2V (void *)
|
|
#define DECLCB(ret_type, name, fn_args, fmt, ...) \
|
|
ret_type name fn_args; \
|
|
\
|
|
ret_type name fn_args { \
|
|
void *args[] = { __VA_ARGS__ }; \
|
|
ret_type ret = (ret_type) 0; \
|
|
debugf("SHIM GRAPHICS: " #name "\n"); \
|
|
if (!shim_callback_name) return ret; \
|
|
local_callback(shim_callback_name, #name, (void *)&ret, fmt, args); \
|
|
debugf("SHIM GRAPHICS: " #name " done.\n"); \
|
|
return ret; \
|
|
}
|
|
|
|
#define VDECLCB(name, fn_args, fmt, ...) \
|
|
void name fn_args; \
|
|
\
|
|
void name fn_args { \
|
|
void *args[] = { __VA_ARGS__ }; \
|
|
debugf("SHIM GRAPHICS: " #name "\n"); \
|
|
if (!shim_callback_name) return; \
|
|
local_callback(shim_callback_name, #name, NULL, fmt, args); \
|
|
debugf("SHIM GRAPHICS: " #name " done.\n"); \
|
|
}
|
|
|
|
#else /* !__EMSCRIPTEN__ */
|
|
|
|
/************
|
|
* libnethack.a interface
|
|
************/
|
|
typedef void(*shim_callback_t)(const char *name, void *ret_ptr, const char *fmt, ...);
|
|
static shim_callback_t shim_graphics_callback = NULL;
|
|
void shim_graphics_set_callback(shim_callback_t cb);
|
|
|
|
void shim_graphics_set_callback(shim_callback_t cb) {
|
|
shim_graphics_callback = cb;
|
|
}
|
|
|
|
#define A2P
|
|
#define P2V
|
|
#define DECLCB(ret_type, name, fn_args, fmt, ...) \
|
|
ret_type name fn_args;\
|
|
\
|
|
ret_type name fn_args { \
|
|
ret_type ret = (ret_type) 0; \
|
|
debugf("SHIM GRAPHICS: " #name "\n"); \
|
|
if (!shim_graphics_callback) return ret; \
|
|
shim_graphics_callback(#name, (void *)&ret, fmt, ## __VA_ARGS__); \
|
|
debugf("SHIM GRAPHICS: " #name " done.\n"); \
|
|
return ret; \
|
|
}
|
|
|
|
#define VDECLCB(name, fn_args, fmt, ...) \
|
|
void name fn_args;\
|
|
\
|
|
void name fn_args { \
|
|
debugf("SHIM GRAPHICS: " #name "\n"); \
|
|
if (!shim_graphics_callback) return; \
|
|
shim_graphics_callback(#name, NULL, fmt, ## __VA_ARGS__); \
|
|
debugf("SHIM GRAPHICS: " #name " done.\n"); \
|
|
}
|
|
#endif /* __EMSCRIPTEN__ */
|
|
|
|
VDECLCB(shim_init_nhwindows,(int *argcp, char **argv), "vpp", P2V argcp, P2V argv)
|
|
DECLCB(boolean, shim_player_selection_or_tty,(void), "b")
|
|
VDECLCB(shim_askname,(void), "v")
|
|
VDECLCB(shim_get_nh_event,(void), "v")
|
|
VDECLCB(shim_exit_nhwindows,(const char *str), "vs", P2V str)
|
|
VDECLCB(shim_suspend_nhwindows,(const char *str), "vs", P2V str)
|
|
VDECLCB(shim_resume_nhwindows,(void), "v")
|
|
DECLCB(winid, shim_create_nhwindow, (int type), "ii", A2P type)
|
|
VDECLCB(shim_clear_nhwindow,(winid window), "vi", A2P window)
|
|
VDECLCB(shim_display_nhwindow,(winid window, boolean blocking), "vib", A2P window, A2P blocking)
|
|
VDECLCB(shim_destroy_nhwindow,(winid window), "vi", A2P window)
|
|
VDECLCB(shim_curs,(winid a, int x, int y), "viii", A2P a, A2P x, A2P y)
|
|
VDECLCB(shim_putstr,(winid w, int attr, const char *str), "viis", A2P w, A2P attr, P2V str)
|
|
VDECLCB(shim_display_file,(const char *name, boolean complain), "vsb", P2V name, A2P complain)
|
|
VDECLCB(shim_start_menu,(winid window, unsigned long mbehavior), "vii", A2P window, A2P mbehavior)
|
|
VDECLCB(shim_add_menu,
|
|
(winid window, const glyph_info *glyphinfo, const ANY_P *identifier, char ch, char gch, int attr, int clr, const char *str, unsigned int itemflags),
|
|
"vipi00iisi",
|
|
A2P window, P2V glyphinfo, P2V identifier, A2P ch, A2P gch, A2P attr, A2P clr, P2V str, A2P itemflags)
|
|
VDECLCB(shim_end_menu,(winid window, const char *prompt), "vis", A2P window, P2V prompt)
|
|
/* XXX: shim_select_menu menu_list is an output */
|
|
DECLCB(int, shim_select_menu,(winid window, int how, MENU_ITEM_P **menu_list), "iiip", A2P window, A2P how, P2V menu_list)
|
|
DECLCB(char, shim_message_menu,(char let, int how, const char *mesg), "ciis", A2P let, A2P how, P2V mesg)
|
|
VDECLCB(shim_mark_synch,(void), "v")
|
|
VDECLCB(shim_wait_synch,(void), "v")
|
|
VDECLCB(shim_cliparound,(int x, int y), "vii", A2P x, A2P y)
|
|
VDECLCB(shim_update_positionbar,(char *posbar), "vs", P2V posbar)
|
|
VDECLCB(shim_print_glyph,(winid w, coordxy x, coordxy y, const glyph_info *glyphinfo, const glyph_info *bkglyphinfo), "vi11pp", A2P w, A2P x, A2P y, P2V glyphinfo, P2V bkglyphinfo)
|
|
VDECLCB(shim_raw_print,(const char *str), "vs", P2V str)
|
|
VDECLCB(shim_raw_print_bold,(const char *str), "vs", P2V str)
|
|
DECLCB(int, shim_nhgetch,(void), "i")
|
|
DECLCB(int, shim_nh_poskey,(coordxy *x, coordxy *y, int *mod), "ippp", P2V x, P2V y, P2V mod)
|
|
VDECLCB(shim_nhbell,(void), "v")
|
|
DECLCB(int, shim_doprev_message,(void),"iv")
|
|
DECLCB(char, shim_yn_function,(const char *query, const char *resp, char def), "css0", P2V query, P2V resp, A2P def)
|
|
VDECLCB(shim_getlin,(const char *query, char *bufp), "vsp", P2V query, P2V bufp)
|
|
DECLCB(int,shim_get_ext_cmd,(void),"iv")
|
|
VDECLCB(shim_number_pad,(int state), "vi", A2P state)
|
|
VDECLCB(shim_delay_output,(void), "v")
|
|
VDECLCB(shim_change_color,(int color, long rgb, int reverse), "viii", A2P color, A2P rgb, A2P reverse)
|
|
VDECLCB(shim_change_background,(int white_or_black), "vi", A2P white_or_black)
|
|
DECLCB(short, set_shim_font_name,(winid window_type, char *font_name),"2is", A2P window_type, P2V font_name)
|
|
DECLCB(char *,shim_get_color_string,(void),"sv")
|
|
|
|
VDECLCB(shim_preference_update, (const char *pref), "vp", P2V pref)
|
|
DECLCB(char *,shim_getmsghistory, (boolean init), "sb", A2P init)
|
|
VDECLCB(shim_putmsghistory, (const char *msg, boolean restoring_msghist), "vsb", P2V msg, A2P restoring_msghist)
|
|
VDECLCB(shim_status_init, (void), "v")
|
|
VDECLCB(shim_status_enablefield,
|
|
(int fieldidx, const char *nm, const char *fmt, boolean enable),
|
|
"vippb",
|
|
A2P fieldidx, P2V nm, P2V fmt, A2P enable)
|
|
/* XXX: the second argument to shim_status_update is sometimes an integer and sometimes a pointer */
|
|
VDECLCB(shim_status_update,
|
|
(int fldidx, genericptr_t ptr, int chg, int percent, int color, unsigned long *colormasks),
|
|
"vipiiip",
|
|
A2P fldidx, P2V ptr, A2P chg, A2P percent, A2P color, P2V colormasks)
|
|
#ifdef __EMSCRIPTEN__
|
|
/* XXX: calling repopulate_perminvent() from shim_update_inventory() causes reentrancy that breaks emscripten Asyncify */
|
|
/* this should be fine since according to windows.doc, the only purpose of shim_update_inventory() is to call repopulate_perminvent() */
|
|
void shim_update_inventory(int a1 UNUSED) {
|
|
if(iflags.perm_invent) {
|
|
repopulate_perminvent();
|
|
}
|
|
}
|
|
|
|
void shim_player_selection() {
|
|
boolean do_genl_player_setup = shim_player_selection_or_tty();
|
|
if (do_genl_player_setup) {
|
|
genl_player_setup(80);
|
|
}
|
|
}
|
|
|
|
win_request_info *
|
|
shim_ctrl_nhwindow(
|
|
winid window UNUSED,
|
|
int request UNUSED,
|
|
win_request_info *wri UNUSED) {
|
|
return (win_request_info *) 0;
|
|
}
|
|
#else /* !__EMSCRIPTEN__ */
|
|
VDECLCB(shim_player_selection, (void), "v")
|
|
VDECLCB(shim_update_inventory,(int a1 UNUSED), "vi", A2P a1)
|
|
DECLCB(win_request_info *, shim_ctrl_nhwindow,
|
|
(winid window, int request, win_request_info *wri),
|
|
"viip",
|
|
A2P window, A2P request, P2V wri)
|
|
#endif
|
|
|
|
/* Interface definition used in windows.c */
|
|
struct window_procs shim_procs = {
|
|
WPID(shim),
|
|
(0
|
|
| WC_ASCII_MAP
|
|
| WC_MOUSE_SUPPORT
|
|
| WC_COLOR | WC_HILITE_PET | WC_INVERSE | WC_EIGHT_BIT_IN),
|
|
(0
|
|
#if defined(SELECTSAVED)
|
|
| WC2_SELECTSAVED
|
|
#endif
|
|
#if defined(STATUS_HILITES)
|
|
| WC2_HILITE_STATUS | WC2_HITPOINTBAR | WC2_FLUSH_STATUS
|
|
| WC2_RESET_STATUS
|
|
#endif
|
|
| WC2_DARKGRAY | WC2_SUPPRESS_HIST | WC2_STATUSLINES),
|
|
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, /* color availability */
|
|
shim_init_nhwindows, shim_player_selection, shim_askname, shim_get_nh_event,
|
|
shim_exit_nhwindows, shim_suspend_nhwindows, shim_resume_nhwindows,
|
|
shim_create_nhwindow, shim_clear_nhwindow, shim_display_nhwindow,
|
|
shim_destroy_nhwindow, shim_curs, shim_putstr, genl_putmixed,
|
|
shim_display_file, shim_start_menu, shim_add_menu, shim_end_menu,
|
|
shim_select_menu, shim_message_menu, shim_mark_synch,
|
|
shim_wait_synch,
|
|
#ifdef CLIPPING
|
|
shim_cliparound,
|
|
#endif
|
|
#ifdef POSITIONBAR
|
|
shim_update_positionbar,
|
|
#endif
|
|
shim_print_glyph, shim_raw_print, shim_raw_print_bold, shim_nhgetch,
|
|
shim_nh_poskey, shim_nhbell, shim_doprev_message, shim_yn_function,
|
|
shim_getlin, shim_get_ext_cmd, shim_number_pad, shim_delay_output,
|
|
#ifdef CHANGE_COLOR /* the Mac uses a palette device */
|
|
shim_change_color,
|
|
#ifdef MAC
|
|
shim_change_background, set_shim_font_name,
|
|
#endif
|
|
shim_get_color_string,
|
|
#endif
|
|
|
|
genl_outrip,
|
|
shim_preference_update,
|
|
shim_getmsghistory, shim_putmsghistory,
|
|
shim_status_init,
|
|
genl_status_finish, genl_status_enablefield,
|
|
#ifdef STATUS_HILITES
|
|
shim_status_update,
|
|
#else
|
|
genl_status_update,
|
|
#endif
|
|
genl_can_suspend_yes,
|
|
shim_update_inventory,
|
|
shim_ctrl_nhwindow,
|
|
};
|
|
|
|
#ifdef __EMSCRIPTEN__
|
|
/* convert the C callback to a JavaScript callback */
|
|
EM_JS(void, local_callback, (const char *cb_name, const char *shim_name, void *ret_ptr, const char *fmt_str, void *args), {
|
|
// Asyncify.handleAsync() is the more logical choice here; however, the stack unrolling in Asyncify is performed by
|
|
// function call analysis during compilation. Since we are using an indirect callback (cb_name), it can't predict the stack
|
|
// unrolling and it crashes. Thus we use Asyncify.handleSleep() and wakeUp() to make sure that async doesn't break
|
|
// Asyncify. For details, see: https://emscripten.org/docs/porting/asyncify.html#optimizing
|
|
Asyncify.handleSleep(wakeUp => {
|
|
// convert callback arguments to proper JavaScript variadic arguments
|
|
let name = UTF8ToString(shim_name);
|
|
let fmt = UTF8ToString(fmt_str);
|
|
let cbName = UTF8ToString(cb_name);
|
|
// console.log("local_callback:", cbName, fmt, name);
|
|
|
|
// get pointer / type conversion helpers
|
|
let getPointerValue = globalThis.nethackGlobal.helpers.getPointerValue;
|
|
let setPointerValue = globalThis.nethackGlobal.helpers.setPointerValue;
|
|
|
|
reentryMutexLock(name);
|
|
|
|
let argTypes = fmt.split("");
|
|
let retType = argTypes.shift();
|
|
|
|
// build array of JavaScript args from WASM parameters
|
|
let jsArgs = [];
|
|
for (let i = 0; i < argTypes.length; i++) {
|
|
let ptr = args + (4*i);
|
|
let val = getArg(name, ptr, argTypes[i]);
|
|
jsArgs.push(val);
|
|
}
|
|
|
|
// do the callback
|
|
let userCallback = globalThis[cbName];
|
|
userCallback.call(this, name, ... jsArgs).then((retVal) => {
|
|
// save the return value
|
|
setPointerValue(name, ret_ptr, retType, retVal);
|
|
reentryMutexUnlock();
|
|
try {
|
|
wakeUp();
|
|
} catch (e) {
|
|
|
|
}
|
|
});
|
|
|
|
function getArg(name, ptr, type) {
|
|
return (type === "p") ? getValue(ptr, "*") : getPointerValue(name, getValue(ptr, "*"), type);
|
|
}
|
|
|
|
function reentryMutexLock(name) {
|
|
globalThis.nethackGlobal = globalThis.nethackGlobal || {};
|
|
if(globalThis.nethackGlobal.shimFunctionRunning) {
|
|
console.error(`'${name}' attempting second call to 'local_callback' before '${globalThis.nethackGlobal.shimFunctionRunning}' has finished, will crash emscripten Asyncify. For details see: emscripten.org/docs/porting/asyncify.html#reentrancy`);
|
|
}
|
|
globalThis.nethackGlobal.shimFunctionRunning = name;
|
|
}
|
|
|
|
function reentryMutexUnlock() {
|
|
globalThis.nethackGlobal.shimFunctionRunning = null;
|
|
}
|
|
});
|
|
})
|
|
#endif /* __EMSCRIPTEN__ */
|
|
|
|
#endif /* SHIM_GRAPHICS */
|