20 Jun 2014

Co-very-routine

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.

Soft Stop

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.

Threading

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.

Usage

So that is the full wish list implemented. Now let’s take a look at some quick example snippets.

Result

The base interface for using the extended coroutines is to start them via the new generic version of MonoBehaviour.StartCoroutine.

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.

Exception

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.

Stop

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.

Soft Stop

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 CoroutineData<T>.

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 Stop, except RequestStop is used by the caller and the coroutine periodically checks CoroutineData<T>.ShouldStop.

Threading

The interface for threading is really very simple. Two new yield classes have been added WaitForWorkerThread and 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.

When yielding 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

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.

Fin

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.

New Gear
Unity, iOS, TeamCity, AppCenter
RAID0 NVMe on Ubuntu
A Change of Gears
CoreObject
Unity Protocol Buffers
Behave 2.7
Learn
Behave 2.6
Trusted Gear
Mad Mash Versioning
Behave 2.5
Behave 2.4
Co-very-routine
Construct
The Engine Wars: Numbers
GDC 14: The Quest For Fun
Moving in Unity
Behave 2.3
Unity and .net assemblies
Behave 2.2
ReView
Behave 2.1
Behave 2.0
Unity Hacks: Dual sticks
Unity Hacks: Cameras
Unity Hacks: Touch gestures
OnRenderTextureGUI
Unite 13 video "Unity Hacks" available
The implicit local network interface
Talks and progress
Five years of Unity expertise looking for contracts
Automagic Unity Android Java gadget OF DOOM!
Invading Planet from your couch
Mountain Lion and laggy bluetooth and duct-tape
Unite 12 video and new videos section available
Asia Bootcamp videos now available
Path is now MIT licensed
Behave 1.4 released
So I've been a bit busy lately
Behave 1.3 released
IGDA Unity SIG slides
Second Unity IGDA SIG this evening: Scene construction and AI
First IGDA Unity SIG this evening
Alternative licensing available
Pathfinding in two lines
Path 2 released
Assembling and assimilating
Path 2 intro screencast
Path 2 beta release for GGJ
AIgameDev master class video now online
Expanding beta
Behave AIgameDev master class public stream
Behave master class on open AIgameDev stream tomorrow
Interview with AIGameDev
New video: From tree to code
Issue tracking on github Behave release project
IT University Copenhagen Unity course completed
IT University Copenhagen Unity course files Thursday
CPH IT University Unity course files
Behave 1.2 released
Video: Behave - starting from scratch
Behave runtime documentation updated
Behave 1.1 released
FAFF cleanup: Sketch
Building a menu of delegates and enums
Pick me! Pick me!
Optimising coroutine yielding in C#
Downloading the hydra
New license of Path: GPL
CopyInspector
Magnetic
GUI drag-drop
Logging an entire GameObject
I bet you can't type an A!
Where did that component go?
UnitySteer
New and improved: Behave 1.0 released
Behave 0.3b and unity 2.5
Behave 0.3b hotfix
Path tutorial video available
Path 1.0 launched!
Continued community tutorials
Community tutorial
New tutorial
First tutorial available
Behave 0.3b
unite '08 open-mic session
Behave 0.2b
Behave 0.1b
Behave pre-release
Path beta 0.3b
Path beta 0.2b
Path beta 0.1b
Path pre-release