shithub: puzzles

Download patch

ref: 9d7b044c01680e408094d3bae82f622ae8a5b48b
parent: 420663d47790a7e34a1662d679a0c00efdb5b7e5
author: Ben Harris <bjh21@bjh21.me.uk>
date: Fri Dec 9 08:56:12 EST 2022

js: Simpler and more robust startup procedure

Previously, we initialised all of the JavaScript event handlers as soon
at the DOM was loaded, and then called main() ourselves once the
Emscripten runtime was ready.  This was slightly dangerous since it
depended on none of those event handlers' being called before main().
In practice this was difficult because most of the elements the event
handlers were attached to were invisible, but it did limit what event
handlers could safely be used.

Now, the event handlers are initialised from main().  This makes things
work in a sufficiently conventional way that we can just let the
Emscripten run-time call main() in its usual way, rather than involving
ourselves in the minutiae of Emscripten's startup.

--- a/cmake/platforms/emscripten.cmake
+++ b/cmake/platforms/emscripten.cmake
@@ -42,7 +42,7 @@
 set(CMAKE_C_LINK_FLAGS "\
 -s ALLOW_MEMORY_GROWTH=1 \
 -s EXPORTED_FUNCTIONS='[${emcc_export_string}]' \
--s EXTRA_EXPORTED_RUNTIME_METHODS='[cwrap,callMain]' \
+-s EXTRA_EXPORTED_RUNTIME_METHODS='[cwrap]' \
 -s STRICT_JS=1")
 if(WASM)
   set(CMAKE_C_LINK_FLAGS "${CMAKE_C_LINK_FLAGS} -s WASM=1")
@@ -63,7 +63,6 @@
 function(set_platform_puzzle_target_properties NAME TARGET)
   em_link_pre_js(${TARGET} ${CMAKE_SOURCE_DIR}/emccpre.js)
   em_link_js_library(${TARGET} ${CMAKE_SOURCE_DIR}/emcclib.js)
-  em_link_post_js(${TARGET} ${CMAKE_SOURCE_DIR}/emccpost.js)
 endfunction()
 
 function(build_platform_extras)
--- a/emcc.c
+++ b/emcc.c
@@ -43,6 +43,8 @@
 /*
  * Extern references to Javascript functions provided in emcclib.js.
  */
+extern void js_init_puzzle(void);
+extern void js_post_init(void);
 extern void js_debug(const char *);
 extern void js_error_box(const char *message);
 extern void js_remove_type_dropdown(void);
@@ -937,6 +939,11 @@
     int i;
 
     /*
+     * Initialise JavaScript event handlers.
+     */
+    js_init_puzzle();
+
+    /*
      * Instantiate a midend.
      */
     me = midend_new(NULL, &thegame, &js_drawing, NULL);
@@ -1039,6 +1046,11 @@
      */
     if (param_err)
         js_error_box(param_err);
+
+    /*
+     * Reveal the puzzle!
+     */
+    js_post_init();
 
     /*
      * Done. Return to JS, and await callbacks!
--- a/emcclib.js
+++ b/emcclib.js
@@ -17,6 +17,23 @@
 
 mergeInto(LibraryManager.library, {
     /*
+     * void js_init_puzzle(void);
+     *
+     * Called at the start of main() to set up event handlers.
+     */
+    js_init_puzzle: function() {
+        initPuzzle();
+    },
+    /*
+     * void js_post_init(void);
+     *
+     * Called at the end of main() once the initial puzzle has been
+     * started.
+     */
+    js_post_init: function() {
+        post_init();
+    },
+    /*
      * void js_debug(const char *message);
      *
      * A function to write a diagnostic to the Javascript console.
--- a/emccpost.js
+++ /dev/null
@@ -1,1 +1,0 @@
-initPuzzle();
--- a/emccpre.js
+++ b/emccpre.js
@@ -31,12 +31,9 @@
 var update_xmin, update_xmax, update_ymin, update_ymax;
 
 // Module object for Emscripten. We fill in these parameters to ensure
-// that Module.run() won't be called until we're ready (we want to do
-// our own init stuff first), and that when main() returns nothing
-// will get cleaned up so we remain able to call the puzzle's various
-// callbacks.
+// that when main() returns nothing will get cleaned up so we remain
+// able to call the puzzle's various callbacks.
 //
-//
 // Page loading order:
 //
 // 1. The browser starts reading *.html (which comes from jspage.pl)
@@ -49,39 +46,45 @@
 //
 // 3. The HTML finishes loading.  The browser is about to fire the
 //    `DOMContentLoaded` event (ie `onload`) but before that, it
-//    actually runs the deferred JS.  THis consists of
+//    actually runs the deferred JS.  This consists of
 //
 //    (i) emccpre.js (this file).  This sets up various JS variables
-//      including the emscripten Module object.
+//      including the emscripten Module object, which includes the
+//      environment variables and argv seen by main().
 //
 //    (ii) emscripten's JS.  This starts the WASM loading.
 //
-//    (iii) emccpost.js.  This calls initPuzzle, which is defined here
-//      in this file.  initPuzzle:
+// When this JS execution is complete, the browser fires the `onload`
+// event.  This is ignored.  It continues loading the WASM.
 //
+// 4. The WASM loading and initialisation completes.  Emscripten's
+//    runtime calls the C `main` to actually start the puzzle.  It
+//    then calls initPuzzle, which:
+//
 //      (a) finds various DOM elements and bind them to variables,
-//      which depend on the HTML having loaded (it has).
+//      which depends on the HTML having loaded (it has).
 //
 //      (b) makes various `cwrap` calls into the emscripten module to
 //      set up hooks; this depends on the emscripten JS having been
 //      loaded (it has).
-//
-//      (c) Makes the call to emscripten's
-//      Module.onRuntimeInitialized, which sets the callback for when
-//      the WASM has finished loading and initialising.  This has to
-//      come before the WASM finishes loading, or we'll miss the
-//      callback.  We are executing synchronously here in the same JS
-//      file as started the WASM loading, so that is guaranteed.
-//
-// When this JS execution is complete, the browser fires the `onload`
-// event.  This is ignored.  It continues loading the WASM.
-//
-// 4. The WASM loading and initialisation completes.  The
-//    onRuntimeInitialised callback calls into emscripten-generated
-//    WASM to call the C `main`, to actually start the puzzle.
 
 var Module = {
-    'noInitialRun': true,
+    'preRun': function() {
+        // Merge environment variables from HTML script element.
+        // This means you can add something like this to the HTML:
+        // <script id="environment" type="application/json">
+        //   { "LOOPY_DEFAULT": "20x10t11dh" }
+        // </script>
+        var envscript = document.getElementById("environment");
+        var k, v;
+        if (envscript !== null)
+            for ([k, v] of
+                 Object.entries(JSON.parse(envscript.textContent)))
+                ENV[k] = v;
+    },
+    // Pass argv[1] as the fragment identifier (so that permalinks of
+    // the form puzzle.html#game-id can launch the specified id).
+    'arguments': [decodeURIComponent(location.hash)],
     'noExitRuntime': true
 };
 
@@ -261,7 +264,7 @@
     onscreen_canvas.focus();
 }
 
-// Init function called from body.onload.
+// Init function called early in main().
 function initPuzzle() {
     // Construct the off-screen canvas used for double buffering.
     onscreen_canvas = document.getElementById("puzzlecanvas");
@@ -686,22 +689,7 @@
         });
     }
 
-    /*
-     * Arrange to detect changes of device pixel ratio.  Adapted from
-     * <https://developer.mozilla.org/en-US/docs/Web/API/Window/
-     * devicePixelRatio> (CC0) to work on older browsers.
-     */
     var rescale_puzzle = Module.cwrap('rescale_puzzle', 'void', []);
-    var mql = null;
-    var update_pixel_ratio = function() {
-        var dpr = window.devicePixelRatio;
-        if (mql !== null)
-            mql.removeListener(update_pixel_ratio);
-        mql = window.matchMedia(`(resolution: ${dpr}dppx)`);
-        mql.addListener(update_pixel_ratio);
-        rescale_puzzle();
-    }
-
     /*
      * If the puzzle is sized to fit the page, try to detect changes
      * of size of the containing element.  Ideally this would use a
@@ -720,36 +708,34 @@
         window.addEventListener("load", resize_handler);
     }
 
-    Module.preRun = function() {
-        // Merge environment variables from HTML script element.
-        // This means you can add something like this to the HTML:
-        // <script id="environment" type="application/json">
-        //   { "LOOPY_DEFAULT": "20x10t11dh" }
-        // </script>
-        var envscript = document.getElementById("environment");
-        var k, v;
-        if (envscript !== null)
-            for ([k, v] of
-                 Object.entries(JSON.parse(envscript.textContent)))
-                ENV[k] = v;
-    };
+}
 
-    Module.onRuntimeInitialized = function() {
-        // Run the C setup function, passing argv[1] as the fragment
-        // identifier (so that permalinks of the form puzzle.html#game-id
-        // can launch the specified id).
-        Module.callMain([decodeURIComponent(location.hash)]);
+function post_init() {
+    /*
+     * Arrange to detect changes of device pixel ratio.  Adapted from
+     * <https://developer.mozilla.org/en-US/docs/Web/API/Window/
+     * devicePixelRatio> (CC0) to work on older browsers.
+     */
+    var rescale_puzzle = Module.cwrap('rescale_puzzle', 'void', []);
+    var mql = null;
+    var update_pixel_ratio = function() {
+        var dpr = window.devicePixelRatio;
+        if (mql !== null)
+            mql.removeListener(update_pixel_ratio);
+        mql = window.matchMedia(`(resolution: ${dpr}dppx)`);
+        mql.addListener(update_pixel_ratio);
+        rescale_puzzle();
+    }
 
-        update_pixel_ratio();
-        // And if we get here with everything having gone smoothly, i.e.
-        // we haven't crashed for one reason or another during setup, then
-        // it's probably safe to hide the 'sorry, no puzzle here' div and
-        // show the div containing the actual puzzle.
-        var apology = document.getElementById("apology");
-        if (apology !== null) apology.style.display = "none";
-        document.getElementById("puzzle").style.display = "";
+    update_pixel_ratio();
+    // If we get here with everything having gone smoothly, i.e.
+    // we haven't crashed for one reason or another during setup, then
+    // it's probably safe to hide the 'sorry, no puzzle here' div and
+    // show the div containing the actual puzzle.
+    var apology = document.getElementById("apology");
+    if (apology !== null) apology.style.display = "none";
+    document.getElementById("puzzle").style.display = "";
 
-        // Default to giving keyboard focus to the puzzle.
-        onscreen_canvas.focus();
-    };
+    // Default to giving keyboard focus to the puzzle.
+    onscreen_canvas.focus();
 }