Modify <build>.framework.js before compression

Hi,
to be able to directly call _emscripten_set_main_loop_timing (and have direct control over frame rate decimation), I currently modify the .framework.js build output file storing a reference to that method on Module (Module[“function_name”] = _emscripten…) which can be accessed through the unityInstance.
While writing a build postprocessor to automate that step I realized I can’t just modify the Brotli-compressed variant of the framework code like the uncompressed js file.

Is there some way to modify the framework code before it’s compressed (or even “globally” so every build has that change in the framework file by default)?

Bump.

I may have found a solution, it works with the SimpleWebServer used by Unity (\Data\PlaybackEngines\WebGLSupport\BuildTools\SimpleWebServer.exe) at least.

I modify the framework file from an uncompressed build and then manually compress it using the brotli.exe bundled with the editor (
\Data\PlaybackEngines\WebGLSupport\BuildTools\Brotli\win_x86_64\brotli.exe) like so:
brotli --input <build name>.framework.js --output <build name>.framework.js.br
…and then use the output file with the other files from a compressed build.

Note that manually compressing an unmodified framework file doesn’t generate the exact same output as a build with Brotli compression enabled (file size is slightly different and the “proper” one contains “UnityWeb Compressed Content (brotli)” at the start of the file which the manually compressed one doesn’t). That doesn’t seem to break anything however.
I will report back if I find a problem in the production environment.

I think this method is under the Browser object in the unity instance module.
So you can create a .jspre file and in it you can get the browser object and add the method to the unityInstance module.

Well that’s a whole lot easier, thank you!

Turns out with a .jspre file (for the uninitiated: put it under Plugins/WebGL/ same as .jslib files; the contents get added into the framework.js(.br) file at the top of the function unityFramework), I can just directly put the
Module["function_name"] = _emscripten_set_main_loop_timing
line in there and that’s it.

Why are .jspre files such arcane knowledge and not documented?
I reported “missing information” on the Interaction with browser scripting documentation page to hopefully make this easier to find out about in the future (right now, the official documentation just states that the file type can be imported).

2 Likes

I recommend avoiding calling emscripten_set_main_loop_timing on your own, the runtime is not developed from a perspective that it would be called separately, so results might be unexpected.

Maybe if there is a limitation in the existing frame rate control mechanisms, we might take in a bug report, or see about a feature request, if an important use case is missed?

Nevertheless, if you do decide to go down the route of calling emscripten_set_main_loop_timing() on your own, then you should be able to unbrotli the .br file, then do the edits, and then re-brotli the modified .br file to restore a compressed version of your modifications.

However, if you do that, then you will need to host the content on a web server that properly serves the Content-Encoding: br header, so that it gets properly decompressed by the browser while it is streaming in the brotli compressed file.

I recall we manually also pass an argument “–comment UnityWeb Compressed Content (brotli)” to the brotli compression step. That might explain the small discrepancy in size.

@jukka_j huh, that’s interesting. Can you elaborate on potential unexpected results? So far I haven’t noticed anything wrong, but I only superficially tested on three devices.

What lead me to calling emscripten_set_main_loop_timing manually is what I explained in this thread (the next post there also explains when I call emscripten_set_main_loop_timing): reducing framerate further than QualitySettings.vSyncCount or Application.targetFrameRate allow (e.g. for 30 fps on a 240 Hz screen, vSyncCount would need to be 8 (but is restricted to values {0, 1, 2, 3, 4}) / targetFrameRate 7.5 (needs to be an int). So both can’t currently work.
Should I report this as a bug?

As I wrote before with the secret .jspre magic I can modify the framework file as needed before compression, so no need for manual (un-)brotli-ing.

@jukka_j huh, that’s interesting. Can you elaborate on potential unexpected results? So far I haven’t noticed anything wrong, but I only superficially tested on three devices.

When we author Unity’s main loop code, we may occassionally refactor the access patterns that Unity calls into emscripten_set_main_loop_timing(). (e.g. the last time a change to that code happened was when WebGPU support was added). It is not possible to anticipate for unknown external calls to this function, so when we refactor code, it may result in some incompatibility arising.

If manually calling emscripten_set_main_loop_timing() works out in your local testing, then it will be safe to use for that particular Unity version, just bear in mind that in some future version the way Unity accesses setting up the main loop might change, so things might be disrupted there.

vSyncCount being capped to 4 seems a bit arbitrary. That would be good to report, the use case of running 30fps on a 240 Hz screen does sound like a perfectly valid one.

Needing targetFrameRate=7.5 sounds a bit odd. Do you have an use case that wouldn’t be able to make do with targetFrameRate=7 or targetFrameRate=8?

Okay, I’m aware that this “solution” is brittle and might not be compatible with other versions, I can live with that.

Regarding the “targetFrameRate = 7.5” thing: mathematically, that would be the value to get from 240 down to 30 fps (since 240 / 8 = 30 and with the assumed frame rate of 60: 60 / 8 = 7.5). I would prefer not to use 7 or 8 instead, as I’ve gathered from for example this post , that values other than multiples of 15 for targetFrameRate lead to a timer-based frame rate reduction instead of requestAnimationFrame with decimation.

I will report the vSyncCount thing and post a link here once I get one.