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?
Answer by Bilelmnasser · Oct 05, 2017 at 09:12 AM
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 " );
}
}
Hmm. This seems a bit complicated. I envisioned a simpler answer to allow others to use my singletons easily, but if none exists I might have to do something similar to this.
I wanted to avoid CoRoutines if possible (because they are a bit clunky to set up, and make use of my singleton not very friendly to use for other developers.)
I also wanted to avoid execution order, because that's easily forgotten and can become a complicated mess to deter$$anonymous$$e the correct ordering of scripts if there's a bug. I'd rather not rely on a scene setting to solve this.
Your suggested paradigm would work, and seems correct) for Async tasks like network / db calls, but for singleton use it feels very clunky. Would this idea also work for OnEnable usage? (because it currently demonstrates Start, but start is ALWAYS after Awake/OnEnable of all scripts, so singleton use would be perfectly fine).
hi @1337GameDev , i edited the answer, maybe the second one is more easy , it's all depends on your design for the problem you face.
I'll test this structure when I get home tonight. It could work to have callbacks, and could be relatively clean code for other developers to understand and use.
I really wish "OnEnable" was always called after start for every gameobject :( I don't know why unity put it before... (in other methods, you can simply check if the current gameobject is enabled before invoking your code).
Or maybe a static object in your conpoment class can do the trick , look at my answer here: static object to hold point of access to all your methods
I'm not a fan of statics in component classes unless it's something explicitly needed for the state of the component (and not just an object reference).
here you have a good example to singleton implementation in unity :
Singleton Implementation for Unity $$anonymous$$onobehavior On the UnityWiki Page
Answer by SirCrazyNugget · Oct 05, 2017 at 05:47 PM
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.
Why shouldn't I use the unity method? It interacts with unity, and can be configured in the inspector like other $$anonymous$$onoBehaviors.
You can use any Unity method though it wouldn't be a singleton.
Object B wants to access Object A during OnEnable is already more information to show it's designed incorrectly. Object A, if truly a singleton, doesn't need to know when it's accessed, only that it can be at any point. Trying to deter$$anonymous$$e the ti$$anonymous$$g of its availability is a huge flaw.
What do you mean "won't be a singleton"
It's simply an object, constrained by constructor / access to only have 1 instance of itself at all times.
And true, AFTER initialization of the singleton, my singleton doesn't care when it's accessed. It's only the first iteration of Awake->OnEnable that my Singleton cares about, because it has it's own initializations to do.
I can't just simply make it not a monobehavior, because I want it able to be modified by the inspector in the scene before game start as well as able to be used by core unity functions (non monobehavior classes can't be).
Answer by Pepeco159 · Jul 28, 2020 at 12:57 PM
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.
Your answer
Follow this Question
Related Questions
Start is not called after reloading the level 3 Answers
NullReferenceException: Object reference not set to an instance of an object in Singleton class. 1 Answer
Custom Editor need for Awake function called when game starts 0 Answers
String variable declared in editor goes blank at runtime 0 Answers
Class array initialization on Start() gives -> Object reference not set to an instance of an object 1 Answer