ref: 1bf591a5735068d1853be13c5a4255962835d5fe
parent: b31ea221673a8f49a383a4a119d6edaabd39642d
author: Simon Tatham <anakin@pobox.com>
date: Tue Sep 5 16:10:16 EDT 2017
Support for saving games in Javascript puzzles. This is done by getting midend_serialise to produce the complete saved-game file as an in-memory string buffer, and then encoding that into a data: URI which we provide to the user as a hyperlink in a dialog box. The hyperlink has the 'download' attribute, which means clicking on it should automatically offer to save the file, and also lets me specify a not-too-silly default file name.
--- a/emcc.c
+++ b/emcc.c
@@ -757,6 +757,49 @@
}
/* ----------------------------------------------------------------------
+ * Called from JS to prepare a save-game file, and free one after it's
+ * been used.
+ */
+
+struct savefile_write_ctx {
+ char *buffer;
+ size_t pos;
+};
+
+static void savefile_write(void *wctx, void *buf, int len)
+{
+ struct savefile_write_ctx *ctx = (struct savefile_write_ctx *)wctx;
+ if (ctx->buffer)
+ memcpy(ctx->buffer + ctx->pos, buf, len);
+ ctx->pos += len;
+}
+
+char *get_save_file(void)
+{
+ struct savefile_write_ctx ctx;
+ size_t size;
+
+ /* First pass, to count up the size */
+ ctx.buffer = NULL;
+ ctx.pos = 0;
+ midend_serialise(me, savefile_write, &ctx);
+ size = ctx.pos;
+
+ /* Second pass, to actually write out the data */
+ ctx.buffer = snewn(size, char);
+ ctx.pos = 0;
+ midend_serialise(me, savefile_write, &ctx);
+ assert(ctx.pos == size);
+
+ return ctx.buffer;
+}
+
+void free_save_file(char *buffer)
+{
+ sfree(buffer);
+}
+
+/* ----------------------------------------------------------------------
* Setup function called at page load time. It's called main() because
* that's the most convenient thing in Emscripten, but it's not main()
* in the usual sense of bounding the program's entire execution.
--- a/emccpre.js
+++ b/emccpre.js
@@ -296,6 +296,32 @@
command(9);
};
+ // 'number' is used for C pointers
+ get_save_file = Module.cwrap('get_save_file', 'number', []);
+ free_save_file = Module.cwrap('free_save_file', 'void', ['number']);
+
+ document.getElementById("save").onclick = function(event) {
+ if (dlg_dimmer === null) {
+ var savefile_ptr = get_save_file();
+ var savefile_text = Pointer_stringify(savefile_ptr);
+ free_save_file(savefile_ptr);
+ dialog_init("Download saved-game file");
+ dlg_form.appendChild(document.createTextNode(
+ "Click to download the "));
+ var a = document.createElement("a");
+ a.download = "puzzle.sav";
+ a.href = "data:application/octet-stream," +
+ encodeURIComponent(savefile_text);
+ a.appendChild(document.createTextNode("saved-game file"));
+ dlg_form.appendChild(a);
+ dlg_form.appendChild(document.createTextNode("."));
+ dlg_form.appendChild(document.createElement("br"));
+ dialog_launch(function(event) {
+ dialog_cleanup();
+ });
+ }
+ };
+
gametypelist = document.getElementById("gametype");
gametypesubmenus.push(gametypelist);
--- a/emccx.json
+++ b/emccx.json
@@ -18,6 +18,9 @@
'_timer_callback',
// Callback from button presses in the UI outside the canvas
'_command',
+ // Game-saving functions
+ '_get_save_file',
+ '_free_save_file',
// Callbacks to return values from dialog boxes
'_dlg_return_sval',
'_dlg_return_ival',
--- a/html/jspage.pl
+++ b/html/jspage.pl
@@ -209,6 +209,7 @@
><li id="new">New game</li
><li id="specific">Enter game ID</li
><li id="random">Enter random seed</li
+><li id="save">Download save file</li
></ul></li
><li>Type...<ul id="gametype"></ul></li
><li class="separator"></li