How to load data from CloudSave at start of a scene?

I created a very simple scene in order to understand how CloudSave works. I have one object called DatabaseManager that contains the following:

public class DatabaseManager : MonoBehaviour {
    async void Awake() {
        await UnityServices.InitializeAsync();
        await AuthenticationService.Instance.SignInAnonymouslyAsync();
        Debug.Log($"Player id:{AuthenticationService.Instance.PlayerId}");
    }

    public static async void SaveScore(int score) {
        var playerData = new Dictionary<string, object>{
          {"score", score},
        };
        await CloudSaveService.Instance.Data.Player.SaveAsync(playerData);
    }

    public static async Task<int> LoadScore() {
        var playerData = await CloudSaveService.Instance.Data.Player.LoadAsync(new HashSet<string> {"score"});

        if (playerData.TryGetValue("score", out var scoreVar)) {
            int score = scoreVar.Value.GetAs<int>();
            Debug.Log($"loaded score value: {score}");
            return score;
        } else {
            throw new Exception("Could not load score");
        }
    }
}

On another object, I have the following code:

public class Score : MonoBehaviour {

    [SerializeField] int score = 0;

    async void Start() {
        score = await DatabaseManager.LoadScore();
    }

    public async void IncreaseScore() {
        score++;
        await DatabaseManager.SaveScore(score);
    }
}

where the function IncreaseScore() is called when a button is clicked.

It sometimes works, but sometimes I get the following exception while trying to load the score:

CloudSaveException: Access token is missing - ensure you are signed in through the Authentication SDK and try again.
...
Unity.Services.CloudSave.Internal.PlayerDataService.LoadAsync (System.Collections.Generic.ISet`1[T] keys) (at ./Library/PackageCache/com.unity.services.cloudsave/Runtime/com.unity.services.cloudsave.internal/Runtime/PlayerDataService.deprecated.cs:126)
DatabaseManager.LoadScore () (at Assets/Scripts/DatabaseManager.cs:44)
Score.Start () (at Assets/Score.cs:8)

I guess it is because the “LoadScore” function is called before the login process is finished. Indeed, when I add the following line at the start of Score.Start:

        await Awaitable.WaitForSecondsAsync(0.1f);

the score is loaded with no error.

But, this is just an arbitrary waiting time - it does not seem like the right solution.

What is the correct way to read data immediately when the scene starts?

The problem is with the Awake and Start methods of both scripts.

In Awake in one script, you are awaiting the initialization.
Another script runs its Start method and tries to load a score.

There is no guarantee that the time it takes for Unity to advance the scene initialization from Awake to Start is enough time for the service initialization to complete. In essence, the Start method does not await the completion of the Awake method of the other script.

The DatabaseManager should be in the very first scene of the project and should call DontDestroyOnLoad(gameObject); in its Awake so that it persists throughout the lifecycle of the application.

The other script thus supposedly being in another scene that gets loaded later guarantees that the DatabaseManager is fully initialized at this point.

Or you’d have a property IsInitialized and/or ignore any save/load calls if not initialized. Or if Save/Load is called without being initialized, you’d first await the initialization and then Save/Load. Plenty of ways to do this but you absolutely have to write the code such that initialization is guaranteed to complete before making any service calls.

This sounds like a good solution. How exactly can I do it - what statement should I write inside my LoadScore method, in order to await the initialization?

Alternatively, how can I raise an event at the end of the DatabaseManager.Awake, that will be intercepted by the Score component?

I think I found a solution. Instead of loading the score directly from Start, I now register a SignedIn event that does this after the sign in is complete. This is my new Score.Start function:

    void Start() {
        AuthenticationService.Instance.SignedIn += async () => {
            SetScore(await DatabaseManager.LoadScore());
        };
    }
1 Like