Starting/Stopping Coroutines

Anyone else feel that stopping coroutines is barbaric in practice?

Why can’t coroutines be targeted to be stopped without referencing to them as a string name? To avoid using a string to stop a coroutine, a method must be called to stop all active coroutines on a monobehaviour called StopAllCoroutines().

Here is what I want:

// this is not possible but would be ideal!
StopCoroutine(myIEnumerator);

Here is what I have to do:

// this is bad because its hardcoded, and if the method name changes, this completely unassociated string must change too!
StopCoroutine("myIEnumerator");

or

// this is the only workaround for not using a string to reference an ienumerator method, but is arguably overkill.
StopAllCoroutines();

So while I was at work, I made this meme to display how it feels when calling StopAllCoroutines just because coroutines cannot be stopped without a string.

// WHY?
StartCoroutine("thisIsMyMethodNameInAStringForSomeReason");
StopCoroutine("thisIsMyMethodNameInAStringForSomeReason");

1553890--91306--$47167538.jpg

I’d be pretty happy if this practice was made smarter. But I mostly just wanted to share this image. Until then I will feel barbarically inclined to use strings.

HAPPY CODING!

I hate coroutines.

After a couple of projects where days were spent debugging dozens of coroutines all running simultaneously and fighting for control over pieces of data… I’ve banned myself from using them.

Almost everything else can be handled by C# events, or can have functionality separated out into another class.

EDIT: Okay maybe that’s a little extreme. I still use them for some simple fire-and-forget stuff. Just never for any “heavy-lifting”.

maybe use this free solution to keep a handle to a coroutine and stop it with that.

This is what I use, I wrote my own Coroutine system, I called it RadicalCoroutine (I once read someone refer to custom coroutines as radicalcoroutines, I liked the name… anyone who feels I ripped off the name, this code isn’t intended to be distributed to the public, just sharing my internal code). I wanted a few things from my coroutines.

I wanted them to be cancellable.
I wanted them to be pausable.
I wanted to be able to define my own yield statements.
I wanted them to operate on the same backbone that coroutines do (threads not an option).
I wanted them to allow nesting of coroutines (just like in coroutines, if you yield an enumerable, it enumerates it before continueing)
I wanted them to operate directly on the object that you operated them (no need for special monobehaviour, or global gameobject)

This is the result:

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

namespace com.spacepuppy
{
    public class RadicalCoroutine : IRadicalYieldInstruction, System.Collections.IEnumerator
    {

        #region Fields

        private Coroutine _coroutine;

        private System.Collections.IEnumerator _routine;
        private object _current;
        private DerivativeOperation _derivative;

        private bool _cancelled = false;
        private bool _paused = false;

        #endregion

        #region CONSTRUCTOR

        private RadicalCoroutine(System.Collections.IEnumerator routine)
        {
            _routine = routine;
        }

        private void SetOwner(Coroutine co)
        {
            _coroutine = co;
        }

        #endregion

        #region Properties

        /// <summary>
        /// A refernence to the routine that is operating this RadicalCoroutine. 
        /// This can be used for yielding this RadicalCoroutine in the midst of 
        /// a standard coroutine.
        /// </summary>
        public Coroutine Coroutine { get { return _coroutine; } }

        public bool Cancelled { get { return _cancelled; } }

        public bool Paused
        {
            get { return _paused; }
            set { _paused = value; }
        }

        #endregion

        #region Methods

        public void Cancel()
        {
            _cancelled = true;
        }

        public void Reset()
        {
            _current = null;
            _derivative = null;
            _cancelled = false;
            _routine.Reset();
        }

        private bool MoveNext()
        {
            _current = null;

            if (_paused) return true;

            if (_cancelled)
            {
                _derivative = null;
                return false;
            }

            int derivativeResult = -1;

            derivativeResult = this.OperateDerivative();
            if (derivativeResult >= 0)
            {
                return (derivativeResult > 0);
            }

            while (_routine.MoveNext())
            {
                var obj = _routine.Current;

                if (obj is IRadicalYieldInstruction)
                {
                    _derivative = new DerivativeOperation(obj as IRadicalYieldInstruction);
                    derivativeResult = this.OperateDerivative();
                    if (derivativeResult >= 0)
                    {
                        return (derivativeResult > 0);
                    }
                }
                else if (obj is System.Collections.IEnumerable)
                {
                    _derivative = new DerivativeOperation(new RadicalCoroutine((obj as System.Collections.IEnumerable).GetEnumerator()));
                    derivativeResult = this.OperateDerivative();
                    if (derivativeResult >= 0)
                    {
                        return (derivativeResult > 0);
                    }
                }
                else if (obj is System.Collections.IEnumerator)
                {
                    _derivative = new DerivativeOperation(new RadicalCoroutine(obj as System.Collections.IEnumerator));
                    derivativeResult = this.OperateDerivative();
                    if (derivativeResult >= 0)
                    {
                        return (derivativeResult > 0);
                    }
                }
                else
                {
                    _current = obj;
                    return true;
                }
            }

            _current = null;
            _derivative = null;
            return false;
        }

        /// <summary>
        /// 1 - continue blocking
        /// 0 - stop blocking
        /// -1 - loop past
        /// </summary>
        /// <returns></returns>
        private int OperateDerivative()
        {
            if (_derivative == null) return -1;

            if (_derivative.ContinueBlocking(this))
            {
                if (_cancelled)
                {
                    _current = null;
                    return 0;
                }
                _current = _derivative.CurrentYieldObject;
                return 1;
            }
            else
            {
                _derivative = null;
                if (_cancelled)
                {
                    _current = null;
                    return 0;
                }
                _current = null;
                return -1;
            }
        }

        #endregion

        #region IRadicalYieldInstruction Interface

        object IRadicalYieldInstruction.CurrentYieldObject
        {
            get { return _current; }
        }

        bool IRadicalYieldInstruction.ContinueBlocking(RadicalCoroutine routine)
        {
            return this.MoveNext();
        }

        #endregion

        #region IEnumerator Interface

        object System.Collections.IEnumerator.Current
        {
            get { return _current; }
        }

        bool System.Collections.IEnumerator.MoveNext()
        {
            return this.MoveNext();
        }

        void System.Collections.IEnumerator.Reset()
        {
            this.Reset();
        }

        #endregion

        #region Factory Methods

        public static RadicalCoroutine StartCoroutine(MonoBehaviour behaviour, System.Collections.IEnumerator routine)
        {
            if (behaviour == null) throw new System.ArgumentNullException("behaviour");
            if (routine == null) throw new System.ArgumentNullException("routine");

            var co = new RadicalCoroutine(routine);
            co.SetOwner(behaviour.StartCoroutine(co));
            return co;
        }

        #endregion

        #region Special Types

        private class DerivativeOperation : IRadicalYieldInstruction
        {
            private IRadicalYieldInstruction _instruction;
            private DerivativeOperation _derivative;

            public DerivativeOperation(IRadicalYieldInstruction instruction)
            {
                if (instruction == null) throw new System.ArgumentNullException("instruction");
                _instruction = instruction;
            }

            public object CurrentYieldObject
            {
                get { return (_derivative != null) ? _derivative.CurrentYieldObject : _instruction.CurrentYieldObject; }
            }

            public bool ContinueBlocking(RadicalCoroutine routine)
            {
                if (_derivative != null)
                {
                    if (_derivative.ContinueBlocking(routine))
                    {
                        return true;
                    }
                    else
                    {
                        _derivative = null;
                    }
                }

                while (_instruction.ContinueBlocking(routine))
                {
                    if (_instruction.CurrentYieldObject is IRadicalYieldInstruction)
                    {
                        _derivative = new DerivativeOperation(_instruction.CurrentYieldObject as IRadicalYieldInstruction);
                        if (!_derivative.ContinueBlocking(routine))
                        {
                            _derivative = null;
                        }
                        else
                        {
                            return true;
                        }
                    }
                    else if (_instruction.CurrentYieldObject is System.Collections.IEnumerable)
                    {
                        _derivative = new DerivativeOperation(new RadicalCoroutine((_instruction.CurrentYieldObject as System.Collections.IEnumerable).GetEnumerator()));
                        if (!_derivative.ContinueBlocking(routine))
                        {
                            _derivative = null;
                        }
                        else
                        {
                            return true;
                        }
                    }
                    else if (_instruction.CurrentYieldObject is System.Collections.IEnumerator)
                    {
                        _derivative = new DerivativeOperation(new RadicalCoroutine(_instruction.CurrentYieldObject as System.Collections.IEnumerator));
                        if (!_derivative.ContinueBlocking(routine))
                        {
                            _derivative = null;
                        }
                        else
                        {
                            return true;
                        }
                    }
                    else
                    {
                        return true;
                    }
                }

                return false;
            }
        }

        #endregion

    }

}

And this is the interface of the IRadicalYieldInstruction:

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

namespace com.spacepuppy
{
    public interface IRadicalYieldInstruction
    {
        object CurrentYieldObject { get; }

        bool ContinueBlocking(RadicalCoroutine routine);
    }
}

And here’s some static extension methods for calling them directly off the object, so you can say something like:

//in a MonoBehaviour
var routine = this.StartRadicalCoroutine(this.SomeMethod());
routine.Cancel();//cancels the routine

//the static methods

        public static RadicalCoroutine StartRadicalCoroutine(this MonoBehaviour behaviour, System.Collections.IEnumerator e)
        {
            return RadicalCoroutine.StartCoroutine(behaviour, e);
        }

        public static RadicalCoroutine StartRadicalCoroutine(this MonoBehaviour behaviour, System.Collections.IEnumerable e)
        {
            return RadicalCoroutine.StartCoroutine(behaviour, e.GetEnumerator());
        }

        public static RadicalCoroutine StartRadicalCoroutine(this MonoBehaviour behaviour, System.Delegate method, params object[] args)
        {
			if (behaviour == null) throw new System.ArgumentNullException("behaviour");
            if (method == null) throw new System.ArgumentNullException("method");

            System.Collections.IEnumerator e;
            if (com.spacepuppy.Utils.ObjUtil.IsType(method.Method.ReturnType, typeof(System.Collections.IEnumerable)))
            {
                e = (method.DynamicInvoke(args) as System.Collections.IEnumerable).GetEnumerator();
            }
            else if (com.spacepuppy.Utils.ObjUtil.IsType(method.Method.ReturnType, typeof(System.Collections.IEnumerator)))
            {
                e = (method.DynamicInvoke(args) as System.Collections.IEnumerator);
            }
            else
            {
                throw new System.ArgumentException("Delegate must have a return type of IEnumerable or IEnumerator.", "method");
            }

            return RadicalCoroutine.StartCoroutine(behaviour, e);
        }