Mostly Harmless


A blog about science, art, coding, life,... mostly harmless stuff.


Improving the Invoke functionality in Unity

When I first discovered Unity, I got thrilled by the super intuitive API and the cool ways they exploit C# features. I totally love design patterns, well designed APIs, and clever use of language constructs to achieve coding powerful and complex behaviors with expressive but simple statements. Unity has much of that, and I fell completely in love when I discovered their neat approach to co-routines. However, there was one part of the API that got me very disappointed from day one: the Invoke method.

Invoke Explained

If you still haven’t used it, here is a very simple review. The MonoBehaviour class has a static method Invoke(string, float) that receives the name of the method you want to invoke and a delay in milliseconds. To see it working, let’s come up with a “real life” example. We’ll build a very basic piece of gameplay. Leveraging on Unity’s standard 2D assets package, is very easy to build something like what’s shown below. The objective of this “game” is to simply get the golden diamond.

A very simple 2D demo for the purpose...

Now, to add some challenges, let’s make those platforms fall after you stand on them. Just for fun we’ll turn the platforms red when you touch them, and make them fall after some predefined time:

using UnityEngine;

public class Fall : MonoBehaviour {
    public float Delay;

    void OnCollisionEnter2D(Collision2D collision) {
        this.GetComponentInChildren<SpriteRenderer>().color = Color.red;
        StartCoroutine(FallLater());
    }

    IEnumerator FallLater() {
        yield return new WaitForSeconds(Delay);
        this.GetComponent<Rigidbody2D>().isKinematic = false;
    }
}

Something like this:

Falling platforms...

Using Unity’s Invoke, we can get rid of the coroutine and delegate it to the engine:

using UnityEngine;

public class Fall : MonoBehaviour {
    public float Delay;

    void OnCollisionEnter2D(Collision2D collision)  {
        this.GetComponentInChildren<SpriteRenderer>().color = Color.red;
        Invoke("FallNow", Delay);
    }

    void FallNow() {
        this.GetComponent<Rigidbody2D>().isKinematic = false;
    }
}

Neat, right? Removes the need to write co-routines just to delay some execution, which… might not seem as much. However, it’s not just about removing that one yield return line. The actual improvement, is that Invoke decouples the invocation target from the invocation time. The coroutine-based method creates a tight-coupling between two responsibilities, activating the fall-down behavior, and deciding when to active it. Of course this small example is not evil enough, but you can probably see the bigger picture here. Using Invoke we can convert any immediate action into a delayed action without extra code. Otherwise, we’d be writing coroutines every time we needed to execute something later on.

Some Drawbacks

Now, Invoke is by no means perfect. There is another eyesore problem here: strings. In order to accept strings, the Invoke method needs to resolve the actual method called using Reflection. And this ought to be pretty expensive, right? Well, to be honest, after a few failed attempts to make Invoke embarrassingly slower than a plain method call, and totally failing, I just gave up. Probably there are scenarios where the added Reflection layer hits the framerate, but, let’s face it, the Unity guys have put a lot of years into making this platform as fast as possible, and there are many other sources of overhead in the complex fabric of game engines. If they cache the resolved method references, and do some other performance kung-fu, we will probably never notice the performance hit. Of course, manipulating strings in real time is dangerous, so if we make an Invoke call passing a string dynamically built so that the compiler cannot intern it, the of course we’ll cause a performance drag. But in practice, we pretty almost always know at compile time the name of the method we want to invoke. And if you don’t, then you do need to pass a string, and you’ll have to swallow the performance hit anyway.

So let’s turn our attention to a more interesting (IMHO) discussion: the design of the API itself, from a software engineering point of view. What we have now is a method that receives a string and a delay, and schedules the invocation. That’s it. For me the biggest drawback here is the lack of parameters for the method being invoked. So, if I have something like this:

void DoSomething(float withValue) {
    // And I want to use that value ...
}

All I can do is something like this:

void DoSomethingLater() {
    Invoke("DoSomethingWithFixedValue", 2f);
}

void DoSomethingWithFixedValue() {
    float fixedValue = 42f;
    DoSomething(fixedValue);
}

I can already imagine myself writing plenty of dummy fixed value portal methods. The main objection to this approach (besides of course the potentially many different portal methods) is that if I only know that fixedValue at runtime, inside the DoSomethingLater method, I have no sane way of passing that fixedValue to the invocation target. Right now I can only think of making fixedValue a field, and that is a really crappy design . So, how could have possibly Unity solved this? The first idea that might come to your mind is providing overloads with some predefined parameter types, like int, float, string and so on. Something like:

void Invoke(string methodName, int parameter, float delay) {
    // ...
}

void Invoke(string methodName, float parameter, float delay) {
    // ...
}

void Invoke(string methodName, string parameter, float delay) {
    // ...
}

And then while resolving the right method using Reflection, also check the if parameter type matches. You can then imagine combinations of 2 parameters, 3 parameters and so on, into a deadly spiral of exponential overloads.

This is kind of what the cousin method SendMessage does but, just to get over with that, the purpose of Invoke is to call a method in this component, potentially delaying the invocation into a future time; while the purpose of SendMessage is to call a method in all components attached to the current gameObject, immediately. Maybe, with a clever combination of both you could get by, but I’ll leave that for another talk (or maybe never).

Improving Invoke

Actually, what I would like to have, is something like this:

public class Fall : MonoBehaviour {
    public float Delay;

    void OnCollisionEnter2D(Collision2D collision) {
        this.GetComponentInChildren<SpriteRenderer>().color = Color.red;
        this.Invoke(() => { this.GetComponent<Rigidbody2D>().isKinematic = false; }).After(Delay);
    }
}

Basically a version of Invoke that receives a delegate (Action). This way I can use callbacks with any number and type of parameters, even use the parameters from the closure of the delegate. And while we are at it, why not also improve a bit on the API, so that we can do things like this:

this.Invoke(() => {
    // ... whatever
})
.After(2f) // Delay first the execution,
.Repeat(10) // queue multiple repetitions,
.Every(0.5f); // and set a custom interval between continuous executions.

Building this API is not really hard, the basic construction block is using a coroutine, much like in our first example. That coroutine simply executes a given callback after some delay:

IEnumerator Coroutine() {
    running = true;
    yield return new WaitForEndOfFrame();

    while (Times != 0) {
        yield return new WaitForSeconds(Period);
        callback();

        if (Times > 0)
            Times--;
    }

    coroutine = null;
    running = false;
}

Picture this code as part of the implementation of the following interface, and from then on is just a matter of adding some extensor methods:

public interface IInvokeCallback {
    float Delay { get; set; }
    float Period { get; set; }
    int Times { get; set; }
    bool Running { get; }
    void Restart();
    void Stop();
}

Of course, in real life, the implementation is a bit trickier. On the one hand, that coroutine needs to be “own” by someone. And that someone might very well be the MonoBehaviour calling an extensor Invoke method, like this:

// !! Not the actual code !!

public static class InvokerExtensions {
    public IInvokeCallback Invoke(this MonoBehaviour sender, Action action) {
        var c = new InvokeCallback(action);
        sender.StarCoroutine(c.Coroutine())
        return c;
    }
}

However, life is not that simple. The problems with this approach are subtle. For example, think what happens if your callback references game objects that will not have the same life cycle as the coroutine owner. If the owner is destroyed the callback is destroyed, which might not be what you want. For instance, suppose that when you get the “Super Shield” power up (activated by the OnCollisionEnter of the SuperShield object), you immediately turn god mode on, and queue a callback to remove god mode after 30 seconds. Then you destroy the power up object, and there goes your callback (and you stay god forever, who would want that?). On the other hand, if the referenced objects in the callback die while the coroutine is executing you’ll get some nasty exceptions (which is true with any coroutine, anyway). And finally, having all those callbacks owned by a centralized object simplifies management and debugging. You can easily keep count of how many invokes are running, stop them all, etc.

One neat solution for this is to use a Singleton pattern. Yeah, I know, friends don’t let friends use Singletons… unless they are adults. My implementation does exactly this, a singleton GameObject that owns all the InvokeCallback coroutines and keeps track of them. On every new level loaded, previously queued coroutines are destroyed (we can easily add a DoNotDestroy property to override this behavior). Besides, since each InvokeCallback instance knows who owns its coroutine, its very easy to implement the Restart and Stop methods.

The actual code is not really that interesting once all magic is explained. Anyway, if you really really want it, check out the (free) Unity package I made.

Moving On

From this basic framework, is very easy to add crazy and exciting extensions, such as:

Continuations

Continuations allow us to concatenate invocations one after the other:

this.Invoke(() => {
    // Something to execute first
})
.After(2f) // which will happen 2 seconds in the future,
.Then(() => {
    // And then another something
})
.After(1f); // one second beyond

The implementation is actually quite simple. We need to add a nextCallback field to the InvokeCallback class, and call its Restart method and the end of the coroutine.

Conditional Execution

Conditional execution is the fancy way I call the ability to stop or continue the execution of an invocation using a predicate:

this.Invoke(() => {
    // Something to do
})
.After(2f) // that will happen 2 seconds in the future,
.Every(1f) // repeating every 1 second,
.While(() => gameObject.isActive); // and stop once the gameObject becomes inactive.

The implementation consists in storing the corresponding predicate in the InvokeCallback class and check it before every iteration of the while cycle in the coroutine. If you specify a condition and a repeat count greater than zero, then both conditions need to hold for execution to continue. In fact, if you concatenate several .While(...) together, then all conditions must hold to continue execution. As a matter of fact, .Repeat() is just a shortcut for the corresponding While(() => count-- > 0) snippet.

Functional Delays

Another fancy name for a very simple but powerful feature, the ability to specify delays not by numeric values, but by functions:

this.Invoke(() => {
    // Something to execute first
})
.After(2f) // which will happen 2 seconds in the future,
.Every(() => Random.value) // repeating in random delays
.While(() => true); // Forever

I will not even bother to explain how to do this, to save time and space, and meanwhile waste it by adding: combining this with closures can make things get crazy (and unreadable) very fast.

One Last example

And finally for the bold minds, behold!:

This footage was generated solely with the following code, and parameters Time=1, Strength=50, Chance=0.61 and Max=500:

public class Explode : MonoBehaviour {
    public float Time;
    public float Strength;
    public float Chance;
    public int Max;

    static int total = 0;

    void Start () {
        total++;

        this.GetComponent<Rigidbody2D>().AddForce(Random.insideUnitCircle * Strength);

        this.Invoke(() => Instantiate(this))
            .After(Time * Random.value)
            .Every(Time * Random.value)
            .While(() => total < Max && Random.value <= Chance)
            .Then(() => {
                this.GetComponent<SpriteRenderer>().color = Color.gray;
            })
            .Then(() =>
            {
                total--;
                Destroy(this.gameObject);

                if (total == 0)
                    SceneManager.LoadScene(SceneManager.GetActiveScene().name);
            })
            .After(Time);
    }
}

If you want to know more, read on….