1

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)

xjcl
  • 12,848
  • 6
  • 67
  • 89
  • You need this on runtime or in the editor? – derHugo May 27 '21 at 06:57
  • I find it inappropriate to close this question as a duplicate. I explicitly set the field but the process of domain reloading re-constructs objects but not their static fields specifically which is far from obvious. – xjcl Jul 18 '21 at 20:23

2 Answers2

1

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}");
     }
}
DekuDesu
  • 2,224
  • 1
  • 5
  • 19
  • Thanks a lot for your answer! Unfortunately using `this` in the constructor seems to give me an error message: `UnityException: get_gameObject is not allowed to be called from a MonoBehaviour constructor (or instance field initializer), call it in Awake or Start instead. Called from MonoBehaviour 'NwPlayer' on game object 'PlayerCap'. See "Script Serialization" page in the Unity Manual for further details.` – xjcl May 26 '21 at 20:07
  • 1
    Seems like that message was actually caused by me also having the MLAPI `NetworkObject` script attached to the object. When I work around it everything seems to function. Thanks! – xjcl May 26 '21 at 20:13
  • You should not implement any constructor in types derived from `MonoBehaviour`! Unity creates the instances internally and having custom constructors on them might break more than it solves ;) – derHugo May 27 '21 at 07:30
  • Can you give a concrete example of why it is dangerous? I haven't noticed anything breaking. – xjcl May 27 '21 at 08:24
  • 1
    @xjcl *so far ;) The issue is that the constructor of `Monobehaviour`s might be called a lot of times. As said they are created internally by Unity and require a lot of additional information (e.g. the associated `.transform`, `.gameObject`, etc) which might be missing if the type was created via its constructor you implemented (therefore also using `new` is "forbidden"). It might also be called various times because of the continuous (De)Serialization going on in the Editor. In your use-case this might be fine but in general it is quite dangerous and should be avoided at all. – derHugo May 27 '21 at 08:38
0

First of all: You didn't implement any Singleton so far.

The main concept of "Singleton" as the name suggests is to make sure there exists one single instance. In your code there is no control if maybe another instance overwrites the instance field.

Singleton is then only abused to also have public access.

The way you have it is also quite dangerous: Any other script could simply set the field to something else.

When/If I do such thing I would usually use a lazy initialization via property like e.g.

public class SingletonTest : MonoBehaviour
{
    // Here you store the actual instance
    private static SingletonTest _instance;

    // Only allow read access for others
    public static SingletonTest Instance
    {
        get
        {
            // If instance is assigned and exists return it right away
            if(_instance) return _instance;

            // Otherwise try to find one in the scene
            _instance = FindObjectOfType<SingletonTest>();

            // Found one? Nice return it
            // this would only happen if this getter is called before the 
            // Awake had its chance to set the instance
            if(_instance) return _instance;

            // Otherwise create an instance now 
            // this will only happen if you forgot to put one into your scene
            // it is not active/enabled
            // or it was destroyed for some reason
            _instance = new GameObject("SingletonTest").AddComponent<SingletonTest>();

            return _instance;
        }
    }

    public int Value = 42;

    private void Awake() 
    {
        // If another instance exists already then destroy this one
        // This is the actual Singleton Pattern
        if(_instance && _instance != this)
        {
            Destroy(gameObject);
            return;
        }

        _instance = this;

        // Optional: also make sure this instance survives any scene changes
        DontDestroyOnLoad(gameObject);
    }
}

Then if you need such a thing also in the Editor, not in PlayMode/runtime you could use [InitializeOnLoadMethod] (NOT [RuntimeInitializeOnLoadMethod] which as the name suggests is for runtime/PlayMode).

Of course you could have both like

#if UNITY_EDITOR
    [UnityEditor.InitializeOnLoadMethod]
#endif
    [RuntimeInitializeOnLoadMethod]
    private static void Init()
    {
        Debug.Log("Initialized", Instance);
    }

The simple access to the Instance property already makes sure it is initialized.

derHugo
  • 83,094
  • 9
  • 75
  • 115
  • Well, let me explain what I'm trying to accomplish. I have one instance of this script in a scene, so Unity automatically creates exactly one instance of it when the scene is loaded, and I just want to refer to it. – xjcl May 27 '21 at 08:23
  • @xjcl so what's wrong with my answer regarding this? ;) From your question it isn't quite clear if you need this on runtime or in edit mode ... The domain reloading is something only happening in the editor e.g. after a recompilation of your scripts so it sounds like you want this to happen in edit mode – derHugo May 27 '21 at 08:39
  • Doesn't my question and title mention editor mode multiple times? – xjcl May 27 '21 at 12:02
  • well ... isn't that anyway what I assumed? This answer will work also for the edit mode since `UnityEditor.InitializeOnLoadMethod` will make the method get called after each and any domain reload – derHugo May 27 '21 at 12:04