Runtime adaptation

The number of Series 40 phones out there is huge, and even if you narrow them down using the full touch feature as the criteria, you still get several devices which vary in terms of performance. When developing games, it is recommended to try to offer a similar user experience on all supported devices. This means that in devices with lesser capabilities you may need to compromise, for example, by lowering the quality of graphics. Roughly there are two approaches to achieve this: compile-time or runtime adaptation.

Compile-time adaptation may be more efficient but also increases the maintenance burden. It is also less meaningful with Java application compared to, for example, C++ based games. Therefore, let's focus on runtime adaptation techniques instead.

Runtime adaptation means figuring out the device and platform properties when the application is running and doing actions that affect the performance based on the properties. We might figure out details about the device, such as the model or specifications of some hardware component, but there are a few arguments against using that information. If we base our performance adaptation for specific models we can be sure that the adaptation is optimal, but it is not future proof - we cannot guess the future models that we want our game to run on. The hardware specs on the other-hand do not tell the whole story, that is, how it all works together.

However, there is a trick that is both fairly accurate and future proof: measure of the performance that is evaluated runtime. For instance, frames per second (FPS) is a measure that is not too difficult to calculate:

  1. Check the current time.

  2. Render all the graphics of a single frame.

  3. Check the current time and calculate the time step 2 took.

  4. Repeat a few times until you get a decent average.

In addition to the adaptive design, there are several places where you can and should optimise, like for instance the usual performance bottlenecks: usage of floating points, reducing the number of method calls, heavy algorithms etc. Some of the places where optimization is worthwhile are obvious, some may surprise you, so it is good to partially use trial-and-error method. When developing aMaze it turned out that the rendering is still causing the biggest performance hit, but it was surprising to find out that drawing just the background caused 30-50% drop in FPS. Thus, it is removed automatically if the FPS measure is below 25.

Another thing to focus on is to use time for measure when updating the game model. In aMaze this means updating the marble velocity and position every t milliseconds where t is a constant. The aim of this approach is, of course, that the performance of the device itself does not change the behavior of the marble. Achieving this is not always possible, for instance if the rendering takes more time than t.

For more information on the subject see: Adaptive design and development and Performance optimisation.

Using runtime adaptation for performance optimization

Here's how it is done in aMaze (MazeCanvas.java):

if (_fpsCount < MIN_FPS_STATS_COUNT) {
    _drawingTime = System.currentTimeMillis();
}

// Draw the graphics
draw3D(_graphics);
draw2D(_graphics);

if (_fpsCount < MIN_FPS_STATS_COUNT) {
    // Calculate the FPS
    _drawingTime = System.currentTimeMillis() - _drawingTime;
    _fps = 1000 / _drawingTime;
    _averageFps += _fps;
    _fpsCount++;

    if (_fpsCount == MIN_FPS_STATS_COUNT) {
        // Enough figures calculated. Now calculate the average.
        _averageFps /= _fpsCount;

        if (_firstTime) {
            if (_averageFps < 25) {
                // Remove the background to boost the performance.
               setBackground(false);
            }
         
            _firstTime = false;
        }
    }
}