Background tasks and Coroutines

Next up in the fabulous journey of scripting, we will cover the treacherous realm of background tasks. We use the background tasks to start something (in the background) so that it is runs independently of the normal game update and draw cycle.

Note

Coroutines, by default, do not run the same thread as the normal game loop. If you are not careful, they can stop your game from running. (You can dispatch them on to separate threads in Unity Pro to offset the work in order to improve the performance.)

For more information on Coroutines and the default execution order of methods, refer to the article in the Unity docs at https://docs.unity3d.com/Documentation/Manual/ExecutionOrder.html.

The following diagram shows that we can have a second process that runs alongside our main game:

Background tasks and Coroutines

This is usually used for systems that are continually running and not for the main events on the screen, such as AI, a background trading system, or even a continual web-service-gathering data for the game.

Unity also has the ability to synchronize these background threads with a simple function that pauses the operation (or returns the control back to Unity). This is until the next frame of the game is drawn (WaitForEndOfFrame or WaitForFixedUpdate), which gives you a pattern like the following screenshot:

Background tasks and Coroutines

The benefit of this is that you can wait for the last update or draw a cycle to finish before running your process. You might do this if you want to render what is drawn on the screen to an image and either save it to a disk or upload it to a web service or website.

The Unity documents provide a good example of using this behavior; you can find them at https://docs.unity3d.com/Documentation/ScriptReference/WaitForEndOfFrame.html.

Enter Coroutines

The proper way to implement long-running tasks in Unity is through the use of a feature called Coroutines. In simple words, Coroutines are Unity's way of launching code in the background, but they do have a few caveats and features around them though.

Note

Coroutines, by default, run on the same thread as the normal game loop and use the same resources as the game loop (albeit at the same time). To enable threading (running processes on separate processors or pipelines, distributing the workload), you will need Unity Pro.

IEnumerator

At their core, Coroutines are just normal methods, but they are implemented using a particular generic interface named IEnumerator as their return type. This enables Unity to track the method's state through several iterations (runs).

Tip

Don't confuse IEnumerator with IEnumerable when defining your Coroutines; otherwise, you will find that they won't work.

To create a basic Coroutine, you simply need to set up the method as shown in the following code:

IEnumerator MyCoroutine()
{
  //Do something
  //Then return
  yield return null;

}

This would create a simple single-use Coroutine that would perform a single function, and when it's finished, it will die and go away.

A more common pattern is to have a loop of some kind within the function that will not finish until some condition is met; this is done by either using a while or for loop as follows:

IEnumerator MyCoroutine (){
  bool complete = false;
  while (!complete)
  {
    //Do some repetitive task
    //When done set complete to true

    //Then return control after each step
    yield return null;
  }
}

The preceding code will simply run in the background until the condition is met; for example, a timer that is counting down should stop when it reaches 0.

Yielding

The Coroutines and IEnumerator feature are perfectly valid, but C# added a new operator in Version 2 (Unity now supports V4) called the yield operator. The yield operator suspends the current method on the current instruction line until the operation is complete; however, it also allows the CPU to continue in between each result that is returned by the called method or the instruction. The following example will pause the loop for two seconds in between the iterations while retuning the control back to the process.

Here's an example; say we have a function to print 10 lines:

IEnumerator Print10Lines()
{
  for (int i = 0; i < 10; i++)
  {
    print("Line" + i.ToString());
    yield return new WaitForSeconds(2);
  }
}

When the preceding code runs, it will simply loop 10 times, and each time it will print out the line number. However, before continuing, it will wait for 2 seconds.

Note

Do not confuse IEnumerator with IEnumerable. Coroutines and the yield keyword only work in a method that returns an IEnumerator feature. This is an easy mistake that can leave you scratching your head for hours.

Starting Coroutines

There are actually two types of Coroutines (it is best to think of them in that way, even though they are actually the same thing): those that are just launched (fire and forget) and those that can be managed. The difference is just in the way they are called. The fire and forget Coroutine functions are simply called by using the following code:

StartCoroutine(MyCoroutine()); //or
StartCoroutine(MyCoroutine(MyParameter)); //to use parameters

In the preceding code, the MyCoroutine function is started using the delegate method. Once started, it will not finish until either the function ends or StopAllCoroutines() is called. Now, start the Coroutine using the following code:

StartCoroutine("MyCoroutine"); //or 
StartCoroutine("MyCoroutine", myParameter); //to use parameters

In the preceding code, you specify the name of your Coroutine function and the method's name using a string. This enables you to stop the Coroutine from running anytime (and from anywhere) using the following code:

StopCoroutine("MyCoroutine");

Note

Currently, there are some enhancements being made in Unity that will enable you to stop the Coroutines that are called using the method's name. It is not clear yet whether this will be in the 4.x or 5.x timescales. Keep watching!

The invocation path is something to be kept in mind. You might ask why not just use the second method all the time. The answer is simple. Unity has to use slower methods to discover the method it needs to track when you provide the Coroutine's name as a string; just passing the method's name is quicker and smoother. The best advice would be to use each type according to its strengths. Only use the string launch method when you need to manage a background task and use the method names when it is a short-lived function that is solely aimed at accomplishing a single task. For everything else, just weigh up the pros and cons of each approach as you implement it.

Coroutines can be powerful additions to the arsenal of your game's framework, but they need to be implemented wisely; too many additions to your game (obviously) will just grind to a halt. If you only ever use the fire and forget Coroutines, you won't be able to stop them without shutting down all the rest as well (including those you started by naming them as a string).

Closing the gap

So now that we understand how we call Coroutines, to make the Print10Lines method described earlier, we will call it as follows:

void Example1()
{
  StartCoroutine(Print10Lines());
  print("I started printing lines");
}

As explained, the preceding code will kick off the Print10Lines function and then continue forward while the routing to print the lines continues simultaneously. On the other hand, the following code will print 10 lines, and only after it is finished will it continue and notify you that printing has finished:

IEnumerator Example2()
{
  yield return StartCoroutine(Print10Lines());
  print("I have finished printing lines");
}

Tip

Any method that has a return type of IEnumerator has to be called using one of the StartCoroutine methods; just calling any method with IEnumerator on its own will do nothing. So, keep this in mind if you are wondering why something is not being called.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.189.186.109