- Home /
Execution manner of co routine
Hi, these are my code
using UnityEngine;
using System.Collections;
public class sample : MonoBehaviour {
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
if (Input.GetKeyDown("space"))
{
print ("called before coroutine");
StartCoroutine("testCOR");
print ("called after coroutine");
}
print ("this is end of frame.");
}
IEnumerator testCOR()
{
for(int i=0;i<3;i++)
{
print ("Now number is: "+i);
print ("Just before coroutine");
yield return null;
}
}
}
When I press spaceBar just one time, the result pattern I am expecting is:
....................
....................
this is end of frame.
this is end of frame.
this is end of frame.
called before coroutine
Now number is: 0
Just before coroutine
called after coroutine
this is end of frame.
called before coroutine
Now number is: 1
Just before coroutine
called after coroutine
this is end of frame.
called before coroutine
Now number is: 2
Just before coroutine
called after coroutine
this is end of frame.
called before coroutine
called after coroutine
this is end of frame.
this is end of frame.
this is end of frame.
In stead I am getting the following:
....................
....................
this is end of frame.
this is end of frame.
this is end of frame.
called before coroutine
Now number is: 0
Just before coroutine
called after coroutine
this is end of frame.
this is end of frame. //why twice. Is unity taking two frames?
Now number is: 1
Just before coroutine
this is end of frame. //where is "called after coroutine"?
Now number is: 2
Just before coroutine
this is end of frame.
this is end of frame.
....................
....................
When I press spaceBar again, I got these:
....................
....................
this is end of frame.
this is end of frame.
called before coroutine
Now number is: 0
Just before coroutine
called after coroutine
this is end of frame.
this is end of frame.
Now number is: 1
Just before coroutine
this is end of frame.
Now number is: 2
Just before coroutine
this is end of frame.
this is end of frame.
....................
....................
The dots means I am getting "this is end of frame." line continuously forever.
So, each time I press spaceBar, I get similar pattern. Probably coroutine does not work like the way I thought. But how does they execute? I need to know this. It is now such a mystery to me now. I thought like these:
when you encounter yield statement, control flow jumps from coroutine to next line of "startCoroutine()" and then finishes rest of the jobs in update() method. After finishing, flow returns(of course after lateUpdate and other stuffs) to again Update method. This time flow goes to co-routine again. But "with having previous state". Loops goes on and on until the "forLoop" does not let it go further. Then flow again returns to update method and completes the rest jobs to render that frame. This happens whenever something triggers the coroutine.
If we use coroutine's yield like "waitforFixedUpdate" manner,then execution resume on next physics change instead of next frame. If we use "waitForEndOfFrame", then execution resume after LateUpdate and rendering(GUI drawing?) on next frame in stead of next frame on "update" method. That means coroutine will not execute while we are on update method if we use "waitForEndOfFrame". Rather it will execute when we finish drawing our frame.
So those were my thoughts on coroutine. Clearly I was wrong. But what is right regarding Coroutine. Please shed some lights on it. It is driving me crazy!
Actually I read it a while ago. The resource is quite good. Sadly it fails to explain in depth of control flow or I have failed to understand it properly. Anyway I recommend everyone new to coroutine to first check Unity Docs about coroutine And then unityGems docs.
Answer by kaiyum · Nov 10, 2013 at 04:19 PM
So I will be answering my own question. Ok. the concept of program counter(PC) is very important. When you run any computer program, binary executes line by line. "Something" reads a line and executes it, then moves it to next line. This "something" is called PC. It is actually a pointer that holds the address of "next instruction to execute". Lets look at the following simple C++ program.
Line 1 #include<iostream>
Line 2 using namespace std;
Line 3 void anotherMessage()
Line 4 {
Line 5 cout<<"yet another message";
Line 6 }
Line 7 int main()
Line 8 {
Line 9 cout<<"Hi there!";
Line 10 showMeMessage();
Line 11 return 0;
Line 12 }
Line 13 void showMeMessage()
Line 14 {
Line 15 cout<<"this is the message";
Line 16 }
Ok.Lets think as a machine. First I will look on a file called "iostream.h" sothat I will know how to print something. Then with "using namespace std;" I realized all the names are standard. Meaning "cout" "endl" etc means something which is standard. If we do not do it then I might say cout is for "a command to get out of comod"!. This is pre-processing. After preprocessing, actual execution happen first at main method. I shall not look at "void anotherMessage()" or "void showMeMessage()" first. First I will look at main method. Main is our entry point. So PC will be at first on "main()" method. Then I first print "hi there". Then I encounter a showMeMessage() method. Then I go straight its definition. After that I print "this is the message". Then I return 0 and exit. So the pattern of PC by line number is: 1>2>7>8>9>10>13>14>15>16>11>12
Now I will explain how coroutine execute as same manner. head up to my first code. Carefully get the line numbers. This frame we are at line number 12, update method. Then 13>14. Are we pressing spacebar now at 14? Lets say we not. So jump into 20. On the next frame, program detect we pressed spacebar. So what will happen?
12>13>14>15>16>17 then it will find a coroutine and will execute it. So after 17,it will be on 23. 23>24>25>26>27>28>29. On 29, we see null(if we do not,lets say we find another coroutine after return. Then PC will go there. You can nest endless coroutine I guess), so we go to 30 and then:
30>18>19. Mark this position. After 19, we are out from the code block of if statement. 19>20>21>22
Thus on this frame(the frame program detects a "spacebar press"), we have this pattern for the sample code: 12>13>14>15>16>17>23>24>25>26>27>28>29>30>18>19>20>21>22
Now on the next frame, program will not detect a "spacebar press" if we do not hit spacebar again. For simplicity, lets assume, we haven't hit any key. So what will happen on next frame. Remember we haven't completed for loop as we returned an yield when we have i=0. So when will we resume. How will PC move this time?
12>13>14. it does not detect any spacebar hit, so code block of if statement "will not execute". Thus after 14, 20>21>22.Wait a min,we haven't resumed our coroutine. Yes gentleman, now we will resume. So after 22, we will not move to next frame. Rather,
23>24>25>26>27>28>29>30. After this we will move on next frame. On next frame we will not register any spaceBar. So, similar pattern will goes on and on until we are done with coroutine(i>=3).
One question remain, if we constantly pressing spaceBar.Meaning, on each frame we detect spaceBar hit, what will happen?[edit: at first, it will complete first invocation. When first coroutine instance is complete, it will go to second one. And thus it goes on and on] After first coroutine invocation, next invocation will wait for finishing of current one? or things will go mess up? Probably another guru will clear up. And I can do some experiments!
So the summery of this long post is: After first yield, it will resume when next frame will complete everything, not in the middle!
EDIT 1:
I have found some interesting ideas. It is about nested coroutines.
what will happen when you do like:
yield return StartCoroutine("anotherCOR");
when anotherCOR() looks like similar to:
IEnumerator anotherCOR()
{
for(int i=0;i<3;i++)
{
print ("Now number is: "+i);
print ("Just before coroutine");
yield return null;
}
}
Well on each invocation of testCOR(), at first anotherCOR() has to be completed. This means: for i=0 of testCOR(), at first i of anotherCOR() has to be 0-1 and then 2. Only then next invocation of testCOR will happen. I guess we can nest another like:
IEnumerator anotherCOR()
{
for(int i=0;i<3;i++)
{
print ("Now number is: "+i);
print ("Just before coroutine");
yield return StartCoroutine("yetAnotherCOR");
}
}
Much like nested if-else. I am thinking its practical use. What it can be?!
From my tests, the sequence is:
1- StartCoroutine: the coroutine executes until a yield is found, when Unity leaves the coroutine stopped and resume execution of the caller code right after the StartCoroutine instruction.
2- This frame is rendered;
3- In the next frame, after all Updates and LateUpdates, Unity resumes execution of the stopped coroutine, and runs it until a yield is found, when the coroutine again gets stopped and Unity takes care of the next pending coroutine. When all coroutines have been serviced, Unity goes to step 2, and so on.
Answer by Berenger · Nov 10, 2013 at 03:11 PM
StartCoroutine will create an instance of the coroutine that will run it's course independantly of the Update function. If you press space several times, you'll have several instances. If you want an in-depth explaination, check this out : http://www.altdevblogaday.com/2011/07/07/unity3d-coroutines-in-detail/
For your problem, insert the prints "before" and "after" inside the for loop of the coroutine.
From my little observation, coroutine always resume after update function. Is this true completely? If I have several coroutines instance triggered, then all instance will be executed one after another,right?
Your answer
Follow this Question
Related Questions
Yield everytime boolean is True (C#) 1 Answer
Waiting for input using yield and coroutines 1 Answer
StartCoroutine important for using yield? 1 Answer
How to use yield within a class function 2 Answers
Update() can not be a coroutine. 2 Answers