5

I have a more general question regarding Unity C# and the brand new Firebase SDK. I've looked through all of the new documentation and haven't seen an answer to this yet. If you retrieve data from the database below, it doesn't allow you to execute methods like Instantiate inside this function because it is not happening on the main thread. How would you go about doing this? TLDR I want to know how to execute game functions after or while retrieving stuff from Firebase.

   FirebaseDatabase.DefaultInstance
    .GetReference("Scenes").OrderByChild("order")
    .ValueChanged += (object sender2, ValueChangedEventArgs e2) => {
      if (e2.DatabaseError != null) {
        Debug.LogError(e2.DatabaseError.Message);
        }
        scenes = asset.text.Split('\n');
        return;
      }
      if (e2.Snapshot != null && e2.Snapshot.ChildrenCount > 0) {
        sceneCollection.Clear();
        foreach (var childSnapshot in e2.Snapshot.Children) {
          var sceneName = childSnapshot.Child("name").Value.ToString(); 
          sceneCollection.Add( new SceneItem(sceneName, 0));

          // I WANTED TO INSTANTIATE SOMTHING HERE

        }

      }
    };
Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
Teddy
  • 113
  • 6
  • I haven't checked it out yet! But if you google "Unity3D call back to the main thread" you will find much discussion about this example... https://forum.unity3d.com/threads/mainthread-function-caller.348198/ ... – Fattie Nov 12 '16 at 02:36
  • I would recommend getting one of the (excellent) packages from the asset store that deal with threading; it will solve the problem. It's surprising Firebase didn't "throw something in" to deal with that. – Fattie Nov 12 '16 at 02:37
  • Great thanks JB I will try those out. Also, I'll put in a feature request with the Firebase team. – Teddy Nov 12 '16 at 14:38
  • Right, it's quite strange they didn't address this. I'm sorry I haven't had a chance to look at it; you're pretty sure they don't *include* a mechanism to get back to the main thread right? – Fattie Nov 12 '16 at 15:17
  • BTW in that code where you += ... I would encourage you to just make that a separate function in your script, and += that function - it's just a lot clearer what the heck is going on you know? – Fattie Nov 12 '16 at 15:18
  • Totally. Thanks for the suggestion JB. – Teddy Nov 14 '16 at 03:53
  • Since the callback is currently called on a different thread to Unity's main thread you could start a MonoBehaviour which maintains a list of actions to execute emulating a SynchronizationContext. This is what we do inside the Firebase Unity SDK for most of the APIs. From your MonoBehaviour.Update you'll need to dequeue and execute actions queued from other threads. In this case you do `ValueChanged += MyHandler` and in `MyHandler` `MonoBehavior.actionQueue.Add(() => { your stuff });` then you execute actions on `MonoBehavior.actionQueue` from `MonoBehavior.Update` – stewartmiles Mar 22 '18 at 18:27

2 Answers2

3

(Ben from the Firebase team here)

This is now fixed as Stewart says below:

https://firebase.google.com/support/release-notes/unity#1.0.1

I'm leaving the code below in case anyone finds it useful to be able to marshal to the ui thread from a background thread. Once you install this synchronizationcontext, you can use it like any other .Net SynchronizationContext:

https://msdn.microsoft.com/en-us/library/system.threading.synchronizationcontext(v=vs.110).aspx

UnitySynchronizationContext.Install();

class UnitySynchronizationContext : SynchronizationContext {
  static UnitySynchronizationContext _instance = null;
  GameObject gameObject;
  Queue<Tuple<SendOrPostCallback, object>> queue;

  private UnitySynchronizationContext() {
    gameObject = new GameObject("SynchronizationContext");
    gameObject.AddComponent<SynchronizationContextBehavoir>();
    queue =
      gameObject.GetComponent<SynchronizationContextBehavoir>()
        .Queue;
  }

  public static void Install() {
    if (SynchronizationContext.Current == null)
    {
      if (_instance == null)
      {
        _instance = new UnitySynchronizationContext();
      }
      SynchronizationContext.SetSynchronizationContext(_instance);
    }
  }

  public override void Post(SendOrPostCallback d, object state) {
    lock (queue)
    {
      queue.Enqueue(new Tuple<SendOrPostCallback, object>(d, state));
    }
  }

  class SynchronizationContextBehavoir : MonoBehaviour {
    Queue<Tuple<SendOrPostCallback, object>> callbackQueue
        = new Queue<Tuple<SendOrPostCallback, object>>();

    public Queue<Tuple<SendOrPostCallback, object>>
        Queue { get { return callbackQueue; }}

    IEnumerator Start() {
      while (true)
      {
        Tuple<SendOrPostCallback, object> entry = null;
        lock (callbackQueue)
        {
          if (callbackQueue.Count > 0)
          {
            entry = callbackQueue.Dequeue();
          }
        }
        if (entry != null && entry.Item1 != null)
        {
          try {
            entry.Item1(entry.Item2);
          } catch (Exception e) { 
            UnityEngine.Debug.Log(e.ToString());
          }
        }
        yield return null;
      }
    }
  }
}
Benjamin Wulfe
  • 1,705
  • 13
  • 9
0

We've fixed this issue in the 1.0.1 release: https://firebase.google.com/support/release-notes/unity#1.0.1

You can download the latest release from: https://firebase.google.com/docs/unity/setup

Cheers,

Stewart (from the Firebase team)

  • Hi! I can call unity related methods from CreateUserWithEmailAndPasswordAsync's callback, but if I switch to .net 4.6 and then do a Assets->Play Services Resolver->Version Handler->Update I get "method can only be called from the mainthread". Is this a known issue in .net 4.6 ? Do we need to handle thread synchronization ourselves ? – Reyn Mar 12 '18 at 05:19