diff --git a/sys/lib/README.md b/sys/lib/README.md index cbbd87b51..3bc7c283a 100644 --- a/sys/lib/README.md +++ b/sys/lib/README.md @@ -34,12 +34,10 @@ Where is the header file for the API you ask? There isn't one. It's three functi ## API: nethack.js The WebAssembly API has a similar signature to `libnethack.a` with minor syntactic differences: * `main(int argc, char argv[])` - The main function for NetHack -* `shim_graphics_set_callback(shim_callback_t cb)` - The same as above, but the signature of the callback is slightly different because WASM can't handle variadic callbacks. The callback is: `void shim_callback_t(const char *name, void *ret_ptr, const char *fmt, void *args[])` - * `name` - same as above - * `ret_ptr` - same as above - * `fmt` - same as above - * `args` - an array of pointers to the arguments, where each pointer can be de-referenced to a value as specified in the `fmt` string. - +* `shim_graphics_set_callback(char *cbName)` - A `String` representing a name of a callback function. The callback function be registered as `globalThis[cbName] = function yourCallback(name, ... args) { /* your stuff */ }`. Note that [globalThis](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis) points to `window` in browsers and `global` in node.js. + * `name` is the name of the [window function](https://github.com/NetHack/NetHack/blob/NetHack-3.7/doc/window.doc) that needs to be handled + * `... args` is a variable number and type of arguments depending on the `window function` that is being called. The arguments associated with each `name` are described in the [NetHack window.doc](https://github.com/NetHack/NetHack/blob/NetHack-3.7/doc/window.doc) + * The function must return the value expected for the specified `name` ## API Stability @@ -54,7 +52,7 @@ typedef void(*shim_callback_t)(const char *name, void *ret_ptr, const char *fmt, void shim_graphics_set_callback(shim_callback_t cb); void window_cb(const char *name, void *ret_ptr, const char *fmt, ...) { - /* TODO -- see windowCallback below for hints */ + /* TODO */ } int main(int argc, char *argv[]) { @@ -65,117 +63,33 @@ int main(int argc, char *argv[]) { ## nethack.js example ``` js -// Module is defined by emscripten -// https://emscripten.org/docs/api_reference/module.html -let Module = { - // if this is true, main() won't be called automatically - // noInitialRun: true, +const path = require("path"); - // after loading the library, set the callback function - onRuntimeInitialized: function (... args) { - setGraphicsCallback(); - } -}; +// starts nethack +function nethackStart(cb, inputModule = {}) { + // set callback + let cbName = cb.name; + if (cbName === "") cbName = "__anonymousNetHackCallback"; + let userCallback = globalThis[cbName] = cb; -var factory = require("./src/nethack.js"); + // Emscripten Module config + let Module = inputModule; + savedOnRuntimeInitialized = Module.onRuntimeInitialized; + Module.onRuntimeInitialized = function (... args) { + // after the WASM is loaded, add the shim graphics callback function + Module.ccall( + "shim_graphics_set_callback", // C function name + null, // return type + ["string"], // arg types + [cbName], // arg values + {async: true} // options + ); + }; -// run NetHack! -factory(Module); - -// register the callback with the WASM library -function setGraphicsCallback() { - console.log("creating WASM callback function"); - let cb = Module.addFunction(windowCallback, "viiii"); - - console.log("setting callback function with library"); - Module.ccall( - "shim_graphics_set_callback", // C function name - null, // return type - ["number"], // arg types - [cb], // arg values - {async: true} // options - ); - - /* TODO: removeFunction */ + // load and run the module + var factory = require(path.join(__dirname, "../build/nethack.js")); + factory(Module); } -// this is the "shim graphics" callback function -// it gets called every time something needs to be displayed -// or input needs to be gathered from the user -function windowCallback(name, retPtr, fmt, args) { - name = Module.UTF8ToString(name); - fmt = Module.UTF8ToString(fmt); - let argTypes = fmt.split(""); - let retType = argTypes.shift(); - - // convert arguments to JavaScript types - let jsArgs = []; - for (let i = 0; i < argTypes.length; i++) { - let ptr = args + (4*i); - let val = typeLookup(argTypes[i], ptr); - jsArgs.push(val); - } - console.log(`graphics callback: ${name} [${jsArgs}]`); - /********** - * YOU HAVE TO IMPLEMENT THIS FUNCTION to render things - **********/ - let ret = yourFunctionToRenderGraphics(name, jsArgs); - setReturn(retPtr, retType, ret); -} - -// takes a character `type` and a WASM pointer and returns a JavaScript value -function typeLookup(type, ptr) { - switch(type) { - case "s": // string - return Module.UTF8ToString(Module.getValue(ptr, "*")); - case "p": // pointer - return Module.getValue(Module.getValue(ptr, "*"), "*"); - case "c": // char - return String.fromCharCode(Module.getValue(Module.getValue(ptr, "*"), "i8")); - case "0": /* 2^0 = 1 byte */ - return Module.getValue(Module.getValue(ptr, "*"), "i8"); - case "1": /* 2^1 = 2 bytes */ - return Module.getValue(Module.getValue(ptr, "*"), "i16"); - case "2": /* 2^2 = 4 bytes */ - case "i": // integer - case "n": // number - return Module.getValue(Module.getValue(ptr, "*"), "i32"); - case "f": // float - return Module.getValue(Module.getValue(ptr, "*"), "float"); - case "d": // double - return Module.getValue(Module.getValue(ptr, "*"), "double"); - default: - throw new TypeError ("unknown type:" + type); - } -} - -// takes a a WASM pointer, a charater `type` and a value and sets the value at pointer -function setReturn(ptr, type, value = 0) { - switch (type) { - case "p": - throw new Error("not implemented"); - case "s": - value=value?value:"(no value)"; - var strPtr = Module.getValue(ptr, "i32"); - Module.stringToUTF8(value, strPtr, 1024); - break; - case "i": - Module.setValue(ptr, value, "i32"); - break; - case "c": - Module.setValue(ptr, value, "i8"); - break; - case "f": - // XXX: I'm not sure why 'double' works and 'float' doesn't - Module.setValue(ptr, value, "double"); - break; - case "d": - Module.setValue(ptr, value, "double"); - break; - case "v": - break; - default: - throw new Error("unknown type"); - } -} +nethackStart(yourCallbackFunction); ``` \ No newline at end of file diff --git a/sys/lib/hints/wasm b/sys/lib/hints/wasm index 711017bae..7f85090af 100644 --- a/sys/lib/hints/wasm +++ b/sys/lib/hints/wasm @@ -15,21 +15,30 @@ EMRANLIB=emranlib EMCC_LFLAGS=-s SINGLE_FILE=1 EMCC_LFLAGS=-s WASM=1 EMCC_LFLAGS+=-s ALLOW_TABLE_GROWTH -EMCC_LFLAGS+=-s ASYNCIFY -s ASYNCIFY_IMPORTS='["_nhmain"]' -O3 +EMCC_LFLAGS+=-s ASYNCIFY -s ASYNCIFY_IMPORTS='["local_callback"]' +EMCC_LFLAGS+=-O3 EMCC_LFLAGS+=-s MODULARIZE EMCC_LFLAGS+=-s EXPORTED_FUNCTIONS='["_main", "_shim_graphics_set_callback"]' EMCC_LFLAGS+=-s EXPORTED_RUNTIME_METHODS='["cwrap", "ccall", "addFunction", "removeFunction", "UTF8ToString", "getValue", "setValue"]' EMCC_LFLAGS+=-s ERROR_ON_UNDEFINED_SYMBOLS=0 EMCC_LFLAGS+=--embed-file wasm-data@/ +# For a list of EMCC settings: +# https://github.com/emscripten-core/emscripten/blob/master/src/settings.js + # WASM C flags EMCC_CFLAGS= EMCC_CFLAGS+=-Wall EMCC_CFLAGS+=-Werror +#EMCC_CFLAGS+=-s DISABLE_EXCEPTION_CATCHING=0 EMCC_DEBUG_CFLAGS+=-s ASSERTIONS=1 +#EMCC_DEBUG_CFLAGS+=-s ASSERTIONS=2 EMCC_DEBUG_CFLAGS+=-s STACK_OVERFLOW_CHECK=2 EMCC_DEBUG_CFLAGS+=-s SAFE_HEAP=1 -EMCC_DEBUG_CFLAGS+=-s LLD_REPORT_UNDEFINED +EMCC_DEBUG_CFLAGS+=-s LLD_REPORT_UNDEFINED=1 +#EMCC_DEBUG_CFLAGS+=-s EXCEPTION_DEBUG=1 +#EMCC_DEBUG_CFLAGS+=-fsanitize=undefined -fsanitize=address -fsanitize=leak +#EMCC_DEBUG_CFLAGS+=-s EXIT_RUNTIME EMCC_PROD_CFLAGS+=-O3 # Nethack C flags diff --git a/sys/lib/npm-package/README.md b/sys/lib/npm-package/README.md index e71f3e5a1..e39db8292 100644 --- a/sys/lib/npm-package/README.md +++ b/sys/lib/npm-package/README.md @@ -11,7 +11,7 @@ npm install nethack ## API The main module returns a setup function: `startNethack(uiCallback, moduleOptions)`. -* `uiCallback(name, ... args)` - Your callback function that will handle rendering NetHack on the screen of your choice. The `name` argument is one of the UI functions of the [NetHack Window Interface](https://github.com/NetHack/NetHack/blob/NetHack-3.7/doc/window.doc) and the `args` are corresponding to the window interface function that is being called. You are required to return the correct type of data for the function that is implemented. +* `uiCallback(name, ... args)` - Your callback function that will handle rendering NetHack on the screen of your choice. The `name` argument is one of the UI functions of the [NetHack Window Interface](https://github.com/NetHack/NetHack/blob/NetHack-3.7/doc/window.doc) and the `args` are corresponding to the window interface function that is being called. You are required to return the correct type of data for the function that is implemented. The `uiCallback` may be an `async` function. * `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). @@ -22,7 +22,7 @@ let nethackStart = require("nethack"); nethackStart(doGraphics); let winCount = 0; -function doGraphics(name, ... args) { +async function doGraphics(name, ... args) { console.log(`shim graphics: ${name} [${args}]`); switch(name) { diff --git a/sys/lib/npm-package/src/nethackShim.js b/sys/lib/npm-package/src/nethackShim.js index 638460278..e7a802c30 100644 --- a/sys/lib/npm-package/src/nethackShim.js +++ b/sys/lib/npm-package/src/nethackShim.js @@ -3,27 +3,38 @@ const path = require("path"); let Module; let userCallback; let savedOnRuntimeInitialized; + +// starts nethack function nethackStart(cb, inputModule = {}) { - if(typeof cb !== "function") throw new TypeError("expected first argument to be callback function"); + if(typeof cb !== "string" && typeof cb !== "function") throw new TypeError("expected first argument to be 'Function' or 'String' representing global callback function name"); if(typeof inputModule !== "object") throw new TypeError("expected second argument to be object"); + let cbName; + if(typeof cb === "function") { + cbName = cb.name; + if (cbName === "") cbName = "__anonymousNetHackCallback"; + if (globalThis[cbName] === undefined) globalThis[cbName] = cb; + else if (globalThis[cbName] !== cb) throw new Error (`'globalThis["${cbName}"]' is not the same as specified callback`); + } + + /* global globalThis */ + userCallback = globalThis[cbName]; + if(typeof userCallback !== "function") throw new TypeError(`expected 'globalThis["${cbName}"]' to be a function`); + // if(userCallback.constructor.name !== "AsyncFunction") throw new TypeError(`expected 'globalThis["${cbName}"]' to be an async function`); + // Emscripten Module config Module = inputModule; - userCallback = cb; savedOnRuntimeInitialized = Module.onRuntimeInitialized; Module.onRuntimeInitialized = function (... args) { // after the WASM is loaded, add the shim graphics callback function - let cb = Module.addFunction(windowCallback, "viiii"); Module.ccall( "shim_graphics_set_callback", // C function name null, // return type - ["number"], // arg types - [cb], // arg values + ["string"], // arg types + [cbName], // arg values {async: true} // options ); - /* TODO: Module.removeFunction() */ - // if the user had their own onRuntimeInitialized(), call it now if (savedOnRuntimeInitialized) savedOnRuntimeInitialized(... args); }; @@ -33,94 +44,6 @@ function nethackStart(cb, inputModule = {}) { factory(Module); } -function windowCallback(name, retPtr, fmt, args) { - name = Module.UTF8ToString(name); - fmt = Module.UTF8ToString(fmt); - 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 = typeLookup(argTypes[i], ptr); - jsArgs.push(val); - } - let retVal = userCallback(name, ... jsArgs); - setReturn(name, retPtr, retType, retVal); -} - -function typeLookup(type, ptr) { - switch(type) { - case "s": // string - return Module.UTF8ToString(Module.getValue(ptr, "*")); - case "p": // pointer - ptr = Module.getValue(ptr, "*"); - if(!ptr) return 0; // null pointer - return Module.getValue(ptr, "*"); - case "c": // char - return String.fromCharCode(Module.getValue(Module.getValue(ptr, "*"), "i8")); - case "0": /* 2^0 = 1 byte */ - return Module.getValue(Module.getValue(ptr, "*"), "i8"); - case "1": /* 2^1 = 2 bytes */ - return Module.getValue(Module.getValue(ptr, "*"), "i16"); - case "2": /* 2^2 = 4 bytes */ - case "i": // integer - case "n": // number - return Module.getValue(Module.getValue(ptr, "*"), "i32"); - case "f": // float - return Module.getValue(Module.getValue(ptr, "*"), "float"); - case "d": // double - return Module.getValue(Module.getValue(ptr, "*"), "double"); - default: - throw new TypeError ("unknown type:" + type); - } -} - -function setReturn(name, ptr, type, value = 0) { - - switch (type) { - case "p": - 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"); - } - - function isFloat(n){ - return n === +n && n !== (n|0) && !Number.isInteger(n); - } -} - // TODO: ES6 'import' style module module.exports = nethackStart; diff --git a/sys/lib/test/libtest.c b/sys/lib/test/libtest.c index 9b554b986..31ca10e71 100644 --- a/sys/lib/test/libtest.c +++ b/sys/lib/test/libtest.c @@ -1,28 +1,37 @@ #include +#include /* external functions */ int nhmain(int argc, char *argv[]); typedef void(*stub_callback_t)(const char *name, void *ret_ptr, const char *fmt, ...); -void stub_graphics_set_callback(stub_callback_t cb); +void shim_graphics_set_callback(stub_callback_t cb); /* forward declarations */ void window_cb(const char *name, void *ret_ptr, const char *fmt, ...); - +void *yourFunctionToRenderGraphics(const char *name, va_list args); int main(int argc, char *argv[]) { - stub_graphics_set_callback(window_cb); + shim_graphics_set_callback(window_cb); nhmain(argc, argv); } -void window_cb(const char *name, void *ret_ptr, const char *fmt, ...) { - /* TODO -- see windowCallback below for hints */ - +void *yourFunctionToRenderGraphics(const char *name, va_list args) { + printf("yourFunctionToRenderGraphics name %s\n", name); /* DO SOMETHING HERE */ - *ret_ptr = yourFunctionToRenderGraphics(name, va_list args); + return NULL; } +void window_cb(const char *name, void *ret_ptr, const char *fmt, ...) { + void *ret; + va_list args; + /* TODO -- see windowCallback below for hints */ + va_start(args, fmt); + ret = yourFunctionToRenderGraphics(name, args); + // *((int *)ret_ptr = *((int *)ret); // e.g. yourFunctionToRenderGraphics returns an int + va_end(args); +} #if 0 function variadicCallback(name, retPtr, fmt, args) { diff --git a/win/shim/winshim.c b/win/shim/winshim.c index 05ed09c07..eec616f4f 100644 --- a/win/shim/winshim.c +++ b/win/shim/winshim.c @@ -5,6 +5,7 @@ /* not an actual windowing port, but a fake win port for libnethack */ #include "hack.h" +#include #ifdef SHIM_GRAPHICS #include @@ -15,26 +16,30 @@ #undef SHIM_DEBUG -#ifndef __EMSCRIPTEN__ -typedef void(*shim_callback_t)(const char *name, void *ret_ptr, const char *fmt, ...); -#else /* __EMSCRIPTEN__ */ -/* WASM can't handle a variadic callback, so we pass back an array of pointers instead... */ -typedef void(*shim_callback_t)(const char *name, void *ret_ptr, const char *fmt, void *args[]); -#endif /* !__EMSCRIPTEN__ */ +#ifdef SHIM_DEBUG +#define debugf printf +#else /* !SHIM_DEBUG */ +#define debugf(...) +#endif /* SHIM_DEBUG */ -/* this is the primary interface to shim graphics, + +/* 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 */ -static shim_callback_t shim_graphics_callback = NULL; #ifdef __EMSCRIPTEN__ - EMSCRIPTEN_KEEPALIVE -#endif -void shim_graphics_set_callback(shim_callback_t cb) { - shim_graphics_callback = cb; +/************ + * WASM interface + ************/ +EMSCRIPTEN_KEEPALIVE +static char *shim_callback_name = NULL; +void shim_graphics_set_callback(char *cbName) { + if (shim_callback_name != NULL) free(shim_callback_name); + shim_callback_name = strdup(cbName); + /* 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); -#ifdef __EMSCRIPTEN__ /* A2P = Argument to Pointer */ #define A2P & /* P2V = Pointer to Void */ @@ -44,8 +49,8 @@ ret_type name fn_args { \ void *args[] = { __VA_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, args); \ + if (!shim_callback_name) return ret; \ + local_callback(shim_callback_name, #name, (void *)&ret, fmt, args); \ return ret; \ } @@ -53,10 +58,21 @@ ret_type name fn_args { \ void name fn_args { \ void *args[] = { __VA_ARGS__ }; \ debugf("SHIM GRAPHICS: " #name "\n"); \ - if (!shim_graphics_callback) return; \ - shim_graphics_callback(#name, NULL, fmt, args); \ + if (!shim_callback_name) return; \ + local_callback(shim_callback_name, #name, NULL, fmt, args); \ } + #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) { + shim_graphics_callback = cb; +} + #define A2P #define P2V #define DECLCB(ret_type, name, fn_args, fmt, ...) \ @@ -64,7 +80,7 @@ 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__); \ + shim_graphics_callback(#name, (void *)&ret, fmt, ## __VA_ARGS__); \ return ret; \ } @@ -72,17 +88,10 @@ ret_type 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__); \ + shim_graphics_callback(#name, NULL, fmt, ## __VA_ARGS__); \ } #endif /* __EMSCRIPTEN__ */ -#ifdef SHIM_DEBUG -#define debugf printf -#else /* !SHIM_DEBUG */ -#define debugf(...) -#endif /* SHIM_DEBUG */ - - enum win_types { WINSHIM_MESSAGE = 1, WINSHIM_MAP, @@ -222,4 +231,122 @@ struct window_procs shim_procs = { genl_can_suspend_yes, }; +#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(async () => { + // convert callback arguments to proper JavaScript varaidic arguments + let name = Module.UTF8ToString(shim_name); + let fmt = Module.UTF8ToString(fmt_str); + let cbName = Module.UTF8ToString(cb_name); + // console.log("local_callback:", cbName, fmt, 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 = typeLookup(argTypes[i], ptr); + jsArgs.push(val); + } + + // do the callback + let userCallback = globalThis[cbName]; + let retVal = await runJsLoop(() => userCallback(name, ... jsArgs)); + + // save the return value + setReturn(name, ret_ptr, retType, retVal); + + // convert 'ptr' to the type indicated by 'type' + function typeLookup(type, ptr) { + switch(type) { + case "s": // string + return Module.UTF8ToString(Module.getValue(ptr, "*")); + case "p": // pointer + ptr = Module.getValue(ptr, "*"); + if(!ptr) return 0; // null pointer + return Module.getValue(ptr, "*"); + case "c": // char + return String.fromCharCode(Module.getValue(Module.getValue(ptr, "*"), "i8")); + case "0": /* 2^0 = 1 byte */ + return Module.getValue(Module.getValue(ptr, "*"), "i8"); + case "1": /* 2^1 = 2 bytes */ + return Module.getValue(Module.getValue(ptr, "*"), "i16"); + case "2": /* 2^2 = 4 bytes */ + case "i": // integer + case "n": // number + return Module.getValue(Module.getValue(ptr, "*"), "i32"); + case "f": // float + return Module.getValue(Module.getValue(ptr, "*"), "float"); + case "d": // double + return Module.getValue(Module.getValue(ptr, "*"), "double"); + default: + throw new TypeError ("unknown type:" + type); + } + } + + // setTimeout() with value of '0' is similar to setImmediate() (which 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; + // 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 + async function runJsLoop(cb) { + return new Promise((resolve) => { + setTimeout(() => { + resolve(cb()); + }, 0); + }); + } + + // sets the return value of the function to the type expected + function setReturn(name, ptr, type, value = 0) { + switch (type) { + case "p": + 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"); + } + + function isFloat(n){ + return n === +n && n !== (n|0) && !Number.isInteger(n); + } + } + }); +}) +#endif /* __EMSCRIPTEN__ */ + #endif /* SHIM_GRAPHICS */ \ No newline at end of file