Search code examples
c#unity-game-enginestaticunity3d-editor

Unity: Static reference disappears after domain reload (editor reload)


I have a C# script that boils down to this:

public class SingletonTest : MonoBehaviour
{
    public static SingletonTest Singleton = null;
    public int Value = 42;

    void Start() {
        Singleton = this;
    }
}

This runs fine at first, but my issue is that when I edit a script and then click back into the Unity editor/IDE, I get a whole string of NullReferenceExceptions for the line SingletonTest.Singleton.Value in some other class.

I googled around a bit and this editor reload seems to trigger a process called Domain Reloading (see also). Apparently domain reloading also resets all static fields which explains my error message. I tried a few workarounds suggested on that page but none of them work:

using UnityEngine;

public class SingletonTest : MonoBehaviour
{
    public static SingletonTest Singleton = null;
    public int Value = 42;

    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] 
    static void StaticStart()
    {
        var arr = FindObjectsOfType<SingletonTest>();
        Debug.Log($"Len arr: {arr.Length}");  // This is 0! :(
        if (arr.Length > 0)
            Singleton = arr[0];
    }

    void Start()
    {
        Singleton = this;
    }

    void OnAfterDeserialize()
    {
        Singleton = this;
    }
}

I am able to kind-of get it to work by putting Singleton = this in the Update() function but that solution is ugly, and still gives me a NullReferenceException on the first frame after reload.

(Please don't suggest making the Value field static, that is just a simplified example, I do need/want a singleton class)


Solution

  • Consider using a private constructor to force the static field to be initialized with a value when the SingletonTest object is created by the CLR.

    Although Unity normally doesn't recommend using constructors with MonoBehvior because they're supposed to be scripts(among other Mono and Unity Quirks). I found this works great for my use cases for singletons(such as static editor Dictionary<T>s that hold loaded metadata etc...

    public class SingletonTest : MonoBehaviour
    {
        public static SingletonTest Singleton { get; set; } = null;
    
        public int Value = 42;
    
        protected SingletonTest()
        {
            Singleton ??= this;
        }
    }
    

    Alternatively consider avoiding the assumption that the given field/property is never null.

    For example:

    void Awake()
    {
         // call method example ↓ (null-coalescing operator)
         SingletonTest.Singleton?.Invoke("Something");
         
         // property example
         if(SingletonTest.Singleton != null)
         {
             Debug.Log($"{SingletonTest.Singleton.gameObject.Name}");
         }
    }