petitviolet blog

    How to use Firestore SDK in WebGL built by Unity

    2022-10-15

    UnityFirestoreC#

    Firebase offers Cloud Firestore which is a well-known NoSQL document datastore that has been widely used in particularly mobile app development.

    One of the key features of Firestore I think is realtime-ish data propagation through Firestore SDK. When a client is subscribing a (sub-)collection and new documents being added to it, the documents are pushed to the client in streaming fashion.

    Unity can use Firestore via official SDK including various platforms like Android and iOS, however, WebGL is not supported for the time being.

    https://github.com/firebase/firebase-unity-sdk/tree/47295d3610304a13b4491cb9dd2347daaec9aa1f#building

    The only way to use Firestore SDK in Unity WebGL is using Firestore JavaScript SDK. This post describes how to do that and some tips in addition to basic usage of Firestore JS SDK from Unity WebGL.

    How to use JS in Unity WebGL

    Since WebGL is basically a web technology based on HTML and JavaScript, we can write JS code to manipulate WebGL objects as we want. But, it's not an easy way. An alternative way is using JavaScript code in .jslib extension.

    https://docs.unity3d.com/Manual/webgl-interactingwithbrowserscripting.html

    A .jslib file will be available when it's placed in Assets/YourAssembly/Plugins/ directory. Then, C# files can run JavaScript functions through [DllImport("__Internal")] annotation.

    To subscribe new documents to a collection, write .jslib code like below:

    firestore.jslib
    mergeInto(LibraryManager.library, {
      OnSnapshotCollection: function (collectionPath, objectName, onUpdate) {
        const _objectName = UTF8ToString(objectName);
        const _onUpdate = UTF8ToString(onUpdate);
        firebase
          .firestore()
          .collection(UTF8ToString(collectionPath))
          .onSnapshot(function (snapshot) {
            snapshot.docChanges().forEach((change) => {
              if (change.type === "added") {
                const data = JSON.stringify(change.doc.data());
                unityInstance.Module.SendMessage(_objectName, _onUpdate, data);
              }
            });
          });
      },
    });
    

    Key points are:

    • String arguments should be cast into JavaScript object via UTF8ToString
    • UnityInstance should be globally available in WebGL html
      • window.unityInstance = ... is required beforehand
    • An Object(or GameObject) named objectName should be available in the active scene

    C# implementation to use the function OnSnapshotCollection defined in the .jslib file looks like the below code snippet.

    firestore.cs
    using UnityEngine;
    
    namespace Firebase
    {
        public static class Firestore
        {
    #if UNITY_WEBGL && !UNITY_EDITOR && !UNITY_INCLUDE_TESTS
            [DllImport("__Internal")]
            public static extern void OnSnapshotCollection(string collectionPath, string objectName, string callbackMethodName);
    #else
    
            public static void OnSnapshotCollection(string collectionPath, string objectName, string callbackMethodName)
            {
                Debug.Log($"OnSnapshotCollection {collectionPath} called.");
            }
    #endif
        }
    }
    

    That's it. A function marked with DllImport annotation can use .jslib function that has the same name, OnSnapshotCollection in this example. The reason why #if macros needed is that when calling OnSnapshotCollection in UnityEditor, .jslib can't work since it's actually not in WebGL. If you can install Firebase Unity SDK in addition to JS SDK, #else block can use the native SDK instead.

    To get updated documents that .jslib function notify through unityInstance.Module.SendMessage, an Object that is named objectName and has a function named callbackMethodName should exist in the active scene in Unity.

    As an example, Receiver#receive is the Object to be receiving notifications of updated firestore documents.

    receiever.cs
    namespace YourNamespace
    {
        public class Receiever : MonoBehavior
        {
            public void Receive(string data)
            {
                Debug.Log("[Receive]received: {data}")
            }
        }
    }
    

    If Receiver object named myReceiver exists in hierarchy, how to use OnSnapshotCollection would look like:

    Firebase.Firestore.OnSnapshotCollection("/my-collection/my-doc/sub-collection", "myReceiver", "Receive");
    

    Then, the .jslib function starts subscribing given collection that new documents to be added and notify them to myReciever.