How to use the new StopCoroutine(IEnumerator)?

I started a coroutine that takes a Transform as one parameter. When I wanted to stop this using the new StopCoroutine what am I supposed to use as a parameter? I wanted to stop so the parameter isn’t even relevant. But no matter what I do, the coroutine won’t stop.

This is my coroutine function. I planned to call StopCoroutine to break from that while(true) loop. The while true loop acts as an Update() function with controllable update frequency.

StartCoroutine(LoseEscapeRoutine(transform));
and so…
StopCoroutine(LoseEscapeRoutine(???));

IEnumerator LoseEscapeRoutine(Transform transform)
    {
        int i = transform.position.x > 0 ? 1 : -1;

        while(true)
        {
            Vector3 v = transform.position;
            v.x += 0.1f * i;
            transform.position = v;
            yield return new WaitForSeconds(0.05f);
        }
    }
1 Like

You need to call it that way :

 StopCoroutine("LoseEscapeRoutine");

You can’t stop the coroutine using your way. It has to be called using the method’s string name.
Here is the quote :

Yeah I did saw that… so what is the point of the new StopCoroutine(IEnumerator) that came with 4.5? Can it only be used with parameter-less function? The documentation did not mention any…

// variable that holds reference to coroutine (IEnumerator)
public IEnumerator routine;

// get IEnumerator from Coroutine and start
routine = DoWork();
StartCoroutine( routine );

// stop Coroutine
StopCoroutine( routine );
5 Likes

You would think they would have added a StopCoroutine that accepted the ‘Coroutine’ object itself, as opposed to the enumerator that the coroutine operates on.

I’m still sticking to my RadicalCoroutine system I set up that is cancellable, and also has a way to implement your own custom YieldInstructions.

Ah, that hits the spot!
So the IEnumerator is a representation of yielded function. I always put the whole function in the StartCoroutine without knowing that. Thank you.

well… technically it is.

A function that has yield commands in it is an ‘iterator function’. All iterator functions are enumerable, so they must return an IEnumerator or IEnumerable.

‘iterator functions’ and the yield keyword are a .Net/mono defined thing. Unity just uses them to iterate over once per frame.

It’s not that an IEnumerator is a representation of a yielded function [sic]… it’s that a iterator function is compiled into an anonymous object that implements both IEnumerable and IEnumerator. It’s syntax sugar.

Oh, also note that when writing C# which is type safe, if your iterator function returns IEnumerable, you’ll have to call ‘GetEnumerator’ or cast to IEnumerator on the enumerable that comes out. The object implements both, but will come out typed as only one, and may throw a compiler error for type mismatch.

1 Like

This new way really is better. It’s more like the way everyone else handles parallel code. In other words, coming from a programming background, this new way is how I assumed they worked back in 3.0.

The idea is, don’t think of the coroutine on the page. Think of each copy of it. Say you have IEnumerator fancyHighlight(Transfom T); which does some sort of glow and bob. You might StartCoroutine this multiple times to flash multiple objects. So you don’t want to stop fancyHighlight, you want to stop the exact fancyHighlight on one particular object.

It’s really the same idea as GameObject gg = Instantiate();. When you spawn, you want to get a “handle” to what you just made, so you can control or destroy it. Likewise, when you spawn a coroutine, you want that same handle:

IEnumerator flashA, flashB;
flashA=fancyHighlight(thingA);
flashB=fancyHighlight(thingB);
… (start them)
StopCoroutine(flashA); // I can pick the exact copy of the coroutine.

FlashA might even be running a different coroutune. This lets me say “whatever you ran to make A flash, stop it.”

Just to let you know, this is why StopCoroutine(Coroutine routine) was added. Instead of making an IEnumerator instance for each coroutine, you just save the reference to each Coroutine that you start.

Can i use this to “yield return StartCoroutine(IEnumerator routine)”?

I am building a little Coroutine-StateMachine but i have trouble controlling the coroutines. If i stop the Coroutine from outside, the yield will never return and the statemachine is basically stuck… See the Code for better explanation, basically i want that “1” stops curStateR from anywhere and the FSM should return from “yield return StartCoroutine(curStateR)” and print “Ended1”

Here is a working Testcode of my FSM, dumbed down to the core issue:

public enum States { State1, State2, None }
private    States curState = States.State1;//2;    //current State
private    IEnumerator curStateR;                    //current Coroutine

void Update () {  
    //State1 Abort, doesn't print "Ended1"
    if(Input.GetKeyDown("1")){
        print ("aborted1 from outside");
        curState = States.None;          

        StopCoroutine(curStateR);
    }
  
    //State2 Abort, doesn't print "Ended2"
    if(Input.GetKeyDown("4")){
        print ("aborted2 from outside");
        curState = States.None;
      
        StopCoroutine("Coroutine2");
    }  
}

private    IEnumerator FSM(){  
    while (true){
        //print("FSM-Iteration");
        switch (curState) {                  
            case States.State1:
                curStateR = Coroutine1();
                yield return StartCoroutine(curStateR);
                print("Ended1");
            break;
              
            case States.State2:
                yield return StartCoroutine("Coroutine2");
                print("Ended2");
            break;
          
            default:
                yield return null;
            break;
        }
        yield return null;//Wait one frame for Input to reset
    }
}

private    IEnumerator Coroutine1(){              
    while(true){      
        print ("1 running");
      
        //those two will stop the Coroutine from within, "Ended1" will printed only for "2"
        if(Input.GetKeyDown("2")){ curState = States.None; yield break;                }
        if(Input.GetKeyDown("3")){ curState = States.None; StopCoroutine(curStateR);}
        yield return null;
    }
}

private    IEnumerator Coroutine2(){              
    while(true){
        print ("2 running");
      
        //those two will stop the Coroutine from within, "Ended2" will printed only for "2"
        if(Input.GetKeyDown("2")){ curState = States.None; yield break;                    }
        if(Input.GetKeyDown("3")){ curState = States.None; StopCoroutine("Coroutine2");    }
        yield return null;
    }
}

This guy shows the same problem:

In your code, the only place you ever instantiate the curStateR variable is within the “FSM” method, but there is no call to FSM, so your curStateR variable will always be null. Use the “Start” method within MonoBehavior to call FSM. And, as you have it written above, FSM should probably have a “void” return value.

If you are looking for an alternative to such hybrid system. I would suggest to have a look at Behaviour Tree. It’s really worth it. It could be a nice alternative to both FSM and coroutine, since it is tool to define logics that unfold over long period of time (several frames).

Have a look at Panda BT (http://www.pandabehaviour.com/), it’s a script-based Behaviour Tree framework.

The advantages over FSM are:
Flexibility: No need to define explicit transitions.
Scalability: You can organized your logic into a tree of actions and sub-actions.
Reusability: You can reuse part of whole of a BT to define other BTs.

As a couroutine, a task (an action) runs over several frames.

Adavantages of BT other coroutine are:
Simplicity: No IEnumator, No yield instruction, No Start/Stop coroutine.
Configurable: A BT tree can run on Update, FixedUpdate, LateUpdate or at your custom tick rate. Whereas coroutine runs only on Update.

Also, with Panda BT, you can visualized the tree as it is running, which gives you detailed information about what’s is actually going on with your system (extremely valuable for debugging).

Please contact me if you have any question about this tool.

The above example really was a dumbed down thing to show that “yield return StartCoroutine(curStateR)” never ever continues if “curStateR” is cancelled from outside itself, i think i carelessly removed the Start() before posting because i had some unrelated stuff in there, so it won’t really work as posted, sorry

Thanks, but i have overhauled my Statemachine to a pretty satisfying state. It also features a Queue so you can either switch state directly or just enqueue the next thing you want to do after your current state sequence has terminated into idle. I can post it if you like to see it.

@Marrt
I was not comparing BT to your specific implementation of FSM, but I was comparing it to FSM in general.

Great that you’ve come up with a system that you’re pretty satisfied with. Though, I would still advice to keep BT on your radar, it is really a great tool.

I know that you are making an implementation to fit your needs, so I can understand that you are not applying a strict definition of FSM. But I must say that I am uneasy calling it an FSM, since, by definition, there is no such thing as a queue or switching state directly; an FSM is just a set of states and a set of transitions. But I am curious to see your implementation. I would be even more interested to see how you use it. Let’s say, how would you implement a simple FSM like this one?

didn’t read anything else but you need to start to the coroutine with a string and then to stop it, stop it with a string. It’s stupid you can’t pass through the actual routine object.

This is how i would implement above statemachine with my current expertise, my TryActions&Queues(see below) are not needed here because all transition logic is located within the states, and not driven by external input when implemented.

StateMachinePacman.cs

using UnityEngine;
using System.Collections;

using System;//Action

public    enum PacmanState        { Wander, Chase, Flee, Return};

public class StateMachinePacman : MonoBehaviour {
              
//STATEMACHINE
    [HideInInspector]    public    PacmanState state = PacmanState.Wander;
  
    public    PacmanState stateSetter{
        get {    return state;    }
        set {
            ExitState();
            if(state != value){ if(stateChanged != null){ stateChanged(value); }}
            state = value;
            EnterState(state);
        }
    }

    private    IEnumerator curCoroutine;

    private    void ExitState(){
        if(curCoroutine != null){    StopCoroutine(curCoroutine);    }
  
        //stuff that needs to be done before changing state
        switch(state){
            case PacmanState.Wander:        break;
            case PacmanState.Chase:            break;
            case PacmanState.Flee:            break;
            case PacmanState.Return:        break;  
        }
        print ("exiting:\t"+System.Enum.GetName(typeof(PacmanState), state)+"\t at"+Time.time);  
    }

    private    void EnterState(PacmanState newState){
        print ("entering:\t"+System.Enum.GetName(typeof(PacmanState), state)+"\t at"+Time.time);
        switch(newState){
            case PacmanState.Wander:    curCoroutine    = PerformWander();    StartCoroutine( curCoroutine );        break;
            case PacmanState.Chase:        curCoroutine    = PerformChase();    StartCoroutine( curCoroutine );        break;
            case PacmanState.Flee:        curCoroutine    = PerformFlee();    StartCoroutine( curCoroutine );        break;
            case PacmanState.Return:    curCoroutine    = PerformReturn();    StartCoroutine( curCoroutine );        break;
        }
    }
//

//NOTIFIERS
    public    event Action<PacmanState> stateChanged;
    public    event Action died;
//
      
    void Start(){  
        stateSetter = PacmanState.Wander;
    }


    #region PerformActions
    private    IEnumerator PerformWander(){          
        while(true){
            //DO STUFF
                  
            //normally we would yield at the end, but we use keyinput here, so we have to yield before checking keys, otherwise the input would be still be valid for new state
            yield return null;
            //state transition event simulated by keyInput:
            if(Input.GetKeyDown("1")){    print("X");        }
            if(Input.GetKeyDown("2")){    print("->chase");    stateSetter = PacmanState.Chase;    }  
            if(Input.GetKeyDown("3")){    print("->flee");    stateSetter = PacmanState.Flee;        }  
            if(Input.GetKeyDown("4")){    print("X");        }      
        }
    }
    private    IEnumerator PerformChase(){          
        while(true){
            //DO STUFF
      
            yield return null;
            if(Input.GetKeyDown("1")){    print("->wander");    stateSetter = PacmanState.Wander;    }
            if(Input.GetKeyDown("2")){    print("X");        }  
            if(Input.GetKeyDown("3")){    print("->flee");    stateSetter = PacmanState.Flee;        }  
            if(Input.GetKeyDown("4")){    print("-X");    }
        }
    }
    private    IEnumerator PerformFlee(){          
        while(true){
            //DO STUFF
      
            yield return null;
            if(Input.GetKeyDown("1")){    print("->wander");    stateSetter = PacmanState.Wander;    }
            if(Input.GetKeyDown("2")){    print("->chase");    stateSetter = PacmanState.Chase;    }  
            if(Input.GetKeyDown("3")){    print("X");        }  
            if(Input.GetKeyDown("4")){    print("->return");    stateSetter = PacmanState.Return;    }
        }
    }
    private    IEnumerator PerformReturn(){          
        while(true){
            //DO STUFF
      
            yield return null;
            if(Input.GetKeyDown("1")){    print("->wander");    stateSetter = PacmanState.Wander;    }
            if(Input.GetKeyDown("2")){    print("X");        }  
            if(Input.GetKeyDown("3")){    print("X");        }  
            if(Input.GetKeyDown("4")){    print("X");        }
        }
    }

    #endregion
}

Adding Queues:
After your remarks i am afraid my contraption it is not really an FSM. It controls a subject in the game and has states and commands:

  • States are Coroutines that the subject currently performs, so i named them with the prefix “Perform” like “private IEnumerator PerformJump”.
  • Commands are ways for ordering the subject to do smth, but letting the Subject check for itself if it is possible. They return a bool and are named with the prefix “Try” like “public bool TryJump”

You can control my StateMachine by:

  • setting the variable “stateSetter” to any MachineState (this is an enum) OR
  • using one of the try-functions like TryMelee to command the subject to perform a MeleeAttack. These try-functions determine if the action of state-switching will be enqueued, performed immediately or is forbidden (which returns false)

This way i can make a normal state-machine where every transition is programmed right into each state. Or i can manually ask for a state change from the outside by using the try-functions which contain more general rules to check if the subject is currently allowed to enter a state.

This is the barebone version of my current script, stripped of all special functionality used for my Coded Abilities that require other stuff from my project like Melee, Spells and MovementForces and { Jump, Focus, Melee, Aim, Shoot, Stagger, Stun, Dead} and grounded-checks. Also changed some things like the Hit function which normally takes an attack instance instead of raw dmg

StateMachine.cs

using UnityEngine;
using System.Collections;
using System.Collections.Generic;//List

using System;//Action
//controls actor by inputs like PerformAttack or move and sets the corresponding animations, must have no link to the Game-UI, status indication is on-object


//Add Statechange Events for player character that are catched by Main-UI (OnFocus...)

public    enum MachineState        { None, Idle, Jump, Dash, Block, Cast, Melee, Stagger, Dead};

//Actor Controller, driven by Input through TryXXXX() functions and steerVector
//Inconsistency: is that Input is gathered in Update, and Timers run in Fixed Update, so first executionFrame is done in Update, following in Fixed
//    - this could be changed by adding a WaitForFixedUpdate() before starting execution but that adds at least one render frame delay until input can be seen

public class StateMachine : MonoBehaviour {
              
//STATEMACHINE
    [HideInInspector]    public    MachineState state = MachineState.None;
  
    public    MachineState stateSetter{
        get {    return state;    }
        set {
            ExitState();
            if(state != value){ if(stateChanged != null){ stateChanged(value); }}
            state = value;
            EnterState(state);
        }
    }

    private    IEnumerator curCoroutine;

    private    void ExitState(){
        if(curCoroutine != null){    StopCoroutine(curCoroutine);    }
  
        //stuff that needs to be done before changing state
        switch(state){
            case MachineState.None:        break;
            case MachineState.Idle:        break;
            case MachineState.Jump:        break;
            case MachineState.Dash:        break;
            case MachineState.Block:    break;
            case MachineState.Cast:        break;
            case MachineState.Melee:    break;
            case MachineState.Stagger:    break;
            case MachineState.Dead:        break;      
        }
        print ("exiting:\t"+System.Enum.GetName(typeof(MachineState), state)+"\t at"+Time.time);  
    }

    private    void EnterState(MachineState newState){
        print ("entering:\t"+System.Enum.GetName(typeof(MachineState), state)+"\t at"+Time.time);
        switch(newState){
            case MachineState.None:        break;
            case MachineState.Idle:        curCoroutine    = PerformIdle();    StartCoroutine( curCoroutine );    break;
            case MachineState.Jump:        break;
            case MachineState.Dash:        break;
            case MachineState.Block:    break;
            case MachineState.Cast:        break;
            case MachineState.Melee:    break;
            case MachineState.Stagger:    actionQueue.Clear();    break;
            case MachineState.Dead:        actionQueue.Clear();    break;
        }  
    }
//

//NOTIFIERS
    public    event Action<MachineState> stateChanged;
    public    event Action died;
//

//ACTION QUEUE
    //Rules:
    //    Dash and Block interrupt all Actions even Dash itself
    //    Melee, Casts should have a shared timeout, preventing high speed Melee->Dash->Melee->Dash... attack speeds caused by mutually interruptable states

    private    int    maxQueueSize = 1;    //increase due to preference, for user input controlled machine keep the queue size 1 and always overwrite the contained item with the last user input if the stack is not empty
    private    Queue<QueueAbleAction> actionQueue = new Queue<QueueAbleAction>();    //Action Queue
  
    public    class QueueAbleAction{
        public    MachineState    type;
        public    Action                action;
        public    QueueAbleAction(MachineState type, Action action){
            this.type = type;
            this.action = action;
        }
    }
//

//    void Awake(){
//    }
  
    void Start(){
  
        stateSetter = MachineState.Idle;
  
//        TickManager.instance.lateUpdateTick1    += new System.Action(LateTick1);
//        TickManager.instance.lateUpdateTick2    += new System.Action(LateTick2);
    }

    void OnDestroy(){
//        TickManager.instance.lateUpdateTick1    -= new System.Action(LateTick1);
//        TickManager.instance.lateUpdateTick2    -= new System.Action(LateTick2);
    }

    void Update(){          
        if(Input.GetKeyDown("1")){    print("Trying Dash");    TryDash(Vector3.forward);    }  
        if(Input.GetKeyDown("2")){    print("Trying Block");    TryBlock(Vector3.forward);    }  
        if(Input.GetKeyDown("3")){    print("Trying Melee");    TryMelee(Vector3.forward);    }  
        if(Input.GetKeyDown("4")){    print("Dealing 1DMG");    Hit(1,Vector3.forward);        }
    }

//    void LateUpdate(){  
//    }
//    void LateTick1(){  
//    }
//    void LateTick2(){  
//    }

    private    int hp = 3;

    //this gets called when subject is hit
    public    bool Hit( int dmg, Vector3 dir, Vector3? sourceDir = null){
        if(state != MachineState.Dead){
      
            if(state != MachineState.Block){
          
                hp -= dmg;
          
                if(hp < 1){
                    stateSetter        = MachineState.Dead;
                    if(died!=null){died();}                          
                    return true;
                }else{              
                    stateSetter        = MachineState.Stagger;
                    return false;
                }
          
            }
      
        }
  
        return false;
    }

    private    IEnumerator PerformIdle(){
        while(true){      
            //check for Actions in Queue
            if(actionQueue.Count>0){          
                MachineState type = actionQueue.Peek().type;
          
                //you can perform special checks like time delay between melee attacks or such things before dequeuing here -> if( type == MachineState.Melee && meleeCastTimeOutRest != 0)...
                          
                print ("dequeued "+System.Enum.GetName(typeof(MachineState), type)+" from Idle() at "+Time.time);          
                actionQueue.Dequeue().action.Invoke();      
            }      
            yield return new WaitForFixedUpdate();
        }
    }
  
    #region TryActions
          
//DASH
    public    bool TryDash(Vector3 dir){
          
        if( state == MachineState.Dead){    return false;    }
  
        bool interrupt = true;    //Dash interrupts all actions
  
        if(interrupt){    actionQueue.Clear();    }
          
        actionQueue.Enqueue(
            new QueueAbleAction( MachineState.Dash,
                ()=>{
                    stateSetter        = MachineState.Dash;
                    curCoroutine    = PerformDash(dir);
                    StartCoroutine( curCoroutine );
                }
            )
        );  
  
        //interrupt to Idle, Idle() checks for Dequeue, so this happens this very frame, not next frame
        if(interrupt){        stateSetter        = MachineState.Idle;}
      
        return false;
    }

//BLOCK
    public    bool TryBlock(Vector3 dir){
        if( state == MachineState.Dead)            {    return false;    }
          
        bool interrupt = true;    //Block interrupts all actions  
  
        if(interrupt){    actionQueue.Clear();    }
          
        actionQueue.Enqueue(
            new QueueAbleAction( MachineState.Block,  
                ()=>{
                    stateSetter        = MachineState.Block;    //stops curCoroutine via set function
                    curCoroutine    = PerformBlock(dir);
                    StartCoroutine( curCoroutine );
                }
            )
        );  
          
        if(interrupt){        stateSetter        = MachineState.Idle;}
          
        return true;
    }

  
//MELEE
    public    bool TryMelee(Vector3 dir){
  
        if( state == MachineState.Dead)            {    return false;    }
  
        bool interrupt = false;
          
        interrupt = state == MachineState.Dash || state == MachineState.Block;//can interrupt Block and Dash, otherwise queues
        if(interrupt){        actionQueue.Clear();    }
  
        if(actionQueue.Count == maxQueueSize){    return false;    }
  
        actionQueue.Enqueue(
            new QueueAbleAction( MachineState.Melee,  
                ()=>{
                    stateSetter        = MachineState.Melee;    //stops curCoroutine via set function
                    curCoroutine    = PerformMelee(dir);
                    StartCoroutine( curCoroutine );
                }
            )
        );
          
        //interrupt PerformChargeAction
        if(interrupt){        stateSetter        = MachineState.Idle;}
          
        return true;
    }


//CAST
    public    bool TryCast(Vector3 dir){
  
        if( state == MachineState.Dead )        {    return false;    }
  
        bool interrupt = state == MachineState.Dash || state == MachineState.Block;//can interrupt Block and Dash
  
        if(interrupt){    actionQueue.Clear();    }  
  
        if(actionQueue.Count == maxQueueSize){    return false;    }
  
        actionQueue.Enqueue(
            new QueueAbleAction( MachineState.Cast,  
                ()=>{
                    stateSetter        = MachineState.Cast;    //stops curCoroutine via set function
                    curCoroutine    = PerformCast(dir/*, attack*/);
                    StartCoroutine( curCoroutine );
                }
            )
        );
  
        //interrupt PerformChargeAction
        if(interrupt){        stateSetter        = MachineState.Idle;}
  
        return true;
    }

    #endregion

    #region PerformActions
  
    //ACTUAL JUMP (currently no manual jump, its to unhandy for touch)
    private    IEnumerator PerformJump(Vector3 dir){
          
        //Do a Jump e.g:
            //this.GetComponent<RigidBody>().AddForce(Vector3.up * intensity, ForceMode.Impulse);  
            //while(!grounded){        yield return new WaitForFixedUpdate();        }
        yield return new WaitForFixedUpdate();
        //at the end transition to Idle;
        stateSetter = MachineState.Idle;
  
  
    }
  
//BLOCK
    private    IEnumerator PerformBlock(Vector3 dir){
  
        //Do a block for certain duration, rotate a shieldObject or whatever  
        for(int i = 0; i<25; i++){    //0.5s
            //shieldTrans.Rotate(new Vector3(-900F*Time.deltaTime,0F,0F));
            yield return new WaitForFixedUpdate();
        }          
        stateSetter        = MachineState.Idle;
    }
  
//DASH
    private    IEnumerator PerformDash(Vector3 dir){
        int dur = 50;
        //Dash
        for(int i = 0; i<dur; i++){      
            //steerVector = dir.normalized *36F;                  
            yield return new WaitForFixedUpdate();
      
        }
  
        stateSetter        = MachineState.Idle;
    }

//MELEE
    private    IEnumerator PerformMelee(Vector3 dir/*, Attack attack*/){
  
            //Do Attack
  
        //    attack.Invoke(dir);  
            int dur = 50;//attack.duration;  
            for(int i = 0; i<dur; i++){          
                yield return new WaitForFixedUpdate();
            }
  
        stateSetter = MachineState.Idle;
    }
      
    //SPELL
    private    IEnumerator PerformCast(Vector3 dir/*, Attack attack*/){
  
            //Do Attack
  
        //    attack.Invoke(dir);  
            int dur = 50;//attack.duration;  
            for(int i = 0; i<dur; i++){          
                yield return new WaitForFixedUpdate();
            }
  
        stateSetter = MachineState.Idle;
    }
      
    #endregion
}

i hope it is understandable, since i really tinkered a lot after the queue addition, i run it in FixedUpdate() but i am aware that user Inputs arrive in Update() meaning that the first iteration of any perform loop will occur in Update, but then continue in FixedUpdate(). I could add a WaitForFixedUpdate() at the begin of every Perform, but this would increase the input-lag by up to a frameLength

Here is a small taste of what i am actually doing with it:
2671836--188561--aaaaaagif.gif
and my actual file consisting out of 2k5 lines of tinker mayhem

2671836–188562–ActorController.cs (80.2 KB)

1 Like