Coroutine Control and StartCoroutine() : Coroutine

Discussion in 'Scripting' started by Adam Buckner, Sep 1, 2011.

  1. Adam Buckner

    Adam Buckner

    Unity Technologies

    Joined:
    Jun 27, 2007
    Messages:
    3,418
    I am firing off a number of coroutines to do some work for me over a series of frames.

    Usually this is a fire and forget scenario - but not always.

    StartCoroutine returns a reference to that Coroutine
    function StartCoroutine (routine : IEnumerator) : Coroutine
    http://unity3d.com/support/documentation/ScriptReference/MonoBehaviour.StartCoroutine.html

    But annoyingly enough StopCoroutine requires a string, and there is no overload that takes the provided reference:
    http://unity3d.com/support/documentation/ScriptReference/MonoBehaviour.StopCoroutine.html

    StopAllCoroutines stops all coroutines running on a behaviour, which is over-kill.
    http://unity3d.com/support/documentation/ScriptReference/MonoBehaviour.StopAllCoroutines.html

    If I have a coroutine:
    Code (csharp):
    1.    
    2.     IEnumerator WaitASec (Object myObject) {
    3.         // SomeCode();
    4.         yield return new WaitForSeconds (waitUpTime);
    5.         // SummoreCode();
    6.     }
    And I call it twice ... or three times...

    I can't stop a specific instance, as "StopCoroutine" requires a string... and I now have two or three coroutines with that same string in the water and racing fast...

    I don't want to "StopAllCoroutines"...

    I can't use a reference to that coroutine...

    There's gotta be a way, no?
  2. bdev

    bdev

    New Member

    Joined:
    Jan 4, 2011
    Messages:
    604
    Its definitely frustrating theres no StopCoroutine that takes a Coroutine. I rarely ever start them with a string to begin with.

    So one way you could accomplish this is by using something like this:
    Code (csharp):
    1.  
    2. class CancellingCoroutine : IEnumerator
    3. {
    4.     public bool stop;
    5.  
    6.     IEnumerator enumerator;
    7.     MonoBehaviour behaviour;
    8.  
    9.     public readonly Coroutine coroutine;
    10.  
    11.     public CoroutineStopable(MonoBehaviour behaviour, IEnumerator enumerator)
    12.     {
    13.         this.behaviour = behaviour;
    14.         this.enumerator = enumerator;
    15.         this.coroutine = behaviour.StartCoroutine(this);
    16.     }
    17.  
    18.     public object Current { get { return enumerator.Current; } }
    19.     public bool MoveNext { return !stop  enumerator.MoveNext(); }
    20.     public void Reset() { enumerator.Reset(); }
    21.     public void Dispose() { enumerator.Dispose(); }
    22.     public implicit operator YieldStatement ( CancellingCoroutine self ) { return self == null ? null : self.coroutine; }
    23. }
    24.  
    Then to start it you would do:
    Code (csharp):
    1.  CancellingCoroutine ccoroutine = new CancellingCoroutine(this, CoFunctionEnumerator() );
    To wait for it
    Code (csharp):
    1.  yield return ccoroutine.coroutine;
    Then to stop it
    Code (csharp):
    1.  ccoroutine.stop = true;
    Its a hacky work around and it would be alot better if YieldStatement wasnt functionally sealed.


    If you change your IEnumerator functions to use IEnumerator<YieldStatement> instead it'd be best. But i don't know how that would fair for builtin stuff like Start...

    You could also set a done variable in the class when MoveNext returns false, thus allowing you to wait multiple times.
    Last edited: Sep 1, 2011
  3. Adam Buckner

    Adam Buckner

    Unity Technologies

    Joined:
    Jun 27, 2007
    Messages:
    3,418
    So - I essentially need to rebuilt coroutines to be able to stop them?

    Humph!
  4. flaminghairball

    flaminghairball

    Member

    Joined:
    Jun 12, 2008
    Messages:
    768
    I'm having trouble visualizing your scenario here… Why are you calling your coroutine three times and then trying to stop it?
  5. Adam Buckner

    Adam Buckner

    Unity Technologies

    Joined:
    Jun 27, 2007
    Messages:
    3,418
    I'm calling a coroutine on an object, or objects.

    This is a fader that fades out the color.a on an object. I have a pool of objects and when I need to fade one out, I call the coroutine and include the object to be faded. Fire and Forget.

    Unless I need to stop that object from fading or remove it immediately.

    With this coroutine I can fade any object I pass to it.
    Code (csharp):
    1.     IEnumerator FadeObject (Object myObject) {
    2.         while (SomeFadingTest) {
    3.             //  SomeFadingCode()
    4.             yield return null;
    5.         }
    6.     // Put object back in the pool code
    7.     }

    Annoyingly enough, I can say:

    Code (csharp):
    1.     Coroutine myNewCoroutine = StartCoroutine (FadeObject(myCurrentObject));
    But there isn't anything I can really do with the reference to myNewCoroutine.

    I just want to be able to track the Coroutines I want and stop the ones I need to so they don't build up and cause problems.

    I also want to maintain enough performance to work with mobile platforms.
  6. VanJones

    VanJones

    New Member

    Joined:
    May 1, 2011
    Messages:
    34
    You should create a new Class for the Fading, put a bool in it to see if you want it to fade or not and just set it to false to quit the while-loop in the Coroutine and let it finish.
    Just stopping the Coroutine might lead to some unpredictable behaviour, might happen that the Object isn't "put back in the pool".

    That way you can also Lock the Object you are fading and prevent Multiple Fades at the same time.
  7. flaminghairball

    flaminghairball

    Member

    Joined:
    Jun 12, 2008
    Messages:
    768
    Ok, why can't you just send the object a message to break out of the coroutine?

    (Unity's handling of this is indeed pretty awful)
  8. Gigiwoo

    Gigiwoo

    Member

    Joined:
    Mar 16, 2011
    Messages:
    1,514
    I came to the same conclusion as vanjones (is that your real name or a political statement?). Anyway, basically, you want to put your behavior in another class. And put the co-routine on that. Then you can stop each one individually with no trouble.

    Gigiwoo.
  9. Ntero

    Ntero

    New Member

    Joined:
    Apr 29, 2010
    Messages:
    1,436
    Another option is to pass in a boolean to disable the coroutine, though it can be a bit messy
    Code (csharp):
    1.  
    2. delegate bool CoroutineCanceller();
    3. IEnumerator Coroutine(CoroutineCanceller canceller)
    4. {
    5.     // SomeCode();
    6.     yield return new WaitForSeconds (waitUpTime);
    7.     if(canceller)
    8.     {
    9.         yield break;
    10.     }
    11.     // SummoreCode();
    12. }
    13.  
    Then as usage:
    Code (csharp):
    1.  
    2. public IEnumerator OuterCoroutine()
    3. {
    4.     bool cancel = false;
    5.     StartCoroutine(Coroutine(delegate{ return cancel; } ));
    6.     yield return new WaitForSeconds(1.0f);
    7.     cancel = true;
    8. }
    9.  
    The reason for the delegate as opposed to just a boolean is that is is passed by reference that way and can be modified and tweaked somewhat on the fly.

    You can then pass in variables of any scope and do it by reference (delegates will box up the bool value type, so it's a proper reference. You can use local scope, and then pass the same bool to two coroutines (and do a sort of first finished cancelling operation) or pass in class variables or whatnot. It's a bit messy sometimes to debug, but it does get the job done. You can even use it to build more complex comparers within the delegate itself.
  10. Adam Buckner

    Adam Buckner

    Unity Technologies

    Joined:
    Jun 27, 2007
    Messages:
    3,418
    This makes sense. I can feel the theory, and it feels good. Now I'll have to spend some time trying to visualize it and get it to work.

    TBH I'm not sure how I'd do this. If I can't grab the coroutine itself, I'm not sure how I can ask the object to break out of it… but this could be my ignorance on the details of coroutines. I understand a tiny bit about what they are doing under the hood, but certainly not enough.

    Ya, "what I said to VanJones". I think I get it… but I'll have to work thru it.

    (VanJones: http://en.wikipedia.org/wiki/Van_Jones) ??

    I'm seeing if I can get my head 'round this one, and it's benefits/ramifications. Again, I'm "feeling" the use of a delegate over a boolean variable more than I'm truly "grokking" it.

    I think I'll need a play-around.

    And yes, ultimately, I'll need to get that object back into the pool...
  11. VanJones

    VanJones

    New Member

    Joined:
    May 1, 2011
    Messages:
    34
    Sorry for the Offtopic!
    I'm from germany and when I came up with that Alias some Years back I didn't know about VanJones, then I registered to Twitter and wondered why everybody is following me :)
    My name is Jonathan and VanJones just seemed cool....
  12. Adam Buckner

    Adam Buckner

    Unity Technologies

    Joined:
    Jun 27, 2007
    Messages:
    3,418
    VanJones is cool.

    Keep it.

    And it's not like the linked Van Jones is (afaict) an unpleasant person to share a name with - like say Jim Jones or something. And I'd no clue who Van Jones was, but I've been out of the country (US) for a few years.

    I'll report back in a bit re: coroutines...
  13. Adam Buckner

    Adam Buckner

    Unity Technologies

    Joined:
    Jun 27, 2007
    Messages:
    3,418
    In the end, I realized while discussing the issue here and in chat that I was approaching this incorrectly.

    I was trying to approach this like a conventional reference, but finally realizing that I have the reference to the object being processed by the coroutine, I could break the coroutine from the inside. Tho' I appreciate the power of the delegate solution, in the end I used a boolean on the object being processed that I could set with the existing reference.
  14. bdev

    bdev

    New Member

    Joined:
    Jan 4, 2011
    Messages:
    604
    Yea, I do agree though a StopCoroutine function that takes a Coroutine object would solve this issue, and its lame that there is none.
  15. n0mad

    n0mad

    Member

    Joined:
    Jan 27, 2009
    Messages:
    3,731
    Hey, thanks a lot BDev for your approach ! It really helped !

    I kind of tweaked it even more so that in a single function, I'm able to create a unique coroutine with multiple parameters :

    1) Create a static Dictionary under a super class (let's call it "Model"), which will store all your new coroutines :
    Code (csharp):
    1. _coroutines = new Dictionary<string, UniqueCoroutine>();
    2) Tweak your CCoroutine class a bit (renamed it UniqueCoroutine here) :

    Code (csharp):
    1.  
    2. public class UniqueCoroutine : IEnumerator {
    3.  
    4.     public bool stop;
    5.     public bool _moveNext;
    6.     string _name;
    7.     IEnumerator enumerator;
    8.     MonoBehaviour behaviour;
    9.  
    10.     public readonly Coroutine coroutine;
    11.  
    12.     public UniqueCoroutine(MonoBehaviour behaviour, IEnumerator enumerator, string _refName)
    13.     {
    14.         this.behaviour = behaviour;
    15.         this.enumerator = enumerator;
    16.         this.stop = false;
    17.     this._name = _refName;
    18.         this.coroutine = this.behaviour.StartCoroutine(this);
    19.     }
    20.  
    21.     public object Current { get { return enumerator.Current; } }
    22.    public bool MoveNext() {
    23.         _moveNext = enumerator.MoveNext();
    24.         if (!_moveNext || stop)
    25.             return !Model.StopUCoroutine(_name);
    26.         else
    27.             return _moveNext;
    28.     }
    29.     public void Reset() { enumerator.Reset(); }
    30.  
    31.  
    32. }
    33.  
    So here, I put in bold what I added.
    I'm just adding a reference name (string _name) for later use in our freshly created UniqueCoroutine Dictionary (under Model).
    I'm also calling a destruction function whenever MoveNext is false (= coroutine is done).

    (I suppressed the last two lines, Dispose and YieldStatement because they're not a part of IEnumerator interface)

    3) Create a function under Model that create a new UniqueCoroutine :
    Code (csharp):
    1.  
    2. public static void UCoroutine(MonoBehaviour behaviour, IEnumerator enumerator, string _name){
    3.  
    4.         StopUCoroutine(_name);
    5.            
    6.         _coroutines.Add(_name, new UniqueCoroutine(behaviour, enumerator, _name));
    7.     }
    8.  
    It safely stops and replaces any existing coroutine under the same name by a new one. No more "dirty" coroutines left.

    And the StopUCoroutine function, still under Model, which has to return a bool for usage under UniqueCoroutine's MoveNext :

    Code (csharp):
    1.  
    2. public static bool StopUCoroutine(string _name){
    3.         if (_coroutines.ContainsKey(_name)){
    4.             _coroutines[_name].stop = true;
    5.             _coroutines.Remove(_name)
    6.                         return true;
    7.         }
    8.                
    9.                 return false; //failed to stop coroutine because it doesn't exist
    10.     }
    11.  


    _________________________



    Et voilà.
    Now, you can create or destroy any coroutine at will, it will never clone itself, and will accept multiple parameters aswell.

    Basically, it's a StopCoroutine() allowing multiple parameters.
    Works like a charm.




    Now I just have one last doubt : even if the UniqueCoroutine reference is removed from the Dictionary, and the MoveNext stopped because of stop = false, is the thread really removed from memory ?
    Last edited: Jan 18, 2013
  16. n0mad

    n0mad

    Member

    Joined:
    Jan 27, 2009
    Messages:
    3,731
    edit : changed the MoveNext override in UniqueCoroutine, there was an error which prevented block code to be executed at certain times.
  17. paulsz

    paulsz

    Member

    Joined:
    Mar 25, 2010
    Messages:
    20
    Hey guys, maybe this helps.

    http://www.youtube.com/user/prime31studios#p/u/2/Ex2td1En94Y it's a coroutine coordinator from prime31.

    It looks pretty good except for the fact that creating a new Job every time you want to start a Coroutine seems a little dangerous for the heap memory and I'm worried it will slow the game down because of the Garbage collector.

    What do you guys think about this ?
  18. paulsz

    paulsz

    Member

    Joined:
    Mar 25, 2010
    Messages:
    20
    Also
    when you use enumerator.Reset it will give a "NotSupportedException: Operation is not supported." i.e. the Reset method from the interface IEnumerator is not implemented in Unity :(

    I'm studying now the possibility of replacing IEnumerator with IEnumerable, since IEnumerable supposably has the Reset() method
    http://forum.unity3d.com/threads/92...t-your-coroutines?highlight=IEnumerator Reset