Difference "StartCoroutine" and "Call-like-a-method"

Hi everyone,

general question:

I usually use the “StartCoroutine” to call my coroutines.
I know if I call them “directly”, it usually does nothing.
However, since it doesn’t marks me the code as wrong, I was wondering when you would use that?
Also, using “yield return” works without “StartCoroutine”, what would that do?

e.g.:

IEnumerator Wait() {
   yield return new WaitForSeconds(5);
}

void Random() {
   StartCoroutine(Wait());

   Wait();
}

IEnumerator RndCor() {
   yield return StartCoroutine(Wait());
   yield return Wait());
}

When you call Wait as a ,method it’ll actually run through the code exactly as written. You might be thinking “Then why doesn’t it wait for 5 seconds?” That’s because WaitForSeconds is not a method that waits for seconds- its just a data type called WaitForSeconds. It doesn’t do anything by itself.

When you run Wait using StartCoroutine, there is part of the Unity engine that will keep iterating over Wait every frame until it’s done. It will catch that WaitForSeconds output and process it and actually wait to continue iterating Wait until after that time.

You might want to read this discussion Difference between "yield return IEnumerator" and "yield return StartCoroutine"

As for calling IEnumerator method without startCoroutine (outside coroutine) Unity didn’t invent C# nor it’s feature. Whether code is marked as wrong is mostly defined by the compiler and language not Unity. Compiler will not complain (most of the time) when you use Unity API in a way that doesn’t make sense as long syntax is correct and you don’t try to put wrong type in wrong place. IEnumerator functions where initially not made for async functions, it was a C# feature made for making enumerators (the name should have hinted that). Unity is just creatively using iterators to implement it’s Coroutine functionality. Although later C# versions introduced async execution based on same language feature. (No idea what came first Unity abusing iterators for coroutines, or proper C# async execution system).

So the initial intention for this language feature without Unity coroutines was something like this:

    // for now ignore the difference between IEnumerable and IEnumerator
    IEnumerable<int> GetEvenNumbers(int v)
    {
        for (int i=0; i<v; i++) {
            if (i % 2 == 0) {
                yield return i;
            }
        }
    }

void Foo()
{
   // Call producing something you an iterate over just like most containers
   foreach (var item in GetEvenNumbers(10)) {
            Debug.Log($"{item}");
    }
}

Note returning a sequence of numbers is somewhat artificial example. Slightly more appropriate use could be implementing iteration functionality for custom container. Or maybe function you want to evaluate lazily if you don’t iterator over all the elements in one go.

2 Likes

Well, that’s not really true :slight_smile: When you call Wait, none of your code is executed. Instead the Wait method returns an object, that’s all that is happening.

If you want to know how the yield magic and coroutines work in Unity, have a look at my coroutine crash course. It may help to better understand how they work.

But that is all the code. Oh, I think I see what you are saying. You mean how once it gets to the first yield return then it’s not going to execute any code after that (assuming there were any)? Not unless you are using it as a collection with “for each” or something like that?

Hi Kdgalla, sorry to interrupt, i have the feeling you are good at ‘wait for an amount of time’.
I’m making a 2.5 Platformer and i’m stuck on an enemy IA.

I’m tryning to make if the players stays in attack range, the enemy to wait for about 2’’ to attack again using OnTRiggerStay.

This is what i’ve got:

public class Locust : MonoBehaviour
{
public GameObject LocustObj, KickDust, KickColliders, DeadDust, LocustSfx, KickSfx;
void Start()
{
KickDust.SetActive(false);
DeadDust.SetActive(false);
KickSfx.GetComponent().Stop();
}
private void OnTriggerEnter(Collider collision)
{
if (collision.gameObject.tag == “Player”)
{
KickColliders.GetComponent().SetTrigger(“Action”);
LocustObj.GetComponent().SetTrigger(“Attack”);
LocustSfx.GetComponent().Play();
KickSfx.GetComponent().Play();
KickDust.SetActive(true);
}
}
private void OnTriggerStay(Collider collision)
{
if (collision.gameObject.tag == “Player”)
{
KickColliders.GetComponent().SetTrigger(“Action”);
LocustObj.GetComponent().SetTrigger(“Attack”);
LocustSfx.GetComponent().Play();
KickSfx.GetComponent().Play();
KickDust.SetActive(true);
}
}
}

Is there any chance you could help?

I already told you how you could fix it, and coroutines are probably not your best choice. And seriously, you already have a post. Don’t steal someone else’s post for your own. I also don’t understand why you used code tags in your original post but not here. If you think @kdgalla can add something different, ping that person in your post and ask them there.

:slight_smile: No, that’s not what I meant. Again, when you call your coroutine method, none of your code is executed, zero. All that is happening is that your method is creating an instance of a statemachine and that is returned. Usually when you pass this object to StartCoroutine, StartCoroutine will immediately call MoveNext on that object to execute your code up to the first yield statement. However if you just create the object and don’t do anything with it, none of your code is executed.

Here’s an actual example you can try:

IEnumerator MyRoutine()
{
    Debug.Log("MyRoutine started");
    yield return new WaitForSeconds(5);
    Debug.Log("MyRoutine middle");
    yield return null;
    Debug.Log("MyRoutine ended");
}

// [ ... ]

Debug.Log("Start: calling MyRoutine");
IEnumerator obj = MyRoutine();
Debug.Log("we returned from MyRoutine()");

bool res1 = obj.MoveNext(); // this will print "MyRoutine started"
Debug.Log("first MoveNext call returned " + res1); // return value will be true

bool res2 = obj.MoveNext(); // this will print "MyRoutine middle"
Debug.Log("second MoveNext call returned " + res2); // return value will be true

bool res3 = obj.MoveNext(); // this will print "MyRoutine ended"
Debug.Log("third MoveNext call returned " + res3); // return value will be false

When you run that code you will notice that when we actually call “MyRoutine”, none of the log statements inside the coroutine will go off. We just created an iterator / statemachine object. When we call MoveNext of that object, we actually execute the code up to the first yield statement. “obj.Current” will contain the WaitForSeconds object that we yielded. MoveNext returned “true” to indicate that Current was set to a value. So you should call it again to get the next object. When we call MoveNext a second time, the coroutine would advance to the next yield statement. MoveNext again returns true and obj.Current would contain that “null” value we yielded. The third MoveNext call would execute the rest of the coroutine and since we reached the end, no Current object would be set and MoveNext would return false to indicate that the iterator has reached the end.

This manual iterating which we just did, is actually done by the Unity coroutine scheduler. As I said, when passing the object to StartCoroutine, it will immediately call MoveNext once. So your coroutine is executed up to the first yield statement. The scheduler will look at the yielded value and decide what to do next. When it detects a WaitForSeconds object it would grab the delay float value that is stored inside that object and stores our coroutine object in an internal list, so it can be continued once the wait time has passed. In case of a null value, it would simply put the object in a list of coroutines that need to be continued the next frame. So as Unity is processing a frame, it reaches the point where it handles “suspended” coroutines. All it does is calling MoveNext again which would advance the statemachine, it gets a new yield value and again decides what to do next. The same as before. Those special yield values like WaitForEndOfFrame simply indicate the scheduler to put the object in a seperate list that is processed at the end of the frame. That’s all there is about coroutines. The scheduler is actually pretty simple. It’s just woven into the engines main loop at various points where a coroutine may need to be continued.

1 Like

Just to be clear about my last code example. Iterating the returned object like I did here for demonstration purposes of course would not “wait” since we currently ignore the yielded values completely. We just let the statemachine “tick” forward with each call of MoveNext. If there’s still something unclear, have a look at my longer code example in my linked crash course. The example I posted there also contained an IEnumerable which makes things a bit more complicated, but the overall concept should be clear. When you let your iterator return an IEnumerator instead of an IEnumerable, the code that your method would actually execute is the creation of the IEnumerator object that is currently inside GetEnumerator().

ps:
I actually used the concept of iterator blocks as coroutines myself outside the context of Unity to implement coroutines :slight_smile: Specifically in the game SpaceEngineers you can actually write ingame C# code (you can place a programmable block onto your ship). Coroutines are a great way to describe complex sequences quite easy.

Here is some pseudocode to ilustrate the process Bunny described.

// Consider this only a pseudocode roughly ilustrating what happens inside Unity to run itearotors like coroutines.
// Actual Unity implementation due to performanace, more complexf features, correctness and other reasons is probably looks different.
// This example doesn't reflect exact Unity behavior for details like what gets executed when.
// This is not even fully valid C# code, don't try to compile it.


List<Coroutine> coroutines; // Unity somewhere stores all the running coroutines

class Coroutine
{
    IEnumerator iterator;
   
    Coroutine(IEnumerator iterator) {
        this.iterator = iterator;
    }
}

Coroutine StartCoroutine(IEnumerator iterator)
{
    var result = new Coroutine(iterator);
    StepCoroutine(result);   
    coroutines.Add(result):
    return result;
}

StopCoroutine(Coroutine coroutine)
{
    coroutines.Remove(coroutine);
}

class WaitForSeconds
{
    float time;
    WaitForSeconds(float seconds) {
        this.time = second;
    }
}

void StepCoroutine(Coroutine coroutine)
{
    if (!coroutine.iterator.MoveNext()) { // advance the IEnumerator function. Consider it finished if it doesn't have any more elments to return.
        corotine.finished = true;
        return;
    }
   
    var yieldedValue = coroutine.iterator.Current; // the values return with yield have meaning only because Unity looks at it and does something based on return value, not Because construction of WaitForSeconds perfroms the waiting directly

    if (yieldedValue == null) { // yield return null
        coroutine.resumeMoment = ResumeMoment.NextFrame;
    } else if (yieldValue is WaitForSeconds waitSeconds) { // handle yield return WaitForSeconds(x);
        coroutine.resumeMoment = ResumeMoment.Time;
        coroutine.resumeTime = Time.Now() + waitSeconds.time;
    } else if (yieldValue is IEnumerator ienumerator) { // handling of yield foo(); without StartCoroutine, start courtine implicitly. This doesn't work when you aren't already in coroutine, because Unity doesn't hook into all the iterators. It only receives values that methods started as coroutines give to Unity.
        var newCoroutine = StartCoroutine(ienumerator);
        coroutine.resumeMoment = ResumeMoment.Coroutine;
        coroutine.waitForCoroutine = newCoroutine;
    } else if (yieldValue is Coroutine waitCoroutine) { // handling of yield StartCoroutine(foo());
        coroutine.resumeMoment = ResumeMoment.Coroutine;
        coroutine.waitForCoroutine = waitCoroutine;
    }
}


bool ShouldResume(Coroutine coroutine)
{
    switch (coroutine.resumeMoment) {
        case ResumeMoment.NextFrame:
            return true;
        case ResumeMoment.WaitForSeconds:
            return Time.Now() > coroutine.resumeTime;
        case ResumeMoment.Coroutine:
            return coroutine.waitForCoroutine.finished;
    }
}

UnityMainEngineLoop()
{
    while (ShouldNotExit()) {
       
        RunPhysics();
        ProcessInput(); // no idea where exactly input handling fits in Unity game engine loop, doesn't matter for this example
        CallUpdateForAllSceneObjects();
       
        // process coroutines, in actual Unity this is slightly more complicated to do ability for coroutine to resume at different positions of main loop (end of FixedUpdate, end of frame, normal case between Update and late Update)
        // I would expect the real implemention to have multiple queues so that it can efficiently get the courintes which are likely to be ready and relevant for current phase, instead of iterating over all of them and polling each frame.

        // check which corutines are ready to be resumed and advance them by one step
        foreach (var coroutine in coroutines) {
            if (ShouldResume(coroutine)) {
                StepCoroutine(coroutine); // Advance a couroutine once the waiting conditions are done
            }
        }
        coroutines.RemoveIf((x) => x.IsFinished());
       
        // do other engine stuff
        CallLateUpdateForAllObjects();
        RenderCurrentFrame();
    }
}
}
2 Likes

Oh, thanks for illustrating that. I was under the impression that you didn’t need the very first MoveNext, but I see how it works now.

That’s about right, though when you yield an IEnumerator Unity does not start a new coroutine but simply runs the new IEnumerator as a sub routine. I actually made a working custom coroutine scheduler in just under 200 lines of code :slight_smile: It supports as many custom queues as you want. It’s a general C# implementation that should not be bound to Unity. Though, here’s a MonoBehaviour test component that shows how to setup queues besides the default queue and how to drive all those queues. In this test it supports WaitForFixedUpdate, null as well as a custom WaitForSeconds instance. In theory we could use Unity’s WaitForSeconds object, however since the internal float value is private we would need to use reflection to get access to the value. So it’s easier to create our own variant :slight_smile:

The test coroutine shows all features and even schedules some code in the fixed update loop. It also runs a sub coroutine in between. I haven’t tested all features, but it should work just fine.

CoroutineSchedulerWaitQueue and some additional info

The CoroutineSchedulerWaitQueue class may be a bit over engineered. It uses a custom sorted linked list to store all coroutines which wait for a certain amount of seconds. When a new coroutine is added to the queue, we simply sort it into the linked list at an increasing timeout value. That means the coroutine that has the smallest timeout is always the first one. That way the queue only has to check the first coroutine if it timed out which makes this queue quite fast, even when you have thousands of coroutines waiting for a certain timeout.

When a queue is “ticked”, the processed coroutines are pulled into a temporary list, so that the processed coroutine can add itself to the same list during its processing without running in an infinite loop. We want to continue the coroutine the next time the queue is processed and not the right now.

In theory we could even add support for multithreading. However due to the reused temp list this would currently not be possible. also it would require a lot of synchronisation at various points to avoid race conditions. Though it’s kinda trivial to add a RunOnSeperateThread yield value once the syncing is finished. Something similar would also be necessary if you want to support other async stuff like a webrequest.

@Bunny83 Is there a way you can observe the difference? I was initially expecting that as well, thinking that when yielding an IEnumerator without StartCoroutine, stopping parent coroutine will also stop the nested IEnmuerator call. But a quick test showed that child IEnumerator call continues running as if it was fully independent coroutine started with StartCoroutine.

Here is my test.

    public void Go(){
        foo =  StartCoroutine(C1());
    }
    IEnumerator C1()
    {
        for (int i=0; i<5; i++) {
            Debug.Log($"C1 {i}");
            yield return new WaitForSeconds(0.5f);

            if (i == 1) {
                yield return C2();
                //yield return StartCoroutine(C2());
            }
        }
    }

    IEnumerator C2()
    {
        for (int i=0; i<5; i++) {
            Debug.Log($"C2 {i}");
            yield return new WaitForSeconds(0.5f);

            if (i == 1) {
                StopCoroutine(foo);
            }
        }
    }

Doesn’t matter if StartCoroutine is used or not when calling C2 behavior is the same. C2 keeps running after C1 is stopped.

That’s really strange because I’m pretty sure I made exactly that test some time ago when they introduced the CustomYieldInstruction and the support for yielding IEnumerators.

You’re right that in your specific case C2 does indeed continue when you stop the parent. However that seems to be connected to the fact that you stop foo inside C2. If I stop the foo from the outside, C2 is stopped as well. So it seems to depends from where you stop the coroutine which is really strange. There seems to be really strange logic behind the scenes. If I stop foo from outside C2, C2 stops as well

Try this one:

        UnityEngine.Coroutine foo;

        public void Go()
        {
            foo = StartCoroutine(C1());
            StartCoroutine(Terminator());

        }

        IEnumerator Terminator()
        {
            yield return new UnityEngine.WaitForSeconds(3);
            StopCoroutine(foo);
        }
        IEnumerator C1()
        {
            Debug.Log("C1");
            yield return new UnityEngine.WaitForSeconds(1);
            yield return C2();
            Debug.Log("C1 done");
        }

        IEnumerator C2()
        {
            for (int i = 0; i < 5; i++)
            {
                Debug.Log($"C2 {i}");
                yield return new UnityEngine.WaitForSeconds(1);
            }
            Debug.Log("C2 done");
        }

You will notice that C2 will count to 1 and then stop. I just tried adding another nested coroutine C3 inside C2 and it’s stopped as well. So your case seems to be a special case which is really strange ^^. It would be really interesting how they have implemented their scheduler on the C++ side. It’s really weird.

1 Like