learn-uxn
the learn-uxn project is a project for taking the uxn emulator and assembler and making them work in the browser. specifically, the project involves using emscripten to translate the C-based uxntal assembler and uxn rom emulator from the uxn repo into javascript and web assembly code that can run easily in the browser.
additionally, the site uses codemirror 6 and the lezer parser to add an editor complete with a parser and syntax highlighter for the uxntal language. from the main page, you can load uxntal source code (left side), and assemble it into uxn roms, and run them in the emulator in the top right. logs and debugging information are printed in the bottom right.
permacomputing
ironically, the point of the uxn emulator and the varvara computing system is to encourage and explore ideas around permacomputing, which is about as far as you can get from trying to port things to the web browser. however, in an effort to both understand uxn and uxntal more generally, and have an environment where you can test out some changes, it might be a useful tool to those learning about the system. my hope is that this tool, while not permacomputing in itself, will be useful to others who are interested in the project, and hopefully encourage more people to consider why permacomputing is interesting and important.
implementation
the emscripten based port of the C-based tools works by taking the C source code and generating the javascript and web assembly modules that can run in the browser. the C-based tools rely primarily on the Simple DirectMedia Layer (SDL2) package for their operation, which covers things like the ability to create a mouse device/cursor, graphical rendering, as well as audio. thankfully, the emscripten community has already created an SDL2 port that can be easily used.
there are two separate programs tha are compiled via a call to the emscripten compiler
with a number of options that make the code amenable to being used inthe browser. various
flags need to be added to the emcc
call in order for it to work:
EMCC_DEBUG=1 emcc \
-s WASM=1 \
-s ASSERTIONS=1 \
-s ENVIRONMENT=web \
-s ASYNCIFY \
-s USE_SDL=2 \
-s USE_SDL_MIXER=2 \
-s FORCE_FILESYSTEM=1 \
-s EXPORTED_FUNCTIONS='["_main"]' \
-s EXPORTED_RUNTIME_METHODS='["callMain", "FS"]' \
-s NO_EXIT_RUNTIME=1 \
--shell-file=shell-uxnemu.html \
--extern-pre-js=pre-uxnemu.js \
-O3 \
-o site/uxnemu.html \
uxn/src/uxn-fast.c \
uxn/src/devices/ppu.c \
uxn/src/devices/apu.c \
uxn/src/uxnemu.c
EMCC_DEBUG
envrionment variable lets you see debug information while compilingWASM=1
- compile to a web assembly moduleASSERTIONS=1
- allows emscripten to be stricter and tell you if something is wrongENVIRONMENT=web
- this makes it so we target the web (browser) instead of node.jsUSE_SDL=2
- this includes the aforemtnioned SDL2 portUSE_SDL_MIXER=2
- this adds support for SDL2 based audioFORCE_FILESYSTEM=1
- add support (and force) the usage of a virtual filesystemEXPORTED_FUNCTIONS
- these are_
prefixed functions defined in the C code that we want to make sure have javascript entrypoints created for themEXPORTED_RUNTIME_METHODS
- these are methods that emscripten defines that we will want to use in the browser - they’re like utilitiesNO_EXIT_RUNTIME
- I don’t think this was actually needed but I added it anyway-O3
- this is the level of optimization
the shell file and extern-pre-js are basically just the template file that will
be created and the javascript that will be injected to the final output (the
html file). In order to properly shutdown and restart the whole process, the
html file that is created in the very end is wrapped in an iframe
so that we
can trigger a reload of the whole iframe, by default, if you you don’t add the
MODULARIZE option, all the javascript stuff will be more or less in the global
(window) scope in a variable called Module
. this can make it hard to have
multiple emscripten projects running simultaneously. the modularize option
looked promising but ended up causing some issues for the workflow, and the
iframe approach seemed simpler, so i just went with that.
for each iframe, there is a content window in the object that has access to that
tool’s Module
. with that reference, we can operate on the virtual file system
provided by emscripten. so in the case of our application, we write uxntal source
code to a file on the uxn assembler’s virtual file system, then read the rom back from that,
and write the rom to the uxn emulator’s virtual file system. there is a little trickery to make
sure that the file exists after reloading the iframe but before the main function is called.
other than that though, the majority of the operation is pretty straight forward and linear.
in order to pass data from the assembler to the emualtor, we simply stash the b64 encoded contents of the rom on the global window object of that iframe, so that the start up function can check if it’s present and create a virtual file before calling the main function: this operation can be seen here.
synchronicity
for the SDL based implementation, there is a call to SDL_Delay
that keeps the framerate correct. however, in the javascript/webassembly version, we don’t need this, but instead, need the emscripten equivalent. currently this is handled by some hacky code due to the way that the source programs are structured:
if ! grep -q 'emscripten_sleep' uxn/src/uxnemu.c; then
sed -i -e '1s/^/#include <emscripten.h>\n/;/SDL_Delay/s/^/emscripten_sleep(10);\n/' uxn/src/uxnemu.c
fi
shout out to @alderwick@merveilles.town for helping me come up with this hack/solution to modifying the uxn source without having to do any refactoring! this is the only change overall to the uxn source code that is needed to make this whole project work.
referenced by: characters