Coroutines make scripting behavior that is spread out over multiple frames a breeze. For example if you wanted to have a traffic light cycle between red, yellow, and green you could do it all in a single coroutine:
function TrafficLight() {
color = Red;
yield WaitForSeconds(1.0);
color = Yellow;
yield WaitForSeconds(1.0);
color = Green;
}
Coroutines can also wait for one another which makes ordered cooperation between coroutines a breeze.
function RunOnGreen() {
// wait for green light
// traffic light cycle ends in green
yield TrafficLight();
// model reaction time
yield WaitForSeconds(0.1);
print("Catch me if you can!");
}
Now you can do all this without coroutines, but alternatives such as event driven callbacks, and state machines are usually not as elegant or concise. Needless to say coroutines are a nice programming tool.
Because I needed more complete control over coroutines in a portion of my code for Wisp I ended up writing my own CoroutineScheduler similar to the one built-in to Unity. Today I contributed this code to the Unity community wiki. Most Unity games will never need this level of control over coroutines and are better off using the built-in support. But seeing how coroutine support is implemented on top of standard .Net/Mono generators leads to a better understanding of how coroutines work behind the scenes which is something anyone can benefit from. The coroutine scheduler is also useful if you want to add coroutine support to a non-Unity based project, and I think you will find the code easy to follow.
To understand coroutines at an even deeper level, you should understand how generators work behind the scenes. Generators allow you to create a function that returns a lazy IEnumerator that yields a sequence of objects. You can also get an IEnumerator by calling GetEnumerator on a regular collection such as an ArrayList.
// if array contents are 1, 2, 3
// then this will print 1 2 3
var arrayEnumerator = someArray.GetEnumerator();
while (arrayEnumerator.MoveNext()) {
print(arrayEnumerator.Current);
}
When you use a "for in" or "foreach" loop construct an IEnumerator is used behind the scenes.
Here are similar examples this time using generators.
function CountTo3() {
yield 1;
yield 2;
yield 3;
}
function CountToN(n : int) {
for (var i = 1; i <= n; i++) {
yield i;
}
}
// print 1 2 3
// without allocating an array of size 3
var generator = CountToN(3);
while (generator.MoveNext()) {
print(generator.Current);
}
Behind the scenes a generator is translated to a state machine. This is where the magic happens. As I noted earlier an alternative to the TrafficLight coroutine is to use a state machine. In fact the TrafficLight coroutine is implemented as a state machine it just happens automatically when the generator is translated into one by the compiler. You can see the state machine class for any generator function by inspecting the .Net assembly using Reflector. But to give you a general idea of what is happening here is a simple example.
function ThreeMore(start : int) {
yield ++start;
yield ++start;
yield ++start;
}
// ThreeMore as a state machine
class ThreeMore {
var state = 0;
var current;
var start : int;
// function parameters become object attributes
function CounToN(start : int) {
this.start = start;
}
function MoveNext() : boolean {
switch (state) {
case 0: current = ++start; break;
case 1: current = ++start; break;
case 2: current = ++start; break;
default: return false;
}
state++;
return true;
}
}
The actual state machine classes generated by the compiler will be a bit different from this example. But the key points to take away are that generators are implemented as classes and on each call to a coroutine function a new instance of their compiler generated state machine class is created. The function parameters are translated into object attributes and function code is spread across multiple state machine states to allow execution to enter and leave the coroutine while maintaining parameter and stack state as object attributes.
That does it for now. I hope you find this post useful.
Disclaimer: Example code was typed into blog editor and may not compile.
0 comments:
Post a Comment