Order of object destruction on scene unload

Hello,

I would like to know if there’s a guaranteed order for object destruction, specifically for cases with objects nested under one another.

When a scene unloads (for example, when loading a new scene) all objects are destroyed and receive the appropriate callbacks (e.g: OnDisable, OnDestroy, etc).

We have the following object hierarchy:

1836443--117776--upload_2014-11-5_9-33-3.png

And this script on the Controller object (i am showing only the relevant parts):

public class LevelFailedController : MonoBehaviour
{
    public GameObject ButtonRetry;

    void OnEnable()
    {
        ButtonRetry.GetComponent<Button>().OnClicked += HandleOnRetryClicked;
    }

    void OnDisable()
    {
        ButtonRetry.GetComponent<Button>().OnClicked -= HandleOnRetryClicked;
    }
}

I am getting reports that for some reason, the code in OnDisable fails for some users with this error:

NullReferenceException in UnityEngine.GameObject.GetComponent[Button]
LevelFailedController.OnDisable ()

I am not sure what may be the reason for that. The reference is assigned in the inspector appropriately, the only thing i suspect of is that for some users, the destruction order will destroy the button object, making the call in OnDisable use a null reference.

So, my questions are:

  1. Is there any guaranteed destruction order for scene objects?
  2. Is my code safe ? (e.g: access a game object and GetComponent inside OnDisable)

Hello @liortal , I know this is an old thread, but it’s first or second record in google, so I would like to ask if you have any answers to you questions or any other information related to this topic.

I encountered very similar problem (in my case ‘this’ is null in event handlers) with objects destroyed on exit playmode, and it seems to appear randomly. Furthermore in one case it breaks sometimes, while the other one works fine always (so far) and this is horror to debug and understand what exactly must be fixed.

Sorry @Qriva but i never received any proper response for this old question.

The order is undefined/arbitrary. It is undocumented by Unity.

Technically there is an order… as it’s not just “random”. It’s likely the order within the scene list they manage internally in unity.

But by being “undefined” they are not beholdened to any documentation that said order MUST be that order. This way in the future if they decide to optimize something in their scene hierarchy list which happens to alter the way that list is ordered, they can.

It’s like this… a vending machine takes money and gives out candy. What happens between putting the money in and the dispensing of the candy is arbitrary and unnecessary knowledge to you the user because all that matters is that candy comes out. This way if down the line new dispensing technology is made they can swap it out and the user experience remains the same even though the internals of what happens between putting in money and dispensing candy has changed.

Now this doesn’t solve the problem outlined by OP 7 years ago. Nor resolved what I presume it @Qriva 's problem.

In that situation what you can do is just check if the Button is null/destroyed first by just saying:

void OnDisable()
{
    if(ButtonEntry)
    {
        var btn = ButtonEntry.GetComponent<Button>();
        if (btn) btn.OnClicked -= HandleOnRetryClicked;
    }
}

Alternatively why even have ButtonEntry be type GameObject? Make it ‘Button’:

public Button ButtonEntry;

void OnDisable()
{
    if (ButtonEntry) ButtonEntry.OnClicked --= HandleOnRetryClicked;
}

Note this does mean that you’re not unregistering the event in the case that ButtonEntry was destroyed first. But this is fine since well… everything is getting destroyed. 2 destroyed things referencing each other is perfect ok and will get cleaned out when GC occurs.

1 Like

Yes, I made the ticket to Unity about this and I hope I will get any additional informations. I know it’s not literally random, but it appears to be random as it is unpredictible, however actually I found some relations/rules of destruction (obviously can’t be trusted, because it comes from observation), but it does solve the problem only in specific situations.

First of all, the order of destruction can change any moment, I think even selecting object in scene could do it.
Second, if object has children, then they will be OnDisable first.
Third, the parent object is never destroyed before their children, but is not specified when it happens - for example whole object tree can be disabled at the beggining, but parent destroyed as the last.

Actually this is not my problem. You are right unregistering can cause problems too, but in my case event is called from OnDisable, so what actually happens:
ObjectA (event provider) triggers the event, ObjectB (listener) is destroyed (at least C++ side) and his ‘this’ does not exist anymore, but because C# side is still there, registered delegate is still called (does not cause error itself), but inside called method there is some logic that need this or this.gameOjbect or Destroy() and this cannot be done obviously.

In other thread related to this problem it was proposed to null check everything and I can do it, but this is stupid and should not be designed this way at first place, that’s why I wanted to propose change, that all OnDisable are called first on all objects, before any of them get destroyed.

This is still an up-to-date problem in my experience. It’s possible to create singletons that aren’t MonoBehaviours, link them to a monobehaviour if needed, and wrap all the necessary if-checks, but it’s not perfect either.

I suppose in a beautiful world the destruction order would respect inverse script execution order, no? Maybe one day?

2 Likes