Search Unity

Question We have Awake, OnEnable, and Start. Why only OnDisable and OnDestroy?

Discussion in 'Scripting' started by joshrs926, Nov 14, 2022.

  1. joshrs926

    joshrs926

    Joined:
    Jan 31, 2021
    Posts:
    115
    I find Awake and Start very useful for initializing MonoBehaviours. Awake is great for initialization that only references itself because other MBs may not yet be ready. Start is great for initialization involving referencing other MBs because they are ready by the time Start is called. OnEnable can be used in there but it may be called many times. What about for de-initialization? There are many times, especially when using events, that I want to run de-initialization logic that references other MBs but I don't believe you're guaranteed they still exist during OnDestroy. We could try to use OnDisable for that, but it may be called many times during an MB's life so that would mean moving that initialization logic into OnEnable to match and ensuring it is valid for the MB to be initialized and de-initialized potentially many times. Also, even if an MB gets disabled, I believe other code can still have references to it and call its methods, so it still needs to be initialized.


    So my questions are these: Why do we not have a matching de-initialization message for Start? And when is the best time for a MonoBehaviour to run de-initialization logic that references other MonoBehaviours?
     
  2. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,338
    If you want to de-initialize, OnDisable and OnDestroy is sufficient, kinda.

    The problem where other objects doesn't exist when you're trying to de-initialize happens in two instances:
    - Those objects can get destroyed by gameplay logic. In those cases, that's something you'll need to handle anyway. Either your dependency is in the wrong direction, or you can do a simple "is this destroyed" check.
    - You're quitting. Unity annoyingly Destroys and calls OnDestroy on all objects in the scene when you exit the game. That very often leads to exception spam when exiting. A good approach if you find yourself in that situation is to do an editor-only "if application is quitting, return" in OnDestroy, and then just live with the fact that shutting down the game will spam nullrefs to a log due to Unity having made a bad decision a million years ago that can't be undone.
     
  3. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,742
    Bunny83 likes this.
  4. AnimalMan

    AnimalMan

    Joined:
    Apr 1, 2018
    Posts:
    1,164
    that’s interesting couldn't you just write your own object destroyer before calling application quit to ensure hierarchy is drained of all objects without problem. After disabling all scripts.
     
  5. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,338
    Yes I could. But that's overengineered and boring and brittle and I don't gain anything from it, so why would I?
     
  6. AnimalMan

    AnimalMan

    Joined:
    Apr 1, 2018
    Posts:
    1,164
    maybe the object destroyer script will do partial scene deconstructions and reconstructions on its off times ^_^ I could certainly think of a use for it, partially deconstruct a scene and construct a new state of the same scene. He would be like the only remaining script. Disable all but me, then delete all but me. Now delete me.
    Kind of like all the switches in the cockpit of a spacecraft. Fuel depleters a offline, Cpu System shutdown. Power shutdown initialised.

    I guess if those logs were being written to a new file they are adding needless space. Otherwise it’s not a problem. But what if you had bug tracking tech in your final product for user to print out the logs. Now it may be worth initialising a shutdown sequence but not all games even need such a thing. Ah who knows. It’s an interesting thing to look out for never the less.
     
  7. joshrs926

    joshrs926

    Joined:
    Jan 31, 2021
    Posts:
    115
    Thanks guys for the replies! @Kurt-Dekker yeah I love that page. I did look at that while researching before posting this question. @Baste I wonder if it would be possible for Unity to implement another message that corresponds to Start. It (c)would be kind of like adding a public method to a publicly available API. You definitely don't want to change existing method signatures/names of publicly available API but adding new ones (may) not mess up people's existing code. I'm sure that's never gonna happen in this case but I can whine and complain about it every time I encounter this problem :) This issue mostly comes up when I'm subscribing in Start and I want to unsubscribe somewhere else (Unstart?)
     
  8. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,742
    Referencing that timing flowchart, at what point in that flowchart should Unstart() get called?
     
    angrypenguin likes this.
  9. joshrs926

    joshrs926

    Joined:
    Jan 31, 2021
    Posts:
    115
    Well I suppose since the initialization order is Awake, OnEnable, Start then the de-initialization order could be Unstart, OnDisable, OnDestroy. Better naming might be Stop instead of Unstart and Sleep (or Die xD) intead of Awake
     
  10. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,338
    But when do you want this Stop method to be called? And under what conditions?

    If it's to match Start, then the logical condition would be when the object is getting destroyed, before OnDisable and OnDestroy, and only if Start had been called in the first place. The condition for Start not getting called is if you spawn a deactivated object and not activating it.

    That's a very narrow corner case! Do you have any actual examples where you would want to use this, or is it just theorycrafting for the heck of it?
     
    Bunny83 and Kurt-Dekker like this.
  11. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,742
    Extremely. That's what I was getting at with this question:

    Let me rephrase it:

    "Where in the chart should your UnStart() be called that is NOT already handled by some other existing call?"

    The edge-casey-ness that @Baste points out above isn't even visualizable in that flowchart, that's how edge-casey-ness it is!

    I am coining a new word: edgecaseyness. Or should it be edgecasiness?

    Sounds like a famous baseball player, someone who could hit the ball so squarely along the first- or third-baseline that the umpires would argue for hours as to whether Edge Casiness' ball was fair or foul.
     
  12. joshrs926

    joshrs926

    Joined:
    Jan 31, 2021
    Posts:
    115
    @Kurt-Dekker thats funny! The only use case I have that makes me think of this is subscribing and unsubscribing from events in MonoBehaviours. If I want one MB to subscribe to another MB’s event I’d do it in Start, not Awake. Then I’d want to unsubscribe in Unstart, not OnDestroy. When I try to unsubscribe in OnDestroy, sometimes the other MB has already been destroyed. This isn’t a big deal because there are other ways to handle the situation such as checking for null in the methods that are being called by the event. Then you could just never unsubscribe. Or perhaps you could unsubscribe if the one Mb is found to be null by the other. There’s options.
     
  13. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,927
    Then... unsubscribe on OnDisable? Which is, you know, called before objects are destroyed.
     
  14. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    Also, sometimes it'll be after OnDisable, if the object was already disabled.

    But more importantly than that, I what @joshrs926, what would you do in that method which can't already be done in one of the others?

    This isn't just preference. According to the manual is is the correct approach, because other GOs are not guaranteed to have been initialised yet in Awake. Awake is to get stuff on the same GO set up, Start is where you hook stuff on different GOs up to each other.

    Of the thing you were subscribed to no longer exists, what exactly is it that you might still feel the need to do?

    My unsubscribe calls typically look like this:
    Code (csharp):
    1. if (eventPublisher != null) eventPublisher.Unsubscribe(OnEvent);
    And that's not just a workaround for exception spam. It'll depend on your general architecture, but for me there tend to be valid cases for it to no longer exist, to have never existed, or to be an object which will go on existing, and this covers all of them regardless of execution order.

    And on that note, I try to make my code either control its own execution order, or behave as expected regardless of execution order. This is a prime example of why. I don't have any direct control over the order Unity destroys GOs in when a scene is unloaded, so any code in OnDestroy (as with any code in Awake) should be written such that the order does not matter.
     
    joshrs926 likes this.
  15. joshrs926

    joshrs926

    Joined:
    Jan 31, 2021
    Posts:
    115
    Great points!