WebGL Background Tabs

When users open multiple browser tabs, only the active (visible) one will be updated regularly at a high rate. Inactive (non visible) tabs get only updated once a second in most browsers. Visibility in this case doesn’t mean, that the window has to be in the foreground, it can also be non-focused and hidden by another window.

This also applies to WebGL exports of Unity and may lead to problems as received and outgoing messages are processed only once a second. This means that the message queues are growing and more and more updates need to get processed on an already low update rate. Many apps get more and more out of sync and run into disconnects while their Tab is in the background.

As WebGL applications are using JavaScript, these builds can be extended in order to send messages from the browser directly to the game logic. This includes the possibility to either disconnect from the game or force additional updates in between the low update rate when the application tab is in background. To do so you can follow one of the following approaches.

Modifying the WebGL build

Before talking about any of the approaches we want to talk about how we determine if the application tab is in background. Therefore we hook into the visibilitychange event and listen for changes. We are differing in two states: hidden and "any other state". Whenever we detect a state change we can send a message to a game object from our game logic and execute a function that way. This can be demonstrated using the following JavaScript code snippet:

<script>

document.addEventListener('visibilitychange', function () {
    if (document.visibilityState == "hidden") {
        SendMessage("Receiver", "DoSomething");
    } else {
        SendMessage("Receiver", "DoSomethingElse");
    }
});

</script>

In the above case we are sending a message to a game object called 'Receiver'. On this object we are executing a function called DoSomething(Else). This function can be placed inside any script attached to the game object.

To attach this code to your WebGL application, navigate to your build and open the created index.html file. You can copy and paste the above snippet and insert it into line 34 inside the <body> tag of that html file. Don't forget to adjust the parameters of the 'SendMessage' function.

First Approach - Closing The Connection

The first approach will disconnect the client from Photon when the WebGL application tab goes in background. It will try to (re-)-connect to Photon again when the application gets in foreground again. This is the recommended way to handle the described scenario.

To do so we are using the code snippet from above in our index.html file and sending messages to a 'Disconnect' and a 'Reconnect 'function from our game logic. These function might look like the following examples:

public void Disconnect()
{
    PhotonNetwork.Disconnect();
}

public void Reconnect()
{
    if (!PhotonNetwork.connected && wasConnected)
    {
        PhotonNetwork.ReconnectAndRejoin();
    }
    else
    {
        PhotonNetwork.ConnectUsingSettings("1.0");
    }
}

Note: The boolean variable wasConnected is set to true when the client had joined a room before. Having this information we can try to directly reconnect and rejoin the room instead of joining the lobby first.

In order to use ReconnectAndRejoin properly you need to set up the room and furthermore its options correctly. This means that you have to set PlayerTtl (describes how long a player can be inactive after a disconnect before getting removed from the server; in milliseconds) to a value larger than 0. If you want to keep an empty room you can additionally set EmptyRoomTtl (describes how long an empty room is kept in the server's memory before getting removed; in milliseconds) to a value larger than 0, too. When testing ReconnectAndRejoin using this approach it might be a good idea to set both values to 30,000 (30 seconds) or even 60,000 (60 seconds).

Second Approach - Using Interest Groups And Keeping The Connection Alive

The second approach requires you doing additional work on your game logic and especially your networking logic. Using this approach we are trying to keep the connection alive without sending or receiving game related messages. This is achieved by using Interest Groups in order to quickly subscribe to or unsubscribe from a certain group.

The default group used by PUN is 0. Since you can't unsubscribe from this group, we can't use it to solve our problem.

As written above we need to use another Interest Group when using this approach. For the following example we use group 1 as we can freely subscribe to and unsubscribe from it.

When connecting to Photon make sure that you enable sending and receiving for your desired group. You can do this by calling:

PhotonNetwork.SetReceivingEnabled(1, true);
PhotonNetwork.SetSendingEnabled(1, true);

Also make sure that your instantiated game objects (those which have a PhotonView component attached to them) have set their group variable to your desired group as well, for example by using:

public void Awake()
{
    GetComponent<PhotonView>().group = 1;
}

If you have 'moved' most of your network traffic to a certain group you can continue adding these two functions which are called from your browser's JavaScript. In our example we name them 'Subscribe' and 'Unsubscribe'. They might look like the following:

public void Subscribe()
{
    GetComponent<PhotonView>().group = 1;

    PhotonNetwork.SetReceivingEnabled(1, true);
    PhotonNetwork.SetSendingEnabled(1, true);
}

public void Unsubscribe()
{
    GetComponent<PhotonView>().group = 2;
    PhotonNetwork.SetReceivingEnabled(1, false);
    PhotonNetwork.SetSendingEnabled(1, false);
}

Last step you have to do is to adjust the JavaScript snippet that you are going to add to the "index.html" file in order to let it call the two above described functions.

This is not a recommended way to solve this problem because it requires a lot more work from you and might cause more trouble than it actually solves.

Third Approach - Forcing Updates And Keeping The Connection Alive

This approach differs from both previous because we want to force processing of all incoming and outgoing messages. Therefore we need to adjust our basic JavaScript snippet first so that it might look like the following:

<script>

var isActive;

document.addEventListener('visibilitychange', function () {
if (document.visibilityState == "hidden") {
    isActive = setInterval(function() { SendMessage("Receiver", "DoSomething"); }, 250);
} else {
    clearInterval(isActive);
    isActive = false;
}
});

</script>

This one creates and destroys a timer based on the current visibility state. When this timer is active it calls the given function each 250 ms or four times a second. The interval can be adjusted to your needs as well as the parameters of the SendMessage function.

Having this updated we can continue adjusting our WebGL project as well. Therefore we create a function DoSomething which will be called from the previous introduced timer. This function might look like the following:

public void DoSomething()
{
    if (!PhotonNetwork.connected || !PhotonNetwork.inRoom)
    {
        return;
    }

    PhotonNetwork.networkingPeer.DispatchIncomingCommands();
    PhotonNetwork.networkingPeer.SendOutgoingCommands();
}

This increases the network's message flow even if the browser's tab is in background. Although this sounds like a good idea this approach has a big disadvantage, too. Due to the fact that you can't control your character when the game is actually not visible you don't change anything related to your character's transform as well. So you will always send the exact same information to other clients. Means that you have an increased count of messages with no important content. So all in all this approach is not recommended, too.

Fourth Approach - Adjusting The Third Approach To Keep The Connection Alive

Even if the third approach is not recommended we can use it for our last approach. Therefore we modify our 'DoSomething' function so that it looks like the following:

public void DoSomething()
{
    PhotonNetwork.networkingPeer.SendAcksOnly();
}

This one simply send acknowledgments to the server in order to keep the connection alive. No game logic related messages are processed in this additionally created intervals.

 To Document Top