CoRoutine and yielding WaitForSeconds - does not loop

Hi,

I’m trying to make my first CoRoutine work. It seems to me that the code is identical to both the documentation and all the posts here on the forum, but I’m fairly sure the problem stems from some oversight on my part. I simply cannot spot it, though, and post in the hopes that someone can point it out to me.

I get no errors.
The code runs once, printing the time once, and then simply appears to stop. At least no subsequent prints appear.
The condition: if (count >= sceneSc.ballList.Count) is never true.

From an Update() function, I start the CoRoutine like this:

if (!waitingForBalls)
                {
                    waitingForBalls = true;
                    StartCoroutine(WaitForBallsToStop());
                }

This is the CoRoutine function:

private IEnumerator WaitForBallsToStop()
    {
        int count = 0;
        foreach (GameObject go in sceneSc.ballList)
        {
            //Count the balls that have stopped
            if(go.rigidbody.IsSleeping())
            {                
                count++;
            }
        }

        //if count is equal to total, change State.
        if (count >= sceneSc.ballList.Count)
        {
            print("Sending ball stopped event");
            EventManager.instance.QueueEvent(new BallStopped());
            SetState(StateType.PLAYING, this);
            GuiBuilder.infoText = "Ready for action!";
            waitingForBalls = false;
            StopCoroutine("WaitForBallsToStop");
        }

        //wait a bit and calculate again
        print(Time.time);
        yield return new WaitForSeconds(0.5f);      
    }

I’m fairly sure I could make this work with Invoke() or InvokeRepeating(), but I’m very annoyed that I don’t understand what’s going on here.

As an aside, are there inherent differences in the performance (as in, use of CPU), of InvokeRepeating() and a CoRoutine?

Hmmm… WaitForBallsToStop() only contains one yield return statement, and that one’s not in a loop. Where are you expecting it to loop?

Perhaps you should do the following :

void Update()
{
StartCoroutine(WaitForBallsToStop());
}

private IEnumerator WaitForBallsToStop()
    {
    var waitingForBalls = true;

    while (waitingForBalls)
        {
        int count = 0;

        foreach (GameObject go in sceneSc.ballList)
        {
            //Count the balls that have stopped
            if(go.rigidbody.IsSleeping())
            {                
                count++;
            }
        }


        //if count is equal to total, change State.
        if (count >= sceneSc.ballList.Count)
        {
            print("Sending ball stopped event");
            EventManager.instance.QueueEvent(new BallStopped());
            SetState(StateType.PLAYING, this);
            GuiBuilder.infoText = "Ready for action!";
            waitingForBalls = false;
            //StopCoroutine("WaitForBallsToStop");
        }
        else
        {
        //wait a bit and calculate again
        print(Time.time);
        yield return new WaitForSeconds(0.5f);      
        }
        }
    }

ahh! I see.
I figured the yield return line actually was the looping mechanism - that it would call the function again in 0.5 seconds.

Many thanks, I’m sure this will solve my problem.

I wouldn’t use the exact code Diablo posted as it would kick off a new Coroutine every frame.

Good catch, I was just posting off the top of my head. Indeed, some check should be in place in your update statement to prevent it from being called more than once.

If you look at the IEnumerator interface, there are two methods of note :

Current(), which gets the current value in the enumeration

MoveNext(), which moves to the next value, making it the current value

Now, I can create a simple class called “AlphabetTop3” that implements this interface and returns a sequence of strings, say… A, B, C. Then, I can do the following :

IEnumerator<string> test = new AlphabetTop3Class();
test.MoveNext();
print(test.Current()); // prints "A"
test.MoveNext();
print(test.Current()); // prints "B"
test.MoveNext();
print(test.Current()); // prints "C"

Now having to call MoveNext() and Current() every time you want to enumerate something can be a pain in the butt, so the c# compiler gives you a shortcut to doing this via the foreach statement :

foreach (var letter in new AlphabetTop3Class())
   {
   print(letter);
   }

Behind the scenes the compiler is translating this into a corresponding loop that calls MoveNext() and Current(). This is all to make life easier for us.

Now c# also provides a “yield return” statement, and the c# does some magic behind the scenes so that you don’t have to create your own IEnumerator class, you could just create a method :

public IEnumerator AlphabetTop3Method()
   {
   yield return "A";
   yield return "B";
   yield return "C";
   }

and this will now behave exactly the way the class did. This is how you would iterate through it :

foreach (var letter in AlphabetTop3Method())
   {
   print(letter);
   }

Underneath the scenes, the c# compile is actually doing this :

while (AlphabetTop3Method.MoveNext())
   {
   print(letter);
   }

And here you can see that, just like in my first example, the first call to MoveNext() will execute ‘yield return “A”’, the second call to MoveNext() will execute ‘yield return “B”’, and the third and final call to MoveNext() will execute ‘yield return “C”’. So the “yield return” statement is the same as saying "hey, here’s a value, take it and I will yield (that is, I will stop and wait here) until the next time you call me, at which point I’ll move on to the next statement.

Now, in all the examples I presented, we actually cared about what the return value was and we did something with it; that is, we printed it. AFAIK (someone please correct me if I’m wrong here) Unity doesn’t care about the return value when used in their Update() method (which is really IEnumerator Update()) nor do they care what you return in their StartCoroutine() method. So you can do the following :

IEnumerator Update()
{
yield return null;
yield return "Hello World";
yield return new WaitForSeconds(0.5f);
}

and Unity will keep looping through each yield return and throws away its value, and when it reaches the end it starts over from the beginning.

I know this can be confusing, but if you keep reading up on it over and over and keep trying numerous examples on your own it will eventually dawn on you.