From 17c18f89dbf02b97b864e032a8eef40146412dbe Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Mon, 9 Nov 2020 00:28:52 +0100 Subject: [PATCH] EmscriptenApplication: add function to create new Module instances --- doc/platforms-html5.dox | 43 +++-- src/Magnum/Platform/EmscriptenApplication.js | 151 +++++++++------- src/Magnum/Platform/Test/CMakeLists.txt | 3 +- .../MultipleEmscriptenApplicationTest.html | 35 ++-- .../WindowlessEmscriptenApplication.js | 161 ++++++++++-------- 5 files changed, 224 insertions(+), 169 deletions(-) diff --git a/doc/platforms-html5.dox b/doc/platforms-html5.dox index 8eaceb3a9..920dff145 100644 --- a/doc/platforms-html5.dox +++ b/doc/platforms-html5.dox @@ -302,32 +302,41 @@ endif() underlying emulation libraries and may vary across Emscripten versions. Running multiple graphical applications on a single page is possible, but -requires a few changes. By default, Emscripten and `EmscriptenApplication.js` -populate a global @cb{.js} Module @ce object with things like callbacks and the -canvas element to render into. This is not suitable when using more than one -application. Emscripten's `-s MODULARIZE` option creates a script -that defines a function with a local @cb{.js} Module @ce object instead. -This way each application runs inside its own function with a separate -environment. - -To let `EmscriptenApplication.js` access the local @cb{.js} Module @ce, -it must be prepended to the application with the `--pre-js` compiler option, -at which point you also don't have to bundle it separately anymore: +requires a few changes. By default, Emscripten populates a global @cb{.js} Module @ce +object with things like callbacks and the canvas element to render into. This is not +suitable when using more than one application. Emscripten's `MODULARIZE` option +creates a script that defines a function with a local @cb{.js} Module @ce object +instead. This way each application runs inside its own function with a separate +environment: @code{.cmake} target_link_libraries(my-application PRIVATE "-s MODULARIZE -s EXPORT_NAME=createModule") -target_link_libraries(my-application PRIVATE "--pre-js ${MAGNUM_EMSCRIPTENAPPLICATION_JS}") @endcode -Instead of running instantly, the application must be instantiated -using the function name set with `EXPORT_NAME`: +Instead of running instantly, the application can then be instantiated +using the function name set with `EXPORT_NAME`. The function optionally +takes an object to populate the local @cb{.js} Module @ce. + +While `EmscriptenApplication.js` populates the @cb{.js} Module @ce object by default, +it also defines a function @cb{.js} createMagnumModule @ce that lets you create +new module objects to pass to the Emscripten module function. This allows you +to include `EmscriptenApplication.js` only once per page without having to bundle it with +Emscripten's `--pre-js` option. To override the default values, pass an object to +@cb{.js} createMagnumModule @ce. This is the easiest way to set the canvas or +status elements of each application: @code{.html-jinja} + @endcode diff --git a/src/Magnum/Platform/EmscriptenApplication.js b/src/Magnum/Platform/EmscriptenApplication.js index ebd627568..48ce2f79d 100644 --- a/src/Magnum/Platform/EmscriptenApplication.js +++ b/src/Magnum/Platform/EmscriptenApplication.js @@ -25,70 +25,93 @@ "use strict"; /* it summons the Cthulhu in a proper way, they say */ -var Module = typeof Module !== "undefined" ? Module : {}; - -Module.preRun = []; -Module.postRun = []; - -Module.arguments = []; - -Module.printErr = function(_message) { - console.error(Array.prototype.slice.call(arguments).join(' ')); -}; - -Module.print = function(_message) { - console.log(Array.prototype.slice.call(arguments).join(' ')); -}; - -Module.onAbort = function() { - Module.canvas.style.opacity = 0.333; - Module.canvas.style.zIndex = -1; - Module.setStatus("Oops :("); - Module.setStatusDescription("The app crashed. Refresh the page or check the browser console for details."); -}; - -Module.canvas = document.getElementById('canvas'); -Module.status = document.getElementById('status'); -Module.statusDescription = document.getElementById('status-description'); - -Module.setStatus = function(message) { - /* Emscripten calls setStatus("") after a timeout even if the app - aborts. That would erase the crash message, so don't allow that */ - if(Module.status && Module.status.innerHTML != "Oops :(") - Module.status.innerHTML = message; -}; - -Module.setStatusDescription = function(message) { - if(Module.statusDescription) - Module.statusDescription.innerHTML = message; -}; - -Module.totalDependencies = 0; - -Module.monitorRunDependencies = function(left) { - this.totalDependencies = Math.max(this.totalDependencies, left); - - if(left) { - Module.setStatus('Downloading...'); - Module.setStatusDescription((this.totalDependencies - left) + ' / ' + this.totalDependencies); - } else { - Module.setStatus('Download complete'); - Module.setStatusDescription(''); +function createMagnumModule(init) { + /* Take the Emscripten-supplied Module object as a base */ + const module = Object.assign({}, Module); + + /* Update it with our things */ + Object.assign(module, { + preRun: [], + postRun: [], + + arguments: [], + + printErr: function(_message) { + console.error(Array.prototype.slice.call(arguments).join(' ')); + }, + + print: function(_message) { + console.log(Array.prototype.slice.call(arguments).join(' ')); + }, + + onAbort: function() { + module.canvas.style.opacity = 0.333; + module.canvas.style.zIndex = -1; + module.setStatus("Oops :("); + module.setStatusDescription("The app crashed. Refresh the page or check the browser console for details."); + }, + + canvas: document.getElementById('canvas'), + status: document.getElementById('status'), + statusDescription: document.getElementById('status-description'), + + setStatus: function(message) { + /* Emscripten calls setStatus("") after a timeout even if the app + aborts. That would erase the crash message, so don't allow that */ + if(module.status && module.status.innerHTML != "Oops :(") + module.status.innerHTML = message; + }, + + setStatusDescription: function(message) { + if(module.statusDescription) + module.statusDescription.innerHTML = message; + }, + + totalDependencies: 0, + + monitorRunDependencies: function(left) { + this.totalDependencies = Math.max(this.totalDependencies, left); + + if(left) { + module.setStatus('Downloading...'); + module.setStatusDescription((this.totalDependencies - left) + ' / ' + this.totalDependencies); + } else { + module.setStatus('Download complete'); + module.setStatusDescription(''); + } + } + }); + + /* Parse arguments, e.g. /app/?foo=bar&fizz&buzz=3 goes to the app as + ['--foo', 'bar', '--fizz', '--buzz', '3'] */ + const args = decodeURIComponent(window.location.search.substr(1)).trim().split('&'); + for(let i = 0; i != args.length; ++i) { + let j = args[i].indexOf('='); + /* Key + value */ + if(j != -1) { + module.arguments.push('--' + args[i].substring(0, j)); + module.arguments.push(args[i].substring(j + 1)); + + /* Just key */ + } else module.arguments.push('--' + args[i]); } -}; - -/* Parse arguments, e.g. /app/?foo=bar&fizz&buzz=3 goes to the app as - ['--foo', 'bar', '--fizz', '--buzz', '3'] */ -var args = decodeURIComponent(window.location.search.substr(1)).trim().split('&'); -for(var i = 0; i != args.length; ++i) { - var j = args[i].indexOf('='); - /* Key + value */ - if(j != -1) { - Module.arguments.push('--' + args[i].substring(0, j)); - Module.arguments.push(args[i].substring(j + 1)); - - /* Just key */ - } else Module.arguments.push('--' + args[i]); + + /* Let the user-supplied object overwrite all the above */ + Object.assign(module, init); + + /* We can do this here because at this point `module.status` should be correct */ + module.setStatus("Downloading..."); + + return module; } -Module.setStatus('Downloading...'); +/* Default global Module object */ +var Module = createMagnumModule(); + +/* UMD export */ +if(typeof exports === 'object' && typeof module === 'object') /* CommonJS/Node */ + module.exports = createMagnumModule; +else if(typeof define === 'function' && define['amd']) /* AMD */ + define([], function() { return createMagnumModule; }); +else if(typeof exports === 'object') /* CommonJS strict */ + exports["createMagnumModule"] = createMagnumModule; diff --git a/src/Magnum/Platform/Test/CMakeLists.txt b/src/Magnum/Platform/Test/CMakeLists.txt index ce365eb00..74bc57f2f 100644 --- a/src/Magnum/Platform/Test/CMakeLists.txt +++ b/src/Magnum/Platform/Test/CMakeLists.txt @@ -60,11 +60,10 @@ if(WITH_EMSCRIPTENAPPLICATION) target_compile_definitions(PlatformMultipleEmscriptenApplicationTest PRIVATE CUSTOM_CLEAR_COLOR=0.2,0,0.2,1) target_link_libraries(PlatformMultipleEmscriptenApplicationTest PRIVATE "-s MODULARIZE -s EXPORT_NAME=createModule") target_link_libraries(PlatformMultipleEmscriptenApplicationTest PRIVATE "-s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1") - target_link_libraries(PlatformMultipleEmscriptenApplicationTest PRIVATE "--pre-js ${CMAKE_CURRENT_SOURCE_DIR}/../EmscriptenApplication.js") - target_link_libraries(PlatformMultipleEmscriptenApplicationTest PRIVATE "--pre-js ${CMAKE_CURRENT_SOURCE_DIR}/MultipleEmscriptenApplicationTestPre.js") add_custom_command(TARGET PlatformMultipleEmscriptenApplicationTest POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/../WebApplication.css + ${CMAKE_CURRENT_SOURCE_DIR}/../EmscriptenApplication.js $ COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/MultipleEmscriptenApplicationTest.html diff --git a/src/Magnum/Platform/Test/MultipleEmscriptenApplicationTest.html b/src/Magnum/Platform/Test/MultipleEmscriptenApplicationTest.html index d226ab9ae..4edb0b263 100644 --- a/src/Magnum/Platform/Test/MultipleEmscriptenApplicationTest.html +++ b/src/Magnum/Platform/Test/MultipleEmscriptenApplicationTest.html @@ -5,6 +5,7 @@ Magnum Multiple EmscriptenApplication Test +

Magnum Multiple EmscriptenApplication Test

@@ -14,16 +15,19 @@
Initialization...
- @@ -32,14 +36,11 @@
Initialization...
- -