Delegate.Combine: How to Check if multicast (combinable) delegates already has a delegate inside of it?

335 Views Asked by At

I am using Unity 3D, however, that information should be irrelevant for solving this problem as the core problem is System.Delegate (I wanted to let you know as I'll be linking to some Unity docs for clarification).

I have a custom window that has a custom update function DirectorUpdate. I need this function to run every editor update regardless of what the user/window is doing.

In order for this to be called every editor update, I Combine my method with the Delegate EditorApplication.update:

protected void OnEnable()
{
    // If I do the below, base EditorApplication.update won't be called anymore.
    // EditorApplication.update = this.DirectorUpdate;
    // So I need to do this:
    EditorApplication.update = (EditorApplication.CallbackFunction)System.Delegate.Combine(new EditorApplication.CallbackFunction(this.DirectorUpdate), EditorApplication.update);

    ... // Other stuff
}

Note that this is done inside a window's OnEnable.

The problem is that OnEnable can be called more than once during a single run (for example, when closing the window and then reopening the window during a single editor session) causing

EditorApplication.update = (EditorApplication.CallbackFunction)System.Delegate.Combine(new EditorApplication.CallbackFunction(this.DirectorUpdate), EditorApplication.update);

to be called multiple times, meaning my update method (this.DirectorUpdate) will eventually get called multiple times per update, which is causing some serious bugs.

So, the question is how do I check if EditorApplication.update already has my method "inside" of it. (By inside of it, I of course mean it has already been System.Delegate.Combine(d) to the delegate.)

I am aware that there could be other solutions, for example restoring EditorApplication.update to what it was prior when the window closes, however that won't account for all situations (for example, OnEnable also gets called during window refresh and such) and the bug will persist. (Also, what if another window Concatenates with EditorApplication.update while this window is open?) As such, the best solution would be to check if EditorApplication.update is already callin this method BEFORE Delegate.Combine-ing it.

2

There are 2 best solutions below

3
On BEST ANSWER

I think you took the complicated road ;)

Subscribing and unsubscribing events and delegates is as simple as using the operators += and -= like

protected void OnEnable()
{
    // You can substract your callback even though it wasn't added so far
    // This makes sure it is definitely only added once (for this instance)
    EditorApplication.update -= DirectorUpdate;
    
    // This basically internally does such a Combine for you
    EditorApplication.update += DirectorUpdate;
    
    ... // Other stuff
}

private void OnDisable()
{
    // Remove the callback once not needed anymore
    EditorApplication.update -= DirectorUpdate;
}

This way you can also have multiple instances of this window open and they all will receive the callback individually.


Btw if this is actually about an EditorWindow then afaik you should not use OnEnabled but you would rather use Awake

Called as the new window is opened.

and OnDestroy.

1
On

I am not familiar with what System.Delegate.Combine(d) does, but you can consider instead of enabling/disabling your window, destroying and instantiating it every time, and move your code to the Start or the Awake for it to be called only once per window "activation".

Last but not least, use a mighty boolean in the OnDisable so that you can handle the combine execution if your component was ever disabled. Like so:

bool omgIWasDisabled;
protected void OnEnable()
{
    if (!omgIWasDisabled) {
        EditorApplication.update = (EditorApplication.CallbackFunction)System.Delegate.Combine(new EditorApplication.CallbackFunction(this.DirectorUpdate), EditorApplication.update);
    }
   
    ... // Other stuff
}  

void OnDisable() {
    omgIWasDisabled = true;
}

Hope any of those works out.