What actually changes if Fade used a yield return instead of a return

I read Confusion in IEnumerators and the difference between return and yield return - #5
and An Explanation Of When And What To Yield Return? - #4
and buy the explanation of why its OK to return instead of yield return.

What I’d like to understand is what actually would change if that line in Fade were changed to a yield return. I don’t think it would have any consequence or delay of any sort but not sure. If I understand how yield return works, within the line:

yield return {something}

the content within {something} is evaluated in full before the yield return happens. Which would be the same as a normal return. I’m asking because I’m trying to test my understanding of what yield return actually does in practice given we’re talking about nuanced race conditions here.

Because Fade is a Coroutine, not an IEnumerator, yield return simply won’t work. Here’s how my error stack blows up by changing return to yield return

Ah interesting. It wasn’t my question but it does help draw our the nuances.

Let me rephrase my question. What’s the difference between the following two in terms of impact on execution order. My guess is these methods would execute the same way until control is returned to the calling method. IOW - the difference would be on what happens in the caller e.g. Portal.Transition

Sorry for asking what seems like a stupid question but I’m trying to test my understanding.

public Coroutine FadeIn(float time)
{ 
   return Fade(0, time);  
}
public IEnumerator FadeIn(float time)
{
   yield return Fade(0, time);
}

Not a stupid question at all.

Part of the confusion comes from the names we use to represent Coroutines.

IEnumerator represents the code that is run as a Coroutine. This is Unity taking advantage of a feature in C# itself in the IEnumerator.
Coroutine is the wrapper for the Coroutine (see the confusion here?).

All Coroutines are started with the StartCoroutine() method. StartCoroutine() only takes in an IEnumerator as a parameter, but it returns a Coroutine. This return is the handle which we can store to stop a coroutine from running.

Coroutine FadeIn() returns a Coroutine (much like StartCoroutine). It’s not the Coroutine itself (still confusing, I know). Fade(), which FadeIn calls is also a method that returns a Coroutine, but is not a Coroutine itself.

        public Coroutine Fade(float target, float time)
        {
            if (currentActiveFade != null)
            {
                StopCoroutine(currentActiveFade);
            }
            currentActiveFade = StartCoroutine(FadeRoutine(target, time));
            return currentActiveFade;
        }

Fade manages the currentActiveCoroutine, which is really just a reference to (Unity calls it a Wrapper) the actual Coroutine which is started with StartCoroutine(FadeRoutine(target, time));

FadeIn calls Fade, which ends a Coroutine if one is running, and starts a new one with StartCoroutine and returns the result of StartCoroutine.

Now if we used IEnumerator instead of Coroutines our code would look like this:

        public IEnumerator FadeOut(float time)
        {
            yield return Fade(1, time);
        }

        public IEnumerator FadeIn(float time)
        {
            yield return Fade(0, time);
        }

        public IEnumerator Fade(float target, float time)
        {
            if (currentActiveFade != null)
            {
                StopCoroutine(currentActiveFade);
            }
            currentActiveFade = StartCoroutine(FadeRoutine(target, time));
            yield return currentActiveFade;
        }

In this case, we would actually be adding overhead to our code execution.
Each time we use yield return SomeIEnumerator(), Unity actually calls StartCoroutine behind the scenes and starts a new Coroutine, adding it to the list.
FadeIn would first be added to Portal’s list of active coroutines. Fader would continue being held as a Coroutine awaiting the end of execution until Fade finished. Fade would also be added to the list of active Coroutines. Finally FadeRoutine would be added to the list of active Coroutines. Fade would not be allowed to finish being evaluated (at the end of each frame, each active Coroutine is evaluated to see if it can run) until FadeRoutine was finished or cancelled.

By using Coroutine instead of IEnumerator, all that is added to the queue of running Coroutines is FadeRoutine, as the rest of the methods will end normally and get out of the way.

I know this section is confusing. It threw me for a loop when Sam first introduced the lecture, and I’ve been coding since the Reagan administration.

Sam put this section in to try to introduce us to some of the under the hood of Coroutines/IEnumerators.

One might be tempted to forego storing the Coroutine and just using StopAllCoroutines in FadeIn and FadeOut keeping them as IEnumerators. The problem with that approach is that it would also stop FadeIn or FadeOut, which is also what we don’t want.

I truly wish I could explain that better. With the return type of Coroutine, talking about Coroutines can leave us wondering what in the Upside-Down is going on.

Ah OK. It was helpful to see these explanations explicitly written. It definitely seems like Coroutines are yet one more example of Unity doing things in C# code that normal C# code wouldn’t do.

The explanation actually makes a lot of sense. I don’t think it would have 2 months ago but it does now after some experience with this stuff. I can also see now why some very experienced engineers also avoid Coroutines. They are very powerful but not super intuitive what’s going on.

More like a prime example. They didn’t want to do true threading/multi-tasking (we have that now with DOTS, but it’s separated from MonoBehaviour logic!).

A Coroutine is not true multi-tasking, it fakes the multitasking by returning to each Coroutine each frame. if it were true threaded multi-tasking, you wouldn’t need yield returns at all, and you’d be able to do massive things inside of them like calculate paths and other fun stuff while the animations keep going.

1 Like

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.

Privacy & Terms