Figuring out Coroutines

So I have done a Tone of research into Coroutines, finding very little on thier limitations or dynamics when it comes to actually using them

so please Correct me if any of this is even remotely incorrect.

A Coroutine is a function that is starts Compatible functions (those that are IEnumerable and contain a yield). I have also heard that you can’t (or there is little point to) start a Coroutine inside the update function (simply because it will update every frame anyways). though i soon realized that Update(); is also a special type of Coroutine (as is fixed update and late update).

so I haven’t heard my questions asked yet so I thought I would give them a try.

  1. Can you start a Coroutine inside the update (obviously behind an if statement or some sort of filter.
  2. Can you have conditional yield statements for more complex code
  3. Can you yield for a function in a different script, perhaps one inside its own coroutine? and how would you go about it?
  4. Is it possible to Read data from a yielding Coroutine?(obviously in a way that is safe)
  5. Is a Coroutine called every frame when yield is not null?

For any of you who can post an answer I thank you in advance

  1. Yes, you can start coroutines in Start but you can’t make them blocking coroutines. If you need a blocking update, consider the CoUpdate pattern.

  2. Yes.

  3. All your coroutine needs is to return IEnumerator, through yield, and it will work in any function. You must still start coroutines from a script or game object though.

  4. Consider using a callback to pass data, but yes, you can do your own “wrapper” around the coroutine functionality. Coroutines aren’t much more than a loop, that each frame calls MoveNext and accesses Current. You can extract the data in your own loop and then yield the result or some other value in turn. (Think a coroutine in a coroutine, but you provide the coroutine engine for the nested coroutine).

  5. It depends. If it yields null, it means to call it again next frame. If it yields for example WaitForSeconds, then it will post pone the call until the time is up.

1:
Yes, you can start a coroutine inside Update, and in fact this is a perfectly valid form of control flow (for example, if you want to perform some coroutined action over time after recieving a key-press). So long as you use ‘StartCoroutine(nameOfCoroutine(paramaters));’, you should be fine.

2:
The yield statement can be used like any other line of code, and as such can be controlled by conditionals and loops just like anything else. There is aboslutely no reason why you can’t do this.

3: If you want to yield for a function in a different script, first make sure you have a reference to that object (obviously!), and then you can call it just like any other public function, so long as you couch it inside the StartCoroutine method call. So:

yield return new StartCoroutine(otherScript.SomeCoroutine());

will work perfectly.

4: Unfortunately, it is not possible to return data from a coroutine (because of the way the scheduler works internally). If you need to read from data that is modified inside the coroutine, you’ll have to just refer to members variables (rather than scope variables), since these will exist outside the scope of the coroutine.

int incrementMeOverTime = 0;

IEnumerator IncreaseTimer(float tickTime, int countUpTo)
{
    do {
        yield return new WaitForSeconds(tickTime);
        incrementMeOverTime++;
    } while (countUpTo-- > 0);
}

Outside this, you can read off ‘incrementMeOverTime’ to find the state of the coroutine, although I admit that this doesn’t really feel like an ‘elegant’ solution.

The best way to get around this would be to declare a class to hold your output data, and then pass a reference to that class into the coroutine. Then, when the corutine is finished, you can read the data contained in that class.

class CoroutineInfo
{
    public int incrementMeOverTime = 0;
}

// in the function
CoroutineInfo myInfo = new CoroutineInfo;
StartCoroutine(IncreaseTimer(myInfo));
Debug.Log("The timer got to: " + myInfo.incrementMeOverTime);

5: Every time you use the ‘yield’ statement, the coroutine will pause execution for at least one frame. If you use ‘yield return null’, it will still pause for a frame- which allows you to use it a little like a temporary ‘Update’ function (that only gets executed up until the coroutine finishes). If you use something like ‘WaitForSeconds’, of course, execution will wait for longer than just one frame.

(Code examples are in C# because the syntax for coroutines is clearer in that language, and makes it more obvious exactly what is going on. I have had any number of questions here about JS couroutine, mostly stemming from justified confusion about exactly how they work. My apologies if it’s not your preferred language)

Used correctly, Coroutines can almost completely supplant the Update loop. They tend to be more efficient, because if you code them right they will only be called when they actually need to do something, not just every frame like Update, and it’s extremely easy to insert timing information into them (where you would have to keep and increment a member variable if you were to do that kind of thing in Update). Lately I’ve been reimplementing a lot of my older code to use them more extensively, because they make control flow more generally intuitive.

While it takes a little bit of practice to get your head around exactly what can and cannot be done with them (for example, you can’t pass references to value types into a coroutine- however, you can pass a reference to a reference type), coroutines are a very powerful tool in your shed, and you would do well to familiarise yourself with them.

I am using coroutines for many tasks but it is exceptionally useful for clearing scene after operation is performed.
It shouldn’t be however used in all cases.

In post Auto destroying particle system in unity3d on my blog I am explaining behavior difference between using update and coroutine when autodestroying particle system.

Have a look you might find it usefull.