Search code examples
c#unity-game-enginefirebase-realtime-databasecallbackleaderboard

why this in a callback causes the code to stop?


After recovering my data with Firebase using a callback in GetValueAsync().ContinueWith(task..) , I would like to instantiate my prefab in order to see the list of scores for my leaderboard. But, it does nothing and I have no errors. The code simply stops in the callback UseSores as soon as it come across on a 'this' or a 'instantiate'.

public class Leaderboardmanager : MonoBehaviour
{

    public GameObject rowLeardBoard;
    FirebaseDB_Read read;
    float positionX; 
    int nbRows = 10;

    void Start()
    {   
        read = (gameObject.AddComponent<FirebaseDB_Read>());
        GetScorePlayer();
    } 

    void GetScorePlayer()
    {
        read.GetScores(UseScores, "entries/LeaderBoard/", nbRows);
    }

    void UseScores(IList<FirebaseDB_Read.Score> scores)
    {
        Debug.Log("arrive here");
        positionX = this.transform.position.y; 
        Debug.Log("does not arrive here");
    }
}

Here is to get my data :

public class FirebaseDB_Read : MonoBehaviour
{

    public class Score
    {
        public string UID;
        public string score;
        public int rank;
    }


    public void GetScores(Action<IList<Score>> callback, string URL_TO_SCORES, int limit)
    {
        DatabaseReference scoresRef = FirebaseDatabase.DefaultInstance.GetReference(URL_TO_SCORES);

        scoresRef.OrderByChild("score").LimitToLast(limit).GetValueAsync().ContinueWith(task =>
        {
            DataSnapshot snapshot = task.Result;
            IList<Score> objectsList = new List<Score> { };

            int i = 1;
            foreach (var childSnapshot in snapshot.Children)
            {
                Score score = new Score();
                score.rank = i;
                score.UID = childSnapshot.Child("UID").GetValue(true).ToString();
                score.score = childSnapshot.Child("score").GetValue(true).ToString();

                objectsList.Add(score);
                i++;
            }

            callback(objectsList);
        });
    }
}

Solution

  • This is an often asked problem in Unity: Because you ContinueWith on a background thread!

    Unity isn't thread-safe, meaning that most of the Unity API can only be used within the Unity main thread.

    Firebase offers an extension specifically for Unity: ContinueWithOnMainThread which assures that the result is handled in the Unity main thread where accessing the API is valid.

    scoresRef.OrderByChild("score").LimitToLast(limit).GetValueAsync().ContinueWithOnMainThread(task =>
    {
        ...
    });
    

    As alternative you can use kind of a so called "main thread dispatcher" pattern and make sure that the callback is executed in the main thread on the receiver side. The advantage of this would be that the still expensive operations on your list are all executed on a background thread, not affecting the UI performance

    scoresRef.OrderByChild("score").LimitToLast(limit).GetValueAsync().ContinueWith(task =>
    {
        ...
    });
    

    but then on receiver side in FirebaseDB_Read

    private readonly ConcurrentQueue<Action> _mainThreadActions = new ConcurrentQueue<Action>();
    
    private void Update()
    {
        if(_mainThreadAction.Count > 0)
        {
            while(_mainThreadActions.TryDequeue(out var action))
            {
                action?.Invoke();
            }
        }
    }
    
    void GetScorePlayer()
    {
        read.GetScores(UseScores, "entries/LeaderBoard/", nbRows);
    }
    
    void UseScores(IList<FirebaseDB_Read.Score> scores)
    {
        // handle this in the next main thread update
        _mainThreadActions.Enqueue(() =>
        {
            Debug.Log("arrive here");
            positionX = this.transform.position.y; 
            Debug.Log("does not arrive here");
        }
    }
    

    which on the offside of course introduces a little overhead for checking for any new actions in Update of course. So if you plan do use multiple of such background actions make sure to implement them in one central place in order to keep the overhead limited ;)