Every component framework I've ever seen implements deferred rendering. The aim of this feature is to optimise 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.

  • Firstly the component constructor runs. This calls the init() function which initialises the start-up state of the component.
  • 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.
  • 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
    }
  }
}