-
Notifications
You must be signed in to change notification settings - Fork 450
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
EmscriptenApplication: custom canvas id, multiple applications on one page #480
EmscriptenApplication: custom canvas id, multiple applications on one page #480
Conversation
Codecov Report
@@ Coverage Diff @@
## master #480 +/- ##
=======================================
Coverage 77.14% 77.14%
=======================================
Files 415 415
Lines 25490 25490
=======================================
Hits 19665 19665
Misses 5825 5825 Continue to review full report at Codecov.
|
Thank you! :)
That's fine, there wasn't any practical use case for a constexpr Configuration anyway. I'll probably experiment myself with replacing
I'd say it gets added once someone has a use case, don't bother with that now.
There's a test for EmscriptenApplication inside the
Yeah this should go into the HTML (and keep the default in the JS), but I wonder what happens to the global |
👍
For multiple canvases you'll need MODULARIZE=1 (together with EXPORT_NAME) which to me seems like it should be the default anyway. It lets you pass a local object with options (what's now the global const Module = { /* same old code as in EmscriptenApplication.js */ };
createModule(Module).then(function(myModule) {
// success, application about to run
// here, myModule == Module
}); |
Got this properly working as far as I can tell now. There was a bug that returned the element ID instead of a CSS selector, as needed by Emscripten. The reason this has worked so far is that it simply found the first One thing I'd still like to fix to make things work with multiple |
Thanks, this looks great so far.
Wow. I had to double-check the docs, and I guess I know where my confusion came from. It's clarified in the text below, but the snippet there is misleading: EMSCRIPTEN_RESULT emscripten_set_some_callback(
const char *target, // ID of the target HTML element. <---- WRONG, it's a CSS selector, not an ID
... This looks like an obvious fix that doesn't need further discussion, so I commited it right away as c760acb.
Hahah. Yeah, this is fine :)
Yeah, sorry -- at this point it probably makes sense to switch the CSS to use classes instead of IDs, so such patching isn't needed. The same for the
The And then, looking into my docs, there's
I'd flip this behavior upside down -- introducing Hm, now I realized there are two different ways / use cases and I'm not sure which one is better:
|
Your questions just made me go and check and I realized that we DO have access to With MODULARIZE, you can do something like the following: const Module1 = {
canvas: ...,
keyboardListeningElement: ...,
...
};
createModule(Module1).then(function(myModule) {
// application 1
});
const Module2 = {
canvas: ...
};
// note: same application being instantiated, could also be another one
createModule(Module2).then(function(myModule) {
// application 2
}); From C++ code calling JS, But really, the _canvasTarget = EM_ASM({Module['canvas'];}); // pseudo code :v I'll play around with |
Oh, nice. Yeah, I gave it a bit more thought and if we can stay with I'm still not exactly sure why Emscripten is gradually moving away from / deprecating these -- is there something inherently wrong with this approach that we overlooked? |
I still need to figure out how to not hardcode a global Module, heh.
IDs restrict the usability to a single canvas on the page. For backwards compatibility purposes those are still kept, but the use is discouraged now.
…ass(). Instead look for a closest parent element of our <canvas> having the .mn-container class. This should thus work for more than one canvas on a page.
Can confirm this worked before and works now as well.
I just opened #481 that updates the CSS to avoid copying/patching it for a different canvas ID. It also contains various other changes that should hopefully help this PR, but there are still a few unresolved issues related to the global Module and the JS driver file. Ideally if you could base this PR off that one and further modify things if needed. |
Just pushed the progress so far, I'll figure out how to merge your PR and changes on master later this week. I reverted the Still not sure if having access to I changed the tests to have both variants of |
…operly account for -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR Module['canvas'] can be read even from code compiled with -s MODULARIZE so it's a better option than hardcoding it in Configuration. The target strings in Emscripten depend on whether we're compiled with DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR (see emscripten-core/emscripten#7977). This is now detected and handled at runtime to prepend element IDs with # and use the correct window and document magic targets.
This requires all but one of the applications to be compiled with -s MODULARIZE.
…ng Module['canvas']
This prevents deleting elements in Module that were set by Emscripten preamble code when using EmscriptenApplication.js with --pre-js
8c25357
to
cc1f400
Compare
Alright, I'm all done here I think. Rebased on top of #481. Multiple applications on one page work properly, including per-canvas events, cursors and redraws as far as I could test. All (🤠) that's left is some documentation. I had to change var Module=typeof createModule!=="undefined"?createModule:{};
var readyPromiseResolve,readyPromiseReject;
Module["ready"]=new Promise(function(resolve,reject){readyPromiseResolve=resolve;readyPromiseReject=reject});
null;
// EmscriptenApplication.js content
var Module={
// ...
}; The custom canvas test is gone since that's being tested in PlatformMultipleEmscriptenApplicationTest anyway. |
Added a quick, uncompiled attempt at documentation, let me know if anything is unclear or missing. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, the docs are great.
Apart from two minor things I commented on below (which I can do post-merge myself as well, no problem), I got an idea that the EmscriptenApplication.js
could be rewritten in a way that (hopefully) wouldn't require any --pre-js
tricks (and thus eliminating the last remaining case where a binary is tied to a particular markup). This expands on my question (and probably answers your comment) on #481 (comment) -- instead of defining a global Module
, it would define a function returning its contents and implicitly / for backwards compatibility putting that to a global Module
:
function createMagnumModule(init) {
var module = {
/* Take the Emscripten-supplied Module object as a base */
...(typeof Module !== "undefined" ? Module : {}),
/* Update it with our things */
...{
preRun: [],
postRun: [],
arguments: [],
// ...
},
/* Let the user-supplied object overwrite all the above */
...(typeof init !== "undefined" ? init : {})
};
/* Parse URL arguments */
var args = decodeURIComponent(window.location.search.substr(1)).trim().split('&');
// ...
/* We can do this here because at this point `module.status` should be correct */
module.setStatus("Downloading...");
return module;
}
/* Default global Module object */
var Module = createMagnumModule();
(Pardon my JavaScript, I hope the spread operator thing is supported well-enough and that I'm using it as it's supposed to be used, heh. Maybe for sanity and compatibility it should be replaced with a bunch of loops iterating over the properties.)
With the above, I think for subsequent <canvas>
es all you'd need to do would be something like this:
<script>
var OtherModule = createMagnumModule({
canvas: document.getElementById('canvas1'),
status: document.getElementById('status1'),
statusDescription: document.getElementById('status-description1')
});
createModule(OtherModule);
</script>
Thus no --pre-js
(which would basically hardcode the canvas IDs into the binary again) needed, and also this would mean the contents of EmscriptenApplication.js
aren't duplicated twice, saving some precious bytes.
Thoughts? :) I hope I'm not too wrong here.
EDIT: am I correct that it should be possible to use the second compiled binary in the markup twice, for two separate canvases? Or is that not how this all works? heh
_deprecatedTargetBehavior = checkForDeprecatedEmscriptenTargetBehavior(); | ||
if(_deprecatedTargetBehavior) { | ||
Debug{verbose} << "Platform::EmscriptenApplication::tryCreate(): using old Emscripten target behavior"; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is great 👍
</script> | ||
<script async="async" src="PlatformEmscriptenApplicationTest.js"></script> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The order seems more logical this way, yup, but -- did the original order cause any issue? I don't remember encountering any myself, so I'd like to know what could have happened :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No issues but it looked like a race condition to me. async
runs the script as soon as it's downloaded; with caching or fast execution of the Emscripten code the constructor could, in theory, be run before the <script>
. In general, if you want ordered script execution, you should either use the default or defer
(which runs everything at the end, but in order).
https://www.growingwiththeweb.com/2014/02/async-vs-defer-attributes.html
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, I have to fix this everywhere then :)
…they're not defined These were only added with 1.38.27
I went ahead and implemented that. The spread operator in object literals is experimental so I didn't use that. What I also did is copy-pasta'd the exports section from Emscripten's .js output. This allows using A note on
You can reuse the same Emscripten binary multiple times on different canvases no problem, as long as you use |
8b4f266
to
66c9746
Compare
Squashed the commits a bit and merged as 66c9746...cc23823, thanks a lot! I rewrote the docs a bit and completely removed the |
Quick draft for making the canvas CSS selector configurable.
I've tested it with the triangle example:
-s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR
. Technically default for newer Emscripten versions but required for 1.39.4 or older.TriangleExample
withConfiguration{}.setCanvasTarget("#canvas2")
id="canvas2"
in the .htmlModule.canvas = document.getElementById('canvas2')
. Could also modify EmscriptenApplication.js directly. This variable isn't necessary withDISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR
anymore, so apart from setting the canvas opacity after a crash it doesn't do anything.Few things worth noting:
DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR
If this looks OK to you I'll add missing documentation and also a few words to https://doc.magnum.graphics/magnum/platforms-html5.html