ActionScript 3.0: A Dynamic Frame Rate Switching Class to Lower CPU Usage

Flash gets a lot of negative press because it’s seen as using a heap of CPU time and bogging everything down. And it’s a fair cop. Most flash will eat up your CPU cycles even when it’s sitting there doing nothing. But this isn’t a fault of flash, but rather of flash developers. Let me explain…

When you start a piece of flash work, you assign it a frame rate at which you want it to run, so it’ll update the screen, say, 30 times a second. This is all fine and good for when you’re animating things on the stage. But what about when you’re not? Well, it’s still running at 30 frames per second and chewing up your CPU like a crazy melon farmer. This is Not A Good Thing. So, anyhow, I’m watching this video about SWF Framerate Optimisation, and the guy’s showing how you can modify your code to lower the frame rate when there’s not a lot happening, and bring it back up when you’re animating. So I had a crack at it, and lo & behold, it works fine for the specific piece of flash I’d coded it into, so I wondered if I couldn’t just go and make a RateController class. This way, I could add a RateController object to any project to dynamically change the project’s frame rate depending on whether the mouse was over the stage or not.

And after much swearing about not having global access to the stage properties, I found that I COULD!!!

Here’s a working example placed into the attracting particles code I wrote yesterday:

Note: The animation starts at full speed for two seconds on startup. It’ll drop to the sleeping rate (5 fps) two seconds after the mouse leaves the stage, and then ramps back up to its waking rate (30 fps) instantly when the cursor is back over the stage. The FPSCounter shows intermediate numbers because it’s based on an average.

To add a RateController to any flash project, you can just use something like:

import RateController;
 
// Add a new RateController, uses the root stage, runs at 5fps when sleeping, 30fps 
// when active (i.e. when mouse is over the stage), and uses a 2000 millisecond
// delay after the mouse leaves the stage before dropping the FPS to the sleeping rate.
addChild(new RateController(stage, 5, 30, 2000));

Not bad, eh?

Full class code & file downloads after the jump…

Behold the RateController Class in all its over-commented glory!:

package
{
	import flash.display.Stage;
	import flash.display.MovieClip;
	import flash.utils.Timer;
	import flash.events.TimerEvent;
	import flash.events.MouseEvent;
	import flash.events.Event;
 
	public class RateController extends MovieClip
	{
		static var switchStateTimer:Timer; // Timer used for when to actually switch states after mouse has left stage
		static var firstRunTimer:Timer; // Timer used to allow full framerate briefly on startup
 
		static var active:Boolean = true;         // Whether the animation is active
		static var instantChange:Boolean = true;  // Whether we're going to instantly change the frame rate
		static var firstRun:Boolean = true;       // Animate normally for duration of timer at startup
 
		var sleepFrameRate:uint;     // The frame rate for when the mouse is not on the stage
		var activeFrameRate:uint;    // The frame rate for when the mouse IS on the stage
		var switchStateDelay:Number; // How long to animate at full rate after the mouse cursor has left the stage
		var currentStage:Stage;	 // A Stage that points to our root stage, we need this to set the framerate
 
		// Constructor
		public function RateController(theStage:Stage, theSleepFrameRate:uint, theActiveFrameRate:uint, theSwitchStateDelay:Number)
		{
			this.currentStage = theStage;
 
			this.sleepFrameRate = theSleepFrameRate;
			this.activeFrameRate = theActiveFrameRate;
			this.switchStateDelay = theSwitchStateDelay;
 
			// Create (but not start!) our timer to switch sleep/wake after a given delay
			switchStateTimer = new Timer(this.switchStateDelay);
 
			// This sleep listener will always listen for the mouse leaving the stage
			this.currentStage.addEventListener(Event.MOUSE_LEAVE, switchController); 
 
			trace("Our current stage is: " + this.currentStage);
 
			// When we start, we want to run at full speed for a little bit to allow any
			// initial animation to happen at the correct rate, and then after a small while
			// put the animation to sleep (if the mouse is moving over it it'll instantly wake
			// up anyway)
			if (firstRun == true) {
				firstRunTimer = new Timer(this.switchStateDelay);
				firstRunTimer.addEventListener(TimerEvent.TIMER, switchStates);
				firstRunTimer.start();
			}
 
		} // End of constructor
 
		// Function to control the frame rate switching
		private function switchController(e:Event):void
		{
			trace("At switchController function...");
			trace("Frame rate is currently: " + stage.frameRate + "\n");
 
			// If we're going to sleep, set the sleep frame rate AFTER the timer delay
			if (instantChange == false) {
 
				trace("Sleeping after timer expires.");
 
				// If we're going to sleep, switch states after the timer delay
				switchStateTimer.addEventListener(TimerEvent.TIMER, switchStates);
				switchStateTimer.start();
			}
			else // If we're waking up, we want to set our active frame rate instantly!
			{
				trace("Waking immediately.");
 
				// switchStates expects a timer event, so we have to pass it a bogus one.
				// Bit of a bodge, I know =/
				var foo:TimerEvent;
				switchStates(foo);
			}
 
		} // End of switchController function
 
		// Function to actually switch the states
		private function switchStates(e:TimerEvent):void
		{
			// If we haven't been called from our initial startup timer
			if (firstRun == false) {
 
				trace("At switchStates function. Stopping timer and switching states...");
				trace("active currently has the value: " + active + "\n");
				switchStateTimer.stop();
				switchStateTimer.removeEventListener(TimerEvent.TIMER, switchStates);
 
				// Swap sleep to wake or vice versa
				if (active == false) // If the animation's asleep, wake it up!
				{
					// Set our flags
					active = true;
					instantChange = false;
 
					// Put the animation to it's waking frame rate and remove the listener for mouse activity on the stage
					this.currentStage.removeEventListener(MouseEvent.MOUSE_MOVE, switchController);
					this.currentStage.frameRate = activeFrameRate;
 
					trace("Setting flash to wake state, where active has the value: " + active);
					trace("Setting wake frame rate: " + stage.frameRate + "\n");
				}
				else // If the animation's awake, put it to sleep!
				{
					trace("active must be true and we're switching states...");
 
					// Set our flags so we know we're not active, and we want to instantly jump
					// to our active framerate when the mouse returns to the stage
					active = false;
					instantChange = true;
 
					// Put the animation to it's sleeping frame rate and add a listener for mouse activity on the stage
					this.currentStage.frameRate = sleepFrameRate;
					this.currentStage.addEventListener(MouseEvent.MOUSE_MOVE, switchController);
 
					trace("Timer has expired, so setting flash to sleep state.");
					trace("Setting sleep frame rate: " + stage.frameRate + "\n");
				}
			}
			else // If we HAVE been called on initial startup (firstRun == true)
			{
				trace("Putting to sleep after initial firstRun timer\n");
 
				// Flip the flags so that:
				firstRun = false;	// We will no longer be in firstRun mode		
				active = false;		// Assume mouse not initially over stage, so the animation is in sleep mode
				instantChange = true;	// When we activate, we want the full frame rate instantly
 
				// Put the animation to it's sleeping frame rate and add a listener for mouse activity on the stage
				this.currentStage.frameRate = sleepFrameRate;
				this.currentStage.addEventListener(MouseEvent.MOUSE_MOVE, switchController);
 
				// Remove our firstRun timer
				firstRunTimer.stop();
				firstRunTimer.removeEventListener(TimerEvent.TIMER, switchStates);
			}
 
		} // End of switch states function
 
	} // End of RateController class
 
} // End of package

There’s a lot of debug code in there at the moment so you can see what it’s doing while it’s doing it, but the version you can download below has all that stripped out.

RateController Class source file: RateController.as

Now for a beer, debugging that thing’s given me a headache…

3 thoughts on “ActionScript 3.0: A Dynamic Frame Rate Switching Class to Lower CPU Usage”

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.