Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Bug CreateLobbyAsync options select the wrong default hostId when passing player data for the host

Discussion in 'Lobby' started by neviovalsa, Feb 22, 2023.

  1. neviovalsa

    neviovalsa

    Joined:
    Jun 24, 2019
    Posts:
    39
    Here's the minimal repro code:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using System.Threading.Tasks;
    4. using Unity.Services.Authentication;
    5. using Unity.Services.Core;
    6. using Unity.Services.Lobbies.Models;
    7. using Unity.Services.Lobbies;
    8. using UnityEngine;
    9.  
    10. public class LobbyTest : MonoBehaviour
    11. {
    12.     [SerializeField] private string _playerId;
    13.     [SerializeField] private string _playerName;
    14.     [SerializeField] private bool _isSignedIn;
    15.     private async void Start()
    16.     {
    17.         InitializationOptions options = new InitializationOptions();
    18.         // this is used to be able to distinguish app instances on the same machine
    19.         // otherwise the Lobby service can't distinguish two players
    20.         options.SetProfile("playerName" + Random.Range(1, 100));
    21.         await UnityServices.InitializeAsync(options);
    22.  
    23.         SetupEvents();
    24.  
    25.         await SignInAnonymouslyAsync();
    26.     }
    27.  
    28.     private void Update()
    29.     {
    30.         if(Input.GetKeyDown(KeyCode.C))
    31.         {
    32.             CreateLobby();
    33.         }
    34.     }
    35.  
    36.     // Setup authentication event handlers if desired
    37.     void SetupEvents()
    38.     {
    39.         AuthenticationService.Instance.SignedIn += () => {
    40.             // Shows how to get a playerID
    41.             Debug.Log($"Signed in with PlayerID: {AuthenticationService.Instance.PlayerId}");
    42.  
    43.             // I set these values just to double check them in the inspector
    44.             _playerId = AuthenticationService.Instance.PlayerId;
    45.             _playerName = AuthenticationService.Instance.Profile;
    46.             _isSignedIn = AuthenticationService.Instance.IsSignedIn;
    47.         };
    48.  
    49.         AuthenticationService.Instance.SignInFailed += (err) => {
    50.             Debug.LogError(err);
    51.         };
    52.  
    53.         AuthenticationService.Instance.SignedOut += () => {
    54.             Debug.Log("Player signed out.");
    55.         };
    56.  
    57.         AuthenticationService.Instance.Expired += () =>
    58.         {
    59.             Debug.Log("Player session could not be refreshed and expired.");
    60.         };
    61.     }
    62.  
    63.     async Task SignInAnonymouslyAsync()
    64.     {
    65.         try
    66.         {
    67.             await AuthenticationService.Instance.SignInAnonymouslyAsync();
    68.         }
    69.         catch (AuthenticationException ex)
    70.         {
    71.             // Compare error code to AuthenticationErrorCodes
    72.             // Notify the player with the proper error message
    73.             Debug.LogException(ex);
    74.         }
    75.         catch (RequestFailedException ex)
    76.         {
    77.             // Compare error code to CommonErrorCodes
    78.             // Notify the player with the proper error message
    79.             Debug.LogException(ex);
    80.         }
    81.     }
    82.  
    83.     private async Task CreateLobby()
    84.     {
    85.         string lobbyName = "new lobby";
    86.         int maxPlayers = 4;
    87.         CreateLobbyOptions options = new CreateLobbyOptions();
    88.         // Ensure you sign-in before calling Authentication Instance.
    89.         // See IAuthenticationService interface.
    90.         options.Player = new Player();
    91.         Lobby lobby = await LobbyService.Instance.CreateLobbyAsync(lobbyName, maxPlayers, options);
    92.         print("AuthenticationService PlayerId: " + AuthenticationService.Instance.PlayerId);
    93.         print("Lobby HostId: " + lobby.HostId);
    94.     }
    95. }
    96.  
    Important lines of code:
    • line 20: this should ensure that each time you run the code a new profile is set (effectively changing the PlayerId)
    • line 90: we do not explicitly pass an id to the new Player() constructor.

    To reproduce :
    1. Setup lobby with Unity Gaming Services on an empty project
    2. Create an empty scene, and attach this script to an empty game object
    3. Run the project twice, each time pressing C to create a Lobby.
    Result: the first time you run the code you will see that the PlayerId in AuthenticationService matches the hostId of the lobby.

    In the second case, you will see that the hostId of the newly create lobby is the same as the one when you first ran the code, which is different from the new PlayerId value in the AuthenticationService.


    Note: if we change line 90 to specify the PlayerId
    Code (CSharp):
    1. options.Player = new Player(id: AuthenticationService.Instance.PlayerId);
    When running the code for the second time, we get the following validation error:


    Code (CSharp):
    1. Lobby]: ValidationError, (16000). Message: request failed validation
    2. UnityEngine.Logger:LogError (string,object)
    3. Unity.Services.Lobbies.Logger:LogError (object) (at Library/PackageCache/com.unity.services.lobby@1.0.3/Runtime/Utils/Logger.cs:17)
    4. Unity.Services.Lobbies.Internal.WrappedLobbyService:ResolveErrorWrapping (Unity.Services.Lobbies.LobbyExceptionReason,System.Exception) (at Library/PackageCache/com.unity.services.lobby@1.0.3/Runtime/SDK/WrappedLobbyService.cs:417)
    5. Unity.Services.Lobbies.Internal.WrappedLobbyService/<TryCatchRequest>d__20`2<Unity.Services.Lobbies.Lobby.CreateLobbyRequest, Unity.Services.Lobbies.Models.Lobby>:MoveNext () (at Library/PackageCache/com.unity.services.lobby@1.0.3/Runtime/SDK/WrappedLobbyService.cs:359)
    6. System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<Unity.Services.Lobbies.Response`1<Unity.Services.Lobbies.Models.Lobby>>:SetException (System.Exception)
    7. Unity.Services.Lobbies.Apis.Lobby.LobbyApiClient/<CreateLobbyAsync>d__6:MoveNext () (at Library/PackageCache/com.unity.services.lobby@1.0.3/Runtime/Apis/LobbyApi.cs:235)
    8. System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<Unity.Services.Lobbies.Http.HttpClientResponse>:SetResult (Unity.Services.Lobbies.Http.HttpClientResponse)
    9. Unity.Services.Lobbies.Http.HttpClient/<MakeRequestAsync>d__3:MoveNext () (at Library/PackageCache/com.unity.services.lobby@1.0.3/Runtime/Http/HttpClient.cs:47)
    10. System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<Unity.Services.Lobbies.Http.HttpClientResponse>:SetResult (Unity.Services.Lobbies.Http.HttpClientResponse)
    11. Unity.Services.Lobbies.Http.HttpClient/<CreateWebRequestAsync>d__7:MoveNext () (at Library/PackageCache/com.unity.services.lobby@1.0.3/Runtime/Http/HttpClient.cs:138)
    12. System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<Unity.Services.Lobbies.Http.HttpClientResponse>:SetResult (Unity.Services.Lobbies.Http.HttpClientResponse)
    13. Unity.Services.Lobbies.Http.HttpClient/<>c__DisplayClass7_0/<<CreateWebRequestAsync>b__0>d:MoveNext () (at Library/PackageCache/com.unity.services.lobby@1.0.3/Runtime/Http/HttpClient.cs:135)
    14. System.Threading.Tasks.TaskCompletionSource`1<Unity.Services.Lobbies.Http.HttpClientResponse>:SetResult (Unity.Services.Lobbies.Http.HttpClientResponse)
    15. Unity.Services.Lobbies.Http.UnityWebRequestHelpers/<>c__DisplayClass0_0:<GetAwaiter>b__0 (UnityEngine.AsyncOperation) (at Library/PackageCache/com.unity.services.lobby@1.0.3/Runtime/Http/UnityWebRequestHelpers.cs:34)
    16. UnityEngine.AsyncOperation:InvokeCompletionEvent ()
    Lobby version: 1.0.3
    Unity Version: 2021.3.18f1
     
  2. RobustKnittedTortoise

    RobustKnittedTortoise

    Unity Technologies

    Joined:
    Dec 10, 2018
    Posts:
    16
    The reason you are seeing a different user logged in is because your previous user stays "logged in" (their credentials are cached and will be used for any SDK operation) until the call to await SignInAnonymouslyAsync() completes.

    It may seem like the work in Start() would be done before update is called but that is not the case because start is "async void"; it does async work and cannot be awaited.

    The framework will basically call start then immediately begin calling update. Without your line in the Update method to wait for the 'C' key to be pressed, there would be more or less a 100% chance of seeing what you are seeing. With the 'C' key check, you just have to be faster than the auth system takes to sign out/sign in a new user.

    here is a really simple example so you can see that the work in start can be ongoing even while the Update loop is running and the results of my hitting the 'C' key a few times right after hitting play:
    Code (CSharp):
    1. public class RaceCondition : MonoBehaviour
    2. {
    3.     [SerializeField] private string _playerId;
    4.     private async void Start()
    5.     {
    6.         _playerId = "player 1";
    7.         await Task.Delay(2000);
    8.         _playerId = "player 2";
    9.         Debug.Log("Slow work from start finally completed!");
    10.     }
    11.  
    12.     private void Update()
    13.     {
    14.         if (Input.GetKeyDown(KeyCode.C))
    15.         {
    16.             Debug.Log("Who is player right now?:  " + _playerId);
    17.         }
    18.     }
    19. }
    20.  

    upload_2023-2-24_9-9-46.png
    The easiest fix is; remove the code that randomly generates a new anonymous user id!
    Other than that the safest thing thing to do is sign in in the same thread that is going to create the lobby after sign in is complete OR track when sign in is complete and gate your lobby creation on that being done.
     
  3. neviovalsa

    neviovalsa

    Joined:
    Jun 24, 2019
    Posts:
    39
    Thanks for answering, but I don't think this problem is related to a racing condition.

    My reason for thinking so is that I made sure to be signed in, before trying to create a lobby(hitting the C key).

    To make sure of that I would wait enough, I would wait for the SignedIn event to be fired (and wait until I see the Log on screen):
    Code (CSharp):
    1. AuthenticationService.Instance.SignedIn += () => {
    2.             // Shows how to get a playerID
    3.             Debug.Log($"Signed in with PlayerID: {AuthenticationService.Instance.PlayerId}");

    That said, I understand generating a random profile every time is clearly bad practice, it was a solution to be able to easily test locally, I guess having a simple in game TextInput and login button would solve the problem since I could ensure the same profiles are used on the same game instances.