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. Dismiss Notice

[unsolved]Nested coroutine won't return from yield when StopCoroutine called

Discussion in 'Scripting' started by Razorwings18, Dec 28, 2014.

  1. Razorwings18

    Razorwings18

    Joined:
    May 8, 2014
    Posts:
    69
    TL;DR:
    I have a line:
    - yield return someMonoBehaviourScript.StartCoroutine(name, parameter);
    If I call:
    - someMonoBehaviourScript.StopCoroutine(name);
    The coroutine "name" stops properly, but the yield never returns

    Long version:
    Consider the two scripts at the bottom of this post.

    Human_Behaviour.cs is added to a GameObject

    The problem is that StartSingleYieldingCoroutine never returns from the:
    "yield return callingScript.StartCoroutine(name, parameter);"
    line. BUT Test() coroutine is properly STOPPED and RESTARTED.
    Functionally, it's not a problem, but I guess every call to StartSingleYieldingCoroutine will leave a junk entry in the stack while the application is running.

    What I'm expecting should happen:
    1. Update calls StartSingleCoroutine
    2. StartSingleCoroutine starts StartSingleYieldingCoroutine (INSTANCE 1)
    3. StartSingleYieldingCoroutine (INSTANCE 1) starts Test
    4. ---- Next frame ----
    5. Update calls StartSingleCoroutine
    6. StartSingleCoroutine starts StartSingleYieldingCoroutine (INSTANCE 2)
    7. StartSingleYieldingCoroutine (INSTANCE 2) stops Test
    8. StartSingleYieldingCoroutine (INSTANCE 1) naturally returns from "yield return callingScript.StartCoroutine" since Test is now stopped. <<< THIS NEVER HAPPENS
    9. StartSingleYieldingCoroutine (INSTANCE 2) starts Test
    etc...

    As additional information, if I remove the "callingScript.StopCoroutine(name);" line from StartSingleYieldingCoroutine, the "yield return callingScript.StartCoroutine..." returns properly.

    Please, keep in mind the posted code is just a very simplified version of what I'm trying to do, to make the problem I'm facing as evident as possible. I KNOW that if I were trying to just accomplish restarting Test() on every frame, there are easier ways to do so.
    CoroutineHandler.cs is a handler that makes using coroutines easier, and I'm trying to make this work in this particular way since it will help simplify this classes' use.

    Human_Behaviour.cs
    Code (CSharp):
    1. using UnityEngine;
    2. using System;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5.  
    6. public class Human_Behaviour : MonoBehaviour {
    7.     CoroutineHandler coroutineHandler = new CoroutineHandler();
    8.    
    9.     void Awake(){
    10.         coroutineHandler.Initialize(this);
    11.     }
    12.    
    13.     void Update () {
    14.         coroutineHandler.StartSingleCoroutine("Test", null, true);
    15.     }
    16.    
    17.     int debugTest = 0;
    18.     int debugStartRun = 0;
    19.     IEnumerator Test(){
    20.         debugStartRun++;
    21.         yield return new WaitForSeconds(5F);
    22.         ++debugTest;
    23.     }
    24. }
    CoroutineHandler.cs
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4.  
    5. public class CoroutineHandler {
    6.     MonoBehaviour callingScript;
    7.  
    8.     public void Initialize(MonoBehaviour script){
    9.         callingScript = script;
    10.     }
    11.  
    12.     public void StartSingleCoroutine(string name, object parameter = null, bool stopOtherInstance = false){
    13.         // Take advantage of the calling script's MonoBehaviour to run StartSingleYieldingCoroutine
    14.         callingScript.StartCoroutine(StartSingleYieldingCoroutine(name, parameter, stopOtherInstance));
    15.     }
    16.  
    17.     public IEnumerator StartSingleYieldingCoroutine(string name, object parameter = null, bool stopOtherInstance = false){
    18.         callingScript.StopCoroutine(name);
    19.          yield return callingScript.StartCoroutine(name, parameter);
    20.         debug.Log("NEVER GETS HERE!!!");
    21.     }
    22. }
     
  2. kietus

    kietus

    Joined:
    Jun 4, 2013
    Posts:
    54
    Hello,

    You should avoid executing named coroutine in that case. In 4.6 they added a StopCoroutine(Coroutine) function that should be nicer to use. (Use a Coroutine object rather than a string).

    Executing a coroutine in the Update function doesn't seems the best idea. Update is launched each frame, that mean you try to exec/Stop a coroutine each frame. Your coroutine can't finish, it wait for 5 sec while a frame is 1/30 sec.

    For your test i will suggest you to add keyInput in ur update, in order to exec your coroutine statement only when a key is pressed.
     
    Razorwings18 likes this.
  3. Razorwings18

    Razorwings18

    Joined:
    May 8, 2014
    Posts:
    69
    Woha! I did not know that, thanks! I'm still on 4.5.

    This is just for testing purposes, and that is exactly the intended behavior. If the coroutine were to end, I'd have known StopCoroutine wasn't working.

    I'd still like to see a way to have this working in 4.5 without having to resort to upgrading to have access to the new StopCoroutine. I've read some people have seen performance degrade in the new version.
     
  4. kietus

    kietus

    Joined:
    Jun 4, 2013
    Posts:
    54
  5. Razorwings18

    Razorwings18

    Joined:
    May 8, 2014
    Posts:
    69
    Thanks for pointing me to this.

    Definitely not the case. Each StartCoroutine is called inside the MonoBehaviour (callingScript) where the coroutines are located. As I mentioned, the coroutine does run properly (as evidenced by the "debugStartRun" variable changing).
     
  6. Dameon_

    Dameon_

    Joined:
    Apr 11, 2014
    Posts:
    542
    Y'know you can have multiple versions of Unity on your computer, so you can test performance yourself pretty easily. I haven't noticed any performance issues that'd make me give up uGUI and various other nifty upgrades.
     
  7. Razorwings18

    Razorwings18

    Joined:
    May 8, 2014
    Posts:
    69
    Thanks! I'll give it a try.
     
  8. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,377
  9. Razorwings18

    Razorwings18

    Joined:
    May 8, 2014
    Posts:
    69
    Sorry I didn't respond sooner, I'm not getting e-mail notifications for some reason.

    As I mentioned, this is part of a Coroutine handling class that is already working great in my project, with the upside that it has almost no impact on garbage or performance. The problem itself is the result of me trying to make the class completely seamless; as it is, I have to call a method at the end of every Coroutine to inform the class that is has ended naturally... no bueno.

    Your solution looks quite powerful.
     
  10. Marrt

    Marrt

    Joined:
    Feb 7, 2012
    Posts:
    612
    Last edited: Jun 18, 2015
  11. Razorwings18

    Razorwings18

    Joined:
    May 8, 2014
    Posts:
    69
    Nope; unfortunately, I found that if you StopCoroutine on a Coroutine that is being yielded, it'll keep yielding indefinitely.

    I've found no workaround either, so it's one thing I keep my eyes peeled for. I can't say how it fares on newer Unity versions, since I'm still on 4.5.
     
  12. Marrt

    Marrt

    Joined:
    Feb 7, 2012
    Posts:
    612
    ok, thanks, i did a small workaround, not as capable as my initial idea but breakable from the outside. But you can only break/transit to another State once per frame.

    Dumbed down example if anyone is interested:

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public class wasd : MonoBehaviour {
    6.  
    7. public enum State {    Approach,BackOff,Charge,MeleeCombat,RangedCombat,Focus,Jumping,Melee,Patrol,Staggered,Unalarmed,Commanded,Dead,None }
    8. bool statebreak = false;
    9.  
    10. private    IEnumerator FSM(){      
    11.     while (true){
    12.         switch (state) {              
    13.             case State.Approach:    yield return StartCoroutine( StateApproach()    );break;                  
    14.             case State.BackOff:        yield return StartCoroutine( StateBackOff()        );break;                              
    15.             case State.Charge:        yield return StartCoroutine( StateCharge()        );break;              
    16.             case State.MeleeCombat:    yield return StartCoroutine( StateMeleeCombat()    );break;              
    17.             case State.Melee:        yield return StartCoroutine( StateMelee()        );break;                  
    18.             case State.Patrol:        yield return StartCoroutine( StatePatrol()        );break;
    19.             case State.Staggered:    yield return StartCoroutine( StateStaggered()    );break;              
    20.             case State.Unalarmed:    yield return StartCoroutine( StateUnalarmed()    );break;              
    21.             case State.Commanded:    yield return StartCoroutine( StateCommanded()    );break;                  
    22.             case State.Dead:        yield return StartCoroutine( StateDead()        );yield break;    break;
    23.             default:                yield return null;                                break;
    24.         }
    25.     }
    26. }
    27.  
    28. private    IEnumerator StatePatrol(){
    29.  
    30.     //coroutine local variables here
    31.  
    32.     while(true){      
    33.      
    34.         //stuff happens here, also state Transition are declared here
    35.      
    36.      
    37.         //End block of any State
    38.         yield return new WaitForEndOfFrame();                //end of current frame, all Coroutines and Update()/LateUpdate() calls that may set stateBreak have finished by now
    39.         if(stateBreak){    stateBreak = false; yield break; }    //break State, the new "state" is already set by the function that has set "stateBreak to true"
    40.         yield return null;                                    //continue after next Update()          
    41.     }
    42. }
    43.  
    44. public    void Hit(int damage){
    45.     health -= damage;  
    46.          
    47.     state = State.Staggered;
    48.     stateBreak = true;    //current state will be broken at the end of this Frame, next State will be running after Update() of the next frame      
    49. }
    50.  
    51. //Info, Unity Timing: 1.Update - 2.Coroutines - 3.LateUpdate, then WaitForEndOfFrame();
    52.  
    53. }
    54.  
    55.  
     
    Last edited: Jun 18, 2015
  13. Dantus

    Dantus

    Joined:
    Oct 21, 2009
    Posts:
    5,667
    I usually don't promote my own Asset Store packages. In this case, it seems to be fit perfectly, especially because I can't see another direct solution.

    I was having some comparable issues and created a custom coroutine solution to overcome this and other issues. You can find the link in my signature.