Search Unity

StartCoroutine - When is it required?

Discussion in 'Scripting' started by omatase, Oct 21, 2014.

  1. omatase

    omatase

    Joined:
    Jul 31, 2014
    Posts:
    159
    I know the advantages of StartCoroutine. I use it when I need to all the time. What I don't understand is times where I am not trying to simplify asynchronous code but that Unity won't execute a method correctly unless I call it using StartCoroutine.

    So, aside from the cases where I *want* to leverage the power of StartCoroutine, as is outlined in the documentation, how can I know *when* it has to be used?
     
  2. numberkruncher

    numberkruncher

    Joined:
    Feb 18, 2012
    Posts:
    953
    You never have to use them, but they can be helpful when scheduling a series of actions.

    For instance:
    • Fire bullet and lock user input.
    • Wait 0.1 seconds.
    • Play sound effect and spawn muzzle flash.
    • Wait 0.5 seconds.
    • Release lock on user input.
    Might be implemented as follows:
    Code (csharp):
    1. private IEnumerator FireWeapon() {
    2.     SpawnBullet();
    3.     lockUserInput = true;
    4.  
    5.     yield return new WaitForSeconds(0.1f);
    6.  
    7.     PlaySoundEffect();
    8.     SpawnMuzzleFlash();
    9.  
    10.     yield return new WaitForSeconds(0.5f);
    11.  
    12.     lockUserInput = false;
    13. }
    14.  
    15. private void Update() {
    16.     if (Input.GetButtonDown("Fire") && !lockUserInput)
    17.         StartCoroutine(FireWeapon());
    18. }
     
  3. omatase

    omatase

    Joined:
    Jul 31, 2014
    Posts:
    159
    I have a different problem then perhaps. But sometimes when I make a method, usually a private method, I can't call that method from within the script elsewhere. Just sometimes, mind you. And when I throw a StartCoroutine around it the problem goes away. And in fact I'm having that problem right now. Here's the code I'm using right now:

    It starts out with a click on a button (new UGUI). That click executes the code here. It's a bit big so suffice it to say the only important line for the purposes of this question is "intializeCurrentMatches();".
    Code (CSharp):
    1. public void TogglePlay()
    2. {
    3.     if (!_showingMatches)
    4.     {
    5.         CurrentMatchesObject.SetActive(true);
    6.         _showingMatches = true;
    7.         HOTween.Rewind(string.Format("ShowMatches{0}", TeamIndex));
    8.         HOTween.Rewind(string.Format("ShowFindNewMatch{0}", TeamIndex));
    9.         HOTween.Play(string.Format("ShowMatches{0}", TeamIndex));
    10.         HOTween.Play(string.Format("ShowFindNewMatch{0}", TeamIndex));
    11.  
    12.         // refresh the active battles
    13.         intializeCurrentMatches();
    14.  
    15.         ((ManageTeamScript)ManageTeamObject.GetComponent<ManageTeamScript>()).FeatureTeam(TeamIndex);
    16.     }
    17.     else
    18.     {
    19.         CurrentMatchesObject.SetActive(false);
    20.         _showingMatches = false;
    21.         HOTween.Rewind(string.Format("HideMatches{0}", TeamIndex));
    22.         HOTween.Rewind(string.Format("HideFindNewMatch{0}", TeamIndex));
    23.         HOTween.Play(string.Format("HideMatches{0}", TeamIndex));
    24.         HOTween.Play(string.Format("HideFindNewMatch{0}", TeamIndex));
    25.  
    26.         ((ManageTeamScript)ManageTeamObject.GetComponent<ManageTeamScript>()).CancelFeature();
    27.     }
    28. }
    That code calls a private method with a single line shown here:
    Code (CSharp):
    1. private void intializeCurrentMatches()
    2. {
    3.     ((CurrentMatchesScript)CurrentMatchesObject.GetComponent<CurrentMatchesScript>()).InitializeActiveBattles(Party.PartyId);
    4. }
    Here's the method that is never called successfully. If I step into with my debugger the execution hits the method signature line, but then returns without stepping into the method.
    Code (CSharp):
    1. public IEnumerator GetActiveBattlesByPartyId(int partyId, CurrentMatchesScript matchesScript)
    2. {
    3.     WWW www = new WWW(string.Format("http://localhost:12527/api/battle?teamId={0}", partyId), null, DirectorHelper.GetHeaders());
    4.  
    5.     yield return www;
    6.  
    7.     byte[] data = Convert.FromBase64String(www.text.Replace("\"", string.Empty));
    8.  
    9.     if (OnGetBattlesByPartyIdSucceeded != null)
    10.     {
    11.         OnGetBattlesByPartyIdSucceeded(this, DirectorHelper.DeserializeFromNetwork<List<Battleschool.Common.Entities.Contract.Battle>>(data));
    12.     }
    13. }
    However, if I change that second snippet to the following, then the method in the third snippet executes just fine.
    Code (CSharp):
    1. private void intializeCurrentMatches()
    2. {
    3.     StartCoroutine(((CurrentMatchesScript)CurrentMatchesObject.GetComponent<CurrentMatchesScript>()).InitializeActiveBattles(Party.PartyId));
    4. }
     
  4. GarthSmith

    GarthSmith

    Joined:
    Apr 26, 2012
    Posts:
    1,240
    Can you post InitializeActiveBattles()? Do you have any yields in that function? How does GetActiveBattlesByPartyId() get called?
     
  5. omatase

    omatase

    Joined:
    Jul 31, 2014
    Posts:
    159
    Sorry, yeah didn't mean to leave one out.

    Code (CSharp):
    1. public IEnumerator InitializeActiveBattles(int partyId)
    2. {
    3.     BattleDirector director = gameObject.AddComponent("BattleDirector") as BattleDirector;
    4.     director.OnGetBattlesByPartyIdSucceeded += (caller, battles) =>
    5.     {
    6.         Battles = battles;
    7.  
    8.         int index = 0;
    9.  
    10.         foreach (var current in Battles)
    11.         {
    12.             var currentMatch = CurrentMatches[index];
    13.             var currentMatchScriptComponent = currentMatch.GetComponent(typeof(MatchScript));
    14.  
    15.             ((MatchScript)CurrentMatches[index].GetComponent(typeof(MatchScript))).Battle = current;
    16.             CurrentMatches[index].SetActive(true);
    17.         }
    18.     };
    19.  
    20.     yield return StartCoroutine(director.GetActiveBattlesByPartyId(partyId, this));
    21. }
     
  6. cowtrix

    cowtrix

    Joined:
    Oct 23, 2012
    Posts:
    322
    You can't call Coroutines like normal functions and expect them to work. Coroutines, especially as implemented in Unity, require some magic behind the scenes to work like they do. This is expected behaviour. So the answer to your question is always call StartCoroutine when you want to... start a coroutine.

    However, as a matter of style, I don't like using Coroutines to just "churn time" so to speak. Something that just sits there and `yield return null`'s until the cow's come home is a waste of perf and a messy implementation. You should be using events and callbacks for those kind of patterns.

    Check this article out for an in depth explanation of the coroutine implementation.
     
    Last edited: Oct 21, 2014
  7. omatase

    omatase

    Joined:
    Jul 31, 2014
    Posts:
    159
    I don't need InitializeActiveBattles to be treated any differently than a normal function, but I do want GetActiveBattlesByPartyId to be. However, there's no way to call GetActiveBattlesByPartyId without a yield return statement, which cascades the requirement for an IEnumerator return type back to InitializeActiveBattles. OK, fine but still I didn't see the need to run that method with StartCoroutine. Anyway I think I just misunderstood what Unity considers to BE a coroutine. Is it any method with an IEnumerator return type? Well maybe it doesn't matter as long as I know that even methods that can execute in a linear fashion need to be executed with StartCoroutine so long as I want the asynchronous functionality out of a separate method call within the first method call.
     
  8. cowtrix

    cowtrix

    Joined:
    Oct 23, 2012
    Posts:
    322
    Why do you need to call GetActiveBattlesByPartyId with a yield statement? It seems like you're just throwing away that IEnumerator value. You can have a function with a IEnumerator return type without it being considered a coroutine. However, calling `yield` within that function WILL make it one.
     
  9. omatase

    omatase

    Joined:
    Jul 31, 2014
    Posts:
    159
    You have to use StartCoroutine when doing calls with WWW otherwise the game will hang on the network call. I didn't realize calling yield in a method made it a coroutine. I thought it was just a coroutine if you called it with StartCoroutine and that a requirement of the coroutine method itself was returning IEnumerator.
     
  10. GarthSmith

    GarthSmith

    Joined:
    Apr 26, 2012
    Posts:
    1,240
    The yield is what's requiring the StartCoroutine().

    Pretty much when you yield a coroutine you are saying come back to this function later. You can't use normal functions like that because you usually don't want the computer to just sit and freeze until the yield is ready.
     
  11. omatase

    omatase

    Joined:
    Jul 31, 2014
    Posts:
    159
    Oh of course. Duh, why I never thought of it in that way I don't know. I always thought "yield" is a C# construct and StartCoroutine is not so yield should work independently of StartCoroutine. And of course it does, but not by just a straight call, you'd have to use it in an iterator or something. OK, don't know why I never looked at it that way.

    Thanks
     
  12. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    yield is a C# construct and StartCoroutine is unique to Unity. It's basically a way to use IEnumerator to monitor the execution progress of a set of instructions. @superpig had a fantastic overview of Coroutines on AltDev which is sadly lost now. Hopefully he'll see this and have a backup somewhere.