Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Why are Coroutines typed as IEnumerable?

Discussion in 'Scripting' started by jpthek9, Jul 6, 2015.

  1. jpthek9

    jpthek9

    Joined:
    Nov 28, 2013
    Posts:
    944
    I think this stems from the subject of how coroutines work. How do coroutines work internally?
     
  2. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,514
    they work on .net's iterator functions:

    https://msdn.microsoft.com/en-us/library/dscyy5s0.aspx

    You're function is an iterator function.

    Unity will iterate over each entry in the enumerator (the functions yield statements) every frame. When a yield instruction is returned by the yield statement, it'll deal with it accordingly. If it says to wait a duration, it won't iterate for that duration. If it's a WWW request, it'll wait until that WWW request is complete. If it's null (or really any value it doesn't have a rule for), it just waits until the next frame.
     
    jpthek9 likes this.
  3. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,294
    Usually (as in outside Unity-land) when you use an IEnumerator, it looks something like this:

    Code (CSharp):
    1. IEnumerator<SomeType> enumerator = mySpecialCollection.GetEnumerator();
    2.  
    3. while(enumerator.MoveNext()) {
    4.     enumerator.Current.DoSomething();
    5. }
    Or, more simply:

    Code (CSharp):
    1. IEnumerator<SomeType> enumerator = mySpecialCollection.GetEnumerator();
    2.  
    3. foreach(var thing in enumerator) {
    4.     thing.DoSomething();
    5. }
    The magic of the IEnumerator construction you do with the yield statements is that they allow you to put the implementation of that enumerator inside a single method, instead of you having to write your own enumerator implementation and use variables to controll the current state, which is the alternative.

    Now, Unity piggybacks onto this concept in a somewhat strange way. In normal IEnumerators, you're trying to get values from a collection in some kind of order, and the important part is what each yield statement returns. In the Coroutine pattern (I guess it's a pattern?), the important part is what you are doing between the yield statements. The yields only return "how long should it take before this method continues exectuion" instead of something interesting.

    The coroutine-though-IEnumerator concept is a stroke of genious. I don't know if it's something the Unity engineers came up with on their own, or if it's borrowed from some other engine, but it's absurdly powerfull, and allows you to skip out on a ton of control variables that would be needed in any other implementation of "do this thing for a bit, in some ordered fashion, then don't do it anymore".
     
    jpthek9 and Kiwasi like this.
  4. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    Coroutines are awesome. How they work internally is even more awesome. First up let's talk about what an IEnumerator is.

    An IEnumerator is a special type that is implemented by collections. A enumerator lets you do things like foreach. The IEnumerator keeps returning values in a collection until you reach the end of the collection. IEnumerators do this with a yield statement. Something like this pseudo code.

    Code (csharp):
    1. private float a;
    2. private float b;
    3.  
    4. public IEnumerator GetValues () {
    5.     yield return a;
    6.     yield return b;
    7. }
    Usage might be something like this. Again, pseudo code.

    Code (csharp):
    1. IEnumerator value = myCollection.GetValues();
    2. while(value.MoveNext() != null) {
    3.     // Do something with each value
    4. }
    Normally you don't get that low level, you just implement IEnumerable and use foreach.

    This is where the innovation of Unity's coroutines came in. Unity changed this model around and made the code in between the yield statements the focus, rather then the values returned by the yield statements.

    So internally a coroutine looks similar to the code I posted above. But instead of doing something with the value, it's checking if the return value is a YieldInstruction. If it is then the IEnumerator gets pushed on to Unity's internal scheduling system. IEnumerator.MoveNext will be called later. If it's not a YieldInstruction it gets scheduled for calling on the next frame.

    There is a great video from Unite detailing the ins and outs of coroutines, and some clever tricks to make them even more powerful.

    http://youtube.com/watch?v=ciDD6Wl-Evk
     
    jpthek9 and SubZeroGaming like this.