Wayback Machinekoobas.hobune.stream
May JUN Jul
Previous capture 13 Next capture
2021 2022 2023
1 capture
13 Jun 22 - 13 Jun 22
sparklines
Close Help
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
avatar image
0
Question by Fattie · Jan 16, 2012 at 10:42 PM · javascriptcoroutineyield

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?

Comment
Add comment · Show 2
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image Kryptos · Jan 19, 2012 at 08:55 AM 0
Share

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.

avatar image Fattie · Jan 20, 2012 at 08:05 AM 0
Share

Fortunately this is irrelevant, Aldo found the answer

3 Replies

· Add your reply
  • Sort: 
avatar image
3
Best Answer

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.

Comment
Add comment · Show 6 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image Fattie · Jan 19, 2012 at 07:48 AM 0
Share

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!

avatar image Fattie · Jan 19, 2012 at 07:57 AM 0
Share

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.

avatar image aldonaletto · Jan 19, 2012 at 12:55 PM 0
Share

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.

avatar image Kryptos · Jan 19, 2012 at 01:06 PM 0
Share

@aldonaletto #2 except that a yield takes exactly one frame because Unity's coroutine scheduler is not-preemptive.

avatar image Fattie · Jan 20, 2012 at 08:02 AM 0
Share

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!

Show more comments
avatar image
0

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?

Comment
Add comment · Show 4 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image dannyskim · Jan 18, 2012 at 08:34 AM 0
Share

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.

avatar image Eric5h5 · Jan 19, 2012 at 08:04 PM 0
Share

@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;
     }
 }
avatar image Kryptos · Jan 19, 2012 at 08:15 PM 1
Share

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

avatar image Fattie · Jan 20, 2012 at 06:56 AM 0
Share

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?

avatar image
0

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.

Comment
Add comment · Show 2 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image Bunny83 · Jan 20, 2012 at 03:50 AM 1
Share

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.

avatar image Fattie · Jan 20, 2012 at 08:05 AM 0
Share

"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

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Follow this Question

Answers Answers and Comments

10 People are following this question.

avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image

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

yield works, but yield WaitForSeconds does not? 1 Answer


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges