How to reconnect and rejoin when mobile app goes to background. Best practice?
Options
Burny123
✭
Hi guys, we are using PUN for almost a year and are very happy with it. The only problem we have is handling the case where the mobile app is going to the background and to trigger a successful reconnect and rejoin. We can’t seem to find some best practices or code snippets so we cooked up something ourselves. The following implementation seems to work most of the times but it’s not 100% reliable. We still have cases where the app is not successfully reconnecting and rejoining.
We hope you could take the time and spot some problems with this script. We also hope you could provide us with a best practice for reconnecting and rejoining.
https://pastebin.com/X83mubxf
FYI:
- The room we create has a PlayerTTL of 300000.
- We are only targeting iOS.
- PUN v2.7
- Unity 2018.3.5
Thanks!
We hope you could take the time and spot some problems with this script. We also hope you could provide us with a best practice for reconnecting and rejoining.
https://pastebin.com/X83mubxf
FYI:
- The room we create has a PlayerTTL of 300000.
- We are only targeting iOS.
- PUN v2.7
- Unity 2018.3.5
Thanks!
0
Comments
-
0
-
@JohnTube Thanks for helping out. The link you provided leads to an empty page on hastebin, I guess something went wrong?
The issue from the link is not the same. We can successfully reconnect and rejoin most of the times, but not always.. It seems random and is hard for us to debug.
I love to see the UtilityScript.
Thanks!
0 -
Hey @Burny123,
I have checked again and I think the link works fine now.
Maybe there was an issue yesterday.
Anyways find the code also here:using System; using Photon.Realtime; using UnityEngine; namespace Photon.Pun.UtilityScripts { /// <summary> /// Unexpected disconnects recovery /// </summary> public class DisconnectsRecovery : MonoBehaviourPunCallbacks { [Tooltip("Whether or not attempt a rejoin without doing any checks.")] [SerializeField] private bool skipRejoinChecks; [Tooltip("Whether or not realtime webhooks are configured with persistence enabled")] [SerializeField] private bool persistenceEnabled; private bool rejoinCalled; private int minTimeRequiredToRejoin = 0; // TODO: set dynamically based on PhotonNetwork.NetworkingClient.LoadBalancingPeer.RoundTripTime private DisconnectCause lastDisconnectCause; private bool wasInRoom; private bool reconnectCalled; public override void OnEnable() { base.OnEnable(); PhotonNetwork.NetworkingClient.StateChanged += this.OnStateChanged; } public override void OnDisable() { base.OnDisable(); PhotonNetwork.NetworkingClient.StateChanged -= this.OnStateChanged; } private void OnStateChanged(ClientState fromState, ClientState toState) { if (toState == ClientState.Disconnected) { Debug.LogFormat("OnStateChanged from {0} to {1}, PeerState={2}", fromState, toState, PhotonNetwork.NetworkingClient.LoadBalancingPeer.PeerState); this.HandleDisconnect(); } } public override void OnDisconnected(DisconnectCause cause) { Debug.LogFormat("OnDisconnected(cause={0}) ClientState={1} PeerState={2}", cause, PhotonNetwork.NetworkingClient.State, PhotonNetwork.NetworkingClient.LoadBalancingPeer.PeerState); if (rejoinCalled) { Debug.LogError("Rejoin failed, client disconnected"); rejoinCalled = false; return; } if (reconnectCalled) { Debug.LogError("Reconnect failed, client disconnected"); reconnectCalled = false; return; } lastDisconnectCause = cause; wasInRoom = PhotonNetwork.CurrentRoom != null; if (PhotonNetwork.NetworkingClient.State == ClientState.Disconnected) { this.HandleDisconnect(); } } private void HandleDisconnect() { switch (lastDisconnectCause) { case DisconnectCause.Exception: case DisconnectCause.ServerTimeout: case DisconnectCause.ClientTimeout: case DisconnectCause.DisconnectByServerLogic: case DisconnectCause.AuthenticationTicketExpired: case DisconnectCause.DisconnectByServerReasonUnknown: if (wasInRoom) { this.CheckAndRejoin(); } else { Debug.Log("PhotonNetwork.Reconnect called"); reconnectCalled = PhotonNetwork.Reconnect(); } break; case DisconnectCause.OperationNotAllowedInCurrentState: case DisconnectCause.CustomAuthenticationFailed: case DisconnectCause.DisconnectByClientLogic: case DisconnectCause.InvalidAuthentication: case DisconnectCause.ExceptionOnConnect: case DisconnectCause.MaxCcuReached: case DisconnectCause.InvalidRegion: case DisconnectCause.None: break; default: throw new ArgumentOutOfRangeException("cause", lastDisconnectCause, null); } lastDisconnectCause = DisconnectCause.None; wasInRoom = false; } public override void OnJoinRoomFailed(short returnCode, string message) { if (!rejoinCalled) { return; } rejoinCalled = false; Debug.LogErrorFormat("Quick rejoin failed with error code: {0} & error message: {1}", returnCode, message); } public override void OnJoinedRoom() { if (rejoinCalled) { Debug.Log("Rejoin successful"); rejoinCalled = false; } } private void CheckAndRejoin() { if (skipRejoinChecks) { Debug.Log("PhotonNetwork.ReconnectAndRejoin called"); rejoinCalled = PhotonNetwork.ReconnectAndRejoin(); } else { bool wasLastActivePlayer = true; if (!persistenceEnabled) { for (int i = 0; i < PhotonNetwork.PlayerListOthers.Length; i++) { if (!PhotonNetwork.PlayerListOthers[i].IsInactive) { wasLastActivePlayer = false; break; } } } if ((PhotonNetwork.CurrentRoom.PlayerTtl < 0 || PhotonNetwork.CurrentRoom.PlayerTtl > minTimeRequiredToRejoin) // PlayerTTL checks && (!wasLastActivePlayer || PhotonNetwork.CurrentRoom.EmptyRoomTtl > minTimeRequiredToRejoin || persistenceEnabled)) // EmptyRoomTTL checks { Debug.Log("PhotonNetwork.ReconnectAndRejoin called"); rejoinCalled = PhotonNetwork.ReconnectAndRejoin(); } else { Debug.Log("PhotonNetwork.ReconnectAndRejoin not called, PhotonNetwork.Reconnect is called instead."); reconnectCalled = PhotonNetwork.Reconnect(); } } } public override void OnConnectedToMaster() { if (reconnectCalled) { Debug.Log("Reconnect successful"); reconnectCalled = false; } } }
}0 -
@JohnTube I tried the script but it not works on my iPad. The main issue seems to be:
’Reconnect() failed. Can only connect while in state 'Disconnected'. Current state: Connected’
Full log: https://pastebin.com/xZCZJibz
We had this issue too and added a coroutine which waits for the player to be disconnected fully.
What do you suggest to do from here?
Thanks!0 -
Hi @Burny123,
I can only think of adding a coroutine to detect when a client + peer are fully properly and disconnected before attempting a reconnect or a rejoin. So it's like your solution, however, you said;implementation seems to work most of the times but it’s not 100% reliable. We still have cases where the app is not successfully reconnecting and rejoining.can we try to find out how to reproduce those cases? Let's try to narrow it down.0 -
ok,
just for the record should other face this issue while we discuss this internally:
when you get a disconnect call back, raise a boolean provate variable, and during an update call, try to reconnect if that bool is true, this is the shortest way to reconnect and avoid the issue explained above.
It's going to be tricky, because it could be a case where it's a race condition situation, if we change the order of property sets and callbacks, it could break other projects.
Bye,
Jean0 -
0
-
Hi,
yes, it needs updating. here's the code featuring the update for the reconnecting to occur,
https://pastebin.com/wk39tgzA
Let me know how it goes.
Bye,
Jean
0 -
I want to clarify/rectify something:
the script shared by @jeanfabre has some extra lines we used for testing internally:
remove them fromOnEnable
:
before:public override void OnEnable() { base.OnEnable(); PhotonNetwork.NetworkingClient.StateChanged += this.OnStateChanged; PhotonNetwork.KeepAliveInBackground = 0f; PhotonNetwork.PhotonServerSettings.RunInBackground = false; Application.runInBackground = false; }
after:public override void OnEnable() { base.OnEnable(); PhotonNetwork.NetworkingClient.StateChanged += this.OnStateChanged; }
@jeanfabre
so if I understood correctly, we need to wait 1 frame before trying to reconnect and rejoin?
we could do this using a coroutine andWaitForEndOfFrame
?
hmm will test this.0 -
Hi,
yes. That's what I am witnessing here. We are currently investigating as to what exactly happens during that frame and if we can remove this and be able to reconnect straight from a disconnect callback.
Bye,
Jean0 -
Hello @JohnTube, i'm not much into coding and sorry for my english grammer if any mistakes, actually even i'm running same problem, but i'm using pun1 classic, im trying to reconnect when one of host or client mobile disconnected from internet and then reconnected, then i couldnt able to run the game. but in the editor even after reconnecting internet also the game works, but not in android mobile. can you please help me out as i'm in very urgent need...0
-
Hello @JohnTube, i'm not much into coding and sorry for my english grammer if any mistakes, actually even i'm running same problem, but i'm using pun1 classic, im trying to reconnect when one of host or client mobile disconnected from internet and then reconnected, then i couldnt able to run the game. but in the editor even after reconnecting internet also the game works, but not in android mobile. can you please help me out as i'm in very urgent need...
anyone there to help......0 -
Hi @Nitish,
Hope this is not too late for you. I came across the same issue a few days ago and manage to solve it using jeanfabre's code posted above.
In gist, the blackscreen on android arises when your client disconnects from the server, which causes the server to despawn/destroy the game character of that client. Upon reconnect, whether via detection in OnApplicationPause or from the OnDisconnectFromPhoton callback, you may get an issue as stated above "Reconnect() failed. Can only connect while in state 'Disconnected'. Current state: Connected" (or in my case the current state is disconnecting), which cause the reconnect call to fail.
The trick is to call reconnect in the next frame as your callbacks. This is achieved by setting a flag in your server logic, and checking that flag in the update call. Once that flag has been set, Update function will take care of reconnect logic in the next frame, therefore circumnavigate the issue.
I'm not sure whether there is a conclusion to the official investigation on this issue since August of 2019, but as of now, I can confirm I'm still running into it.
Hope it helps0