StopCoroutine results in unreachable code?

Hey everyone!

Is it to be expected that “Start: counting end” will never be logged when pressing the space bar? (I do expect “counting end” to not be logged):

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CoroutineTest : MonoBehaviour {

IEnumerator Start () {
    counting = Counting();
    Debug.Log("Start: counting start");
    yield return StartCoroutine(counting);
    Debug.Log("Start: counting end");
}

void Update () {
    if (Input.GetKeyDown(KeyCode.Space)) {
        StopCoroutine(counting);
    }
}

int i = 0;
IEnumerator counting;
IEnumerator Counting () {
    Debug.Log("counting start");
    while (true) {
        i++;
        Debug.Log(i.ToString());
        yield return new WaitForSeconds(0.2f);
    }
    Debug.Log("counting end");
}


}

Best wishes,
Shu

I don’t think that’s valid, as there isn’t a single ‘yield’ instruction present in the coroutine.

You’re right, there was a “yield return null;” missing. I corrected this.
This would be a compiler error, though. I didn’t actually run this isolated script, which I will do now, to make sure.

EDIT:
I just updated the original post’s code to a full script that can run in the Editor and shows that some code cannot be executed.

Seems right - it would never get there? :slight_smile:
Stop doesn’t seem to be considered the ‘return’ from the coroutine…

1 Like

@methos5k
Seems like it. Quite confusing, though. But not a bug, is it? Maybe I report this and see what they say about it.

I doubt it’s a bug… Plus, now that you tested it, it won’t be so confusing next time. :slight_smile:

1 Like

It’s not a bug, just difficult to interpret.

See the execution order manual page:

It specifies that yielding coroutine “counting” from within coroutine “Start” will wait for counting to complete.

Now see the API reference on StopCoroutine:

It specifies that the given coroutine is stopped. Not that it is completed.

Since your counting coroutine can never complete, anything following the yield return call to it will never execute.

1 Like

@eisenpony
I see! Thanks for your answer! It does make sense, then!
In that case, using flags to check if the coroutine is running will probably be better:

bool someCoroutineIsRunning = false;
IEnumerator someCoroutine;
IEnumerator SomeCoroutine () {
    someCoroutineIsRunning = true;
    // stuff
    someCoroutineIsRunning = false;
}

void SomeCoroutine_Stop () {
    StopCoroutine(someCoroutine);
    someCoroutineIsRunning = false;
}

IEnumerator Start () {
    someCoroutine = SomeCoroutine();
    StartCoroutine(someCoroutine);
    while (someCoroutineIsRunning) {yield return null;}
    // the above two lines instead of: yield return StartCoroutine(someCoroutine);
    // code here should be executed
}

EDIT:
There may still be problems with that solution, though. For example, when you try stopping and starting the coroutine within the same frame:

bool someCoroutineIsRunning = false;
IEnumerator someCoroutine;
IEnumerator SomeCoroutine () {
    someCoroutineIsRunning = true;
    // stuff
    someCoroutineIsRunning = false;
}

void SomeCoroutine_Stop () {
    StopCoroutine(someCoroutine);
    someCoroutineIsRunning = false;
}

IEnumerator Start () {
    someCoroutine = SomeCoroutine();
    StartCoroutine(someCoroutine);
    while (someCoroutineIsRunning) {yield return null;}
    // the above two lines instead of: yield return StartCoroutine(someCoroutine);
    // code here should be executed

    someCoroutine = SomeCoroutine();
    StartCoroutine(someCoroutine);
    yield return new WaitForSeconds(10.0f);
    
    StartCoroutine(SomeCoroutine_Ended);
    SomeCoroutine_Stop();
    // stopping and starting within the same frame,
    // probably setting someCoroutineIsRunning = true again before SomeCoroutine_Ended checks it
    someCoroutine = SomeCoroutine();
    StartCoroutine(someCoroutine);

}

IEnumerator SomeCoroutine_Ended () {
    while (someCoroutineIsRunning) {yield return null;}
    // probably not executed ?
}

I’m not sure what you’re trying to accomplish. Why not just execute the code you want to happen when the coroutine ends directly from Start after your waiting while loop?

Actually, I think it’s better to use the flag to cause the coroutine to complete rather than testing and setting it from all over the place. the only disadvantage is that the operation might not cancel immediately, which I think is more sensible anyways as it gives the coroutine control over any cleanup tasks.

public class SomeSubroutine
{
  bool shouldContinue = true;
  public event Action<bool> Completed;
  IEnumerator Execute
  {
    while (shouldContinue && SomeOtherCondition())
    {
      // Some repeating code
      yield return null;
    }
    // Clean up code
  }

  public IEnumerator Start()
  {
    yield return StartCoroutine(Execute);
    OnComplete(!shouldContinue);
  }

  public void Cancel()
  {
    shouldContinue = false;
  }

  void OnComplete(bool wasCanceled)
  {
    var completed = Completed;
    if (completed != null)
      completed(wasCanceled);
  }
}
1 Like

I should add, a lot of this stuff has been thought about before. If you’re just here to learn, I’m happy to continue the discussion but if you prefer to ride on someone’s coat tails, I suggest @lordofduct 's radical coroutine’s. He provides quite a bit of infrastructural code for free.

1 Like

I’ve been cleaning up SPFramework and moving it to Unity 2017+.

Note some features in it can be consider superfluous these days. Like the custom yield instructions. When I first started writing it, CustomYieldInstruction did not exist yet.

2 Likes

@eisenpony
I don’t really want to use code that I don’t fully understand, because I would like to be able to expand and maintain it. And before reading and understanding a system that is already there, I tend to just develop my own system fitting best to the project’s needs.

The workaround that you suggested may work in some cases, but in most cases that I could think of, it’s not suitable due to not stopping the coroutine immediately. (This may highly depend on the use cases, though.) Also, when you try to stop a coroutine using the flag and start the same coroutine again within one frame, you end up with two coroutines running, since the first one didn’t stop, because the second one sets shouldContinue to true before it yields (which it should, because otherwise the repeating code wouldn’t run?).

I just wrote some code that may be better for illustrating what I am trying to do (didn’t test it, though):

int attackStrength = 1;
void Update () {
    if (Input.GetKeyDown(KeyCode.Space)) {
        if (!newAttackRunning) {NewAttack();}
    }
}

bool newAttackRunning = false;
void NewAttack () {
    newAttackRunning = true;

    // if (attack != null) {StopCoroutine(attack);}
    // < this doesn't work since it stops the coroutine before destroying the vfx

    // attackRunning = false;
    // yield return null;
    // < this sort of needlessly skips a frame

    Attack_Stop(); // this may work without sacrifices

    attack = Attack(attackStrength);
    StartCoroutine(attack);
    newAttackRunning = false;
}

void Attack_Stop () {
    StopCoroutine(attack);
    Destroy(cloned_attackVFX);
    // < placing cleanup code between stopping and starting
    // outside of Attack
}

public GameObject[] attackVFX;
GameObject cloned_attackVFX;
Transform cloned_attackVFXtransform;
public Transform attackVFXfollow; // its position is animated by other code
IEnumerator attack;
bool attackRunning = false;
IEnumerator Attack (int attackVFX_i) {
    attackRunning = true;
    cloned_attackVFX = Instantiate(
        attackVFX[attackVFX_i],
        attackVFXfollow.position,
        Quaternion.identity
    ) as GameObject;
    cloned_attackVFXtransform = cloned_attackVFX.GetComponent<Transform>();
    while (attackRunning) {
        cloned_attackVFXtransform.position = attackVFXfollow.position;
        yield return null;
    }
    Destroy(cloned_attackVFX); // not necessary here when using Attack_Stop
}

So if you want a stateful coroutine. You can wrap it like my RadicalCoroutine with a state value.

Now my RadicalCoroutine has tons of features that we could actually scale back to just this stateful thing.

Like this, a much more understandable base version:

using UnityEngine;
using System.Collections;

public class StatefulCoroutine : IEnumerator
{

    public enum OperatingState
    {
        Cancelled = -2,
        Cancelling = -1,
        Inactive = 0,
        Active = 1,
        Complete = 2
    }

    #region Fields

    private IEnumerator _routine;
    private Coroutine _token;
    private OperatingState _state;

    #endregion

    #region CONSTRUCTOR

    public StatefulCoroutine(IEnumerator routine)
    {
        if (routine == null) throw new System.ArgumentNullException("routine");
        _routine = routine;
    }

    #endregion

    #region Methods

    public Coroutine Token
    {
        get { return _token; }
    }

    public OperatingState State
    {
        get { return _state; }
    }

    #endregion

    #region Methods

    public void Start(MonoBehaviour handle)
    {
        _token = handle.StartCoroutine(_routine);
        _state = OperatingState.Active;
    }

    public void Cancel()
    {
        if(_state == OperatingState.Active)
        {
            _state = OperatingState.Cancelling;
        }
    }

    #endregion

    #region IEnumerator Interface

    object IEnumerator.Current
    {
        get { return _routine != null ? _routine.Current : null; }
    }

    bool IEnumerator.MoveNext()
    {
        switch(_state)
        {
            case OperatingState.Cancelled:
                return false;
            case OperatingState.Cancelling:
                _state = OperatingState.Cancelled;
                return false;
            case OperatingState.Inactive:
                return false;
            case OperatingState.Active:
                if(_routine.MoveNext())
                {
                    return true;
                }
                else
                {
                    _state = OperatingState.Complete;
                    return false;
                }
            default:
                return false;
        }
    }

    void IEnumerator.Reset()
    {
        _routine.Reset();
    }

    #endregion

}

/// <summary>
/// Some extension methods for StatefulCoroutine
/// </summary>
public static class CoroutineUtil
{

    public static StatefulCoroutine StartStatefulCoroutine(this MonoBehaviour handle, IEnumerator routine)
    {
        var r = new StatefulCoroutine(routine);
        r.Start(handle);
        return r;
    }

    public static StatefulCoroutine StartStatefulCoroutine(this MonoBehaviour handle, IEnumerable routine)
    {
        var r = new StatefulCoroutine(routine.GetEnumerator());
        r.Start(handle);
        return r;
    }

    public static StatefulCoroutine StartStatefulCoroutine(this MonoBehaviour handle, System.Func<IEnumerator> routine)
    {
        var r = new StatefulCoroutine(routine());
        r.Start(handle);
        return r;
    }

}

Then your example could be rewritten as:

#region Fields

    public int attackStrength = 1;
    public GameObject[] attackVFX;
    public Transform attackVFXfollow; // its position is animated by other code
  
    private StatefulCoroutine _attackRoutine;
    private GameObject _clone;

#endregion

#region Properties

    public bool AttackRunning
    {
        get { return _attackRoutine != null; }
    }

#endregion

#region Methods

    public void CancelAttack()
    {
        if(_attackRoutine != null)
        {
            _attackRoutine.Cancel();
            _attackRoutine = null;
        }
        if (_clone != null)
        {
            Destroy(_clone);
        }
    }

    void Update()
    {
        if (!this.AttackRunning && Input.GetKeyDown(KeyCode.Space))
        {
            _attackRoutine = this.StartStatefulCoroutine(this.AttackRoutine(attackStrength));
        }
    }
  
    private System.Collections.IEnumerator AttackRoutine(int attackVFX_i)
    {
        _clone = Instantiate(
            attackVFX[attackVFX_i],
            attackVFXfollow.position,
            Quaternion.identity
        ) as GameObject;
        var trans = _clone.GetComponent<Transform>();
        while (true)
        {
            _clone.position = attackVFXfollow.position;
            yield return null;
        }
        Destroy(_clone);
      
        _attackRoutine = null;
    }
  
#endregion

This way when you call cancel, the routine stops immediately. Since it gets flagged as ‘Cancelling’ and the logic of it does not progress the routine any further.

Of course if you cancel from within the routine, it’ll progress to the next yield statement. So always yield break after calling cancel on itself.

2 Likes

Thanks for your suggestion and the code samples! Very interesting! Seems quite reasonable!