Why doesn't a coroutine start executing from beginning when I start it again?

I’m working on a simulator projet that has many stages, the execution can be in one and only one stage at a time.
I created a coroutine for every stage and created a SwitchManager that switches between stages (coroutines) as needed.

my problem is described in detail as a comment in the right place in the code.

Here’s an example code:

StageInfo.cs

using UnityEngine;
using System.Collections;

public class StageInfo
{
    public bool status;
    public IEnumerator Functionality;
    
    public StageInfo(bool status, IEnumerator functionality)
    {
        this.status = status;
        this.Functionality = functionality;
    }
	
}

Main.cs

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

public class Main : Singelton<Main>
{
    // fields...
    public Machine machine;
    public AudioSource audioSource;
    public NewPlayer player;
    public checklistOne, checklistTwo, checklistThree;

    public enum Stage
    {
        Overview,
        ShowChecklistOne,
        TeleportToPositionOne,
        MoveCradle,
        OpenArms,
        RaiseArms,
        MoveSpool,
        LowerArms,
        TeleportToPositionFour,      
        None,
    }
    public SortedDictionary<Stage, StageInfo> stageBox; // the data structure that stores the stages with their status and their coroutines!

    private Stage currentStage;
    private IEnumerator currentRoutine;
    public Stage CurrentStage // it lunches the suitable coroutine when you assign a new state
    {
        get { return currentStage; }
        set
        {
            currentStage = value;
            if (currentRoutine != null)
                StopCoroutine(currentRoutine);
            currentRoutine = stageBox[currentStage].Functionality;
            StartCoroutine(currentRoutine);
        }
    }

    // methods & coroutines...
    private new void Awake()
    {
        // initializing stageBox:
        stageBox = new SortedDictionary<Stage, StageInfo>()
        {
            {Stage.Overview,                 new StageInfo(false, Overview()) },
            {Stage.ShowChecklistOne,         new StageInfo(false, ShowChecklistOne()) },
            {Stage.TeleportToPositionOne,    new StageInfo(false, TeleportToPositionOne()) },
            {Stage.MoveCradle,               new StageInfo(false, MoveCradle()) },
            {Stage.OpenArms,                 new StageInfo(false, OpenArms()) },
            {Stage.RaiseArms,                new StageInfo(false, RaiseArms()) },
            {Stage.MoveSpool,                new StageInfo(false, MoveSpool()) },
            {Stage.LowerArms,                new StageInfo(false, LowerArms()) },
            {Stage.TeleportToPositionFour,   new StageInfo(false, TeleportToPositionFour()) },
            {Stage.None, new StageInfo(false, null )},

        };
    }

    private void Start()
    {
        CurrentStage = Stage.Overview; /*************** ENTRY POINT ***************/
        // this is a property with Set statement that handles coroutine stopping and starting
    }


    // this method handle the Switch between the different coroutines by stoping one and starting another.

    private void ManageSwitch()
    {
        // Switch to the first Stage which status is false
        foreach (Stage s in stageBox.Keys)
        {
            if (stageBox~~.status == false)~~

{
print("Picked Stage: " + s);
CurrentStage = s;
return;
}
}
// if all stages are done go to Stage.None:
currentStage = Stage.None;
}

/************************************************************************
* *
* STAGES’ COROUTINES *
* *
************************************************************************/

private IEnumerator Overview()
{
print(“START Overview”);
Highlight.Instance.list.Clear();
yield return player.FadeIn();
stageBox[Stage.Overview].status = true;
print(“END Overview”);
ManageSwitch();
}

private IEnumerator ShowChecklistOne()
{
print(“START ShowChecklistOne”);
Highlight.Instance.list.Clear();
yield return checklistOne.CreateChecklist(new int[] { 0, 1, 2, 3, 4, 38, 39, 40 });
StartCoroutine(Environment.Instance.ShowMoteAfter(5, checklistOne.moteLocation)); // showing mote after 5 seconds if user didn’t interact
while (true) // waiting for user interaction to move checklist to destionation
{
if (Interaction.Instance.GetDown(Interaction.InputButton.Select))
{
yield return checklistOne.MoveToDestination();
stageBox[Stage.ShowChecklistOne].status = true;
yield return Environment.Instance.HideMoteAndStopShowAfterRoutine();
print(“END ShowChecklistOne”);
break;
}
else
yield return null;
}
ManageSwitch();
}

private IEnumerator TeleportToPositionOne()
{
print(“START TeleportToPisitionOne”);
Highlight.Instance.list.Clear();
yield return Environment.Instance.ShowFootPrint(Environment.Instance.footPrintLocations[1]);
StartCoroutine(Environment.Instance.ShowMoteAfter(5, Environment.Instance.moteLocations[1])); // showing mote after 5 seconds if user didn’t interact

while (true)
{
if (Interaction.Instance.GetDown(Interaction.InputButton.Select))
{
yield return player.TeleportTo(1);
stageBox[Stage.TeleportToPositionOne].status = true;
// hidiing Environment objects:
Environment.Instance.HideFootPrint();
yield return Environment.Instance.HideMoteAndStopShowAfterRoutine();
print(“END TeleportToPositionOne”);
break;
}
else
yield return null;
}
ManageSwitch();
}

private IEnumerator MoveCradle()
{
print(“START MoveCradle”);
machine.cradleHologram.SetActive(true); // showing hologram
Highlight.Instance.Unhighlight();
Highlight.Instance.list.Add(machine.cradle.gameObject); // highlighting mainConsole’s buttons & the cradle:
foreach (Button button in machine.mainConsole.buttonsArray)
Highlight.Instance.list.Add(button.gameObject);
Highlight.Instance.HighlightObjects();

while (true)
{
machine.mainConsole.ShowElementOnButton(machine.mainConsole.cradleRightButton, 0); // showing text
Interaction.Instance.EnableMainConsole(true); // Enabling interaction for MainConsole
if (machine.cradle.isInRightDestination) // success…
{
machine.cradleHologram.SetActive(false);
machine.mainConsole.RemoveElementAndMarker();
Highlight.Instance.Unhighlight();
checklistOne.CheckElement(0);
stageBox[Stage.MoveCradle].status = true;
print(“END MoveCradle”);
break;
}
else
yield return null;
}
ManageSwitch();
}

private IEnumerator OpenArms()
{
print(" START OpenArms");

while (true)
{
if (!CheckMoveCradle())
// the problem is when CheckMoveCradle() returns false…

/* CheckMoveCradle will turn stageBox[Stage.MoveCradle].status to
* false and return false.
* That breaks the while loop and call the ManageSwitch() which in
* turn will stop the current running coroutine and switch
* the execution to MoveCradle coroutine. HERE’S MY PROBLEM!!!
* (note: by this time, MoveCradle() coroutine is being executed
* for the second time, not first!).
* When StartCoroutine(MoveCradle()) is called from inside
* ManageSwitch(), it doesn’t start a new instance of the coroutine,
* or start executing it from beginning (as I understand it should do!!!),
* instead it just enter the coroutne at its last line, do nothing but
* return to the
* caller method (ManageSwitch()) even though there’s no Yield the the end.
* My Question is why doesn’t it start executing MoveCradle()
* coroutine from beginning?!

*/
break;

if (machine.armRig.armsDistance >= machine.armRig.warningDistance) // success…
{
machine.armRig.leftArmHologram.SetActive(false); // hiding holograms
machine.armRig.rightArmHologram.SetActive(false);
machine.mainConsole.RemoveElementAndMarker();
Highlight.Instance.Unhighlight();
checklistOne.CheckElement(1);
stageBox[Stage.OpenArms].status = true;
print(“END OpenArms”);
break;
}
else
yield return null;
}
ManageSwitch();
}

/************************************************************************
* *
* Check methods *
* *
************************************************************************/

private bool CheckMoveCradle()
{
if (machine.cradle.isInRightDestination)
{
stageBox[Stage.MoveCradle].status = true;
return true;
}
else
{
stageBox[Stage.MoveCradle].status = false;
checklistOne.UncheckElement(0);
return false;
}
}
}

Coroutines are not threads. Coroutines can only be interrupted (which includes “stopping”) at a yield statement.

So when a coroutine tries to “stop” itself, it will be stopped at the next yield statement because that’s the point where you actually yield the control back to the coroutine scheduler.

Also you need to be careful. When you start a coroutine, the StartCoroutine call will immediately execute the started coroutine up to the first yield before it actually returns. So a construct like this will cause a stackoverflow / crash:

IEnumerator R1()
{
    StartCoroutine(R2());
    yield return null;
}

IEnumerator R2()
{
    StartCoroutine(R1());
    yield return null;
}

However the following would work as StartCoroutine will return and finish the calling coroutine:

IEnumerator R1()
{
    yield return null;
    StartCoroutine(R2());
}

IEnumerator R2()
{
    yield return null;
    StartCoroutine(R1());
}

Though starting and stopping coroutines quickly is not a good idea as each new coroutine will create garbage. Coroutines are not “methods” but auto-generated classes and when you “start” one you actually create an instance of that class.

If you have trouble understanding how coroutines work, i suggest reading this blog article. Unfortunately the blog doesn’t exist anymore so i linked a cached version.