Terminal-like GUI, wait for input

Some time ago I asked a question about coroutines, while still not really understanding what they do.
Coroutine is being skipped. - Questions & Answers - Unity Discussions (Link to topic)

I finally think I got it now.

So what I tried to do last time was

IEnumerator waitForInput()
	{
		while(!inputChanged)
		{
			yield return null;
		}

		inputChanged = false;
	}

void mainMenu()
{
	StartCoroutine(waitForInput());

	if(input=="blabla")
	{
		dothis();
	}
}

As dumb as I were, I couldn’t understand that it wouldn’t actually halt my mainMenu function.

So I got that now, and changed my mainMenu to an Ienumerator, and put my while loop inside the mainMenu too, so it looked like this:

void start()
{
	StartCoroutine(mainMenu());
}

Ienumerator mainMenu()
{
	while(!inputChanged)
	{
		yield return null;
	}
	
	inputChanged = false;
	
	if(input=="blabla")
	{
		dothis();
	}
}

That works perfectly! Althought this limits me, and also goes against one of the best coding practices. “Don’t repeat yourself”.

If I do it like this, I’ll have to start a new coroutine for every program I make, and I’d have to repeat myself by checking for inputChanged, instead of having a single function that I could just call in all of my programs.

Let’s say I want to create “programs” from my main menu. For example

IEnumerator calculator()
	{
		while(!inputChanged)
		{
			yield return null;
		}

		inputChanged = false;

		if(input == "blabla")
		{

		}
	}

Then in my mainMenu I’ll do if input = blabla, StartCoroutine(calculator());

Wouldn’t it be bad to create a coroutine inside a coroutine?

Also, I’d probably want an exit command in my calculator program, which would return to my mainMenu.

So I’d just start a new coroutine running mainMenu.
(But what about the old mainMenu coroutine I had running? Am I not creating a duplicate coroutine when calling it on input == “exit”?)

Fx

>StartCoroutine(mainMenu());
	>StartCoroutine(calculator());
		>StartCoroutine(mainMenu());

If I keep doing stuff like this, wouldn’t it create an enormous amount of coroutines?

What could I do to create a terminal-like system, without getting myself into a unending coroutine mess?

Well, If you would call StartCoroutine(mainMenu()); and inside that you would call StartCoroutine(calculator()); and inside that you would call StartCoroutine(mainMenu()); again, then you would indeed cause an infinite loop, as no coroutine would ever finish (sinse they wait for a new coroutine to finish before they finish themselves, and that new croutine does the exact same thing thus never finishing at all. Only as long a you would yield those coroutines aswell though, otherwise the original coroutine continues and doesn’t wait for the new coroutine to finish and thus there is technically no infinite loop, just some odd design perhaps :p). This infinite loop is hard to detect though, as everything will probably run as expected up untill the point where something runs out of memory, which could take a long time before that happens :stuck_out_tongue:

However, if you call yield return StartCoroutine(calculator()); inside your mainmenu function and only handle calculator thing inside calculator this would be totally fine as this is simply a nested coroutine. As at some point the calculator method finishes and the mainMenu coroutine will continue executing after the yied return statement. So as long as you don’t create a infinite loop that would cause a StackOverFlowException in normal situations you are good to go…

So as to your part for not having to write the same code twice, you could still do this…

IEnumerator waitForInput()
{
    while(!inputChanged)
    {
        yield return null;
    }
    inputChanged = false;
}

And then call that like this:

Ienumerator mainMenu()
{
    yield return StartCoroutine(waitForInput());

    if(input=="blabla")
    {
        dothis();
    }
}

And then call that like you do now:

StartCoroutine(mainMenu());

Just some extra things to make it more clear:

  • You can yield StartCoroutine(waitForInput()); calling just yield waitForInput() does not work as you’d expect :wink:
  • you could put a while(true) loop (or while(!cancel) or something like that) inside your mainMenu coroutine to make it execute forever as long as: your application is running, the MonoBehaviour that called StartCoroutine still exists and StopAllCoroutines isn’t called. This will make it loop forever without freezing Unity. That way, once all nested coroutines you call inside your mainmenu method have been executed you would just start over again, accepting new input again. (As long as you yield something inside this infinite while loop, as otherwise Unity would obviously freeze on your infinite loop).
  • In this example, your calculator method could also call yield return StartCoroutine(waitForInput()); without problems and you could call yield return StartCoroutine(calculator()); from inside mainMenu just fine, as long as you then don’t call yield return StarCoroutine(mainMenu()); from any of those methods aswell.