Coroutines and building a Coroutine Que
Intro
In unity I have found myself using coroutines methods like Destory(gameobject, delay), and Move(gameobject, movetime) quite often, today I took the time to understand how they really work.
A coroutine is like a function that has the ability to pause execution and return control to Unity but then to continue where it left off on the following frame. I highly recommend reading entire documentation on Coroutines here before continuing.
Outline
To understand what a coroutine really is it is important to understand two things
Enumerators
A coroutine is similar to a foreach statement in that they are both enumerators! A foreach stament, while loop, and a coroutine are all enumerators. The following code blocks are equivalent.
By “functionally equivalent,” I mean that’s actually what the compiler turns the code into. You can’t use foreach on baz in this example unless baz implements IEnumerable.
Adding Foreach to your own class
I started with learning how you can turn any class into a enumerator. That’s right! You can write your very own class that will allow you to call foreach on it, as long as it implements IEnumerable.
This helped me a lot to cement my understanding of how the magic really happens with foreach etc.
To implement IEnumerable you have to have 1 method in your class which is called getIEnumerator. And the IEnumerator class must implement 2 methods bringing the total to 3. They are as follows.
IEnumerator GetEnumerator()
bool MoveNext()
Object Current()
If you want to read and understand this even better the full explanation of the IEnumerable class is here
Yield
Understanding what a yield statement does is also essential to understanding coroutines. Understanding yields is quite cool and very useful. Lets start with an example of when a yield would be helpful. This First a code block just has a function which returns the Fibonacci Sequence then prints it out. It doesn’t use a yield statement. You will probably have to look at this code awhile before it sinks in.
Here we need to create two lists, and iterate through each of them. This might seem ok to you, but using a yield statement it can be done better. Here is what it looks like
So to explain the yield code above a little better.
• Step 1:- Caller calls the function to iterate through the Fibbonacce sequence.
• Step 2:- Inside the function the for loop runs until it reaches the yield return. The “yield” keyword sends this data back to the caller.
Step 3:- Caller displays the value on the console and re-enters the function for more data. It reenters the function right after the yield return, and then executes the j = k; and k = temp;. It has also remembered all their values from before! Then it reaches the end of the loop and starts again, calling the next yield. The iteration continues further as usual.
Yes this actually works! A yield keyword can do these iterations with the need of creating a extra “temporaty collection”. Note that the control is returned to the caller each time the “yield return” statement is encountered and executed. Most importantly, with each such call, the callee’s state information is preserved so that execution can continue immediately after the yield statement when the control returns. If you want a detailed explanation of why this is cool at a slower pace you can look at the original explanation here or here.
Coroutine Que
In many cases I have created terrible bugs with coroutines being called from different objects that conflict with each other. Say for example I called a move coroutine from a player object, in the move coroutine if I took damage, I would call the delete coroutine from a board manager object. If the delete coroutine finished before the move coroutine then I would get all kinds of weird errors because the object being moved was deleted. This might not be the best example, but problems like this can be easily solved with a Coroutine Que. This Coroutine Que is based on one from the Udemy class “How to make a card battle system”. It’s very simple. It only allows one coroutine to run at a time. After the coroutine is done, it sends a message back to the Que, and the next coroutine is started. Of course in some cases it is altogether better to avoid using coroutines and just called these methods in update or fixed update.