I really like coroutines. Like with most things, they should be applied in well-considered quantities, but when applied they can really expand what you can achieve in just a few lines of code. Particularly in our frame-bound world.
However there has always been this little subset of extra things, which I would have liked to have seen supported by Unity coroutines out of the box.
“Hang on, weren’t you just there?”
Yep. I was and terrible as the excuse might be, there was always something that seemed shinier or more on fire.
Then at Unite 13, Devin and Matthew of Twisted Oak Studios did an excellent talk about extending coroutines by using extension methods to insert a proxy layer between coroutines and the Unity coroutine-system. Unfortunately I didn’t catch the talk live (I don’t see many talks at conferences), but shortly after I could catch it on YouTube. Technology, yay!
In any case that really ended my supply of excuses, other than work.
Devin and Matthew had specifically implemented the setup to allow coroutines to provide a return value, provide a nicer stopping mechanism, as well as provide a useful way of handling exceptions thrown during the execution of coroutines. This provided an excellent platform to work off and did tick three items off my wishlist, but didn’t quite deliver the whole package.
It is an interesting talk and if you have the time, you should give it a look.
The Whole Package
So I slacked off doing work for a bit and somewhere in Q2 2014 got started writing my own implementation of extended coroutines, with the extra bits and bops on my list.
This here is the list:
- Coroutines providing return values (yay! free!)
- Handle exceptions thrown during coroutine execution (yay! free!)
- Useful stopping behaviour (yay! free!)
- Built-in soft stop.
- Better built-in communication between caller and coroutine.
- Easier way of intermixing threading and coroutines.
- Built-in simple load balancing.
Straight up stopping a coroutine is fine, but dependent on what it was doing while being stopped, it might leave a mess behind. The idea with a soft stop mechanism is that in stead of directly stopping the coroutine, the external controller in stead requests that the coroutine stops.
This allows the coroutine to monitor the state of the requested stop flag at appropriate points in its execution and terminate with the necessary cleanup in response.
Caller – Coroutine Communications
But why stop the communications at just requesting a nice stop? What if the work produced by the coroutine could have relevance throughout its execution?
By making the result of the coroutine accessible before the completion of the coroutine, we gain the capability of not just reading it early on, but also contributing to its building from outside of the coroutine.
Sufficiently intense tasks, working on data types not tied to the UnityEngine, API can benefit from threading. Unfortunately, most of the time things aren’t that cleanly cut out, and only part of the task is suitable for threading.
At that point the alternatives are to either forget about partially threading the task or to bridge the two types of concurrency system. Since the latter rarely turns out very pretty, the former is often chosen.
Building threading right into the coroutine system makes the choice easy.
Simple Load Balancing
Finally, heavy tasks which for one reason or another cannot be threaded (perhaps the very nature of the task requires dealing with the UnityEngine API), can in stead be frame sliced inside a coroutine.
However the logic for doing so can be more or less pretty and writing boilerplate is boring. Clearly this would be great to also have built into the base coroutine system.
So that is the full wish list implemented. Now let’s take a look at some quick example snippets.
The base interface for using the extended coroutines is to start them via the new generic version of
For running regular coroutines, the only other difference is that in stead of yielding directly to the return value of
StartCoroutine<T>, you yield to the .Coroutine member of its
CoroutineData<T> return value.
This is also the reference which gives you access to the result of the coroutine execution.
Since the coroutine calls are executed by the coroutine system and not the callee, normally exceptions just get swallowed up by the coroutine system and logged out.
The approach of Devin and Matthew, as implemented here, is to swallow the exception, break coroutine execution and then re-throw the same exception when next the coroutine comes into contact with the caller – namely when its result is accessed.
In stead of relying on the strange, existing methods of stopping coroutines and the limitations they come with (such as only stopping a coroutine initially started by string parameter or stopping all coroutines on a behaviour), the
CoroutineData<T> reference yields another possibility.
Since it effectively represents an instance of an executing coroutine, adding a
Stop method to it becomes much more intuitive.
Requesting that a coroutine stops, requires a line of communication between the caller and coroutine. This is provided in the form of the instance of
However in order to effectively pass this, the calling convention on the coroutine is slightly different. Rather than passing the return value of the coroutine to
StartCoroutine<T>, the coroutine is passed as a delegate, taking
CoroutineData<T> as its only parameter.
The rest of the flow is exactly as with
RequestStop is used by the caller and the coroutine periodically checks
The interface for threading is really very simple. Two new yield classes have been added
WaitForMainThread. Yielding to an instance of either will do exactly as advertised. Yielding break or the result or throwing an exception will terminate the thread and the coroutine.
WaitForSeconds on the worker thread, the thread will sleep for the specified amount of seconds. And finally the
CoroutineData<T> holds a flag accessor to determine the thread state of the coroutine.
Load balancing could be done in a bunch of different ways and I would encourage you to add your own (I would love to see your ideas as well). So I decided to just do the simplest I could think of.
WaitIfFrameTime will yield until the next frame if the current frame has taken longer than the specified amount of time. This way it effectively sets down a max frame time while utilising all the time leading up to that cap.
And that is it! I hope you find some use of these extensions to the extensions and do let me know if you come up with some cool further extensions.