Search Unity

The correct way to handle script execution order?

Discussion in 'Scripting' started by FeastSC2, Oct 13, 2017.

  1. FeastSC2

    FeastSC2

    Joined:
    Sep 30, 2016
    Posts:
    978
    I often get issues where my scripts are not running in the order I want, I roughly understand how Unity works with the components execution order but I woud like not to rely on "Edit > Project Settings > Script Execution Order" because I think it's gonna end up being messy.

    What's the proper way to do it through code that doesn't use shenanigans like OnEnable(), Awake(), Start()?
     
    Tymianek likes this.
  2. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    I was with you on not relying on Project settings, unless you absolutely have to for good reason.

    You completely lost me with the sentence "Shenanigans like OnEnable, Awake, and Start" lol.
    Those are very important. You should use them :)
     
    StarManta likes this.
  3. FeastSC2

    FeastSC2

    Joined:
    Sep 30, 2016
    Posts:
    978
    Haha, yes I mean they're useful but I don't want to use them to decide which script should start before the other one. Basically they're not supposed to be used for taking care of the script execution order. I do use them of course.
     
  4. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    Ah okay.. well, in that case..
    Generally, you want to setup anything on the game object/script in Awake and stuff that relies on other scripts being "awake" in Start.
    I guess you already know that, too.. Beyond that, I'm not sure what issue(s) you're having.

    There are a few situations where I will yield a frame at the start to do some setup..

    Can you give an example of a problem you face, maybe that would help for some answers/insight?
     
    ecssiah likes this.
  5. FeastSC2

    FeastSC2

    Joined:
    Sep 30, 2016
    Posts:
    978
    Okay, I guess keeping in mind that Awake should be always used to GetComponent and instantiating stuff is a good idea.

    Well the example I last remember was with OnEnable() and OnDisable() I wanted to register/unregister to an event but the class I wanted to be registered to was not instantiated yet.
     
  6. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    Ah, yes, that is a good example I have come across, as well (or something very similar). If you yield 1 frame, you can get it done, in OnEnable.

    As for the first part of your response, that's absolutely the best way for most things. :)
     
    djnX and FeastSC2 like this.
  7. McDev02

    McDev02

    Joined:
    Nov 22, 2010
    Posts:
    664
    If you want to have more controll you might want your own ObjectHandler which can work like shown below.
    It is just an example, maybe you use different controllers for different objects. You can also just skip IControlledObject and simply enable or call SetActive(true) on your objects in a routine like this. Then you instantiate the objects before and disable them right after.

    For my project I use a few different managers which controll different objects and I controll the instantiation, setup and execution (Run()) in the GameManager in its Loading routine.
    I also avoid Update() and my Manager calls the update, but this is just a personal preference and not suited for all applications

    Code (CSharp):
    1.  
    2. public interface IControlledObject
    3. {
    4.     void Setup();
    5.     void Run();
    6.     void Stop();
    7. }
    8. public class MyObject : MonoBehaviour, IControlledObject
    9. {
    10.     public bool IsRunning = false;
    11.     public void Run()
    12.     {
    13.         IsRunning = true;
    14.     }
    15.     public void Stop()
    16.     {
    17.         IsRunning = false;
    18.     }
    19.  
    20.     public void Setup()
    21.     {
    22.     }
    23.  
    24.     void Update()
    25.     {
    26.         if (!IsRunning)            return;
    27.         //Execute Stuff
    28.     }
    29. }
    30. public class ObjectManager : MonoBehaviour
    31. {
    32.     List<IControlledObject> Objects;
    33.  
    34.     public void SetupObjects()
    35.     {
    36.         for (int i = 0; i < Objects.Count; i++)
    37.         {
    38.             Objects[i].Setup();
    39.         }
    40.     }
    41.     public void RunObjects()
    42.     {
    43.         for (int i = 0; i < Objects.Count; i++)
    44.         {
    45.             Objects[i].Setup();
    46.         }
    47.     }
    48. }
    49.  
    50. public class GameManager : MonoBehaviour
    51. {
    52.     ObjectManager ObjectManager;
    53.  
    54.     void OnGameLoad()
    55.     {
    56.         //LoadObjects and initialize
    57.         ObjectManager.SetupObjects();
    58.  
    59.         //Do stuff...
    60.  
    61.         //Start Game:
    62.         ObjectManager.RunObjects();
    63.     }
    64. }
    Here is an alternative, maybe that makes more sense. This is how I controll all of my Applications usually. It is also great for references (dependency injection).
    Code (CSharp):
    1. public class GameManager : MonoBehaviour
    2. {
    3.     CameraController Camera;
    4.     EnemiesController Enemies;
    5.     FancyStuffController FancyStuff;
    6.  
    7.     void OnGameLoad()
    8.     {
    9.         //LoadObjects and initialize
    10.         Camera.Setup(this);
    11.         Enemies.Setup(this);
    12.         FancyStuff = new FancyStuff(Enemies); //Handle non MonoBehaviours easily
    13.  
    14.         //Do stuff...
    15.  
    16.         //Start Game:
    17.         Camera.Run();
    18.         Enemies.Run();
    19.         FancyStuff.Run();
    20.     }
    21. }
     
    Last edited: Sep 8, 2019
    miracalious, GregoryFenn and FeastSC2 like this.
  8. DonLoquacious

    DonLoquacious

    Joined:
    Feb 24, 2013
    Posts:
    1,667
    Self-initialization in Awake, initialization requiring any other scripts in Start- following just that one guideline will save you thousands of headaches, and there's no need to mess with Script Execution Order except in very specific cases. OnEnable always happens after Awake, but before Start, so you have to be careful that you don't specifically reference other MonoBehaviours' data that require the Start functions to have run before you can get the information you need from them- but that's the case in Start too. You can delay a frame if necessary- coroutines and invoke functions are both common for more precise load sequences.

    I would personally examine the data structure and see if there's anything you can offset to a static manager elsewhere instead.That's me though- every single GameObject and MonoBehaviour I use is simply a shell- the absolute minimum to get the job done, and everything that holds real data is in a static manager somewhere. I'm not a fan of using GameObjects to the sole proprietors of any data that isn't strictly used for physics or on-screen display, as it makes management of that data painful.
     
    ecssiah, YetAnotherKen and FeastSC2 like this.
  9. FeastSC2

    FeastSC2

    Joined:
    Sep 30, 2016
    Posts:
    978
    So a proper utilization of Awake/Start/OnEnable seems to be the answer for most situations, ok. Thanks for the ideas!

    I had been trying to avoid static classes or managers because I used to not use componentization enough but it definitely has its uses, I'll start using them more again + getting components everywhere gets quite a bit tedious.

    This has made me think of more questions if that's alright:
    - If GetComponent() has some overhead to it then isn't generally speaking static classes more efficient/ a better approach?

    - Unity has this GameObject <==> Transform link where they have a reference to each other. Is this something I should try and recreate on most of my components to avoid unnecessary GetComponent calls?

    - It was mentioned to me that it was a bad thing to have classes refer to each other (in c++ classes), they called it circle ... (some word I forgot). Say I created a MonsterManager and that in my Monster class I had a reference to the manager. Is that necessarily bad design? Is it because of idea that the class should be a "black box" or is it something related to performance?
     
  10. Flynn_Prime

    Flynn_Prime

    Joined:
    Apr 26, 2017
    Posts:
    387
    Apologies for going slightly off-topic and asking a question. But what is the problem with using script execution order? I often use it, for example, with my items to ensure my ItemDatabase is created before my inventory initializes. Is this bad practice?
     
  11. FeastSC2

    FeastSC2

    Joined:
    Sep 30, 2016
    Posts:
    978
    I barely use it because I think it's eventually going to get messy. I can already imagine myself thinking "Why did I call that script before that one etc...". Other thing I can think of is in a situation where you'd want to use your code in some other projects it'd mean you'd have to also integrate the execution order. Basically I want my code to work without the help of some other variable that might make it fail.
     
  12. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    Generally speaking, yes. Use Awake and Start to manage this as others have mentioned.
     
  13. BlackPete

    BlackPete

    Joined:
    Nov 16, 2016
    Posts:
    970
    An alternative way to handle that would be to have the inventory add a listener to ItemDatabase for when it updates with new data. That's pretty much how all UI should behave, actually.

    Then it doesn't really matter whether the inventory or ItemDatabase starts first.
     
    DonLoquacious and KelsoMRK like this.
  14. McDev02

    McDev02

    Joined:
    Nov 22, 2010
    Posts:
    664
    I would use the execution order mainly for the Update calls. You basically describe an initialization process which happens only once and during startup of the game. This and because you maybe want a proper loading routine which is capable to load the game over many frames and can show a progress bar which tells you what is currently being done.

    So my suggestion is to make a structured Loading Routine which does all the Initilization stuff such as setting up ItemDatabase and let the execution Order handle things like Update Routines. So that your Inputs are called before your camera controller, the game logic and then the view and rendering stuff. Some of that of course you can controll with LateUpdate)(), but there is no EarlyUpdate().

    You need some kind of controllers otherwise you end up with issues just as you have described. What if you make changes to code? Who guarantees that the execution order of low level objects like DatabaseItem is still correct if you make changes to the inventory, like adding another type of object.

    But I think the issue the OP has is exactly when the listener would be assigned so that database exists already.
     
    ecssiah likes this.
  15. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,775
    It ties your code with your project file, for one. Try to port your inventory into a different project, and this execution order is lost.

    It's better to enforce it in code. One way to do this is to use Awake in one and Start in the other, and that works 99% of the time. For the other 1%, there are other techniques to employ. Here is an example of code that's guaranteed to execute in the correct order, both during the "Awake" phase of execution.
    Code (csharp):
    1. // First.cs
    2. public bool isInitialized = false;
    3. public event System.Action OnInitialize;
    4.  
    5. void Awake() {
    6. if (!isInitialized) Initialize();
    7. }
    8. public void Initialize() {
    9. Debug.Log("First is initializing");
    10. if (OnInitialize != null) OnInitialize();
    11. isInitialized = true;
    12. }
    13.  
    14. // Second.cs
    15. public First first;
    16. void Awake() {
    17. first = GetComponent<First>();
    18. if (first.isInitialized ) {
    19. Initialize();
    20. }
    21. else {
    22. first.OnInitialize += Initialize;
    23. }
    24. }
    25.  
    26. public void Initialize() {
    27. Debug.Log("Second is initializing");
    28. }
     
    rightdroid and KelsoMRK like this.
  16. BlackPete

    BlackPete

    Joined:
    Nov 16, 2016
    Posts:
    970
    As long as you self-initialize in Awake, and find objects in Start(), that still shouldn't matter.

    The database itself should exist by the time you reach Start(). It may or may not be empty at the time the listener is registered. I'm saying make it so that it doesn't matter whether it's empty at that point or not.

    Try to pull whatever data it has at that point, and add a listener to receive new data after that point.
     
  17. D43DB33F

    D43DB33F

    Joined:
    Apr 6, 2017
    Posts:
    43
    Am I the only one thinking this way of initializing things is absolutely awful ? I really don't understand why Unity is forcing players to do that, but maybe I am missing something.

    While doing "self-initialization" (if such a thing exists...) in Awake() and anything else requiring other components in Start(), there is no Stop() method to be used in combination with the OnDestroy() method. Thus, what can be fixed by applying this "good practice" described above for initialization cannot be done simmetrically for uninitialization. So in the end, it just moves the problem without resolving it.

    I'm in a situation where I have to attach/dettach event handlers from within a component to a member of another object, member which only exists once the other object is properly initialized. While I can attach that event in Start(), when I'm sure that Awake() has been called on the other object, I cannot detach it in Stop(), because there's no such method. If I detach it in OnDestroy(), nothing guarantees me that the other objet hasn't been destroyed first, in which case its member I need to detach the event from might just be null.

    Unity is capable of so many advanced things, I can't beleive something as simple as a Start() / Stop() symertry cannot be performed. And at this day, I still don't know of any way to do that cleanly. The only way I know is creating all objects at run-time, using Instantiate() and DestroyImmediate(), which is a reliable way to be sure that objects are awaken, started and destroyed in the expect order, but that prevents me from using editor created content, which is dirty, harder to maintain, has worse performances, etc...

    Also script exceution order is way too "global" to fix that kind of issue. For a given set of components, I do not necessarily always want the same initialization order.
     
    Last edited: Sep 8, 2019
  18. Dameon_

    Dameon_

    Joined:
    Apr 11, 2014
    Posts:
    542
    When would you expect the Stop method to be called?
     
  19. McDev02

    McDev02

    Joined:
    Nov 22, 2010
    Posts:
    664
    There is: https://docs.unity3d.com/ScriptReference/MonoBehaviour.OnDisable.html

    For the rest, first Unity is not forcing users anything. It might not be easy all the time but you can define your own ways of how execution, initialization, Updates, and else are being called. You don't have to use MonoBehavior.Update, Start etc.

    That has nothing to do with Unity. You can either make sure that this objects exists while something is listening to it or use a simple null check.

    But in the end you are right, especially when you have bigger projects simply relying on Awake(), Start() etc is not gonna work. Build your own handlers around that, just as I have described above. These are just two examples.
     
  20. D43DB33F

    D43DB33F

    Joined:
    Apr 6, 2017
    Posts:
    43
    I would expect Stop() to be the symetrical of Start(), just like OnDestroy() is the symetrical of Awake().
    Start() is only called once all Awake() have been called. Stop() would be called before all OnDestroyed() are called.
    In Stop() you'd detach the component from whichever other object it is related to, and in OnDestroy you'd do the self-uninitialization. That would be the exact opposite of Start and would fix many issues. The existence of this thread, among others, is just evidence showing there's something unclear and/or missing about this subject that causes most developpers to have headaches.

    Yes Unity is "forcing players". You HAVE to use the Unirt events (Awake(), Start(), etc) if you want to be able to use editor-initialized content. And the fact that objects are initialized in a random order is an issue. Why are they not making objects initialize in the order they are in, inside the object tree ? Objects could initialize in that order, recursively. Parents could initialize before children, or the other way, but at least it would be predictable.

    OnDisable is NOT the symetrical of Start(), it's the symetrical of Enable(). Sorry but this seems obvious to me. OnDisable is called whenever the object is disabled, but Start() is NOT call when the object is enabled (again). Thus if you disable and then re-enable an object, you'd expect it to be in the same state as it was before you disabled it, which is not the case if it got uninitialized in OnDisable() and never re-initialized due to not being started again.
    Also you're not going to perform any uninitialization in OnDisable because the object must just be temporarily disabled/suspended to be used again later.

    I read your solution above, and while it provides a solution for the initialization, it does not for uninitialization. And even if you added some sort of Unsteup() method, that would be some kind of Stop() method, I would only work when that controller object would be destroyed, which would not be enough to cover all cases, unless you write one of these controllers for every case.
     
  21. Dameon_

    Dameon_

    Joined:
    Apr 11, 2014
    Posts:
    542
    I'm trying to get a feel for the issues you want to solve. I'm having a hard time seeing how a Stop() method would guarantee that the things that you want to "detach" from haven't been destroyed yet. if I destroy object A, and then on the next line destroy object B, the order of things happening means that if B has a reference to A, then B's reference to A is going to be null by the time we go to destroy B. It doesn't matter if we're trying to access that reference in some Stop method or an OnDestroy method; either way the fact that things happen sequentially is going to mean that A no longer exists by the time we destroy B.
     
  22. McDev02

    McDev02

    Joined:
    Nov 22, 2010
    Posts:
    664
    What is "editor-initialized content"?
    Right, OnDisable is in fact, the counterpart to OnEnable. The counterpart to Awake and maybe Start as well is OnDestroy. I can not see why you need another method here, especially as Awake and Start are called in different frames, this can not work with Destroy as the object is expected to be gone the next frame.

    The issues you describe are definitely there but therefore this is the reason why you should not follow the path that Unity lights up with Awake, Start etc. I avoid these methods if I can by using patterns such as described above, MVC, ECS or whatever. Have some other object control the existence of other objects, both instantiation and demolition and this way you can control execution much better.

    For example do not call OnDestroy, but rather SomeController.Destroy(MyObject). Then you can call a custom Stop() method for example before you call Destroy() if you need to. And if you talk about editor scripts there are still ways of doing so, I did it already and I have no such issues which you describe.
     
  23. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    I'm not really understanding the issue here either.

    OnDestroy is the "uninitialization" hook in a MonoBehavior's lifecycle. It just so happens that it provides 2 hooks for initialization - one for internal dependencies and one for external dependencies. This is not an uncommon feature in other frameworks.

    You've mentioned "attaching and detaching events" but you're not being specific enough about your actual use case. Are you using UnityEvent, event, or some bespoke solution? What's the actual issue you're trying to solve? Ultimately, if the thing that is responsible for emitting events is destroyed, then it simply doesn't emit events anymore. If you have some concern or bug that you need each listener to "know" when that destruction happens - maybe some reference is hanging around that's not being GC'ed - then it seems like you've got your logic inverted and the emitter should be responsible for "unsubscribing" all of its listeners or otherwise saying "I'm done emitting events forever".
     
  24. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    I might be misunderstanding your complaint or your situation, but your proposed solution of having a "Stop" method wouldn't solve this problem in any way.

    You'll always be facing the same problems due to the very simple fact that GameObjects / Components can be destroyed at arbitrary points in time. A stop method wouldn't help to solve that dilemma.
     
    DonLoquacious and KelsoMRK like this.
  25. Nate-CD

    Nate-CD

    Joined:
    Jun 19, 2013
    Posts:
    1
    I agree that one should try to find a code based solution for some things, usually this is just the correct use of initialisation in Awake/OnEnable/Start or a HasInit/IsStarted flag, but defining execution order is still a useful tool.

    The last time I checked, execution order was set in the meta files along with the script, so it could be ported that way. However, unity also has an undocumented code based solution for setting execution order, the class attribute
    [DefaultExecutionOrder(-10)]
    .

    As this is definable in code you can also document the reason for changing the execution order to avoid the inevitable problem with the GUI, "When/Why did I change that?"

    In this example, in order to force the Input manager to process player input before one of the selected controllers consumes it, the execution order is modified.

    Code (CSharp):
    1.  
    2. /// <summary>
    3. /// Controls the players character
    4. /// </summary>
    5. public class PlayerController : MonoBehaviour
    6. {
    7.     public InputManager Input;
    8.  
    9.     private void Update()
    10.     {
    11.         /*
    12.          ... Do something with Input
    13.         */
    14.     }
    15. }
    16.  
    17. /// <summary>
    18. /// Controls the players vehicle
    19. /// </summary>
    20. public class PlayerVehicleController : MonoBehaviour
    21. {
    22.     public InputManager Input;
    23.  
    24.     private void Update()
    25.     {
    26.         /*
    27.          ... Do something with Input
    28.         */
    29.     }
    30. }
    31.  
    32. /// <summary>
    33. /// Input state management
    34. /// </summary>
    35. [DefaultExecutionOrder(-100)]
    36. // Order modified so that input is updated before external usage
    37. public class InputManager : MonoBehaviour
    38. {
    39.     private void Update()
    40.     {
    41.         SetInputState();
    42.     }
    43.  
    44.     /*
    45.     ...
    46.     */  
    47. }
    I find absolutely no problem in modifying the execution order of scripts in a maintainable way like this, there is no reason to spend days on complex designs just to avoid changing execution order.
     
    Reshima and Mrbeardy like this.
  26. ZeeshanQaswar

    ZeeshanQaswar

    Joined:
    May 4, 2017
    Posts:
    2
    Hi, just want to add What I do to counter this problem is in my GameManager, Like I have Two scripts LevelManager & UiManager and I want one to initialise the content after the other what I do is Instead is.

    public class UIManager{

    public UIManager Instance = this;

    public void Initialize( ) {
    // Initializations
    }

    }

    public class LevelManager{

    public LevelManager Instance = this;

    public void Initialize( ) {
    // Initializations
    }

    }


    public class GameManager{

    public void Awake( ) {
    UIManager.Instance.Initialize();
    LevelManager.Instance.Initialize();
    }

    }


    Is This Approach acceptable?
     
  27. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,775
    Yes, but posting code without code tags is not
     
    Walter_Hulsebos and Suddoha like this.
  28. ZeeshanQaswar

    ZeeshanQaswar

    Joined:
    May 4, 2017
    Posts:
    2
    Haha!! ok I am new and don't know how to do that.
     
  29. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,775
    It's a sticky post on the forum
     
  30. RafaelKuhn

    RafaelKuhn

    Joined:
    Dec 5, 2019
    Posts:
    3
    For those reading now:
    "OnEnable always happens after Awake, but before Start"
    Yes, Don's comment is correct, but there's a catch:

    Object A OnEnable is not guaranteed to be called AFTER Object B Awake.
    So if object A OnEnable depends on Object B's Awake, it can (and eventually will) produce unexpected behaviour

    Object A OnEnable WILL be called before IT'S OWN Awake, but not other objects' Awake
     
    _Prism_ likes this.