mirror of
git://git.tartarus.org/simon/puzzles.git
synced 2025-04-21 16:05:44 -07:00
Support user preferences in the Emscripten frontend.
Here, user preferences are stored in localStorage, so that they can persist when you come back to the same puzzle page later. localStorage is global across a whole web server, which means we need to take care to put our uses of it in a namespace reasonably unlikely to collide with unrelated web pages on the same server. Ben suggested that a good way to do this would be to store things in localStorage under keys derived from location.pathname. In this case I've appended a fragment id "#preferences" to that, so that space alongside it remains for storing other things we might want in future (such as serialised saved-game files used as quick-save slots). When loading preferences, I've chosen to pass the whole serialised preferences buffer from Javascript to C as a single C string argument to a callback, rather than reusing the existing system for C to read the save file a chunk at a time. Partly that's because preferences data is bounded in size whereas saved games can keep growing; also it's because the way I'm storing preferences data means it will be a UTF-8 string, and I didn't fancy trying to figure out byte offsets in the data on the JS side. I think at this point I should stop keeping a list in the docs of which frontends support preferences. Most of the in-tree ones do now, and that means the remaining interesting frontends are ones I don't have a full list of. At this moment I guess no out-of-tree frontends support preferences (unless someone is _very_ quick off the mark), but as and when that changes, I won't necessarily know, and don't want to have to keep updating the docs when I find out.
This commit is contained in:
@ -47,6 +47,8 @@ set(emcc_export_list
|
||||
_restore_puzzle_size
|
||||
# Callback when device pixel ratio changes
|
||||
_rescale_puzzle
|
||||
# Callback for loading user preferences
|
||||
_prefs_load_callback
|
||||
# Main program, run at initialisation time
|
||||
_main)
|
||||
|
||||
|
84
emcc.c
84
emcc.c
@ -101,6 +101,9 @@ extern void js_focus_canvas(void);
|
||||
|
||||
extern bool js_savefile_read(void *buf, int len);
|
||||
|
||||
extern void js_save_prefs(const char *);
|
||||
extern void js_load_prefs(midend *);
|
||||
|
||||
/*
|
||||
* These functions are called from JavaScript, so their prototypes
|
||||
* need to be kept in sync with emccpre.js.
|
||||
@ -121,6 +124,11 @@ void resize_puzzle(int w, int h);
|
||||
void restore_puzzle_size(int w, int h);
|
||||
void rescale_puzzle(void);
|
||||
|
||||
/*
|
||||
* Internal forward references.
|
||||
*/
|
||||
static void save_prefs(midend *me);
|
||||
|
||||
/*
|
||||
* Call JS to get the date, and use that to initialise our random
|
||||
* number generator to invent the first game seed.
|
||||
@ -743,10 +751,20 @@ static void cfg_end(bool use_results)
|
||||
* open for the user to adjust them and try again.
|
||||
*/
|
||||
js_error_box(err);
|
||||
} else if (cfg_which == CFG_PREFS) {
|
||||
/*
|
||||
* Acceptable settings for user preferences: enact them
|
||||
* without blowing away the current game.
|
||||
*/
|
||||
resize();
|
||||
midend_redraw(me);
|
||||
free_cfg(cfg);
|
||||
js_dialog_cleanup();
|
||||
save_prefs(me);
|
||||
} else {
|
||||
/*
|
||||
* New settings are fine; start a new game and close the
|
||||
* dialog.
|
||||
* Acceptable settings for the remaining configuration
|
||||
* types: start a new game and close the dialog.
|
||||
*/
|
||||
select_appropriate_preset();
|
||||
midend_new_game(me);
|
||||
@ -849,6 +867,9 @@ void command(int n)
|
||||
post_move();
|
||||
js_focus_canvas();
|
||||
break;
|
||||
case 10: /* user preferences */
|
||||
cfg_start(CFG_PREFS);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -925,6 +946,64 @@ void load_game(void)
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------
|
||||
* Functions to load and save preferences, calling out to JS to access
|
||||
* the appropriate localStorage slot.
|
||||
*/
|
||||
|
||||
static void save_prefs(midend *me)
|
||||
{
|
||||
struct savefile_write_ctx ctx;
|
||||
size_t size;
|
||||
|
||||
/* First pass, to count up the size */
|
||||
ctx.buffer = NULL;
|
||||
ctx.pos = 0;
|
||||
midend_save_prefs(me, savefile_write, &ctx);
|
||||
size = ctx.pos;
|
||||
|
||||
/* Second pass, to actually write out the data. As with
|
||||
* get_save_file, we append a terminating \0. */
|
||||
ctx.buffer = snewn(size+1, char);
|
||||
ctx.pos = 0;
|
||||
midend_save_prefs(me, savefile_write, &ctx);
|
||||
assert(ctx.pos == size);
|
||||
ctx.buffer[ctx.pos] = '\0';
|
||||
|
||||
js_save_prefs(ctx.buffer);
|
||||
|
||||
sfree(ctx.buffer);
|
||||
}
|
||||
|
||||
struct prefs_read_ctx {
|
||||
const char *buffer;
|
||||
size_t pos, len;
|
||||
};
|
||||
|
||||
static bool prefs_read(void *vctx, void *buf, int len)
|
||||
{
|
||||
struct prefs_read_ctx *ctx = (struct prefs_read_ctx *)vctx;
|
||||
|
||||
if (len < 0)
|
||||
return false;
|
||||
if (ctx->len - ctx->pos < len)
|
||||
return false;
|
||||
memcpy(buf, ctx->buffer + ctx->pos, len);
|
||||
ctx->pos += len;
|
||||
return true;
|
||||
}
|
||||
|
||||
void prefs_load_callback(midend *me, const char *prefs)
|
||||
{
|
||||
struct prefs_read_ctx ctx;
|
||||
|
||||
ctx.buffer = prefs;
|
||||
ctx.len = strlen(prefs);
|
||||
ctx.pos = 0;
|
||||
|
||||
midend_load_prefs(me, prefs_read, &ctx);
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------
|
||||
* 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()
|
||||
@ -948,6 +1027,7 @@ int main(int argc, char **argv)
|
||||
* Instantiate a midend.
|
||||
*/
|
||||
me = midend_new(NULL, &thegame, &js_drawing, NULL);
|
||||
js_load_prefs(me);
|
||||
|
||||
/*
|
||||
* Chuck in the HTML fragment ID if we have one (trimming the
|
||||
|
23
emcclib.js
23
emcclib.js
@ -792,5 +792,28 @@ mergeInto(LibraryManager.library, {
|
||||
*/
|
||||
js_savefile_read: function(buf, len) {
|
||||
return savefile_read_callback(buf, len);
|
||||
},
|
||||
|
||||
/*
|
||||
* void js_save_prefs(const char *);
|
||||
*
|
||||
* Write a buffer of serialised preferences data into localStorage.
|
||||
*/
|
||||
js_save_prefs: function(buf) {
|
||||
var prefsdata = UTF8ToString(buf);
|
||||
localStorage.setItem(location.pathname + "#preferences", prefsdata);
|
||||
},
|
||||
|
||||
/*
|
||||
* void js_load_prefs(midend *);
|
||||
*
|
||||
* Retrieve preferences data from localStorage. If there is any,
|
||||
* pass it back in as a string, via prefs_load_callback.
|
||||
*/
|
||||
js_load_prefs: function(me) {
|
||||
var prefsdata = localStorage.getItem(location.pathname+"#preferences");
|
||||
if (prefsdata !== undefined && prefsdata !== null) {
|
||||
prefs_load_callback(me, prefsdata);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
12
emccpre.js
12
emccpre.js
@ -141,6 +141,11 @@ var dlg_return_sval, dlg_return_ival;
|
||||
// process of loading at a time.
|
||||
var savefile_read_callback;
|
||||
|
||||
// void prefs_load_callback(midend *me, const char *prefs);
|
||||
//
|
||||
// Callback for passing in preferences data retrieved from localStorage.
|
||||
var prefs_load_callback;
|
||||
|
||||
// The <ul> object implementing the game-type drop-down, and a list of
|
||||
// the sub-lists inside it. Used by js_add_preset().
|
||||
var gametypelist = document.getElementById("gametype");
|
||||
@ -161,6 +166,7 @@ var permalink_desc = document.getElementById("permalink-desc");
|
||||
// The various buttons. Undo and redo are used by js_enable_undo_redo().
|
||||
var specific_button = document.getElementById("specific");
|
||||
var random_button = document.getElementById("random");
|
||||
var prefs_button = document.getElementById("prefs");
|
||||
var new_button = document.getElementById("new");
|
||||
var restart_button = document.getElementById("restart");
|
||||
var undo_button = document.getElementById("undo");
|
||||
@ -425,6 +431,10 @@ function initPuzzle() {
|
||||
if (dlg_dimmer === null)
|
||||
command(9);
|
||||
};
|
||||
if (prefs_button) prefs_button.onclick = function(event) {
|
||||
if (dlg_dimmer === null)
|
||||
command(10);
|
||||
};
|
||||
|
||||
// 'number' is used for C pointers
|
||||
var get_save_file = Module.cwrap('get_save_file', 'number', []);
|
||||
@ -682,6 +692,8 @@ function initPuzzle() {
|
||||
dlg_return_ival = Module.cwrap('dlg_return_ival', 'void',
|
||||
['number','number']);
|
||||
timer_callback = Module.cwrap('timer_callback', 'void', ['number']);
|
||||
prefs_load_callback = Module.cwrap('prefs_load_callback', 'void',
|
||||
['number','string']);
|
||||
|
||||
if (resizable_div !== null) {
|
||||
var resize_handle = document.getElementById("resizehandle");
|
||||
|
@ -340,6 +340,7 @@ ${unfinishedpara}
|
||||
<li><button type="button" id="random">Enter random seed...</button></li>
|
||||
<li><button type="button" id="save">Download save file...</button></li>
|
||||
<li><button type="button" id="load">Upload save file...</button></li>
|
||||
<li><button type="button" id="prefs">Preferences...</button></li>
|
||||
</ul></div></li>
|
||||
<li><div tabindex="0">Type<ul role="menu" id="gametype"></ul></div></li>
|
||||
<li role="separator"></li>
|
||||
|
@ -179,10 +179,10 @@ solving it yourself after seeing the answer, you can just press Undo.
|
||||
|
||||
\dt \i\e{Preferences}
|
||||
|
||||
\dd Where supported (currently on Windows, Unix and MacOS), brings up
|
||||
a dialog allowing you to configure personal preferences about a
|
||||
particular game. Some of these preferences will be specific to a
|
||||
particular game; others will be common to all games.
|
||||
\dd Where supported, brings up a dialog allowing you to configure
|
||||
personal preferences about a particular game. Some of these
|
||||
preferences will be specific to a particular game; others will be
|
||||
common to all games.
|
||||
|
||||
\lcont{
|
||||
|
||||
|
Reference in New Issue
Block a user