Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

I Don't Get Coroutines

Discussion in 'Scripting' started by John-B, Jul 30, 2010.

  1. John-B

    John-B

    Joined:
    Nov 14, 2009
    Posts:
    1,253
    I've used coroutines for things like camera moves and alpha fades, and they always work as expected. But I've run into a case where I can't get a coroutine to work.

    I have a long calculation to perform (several seconds) and want to show an animated busy graphic. I show the graphic (set active to true), set a flag to tell the coroutine when to stop, call the coroutine, then call the calculations function. The coroutine updates the graphic after calling yield WaitForSeconds, and checks the flag in a loop that repeats until the flag is true.

    Here's how I call the coroutine:
    Code (csharp):
    1. filterDone = false;
    2. waitMessage.active = true;
    3. originalImageGUI.color.a = 0.1;
    4. waitCursor.enabled = true;
    5.  
    6. DoBusy();  // call coroutine
    7.  
    8. // do calculations...
    9.    
    10. filterDone = true;
    11.  
    And here's the coroutine:
    Code (csharp):
    1. function DoBusy () {
    2.     var count: int;
    3.  
    4.     count = 3;
    5.    
    6.     while (!filterDone) {
    7.         waitCursor.texture = cursors[count];
    8.         count -= 1;
    9.         if (count < 0)
    10.             count = 3;
    11.         yield WaitForSeconds (0.3);
    12.     }
    13.    
    14.     waitMessage.active = false;
    15.     originalImageGUI.color.a = 1.0;
    16.     waitCursor.enabled = false;
    17. }
    18.  
    The problem is, the graphic never shows up until the calculations are finished. It flashes briefly right before it's hidden again. The loop that updates the graphic is only executed once, apparently just before it quits.
     
  2. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,401
    Code (csharp):
    1. DoBusy();  // call coroutine
    2.  
    3. // do calculations...
    4.    
    5. filterDone = true;
    This calls the coroutine and then immediately sets filterDone to true. So,

    Code (csharp):
    1. while (!filterDone) {
    will only ever execute once.

    The problem is, you can't actually do that with a coroutine, unless your long calculation is itself a coroutine that yields every so often. But that's pretty dodgy at best. That's the sort of thing you want to use actual threads for, and coroutines aren't threads. They don't run while your long calculations are calculating; all scripting in Unity is normally done in one thread.

    --Eric
     
  3. John-B

    John-B

    Joined:
    Nov 14, 2009
    Posts:
    1,253
    I think you misunderstood. The comment "// do calculations" is for brevity in this snippet. That's where the code goes that does the number crunching. So the done flag is not set until that code is finished, which can take several seconds.
     
  4. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,401
    No, I understood that. It doesn't matter if the number-crunching code takes a nanosecond or 100 years to finish. What happens with your code when you do "DoBusy()", is that DoBusy runs, once, up to the "yield WaitForSeconds" statement. Then your "// do calculations" run. Then "filterDone" is set to true. Then your coroutine runs again the next frame (since .3 seconds probably has long since passed), and since filterDone is true, the loop never runs again. As I said, coroutines are not threads.

    The only way to make coroutines behave "sort of" like threads is to make your number-crunching code into a coroutine that yields occasionally, in order to give other coroutines a chance to run, but that's generally a poor way of handling things. If you need actual threads, which you would in this case, then you'd use actual threads, not coroutines.

    --Eric
     
  5. John-B

    John-B

    Joined:
    Nov 14, 2009
    Posts:
    1,253
    That's the part I didn't understand. I thought when you did a yield WaitForSeconds, it would continue with other code, then come back and pick up after the yield after the time elapsed. I didn't know I could use actual threads. I can't find anything about threads in any of the Unity docs.

    One other thing I still don't understand is why my GUITexture (waitMessage in the sample code) never shows. It gets set to active before the yield, yet it only flashes briefly when the DoBusy function finishes. Could it just be a matter of the screen not updating while in the calculations loop? I'd be (relatively) happy if I could just show a busy message while the calculations code is running.
     
  6. afalk

    afalk

    Joined:
    Jun 21, 2010
    Posts:
    164
    John, I am having similar issues, and apparently I mis-understodd the yield functionality as well.

    Eric THANK you for the clarification.

    Any chance we can get some info on Thread creation/management or usage ???

    Thanks!
     
  7. menneske

    menneske

    Joined:
    Jan 23, 2009
    Posts:
    44
    You are exactly right, the screen is not updating while doing the calculations.

    As Eric said, coroutines are not threads. They are more like pausable functions - the yield statement does just what it sais, it yields control so other code can execute.

    But when you yield, you yield atliest until the next frame. In essence, this is what unity does with your code:

    • update screen
      execute DoBusy until yield statement
      execute heavy calculation
      update screen
      execute DoBusy after yield statement
    If you want to show a busy animation, you'll need to put your heavy calculation in its own coroutine and stuff it full of yield's.

    Or simply just
    Code (csharp):
    1.  
    2. function HeavyCalculation() {
    3.   ShowBusySign();
    4.   yield;
    5.   // Do heavy calculations
    6.   HideBusySign();
    7. }
    8.  
     
  8. menneske

    menneske

    Joined:
    Jan 23, 2009
    Posts:
    44
  9. afalk

    afalk

    Joined:
    Jun 21, 2010
    Posts:
    164
    Yep Google is a great source - buit was hoping that perhaps there were some Unity specific examples/references (being at work I havent had a chance to go digging properly)
     
  10. John-B

    John-B

    Joined:
    Nov 14, 2009
    Posts:
    1,253
    I just tried this, taken from some .Net sample code:

    Code (csharp):
    1. var job: System.Threading.ThreadStart;
    2. var theThread: System.Threading.Thread;
    3.    
    4. job = new System.Threading.ThreadStart(DoBusy);
    5. theThread = new System.Threading.Thread(job);
    6. theThread.Start();
    But it doesn't work. The DoBusy function never gets called. Maybe I'm not translating the C# example correctly. I'll second afalk's request for some sample code.

    Also, is there any way to force a screen update in Unity? Will yield do that? Like I said, I'd be satisfied with just showing a graphic and forget the animation.
     
  11. John-B

    John-B

    Joined:
    Nov 14, 2009
    Posts:
    1,253
    I tried that, and the texture still doesn't show.

    Code (csharp):
    1. function FilterImage () {
    2.     waitMessage.active = true;     
    3.     yield;
    4.     // calc loop...
    5.     waitMessage.active = false;
    6. }
    Also, watching the texture's active flag in the inspector while running, it never gets set to true.
     
  12. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,401
    Yeah, what I do in that situation is:

    Code (csharp):
    1. // set up graphics
    2. yield;
    3. // do intensive stuff
    That way you give the graphics a chance to show for one frame before the game is essentially paused while doing the intensive stuff.

    As far as threads, the only thing Unity-specific about them is that Unity functions aren't thread-safe and are guaranteed to crash sooner or later. So you can only use thread-safe .net functions. Here's an example though (might want to increase the value of "loops" if your computer is a lot faster than mine):

    Code (csharp):
    1. import System.Threading;
    2.  
    3. var loops = 100000;
    4. private var progress = 0.0;
    5.  
    6. function Start () {
    7.     var tex = new Texture2D(2, 2);
    8.     var guiTex = new GameObject("GUITex", GUITexture);
    9.     guiTex.guiTexture.texture = tex;
    10.     guiTex.guiTexture.color = Color.white;
    11.     guiTex.transform.position.y = .5;
    12.     guiTex.transform.localScale = Vector3(0.0, 0.1, 1.0);
    13.  
    14.     Thread(DoStuff).Start();
    15.    
    16.     while (progress < 1.0) {
    17.         guiTex.guiTexture.pixelInset.width = Screen.width*progress;
    18.         yield;
    19.     }
    20.    
    21.     Destroy(guiTex);
    22.     Destroy(tex);
    23. }
    24.  
    25. // NOTE: UNDER NO CIRCUMSTANCES SHOULD ANY UNITY-SPECIFIC FUNCTIONS BE CALLED IF THIS IS BEING USED AS A THREAD.
    26. // THEY ARE NOT THREAD-SAFE AND WILL CRASH RANDOMLY. ONLY USE THREAD-SAFE .NET FUNCTIONS.
    27. // DO NOT REMOVE THIS LABEL UNDER PENALTY OF LAW.
    28.  
    29. function DoStuff () {
    30.     var rand = new System.Random();
    31.     for (count = 1; count <= 1000; count++) {
    32.         for (i = 0; i < loops; i++) {
    33.             var foo = System.Math.Sqrt(rand.Next());
    34.         }
    35.         progress = count*.001;
    36.     }
    37. }