Browse Source

EmscriptenApplication: add function to create new Module instances

pull/480/head
Pablo Escobar 6 years ago
parent
commit
17c18f89db
  1. 43
      doc/platforms-html5.dox
  2. 151
      src/Magnum/Platform/EmscriptenApplication.js
  3. 3
      src/Magnum/Platform/Test/CMakeLists.txt
  4. 35
      src/Magnum/Platform/Test/MultipleEmscriptenApplicationTest.html
  5. 161
      src/Magnum/Platform/WindowlessEmscriptenApplication.js

43
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}
<script src="EmscriptenApplication.js"></script>
<script src="{{ application }}.js"></script>
<script>
createModule().then(function(myModule) {
// application loaded and running
});
const myModule = createMagnumModule({
canvas: document.getElementById('my-canvas'),
status: document.getElementById('my-status'),
statusDescription: document.getElementById('my-status-description')
});
createModule(myModule).then(function(myModule) {
// application loaded and running
});
</script>
@endcode

151
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;

3
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
$<TARGET_FILE_DIR:PlatformMultipleEmscriptenApplicationTest>
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${CMAKE_CURRENT_SOURCE_DIR}/MultipleEmscriptenApplicationTest.html

35
src/Magnum/Platform/Test/MultipleEmscriptenApplicationTest.html

@ -5,6 +5,7 @@
<title>Magnum Multiple EmscriptenApplication Test</title>
<link rel="stylesheet" href="WebApplication.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="EmscriptenApplication.js"></script>
</head>
<body>
<h1>Magnum Multiple EmscriptenApplication Test</h1>
@ -14,16 +15,19 @@
<div class="mn-status" id="status1">Initialization...</div>
<div class="mn-status-description" id="status-description1"></div>
<script src="PlatformMultipleEmscriptenApplicationTest.js"></script>
<script>
/*
This application is compiled with -s MODULARIZE -s EXPORT_NAME=createModule
so it doesn't run until we instantiate it with its own Module object.
We embedded EmscriptenApplication.js into the application .js via --pre-js
so that its code is run before the Emscripten runtime code.
All changes to Module (e.g overriding the canvas element) have to be done
in another --pre-js (MultipleEmscriptenApplicationTestPre.js).
*/
createModule();
<script async="async">
/* This application is compiled with -s MODULARIZE -s EXPORT_NAME=createModule
so it doesn't run until we instantiate it with its own Module object. */
const myModule = createMagnumModule({
canvas: document.getElementById('canvas1'),
status: document.getElementById('status1'),
statusDescription: document.getElementById('status-description1')
});
myModule.keyboardListeningElement = myModule.canvas;
myModule.canvas.addEventListener('mousedown', function(event) {
event.target.focus();
});
createModule(myModule);
</script>
</div></div></div>
</div>
@ -32,14 +36,11 @@
<canvas class="mn-canvas" id="canvas2" tabindex="1"></canvas>
<div class="mn-status" id="status2">Initialization...</div>
<div class="mn-status-description" id="status-description2"></div>
<!--
Create a fresh Module. This application is compiled without -s MODULARIZE
so Module is expected to be global and the application is run instantly.
You'll usually want to use MODULARIZE for all applications but this
makes it easier to reuse the existing PlatformEmscriptenApplicationTest.js.
-->
<script src="EmscriptenApplication.js"></script>
<script>
/* This application is compiled without -s MODULARIZE so it uses the
global Module set by EmscriptenApplication.js and runs instantly.
You'll usually want to use MODULARIZE for all applications but this
makes it easier to reuse the existing PlatformEmscriptenApplicationTest */
Module.canvas = document.getElementById('canvas2');
Module.status = document.getElementById('status2');
Module.statusDescription = document.getElementById('status-description2');

161
src/Magnum/Platform/WindowlessEmscriptenApplication.js

@ -23,75 +23,98 @@
DEALINGS IN THE SOFTWARE.
*/
var Module = typeof Module !== "undefined" ? Module : {};
Module.preRun = [];
Module.postRun = [];
Module.arguments = [];
Module.doNotCaptureKeyboard = true;
Module.printErr = function(message) {
var log = document.getElementById('log');
log.innerHTML += Array.prototype.slice.call(arguments).join(' ')
.replace(/[\"&<>]/g, function (a) {
return { '"': '&quot;', '&': '&amp;', '<': '&lt;', '>': '&gt;' }[a];
}) + '\n';
};
Module.print = function(message) {
var log = document.getElementById('log');
log.innerHTML += Array.prototype.slice.call(arguments).join(' ')
.replace(/[\"&<>]/g, function (a) {
return { '"': '&quot;', '&': '&amp;', '<': '&lt;', '>': '&gt;' }[a];
}) + '\n';
};
/* onAbort not handled here, as the output is printed directly on the page */
Module.canvas = document.getElementById('canvas');
Module.status = document.getElementById('status');
Module.statusDescription = document.getElementById('status-description');
Module.log = document.getElementById('log');
Module.setStatus = function(message) {
if(Module.status) 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('');
Module.log.style.display = 'block';
"use strict";
function createMagnumModule(init) {
const module = Object.assign({}, Module);
Object.assign(module, {
preRun: [],
postRun: [],
arguments: [],
printErr: function(_message) {
if(module.log) {
module.log.innerHTML += Array.prototype.slice.call(arguments).join(' ')
.replace(/[\"&<>]/g, function (a) {
return { '"': '&quot;', '&': '&amp;', '<': '&lt;', '>': '&gt;' }[a];
}) + '\n';
}
},
print: function(_message) {
if(module.log) {
module.log.innerHTML += Array.prototype.slice.call(arguments).join(' ')
.replace(/[\"&<>]/g, function (a) {
return { '"': '&quot;', '&': '&amp;', '<': '&lt;', '>': '&gt;' }[a];
}) + '\n';
}
},
/* onAbort not handled here, as the output is printed directly on the page */
canvas: document.getElementById('canvas'),
status: document.getElementById('status'),
statusDescription: document.getElementById('status-description'),
log: document.getElementById('log'),
setStatus: function(message) {
if(module.status)
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('');
module.log.style.display = 'block';
}
}
});
/* 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]);
Object.assign(module, init);
module.setStatus("Downloading...");
if(module.log)
module.log.style.display = 'none';
return module;
}
Module.setStatus('Downloading...');
Module.log.style.display = 'none';
/* 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;

Loading…
Cancel
Save