From 36c282aaa92bd42d570cdc76fe3b9e76d8da1ff1 Mon Sep 17 00:00:00 2001 From: Ben Harris Date: Mon, 3 Apr 2023 15:16:35 +0100 Subject: [PATCH] js: Load save files into the C side incrementally Before this commit, JavaScript Puzzles loaded a save file by pushing the entire file onto the Emscripten stack and then reading it from there. This worked tolerably for typical save files, but Emscripten's stack defaults to only having 64 kiB of space. That meant that trying to load something that wasn't a real save file tended to cause a stack overflow. I expect that at least some real save files would suffer from the same problem. The stack overflow would generally cause a JavaScript exception and then leave the stack pointer outside the stack, so that any future attempt to call into C would fail as well. To fix this, arrange that the C function for reading data from the save file calls out to JavaScript. The JavaScript can then copy just the requested data into the caller's buffer. We can't pass a JavaScript function pointer to C, but since only one file can be loaded at a time, we can just have a global variable that's the current loading callback. There might still be a problem if you try to load a stupendously large file, since I think FileReader.readAsArrayBuffer() reads the whole file into the browser's RAM. It works on my laptop with files up to a few hundred megabytes, though. --- emcc.c | 23 ++++++++++------------- emcclib.js | 9 +++++++++ emccpre.js | 21 ++++++++++++++++++--- 3 files changed, 37 insertions(+), 16 deletions(-) diff --git a/emcc.c b/emcc.c index 31aedbd..25f5282 100644 --- a/emcc.c +++ b/emcc.c @@ -99,6 +99,8 @@ extern void js_dialog_launch(void); extern void js_dialog_cleanup(void); extern void js_focus_canvas(void); +extern bool js_savefile_read(void *buf, int len); + /* * These functions are called from JavaScript, so their prototypes * need to be kept in sync with emccpre.js. @@ -112,7 +114,7 @@ void timer_callback(double tplus); void command(int n); char *get_save_file(void); void free_save_file(char *buffer); -void load_game(const char *buffer, int len); +void load_game(void); void dlg_return_sval(int index, const char *val); void dlg_return_ival(int index, int val); void resize_puzzle(int w, int h); @@ -904,23 +906,18 @@ struct savefile_read_ctx { static bool savefile_read(void *vctx, void *buf, int len) { - struct savefile_read_ctx *ctx = (struct savefile_read_ctx *)vctx; - if (ctx->len_remaining < len) - return false; - memcpy(buf, ctx->buffer, len); - ctx->len_remaining -= len; - ctx->buffer += len; - return true; + return js_savefile_read(buf, len); } -void load_game(const char *buffer, int len) +void load_game() { - struct savefile_read_ctx ctx; const char *err; - ctx.buffer = buffer; - ctx.len_remaining = len; - err = midend_deserialise(me, savefile_read, &ctx); + /* + * savefile_read_callback in JavaScript was set up by our caller + * as a closure that knows what file we're loading. + */ + err = midend_deserialise(me, savefile_read, NULL); if (err) { js_error_box(err); diff --git a/emcclib.js b/emcclib.js index 07cbf3b..80d17b9 100644 --- a/emcclib.js +++ b/emcclib.js @@ -783,5 +783,14 @@ mergeInto(LibraryManager.library, { */ js_focus_canvas: function() { onscreen_canvas.focus(); + }, + + /* + * bool js_savefile_read(void *buf, int len); + * + * Read len bytes from the save file that we're currently loading. + */ + js_savefile_read: function(buf, len) { + return savefile_read_callback(buf, len); } }); diff --git a/emccpre.js b/emccpre.js index 2637bcb..837f815 100644 --- a/emccpre.js +++ b/emccpre.js @@ -135,6 +135,12 @@ var dlg_return_funcs = null; // pass back the final value in each dialog control. var dlg_return_sval, dlg_return_ival; +// Callback for reading from a savefile. This will be filled in with +// a suitable closure by the JS loading code and called by +// js_savefile_read(). This assumes that only one file can be in the +// process of loading at a time. +var savefile_read_callback; + // The