Merge branch 'libnethack2' into NetHack-3.7

This commit is contained in:
nhmall
2020-10-04 20:13:35 -04:00
11 changed files with 1371 additions and 101 deletions
File diff suppressed because it is too large Load Diff
+5
View File
@@ -15,6 +15,11 @@ The main module returns a setup function: `startNethack(uiCallback, moduleOption
* `moduleOptions` - An optional [emscripten Module object](https://emscripten.org/docs/api_reference/module.html) for configuring the WASM that will be run. * `moduleOptions` - An optional [emscripten Module object](https://emscripten.org/docs/api_reference/module.html) for configuring the WASM that will be run.
* `Module.arguments` - Of note is the [arguments property](https://emscripten.org/docs/api_reference/module.html#Module.arguments) which gets passed to NetHack as its [command line parameters](https://nethackwiki.com/wiki/Options). * `Module.arguments` - Of note is the [arguments property](https://emscripten.org/docs/api_reference/module.html#Module.arguments) which gets passed to NetHack as its [command line parameters](https://nethackwiki.com/wiki/Options).
There are a number of auxilary functions and variables that may help with building your applications. All of these are under `globalThis.nethackOptions`. Use `console.log(globalThis.nethackOptions)` for a full list of options. Some worth mentioning are:
* `globalThis.nethackOptions.helpers` - Helper functions that are useful for NetHack windowing ports
* `globalThis.nethackOptions.mapglyphHelper` - Converts an integer glyph into a character to be displayed. Useful if you are using ASCII characters for representing NetHack (as opposed to tiles). Interface is `mapglyphHelper(glyph, x, y, mgflags)` and will typically be called as part of the `shim_print_glyph` function.
* `globalThis.nethackOptions.constants` - A Object full of constants that are `#define`'d in NetHack's C code. Useful for translating to / from numbers in the APIs and return values.
## Example ## Example
``` js ``` js
let nethackStart = require("nethack"); let nethackStart = require("nethack");
+5
View File
@@ -0,0 +1,5 @@
{
"name": "@neth4ck/neth4ck",
"version": "1.0.0",
"lockfileVersion": 1
}
+7 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@neth4ck/neth4ck", "name": "@neth4ck/neth4ck",
"version": "1.0.0", "version": "1.0.1",
"description": "The original NetHack rogue-like game built as a WebAssembly module", "description": "The original NetHack rogue-like game built as a WebAssembly module",
"main": "src/nethackShim.js", "main": "src/nethackShim.js",
"scripts": { "scripts": {
@@ -13,7 +13,12 @@
"nethack", "nethack",
"rogue", "rogue",
"rogue-like", "rogue-like",
"game" "roguelike",
"dungeon",
"dungeons",
"game",
"rpg",
"dnd"
], ],
"author": "Adam Powers <apowers@ato.ms>", "author": "Adam Powers <apowers@ato.ms>",
"license": "SEE LICENSE IN LICENSE.md" "license": "SEE LICENSE IN LICENSE.md"
+55
View File
@@ -0,0 +1,55 @@
let nethackStart = require("../src/nethackShim.js");
Error.stackTraceLimit = 20;
// debugging to make sure the JavaScript event loop isn't blocked
// const {performance} = require("perf_hooks");
// let currentTime = 0;
// let lastTime = 0;
// setInterval(() => {
// lastTime = currentTime;
// currentTime = performance.now();
// console.log("Time since last JavaScript loop:", currentTime-lastTime);
// }, 10);
let Module = {};
let winCount = 0;
/* global globalThis */
nethackStart(async function (name, ... args) {
switch(name) {
case "shim_init_nhwindows":
console.log("globalThis.nethackGlobal", globalThis.nethackGlobal);
break;
case "shim_create_nhwindow":
winCount++;
console.log("creating window", args, "returning", winCount);
return winCount;
case "shim_print_glyph":
var x = args[1];
var y = args[2];
var glyph = args[3];
var ret = globalThis.nethackGlobal.helpers.mapglyphHelper(glyph, x, y, 0);
console.log(`GLYPH (${x},${y}): ${String.fromCharCode(ret.ch)}`);
return;
// case "shim_update_inventory":
// globalThis.nethackGlobal.helpers.displayInventory();
// return;
case "shim_select_menu":
return await selectMenu(...args);
case "shim_yn_function":
case "shim_message_menu":
return 121; // 'y'
case "shim_nhgetch":
case "shim_nh_poskey":
return 0;
default:
console.log(`called doGraphics: ${name} [${args}]`);
return 0;
}
}, Module);
async function selectMenu(window, how, selected) {
Module.setValue(selected, 0, "*");
return -1;
}
+1
View File
@@ -146,5 +146,6 @@ PANICTRACE_LIBC=0
# option settings via NETHACKOPTIONS in their environment or via # option settings via NETHACKOPTIONS in their environment or via
# ~/.nethackrc run-time configuration file. # ~/.nethackrc run-time configuration file.
#OPTIONS=!autopickup,fruit:tomato,symset:DECgraphics #OPTIONS=!autopickup,fruit:tomato,symset:DECgraphics
OPTIONS=perm_invent
#eof #eof
+2 -2
View File
@@ -161,7 +161,7 @@ $(TARGETPFX)recover.exe : $(TARGETPFX)recover.o
$(TARGET_LINK) $(TARGET_LFLAGS) $(TARGETPFX)recover.o -o $@ $(TARGET_LINK) $(TARGET_LFLAGS) $(TARGETPFX)recover.o -o $@
endif # CROSS_SHARED endif # CROSS_SHARED
# #
ifdef BUILD_LUA ifdef BUILD_TARGET_LUA
# Lua lib # Lua lib
$(LUACROSSLIB): $(LUALIBOBJS) $(LUACROSSLIB): $(LUALIBOBJS)
if [ -f $@ ]; then rm $@; fi; if [ -f $@ ]; then rm $@; fi;
@@ -206,7 +206,7 @@ $(TARGETPFX)lundump.o : $(LUATOP)/src/lundump.c
$(TARGETPFX)lutf8lib.o : $(LUATOP)/src/lutf8lib.c $(TARGETPFX)lutf8lib.o : $(LUATOP)/src/lutf8lib.c
$(TARGETPFX)lvm.o : $(LUATOP)/src/lvm.c $(TARGETPFX)lvm.o : $(LUATOP)/src/lvm.c
$(TARGETPFX)lzio.o : $(LUATOP)/src/lzio.c $(TARGETPFX)lzio.o : $(LUATOP)/src/lzio.c
endif # BUILD_LUA endif # BUILD_TARGET_LUA
ifdef BUILD_PDCURSES ifdef BUILD_PDCURSES
ifdef WANT_WIN_CURSES ifdef WANT_WIN_CURSES
+12 -9
View File
@@ -5,7 +5,7 @@
# #
ifdef CROSS_TO_MSDOS ifdef CROSS_TO_MSDOS
BUILD_LUA=1 BUILD_TARGET_LUA=1
BUILD_PDCURSES=1 BUILD_PDCURSES=1
CROSS_SHARED=1 CROSS_SHARED=1
override TARGET = msdos override TARGET = msdos
@@ -15,7 +15,7 @@ override TARGET_LIBS=
endif endif
ifdef CROSS_TO_AMIGA ifdef CROSS_TO_AMIGA
BUILD_LUA=1 BUILD_TARGET_LUA=1
BUILD_PDCURSES=1 BUILD_PDCURSES=1
CROSS_SHARED=1 CROSS_SHARED=1
override TARGET = amiga override TARGET = amiga
@@ -25,14 +25,14 @@ override TARGET_LIBS=
endif endif
ifdef CROSS_TO_WASM ifdef CROSS_TO_WASM
BUILD_LUA=1 BUILD_TARGET_LUA=1
override TARGET = wasm override TARGET = wasm
override TARGETDIR=../targets/$(TARGET) override TARGETDIR=../targets/$(TARGET)
override TARGETPFX = $(TARGETDIR)/ override TARGETPFX = $(TARGETDIR)/
override TARGET_LIBS= override TARGET_LIBS=
endif endif
ifdef BUILD_LUA ifdef BUILD_TARGET_LUA
#===============-================================================= #===============-=================================================
# LUA library # LUA library
# Source from http://www.lua.org/ftp/lua-5.4.0.tar.gz # Source from http://www.lua.org/ftp/lua-5.4.0.tar.gz
@@ -65,7 +65,7 @@ BUILDMORE += $(LUACROSSLIB)
override TARGET_LIBS += $(LUACROSSLIB) -lm override TARGET_LIBS += $(LUACROSSLIB) -lm
else else
LUAINCL= LUAINCL=
endif # BUILD_LUA endif # BUILD_TARGET_LUA
ifdef BUILD_PDCURSES ifdef BUILD_PDCURSES
#===============-================================================= #===============-=================================================
@@ -333,11 +333,11 @@ override TARGET_CFLAGS = $(EMCC_CFLAGS) -c \
override TARGET_CXXFLAGS = $(TARGET_CFLAGS) override TARGET_CXXFLAGS = $(TARGET_CFLAGS)
override TARGET_LINK = $(TARGET_CC) override TARGET_LINK = $(TARGET_CC)
override TARGET_LFLAGS= $(EMCC_LFLAGS) override TARGET_LFLAGS= $(EMCC_LFLAGS)
override SYSSRC = ../sys/unix/unixmain.c \ override SYSSRC = ../sys/lib/libnethackmain.c \
../sys/share/ioctl.c ../sys/share/unixtty.c \ ../sys/share/ioctl.c ../sys/share/unixtty.c \
../sys/unix/unixunix.c ../sys/unix/unixres.c \ ../sys/unix/unixunix.c ../sys/unix/unixres.c \
../win/shim/winshim.c ../win/shim/winshim.c
override SYSOBJ= $(TARGETPFX)unixmain.o \ override SYSOBJ= $(TARGETPFX)libnethackmain.o \
$(TARGETPFX)ioctl.o $(TARGETPFX)unixtty.o \ $(TARGETPFX)ioctl.o $(TARGETPFX)unixtty.o \
$(TARGETPFX)unixunix.o $(TARGETPFX)unixres.o \ $(TARGETPFX)unixunix.o $(TARGETPFX)unixres.o \
$(TARGETPFX)winshim.o $(TARGETPFX)winshim.o
@@ -354,6 +354,9 @@ CLEANMORE += rm -f -r $(TARGETDIR)
# Rule for file in sys/unix # Rule for file in sys/unix
$(TARGETPFX)%.o : ../sys/unix/%.c $(TARGETPFX)%.o : ../sys/unix/%.c
$(TARGET_CC) $(TARGET_CFLAGS) -c -o$@ $< $(TARGET_CC) $(TARGET_CFLAGS) -c -o$@ $<
# Rule for file in sys/lib
$(TARGETPFX)%.o : ../sys/lib/%.c
$(TARGET_CC) $(TARGET_CFLAGS) -c -o$@ $<
# Rule for files in win/shim # Rule for files in win/shim
$(TARGETPFX)%.o : ../win/shim/%.c $(TARGETPFX)%.o : ../win/shim/%.c
$(TARGET_CC) $(TARGET_CFLAGS) -c -o$@ $< $(TARGET_CC) $(TARGET_CFLAGS) -c -o$@ $<
@@ -377,11 +380,11 @@ $(TARGETPFX)%.o : ../util/%.c
$(TARGET_CC) $(TARGET_CFLAGS) -o$@ $< $(TARGET_CC) $(TARGET_CFLAGS) -o$@ $<
endif # CROSS_SHARED endif # CROSS_SHARED
ifdef BUILD_LUA ifdef BUILD_TARGET_LUA
# Rule for LUA files # Rule for LUA files
$(TARGETPFX)%.o : $(LUATOP)/src/%.c $(TARGETPFX)%.o : $(LUATOP)/src/%.c
$(TARGET_CC) $(TARGET_CFLAGS) $(LUA_FLAGS) -o$@ $< $(TARGET_CC) $(TARGET_CFLAGS) $(LUA_FLAGS) -o$@ $<
endif # BUILD_LUA endif # BUILD_TARGET_LUA
# #
# End of cross-compiling -PRE section # End of cross-compiling -PRE section
#===============-================================================= #===============-=================================================
+8 -3
View File
@@ -166,10 +166,15 @@ VARDATND += $(sort $(VARDATND0))
ifdef WANT_LIBNH ifdef WANT_LIBNH
CFLAGS += -DSHIM_GRAPHICS -DNOTTYGRAPHICS -DNOSHELL -DLIBNH CFLAGS += -DSHIM_GRAPHICS -DNOTTYGRAPHICS -DNOSHELL -DLIBNH
WINOBJ = winshim.o LIBNHSYSSRC = ../sys/lib/libnethackmain.c \
../sys/share/ioctl.c ../sys/share/unixtty.c \
../sys/unix/unixunix.c ../sys/unix/unixres.c \
../win/shim/winshim.c
LIBNHSYSOBJ= libnethackmain.o ioctl.o unixtty.o unixunix.o \
unixres.o winshim.o
MOREALL += ( cd src ; $(MAKE) pregame ; $(MAKE) libnethack.a ) MOREALL += ( cd src ; $(MAKE) pregame ; $(MAKE) libnethack.a )
libnethack.a: $(HOBJ) $(SYSOBJ) $(WINOBJ) ../lib/lua/liblua.a libnethack.a: $(HOBJ) $(LIBNHSYSOBJ) ../lib/lua/liblua.a
$(AR) rcs $@ $(HOBJ) $(WINOBJ) ../lib/lua/liblua.a $(AR) rcs $@ $(HOBJ) $(LIBNHSYSOBJ) ../lib/lua/liblua.a
@echo "$@ built." @echo "$@ built."
winshim.o : ../win/shim/winshim.c $(HACK_H) winshim.o : ../win/shim/winshim.c $(HACK_H)
$(CC) $(CFLAGS) -c -o$@ $< $(CC) $(CFLAGS) -c -o$@ $<
+8 -3
View File
@@ -173,10 +173,15 @@ VARDATND += $(sort $(VARDATND0))
ifdef WANT_LIBNH ifdef WANT_LIBNH
CFLAGS += -DSHIM_GRAPHICS -DNOTTYGRAPHICS -DNOSHELL -DLIBNH CFLAGS += -DSHIM_GRAPHICS -DNOTTYGRAPHICS -DNOSHELL -DLIBNH
WINOBJ = winshim.o LIBNHSYSSRC = ../sys/lib/libnethackmain.c \
../sys/share/ioctl.c ../sys/share/unixtty.c \
../sys/unix/unixunix.c ../sys/unix/unixres.c \
../win/shim/winshim.c
LIBNHSYSOBJ= libnethackmain.o ioctl.o unixtty.o unixunix.o \
unixres.o winshim.o
MOREALL += ( cd src ; $(MAKE) pregame ; $(MAKE) libnethack.a ) MOREALL += ( cd src ; $(MAKE) pregame ; $(MAKE) libnethack.a )
libnethack.a: $(HOBJ) $(SYSOBJ) $(WINOBJ) ../lib/lua/liblua.a libnethack.a: $(HOBJ) $(LIBNHSYSOBJ) ../lib/lua/liblua.a
$(AR) rcs $@ $(HOBJ) $(WINOBJ) ../lib/lua/liblua.a $(AR) rcs $@ $(HOBJ) $(LIBNHSYSOBJ) ../lib/lua/liblua.a
@echo "$@ built." @echo "$@ built."
winshim.o : ../win/shim/winshim.c $(HACK_H) winshim.o : ../win/shim/winshim.c $(HACK_H)
$(CC) $(CFLAGS) -c -o$@ $< $(CC) $(CFLAGS) -c -o$@ $<
+141 -82
View File
@@ -51,6 +51,7 @@ ret_type name fn_args { \
debugf("SHIM GRAPHICS: " #name "\n"); \ debugf("SHIM GRAPHICS: " #name "\n"); \
if (!shim_callback_name) return ret; \ if (!shim_callback_name) return ret; \
local_callback(shim_callback_name, #name, (void *)&ret, fmt, args); \ local_callback(shim_callback_name, #name, (void *)&ret, fmt, args); \
debugf("SHIM GRAPHICS: " #name " done.\n"); \
return ret; \ return ret; \
} }
@@ -60,6 +61,7 @@ void name fn_args { \
debugf("SHIM GRAPHICS: " #name "\n"); \ debugf("SHIM GRAPHICS: " #name "\n"); \
if (!shim_callback_name) return; \ if (!shim_callback_name) return; \
local_callback(shim_callback_name, #name, NULL, fmt, args); \ local_callback(shim_callback_name, #name, NULL, fmt, args); \
debugf("SHIM GRAPHICS: " #name " done.\n"); \
} }
#else /* !__EMSCRIPTEN__ */ #else /* !__EMSCRIPTEN__ */
@@ -81,6 +83,7 @@ ret_type name fn_args { \
debugf("SHIM GRAPHICS: " #name "\n"); \ debugf("SHIM GRAPHICS: " #name "\n"); \
if (!shim_graphics_callback) return ret; \ if (!shim_graphics_callback) return ret; \
shim_graphics_callback(#name, (void *)&ret, fmt, ## __VA_ARGS__); \ shim_graphics_callback(#name, (void *)&ret, fmt, ## __VA_ARGS__); \
debugf("SHIM GRAPHICS: " #name " done.\n"); \
return ret; \ return ret; \
} }
@@ -89,6 +92,7 @@ void name fn_args { \
debugf("SHIM GRAPHICS: " #name "\n"); \ debugf("SHIM GRAPHICS: " #name "\n"); \
if (!shim_graphics_callback) return; \ if (!shim_graphics_callback) return; \
shim_graphics_callback(#name, NULL, fmt, ## __VA_ARGS__); \ shim_graphics_callback(#name, NULL, fmt, ## __VA_ARGS__); \
debugf("SHIM GRAPHICS: " #name " done.\n"); \
} }
#endif /* __EMSCRIPTEN__ */ #endif /* __EMSCRIPTEN__ */
@@ -133,9 +137,9 @@ VDECLCB(shim_add_menu,
"viipiiisi", "viipiiisi",
A2P window, A2P glyph, P2V identifier, A2P ch, A2P gch, A2P attr, P2V str, A2P itemflags) A2P window, A2P glyph, P2V identifier, A2P ch, A2P gch, A2P attr, P2V str, A2P itemflags)
VDECLCB(shim_end_menu,(winid window, const char *prompt), "vis", A2P window, P2V prompt) VDECLCB(shim_end_menu,(winid window, const char *prompt), "vis", A2P window, P2V prompt)
DECLCB(int, shim_select_menu,(winid window, int how, MENU_ITEM_P **menu_list), "iiip", A2P window, A2P how, P2V menu_list) /* 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_P let, int how, const char *mesg), "ciis", A2P let, A2P how, P2V mesg) DECLCB(char, shim_message_menu,(CHAR_P let, int how, const char *mesg), "ciis", A2P let, A2P how, P2V mesg)
VDECLCB(shim_update_inventory,(void), "v")
VDECLCB(shim_mark_synch,(void), "v") VDECLCB(shim_mark_synch,(void), "v")
VDECLCB(shim_wait_synch,(void), "v") VDECLCB(shim_wait_synch,(void), "v")
VDECLCB(shim_cliparound,(int x, int y), "vii", A2P x, A2P y) VDECLCB(shim_cliparound,(int x, int y), "vii", A2P x, A2P y)
@@ -144,11 +148,11 @@ VDECLCB(shim_print_glyph,(winid w, int x, int y, int glyph, int bkglyph), "viiii
VDECLCB(shim_raw_print,(const char *str), "vs", P2V str) VDECLCB(shim_raw_print,(const char *str), "vs", P2V str)
VDECLCB(shim_raw_print_bold,(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_nhgetch,(void), "i")
DECLCB(int, shim_nh_poskey,(int *x, int *y, int *mod), "ippp", P2V x, P2V y, P2V mod) DECLCB(int, shim_nh_poskey,(int *x, int *y, int *mod), "iooo", P2V x, P2V y, P2V mod)
VDECLCB(shim_nhbell,(void), "v") VDECLCB(shim_nhbell,(void), "v")
DECLCB(int, shim_doprev_message,(void),"iv") DECLCB(int, shim_doprev_message,(void),"iv")
DECLCB(char, shim_yn_function,(const char *query, const char *resp, CHAR_P def), "cssi", P2V query, P2V resp, A2P def) DECLCB(char, shim_yn_function,(const char *query, const char *resp, CHAR_P def), "cssi", P2V query, P2V resp, A2P def)
VDECLCB(shim_getlin,(const char *query, char *bufp), "vsp", P2V query, P2V bufp) VDECLCB(shim_getlin,(const char *query, char *bufp), "vso", P2V query, P2V bufp)
DECLCB(int,shim_get_ext_cmd,(void),"iv") DECLCB(int,shim_get_ext_cmd,(void),"iv")
VDECLCB(shim_number_pad,(int state), "vi", A2P state) VDECLCB(shim_number_pad,(int state), "vi", A2P state)
VDECLCB(shim_delay_output,(void), "v") VDECLCB(shim_delay_output,(void), "v")
@@ -168,11 +172,24 @@ VDECLCB(shim_status_enablefield,
(int fieldidx, const char *nm, const char *fmt, BOOLEAN_P enable), (int fieldidx, const char *nm, const char *fmt, BOOLEAN_P enable),
"vippi", "vippi",
A2P fieldidx, P2V nm, P2V fmt, A2P enable) 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, VDECLCB(shim_status_update,
(int fldidx, genericptr_t ptr, int chg, int percent, int color, unsigned long *colormasks), (int fldidx, genericptr_t ptr, int chg, int percent, int color, unsigned long *colormasks),
"vipiiip", "vioiiip",
A2P fldidx, P2V ptr, A2P chg, A2P percent, A2P color, P2V colormasks) 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() {
if(iflags.perm_invent) {
display_inventory(NULL, FALSE);
}
}
#else /* !__EMSCRIPTEN__ */
VDECLCB(shim_update_inventory,(void), "v")
#endif
/* Interface definition used in windows.c */ /* Interface definition used in windows.c */
struct window_procs shim_procs = { struct window_procs shim_procs = {
"shim", "shim",
@@ -234,13 +251,23 @@ struct window_procs shim_procs = {
#ifdef __EMSCRIPTEN__ #ifdef __EMSCRIPTEN__
/* convert the C callback to a JavaScript callback */ /* 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), { EM_JS(void, local_callback, (const char *cb_name, const char *shim_name, void *ret_ptr, const char *fmt_str, void *args), {
Asyncify.handleAsync(async () => { // 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 // convert callback arguments to proper JavaScript varaidic arguments
let name = Module.UTF8ToString(shim_name); let name = UTF8ToString(shim_name);
let fmt = Module.UTF8ToString(fmt_str); let fmt = UTF8ToString(fmt_str);
let cbName = Module.UTF8ToString(cb_name); let cbName = UTF8ToString(cb_name);
// console.log("local_callback:", cbName, fmt, 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 argTypes = fmt.split("");
let retType = argTypes.shift(); let retType = argTypes.shift();
@@ -248,53 +275,117 @@ EM_JS(void, local_callback, (const char *cb_name, const char *shim_name, void *r
let jsArgs = []; let jsArgs = [];
for (let i = 0; i < argTypes.length; i++) { for (let i = 0; i < argTypes.length; i++) {
let ptr = args + (4*i); let ptr = args + (4*i);
let val = typeLookup(argTypes[i], ptr); let val = getArg(name, ptr, argTypes[i]);
jsArgs.push(val); jsArgs.push(val);
} }
decodeArgs(name, jsArgs);
// do the callback // do the callback
let userCallback = globalThis[cbName]; let userCallback = globalThis[cbName];
let retVal = await runJsLoop(() => userCallback(name, ... jsArgs)); runJsEventLoop(() => userCallback.call(this, name, ... jsArgs)).then((retVal) => {
// save the return value
setPointerValue(name, ret_ptr, retType, retVal);
// return
setTimeout(() => {
reentryMutexUnlock();
wakeUp();
}, 0);
});
// save the return value // make callback arguments friendly: convert numbers to strings where possible
setReturn(name, ret_ptr, retType, retVal); function decodeArgs(name, args) {
// if (!globalThis.nethackGlobal.makeArgsFriendly) return;
// convert 'ptr' to the type indicated by 'type' switch(name) {
function typeLookup(type, ptr) { case "shim_create_nhwindow":
switch(type) { args[0] = globalThis.nethackGlobal.constants["WIN_TYPE"][args[0]];
case "s": // string break;
return Module.UTF8ToString(Module.getValue(ptr, "*")); case "shim_status_update":
case "p": // pointer // which field is being updated?
ptr = Module.getValue(ptr, "*"); args[0] = globalThis.nethackGlobal.constants["STATUS_FIELD"][args[0]];
if(!ptr) return 0; // null pointer // arg[1] is a string unless it is BL_CONDITION, BL_RESET, BL_FLUSH, BL_CHARACTERISTICS
return Module.getValue(ptr, "*"); if(["BL_CONDITION", "BL_RESET", "BL_FLUSH", "BL_CHARACTERISTICS"].indexOf(args[0] && args[1]) < 0) {
case "c": // char args[1] = getArg(name, args[1], "s");
return String.fromCharCode(Module.getValue(Module.getValue(ptr, "*"), "i8")); } else {
case "0": /* 2^0 = 1 byte */ args[1] = getArg(name, args[1], "p");
return Module.getValue(Module.getValue(ptr, "*"), "i8"); }
case "1": /* 2^1 = 2 bytes */ break;
return Module.getValue(Module.getValue(ptr, "*"), "i16"); case "shim_display_file":
case "2": /* 2^2 = 4 bytes */ args[1] = !!args[1];
case "i": // integer case "shim_display_nhwindow":
case "n": // number args[0] = decodeWindow(args[0]);
return Module.getValue(Module.getValue(ptr, "*"), "i32"); args[1] = !!args[1];
case "f": // float break;
return Module.getValue(Module.getValue(ptr, "*"), "float"); case "shim_getmsghistory":
case "d": // double args[0] = !!args[0];
return Module.getValue(Module.getValue(ptr, "*"), "double"); break;
default: case "shim_putmsghistory":
throw new TypeError ("unknown type:" + type); args[1] = !!args[1];
break;
case "shim_status_enablefield":
console.log("shim_status_enablefield arg 1:", args[1]);
args[3] = !!args[3];
break;
case "shim_add_menu":
args[0] = decodeWindow(args[0]);
// args[1] = mapglyphHelper(args[1]);
// args[5] = decodeAttr(args[5]);
break;
case "shim_putstr":
args[0] = decodeWindow(args[0]);
break;
case "shim_select_menu":
args[0] = decodeWindow(args[0]);
args[1] = decodeSelected(args[1]);
break;
case "shim_clear_nhwindow":
case "shim_destroy_nhwindow":
case "shim_curs":
case "shim_start_menu":
case "shim_end_menu":
case "shim_print_glyph":
args[0] = decodeWindow(args[0]);
break;
} }
} }
// setTimeout() with value of '0' is similar to setImmediate() (which isn't standard) function decodeWindow(winid) {
let { WIN_MAP, WIN_INVEN, WIN_STATUS, WIN_MESSAGE } = globalThis.nethackGlobal.globals;
switch(winid) {
case WIN_MAP: return "WIN_MAP";
case WIN_MESSAGE: return "WIN_MESSAGE";
case WIN_STATUS: return "WIN_STATUS";
case WIN_INVEN: return "WIN_INVEN";
default: return winid;
}
// return winid;
}
function decodeSelected(how) {
let { PICK_NONE, PICK_ONE, PICK_ANY } = globalThis.nethackGlobal.constants.MENU_SELECT;
switch(how) {
case PICK_NONE: return "PICK_NONE";
case PICK_ONE: return "PICK_ONE";
case PICK_ANY: return "PICK_ANY";
default: return how;
}
}
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 // 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() // 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; // 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 // 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... // the stack isn't running under main() anymore...
// I think this is suboptimal, but we will have to live with it // I think this is suboptimal, but we will have to live with it (for now?)
async function runJsLoop(cb) { async function runJsEventLoop(cb) {
return new Promise((resolve) => { return new Promise((resolve) => {
setTimeout(() => { setTimeout(() => {
resolve(cb()); resolve(cb());
@@ -302,48 +393,16 @@ EM_JS(void, local_callback, (const char *cb_name, const char *shim_name, void *r
}); });
} }
// sets the return value of the function to the type expected function reentryMutexLock(name) {
function setReturn(name, ptr, type, value = 0) { globalThis.nethackGlobal = globalThis.nethackGlobal || {};
switch (type) { if(globalThis.nethackGlobal.shimFunctionRunning) {
case "p": 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`);
throw new Error("not implemented");
case "s":
if(typeof value !== "string")
throw new TypeError(`expected ${name} return type to be string`);
value=value?value:"(no value)";
var strPtr = Module.getValue(ptr, "i32");
Module.stringToUTF8(value, strPtr, 1024);
break;
case "i":
if(typeof value !== "number" || !Number.isInteger(value))
throw new TypeError(`expected ${name} return type to be integer`);
Module.setValue(ptr, value, "i32");
break;
case "c":
if(typeof value !== "number" || value < 0 || value > 128)
throw new TypeError(`expected ${name} return type to be integer representing an ASCII character`);
Module.setValue(ptr, value, "i8");
break;
case "f":
if(typeof value !== "number" || isFloat(value))
throw new TypeError(`expected ${name} return type to be float`);
// XXX: I'm not sure why 'double' works and 'float' doesn't
Module.setValue(ptr, value, "double");
break;
case "d":
if(typeof value !== "number" || isFloat(value))
throw new TypeError(`expected ${name} return type to be float`);
Module.setValue(ptr, value, "double");
break;
case "v":
break;
default:
throw new Error("unknown type");
} }
globalThis.nethackGlobal.shimFunctionRunning = name;
}
function isFloat(n){ function reentryMutexUnlock() {
return n === +n && n !== (n|0) && !Number.isInteger(n); globalThis.nethackGlobal.shimFunctionRunning = null;
}
} }
}); });
}) })