Search Unity

Problem - Calling Coroutine with multiple params multiple times

Discussion in 'Scripting' started by Ziron999, May 20, 2019.

  1. Ziron999

    Ziron999

    Joined:
    Jan 22, 2014
    Posts:
    282
    Code (CSharp):
    1.     private void Start()
    2.     {
    3.         Water.Init(TotalWater, WaterAmount);
    4.         Wood.TotalProductionTime = 0.3f;
    5.         Wood.Init(TotalWood, WoodAmount);
    6.  
    7. StartCoroutine(Update(Water.Automation, Water, WaterProgressBar, TotalWater));
    8.         StartCoroutine(Update(Wood.Automation, Wood, WoodProgressBar, TotalWood));
    9.     }
    10. IEnumerator Update(bool Automation, Production prod, Image ProgressBar, Text Total)
    11.     {
    12.         while (Automation)
    13.         {
    14.             yield return null;
    15.             if (prod.TotalValue < prod.TotalStorage)
    16.             {
    17.                 if (Total.color != Color.white)
    18.                     Total.color = Color.white;
    19.                 if (!DetailsWindowOpen)
    20.                     prod.UpdateToCurrentValues(ProgressBar, Total);
    21.                 else
    22.                     prod.UpdateToCurrentValues(ProgressBar, Total, PerSecLabel);
    23.             }
    24.             else if (Total.color != Color.red)
    25.                 Total.color = Color.red;
    26.             yield return null;
    27.         }
    28.         yield return null;
    29.     }
    I want to start off by saying that surprisingly this works without issue. It's when it comes to handling pausing/stopping that i am running into a huge issue. How can i properly do this?

    These are both calling this ienumerator within the same monobehavior class.
    The ONLY problem is stopping/controlling them.
     
  2. Ziron999

    Ziron999

    Joined:
    Jan 22, 2014
    Posts:
    282
    nvm, already figured it out sorry:
    Code (CSharp):
    1. Coroutine water = null;
    2. Coroutine wood = null;
    3.         water = StartCoroutine(Update(Water.Automation, Water, WaterProgressBar, TotalWater));
    4.         wood = StartCoroutine(Update(Wood.Automation, Wood, WoodProgressBar, TotalWood));
    5.  
    6.         StopCoroutine(water);
    7.         StopCoroutine(wood);
     
  3. dgoyette

    dgoyette

    Joined:
    Jul 1, 2016
    Posts:
    4,196
    You might have gotten something working, but I'd recommend some changes to your code.

    First, calling your method "Update" is a poor choice. Unity has a built-in "special" method called Update, and it's very confusing looking at this code and seeing that you've declared another method called Update. It's likely Unity won't have a problem with this, but to a Unity developer it just looks bizarre, and is likely to lead to confusion.

    Second, you're stopping the coroutine on the same frame you started it. That means, at best, the coroutine will run for a single "yield return", and then stop. I'm not sure how it would be running correctly for you at this point, given that the first thing you're doing is "yield return null". I would expect that none of the work in your Update coroutine would actually be done.

    Make sure you understand that when you call StartCoroutine, that doesn't make the rest of the code after it wait until the coroutine is done. So, the way you have your code now, it will call StartCoroutine once for "water", and then that coroutine will call "yield return null". Then, all in the same frame, StartCoroutine will be called again for "wood", then "yield return null", then StopCoroutine will be called on both routines. It won't loop.
     
  4. Sluggy

    Sluggy

    Joined:
    Nov 27, 2012
    Posts:
    989
    I think your slightly confused about the yield return null. That simply yields the enumerator, effective pausing for one frame. Yield break on the other hand will stop the coroutine right away. I totally agree that Update is a terrible name though. Even worse, some magic functions can actually be declared as enumerators and will start a coroutine automatically if they have the IEnumerator return type (Start comes to mind).
     
  5. palex-nx

    palex-nx

    Joined:
    Jul 23, 2018
    Posts:
    1,748
    You'd better have some AutomationManager class to handle all automations in it's Update method or single coroutine. Creating at least 2 coroutine for every such object in your game may hit your game performance when there will be a lot of objects of this kind.
     
  6. dgoyette

    dgoyette

    Joined:
    Jul 1, 2016
    Posts:
    4,196
    I don't disagree with you, but I also wasn't stating that `yield return null` stops the coroutine. I was saying that because `yield return null` forces the coroutine to resume on the next frame, and because he's explicitly calling StopCoroutine on that coroutine on the first frame, the coroutine will be dead before it can resume on the next frame.

    To be clear, mainly for the OP, here's some code that starts a coroutine and kills it on the first frame, and tries to do some logging. This is the same thing the OP is doing in his script:

    Code (CSharp):
    1.     public class SomeScript : MonoBehaviour
    2.     {
    3.         // Start is called before the first frame update
    4.         void Start()
    5.         {
    6.             Debug.Log($"About to Start the coroutine at frame {Time.frameCount}");
    7.             var myRoutine = StartCoroutine(SomeRoutine());
    8.  
    9.             Debug.Log($"About to Stop the coroutine at frame {Time.frameCount}");
    10.             StopCoroutine(myRoutine);
    11.             Debug.Log($"The coroutine is stopped at frame {Time.frameCount}");
    12.         }
    13.  
    14.         private IEnumerator SomeRoutine()
    15.         {
    16.             Debug.Log($"SomeRoutine has started at frame {Time.frameCount}");
    17.             yield return null;
    18.             Debug.Log($"SomeRoutine continues at frame {Time.frameCount}");
    19.  
    20.             for (int i = 0; i < 5; i++)
    21.             {
    22.                 Debug.Log($"Loop {i} in frame {Time.frameCount} at time {Time.timeSinceLevelLoad}");
    23.             }
    24.         }
    25.     }
    If you run this, you'll get the following console log entries:
    • About to Start the coroutine at frame 1
    • SomeRoutine has started at frame 1
    • About to Stop the coroutine at frame 1
    • The coroutine is stopped at frame 1
    Note that none of the code after the `yield return null` in the coroutine is ever executed, as the coroutine was stopped before it could be resumed on frame 2.

    So, @Sluggy and I are probably in complete agreement here on `yield return null', and I maybe just phrased things unclearly in my initial response.

    Also, `yield break;' is interesting. I've never come across that before as a way to halt a coroutine early. Good stuff.
     
    Sluggy likes this.
  7. Ziron999

    Ziron999

    Joined:
    Jan 22, 2014
    Posts:
    282
    1. i use nulls because it seems to help reduce garbage collection ( i don't know why ).
    2. it isn't named update it's just an example name
    3. assigning a variable to seconds (if you use them) also seems to reduce garbage collection then just throwing a number inside of the seconds. There is a ton of tricks i already know. I just needed to know how to STOP parameter ienumerators properly and i already figured out how to and provided the answer.

    the null variables is because i actually put those variables in the class so they initialize on load as null then create a load function of sometime on when to stop/start them. pretty easy