IDisposable Coroutines in Unity3D: Cleaning Up After Yourself

In this article, I present a way to perform tidy-up when a Unity3D coroutine is terminated before completing.  Mum would be proud – I finally learnt how to clean up after myself!

Photo of an untidy room

This is what a world without disposable coroutines looks like.

In Unity3D, coroutines provide a great way of executing a single method across time, in a pseudo-asynchronous way, via the use of the yield keyword.  This is perfect for logic that must span several frames of gameplay while maintaining state – a classic requirement for implementing AI behaviours.

While implementing such a thing, I stumbled into a situation whereby my AI code would set some Animator variables at the begin of a coroutine that needed to be “unset” at the end – fairly standard stuff.

The problem occurred when the coroutine never got to finish it’s full execution, because a coroutine method is never guaranteed to run to completeness due to its piecemeal-executed nature.

As an example, my AI character was navigating through the level and had set some special Animator variables for the journey.  These variables were to be reset to their original values at the end of the journey.  My AI design was coroutine-based, where the current behaviour was executed in a coroutine.  The coroutine was swapped in and out depending on what the AI character was doing at the time.  For example, the “navigate to goal” coroutine looked like this:

private IEnumerator NavigateToGoalC(Vector3 goal)
{
    // Set this when we begin the behaviour
    _player.Animator.IsRunning = true;

    // Set our goal
    SetMovementDestination(goal);
    // Wait until we reach our goal
    while(!IsAtGoal(goal))
    {
        // Yield until next frame
        yield return null;
    }

    // Clear this when we end the behaviour
    _player.Animator.IsRunning = false;
}

However, if the AI encountered an enemy en route, he would abandon that coroutine and start another one, replacing the coroutine.  The “cleanup” code at the end of the original coroutine never got executed, because the coroutine never gets a chance to complete its execution.

OK, fair enough, this is the nature of coroutines.  But I have an ace up my sleeve, because such a situation is a perfect case to use a try..finally block!  So I changed the code to look like this:

private IEnumerator NavigateToGoalC(Vector3 goal)
{
    try
    {
        // Set this when we begin the behaviour
        _player.Animator.IsRunning = true;

        // Set our goal
        SetMovementDestination(goal);
        // Wait until we reach our goal
        while(!IsAtGoal(goal))
        {
            // Yield until next frame
            yield return null;
        }
    }
    finally
    {
        // Clear this when we end the behaviour
        _player.Animator.IsRunning = false;
    }
}

Much to my dismay, upon playing the game, the finally block did not get called when the coroutine was switched out for a new coroutine. I Google for some causes, and discovered that this was because there is no formal “cancelling” of coroutines (they are simply discarded) compounded by the fact that the coroutine is never disposed of, so “disposey” things like finally blocks aren’t executed.

Furthermore, even if the coroutine was disposed of, it would likely happen at the next garbage collection, which may be some time after the coroutine was switched out, meaning the Animator variables would be set to their original values some pseudo-random amount of time after the AI had switched behaviours.

Note that I’m no C# expert and discovered this was the case from this article: “Yield and usings – your Dispose may not be called!” on Dan Crevier’s Blog.

The solution I came up with was to manually dispose coroutines when they are replaced by another coroutine.  This is achieved by remembering which behaviour coroutine is being run, and if it is replaced, manually disposing the ejected coroutine.

For re-usability, I put this logic into an extension method that instantiates and manages a new component to execute the coroutine.  Usage is then as simple as calling someComponent.StartDisposableCoroutine() rather than someComponent.StartCoroutine().

Because of the automatic nature of the extension method, you can only have one disposable coroutine per GameObject, however the code is simple and could be modified to suit cases where you need multiple simultaneous coroutines.

The classes implementing these “disposable coroutines” are as follows:

using System;
using System.Collections;
using UnityEngine;

namespace JoelMalone.Unity.Components
{

    /// <summary>
    /// Coroutune executor that will execute a single coroutine, calling Dispose()
    /// on it if it is aborted before it can complete execution.
    /// This is handy for times when you need try..finally or using()
    /// blocks to execute within your coroutines, e.g. to perform
    /// cleanup.
    /// Note: does not explicitly call Dispose() when a coroutine
    /// runs to completion, though try..finally and using() blocks
    /// would have already executed anyway, in this situation.
    /// To use this class, call extension method StartDisposableCoroutine()
    /// on the component class; the coroutine itself will be executed on a
    /// self-managed child gameobject.
    /// </summary>
    public class DisposableCoroutineExecutorComponent : MonoBehaviour
    {

        private IEnumerator _disposableCoroutine;

        public void StartDisposableCoroutine(IEnumerator coroutine)
        {
            if (_disposableCoroutine != null)
            {
                var disp = _disposableCoroutine as IDisposable;
                if (disp != null)
                    disp.Dispose();
                StopAllCoroutines();
            }

            _disposableCoroutine = coroutine;

            if (_disposableCoroutine != null)
                StartCoroutine(_disposableCoroutine);
        }

    }

    public static class DisposableCoroutineExecutorComponentExtensions
    {

        public static void StartDisposableCoroutine(this Component me, IEnumerator coroutine)
        {
            var executor = me.GetComponent<DisposableCoroutineExecutorComponent>();
            if(!executor)
                executor = me.gameObject.AddComponent<DisposableCoroutineExecutorComponent>();

            executor.StartDisposableCoroutine(coroutine);
        }

    }

}

The above code is part of my publicly-available set of Unity tools, named JoelMalone.Unity, which is hosted on Bitbucket.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s