Access singleton in OnEnable when it's initialized in Awake

I have a singleton class (let’s call it A) that has some initialization code in Awake().

I have a separate class (We’ll call it B), that wants to use the singleton A in OnEnable, so every time the B is enabled (B could be toggled throughout the game) I want it to use this Singleton.

The problem is that OnEnable is called alongside Awake for every object, and having code in OnEnable does not guarantee that ALL other objects’ Awake methods have been called.

From my understanding, unity only guarantees that (for a particular Monobehaviour) it calls functions in the order of Awake, OnEnable, Start.

If I have Object1, and Object2 with their own functions, they’ll be called like the following:

  • Object1.Awake()

  • Object1.OnEnable()

  • Object2.Awake()

  • Object2.OnEnable
    – THEN –
    (in any order)

  • Object1.Start()

  • Object2.Start()

Code in OnEnable cannot assume ALL other Awake calls have been made, only that THIS object’s Awake call has.

How do I get around this?

I’ve tried the following:

public class Object1 {
    private bool HasInitialized = false;
    public void OnEnable() {
        if(HasInitialized) {
            Singleton.Method();
        }
    }

    public void Start() {
        if(!HasInitialized) {
            Singleton.Method();
        }

        HasInitialized = true;
    }

}

But it seems very clunky and cumbersome to write on EVERY class that want’s to use a singleton.
Is there a better way to go about this?

hi,
the Awake, OnEnable and Update functions of different scripts are called in the order the scripts are loaded (which is arbitrary). However, it is possible to modify this order using the Script Execution Order settings (menu: Edit > Project Settings > Script Execution Order).

otherwise if the execution time is long and you need to wait to all tasks are finished . (sometime depends on other factors (exemple network or any other job)), i can use Couroutine on Start function to wait until my other object was successfully initialized or refreshed (example retrieved database data )

SOLUTION 1 :

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class A : MonoBehaviour {

    public bool _awakened = false;
    public bool _enabled=false;
    public bool _Started= false;

    private void Awake()
    {
        //do jobs
        Debug.Log( "object A is Awakened" );
        _awakened = true;
    }

    private void OnEnable()
    {
        //do jobs
        Debug.Log( "object A is Enabled" );
        _enabled = true;
    }

    private IEnumerator Start()
    {
        // wait some time for simulated job
        yield return new WaitForSeconds( 10 );
        //do jobs
        _Started = true;
    }

   
}

and i have a class B that use class A but need A to be ready before using it :

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class B : MonoBehaviour {

    public A ReferenceObjectA;

	// Use this for initialization
	IEnumerator Start () {
        yield return new WaitUntil( () => ReferenceObjectA._enabled && ReferenceObjectA._awakened && ReferenceObjectA._Started );
        Debug.Log( "ReferenceObjectA is awakened and enabled and started !!" );
        // do my logic game  
	}
}

Edit (SOLUTION 2): another idea come across my mind, is to use Delegates , or UnityEvent , you can implement the next same logic with both of them [the object him self will call the
other object(s) that he is ready ]
(OnEnabled can be static member depends on your need )

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

public class A : MonoBehaviour {
//   UnityEvent   that  well   invoke   when   we   are   ready,   it   may   be   a   static  if   you   want
    public UnityEvent OnEnabled;


    private void OnEnable()
    {
        //invoke  all  UnityAction   attached   to  this  UnityEvent 
        OnEnabled.Invoke( );

       
    }
}

and then you have a class B that you want her to execute some code when an object A said he is ready.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class B : MonoBehaviour {
    public A ReferencedAObject;
	// Use this for initialization
	void Awake () {
        Debug.Log( "B Awake  ");
       
        ReferencedAObject.OnEnabled.AddListener( When_A_is_Ready );
    }

    //this   will  be   called   by  object   A  him  self
    public void   When_A_is_Ready()
    {

        //   job   that   will   be   excuted  when   A   call  OnEnabled
        Debug.Log( "A  is  now   ready "  );
    }

}

I had a similar issue with the generic singleton class I am using, so I decided to add some more info for those who want to know why this implementation is breaking the codes and how to prevent those problems.

First, take a look at the code below(the one causing problems). Notice that we use unity’s Awake call to ensure that only one instance of the class is created.

using UnityEngine;

public class Singleton<T> : MonoBehaviour where T : Singleton<T>
{
    private static T _instance;
    public static T Instance
    {
        get{return instance;}
    }

    public static bool isInicialized
    {
        get{return _instance != null;}
    }

    // Start is called before the first frame update
    protected virtual void Awake()
    {
        if(_instance != null)
        {
            Debug.LogError("[Singleton] Trying to create a second instance of singleton ", this);
        }
        else
        {
            _instance = (T) this;
        }
    }

    protected virtual void OnDestroy()
    {
        if(_instance == this)
        {
            _instance = null;
        }
    }
}

In this implementation, you’ll need to attach the script to a gameObject on the editor by yourself in order to use it.

We can see that it becomes a big problem since we cannot control the order that the scripts Awake’s will be called, and quite often you’ll see that scripts that have onAwake or onEnable references to the singleton will break.

Now let’s see a implementation that doesn’t use Awake.

public class Singleton<T> : MonoBehaviour where T : Singleton<T>
{
    private static T _instance;
    public static T Instance
    {
        get
        {
            if(_instance == null)
            {
                var objs = FindObjectsOfType(typeof(T)) as T[];

                if (objs.Length > 0)
                    _instance = objs[0];

                if (objs.Length > 1)
                {
                    Debug.LogError("[Singleton] There is more than one instance of " + typeof(T).Name + " in the scene.");
                }

                if (_instance == null)
                {
                    GameObject obj = new GameObject();
                    obj.hideFlags = HideFlags.DontSave;
                    _instance = obj.AddComponent<T>();
                }
            }

            return _instance;

        }
    }

    public static bool isInicialized
    {
        get{return _instance != null;}
    }

    protected virtual void OnDestroy()
    {
        if(_instance == this)
        {
            _instance = null;
        }
    }
}

Here we used the getter to ensure that anytime a script tries to access the Instance there will be a reference.
We could even destroy the other instances if found more than one, though Logging error seems enough to me.

For the singleton pattern you shouldn’t be using any of Unity’s built-in functions as an initialiser. The getter of the instance should always ensure there is one and only one ever this one returned.

MSDN Singleton in C#