Home Journal Building an AS3 component framework: Deferred Rendering

Building an AS3 component framework: Deferred Rendering

by John

Every component framework I’ve ever seen implements deferred rendering. The aim of this feature is to optimize the redrawing of a component so that it does not perform any unnecessary operations.

The Problem
Component properties can change many times during a frame and redrawing them every time this happens is wasteful. The optimal solution is to redraw the component once per frame and only if one or more properties have changed. This process of batching state changes for later rendering is called ‘invalidation’.

There is a second issue to be aware of when dealing with redrawing and it’s of critical importance during the instantiation process. The component does not have access to the stage until it has been added to the active DisplayList.

The Solution
Every component has a draw() function which when called renders the component based upon its current internal state. Instead of calling draw() every time a property changes, the component caches the value of the property and calls invalidate() instead. This function schedules a draw() event at the next available opportunity. With this technique the properties can be changed hundreds of times a frame without any significant drop in performance.

There is two common strategies for implementing invalidation in AS3. I compared both of these when I looked Inside the MinimalComps and concluded that unless I can find a fail-safe way of using stage.invalidate() then I’m going to have to go with the old ENTER_FRAME event. Here’s the process flow for a typical component: It can be instantiated by either placing it on the timeline or by using the new() command in code.

1. Firstly the component constructor runs. This calls the init() function which initialises the start-up state of the component.

2. When the component is added to the stage the resume() function gets called. This function starts up any internal listeners then calls the draw() function which redraws the component based upon its internal state.

3. Removing a component from the stage (by code or by moving the timeline playhead forces the suspend() function to be called. This freezes the component whilst it is not on the DisplayList. That means no more realtime 3D hogging the processor and no more mp3s playing from nowhere!

The invalidate() and draw() functions are currently both protected. I can’t think of a good reason for making them public right now. They are both housekeeping functions that get used by the public properties and methods of the individual component.

The memory management methods however, suspend(), resume() and destroy() are all public so they can be called by parents or managers at any time.

The Benefits
Deferred Rendering will greatly improve the overall performance of your application.

The Code

package org.computus.core
{
	import flash.events.Event;
	import flash.display.MovieClip;

	public class AbstractComponent extends MovieClip
	{

	// ------------------------------------------
	// PROPERTIES

	// isSuspended starts as true as the component is off stage
	protected var _isSuspended:Boolean = true;
	
	// ------------------------------------------
	// CONSTRUCTOR

	public function AbstractComponent():void
	{
		super();
		init();
		if (stage) { resume() }
	}

	// ------------------------------------------
	// MEMORY MANAGEMENT

	protected function init():void
	{
		// concrete classes that override this function should call super.init()
		// n.b. These events require Flash Player 9.0.28.0
		addEventListener(Event.ADDED_TO_STAGE, onAddedToStage, false, 0, true);
		addEventListener(Event.REMOVED_FROM_STAGE, onRemovedFromStage, false, 0, true);

		// initialise component here.
		// no need to call draw() as it will be called when component is added to the stage.
	}

	public function destroy():void
	{
		// concrete classes that override this function should call super.destroy()
		// remove all listeners here
		suspend();
		removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
		removeEventListener(Event.REMOVED_FROM_STAGE, onRemovedFromStage);
	}

	public function suspend():void
	{
		// concrete classes that override this function should call super.suspend()

		// suspend all processes
		removeEventListener(Event.ENTER_FRAME, onInvalidate)
		_isSuspended = true;
	}

	public function resume():void
	{
		// concrete classes that override this function should call super.resume()
		// resume all processes
		_isSuspended = false;
		draw();
	}

	public function get isSuspended():Boolean
	{
		return _isSuspended;
	}

	// ------------------------------------------
	// EVENTS

	private function onInvalidate(e:Event):void
	{
		removeEventListener(Event.ENTER_FRAME, onInvalidate);
		draw();
	}

	private function onAddedToStage(e:Event):void
	{
		resume()
	}

	private function onRemovedFromStage(e:Event):void
	{
		suspend()
	}

	// ------------------------------------------
	// DRAW

	protected function invalidate():void
	{
		addEventListener(Event.ENTER_FRAME, onInvalidate, false, 0, true);
	}

	protected function draw():void
	{
		// concrete classes should override this function
		// redraw component state
	}
	}
}

You may also like

2 comments

triynko April 29, 02014 - 9:35 pm

Suppose every component has a draw method that runs in response to a render event. That seems rather inefficient to run the draw method of every object listening for render when only a single object may have changed.

Reply
John May 17, 02014 - 9:11 am

The draw method should only run in response to an invalidate request. The thinking behind it being you update the model as much as you like and then only draw the changes once.

Reply

Leave a Reply

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