The correct way to handle script execution order?

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.

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.

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”.

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.

2 Likes

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.

/// <summary>
/// Controls the players character
/// </summary>
public class PlayerController : MonoBehaviour
{
    public InputManager Input;

    private void Update()
    {
        /*
         ... Do something with Input
        */
    }
}

/// <summary>
/// Controls the players vehicle
/// </summary>
public class PlayerVehicleController : MonoBehaviour
{
    public InputManager Input;

    private void Update()
    {
        /*
         ... Do something with Input
        */
    }
}

/// <summary>
/// Input state management
/// </summary>
[DefaultExecutionOrder(-100)]
// Order modified so that input is updated before external usage
public class InputManager : MonoBehaviour
{
    private void Update()
    {
        SetInputState();
    }

    /*
    ...
    */  
}

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.

2 Likes

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?

Yes, but posting code without code tags is not

2 Likes

Haha!! ok I am new and don’t know how to do that.

It’s a sticky post on the forum

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

1 Like