Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Awake, Start and OnEnable walked into a bar

Discussion in 'Scripting' started by besttof, Oct 28, 2014.

  1. besttof

    besttof

    Joined:
    Jul 17, 2012
    Posts:
    29
    *Insert joke about the order they walk in here*

    I was wondering if someone could enlighten me on the rationale behind the way OnEnable gets called? I don't seem to be able to wrap my mind around it and constantly fall into the trap of assuming that OnEnable is always called after the Start message (instead of, you know, on creation before start and runtime probably after start).

    For me the sensible usage for each message is more or less like this:

    Awake() -> Initialize stuff related to this script only, can't rely on the outside world to exist yet
    Start() -> Connect stuff to other objects/scripts etc. Basically say hi to the outside world
    OnEnable() -> Pair with OnDisable, subscribe to events, use for pooling/recycling objects

    The last statement is a problem though because it often ends up relying on something from the outside world. Which is fine as long as the object starts disabled. But that is quite cumbersome (for me anyway).

    So, does anybody know why OnEnabled is called in the same "phase" as Awake when an object is created instead of always calling it in the same "phase" (and always after) Start?
     
    Kurt-Dekker and ModLunar like this.
  2. A.Killingbeck

    A.Killingbeck

    Joined:
    Feb 21, 2014
    Posts:
    483
  3. besttof

    besttof

    Joined:
    Jul 17, 2012
    Posts:
    29
    Yes, I know that graph (there is an official one in the scripting reference too). But the thing I wanted to know if why OnEnable is called directly after Awake (also see Walt D's comment in that article) when an object is instantiated instead of directly after Start?

    It just seems to make little sense, there is already a constructor-like function (Awake), so why add another message that sometimes functions as Awake (i.e. the outside world is unreliable, might not be constructed yet, etc. etc.) and sometimes like Start (i.e. the outside word is constructed). It just seems to make the OnEnable/OnDisable pair less usable (because you can't rely on anything that is initialised/set up in the Start message)...
     
  4. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,514
    I don't know why. But I agree, I find it really annoying.

    I have my own custom component that I inherit all my components from that I call SPComponent (SP for Spacepuppy, the name of my framework).

    In it I have a method called 'StartOrEnable' that is called either on start OR on enable after it's been started (so that first enable isn't called) because I often find myself checking if started yet.

    Check it out here:
    https://code.google.com/p/spacepupp...trunk/SpacepuppyUnityFramework/SPComponent.cs

    There's several things in there going on. You can ignore the notificationdispatcher part, and the IComponent part. You are interested in what's in the CONSTRUCTOR region.

    Here it is with all the stuff you don't need trimmed out:

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections.Generic;
    4. using System.Linq;
    5.  
    6. using com.spacepuppy.Utils;
    7.  
    8. namespace com.spacepuppy
    9. {
    10.  
    11.     public abstract class SPComponent : MonoBehaviour
    12.     {
    13.  
    14.         #region Fields
    15.  
    16.         [System.NonSerialized]
    17.         private bool _started = false;
    18.  
    19.         #endregion
    20.  
    21.         #region CONSTRUCTOR
    22.  
    23.         protected virtual void Start()
    24.         {
    25.             _started = true;
    26.             this.OnStartOrEnable();
    27.         }
    28.  
    29.         protected virtual void OnEnable()
    30.         {
    31.             if (_started) this.OnStartOrEnable();
    32.         }
    33.  
    34.         /// <summary>
    35.         /// 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.
    36.         /// OnEnable occurs BEFORE Start sometimes, and other components aren't ready yet. This remedies that.
    37.         /// </summary>
    38.         protected virtual void OnStartOrEnable()
    39.         {
    40.  
    41.         }
    42.  
    43.         #endregion
    44.  
    45.         #region Properties
    46.  
    47.         /// <summary>
    48.         /// Start has been called on this component.
    49.         /// </summary>
    50.         public bool started { get { return _started; } }
    51.  
    52.         #endregion
    53.  
    54.     }
    55.  
    56. }
    57.  
     
    Last edited: Oct 28, 2014
  5. Kirlim

    Kirlim

    Joined:
    Aug 6, 2012
    Posts:
    126
    As far as I can tell, the order is Awake->Enable->Start

    Dirty dirty tricky then! Is the object active? Awake will always be called! Even if you instantiate the object deactivated. Now OnEnable will always be called before Start (as far as my experience goes). It gets trickier when you realized that OnEnable is called immediately after Awake, before any other object Awake has the chance of being called, making code sync of OnEnable with Awake of other instances of the same component type (or other types) quite complex.

    So, my theory is:
    (if active component and object instantiated)
    Awake + Enable (one phase)
    Start (other phase)

    (if inactive)
    Awake (first phase)
    (when activated)
    OnEnable(second phase)
    OnStart(third phase)

    Where "phase" is a batch of the same "method" of all other components, following scripts execution order.

    Edit: Awake is the "sole" constructor. Start and OnEnable can or not be initializers, depending on your context. But this does not mean their purpose is to be initializers. OnEnable and OnDisable can be used to automatic pools updating, automatic events registering/unregistering... anything that you might do with a type that will need specific behavior every time it is activated or deactivated - without creating new instances. Some things are just too undesirable to depend on Awake/Start and OnDestroy...
     
    Last edited: Oct 29, 2014
  6. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,514
    The problem is that OnEnable happens along with Awake if enabled. It's an out of order message. So if you were to access other objects, they may not be initialized and ready.

    The point of Awake and Start is that Awake you should initialize the self, Start you access others. As Start is done after all components have been called Awake.

    This means that OnEnable may work accessing other components in some scenarios, and won't work in others, depending on the startup situation. So if I want to register an event with another component, I have to do it either on Start the first time, and then OnEnable all other times, but not OnEnable the first time if not started yet.

    This is why I created the 'StartOrEnable' method for this purpose. It remedies that whole problem.
     
  7. besttof

    besttof

    Joined:
    Jul 17, 2012
    Posts:
    29
    @lordofduct That is a solution to the problem, yes, and possibly something I might end up with as well. The thing is, and I feel you agree with me even if you don't know why, I can't think of an advantage to calling OnEnable the way it is called in Unity right now. The way your base behaviour works makes much, much more sense (especially with events).

    So the question remains: why is the order of Monobehaviour's OnEnable in relation to Awake and Start designed like this?

    @Kirlim I know the details of the order in which these messages are called, which is the whole point actually. It doesn't make sense to me and I'd love to be enlightened ;)
     
  8. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,514
    Well, as I said before, I don't know why.

    None of us have access to the actual source, so we can't really say why really. (note, it's on the unmanaged side of things, not in the .net dll's. They just do internal calls. And I bet anyone with source access outside of Unity employees aren't allowed to say (I'm betting it's baked into the source code license that you can't divulge the details of implementation).

    I could make guesses as to why.

    I'm betting that the message OnEnable is called when 'Behaviour.enable' is set. And if that's so, an enabled component gets its 'enabled' property set while it's being created. Which is why it happens out of order and just after Awake. It's order just falls out of the process.

    A change to it at this point would probably 1) require extra code to check, similar to how I do and 2) would break backward support as fundamental functionality has changed.

    That's the thing about live software. A small design choice early in the life can impact the life of the project later. Do you change, knowing you'll break existing code in the community?
     
  9. besttof

    besttof

    Joined:
    Jul 17, 2012
    Posts:
    29
    @lordofduct Sorry, I didn't expect you to know why, I'm secretly hoping that a Unity dev will pop in an shed a little light on it :)

    And I agree that changing it could potentially wreak havoc on existing projects and libraries; I'm not really asking for that*, I'm just curious about the rationale...

    *though it would've been nice if Unity 5 addressed this, but as far as I can see the current beta still has the same behaviour
     
  10. vdogamr

    vdogamr

    Joined:
    Feb 23, 2018
    Posts:
    15
    I know this is an old thread but it is in line with my suggestion.
    Initializing an object from a prefab, and initializing it from a disabled object you have stored in a pool don't have the same startup sequence, as seen here:

    FROM PREFAB VARIABLE
    1) Some code...
    2) Behavior beh = GameObject.Instantiate<Behavior>(prefab)
    3) Behavior::Awake()
    4) Behavior::OnEnable() << Can't use my variables yet
    5) beh.Variables = values;
    6) Some code...
    6) Behavior::Start() << Now I can use my variables
    7) Behavior::Update()

    FROM OBJECT POOL
    1) Some code...
    2) Behavior beh = somePoolCode.GetDisabledObject()
    3) beh.Variables = values;
    2) beh.SetActive(true);
    3) Behavior::OnEnable() << This time I can use my variables
    4) Some code...
    << Start() is not called
    5) Behavior::Update()

    If I want to set some variables to control the behavior of a Behavior, there is no good place to put the combined setup code. I have many ways I can/do work around this, but the real solution is...

    "Please make Start() always be called before the first update after an OnEnable() is processed"

    Again there are a lot of work arounds, but none of them need to exist if we can just assume that Start() is always called after we had a chance to set our variables, after all the other components have been enabled, and before the object is shown for the first time (after having not been shown in a while or ever).

    It would be nice, and a lot easier to explain, if the flow was always: OnEnable(), programmer control, Start(), Repeated Update()s

    Thank you for reading. I hope you honestly consider it. Sorry for posting to an old thread.
     
  11. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,923
    This makes no sense since Start is only called once for the lifetime of the object. Start is never called twice. OnEnable and OnDisable are not related to the object creation at all. They are called directly from the enabled property internally. Just try code like this:

    Code (CSharp):
    1. someScript.enabled = true;
    2. someScript.enabled = false;
    3. someScript.enabled = true;
    4. someScript.enabled = false;
    5. someScript.enabled = true;
    6. someScript.enabled = false;
    7. someScript.enabled = true;
    It will call OnEnable and OnDisable every time the enabled state changes. This happens immediately before the property setter returns. If the object is enabled when it's constructed / deserialized the enabled state will be set exactly at that point in time. It would not make any sense for OnEnabled to be called after Start. Start is heavily delayed. So your user code could enable and disable the component several times manually before Start is even executed.

    If you want to use OnEnabled to initialize your object on demand, you should disable or deactivate the prefab object so it's not enabled when you instantiate it.

    Awake and OnDestroy are related to the object creation. OnEnabled and OnDisabled are just callbacks when the enabled state changes. Start is just a late initialize callback.

    If you need code that runs after you instantiated an object, just implement your own method that you call when you want. That's better for performance and gives you 100% control over when the method is executed.