How to save and load a ScriptableObject from a Firebase Realtime Database?

Hello everyone,

so far I have a very simple PlayerDataSO which I use to easily transport player data from scene to scene.

public class PlayerDataSO : ScriptableObject
{
    [Header("Variable Values")]
    [SerializeField] private string playerName;
    [SerializeField] private string playerDBUserId;

    [Header("Fixed Values")]
    [SerializeField] private float _baseSidewayForce;

Now I try to save it to my realtime database (Firebase) and load it again.
My test methods look like this:

public void SavePlayerData(PlayerDataSO playerData)
    {
        string jsonNet = JsonConvert.SerializeObject(playerData);
        Debug.Log("SavePlayerData - jsonNet - " + jsonNet);
        DBreference.Child("PlayerData").SetRawJsonValueAsync(jsonNet);
        LoadPlayerDataTest();        
    }
    private void LoadPlayerDataTest()
    {
        PlayerDataSO tmpPlayerSO = null;
        DBreference.Child("PlayerData").GetValueAsync().ContinueWith(task =>
        {
            if (task.IsFaulted)
                Debug.Log("OH OH, ERROR");
            else if (task.IsCompleted)
            {
                DataSnapshot snapshot = task.Result;
                Debug.Log("LoadPlayerDataTest - snapshot - to string " + snapshot.Child("PlayerName").Value.ToString());
                Debug.Log("LoadPlayerDataTest - snapshot - JSON --> " + snapshot.GetRawJsonValue());
                // evil line of code coming:
                tmpPlayerSO = JsonConvert.DeserializeObject<PlayerDataSO>(snapshot.GetRawJsonValue()); 
                string tmpJson = JsonConvert.SerializeObject(tmpPlayerSO);
                Debug.Log("LoadPlayerDataTest - snapshot - JSON --> " + tmpJson);
            }
        });
    }

The debug output is this:

SavePlayerData - jsonNet - {“PlayerName”:“oetzi”,"PlayerDBUserId…[shortened]

LoadPlayerDataTest - snapshot - to string oetzi

LoadPlayerDataTest - snapshot - JSON → {“BaseSidewayForce”:50,"Player…[shortened]


As you can see the last debug output is missing and with some testing I found out that the following line causes a wierd behaviour.

tmpPlayerSO = JsonConvert.DeserializeObject<PlayerDataSO>(snapshot.GetRawJsonValue());

The execution of the code simply stops. No exception is thrown, nothing.


So I have 2 questions:

First: How can I reach my actual goal, to load my data from the database back into my scriptable object?

Second: Why does Unity behave that strange?

2 Answers

2

Are you sure no exception is thrown? If you put the line tmpPlayerSO = JsonConvert.DeserializeObject<PlayerDataSO>(snapshot.GetRawJsonValue()); into a try-catch block, does it output any messages?


I believe that because you’re using GetValueAsync() your code runs in a task that isn’t the main Unity thread. Therefore, scriptable objects and Monobehaviour methods work unexpectedly, such as GameObject.Find, which doesn’t work outside the main Unity thread.


I think if you use the namespace Firebase.Extensions, you have access to the ContinueWithOnMainThread method, which runs on the main Unity thread, so creating scriptable objects should work as expected, hopefully. @XoetziX

@Llama_w_2Ls Thanks for your reply. While trying to follow your hints I stepped into another curious problem, where again an exception was hidden. At least your hint with the try catch block helped me to locate it. I postet it here: https://answers.unity.com/questions/1840676/firebasedatabasedefaultinstancerootreference-null.html ---------- I still do not understand why exceptions are hidden by Unity if the code runs on another thread. I mean it is not an exceptional case to run something in a thread, is it?!

@Llama_w_2Ls You are absolutly right with your assumption regarding the parallel thread!

“I believe that because you’re using GetValueAsync() your code runs in a task that isn’t the main Unity thread. Therefore, scriptable objects and Monobehaviour methods work unexpectedly, such as GameObject.Find, which doesn’t work outside the main Unity thread.”


Using the try catch block helped indeed to get the hidden exception:

ScriptableObject.ctor can only be called from the main thread.
Constructors and field initializers will be executed from the loading thread when loading a scene.
Don't use this function in the constructor or field initializers, instead move initialization code to the Awake or Start function.

How ironic: By not supporting the use of threads Unity not only causes the problem that I cannot call the constructor but it also causes that I do not get the information that it cannot use it…


Now, the question of all questions:

Is there an elegant way to solve this issue?

The first thing which comes into my mind is that I can use a reference to a class within my ScriptableObject and then store this class in the database and the other way round. Would work probably but I would have to live with another tier within my ScriptableObjects which makes it more complicate to work with it.

If you use the Firebase.Extensions namespace, you should have access to the task method: ContinueWithOnMainThread instead of the asynchronous ContinueWith. This way, there's no need to worry about contacting the Unity API outside the main Unity thread.