Completely Suspending Unity WebGL Apps

I’ve previously posted about pausing an application in Unity. What if we want to pause it completely?

Unfortunately, I haven’t been able to find an official way – and the fact that there’s a pause function that doesn’t actually pause (it throttles) leads me to assume there might not actually be an official way to temporarily and completely halt an embedded app. But, I didn’t create this article just to tell you I don’t have anything for this, so let’s see what I’ve found!

While rooting-through the internals of the Unity WebGL apps’ game instance and module class, I found these variables called dynCall. They’re a series of variables that start with dynCall_ and then have a roman numeral post-fixed. By tinkering, I’ve discovered something. But before I get to it, some caveats:

  • I don’t know what these dynCalls do.
  • I can’t begin to comprehend any specifics on why this works.
  • I don’t know if this completely halts the game. There may be invisible, lightweight engine and app processes still running in the background.
  • I don’t know any side-effects and consequences that can happen. I can speculate, but this is a black box, so I can’t know for sure. At least not without a lot of probing that I’m not willing to do at the moment.

How To Suspend The App

The magical dynCalls are v and vi. They can be set to do-nothing functions to put the app into stasis, and reset to their original values to resume the game. Something to also point out is in the Module class, there is an asm member that seems to have backup copies of all these dynCall functions, so we don’t need to store our own backups of these original functions.

<! –– Including generic Unity WebGL utilities ––> 
<script src="/blog/content/WebHalt/TemplateData/UnityProgress.js"></script>
<script src="/blog/content/WebHalt/Build/UnityLoader.js"></script>

<! –– Initializing the app ––> 
<script>
    // Intanciate the unity WebGL app
    var webhalt = 
        UnityLoader.instantiate(
            "webhalt", 
            "/blog/content/WebHalt/Build/WebHalt.json", 
            {
                onProgress: UnityProgress,
                Module:
                {
                    postRun:
                    [
                        // Add a postRun function to set up the suspension features. We can't 
                        // do it before, because the variables we need to reference don't 
                        // exist before the app is actually instanced.
                        function()
                        {
                            // Start the app paused. Empty out the function to suspend it.
                            webhalt.Module.dynCall_v = function(){}; 
                            webhalt.Module.dynCall_vi = function(){};

                            // Set up the HTML DOM node to restore the original implementations 
                            // of  v and vi to resume to app.
                            webhalt.Module.canvas.onmouseenter = 
                                function(){
                                    webhalt.Module.dynCall_v = webhalt.Module.asm.dynCall_v; 
                                    webhalt.Module.dynCall_vi = webhalt.Module.asm.dynCall_v;};
                                    
                            // Set up the HTML DOM node to suspend the app by emptying the 
                            // v and vi functions.
                            webhalt.Module.canvas.onmouseleave = 
                                function(){
                                    webhalt.Module.dynCall_v = function(){};
                                    webhalt.Module.dynCall_vi = function(){};};
                        }
                    ]
                }
            });
</script>

<! ––  ––> 
<div class="webgl-content">
   <div id="webhalt" style="width: 600px; height: 600px"></div>
</div>

The running implementation below. Move your mouse cursor over it to start it. Note the start process also includes the splash screen. After the app loads, you can move your cursor in and out of the app to test suspending and resuming it.

Depending on your usage, you may not want it paused and resumed based off mouse hovering. The triggering-and-resuming mechanism can be anything, as long as you’re emptying v and vi, and then restoring the original. When I use the word emptying I’m referring to setting it to an empty function, not setting it to null. That will throw exceptions.

Possible Interruption Issues

For this simple demo, everything seems fine. Even coroutines continue without a hitch. However, it’s unknown if any issues appear in more complex situations.

Unless you’re doing anything critical, there shouldn’t be any consequences from any unforeseen issues that couldn’t be fixed by losing your app state and refreshing the page to restart the embedded app. For something like having in-app purchases and not doing the risk-assessment of what happens if you suspend the game mid-transaction – but if you’re doing stuff like that, you’ve probably already identified these types of issues long before I mentioned it.

My Metrics

It seems to do a little better than throttling when suspended chrome’s thread for the tab goes to hover 0.0% – 0.4% processor utilization. It’s hard to say how much better this is – the rendering, timing mechanisms, and the game loop appear to stop completely. Evaluation of throttling vs. suspending the app might be more effective if the WebHalt demo was more processor intensive.

Information on the sample WebHalt WebGL app can be found here.
Built with Unity 2018.3.8f1
Authored and tested with Chrome.
– Stay strong, code on. William Leu

Explore more articles here.
Explore more articles about Unity WebGL here.