## 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
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[]) {
## 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,
-
- // after loading the library, set the callback function
- onRuntimeInitialized: function (... args) {
- setGraphicsCallback();
- }
-};
-
-var factory = require("./src/nethack.js");
-
-// 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 */
-}
-
-// 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);
+const path = require("path");
+
+// starts nethack
+function nethackStart(cb, inputModule = {}) {
+ // set callback
+ let cbName = cb.name;
+ if (cbName === "") cbName = "__anonymousNetHackCallback";
+ let userCallback = globalThis[cbName] = cb;
+
+ // 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
+ );
+ };
+
+ // load and run the module
+ var factory = require(path.join(__dirname, "../build/nethack.js"));
+ factory(Module);
}
-// 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
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
## 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).
nethackStart(doGraphics);
let winCount = 0;
-function doGraphics(name, ... args) {
+async function doGraphics(name, ... args) {
console.log(`shim graphics: ${name} [${args}]`);
switch(name) {
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);
};
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;
#include <stdio.h>
+#include <stdarg.h>
/* 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) {
/* not an actual windowing port, but a fake win port for libnethack */
#include "hack.h"
+#include <string.h>
#ifdef SHIM_GRAPHICS
#include <stdarg.h>
#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 */
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; \
}
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, ...) \
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; \
}
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,
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