Change the inner workings of the experimental TTY_PERM_INVENT. Switch to delivering the content to tty for the experimental perm_invent via the existing window port interface (start_menu(), add_menu(), end_menu). This also adds a new window port interface call ctrl_nhwindow() for delivering information to the window port, and/or obtaining specific information from the window port. The information and requests can be extended as required. To be documented later once the changes settle down. Due to the intrusive nature of these changes and the possibility of some bugs in the new code, I'm going to leave TTY_PERM_INVENT commented out in the repository for a day or two. Anyone wishing to test it out can do so by uncommenting TTY_PERM_INVENT in config.h.
340 lines
14 KiB
C
340 lines
14 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)
|
|
VDECLCB(shim_player_selection,(void), "v")
|
|
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), "vii", 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), "vsi", 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),
|
|
"vippiiisi",
|
|
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), "iiio", 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), "vp", P2V posbar)
|
|
VDECLCB(shim_print_glyph,(winid w, coordxy x, coordxy y, const glyph_info *glyphinfo, const glyph_info *bkglyphinfo), "viiipp", 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), "iooo", 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), "cssi", P2V query, P2V resp, A2P def)
|
|
VDECLCB(shim_getlin,(const char *query, char *bufp), "vso", 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")
|
|
|
|
/* other defs that really should go away (they're tty specific) */
|
|
VDECLCB(shim_start_screen, (void), "v")
|
|
VDECLCB(shim_end_screen, (void), "v")
|
|
VDECLCB(shim_preference_update, (const char *pref), "vp", P2V pref)
|
|
DECLCB(char *,shim_getmsghistory, (boolean init), "si", A2P init)
|
|
VDECLCB(shim_putmsghistory, (const char *msg, boolean restoring_msghist), "vsi", 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),
|
|
"vippi",
|
|
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),
|
|
"vioiiip",
|
|
A2P fldidx, P2V ptr, A2P chg, A2P percent, A2P color, P2V colormasks)
|
|
|
|
#ifdef __EMSCRIPTEN__
|
|
/* XXX: calling display_inventory() 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 display_inventory() */
|
|
void shim_update_inventory(int a1 UNUSED) {
|
|
if(iflags.perm_invent) {
|
|
display_inventory(NULL, FALSE);
|
|
}
|
|
}
|
|
win_request_info *
|
|
shim_ctrl_nhwindow(
|
|
winid window UNUSED,
|
|
int request,
|
|
win_request_info *wri UNUSED) {
|
|
return (win_request_info *) 0;
|
|
}
|
|
#else /* !__EMSCRIPTEN__ */
|
|
VDECLCB(shim_update_inventory,(int a1 UNUSED)
|
|
DECLB(win_request_info *, shim_ctrl_nhwindow,
|
|
(winid window, int request, win_request_info *wri),
|
|
"viip",
|
|
A2P window UNUSED, A2P request UNUSED, P2V wri UNUSED)
|
|
#endif
|
|
|
|
/* Interface definition used in windows.c */
|
|
struct window_procs shim_procs = {
|
|
WPID(shim),
|
|
(0
|
|
| WC_ASCII_MAP
|
|
| 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),
|
|
#ifdef TEXTCOLOR
|
|
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, /* color availability */
|
|
#else
|
|
{1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1},
|
|
#endif
|
|
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
|
|
|
|
/* other defs that really should go away (they're tty specific) */
|
|
shim_start_screen, shim_end_screen, 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 varaidic 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];
|
|
runJsEventLoop(() => userCallback.call(this, name, ... jsArgs)).then((retVal) => {
|
|
// save the return value
|
|
setPointerValue(name, ret_ptr, retType, retVal);
|
|
// return
|
|
setTimeout(() => {
|
|
reentryMutexUnlock();
|
|
wakeUp();
|
|
}, 0);
|
|
});
|
|
|
|
function getArg(name, ptr, type) {
|
|
return (type === "o")?ptr:getPointerValue(name, getValue(ptr, "*"), type);
|
|
}
|
|
|
|
// setTimeout() with value of '0' is similar to setImmediate() (but setImmediate isn't standard)
|
|
// this lets the JS loop run for a tick so that other events can occur
|
|
// XXX: I also tried replacing the for(;;) in allmain.c:moveloop() with emscripten_set_main_loop()
|
|
// unfortunately that won't work -- if the simulate_infinite_loop arg is false, it falls through
|
|
// and the program ends;
|
|
// if is true, it throws an exception to break out of main(), but doesn't get caught because
|
|
// the stack isn't running under main() anymore...
|
|
// I think this is suboptimal, but we will have to live with it (for now?)
|
|
async function runJsEventLoop(cb) {
|
|
return new Promise((resolve) => {
|
|
setTimeout(() => {
|
|
resolve(cb());
|
|
}, 0);
|
|
});
|
|
}
|
|
|
|
function reentryMutexLock(name) {
|
|
globalThis.nethackGlobal = globalThis.nethackGlobal || {};
|
|
if(globalThis.nethackGlobal.shimFunctionRunning) {
|
|
throw new 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 */
|