yield and coroutines, how do they actually work?

Hello,

I think I have a good understanding of the yield keyword, still I do not understand how practically something like :

IEnumerator Example() {
       print(Time.time);
       yield return new WaitForSeconds(5);
       print(Time.time);    }

can work.

I am sure that the MonoBehaviour StartCoroutine registers the single routines, which are then called every frame. In this way the Iterator Block is recalled as it should be.
It is everything clear, expect the fact I do not understand how the iterator block can know that the YieldInstruction is actually finished. I mean I was expecting something like yield break, but things like :

yield return www o yield return WaitForSecond() look like they should never end OR actually called just once.

Please help me to understand what is the trick Unity does.

N.B. I was expecting at least to see the WWW class or the YieldInstruction to be IEnumerable, but it is not even this the case :frowning:

Unity just make use of enumerators. The compiler will add some extra code to the function so the code can jump to where it yielded next time it is enumerated. This allow us to execute parts of the code in small steps.

You can do your own code that goes through an enumerator in the same way.

IEnumerator e = Example();

while (e.MoveNext())
{
    Debug.Log("Yielded: " + e.Current);
}

When yielding a WaitForSeconds, Unity will wait with calling MoveNext until the duration has passed. If the object yielded is null, it will wait until next frame. If it is a WaitForSeconds, it will read the duration. “Simple” as that.

To get an idea about this, you could think:

if (e.Current is WWW) ... // call e.MoveNext when the download finished! 

You can make your own system like this by keeping a list of pairs of IEnumerator and Func< bool > predicates (for letting know if it is ok to advance to the next yield).

Each update, you loop through all pairs, check if the predicate allow moving the enumerator. If it does, move the enumerator and set the predicate to the next criteria. For example, if e.Current was WWW, you might make a predicate to check if WWW has completed or not. If it is WaitForSeconds, you make a predicate that return true once the time reaches Now + Duration.

I hope you understand more about how this works. IEnumerator is nothing specific with Unity, so you can read more about IEnumerator online to go more in depth.

As @Statement explained, Unity makes use of enumerators. However, I’d like to go a bit deeper on how I think it works internally.

When your co-routine returns an object by calling yield return new SomeType(), Unity will check its type. If it is of type YieldInstruction or Ienumerator, Unity will call MoveNext() on that enumerator as well. As long as MoveNext() returns true, Unity will continue iterating over this enumerator in the next frames and it will not continue your initial coroutine execution.

From within your returned IEnumerator you can yield return another IEnumerator. This way you can chain multiple Enumerators. Inception! I know!

If you wonder how WaitForSeconds and other extensions of YieldInstruction work internally, check out this documentation page. It seems that YieldInstrction could implement the Ienumerator interface like this:

class YieldInstruction: IEnumerator {

        // default implementation that must be overridden
        public bool keepWaiting { get { return false; } }

        public object Current { get { return null; } }

        public bool MoveNext() { return keepWaiting(); }

	    public void Reset() {}
}