Merge branch 'libnethack2' into NetHack-3.7
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -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");
|
||||||
|
|||||||
Generated
+5
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"name": "@neth4ck/neth4ck",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 1
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
#===============-=================================================
|
#===============-=================================================
|
||||||
|
|||||||
@@ -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$@ $<
|
||||||
|
|||||||
@@ -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
@@ -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;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user