Merge branch 'libnethack' of https://github.com/apowers313/NetHack into libnethack2

This commit is contained in:
nhmall
2020-10-04 19:51:37 -04:00
10 changed files with 1375 additions and 104 deletions
+7
View File
@@ -87,3 +87,10 @@ targets/*
#test.js #test.js
#sys/lib/npm-package/build/nethack.js #sys/lib/npm-package/build/nethack.js
#sys/lib/npm-package/build/nethack.wasm #sys/lib/npm-package/build/nethack.wasm
src/libnethack.a
/libtest.c
/nhlibtest
/run.sh
/test.js
sys/lib/npm-package/build/nethack.js
sys/lib/npm-package/build/nethack.wasm
+9 -2
View File
@@ -331,8 +331,15 @@ struct version_info {
unsigned long incarnation; /* actual version number */ unsigned long incarnation; /* actual version number */
unsigned long feature_set; /* bitmask of config settings */ unsigned long feature_set; /* bitmask of config settings */
unsigned long entity_count; /* # of monsters and objects */ unsigned long entity_count; /* # of monsters and objects */
#ifndef __EMSCRIPTEN__
unsigned long struct_sizes1; /* size of key structs */ unsigned long struct_sizes1; /* size of key structs */
unsigned long struct_sizes2; /* size of more key structs */ unsigned long struct_sizes2; /* size of more key structs */
#else /* __EMSCRIPTEN__ */
/* 'long' in WASM is 4 bytes, which is too small to hold version numbers
* such as: VERSION_SANITY2 */
unsigned long long struct_sizes1; /* size of key structs */
unsigned long long struct_sizes2; /* size of more key structs */
#endif /* !__EMSCRIPTEN__ */
}; };
struct savefile_info { struct savefile_info {
@@ -396,7 +403,7 @@ struct savefile_info {
/* PANICTRACE: Always defined for NH_DEVEL_STATUS != NH_STATUS_RELEASED /* PANICTRACE: Always defined for NH_DEVEL_STATUS != NH_STATUS_RELEASED
but only for supported platforms. */ but only for supported platforms. */
#ifdef UNIX #if defined(UNIX) && !defined(__EMSCRIPTEN__)
#if (NH_DEVEL_STATUS != NH_STATUS_RELEASED) #if (NH_DEVEL_STATUS != NH_STATUS_RELEASED)
/* see end.c */ /* see end.c */
#if !defined(CROSS_TO_WASM) #if !defined(CROSS_TO_WASM)
@@ -414,7 +421,7 @@ struct savefile_info {
#if defined(MACOSX) #if defined(MACOSX)
#define PANICTRACE_LIBC #define PANICTRACE_LIBC
#endif #endif
#ifdef UNIX #if defined(UNIX) && !defined(__EMSCRIPTEN__) /* no popen in WASM */
#if !defined(CROSS_TO_WASM) /* no popen in WASM */ #if !defined(CROSS_TO_WASM) /* no popen in WASM */
#define PANICTRACE_GDB #define PANICTRACE_GDB
#endif #endif
+18 -18
View File
@@ -1,4 +1,4 @@
/* NetHack 3.7 rip.c $NHDT-Date: 1597967808 2020/08/20 23:56:48 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.33 $ */ /* NetHack 3.7 rip.c $NHDT-Date: 1596498204 2020/08/03 23:43:24 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.32 $ */
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
/*-Copyright (c) Robert Patrick Rankin, 2017. */ /*-Copyright (c) Robert Patrick Rankin, 2017. */
/* NetHack may be freely redistributed. See license for details. */ /* NetHack may be freely redistributed. See license for details. */
@@ -61,10 +61,12 @@ static const char *rip_txt[] = {
}; };
#define STONE_LINE_CENT 19 /* char[] element of center of stone face */ #define STONE_LINE_CENT 19 /* char[] element of center of stone face */
#endif /* NH320_DEDICATION */ #endif /* NH320_DEDICATION */
#define STONE_LINE_LEN 16 /* # chars that fit on one line #define STONE_LINE_LEN \
* (note 1 ' ' border) */ 16 /* # chars that fit on one line \
#define NAME_LINE 6 /* *char[] line # for player name */ * (note 1 ' ' border) \
#define GOLD_LINE 7 /* *char[] line # for amount of gold */ */
#define NAME_LINE 6 /* *char[] line # for player name */
#define GOLD_LINE 7 /* *char[] line # for amount of gold */
#define DEATH_LINE 8 /* *char[] line # for death description */ #define DEATH_LINE 8 /* *char[] line # for death description */
#define YEAR_LINE 12 /* *char[] line # for year */ #define YEAR_LINE 12 /* *char[] line # for year */
@@ -89,9 +91,9 @@ time_t when;
register char **dp; register char **dp;
register char *dpx; register char *dpx;
char buf[BUFSZ]; char buf[BUFSZ];
long year;
register int x; register int x;
int line, year; int line;
long cash;
g.rip = dp = (char **) alloc(sizeof(rip_txt)); g.rip = dp = (char **) alloc(sizeof(rip_txt));
for (x = 0; rip_txt[x]; ++x) for (x = 0; rip_txt[x]; ++x)
@@ -99,15 +101,13 @@ time_t when;
dp[x] = (char *) 0; dp[x] = (char *) 0;
/* Put name on stone */ /* Put name on stone */
Sprintf(buf, "%.*s", (int) STONE_LINE_LEN, g.plname); Sprintf(buf, "%s", g.plname);
buf[STONE_LINE_LEN] = 0;
center(NAME_LINE, buf); center(NAME_LINE, buf);
/* Put $ on stone */ /* Put $ on stone */
cash = max(g.done_money, 0L); Sprintf(buf, "%ld Au", g.done_money);
/* arbitrary upper limit; practical upper limit is quite a bit less */ buf[STONE_LINE_LEN] = 0; /* It could be a *lot* of gold :-) */
if (cash > 999999999L)
cash = 999999999L;
Sprintf(buf, "%ld Au", cash);
center(GOLD_LINE, buf); center(GOLD_LINE, buf);
/* Put together death description */ /* Put together death description */
@@ -115,11 +115,11 @@ time_t when;
/* Put death type on stone */ /* Put death type on stone */
for (line = DEATH_LINE, dpx = buf; line < YEAR_LINE; line++) { for (line = DEATH_LINE, dpx = buf; line < YEAR_LINE; line++) {
register int i, i0;
char tmpchar; char tmpchar;
int i, i0 = (int) strlen(dpx);
if (i0 > STONE_LINE_LEN) { if ((i0 = strlen(dpx)) > STONE_LINE_LEN) {
for (i = STONE_LINE_LEN; (i > 0) && (i0 > STONE_LINE_LEN); --i) for (i = STONE_LINE_LEN; ((i0 > STONE_LINE_LEN) && i); i--)
if (dpx[i] == ' ') if (dpx[i] == ' ')
i0 = i; i0 = i;
if (!i) if (!i)
@@ -136,8 +136,8 @@ time_t when;
} }
/* Put year on stone */ /* Put year on stone */
year = (int) ((yyyymmdd(when) / 10000L) % 10000L); year = yyyymmdd(when) / 10000L;
Sprintf(buf, "%4d", year); Sprintf(buf, "%4ld", year);
center(YEAR_LINE, buf); center(YEAR_LINE, buf);
#ifdef DUMPLOG #ifdef DUMPLOG
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
+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;
}
} }
}); });
}) })