One of the drivers of this change was that screen coordinates require a
type that can hold values greater than 127. Parameters to the window
port routines require a large type in order to be able to have values
a fair bit larger than COLNO and ROWNO passed to them, particularly for
their use to the right of the map window.
This splits the uses of xchar into 3 different situations, and adjusts
their type and size:
xchar
|
-----------------------
| | |
coordxy xint16 xint8
coordxy: Actual x or y coordinates for various things (moved to 16-bits).
xint16: Same data size as coordxy, but for non-coordinate use (16-bits).
xint8: There are only a few use cases initially, where it was very
plain to see that the variable could remain as 8-bits, rather
than be bumped to 16-bits. There are probably more such cases
that could be changed after additional review.
Note: This first changed all xchar variables to coordxy. Some were
reviewed and got changed to xint16 or xint8 when it became apparent that
their usage was not for coordinates.
This increments EDITLEVEL in patchlevel.h
342 lines
14 KiB
C
342 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,(int *x, int *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);
|
|
}
|
|
}
|
|
perminvent_info *
|
|
shim_update_invent_slot(
|
|
winid window UNUSED, /* window to use, must be of type NHW_MENU */
|
|
int inventory_slot UNUSED, /* slot id: 0 - info return to core */
|
|
/* 1 - gold slot */
|
|
/* 2 - 29 obj slots */
|
|
perminvent_info *pi UNUSED) {
|
|
return (perminvent_info *) 0;
|
|
}
|
|
#else /* !__EMSCRIPTEN__ */
|
|
VDECLCB(shim_update_inventory,(int a1 UNUSED)
|
|
DECLB(perminvent_info *, shim_update_invent_slot,
|
|
(winid window, int inventory_slot, perminvent_info *pi),
|
|
"viip",
|
|
A2P window UNUSED, A2P inventory_slot UNUSED, P2V pi 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_update_invent_slot,
|
|
};
|
|
|
|
#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 */
|