I use PlayerAccountService.Instance.StartSignInAsync() to sign in the player with Google or Apple, following the tutorial https://www.youtube.com/watch?v=XVqYIFcjhLE. It works well, it does log in and retrieve playerinfo.
Now after the game is stopped and restarted, I am trying to re-authenticate silently using the cached token. Unity’s doc says SignInAnonymouslyAsync() will do the job, with an example provided at Use anonymous sign-in.
My question is: what is the recommended way to invoke this function? I have tried to launch it from the first scene’s Start() function but don’t want to block the function (I don’t want to do await AuthenticationService.Instance.SignInAnonymouslyAsync()), so I have tried to just call AuthenticationService.Instance.SignInAnonymouslyAsync() and wait for the SignedIn event, but the event doesn’t come.
Thoughts?
My current code is:
public class LoginController : MonoBehaviour
{
public event Action<PlayerProfile> OnSignedIn;
private async void Awake()
{
await UnityServices.InitializeAsync();
PlayerAccountService.Instance.SignedIn += SignedIn;
}
public void SignInCachedUser()
{
// Check if a cached player already exists by checking if the session token exists
if (!AuthenticationService.Instance.SessionTokenExists)
{
// if not, then do nothing
Debug.Log("Found no cached authentication");
return;
}
// Sign in Anonymously
// This call will sign in the cached player.
Debug.Log("Found cached authentication, trying silent re-authentication");
try
{
AuthenticationService.Instance.SignInAnonymouslyAsync();
Debug.Log("Sign in anonymously started!");
// Shows how to get the playerID
//Debug.Log($"PlayerID: {AuthenticationService.Instance.PlayerId}");
}
catch (AuthenticationException ex)
{
// Compare error code to AuthenticationErrorCodes
// Notify the player with the proper error message
Debug.LogException(ex);
}
catch (RequestFailedException ex)
{
// Compare error code to CommonErrorCodes
// Notify the player with the proper error message
Debug.LogException(ex);
}
}
}
public class MainMenuManager : MonoBehaviour
void Start()
{
loginController.SignInCachedUser();
}
}
I assume the first scene is something like a title screen?
You should definitely await the call and you may want to defer this to a static class/method or simply a script whose GameObject is in DontDestroyOnLoad so the lifetime of the object executing the call isn’t bound to the currently loaded scene.
Putting service calls in DDOL is generally good practice as you don’t want any of these async calls to be interrupted or their results not being responded to or accessing already removed references.
Thank you for your response. Yes the first scene is a title screen.
Is it ok then to have a long (multiple seconds) await task called from a scene’s Start() function, and make that Start() function async?
public class MainMenuManager : MonoBehaviour
async void Start()
{
await loginController.SignInCachedUserAsync(); // may take multiple seconds
}
}
Sure but I would not do so in a script that does other things too, unless you want to block that script entirely.
It would be best practice to have a script like that but not the MainMenuManager
but rather a MonoBehaviour like PlayerSignIn
and any other functionality in the main menu that needs to have the player already signed in would use the loginController
to either check an IsSignedIn
property or better yet, responds to OnPlayerSignedIn
(eg in MainMenuManager
this could then enable the Multiplayer menu item) and OnPlayerSignInFailed
(for automated retry every few seconds, I would use this in PlayerSignIn
and maybe MainMenuManager
to show a notification on screen like “Not signed in, retrying in 3 seconds”).
Generally advisable to encapsulate/isolate all service calls and use events to inform other scripts to service state changes.
Also, be aware async void’s are not awaitable, and the monobehavior lifecycle doesnt account for this even if you stap a Task
on that.
That is, your Start
may still be awaiting after your first update
is called. Try this out in a simple monobehavior with a await Task.Delay(50)
and you’ll see what i mean.
1 Like
Good pointer! I think that might actually explain a weird out-of-order initialization issue that occurs for me every 20th time I enter playmode and seems rather inexplicable.
Thank you @CodeSmile and @GabKBelmonte.
Following your suggestion, I would call the async function SignInCachedUser() from Start() whitout waiting, as mentioned in the original post. This function ultimately calls AuthenticationService.Instance.SignInAnonymouslyAsync() and doesn’t wait. My problem though is it does log in the user silently but it never calls the SignedIn event. This is a question about the Authentication service specifically.
what editor version, core package version and authentication version are you using?
BTW, im not saying “dont await the call”, I’m just saying awaiting doesnt stop the monobehavior lifecycle from continuing. You should await, but you should also either check the auth package for the state of the lifecycle or keep your own state (state machine some would call it)
async Start()
{
await UnityServices.InitializeAsync();
await AuthenticationService.Instance.SignInAnonymouslyAsync();
m_Started = true;
}
async void Update()
{
if (!m_Started || m_Running)
return;
m_Running = true;
var module = new myccmBindings(CloudCodeService.Instance);
var result = await module.SayHello("Hello");
Debug.Log(result);
m_Running = false;
}
This update loop waits for started to be actually finished.
The issue here is that control is returned to the caller when “await” is hit, then update will run.
Basically, you can consider anything after await as a callback. Start will not have ended before Update does its first run, same with each subsequent update.
you can always try this
public async void Start()
{
Debug.Log("Start - Start");
await Task.Delay(3000);
Debug.Log("Start - End");
}
int frameCount = 0;
public async void Update()
{
if (frameCount++ > 5)
return;
Debug.Log($"Update part 1 | frame {frameCount}");
await Task.Delay(1000);
Debug.Log($"Update part 2 | frame {frameCount}");
}
very simple code to understand what I mean. It only runs for 5 frames.
1 Like
Also asked my Auth colleague, and he said you might wanna be looking for SignInFailed event.
Also, since you already have a token, its possible the sign-in event is not raised? you can check IsSignedIn
For testing, you might wanna clear credentials, that could have a problem if you’ve done caching and a few other shennanigans with ClearSessionToken
. I dont recall the exact circumstances, but when devs are creating/switching environments for testing they may end up with credentials that mismatch the cache. I seen it a couple of times.
Got it. I think awaiting for AuthenticationService.Instance.SignInAnonymouslyAsync(); in Start() with your little state machine for Update() will work for me.
Thank you for the help!
any time, please leave a 5 stars on yelp and tripadvisor and happy holidays