Search Unity

OnPause events for WebGL builds

Discussion in 'Web' started by Aurigan, Sep 23, 2016.

  1. Aurigan

    Aurigan

    Joined:
    Jun 30, 2013
    Posts:
    291
    Ok, I'm not really sure this is even a sensible thing for Unity to try implementing BUT it would be neat if, for parity with other platforms, OnPause was triggered.

    Currently I don't think it is which means, for my game at least which involves gamestate progressing based on time elapsing, players 'lose' progress because the onpause handlers that are there to work out how much time passed while paused ... don't get called.
     
  2. Aurigan

    Aurigan

    Joined:
    Jun 30, 2013
    Posts:
    291
    Buuuuump! Would be good to get a Unity dev response on this even if only to say 'yeah we're not going to do that'. Thanks!
     
  3. Marco-Trivellato

    Marco-Trivellato

    Unity Technologies

    Joined:
    Jul 9, 2013
    Posts:
    1,654
    I am not sure I know what you mean by OnPause events. Could you clarify? What would you need it for ?
     
  4. Aurigan

    Aurigan

    Joined:
    Jun 30, 2013
    Posts:
    291
  5. Aurigan

    Aurigan

    Joined:
    Jun 30, 2013
    Posts:
    291
    bump again ...
     
  6. Aurigan

    Aurigan

    Joined:
    Jun 30, 2013
    Posts:
    291
    and again ...
     
  7. alexsuvorov

    alexsuvorov

    Unity Technologies

    Joined:
    Nov 15, 2015
    Posts:
    327
    Hello Aurigan.

    JavaScript code and native application have different access to the system events. Could you describe expected behavior in more details, providing specific use case examples?
    For example, the following events can be potentially tracked:
    - user switched to/from another browser tab or minimized/restored the browser window
    - computer woke up from sleep/hibernation (not 100% reliable)
    Can your problem be described in such terms?
     
  8. Aurigan

    Aurigan

    Joined:
    Jun 30, 2013
    Posts:
    291
    Hi Alex, thanks for the reply. It's the sleep/hibernation detection that's missing currently.

    In states where the tab gets 'backgrounded' like by switching tabs everything will keep running but with a less frequent updates - using Time.unscaledDeltaTime instead of Time.deltaTime works around that.

    The issue is where the browser gets hibernated - there's no way to keep the game state updating when this happens and no (Unity) notification that this state occurs so no way for a dev to do something about it.
     
  9. alexsuvorov

    alexsuvorov

    Unity Technologies

    Joined:
    Nov 15, 2015
    Posts:
    327
    Hello Aurigan.

    As far as I know, there is no way to track the moment when computer is going to sleep/hibernate from JavaScript (i.e. if you want to notify other players about this event). You can still try do this externally, for example, the build can send keep-alives to the server, so when keep-alives are no longer received, the server can assume that the game has been closed or paused. Unfortunately, there does not seem to be a reliable way to differentiate between pause and close (until computer wakes up), you might still try to do some tricks from the unload handler when the tab is closed, but it will not work reliably in situations when the browser itself is closed or crashes.

    Nevertheless, you are able to track the moment when computer wakes up from sleep/hibernation. All you have to do is to periodically execute some function and check the difference between its execution times. When the difference is bigger than expected, you can assume that execution has been suspended.

    A simple approach would therefore be:
    Code (JavaScript):
    1. function testAlive() {
    2.   var time = Date.now();
    3.   if (testAlive.time && time - testAlive.time > 5000 && Module["calledRun"])
    4.     SendMessage("GameObject", "ResumeEvent", time - testAlive.time);
    5.   testAlive.time = time;
    6.   setTimeout(testAlive, 1000);
    7. }
    8. testAlive();
    Note that you should make sure that Module["calledRun"] has been set before you use SendMessage, otherwise you might end up with a runtime error.

    The disadvantage of the simple approach is that while running on the main thread, this code would block every time when a popup dialog appears (like alert or prompt) or some computative synchronous task is in progress (like synchronous XMLHttpRequest or asm.js module compilation). To avoid those issues, you may run it in a separate thread (using a worker). Add the following /Assets/Plugins/testAlive.jspre plugin to your project (modify the setTimeout period and delay threshold according to your needs):
    Code (JavaScript):
    1. function testAlive() {
    2.   var time = Date.now();
    3.   if (testAlive.time && time - testAlive.time > 5000)
    4.     postMessage(time - testAlive.time);
    5.   testAlive.time = time;
    6.   setTimeout(testAlive, 1000);
    7. }
    8.  
    9. var workerUrl = URL.createObjectURL(new Blob([testAlive.toString(), "testAlive();"], { type: "text/javascript" }));
    10. var worker = new Worker(workerUrl);
    11. worker.onmessage = function (e) { if (Module["calledRun"]) SendMessage("GameObject", "ResumeEvent", e.data); };
    12. URL.revokeObjectURL(workerUrl);
    13.  
    And the C# handler code for the message:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class GameObjectScript : MonoBehaviour {
    5.     void ResumeEvent(int milliseconds) {
    6.         Debug.Log("The game has been paused for " + milliseconds + " ms");
    7.     }
    8. }
    Now the ResumeEvent should be called each time after execution has been suspended for longer than 5 seconds due to sleep, hibernation or other reason.
     
    Last edited: Nov 29, 2016
    Aurigan likes this.
  10. Aurigan

    Aurigan

    Joined:
    Jun 30, 2013
    Posts:
    291
    Epic reply! Thank you for taking the time to work this all out, clearly not a simple solution here.

    For my specific use-case I can't trust the local machine time (players will use it to cheat, a lot). I also believe this approach would need to have a really long (1min+) timer to stop false-positives from when the browser switches tabs and decides to run everything slower (?).

    I do happen to have a time-server setup already in the game that I can see ways to use (could compare a known server time + unscaled time passed vs. next timer server timestamps) ... fiddly though!

    Thanks again for the reply ... maybe these events *should* be a part of the way browsers work already? Who do I suggest that to? :)
     
  11. alexsuvorov

    alexsuvorov

    Unity Technologies

    Joined:
    Nov 15, 2015
    Posts:
    327
    Hello Aurigan.

    Actually it is the opposite. In fact, the code does not run any slower when you switch the tab or minimize the window. It is just the frame rendering callback which is being executed less often due to the requestAnimationFrame period being adjusted (normally from monitor refresh rate to 1 fps). You can even modify this behavior by manually setting the frame rate. This makes the main thread less busy, therefore the chances of a false-positive hibernation check (when you run it in the main thread) will actually decrease, and not increase.

    Note that this does not apply to the solution running in a worker (suggested above) in the first place, because it will be running in a separate thread. You can easily check on that if you execute the following JavaScript function while your game is active:
    Code (JavaScript):
    1. function blockMainThread(milliseconds) {
    2.   for (var unblockTime = Date.now() + milliseconds; Date.now() < unblockTime;);
    3. }
    Your game will get completely frozen for the specified period of time and even the browser window might become unresponsive (which depends on the browser), but the hibernation check will continue to work without any issues at all, because it runs in a separate thread.

    Note that you do not have to use the system time, you may as well perform requests to your server (i.e. for time synchronization and checks) directly from the JavaScript worker.

    There is also a more reliable way to detect this event, which you can just add to a .jspre plugin:
    Code (JavaScript):
    1. document.addEventListener("visibilitychange", function() {
    2.   console.log("The game is " + document.visibilityState);
    3. });
    You may also use the boolean document.hidden value instead of the document.visibilityState string.
     
    Last edited: Nov 30, 2016
  12. Aurigan

    Aurigan

    Joined:
    Jun 30, 2013
    Posts:
    291
    Thanks again Alex, given me a few things to think about. Interesting to learn that you can manually set the frame rate to keep the game updating more frequently while in backgrounded states!
     
  13. Meltdown

    Meltdown

    Joined:
    Oct 13, 2010
    Posts:
    5,822
    Hi Alex,

    How exactly in Unity can we raise an event when this happens?

    Thanks
     
  14. alexsuvorov

    alexsuvorov

    Unity Technologies

    Joined:
    Nov 15, 2015
    Posts:
    327
    Hello Meltdown.

    Attach the following script to an object named MyObject:
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using System.Runtime.InteropServices;
    5.  
    6. public class MyScript : MonoBehaviour {
    7.   [DllImport("__Internal")]
    8.   private static extern void registerVisibilityChangeEvent();
    9.  
    10.   void Start() {
    11.     registerVisibilityChangeEvent();
    12.   }
    13.  
    14.   void OnVisibilityChange(string visibilityState) {
    15.     System.Console.WriteLine("[" + System.DateTime.Now + "] the game switched to " + (visibilityState == "visible" ? "foreground" : "background"));
    16.   }
    17. }
    add the following /Assets/Plugins/visibilityChangeEvent.jslib plugin:
    Code (JavaScript):
    1. mergeInto(LibraryManager.library, {
    2.   registerVisibilityChangeEvent: function () {
    3.     document.addEventListener("visibilitychange", function () {
    4.       SendMessage("MyObject", "OnVisibilityChange", document.visibilityState);
    5.     });
    6.     if (document.visibilityState != "visible")
    7.       SendMessage("MyObject", "OnVisibilityChange", document.visibilityState);
    8.   },
    9. });
    A small note:
    One might assume that the game always starts in foreground, however, that is not always the case, because user can switch the browser tab while the game is still loading. In this case document.visibilityState can still be occasionally set to "hidden" at the time when your script starts, so you might want to explicitly notify your application about it at the time of event registration (see the plugin code above).
     
  15. Meltdown

    Meltdown

    Joined:
    Oct 13, 2010
    Posts:
    5,822
    Great, thanks for the help.
     
  16. Farage

    Farage

    Joined:
    Apr 25, 2014
    Posts:
    44
    5 years later and this thread saved me
     
    DungDajHjep likes this.
  17. Marks4

    Marks4

    Joined:
    Feb 25, 2018
    Posts:
    546
    I made a plugin for this. It can also detect when the web app loses focus for any reason.
     
  18. rmgomez

    rmgomez

    Joined:
    Oct 2, 2014
    Posts:
    18
    To Update this,
    OnApplicationFocus detects Tab/Switch and focus lost/gained on Unity 2021.3LTS. For some reason is not triggering this when you minimize the whole browser. To detect this I had to use Alexsurovov script. In firefox takes a second and a half to trigger but works..
     
    joan_stark, mtuf1989 and Gamemakers3D like this.
  19. DungDajHjep

    DungDajHjep

    Joined:
    Mar 25, 2015
    Posts:
    201
    Thanks !
     
    Last edited: Nov 18, 2022