Search Unity

Question Client can't join back lobby after leaving

Discussion in 'Lobby' started by elzbeb, Jan 10, 2023.

  1. elzbeb

    elzbeb

    Joined:
    Jul 30, 2020
    Posts:
    54
    Hey so I am trying to fix an issue where when the client disconnects, they cant reconnect again to the same lobby, the reason is that the playerID is still registered in the lobby even after leaving, so I used the RemovePlayerAsync in the OnDestroy function as you can see in the script, the issue is when I leave the lobby and try to rejoin, on the client side it throws this error
    AuthenticationException: Invalid state for this operation. The player is already signd in
    and on the host side this error
    [Lobby]: Unauthorized, (16401). Message: HTTP/1.1 401 Unauthorized
    and it keeps logging every 5 seconds or so. Weird thing is that if I set the OnDestroy as a command, and I call it while the game is running on the client, it successfully removes the playerID from the lobby (clients doesn't disconnect) and when I leave as the client, I can rejoin the old lobby, but on the host side it starts logging the error again.
    I made a video so it's easier to visualize:


    Full error:
    [Lobby]: Unauthorized, (16401). Message: HTTP/1.1 401 Unauthorized
    0x00007ff6319a6edd (Unity) StackWalker::GetCurrentCallstack
    0x00007ff6319adbd9 (Unity) StackWalker::ShowCallstack
    0x00007ff632950f53 (Unity) GetStacktrace
    0x00007ff633003a53 (Unity) DebugStringToFile
    0x00007ff630a16fa6 (Unity) DebugLogHandler_CUSTOM_Internal_Log
    0x000001c01e82cf43 (Mono JIT Code) (wrapper managed-to-native) UnityEngine.DebugLogHandler:Internal_Log (UnityEngine.LogType,UnityEngine.LogOption,string,UnityEngine.Object)
    0x000001c01e82ce5b (Mono JIT Code) UnityEngine.DebugLogHandler:LogFormat (UnityEngine.LogType,UnityEngine.Object,string,object[])
    0x000001c08d4600da (Mono JIT Code) UnityEngine.Logger:LogError (string,object)
    0x000001c08d45ffba (Mono JIT Code) [Logger.cs:17] Unity.Services.Lobbies.Logger:LogError (object)
    0x000001c08d45fc4b (Mono JIT Code) [WrappedLobbyService.cs:417] Unity.Services.Lobbies.Internal.WrappedLobbyService:ResolveErrorWrapping (Unity.Services.Lobbies.LobbyExceptionReason,System.Exception)
    0x000001c01e88adab (Mono JIT Code) [WrappedLobbyService.cs:340] Unity.Services.Lobbies.Internal.WrappedLobbyService/<TryCatchRequest>d__19`1<TRequest_REF>:MoveNext ()
    0x000001bee2bff872 (Mono JIT Code) System.Runtime.CompilerServices.AsyncMethodBuilderCore/MoveNextRunner:InvokeMoveNext (object)
    0x000001bee2b7cb26 (Mono JIT Code) System.Threading.ExecutionContext:RunInternal (System.Threading.ExecutionContext,System.Threading.ContextCallback,object,bool)
    0x000001bee2b7c60b (Mono JIT Code) System.Threading.ExecutionContext:Run (System.Threading.ExecutionContext,System.Threading.ContextCallback,object,bool)
    0x000001bee2bff36b (Mono JIT Code) System.Runtime.CompilerServices.AsyncMethodBuilderCore/MoveNextRunner:Run ()
    0x000001c01b95fcba (Mono JIT Code) System.Threading.Tasks.AwaitTaskContinuation:InvokeAction (object)
    0x000001c01b95fb15 (Mono JIT Code) System.Threading.Tasks.AwaitTaskContinuation:RunCallback (System.Threading.ContextCallback,object,System.Threading.Tasks.Task&)
    0x000001c01b98ff03 (Mono JIT Code) System.Threading.Tasks.SynchronizationContextAwaitTaskContinuation:Run (System.Threading.Tasks.Task,bool)
    0x000001bee2b89fd3 (Mono JIT Code) System.Threading.Tasks.Task:FinishContinuations ()
    0x000001bee2b896d3 (Mono JIT Code) System.Threading.Tasks.Task:FinishStageThree ()
    0x000001bee2b88983 (Mono JIT Code) System.Threading.Tasks.Task:FinishStageTwo ()
    0x000001bee2b8835b (Mono JIT Code) System.Threading.Tasks.Task:Finish (bool)
    0x000001c08d45edab (Mono JIT Code) System.Threading.Tasks.Task:TrySetException (object)
    0x000001c08d45ec0b (Mono JIT Code) System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<TResult_REF>:SetException (System.Exception)
    0x000001c08d4740d3 (Mono JIT Code) [LobbyApi.cs:262] Unity.Services.Lobbies.Apis.Lobby.LobbyApiClient/<DeleteLobbyAsync>d__7:MoveNext ()
    0x000001bee2bff872 (Mono JIT Code) System.Runtime.CompilerServices.AsyncMethodBuilderCore/MoveNextRunner:InvokeMoveNext (object)
    0x000001bee2b7cb26 (Mono JIT Code) System.Threading.ExecutionContext:RunInternal (System.Threading.ExecutionContext,System.Threading.ContextCallback,object,bool)
    0x000001bee2b7c60b (Mono JIT Code) System.Threading.ExecutionContext:Run (System.Threading.ExecutionContext,System.Threading.ContextCallback,object,bool)
    0x000001bee2bff36b (Mono JIT Code) System.Runtime.CompilerServices.AsyncMethodBuilderCore/MoveNextRunner:Run ()
    0x000001c01b95fcba (Mono JIT Code) System.Threading.Tasks.AwaitTaskContinuation:InvokeAction (object)
    0x000001c01b95fb15 (Mono JIT Code) System.Threading.Tasks.AwaitTaskContinuation:RunCallback (System.Threading.ContextCallback,object,System.Threading.Tasks.Task&)
    0x000001c01b98ff03 (Mono JIT Code) System.Threading.Tasks.SynchronizationContextAwaitTaskContinuation:Run (System.Threading.Tasks.Task,bool)
    0x000001bee2b89fd3 (Mono JIT Code) System.Threading.Tasks.Task:FinishContinuations ()
    0x000001bee2b896d3 (Mono JIT Code) System.Threading.Tasks.Task:FinishStageThree ()
    0x000001c01b9646bb (Mono JIT Code) System.Threading.Tasks.Task`1<TResult_REF>:TrySetResult (TResult_REF)
    0x000001c01b9344a3 (Mono JIT Code) System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<TResult_REF>:SetResult (TResult_REF)
    0x000001c01e884403 (Mono JIT Code) [HttpClient.cs:47] Unity.Services.Lobbies.Http.HttpClient/<MakeRequestAsync>d__3:MoveNext ()
    0x000001bee2bff872 (Mono JIT Code) System.Runtime.CompilerServices.AsyncMethodBuilderCore/MoveNextRunner:InvokeMoveNext (object)
    0x000001bee2b7cb26 (Mono JIT Code) System.Threading.ExecutionContext:RunInternal (System.Threading.ExecutionContext,System.Threading.ContextCallback,object,bool)
    0x000001bee2b7c60b (Mono JIT Code) System.Threading.ExecutionContext:Run (System.Threading.ExecutionContext,System.Threading.ContextCallback,object,bool)
    0x000001bee2bff36b (Mono JIT Code) System.Runtime.CompilerServices.AsyncMethodBuilderCore/MoveNextRunner:Run ()
    0x000001c01b95fcba (Mono JIT Code) System.Threading.Tasks.AwaitTaskContinuation:InvokeAction (object)
    0x000001c01b95fb15 (Mono JIT Code) System.Threading.Tasks.AwaitTaskContinuation:RunCallback (System.Threading.ContextCallback,object,System.Threading.Tasks.Task&)
    0x000001c01b98ff03 (Mono JIT Code) System.Threading.Tasks.SynchronizationContextAwaitTaskContinuation:Run (System.Threading.Tasks.Task,bool)
    0x000001bee2b89fd3 (Mono JIT Code) System.Threading.Tasks.Task:FinishContinuations ()
    0x000001bee2b896d3 (Mono JIT Code) System.Threading.Tasks.Task:FinishStageThree ()
    0x000001c01b9646bb (Mono JIT Code) System.Threading.Tasks.Task`1<TResult_REF>:TrySetResult (TResult_REF)
    0x000001c01b9344a3 (Mono JIT Code) System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<TResult_REF>:SetResult (TResult_REF)
    0x000001c01e884b8b (Mono JIT Code) [HttpClient.cs:138] Unity.Services.Lobbies.Http.HttpClient/<CreateWebRequestAsync>d__7:MoveNext ()
    0x000001bee2bff872 (Mono JIT Code) System.Runtime.CompilerServices.AsyncMethodBuilderCore/MoveNextRunner:InvokeMoveNext (object)
    0x000001bee2b7cb26 (Mono JIT Code) System.Threading.ExecutionContext:RunInternal (System.Threading.ExecutionContext,System.Threading.ContextCallback,object,bool)
    0x000001bee2b7c60b (Mono JIT Code) System.Threading.ExecutionContext:Run (System.Threading.ExecutionContext,System.Threading.ContextCallback,object,bool)
    0x000001bee2bff36b (Mono JIT Code) System.Runtime.CompilerServices.AsyncMethodBuilderCore/MoveNextRunner:Run ()
    0x000001c01b95fcba (Mono JIT Code) System.Threading.Tasks.AwaitTaskContinuation:InvokeAction (object)
    0x000001c01b95fb15 (Mono JIT Code) System.Threading.Tasks.AwaitTaskContinuation:RunCallback (System.Threading.ContextCallback,object,System.Threading.Tasks.Task&)
    0x000001c01b98ff03 (Mono JIT Code) System.Threading.Tasks.SynchronizationContextAwaitTaskContinuation:Run (System.Threading.Tasks.Task,bool)
    0x000001bee2b89fd3 (Mono JIT Code) System.Threading.Tasks.Task:FinishContinuations ()
    0x000001bee2b896d3 (Mono JIT Code) System.Threading.Tasks.Task:FinishStageThree ()
    0x000001c01b9646bb (Mono JIT Code) System.Threading.Tasks.Task`1<TResult_REF>:TrySetResult (TResult_REF)
    0x000001c01b9344a3 (Mono JIT Code) System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<TResult_REF>:SetResult (TResult_REF)
    0x000001c01e886643 (Mono JIT Code) [HttpClient.cs:135] Unity.Services.Lobbies.Http.HttpClient/<>c__DisplayClass7_0/<<CreateWebRequestAsync>b__0>d:MoveNext ()
    0x000001bee2bff872 (Mono JIT Code) System.Runtime.CompilerServices.AsyncMethodBuilderCore/MoveNextRunner:InvokeMoveNext (object)
    0x000001bee2b7cb26 (Mono JIT Code) System.Threading.ExecutionContext:RunInternal (System.Threading.ExecutionContext,System.Threading.ContextCallback,object,bool)
    0x000001bee2b7c60b (Mono JIT Code) System.Threading.ExecutionContext:Run (System.Threading.ExecutionContext,System.Threading.ContextCallback,object,bool)
    0x000001bee2bff36b (Mono JIT Code) System.Runtime.CompilerServices.AsyncMethodBuilderCore/MoveNextRunner:Run ()
    0x000001c01b95fcba (Mono JIT Code) System.Threading.Tasks.AwaitTaskContinuation:InvokeAction (object)
    0x000001c01b95fb15 (Mono JIT Code) System.Threading.Tasks.AwaitTaskContinuation:RunCallback (System.Threading.ContextCallback,object,System.Threading.Tasks.Task&)
    0x000001c01b98ff03 (Mono JIT Code) System.Threading.Tasks.SynchronizationContextAwaitTaskContinuation:Run (System.Threading.Tasks.Task,bool)
    0x000001bee2b89fd3 (Mono JIT Code) System.Threading.Tasks.Task:FinishContinuations ()
    0x000001bee2b896d3 (Mono JIT Code) System.Threading.Tasks.Task:FinishStageThree ()
    0x000001c01b9646bb (Mono JIT Code) System.Threading.Tasks.Task`1<TResult_REF>:TrySetResult (TResult_REF)
    0x000001c01b964553 (Mono JIT Code) System.Threading.Tasks.TaskCompletionSource`1<TResult_REF>:TrySetResult (TResult_REF)
    0x000001c01b98fd63 (Mono JIT Code) System.Threading.Tasks.TaskCompletionSource`1<TResult_REF>:SetResult (TResult_REF)
    0x000001c01e886de3 (Mono JIT Code) [UnityWebRequestHelpers.cs:35] Unity.Services.Lobbies.Http.UnityWebRequestHelpers/<>c__DisplayClass0_0:<GetAwaiter>b__0 (UnityEngine.AsyncOperation)
    0x000001c01b98dfcb (Mono JIT Code) UnityEngine.AsyncOperation:InvokeCompletionEvent ()
    0x000001c0b6d04368 (Mono JIT Code) (wrapper runtime-invoke) object:runtime_invoke_void__this__ (object,intptr,intptr,intptr)
    0x00007ff80bd902f4 (mono-2.0-bdwgc) [mini-runtime.c:3445] mono_jit_runtime_invoke
    0x00007ff80bcceb34 (mono-2.0-bdwgc) [object.c:3066] do_runtime_invoke
    0x00007ff80bcceccc (mono-2.0-bdwgc) [object.c:3113] mono_runtime_invoke
    0x00007ff6318cb464 (Unity) scripting_method_invoke
    0x00007ff6318aadb4 (Unity) ScriptingInvocation::Invoke
    0x00007ff631560de7 (Unity) AsyncOperation::InvokeCoroutine
    0x00007ff631e40dbc (Unity) UnityWebRequestAsyncOperation::InvokeCoroutine
    0x00007ff631e40fa1 (Unity) UnityWebRequestProto<UnityWebRequestTransport,AtomicRefCounter,RedirectHelper,ResponseHelper,DownloadHandler,UploadHandler,CertificateHandler,HeaderHelper,AsyncOperation>::Job_InvokeCoroutine
    0x00007ff63151a50a (Unity) BackgroundJobQueue::ExecuteMainThreadJobs
    0x00007ff63158d5dc (Unity) `InitPlayerLoopCallbacks'::`2'::EarlyUpdateExecuteMainThreadJobsRegistrator::Forward
    0x00007ff6324f1151 (Unity) Application::TickTimer
    0x00007ff632956baa (Unity) MainMessageLoop
    0x00007ff63295b9b4 (Unity) WinMain
    0x00007ff633cc464e (Unity) __scrt_common_main_seh
    0x00007ff861ee26bd (KERNEL32) BaseThreadInitThunk
    0x00007ff86356dfb8 (ntdll) RtlUserThreadStart


    Script:
    Code (CSharp):
    1. using TMPro;
    2. using Unity.Netcode;
    3. using UnityEngine;
    4. using UnityEngine.UI;
    5. using System;
    6. using System.Collections;
    7. using System.Collections.Generic;
    8. using System.Threading.Tasks;
    9. using QFSW.QC;
    10. using Unity.Netcode.Transports.UTP;
    11. using Unity.Services.Authentication;
    12. using Unity.Services.Core;
    13. using Unity.Services.Lobbies;
    14. using Unity.Services.Lobbies.Models;
    15. using Unity.Services.Relay;
    16. using Unity.Services.Relay.Models;
    17.  
    18. #if UNITY_EDITOR
    19. using ParrelSync;
    20. #endif
    21.  
    22. public class NetworkManagerUI : NetworkBehaviour
    23. {
    24.     [SerializeField] private Button startHostButton;
    25.     [SerializeField] private Button startClientButton;
    26.     [SerializeField] private Button playerCount;
    27.     [SerializeField] private Button quickJoin;
    28.     [SerializeField] private TMP_InputField joinCodeInput;
    29.  
    30.     private Lobby _connectedLobby;
    31.     private QueryResponse _lobbies;
    32.     private UnityTransport _transport;
    33.     private const string JoinCodeKey = "j";
    34.     private string _playerId;
    35.     // private bool hasServerStarted;
    36.  
    37.     private void Awake()
    38.     {
    39.         Cursor.visible = true;
    40.         _transport = FindObjectOfType<UnityTransport>();
    41.     }
    42.  
    43.     void Start()
    44.     {
    45.         // // PLAYER COUNT
    46.         // playerCount?.onClick.AddListener(async () =>
    47.         // {
    48.         //     Test();
    49.         // });
    50.         // START HOST
    51.         startHostButton?.onClick.AddListener(async () =>
    52.         {
    53.             // // this allows the UnityMultiplayer and UnityMultiplayerRelay scene to work with and without
    54.             // // relay features - if the Unity transport is found and is relay protocol then we redirect all the
    55.             // // traffic through the relay, else it just uses a LAN type (UNET) communication.
    56.             // if (RelayManager.Instance.IsRelayEnabled)
    57.             //     await RelayManager.Instance.SetupRelay();
    58.             //
    59.             // if (NetworkManager.Singleton.StartHost())
    60.             //     Debug.Log("Host started...");
    61.             // else
    62.             //     Debug.Log("Unable to start host...");
    63.             await Authenticate();
    64.             _connectedLobby = await CreateLobby();
    65.         });
    66.  
    67.         // START CLIENT
    68.         startClientButton?.onClick.AddListener(async () =>
    69.         {
    70.             if (RelayManager.Instance.IsRelayEnabled && !string.IsNullOrEmpty(joinCodeInput.text))
    71.                 await RelayManager.Instance.JoinRelay(joinCodeInput.text);
    72.  
    73.             if(NetworkManager.Singleton.StartClient())
    74.                 Debug.Log("Client started...");
    75.             else
    76.                 Debug.Log("Unable to start client...");
    77.         });
    78.      
    79.         // AUTO JOIN
    80.         quickJoin?.onClick.AddListener(async () =>
    81.         {
    82.             await Authenticate();
    83.  
    84.             _connectedLobby = await QuickJoinLobby(); // ?? await CreateLobby()
    85.         });
    86.  
    87.         // STATUS TYPE CALLBACKS
    88.         NetworkManager.Singleton.OnClientConnectedCallback += (id) =>
    89.         {
    90.             Debug.Log($"{id} just connected...");
    91.         };
    92.  
    93.     }
    94.  
    95.     private async Task Authenticate()
    96.     {
    97.         var options = new InitializationOptions();
    98.  
    99. #if UNITY_EDITOR
    100.         options.SetProfile(ClonesManager.IsClone() ? ClonesManager.GetArgument() : "Primary");
    101. #endif
    102.  
    103.         await UnityServices.InitializeAsync(options);
    104.  
    105.         await AuthenticationService.Instance.SignInAnonymouslyAsync();
    106.         _playerId = AuthenticationService.Instance.PlayerId;
    107.         Debug.Log("Successfully signed in anonymously");
    108.         Debug.Log($"PlayerID: {_playerId}");
    109.     }
    110.  
    111.     private async Task<Lobby> QuickJoinLobby()
    112.     {
    113.         try
    114.         {
    115.             // Attempt to join an existing lobby
    116.             var lobby = await Lobbies.Instance.QuickJoinLobbyAsync();
    117.  
    118.             // If we found one, grab the play allocation details
    119.             var a = await RelayService.Instance.JoinAllocationAsync(lobby.Data[JoinCodeKey].Value);
    120.  
    121.             SetTransformAsClient(a);
    122.  
    123.             // Join the game room as a client
    124.             NetworkManager.Singleton.StartClient();
    125.             // PrintPlayers(lobby);
    126.             return lobby;
    127.         }
    128.         catch (Exception e)
    129.         {
    130.             Debug.Log($"No lobbies available via quick join\nError:{e}");
    131.             return null;
    132.         }
    133.     }
    134.  
    135.     private async Task<Lobby> CreateLobby()
    136.     {
    137.         try
    138.         {
    139.             const int maxPlayers = 40;
    140.          
    141.             // Create a relay allocation and generate a join code to share with the lobby
    142.             var a = await RelayService.Instance.CreateAllocationAsync(maxPlayers);
    143.             var joinCode = await RelayService.Instance.GetJoinCodeAsync(a.AllocationId);
    144.          
    145.             // Create a lobby, adding the relay join code to the lobby data
    146.             var options = new CreateLobbyOptions
    147.             {
    148.                 Data = new Dictionary<string, DataObject>
    149.                     { { JoinCodeKey, new DataObject(DataObject.VisibilityOptions.Public, joinCode) } }
    150.             };
    151.             var lobby = await Lobbies.Instance.CreateLobbyAsync("Lobby Name", maxPlayers, options);
    152.          
    153.             // Send a heartbeat every 15 seconds to keep the room alive
    154.             StartCoroutine(HeartbeatLobbyCoroutine(lobby.Id, 15));
    155.          
    156.             // Set the game room to use the relay allocation
    157.             _transport.SetHostRelayData(a.RelayServer.IpV4, (ushort)a.RelayServer.Port, a.AllocationIdBytes, a.Key, a.ConnectionData);
    158.          
    159.             // Start the room immediately
    160.             NetworkManager.Singleton.StartHost();
    161.             // PrintPlayers(lobby);
    162.             return lobby;
    163.         }
    164.         catch (Exception)
    165.         {
    166.             Debug.LogFormat("Failed to create a lobby");
    167.             return null;
    168.         }
    169.     }
    170.  
    171.     private void SetTransformAsClient(JoinAllocation a)
    172.     {
    173.         _transport.SetClientRelayData(a.RelayServer.IpV4, (ushort)a.RelayServer.Port, a.AllocationIdBytes, a.Key, a.ConnectionData, a.HostConnectionData);
    174.     }
    175.  
    176.     private static IEnumerator HeartbeatLobbyCoroutine(string lobbyId, float waitTimeSeconds)
    177.     {
    178.         var delay = new WaitForSecondsRealtime(waitTimeSeconds);
    179.         while (true)
    180.         {
    181.             Lobbies.Instance.SendHeartbeatPingAsync(lobbyId);
    182.             yield return delay;
    183.         }
    184.     }
    185.  
    186.     // private void PrintPlayers(Lobby lobby)
    187.     // {
    188.     //     Debug.Log("Players in lobby " + lobby.Name);
    189.     //     foreach (Player player in lobby.Players)
    190.     //     {
    191.     //         Debug.Log("!!!!!!!!");
    192.     //         Debug.Log(player.Id);
    193.     //         Debug.Log("!!!!!!!!");
    194.     //     }
    195.     // }
    196.  
    197.     // [Command]
    198.     // private void Test()
    199.     // {
    200.     //     int a = 0;
    201.     //     while (a <= 100)
    202.     //     {
    203.     //         PrintPlayers(_connectedLobby);
    204.     //
    205.     //         a += 1;
    206.     //     }
    207.     // }
    208.  
    209.     [Command]
    210.     public override void OnDestroy()
    211.     {
    212.         try
    213.         {
    214.             StopAllCoroutines();
    215.             // Call the PrintPlayers function
    216.             // if (_connectedLobby != null) PrintPlayers(_connectedLobby);
    217.  
    218.             if (_connectedLobby != null)
    219.             {
    220.                 if (_connectedLobby.HostId == _playerId) Lobbies.Instance.DeleteLobbyAsync(_connectedLobby.Id);
    221.                 else
    222.                 {
    223.                     // AuthenticationService.Instance.SignOut();
    224.                     Lobbies.Instance.RemovePlayerAsync(_connectedLobby.Id, _playerId);
    225.                     Debug.Log($"Removing playerid: {_playerId} in session {_connectedLobby.Id}");
    226.                 }
    227.             }
    228.         }
    229.         catch (Exception e)
    230.         {
    231.             Debug.Log($"Error shutting down lobby: {e}");
    232.         }
    233.     }
    234.  
    235.  
    236. }
     
  2. RobustKnittedTortoise

    RobustKnittedTortoise

    Unity Technologies

    Joined:
    Dec 10, 2018
    Posts:
    16
    I will take a closer look at the code you posted above, however based on what you said at the beginning
    It sounds like you may just want to call the lobby reconnect api

    That API is intended to flag users who are still in the lobby, but were using relay and had been flagged as disconnected.
     
  3. elzbeb

    elzbeb

    Joined:
    Jul 30, 2020
    Posts:
    54
    Hey, I got passed this issue, it was because I was calling AuthenticationService.Instance.SignOut();. After I removed it, it no longer gave me the error, although I have made a ticket and reported a potential bug with the lobby system. Here is what I am doing when OnNetworkDespawn, something is clearly not working correctly
    https://answers.unity.com/questions/1935370/client-unable-to-reconnect-to-previous-lobby-with.html
     
  4. RobustKnittedTortoise

    RobustKnittedTortoise

    Unity Technologies

    Joined:
    Dec 10, 2018
    Posts:
    16
    I took a closer look at the code you had posted and that video. The reason why you couldn't quick join back into the lobby is because you were most likely still in the lobby, quickjoin specifically avoids joining you to any lobby you are already in. The reason you are still in the lobby is likely because the game shutdown before it was able to execute the command to remove the player from the lobby (even if the method that should do it was called, I will elaborate below).

    Firstly, you can't guarantee on all platforms that code that needs to execute on exit will actually be invoked; the OS may kill the process without giving it a chance to execute ANY cleanup code.
    With this in mind, you should start always the game assuming it may not have cleaned up any external resources from the previous instance correctly.
    Lobby provides a way to find any lobbies you are currently still in, so that you can essentially automatically rejoin the lobby without explicitly having to find it or call join as you were never actually removed: https://docs.unity.com/lobby/get-joined-lobbies.html

    Second, the cleanup code as it is written, even if it was guaranteed to be called before exit and the process was waiting for that method to return before killing your game client, is non deterministic and likely to never actually remove the player from the lobby, unless something that takes a long time is also done write after the cleanup call to keep the process alive! The original sample you followed from Tarodev also has a similar problem. The issue is the call to
    Lobbies.Instance.RemovePlayerAsync
    is not being awaited (there are ways to do this from a non async method and make it block). So the method
    DisconnectAndSignoutPlayer
    will be called and return immediatly, even before a thread is created and scheduled to do the RemovePlayerAsync work!

    The Tarodev solution comes closer as it does await the
    Lobbies.Instance.RemovePlayerAsync
    call, however, you couldn't rely on the OnDestroy method there, if called at shutdown to cleanup either as it calls the
    MatchmakingService.LeaveLobby();
    without waiting for the result, so there is no guarantee after that block of code executes, that the lobby service was called. If the game client stays alive for at lease another few hundred milliseconds, its almost guaranteed, but if we are talking about a shutdown/crash scenario, what actually gets invoked before the process is terminated is not guaranteed
     
  5. elzbeb

    elzbeb

    Joined:
    Jul 30, 2020
    Posts:
    54
    Yeah, I see, I noticed that the client was not actually leaving the lobby a while ago, I just don't know if it is intentional and if I need to be calling something different or not, that is why I am asking, did unity do this on purpose? If yes, what would you suggest doing and where is the documentation about it as I have been searching for quite a while and not finding the proper way to disconnect a player. If not well I already filed a ticket in the dashboard lobby section just in case.
     
  6. RobustKnittedTortoise

    RobustKnittedTortoise

    Unity Technologies

    Joined:
    Dec 10, 2018
    Posts:
    16
    Got it, the process is documented but there are a few moving parts.

    I will look into consolidating this info to make it more discoverable as your issue is a common and expected case we want to support.

    What you are currently seeing is by design and alluded to in the reconnect to lobby docs (stealing the text from there):
    I notice it doesn't actually mention the name of the config setting used to control this time to live for a player who has disconnected from the rely, i will update the docs to make it more clear. That setting is controlled by the Disconnect removal time:
    So to summarize what should happen:
    1. After your player disconnects from relay by quitting/crashing etc. there will be a small delay while the relay server notices (milliseconds to seconds?).
    2. Relay will optimistically notify lobby that a playerId + allocationId have disconnected.
    3. If the player has linked their relay allocationId linked to their player record in a lobby, lobby will mark them as disconnected. (see relay lobby integration docs). The linking is really just making sure to update the player.AllocationId property to the relay allocationId
    4. At this point, if lobby found a matching player, it now has whatever you configured Disconnect removal time is to live before it is automatically removed from the lobby (default is 120 seconds).
    5. If the client now recovers by restarting, they will still be in the lobby until at least the time mentioned in step 4 elapses.
    6. If the client should get the id of any lobby they may still be in either by calling the Get Joined Lobbies API ( the client could also read/write this to local remote storage if desired)
    7. If any previously joined lobbyId is found, the client should then call Reconnect to lobby otherwise they will still be flagged as disconnected and eventually removed!
    I will try to get all this added to the Relay Integration page as that seems like the best place for a comprehensive flow to be outlined.
     
  7. elzbeb

    elzbeb

    Joined:
    Jul 30, 2020
    Posts:
    54
    I see, thank you for the detailed explanation, now where would you put this? In the script, I gave above which is managing lobby, relay + the UI, or in my other script, the PlayerNetworkController, which is attached to the player and has an OnNetworkSpawn and OnNetworkDespawn?
    Also, is the OnNetworkDespawn of any use in my case? I am kinda lost between all of this so if you are able to elaborate on where to put those lines of code it would be perfect.
    Again thank you for taking the time and helping out.