Day 17: Bringing It on Stage – Part 1

Stage is the graphical front-end of our system. It handles OpenGL ES life cycle, and maintains the game flow. Today we start building upon Stage.java of day 6. However, since the implementation is lengthy, we only cover animation handling and how it is incorporated into the stage. Graphical aspects of this class will be discussed next day.

Animation handling

Stage is an interface between our graphical data structures and the OpenGL ES API (which is called EGL). Scenes are added to it, and it has to render and animate them in a fail-safe manner.

In day 16 we added a method called advance to the Scene class, the purpose of which was to advance or increment animations on every sprite. The question we would like to answer today is when and how this method is called.

A naive implementation would call the advance method in onDrawFrame, where the renderer is called to render the scene. To see why this is a bad practice, we should consider some facts:

  1. Rendering is handled by OpenGL ES on a thread dedicated for this purpose. And it is best to keep it this way.
  2. Advancing animations can become a lengthy task as our scenes grow in complexity. When the frame is ready to render, it is already too late to start processing it. It will cause hiccups or stutter in the animation.
  3. OpenGL draws frames at the highest frequency it physically can. By default, rendering is performed whenever the OpenGL is ready to draw, and the rate it is called at it not deterministic (this rate is well known as FPS or frames per second).

The reason games have different FPS’s on different devices or different stages of the game-play is simply that some frames take longer than others to render and some machines are slower than others. So, by no means should we add more work to such a time-critical procedure.

The other possibility is to do so in the activity or within the stage class. Can we loop somewhere in the Stage class, say onSurfaceCreated, and keep calling advance?

The answer is a definite no! This is even a worse practice. The activity, and the stage as part of it, run on the main application thread (also called the UI thread). Blocking the OpenGL ES thread can delay rendering at worst; but if we block the UI thread, our whole application would be unresponsive. The UI thread is used by the Android platform to communicate with our app and pass input events and other signals. If it fails to do so because this thread is preoccupied, it pops the “wait/force close” option dialog, and we certainly don’t want that.

Creating the advance thread

The only option left is to create a third thread dedicated to animation. This thread would run in parallel with the other threads, leaving room for rendering and the UI thread. To do so, we define a thread class in Stage:

    private final class StageAdvanceTask extends Thread {
        private static final int FRAME_DURATION_MS = 20;
        private long mLastTime;

        private StageAdvanceTask() {
            mLastTime = System.nanoTime();
        }

        @Override
        public void run() {
            while(true) {
                final long time = System.nanoTime();
                final long diff = time - mLastTime;

                // TODO: Advance the scene with elapsedTime = diff

                mLastTime = time;
            }
        }
    }

Let’s explain the code above. First of all, notice that it is not a static class, and it is private. This makes it only visible to Stage, and it will have access to all non-static members of stage in run-time. This is not a thread-safe practice in nature, but with enough safety measures, it is doable.

The purpose of mLastTime is to keep track of the time passed since the last animation frame. All our animation methods in Sprite, AnimationHandler, and Scene take an argument called elapsedTime, which is used to know, for example, how much something should be moved in this frame if it has a certain speed.

The run method is the main part of the thread. On each iteration of the loop, it calculates the time difference between now and the previous frame, it advances the scene, and saves the new time.

We are now arriving at a viable solution, but not just yet. The problem with this code is that we are actively running commands on and on. A while(true) loop essentially occupies the execution thread. Thankfully this is not the UI thread, but still, it gets a large portion of the CPU time, essentially doing nothing. It is unnecessary to advance at 10000 frames/second or more. No scene can change so rapidly.

Why do we even care? The CPU always working is never good. If we were on a desktop machine, the CPU would soon become so hot that we would hear a loud noise from the fan. On a mobile device, keeping the CPU busy like this is a battery-eater. We don’t want our game to be at the top of the battery usage list, right below “Display”.

Saving CPU time

How do we keep CPU from constantly working, and yet loop for frame advance? There is a feature of the execution pipeline called sleep, or nap. We simply tell the thread to take a nap for a given amount of time, when it is not needed. Doing so will allow the CPU to take a rest from our thread, and use the extra time to process other threads and processes in the system, and possibly take some free time and cool down.

In Java, the corresponding method is Thread.sleep(). Adding this to our run method would make it look like this:

@Override
public void run() {
	while(true) {
		final long time = System.nanoTime();
		final long diff = time - mLastTime;

		// TODO: Advance the scene with elapsedTime = diff

		mLastTime = time;

		// Did advancing take less than 18 milliseconds?
		if(diff < (FRAME_DURATION_MS - 2) * 1000000) {
			try {
				Thread.sleep(FRAME_DURATION_MS - diff / 1000000);
			} catch (InterruptedException e) {
				// Do nothing
			}
		}
	}
}

What we do here is to wait a bit, if we finish in time. Artenus' animation frames are 20 ms apart. The decision of this period is completely yours to make. But 20 ms (20000000 ns) has proven to be enough for animating even complicated scenes. If we finish advancing in less than 18 ms, we have some time to spare before the next frame. So, we ask the thread to sleep until then. But if we are tight on time, we don't do that. The try/catch block is necessary when we call sleep, as it might be interrupted if the thread is killed for example, which needs to be handled.

Starting the thread

To use this thread, we should create an instance of it and call the start private Thread advanceThread;

The advance thread should start when the application starts, and whenever the application gets back focus after going to the background. To create the thread, we use the following routine:

private void scheduleTimer() {
	if(advanceThread == null) {
		advanceThread = new StageAdvanceTask();
		advanceThread.start();
	}
}

Then, call it in the Stage constructor (note that code snippet is omitted here). The other place it should be called is when the app gets back focus. This is handled in the onResume method.

@Override
public void onResume() {
	super.onResume();
	scheduleTimer();
}

Stopping the thread

We should kill the thread when the app goes to the background, as it is no longer needed. However, for technical reasons, it is not safe to manually terminate a thread from outside. What we do instead is to let the thread know that it should stop its execution. The easiest way is to simply set advanceThread to null. Since our thread has access to this member, it can stop once this happens.

@Override
public void onPause() {
	super.onPause();
	advanceThread = null;
}

Then we add a check to the run method to complete the process:

@Override
public void run() {
	while(advanceThread != null) {
		final long time = System.nanoTime();
		final long diff = time - mLastTime;
...

The thread will exit the loop, and as a result it will stop execution, once advanceThread is set to null.

Adding the scene

With a multi-threaded animation system in place, we should now add the scene to be animated and rendered. Add the following private member to the Stage class:

private Scene currentScene;

We also create the encapsulating methods so the current scene can be manipulated from outside:

public final void setScene(Scene scene) {
	currentScene = scene;
}

public final Scene getScene() { 
	return currentScene;
}

Now we can complete the run method of our advance thread by filling in the TODO part:

    if(currentScene != null)
        if(currentScene.isLoaded())
            currentScene.advance(diff / 1000000000.0f);

Note that elapsedTime should be provided in terms of seconds, and hence we have to divide diff by 1000000000.

Next steps

Now that we have the animation system in place, we need to draw the scene. Next day we will discuss how we should set up the stage for OpenGL ES and draw the scene on it. Below you can download the final code from today.