Search Unity

Script Execution Order on Instantiated Objects

Discussion in 'Scripting' started by IronHelmet, Apr 6, 2020.

  1. IronHelmet

    IronHelmet

    Joined:
    May 2, 2017
    Posts:
    85
    Hi Folks,

    I'm building a procedurally generated game. My scenes are empty and then when the user clicks a button I instantiate heaps of game objects to form the level and everything needed for game play. (All in the same frame)

    Is it true that I can no longer depend on the Start functions being called before Update functions of these newly instantiated game objects?
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,692
  3. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    Start will always occur before any call to Update, for that object.

    Of course other objects may have already had Update called (even if that object is the same type). But that's all to do with when they were instantiated relative to one another.

    With in a single given instance though, Start will always occur first, just like the link Kurt-Dekker posted suggests.

    If you want to see this in action.. attach this script to a gameobject in an empty scene:
    Code (csharp):
    1.  
    2. public class zTest01 : MonoBehaviour
    3. {
    4.  
    5.     private bool _firstUpdateOccurred;
    6.  
    7.     void Awake()
    8.     {
    9.         _firstUpdateOccurred = false;
    10.         Debug.Log(string.Format("AWAKE: {0} : FRAME {1}", this.GetInstanceID(), Time.frameCount), this);
    11.     }
    12.  
    13.     void OnEnable()
    14.     {
    15.         Debug.Log(string.Format("ENABLED: {0} : FRAME {1}", this.GetInstanceID(), Time.frameCount), this);
    16.     }
    17.  
    18.     void Start()
    19.     {
    20.         Debug.Log(string.Format("START: {0} : FRAME {1}", this.GetInstanceID(), Time.frameCount), this);
    21.     }
    22.  
    23.     void OnDisable()
    24.     {
    25.         Debug.Log(string.Format("DISABLED: {0} : FRAME {1}", this.GetInstanceID(), Time.frameCount), this);
    26.     }
    27.  
    28.     void OnDestroy()
    29.     {
    30.         Debug.Log(string.Format("DESTROYED: {0} : FRAME {1}", this.GetInstanceID(), Time.frameCount), this);
    31.     }
    32.  
    33.     void Update()
    34.     {
    35.         if(!_firstUpdateOccurred)
    36.         {
    37.             _firstUpdateOccurred = true;
    38.             Debug.Log(string.Format("FIRST UPDATE: {0} : FRAME {1}", this.GetInstanceID(), Time.frameCount), this);
    39.         }
    40.  
    41.         if(Input.GetKeyDown(KeyCode.Space))
    42.         {
    43.             Debug.Log(string.Format("SPAWN REQUESTED: {0} : FRAME {1}", this.GetInstanceID(), Time.frameCount), this);
    44.             var other = Instantiate(this);
    45.             Debug.Log(string.Format("SPAWNED: {0} BY: {1} : FRAME {2}", other.GetInstanceID(), this.GetInstanceID(), Time.frameCount));
    46.             Destroy(this.gameObject);
    47.         }
    48.     }
    49.  
    50. }
    51.  
    And you'll get:
    upload_2020-4-6_14-5-21.png

    You'll notice when we instantiate a new object on a key press, Instantiate blocks through the Awake/Enable messages. Then Start is called while still on this same frame. But Update on the new object isn't called until NEXT frame (this is why we can actually do this without worrying the space bar input won't create an infinite loop on instantiated its clones).

    It could be argued though that the link doesn't tell the whole story of how 'Start' is called. Since you can tell objects newly created that frame will actually have Start called between OnDisable and OnDestroy of other objects... rather than at the beginning of the next frame. The link is really describing how objects have their initialization methods called on scene load.

    ...

    On a tangent... I hate how OnEnable is called. After Awake but before Start? This is annoying since Start is used for state initialization (as Unity says, use Awake to initialize self, Start to interact with other objects). Well what if you want to register stuff with other objects when enabled, and unregsiter when disabled. Well you can't... OnEnable occurs relative to Awake, so other other objects may not have Awake called on them.

    You end up wanting to use Start for the first time, and OnEnable for all subsequent times. So you gotta know if it's the first time or not! I came up with a work around... but dang it, why!?
     
    Last edited: Apr 6, 2020
  4. IronHelmet

    IronHelmet

    Joined:
    May 2, 2017
    Posts:
    85
    "Of course other objects may have already had Update called (even if that object is the same type). But that's all to do with when they were instantiated relative to one another."

    If updates are not called until the next frame, how can some objects get an update call before the start of others if they are all instantiated in the same frame.

    Is instantiate somehow asynchronous?
     
  5. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    I meant other objects that already existed at that point. Like objects created N frames ago. Their updates are firing as usual. I was trying to emphasize we're talking about Update in relative terms to THIS object, and not ALL objects.
     
  6. Joe-Censored

    Joe-Censored

    Joined:
    Mar 26, 2013
    Posts:
    11,847
    When you instantiate all Awake methods on those GameObjects are called immediately. All Start methods are called after the current frame, but before the next frame. All Update methods are called on the next frame.

    The only exception to this I've ever seen was some nonsense from Unet spawned networked GameObjects where Update could actually be called before Start. But Unet is dead and basically gone so don't worry about that.
     
  7. IronHelmet

    IronHelmet

    Joined:
    May 2, 2017
    Posts:
    85
    Say we spawn objects A, B, and C all at the same time in a single frame.

    I'm pretty sure we are seeing Update on C being called before the Start of A.

    We are doing things like, A connecting to B in Start, and then C assumes A's connection to B to be in place during the Update call, but its not yet.
     
  8. ianswerquestions20

    ianswerquestions20

    Joined:
    Apr 7, 2020
    Posts:
    22
    There has to be something else going on in your code. As stated above, if you truly instantiate A, B and C in a single frame, the Start methods will always run before the Update methods in each class. Could you possibly post some code?
     
  9. IronHelmet

    IronHelmet

    Joined:
    May 2, 2017
    Posts:
    85
    I don't have a reliable repo case. We have quite a big project and have seen the problem only three times, and even in those cases the problem only popped up rarely. A colleague said that they had read a forum post here somewhere that the start / update timing could not be relied upon when you instantiate stuff.

    I'll investigate further next time we see the issue.
     
  10. JohnnyA

    JohnnyA

    Joined:
    Apr 9, 2010
    Posts:
    5,041
    Once you reach a certain complexity I think its better to just track initialisation yourself. This can be as simple as a bool that early exit from Update:

    if (!initialised) return;

    I often extend all my classes from my own MonoBehaviour subclass as a simple way to get useful helper functions and structure.

    In this case it is a tiny bit of plumbing that helps with several different use cases, some obvious ones being:

    - supporting initialisation that takes a long time and needs to be done over multiple frames or pushed to a thread
    - reusing objects in a pool