Wednesday, January 21, 2015

Unity: Use StartCoroutine to perform long time processing

This post shows how to use StartCoroutine to perform long time processing. Below is a simple basic code for calling long processing function (which i named LongProcessingFunction()) in a C# script attached to game object in a scene.


void Update()
{
  int iterationCount1 = 10000000;
  int iterationCount2 = 10000;
  StartCoroutine(LongProcessingFunction(iterationCount1, iterationCount2));
}

private IEnumerator LongProcessingFunction(int count1, int count2)
{
  yield return new WaitForEndOfFrame();
  for(int i = 0; i < count1; ++i)
  {
     for(int j=0; j < count2; ++j)
  {
     Debug.Log("processing ... "+i+" and "+j);
  }
  }
}

If you run the above code, your game will probably hang or suspend for a while during which you cannot click and interact with anything in your game scene. This makes one wonder why the coroutine prevents the running of the game. The important point to note, though, is that calling StartCoroutine(LongProcessingFunction()) does not really start a new thread or a parallel task, as in .NET. By calling StartCoroutine on LongProcessingFunction(), as soon as the "yield return new WaitForEndOfFrame()" line in the LongProcessingFunction, the unity game engine resumes temporally jump out of the LongProcessingFunction() execution, and runs a few frames before resumes the execution of the LongProcessingFunction() from the point it left off earlier. The execution will continue until the next time the "yield return ..." is encountered again in the LongProcessingFunction.

What this means is the execution of LongProcessingFunction is on the same thread as the calling function Update(). Therefore, as long as "yield return ..." is not encountered, the execution will continue on the LongProcessingFunction() until it is completed, then unity game engine resume the execution of frames again. Unfortunately, this means that during the time in which LongProcessingFunction is processing, the graphics of the game becomes unresponsive, which is what causes the "hanging" of the game.

Below is the code I designed to get around this problem, not elegant, but it gets the job done for me.

private bool isInLongProcessing;
private int mLoop1;
private int mLoop2;

void Start()
{
  isInLongProcessing = false;
}

void Update()
{
  int iterationCount1 = 10000000;
  int iterationCount2 = 10000;
  if(isInLongProcessing)
  {
    //do something here during long processing time
    return;
  }
  else
  {
   if(Input.GetKey(KeyCode.A))
   {
  StartCoroutine(LongProcessingFunction(iterationCount1, iterationCount2));
   }
   else
   {
      //do something here during normal time
   }
  }
}

void OnGUI()
{
  if(isInLongProcessing)
  {
    string message = string.Format("Long Processing: {0} {1}", mLoop1, mLoop2);
    GUI.Label(new Rect(0, 0, Screen.width, 20), message);
  }
}

private IEnumerator LongProcessingFunction(int count1, int count2)
{
  yield return new WaitForEndOfFrame();
  isInLongProcessing = true;
  DateTime currentTimeTick = DateTime.Now;
  DateTime intervalTimeTick = currentTimeTick;
  for(mLoop1 = 0; mLoop1 < count1; ++mLoop1)
  {
     for(int mLoop2=0; mLoop2 < count2; ++mLoop2)
  {
  currentTimeTick = DateTime.Now;
  TimeSpan ts = currentTimeTick - intervalTimeTick;
  if(ts.TotalMilliseconds >= 50)
  {
    intervalTimeTick = currentTimeTick;
    yield return null;
  }
  }
  }
  isInLongProcessing = false;
}

My idea is very simple, the code in LongProcessingFunction uses "currentTimeTick" and "intervalTimeTick" to keep track the amount has been elapsed since the last time "yield return null" is called, when the elapsed time is around 50 milliseconds, the "yield return null" is called, which trigger the unity game engine to temporarily leaves the LongProcessingFunction and go to process one frame and then return to the point it left off in the LongProcessingFunction (The "yield return null" basically tolds the unity game engine to come back to LongProcessingFunction after one frame execution). In this way, the game graphics will have a frame rate of roughly 20 frames per seconds, which is bearable, since one wants to spend as much time in the LongProcessingFunction as possible, so that is can be completed asap. The three variables "isInLongProcessing", "mLoop1" and "mLoop2" can be used to display processing progress. and signal the Update() whether it is still doing long  processing (I uses A key press to trigger the long processing, just for demo purpose).

No comments:

Post a Comment