Copyright © 2004 O'Reilly Media, Inc. All Rights Reserved.
Flash Hacks: 100 Industrial-Stength Tips & Tools
By Sham Bhangal
June 2004
ISBN: 0-596-00645-4
http://www.oreilly.com/catalog/flashhks/index.html
Available from booksellers or direct from O'Reilly Media, www.oreilly.com.

Cover image
This content is excerpted from the above-named O'Reilly publication, with permission, by agreement with ActionScript.org.

Measure the Flash Player's runtime performance in order to adjust the complexity of an effect or animation dynamically to run optimally on both low-end and high-end machines.

In the preceding hacks, we've seen ways to improve performance, regardless of the playback system, by optimizing a Flash movie's assets at authoring time. Optimization of that sort is essentially a lowest common denominator affair. This hack addresses the need to maximize animation and other effects for faster machines without creating a movie that bogs down on slower machines.

Although Flash Player 7 performs better than previous versions, it is still not a speed demon. The underlying goal of Flash is to optimize filesize (i.e., reduce download time) rather than provide breathtaking performance. Although we can improve performance by lowering the render quality [Hack #67], in many cases, turning off antialiasing can result in an unacceptably poor appearance for your site.

A better way to improve performance is to adjust the complexity (i.e., how much is happening per frame) of a graphical effect rather than its quality or frame rate. This approach can yield better results over a wider range of user systems with varying processing power than a one-size-fits-all optimization.

Adjusting the complexity is often an iterative process based on experimentation (with time it will become second nature and require fewer iterations):

  • Start with a target frame rate, such as 18 fps.

  • Measure the achieved frame rate, as covered shortly.

  • If the desired frame rate is not achievable on the test machine, reduce the complexity of the effect.

  • If the effect is rendered quickly enough, you have the leeway to increase the complexity of the effect.

Now that you understand the basics of the approach, let's automate it so that Flash decides dynamically whether to reduce or increase the complexity of the animation. This approach protects against sluggish performance on slower machines and (when done well) enhances the effect on faster machines.

We do not intend to change the frame rate, but rather change the complexity of the effect, in such a way that it always completes just before the next frame starts.

Calculate the Achieved Frame Rate

The following code creates an empty movie clip called perfMon (performance monitor) and attaches an onEnterFrame( ) event handler to it. This event handler calculates the average frame duration (in milliseconds) using the last two timer measurements, creating a rolling average. This rolling average, time, is compared against the expected duration, FRAME_DUR, and a Boolean flag, slow, is set to true or false based on whether the Flash Player is achieving the target frame rate.

function performanceMonitor( ) {
 this.createEmptyMovieClip("perfMon", 10000);
 perfMon.onEnterFrame = function( ) {
 time = (getTimer( ) - lastTime) / 2;
 lastTime = getTimer( );
 // Set <em class="replaceable"><code>slow</code></em> to a Boolean based on whether the
 // elapsed time exceeds the allowed frame duration
 slow = time > FRAME_DUR;
 };
}
// Set FRAME_RATE to match the movie's target frame rate
var FRAME_RATE:Number = 18;
var FRAME_DUR:Number = (1 / FRAME_RATE) * 1000;
var time:Number = 0;
var lastTime:Number = getTimer( );
performanceMonitor( );

A slow value of false indicates that we can make the Flash Player do more without compromising performance because it is completing all its tasks before the start of the next frame. Ideally, we would want the Flash Player to finish all tasks for the current frame just as the next frame starts. We know this is happening when our rolling average, time, is equal to the expected frame duration, FRAME_DUR.

Adjust the Complexity Based on Performance

Let's see how to make Flash increase or decrease the animation's complexity based on the performance monitor's calculation. One simple approach is to draw more or fewer movie clips depending on the value of our slow flag. For the sake of example, let's use a star field particle effect [Hack #33] . The following code assumes a default Stage size of 550 400 pixels, with a dark background color. The effect works best with a frame rate of 18-24 fps. To set the background color and frame rate, select Modify?Document and adjust the properties as desired. The following code assumes a frame rate of 24 fps, and if you choose any other rate, you will need to change the following line to reflect this:

var FRAME_RATE:Number = 24;

Note that you have to do this manually because there is no property or method in Flash that returns the movie's target frame rate (strange but true!).

We've modified the starfield( ) function from [Hack #33] to draw a single star at a time. Instead of setting the slow flag when the animation is running slowly, we use an if statement to adjust the animation's complexity. If the animation is running quickly enough, it draws additional stars by calling starfield( ); otherwise, it deletes stars to maintain performance.

Again, it is important to realize that we are not varying the frame rate (and doing so would not be transparent to the rest of the SWF, in any case), but rather the complexity of the animation so that the time taken to render each frame in the animation is equal to all the available time per frame.

function performanceMonitor( ) {
 var perfMon:MovieClip = this.createEmptyMovieClip("perfMon", 10000);
 perfMon.onEnterFrame = function( ) {
 time = (getTimer( ) - lastTime) / 2;
 if (time < (FRAME_DUR)) {
 // Speed is okay
 stars++;
 starField( );
 } else if (time > (FRAME_DUR + 10)) {
 // Running too slowly
 _root["star" + stars].removeMovieClip( );
 stars--;
 }
 lastTime = getTimer( );
 };
}
function mover( ) {
 this._y += this.speed;
 this._yscale += this.speed;
 if (this._y > 275) {
 this._y = 0;
 this.speed = Math.ceil(Math.random( ) * 10);
 this._yscale = 100;
 }
}
function starField( ) {
 var star:MovieClip = this.createEmptyMovieClip("star" + stars, stars);
 star._rotation = Math.random( )*360;
 star._x = 275;
 star._y = 200;
 var dot:MovieClip = star.createEmptyMovieClip("dot", 0);
 dot.speed = Math.ceil(Math.random( ) * 10);
 dot.lineStyle(1, 0xFFFFE0, 100);
 dot.moveTo(0, 2);
 dot.lineTo(0, 5);
 dot.onEnterFrame = mover;
}
// Set FRAME_RATE to match the movie's target frame rate
var FRAME_RATE:Number = 24;
var FRAME_DUR:Number = (1 / FRAME_RATE) * 1000;
var time:Number = 0;
var lastTime:Number = 0;
var stars:Number = 0;
performanceMonitor( );

If you run the preceding code, you will see the star field build from no stars up to a maximum of several hundred stars, as shown in Figure 9-7. Use the Variables tab in the Debugger panel to view the variables on the main timeline, which displays results similar to those shown in Figure 9-7