Search Unity

Unity Event Function Execution Order Unexpected Behavior

Discussion in 'Scripting' started by owen_proto, May 18, 2020.

  1. owen_proto

    owen_proto

    Joined:
    Mar 18, 2018
    Posts:
    120
    My game manager singleton is instantiated in it's Awake method, but when I try to access it from OnEnable in another script it returns null. It does not return null in Start or afterwards. This seems to contradict the graphic in the documentation about execution order, though the text doesn't explicitly say one way or the other. However, it seems to imply Awake is called before any OnEnable functions by the way Awake is listed first.
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,742
    Have you tried using one of the various boilerplate UnitySingleton<T> classes out there, like on wikis and all? There's a ton of subtle variations among them but basically you never instantiate, just access the instance, and things get loaded / created.

    If you need finer-grained control of that, just make a loader scene that you always start from and spin things up in the order you want.

    As for Awake vs OnEnable, not sure what to tell you. I agree that it appears Awake comes first, BUT they are grouped in a single gray box, so maybe both are called at once, then other scripts go?? It's super-easy to test with some Debug.Log() statements.
     
  3. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,537
    I assume you mean you set the static field that stores a ref to your singleton in Awake. You most certainly didn't instantiate it during Awake since it was already instantiated for Awake to even be called on it.

    Annoyingly Awake/OnEnable are called in succession BEFORE any other Start.

    The graphic really only pertains to a single object and doesn't really convey the relationship between scripts.

    When an object is created it'll have Awake/OnEnable called in succession (of course as long as the object is enabled), then it moves to the next object. It does this for every object... Awake/OnEnable. Then once all objects have been created and initialized like this, it then goes back through the list and calls Start on all of them.

    This is "sort of" implied visually by the clustering in the image:
    upload_2020-5-18_18-23-43.png
    Note how Awake/OnEnable are in their own little coloured grouping, then Reset, then Start.

    But it's not super clear IMO. And it's also SUPER ANNOYING.

    It makes OnEnable ordering super annoying to deal with. I have things I want to register in OnEnable, and unregister in OnDisable... but since OnEnable behaves more like Awake the first time it's called, and only the first time, all others are safe... with the exception of it was disabled on instantiation in which case OnEnable will be called at some later time if enabled and is safe.

    I always ended up having to do a check for if we were started yet.

    So basically what's going on right now is that your singleton may be setting the variable in Awake. But the OnEnable of your other script was called before your singleton has had Awake called.

    There's ways around this... 1 is well, set the execution order of your singleton to a really low number:
    https://docs.unity3d.com/Manual/class-MonoManager.html

    Or, how I worked around it, because I think the timing of OnEnable before Start is dumb. I created my own method called "OnStartOrEnable" where it is called either on start (which means it's enabled) or any time OnEnable is called AFTER start. The original version from years ago of this was done simply with a base component type I called SPComponent:
    Code (csharp):
    1.  
    2.         protected virtual void Start()
    3.         {
    4.             _started = true;
    5.             //this.SyncEntityRoot();
    6.             this.OnStartOrEnable();
    7.             if (this.OnStarted != null) this.OnStarted(this, System.EventArgs.Empty);
    8.         }
    9.  
    10.         /// <summary>
    11.         /// On start or on enable if and only if start already occurred. This adjusts the order of 'OnEnable' so that it can be used in conjunction with 'OnDisable' to wire up handlers cleanly.
    12.         /// OnEnable occurs BEFORE Start sometimes, and other components aren't ready yet. This remedies that.
    13.         /// </summary>
    14.         protected virtual void OnStartOrEnable()
    15.         {
    16.  
    17.         }
    18.  
    19.         protected virtual void OnEnable()
    20.         {
    21.             if (this.OnEnabled != null) this.OnEnabled(this, System.EventArgs.Empty);
    22.  
    23.             if (_started)
    24.             {
    25.                 this.OnStartOrEnable();
    26.             }
    27.         }
    28.  
    https://github.com/lordofduct/spacepuppy-unity-framework/blob/master/SpacepuppyBase/SPComponent.cs

    Now a days I have a mixin technique of doing it (a little more involved, I would go with the former for ease of implementation):
    https://github.com/lordofduct/space...master/SpacepuppyUnityFramework/IMixin.cs#L86

    I created this new mixin version so that way I can have my 'late' versions as well and not have a bunch of extra work happening on all my SPComponents, but only the ones that implemented the appropriate mixin.
     
  4. owen_proto

    owen_proto

    Joined:
    Mar 18, 2018
    Posts:
    120
    Yes, you're right. It's a MonoBehaviour, so it exists on a GameObject. Here is my implementation:

    Code (CSharp):
    1. public class GameManager : MonoBehaviour
    2. {
    3.     private static GameManager instance;
    4.     public static GameManager Instance { get { return instance; } }
    5.  
    6.     private void Awake()
    7.     {
    8.         if (instance != null && instance != this)
    9.             Destroy(this.gameObject);
    10.         else
    11.             instance = this;
    12.     }
    13. }
    Thanks for sharing your solution. I didn't want to have to mess with script execution order as it makes the code less portable/debuggable. It's good to know if I want to get the functionality I expect that I will have to add a check. Simple enough if a bit inelegant. Having a method that is called when a GameObject is enabled is nifty, perhaps it should be completely divorced from pre-start execution or given the full compliment of a pre-start event function features (ie. it's own execution frame). Thanks again!
     
  5. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,537
    It's one of those things that's just been that way in Unity for ages. And changing it now would cause code that expects it to behave that way to not act correctly.

    Effectively it's a legacy support issue.

    You're going to run into a LOT of those... like the == null thing. Where any UnityEngine.Object that has been destroyed will evaluate true with == null if the variable is types as UnityEngine.Object or a subtype there in... but if you have it typed as say an interface, or System.Object, it'll evaluate false since the C#/managed object still exists. What's really going on is that Unity overloads the == operator for UnityEngine.Object to evaluate true for null if it's destroyed, but since you cast the object to a none UnityEngine.Object type (object, interface), it doesn't know to use that overload.

    They've considered fixing it, but won't because it'll break existing code.
     
  6. mahdiii

    mahdiii

    Joined:
    Oct 30, 2014
    Posts:
    856
    As mentioned, OnEnable can run before Awake. It is like Awake1,OnEnable1,Awake2,OnEnable2,...,Start1,Start2
    I do not know why you want to access the singleton reference in OnEnable of that script
    You can use script execution order but it is messy
    Also, @lordofduct suggestion is perfect but my strategy is to avoid using singleton, then avoid accessing it in OnEnable or Awake of another script.
    Also you can write better singleton class with lazy approach
     
  7. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,742
    In the intervening year or so since I wrote the above, I have amended my thoughts on this to say "Keep it even simpler," and I no longer see the value of a universal singleton<T> style boilerplate. This is my current answer for such things:

    Simple Singleton (UnitySingleton):

    Some super-simple Singleton examples to take and modify:

    Simple Unity3D Singleton (no predefined data):

    https://gist.github.com/kurtdekker/775bb97614047072f7004d6fb9ccce30

    Unity3D Singleton with a Prefab (or a ScriptableObject) used for predefined data:

    https://gist.github.com/kurtdekker/2f07be6f6a844cf82110fc42a774a625

    These are pure-code solutions, do not put anything into any scene, just access it via .Instance!

    If it is a GameManager, when the game is over, make a function in that singleton that Destroys itself so the next time you access it you get a fresh one, something like:

    Code (csharp):
    1. public void DestroyThyself()
    2. {
    3.    Destroy(gameObject);
    4.    Instance = null;    // because destroy doesn't happen until end of frame
    5. }