- Home /
Need to call yield TWICE ??? (ANSWERED)
A great big thank-you to ALDONETTO who actually did find the actual answer to this actual question, thank you very much Aldo.
The solution is simply that the print-to-the-console statement is buggy / behaves strangely.
Simply change the test routine to this form:
var someThing:Transform;
function runMeOneChunkAtATime()
{
for(x=0;x<100000000;++x) r += Mathf.Sqrt(x+r);
print("aaa");
someThing.position.x +=50; // add this line
yield; // just the one needed
etc...
}
And, thanks to Aldonetto, you will see that it performs perfectly and consistently in moving the object - while the print statement perfectly and consistently exhibits the strange behaviour explained below. (You will see the two things matching and then not matching in time.)
An interesting practical upshot of this:
If you are using the print statement to debug time-based programming, it does not work reliably. BUT if you add the bizarre "double-yield" as a quick fix during development -- in fact the print statement will then effectively work properly for you during development.
Once again thank you very much Aldonetto for the awesome find. Thanks.
If you are truly an expert with yield and coroutines ... perhaps you can explain this!
In Javascript/Unityscript. Make a testing button ..
function OnGUI()
{
if (GUI.Button (Rect (400,10,70,50), "TEST"))
{ runMeOneChunkAtATime(); }
}
Now the following test routine ..
function runMeOneChunkAtATime()
{
var r:float;
var x:int;
for(x=0;x<100000000;++x) r += Mathf.Sqrt(x+r);
print("aaa");
yield;
yield;
for(x=0;x<100000000;++x) r += Mathf.Sqrt(x+r);
print("bbb");
yield;
yield;
for(x=0;x<100000000;++x) r += Mathf.Sqrt(x+r);
print("ccc");
yield;
yield;
for(x=0;x<100000000;++x) r += Mathf.Sqrt(x+r);
print("ddd");
yield;
yield;
}
Now - why have double yield statements in each position?
First: put just ONE yield statement in each place, as you wold expect, and run it.
Notice it "doesn't work" - the first one "does not pause". Notice 'aaa' and 'bbb' appear on the console with no time gap between.
Now try it with double yield in each position. Notice the behaviour is perfectly what you would expect.
How to explain?
The problem is: you cannot use coroutines inside OnGUI (as OnGUI itself is not a coroutine). At least it will not work the way you want it. A coroutine will start each time you hit the button, but will continue its execution the next frame on its own.
What you need to do is make a state-machine, where hitting the button will advance the current state.
Answer by aldonaletto · Jan 18, 2012 at 08:24 PM
I tested this script, and it performed the same with one or two yield (at least in my PC notebook).
Anyway, if you don't want to stop Unity while a long routine executes, you must place yield instructions at well chosen places. Simple yield instructions like those in your code just stop coroutine execution and return to the calling routine - but an internal control structure remains active, and in the next update cycle the coroutine is automatically resumed in the instruction after the yield (that's why @dannyskim was talking about frames).
If you must call some slow function 10000 times at some point of your game, for instance, and want to span the loop over several frames, you can use this:
for (var i = 0; i < 10000; i++){
SlowFunction(i);
yield; // let Unity free until next frame
}
But since each yield releases Unity up to the next frame, this will take 10000 frames to execute! You should thus find a good compromise - maybe yield at each 100 iterations:
var n = 0;
for (var i = 0; i < 10000; i++){
SlowFunction(i);
if (++n == 100){ // reached 100?
n = 0;
yield; // let Unity do other jobs until next frame
}
}
Since this is a coroutine, control will return to the caller code immediately (or in the first yield, I'm not sure), but it will keep running behind the scenes, thus you can do anything you want while the long routine executes.
NOTE: Yield cannot be used in all routines! The docs mention Update and FixedUpdate, but other periodic functions like LateUpdate and OnGUI can't be coroutines as well. Another limitation: a coroutine can't return anything (it actually returns IEnumerator, thus no other value can be returned).
EDITED: I tested the script again with several combinations of yields, and it produced weird and unexpected results for each combination. I also tested it in a C# version, but the results were the same. It's really frustating: you just can't predict what the hell it will do in each case!
But the problem is actually caused by the sync between the console and the update cycles: Unity performs as expected, resuming after the yield instruction in the next frame, but the console is updated at weird intervals.
The script below shows the actual time and frame count, but the first two lines appear at the same time, while the others seem to appear at the right intervals:
function Update(){ if (Input.GetButtonDown("Fire1")){ runMeOneChunkAtATime(); } }
var startTime: float; var startFrame: int;
function PrintTime(id: String){ print(id+" T= "+(Time.realtimeSinceStartup-startTime).ToString("F3")+" F= "+(Time.frameCount-startFrame)); }
function runMeOneChunkAtATime(){
var r:float;
var x:int;
startTime = Time.realtimeSinceStartup;
startFrame = Time.frameCount;
for(x=0;x<100000000;++x) r += Mathf.Sqrt(x+r);
PrintTime("aaa");
yield;
for(x=0;x<100000000;++x) r += Mathf.Sqrt(x+r);
PrintTime("bbb");
yield;
for(x=0;x<100000000;++x) r += Mathf.Sqrt(x+r);
PrintTime("ccc");
yield;
for(x=0;x<100000000;++x) r += Mathf.Sqrt(x+r);
PrintTime("ddd");
} The values updated at the right times when a GUIText was used instead of print.
NOTE: I finally found the answer to my own question: the coroutine returns control to the caller when the first yield is executed, not immediately.
Hi Aldo!
"I tested this script, and it performed the same with one or two yield (at least in my PC notebook)."
In short we have tested this in many many situations and the behaviour always happens. If you have found an exception let me know the details please!
In the interested of cutting down CLUTTER, I have completely excised all the general chat about how to program interrupts, which is totally irrelevant to the question at hand.
1- About the double yield: it's really a mystery, and I tried several alternatives - each one producing different and more disappointing results! But after burning my brain a lot, I suspected that the culprit is the Editor; I'll try a standalone version and return here soon.
2- Using yield in a long process is somewhat like calling Application.Process$$anonymous$$essages in Delphy Pascal: it lets the system execute other pending jobs before returning to the long routine. You don't have too much control over the time it will take, thus you must find by trial and error a good compromise.
@aldonaletto #2 except that a yield takes exactly one frame because Unity's coroutine scheduler is not-preemptive.
You are correct Aldo - it was just that the print statement is flakey. $$anonymous$$y earlier testing was wrong.
Thank you so much for actually finding the real answer here!
Answer by dannyskim · Jan 17, 2012 at 06:45 PM
Well, when utilizing yield inside of a java script, I believe it's default application would be to yield for one frame. So when using two yield statements, you are now yielding for two frames.
Using yield by itself and waiting for one frame, or trying to control waiting by utilizing frames in my opinion can give you varying results in varying situations, hardware, processing allocation, etc.
Is there a particular reason you're not using
yield WaitForSeconds(amount);
Or if you really want to utilize waiting for one specific frame, maybe you should try using
yield new WaitForFixedUpdate();
for more predictable results?
Hmm, you're right. For some reason I took your code as being in a while loop, which in that case it would seem to yield for one frame ins$$anonymous$$d of returning control to the caller.
@Fattie: Yield does have a connection to frames; using "yield" by itself will wait until the next frame. e.g., this is functionally equivalent to Update:
function Start () {
while (true) {
yield;
}
}
Actually you are both right. yield DO return control to the caller.
But in this case, the caller is a scheduler inside Unity engine. And this scheduler calls each coroutine once per frame (or per fixed frame if you use WaitForFixedUpdate).
Consequently, yield return null (in C#) or just yield (in JS) is equivalent to waiting exactly one frame.
To be more precise, the scheduler code is inside the $$anonymous$$onoBehaviour class. That's why all coroutine stop when the calling GameObject is destroyed.
$$anonymous$$ore on coroutines can be found in this previous answer: http://answers.unity3d.com/questions/36690/mechanics-of-coroutines.html
Hi @eric, in your example it's just returning to the (basically!) cron process that is running Start (I think what $$anonymous$$ryptos is saying also).
In any event, this is beco$$anonymous$$g one of those questions with a lot of incidental talk about issues that everyone understands but not actually addressing the meat of the question!
Do you know why the "double" yield behaves differently?
Answer by syclamoth · Jan 18, 2012 at 09:23 AM
May I ask what, exactly, you expect your code to do? Remember that when coding in UnityScript, what you see isn't actually what you get. In this case, the line
runMeOneChunkAtATime();
Will be translated into
StartCoroutine(runMeOneChunkAtATime());
at compile time!
Would this explain the inconsistencies you're experiencing? This is one of the big reasons why I don't like the JavaScript implementation in Unity, btw- there's too much magical stuff going on that is really not obvious to the user.
It will work correctly. Yield just interrupts the execution at this point and Unity goes on to the next frame. Using two yields will just wait one more frame.
Debug.Log()
(or the $$anonymous$$onoBehaviour's alias print()
) collects multiple messages to improve the performance. As far as i can tell the console is updated asynchronously(but rendered / displayed per frame), so using it for frame by frame feedback isn't a good idea. Usually you shouldn't block Unity for such a long time. Like @aldonaletto said you should yield in between or your framerate drops to something around 0
Try using another feedback, the GUI for example. It is rendered and updated reliably every frame. You could increment an int-value and display it with a GUI.Label.
"It will work correctly." You need only cut and paste the script to see it not working!
In fact aldonetto is the hero of the day and has figured out the problem, as he discovered the print statement is flakey. ($$anonymous$$y earlier testing on that issue was fucked.) Thanks again for the actual answer, aldo!!!
Your answer
Follow this Question
Related Questions
Stuck while solving a Coroutine problem 0 Answers
Waypoint / Yield help 1 Answer
Coroutine a function within a loop? 1 Answer
Confused about Coroutines 2 Answers