How to ''stack'' coroutines, and call each one till all are executed?

My coroutine Lerpfunction() lerps an image to a larger size. This lerp takes 10 seconds. If the Lerpfunction() is called again during that 10 seconds the coroutines override each other, and I get weird behavior. Is it possible to, sort of ‘‘stack’’ these calls and call each Lerpfunction() after another, till all calls are executed?

I tried to solve it with a whileloop, Booleans and a Queue but without success.

Any suggestions?

IEnumerator Lerpfunction ()
{
	//Lerp implementation
}

You can stack them, but you have to do it manually. There are several ways this could be done. One is to use a Queue<IEnumerator> and a boolean variable to determine if a coroutine is already running and then either just start a coroutine when none is running at the moment or instead of passing it to StartCoroutine you can store it in the queue. At the end of the coroutine you can check the queue and if it has items, just pop one and start that coroutine. While the overall setup is quite simple, it has some drawbacks. First of all if you want to queue several different coroutines, the starting of the queue coroutines need to be at the end of every coroutine you want to stack. Also starting / queue a coroutine always requires this if-else statement.

Another solution is to use a Queue in combination of a coordinator / director coroutine that is running all the time and that will actually start the coroutines at the right time(s).

Both approaches have one issue: You can not use StopCoroutine to terminate a running coroutine. You could clear the queue which would cancel all queued coroutines but the current one has to finish. If you need to be able to stop the coroutine at any time the first approach would allow this with a few modifications. If not the second one is the simplest one to use.

// Second case:

private Queue<IEnumerator> coroutineQueue = new Queue<IEnumerator> ();

void Start()
{
    StartCoroutine(CoroutineCoordinator());
}

IEnumerator CoroutineCoordinator()
{
    while (true)
    {
        while (coroutineQueue.Count >0)
            yield return StartCoroutine(coroutineQueue.Dequeue());
        yield return null;
    }
}

IEnumerator Lerpfunction ()
{
    //Lerp implementation
}

With this setup you can simply use

coroutineQueue.Enqueue(LerpFunction());

instead of

StartCoroutine(LerpFunction());

The nice thing is you can queue any kind of coroutine (as long as it is not stopped manually) without any special addition to the coroutine. The “CoroutineCoordinator” will pick up any coroutine that is queued and just start it if he is currently idling. If another coroutine is queued while the coordinator is already running a coroutine, nothing happens. When the first coroutine has finished it automatically starts the next one.


The first case i’ve mentioned would look like this:

// first case

IEnumerator runningCoroutine = null;
private Queue<IEnumerator> coroutineQueue = new Queue<IEnumerator> ();

IEnumerator Lerpfunction()
{
    //Lerp implementation
    runningCoroutine = null;
    if (coroutineQueue.Count > 0)
    {
        runningCoroutine = coroutineQueue.Dequeue();
        StartCoroutine(runningCoroutine);
    }
}

To start / queue a coroutine you would do this:

if (runningCoroutine == null)
{
    runningCoroutine = LerpFunction();
    StartCoroutine(runningCoroutine);
}
else
    coroutineQueue.Enqueue(LerpFunction());

To stop all running and queued coroutines you can now do:

coroutineQueue.Clear();
if (runningCoroutine != null)
    StopCoroutine(runningCoroutine);

Good day.

First, i expect you know about

yield return new WaitforSeconds(10);

To “hold” the execution of that method for X seconds.

then, You only need to create a bool variable to know if its running,

IEnumerator Lerpfunction ()
 {
yourbool = true;
//Do things
yield return new WaitForSeconds (10);
counter++;
yourbool=false;
 }

So now, you can check for yourbool variable to know if it is running. If not, Run it again.
You can also add a counter to know how many times you executed the function.

Bye!!

Not sure I understand what you are saying. I think you’re doing a health bar. If yes, try it:

    private IEnumerator enumerator;

     // I don't use toLerp and formLerp, but use directly fillAmount.
     // This will keep the fillAmount value softened continuously.
     private IEnumerator Lerp(float finalValue, fload lerpDuration)
            {
                float lerpSpeed = Mathf.Abs(myImage.fillAmount - finalValue) / lerpDuration;
                while (!Mathf.Approximately(myImage.fillAmount, finalValue))
                {
                    myImage.fillAmount = Mathf.MoveTowards(myImage.fillAmount, finalValue, fadeSpeed * Time.deltaTime);
                    yield return null;
                }
                enumerator =null;
            }

Use this to call:

    if(enumerator !=null)
       StopCoroutine(enumerator );
    enumerator = Lerp(finalAlpha,lerpDuration);
    StartCoroutine(enumerator);

Cant you use aync methods? I think they can stacked but i can not tested it right now sorry.

@Bunny83, Thank you for your great solution.

Just for future reference for anyone who is referring to this, I would like to suggest a small modification to your second solution.

Instead of having the CoroutineCoordinator running all the time, we can stop it when the queue is empty, then restart it whenever it is needed. This combines the best of both solutions you provided.

I created a simple class that you can have an instance of in your scripts:

public class QueueManager : MonoBehaviour
{
	private readonly Queue<IEnumerator> _coroutineQueue = new Queue<IEnumerator>();

	public void AddToQueue(IEnumerator coroutine)
	{
		_coroutineQueue.Enqueue(coroutine);
		if (_coroutineQueue.Count ==1) //no previous items in queue
			StartCoroutine(CoroutineCoordinator());
	}

	private IEnumerator CoroutineCoordinator()
	{
		while (true)
		{
			while (_coroutineQueue.Count > 0)
				{
				yield return StartCoroutine(_coroutineQueue.Peek());
				_coroutineQueue.Dequeue();
			}
			if (_coroutineQueue.Count == 0)
				yield break;
		}
	}
}

Then you can call it in your other class:

private   QueueManager _animationQueue; 
    
void Start()
{
	 _animationQueue= gameObject.AddComponent<QueueManager>();
}    
// Then
_animationQueue.AddToQueue(LerpFunction())

I know that the change is very minor, but the continuous execution of the coroutine was bothering me, and the first solution didn’t provide the decoupled code like the second one.