UDN
Search public documentation:

ScaleformActionScriptBestPractices
日本語訳
中国翻译
한국어

Interested in the Unreal Engine?
Visit the Unreal Technology site.

Looking for jobs and company info?
Check out the Epic games site.

Questions about support via UDN?
Contact the UDN Staff

UE3 Home > User Interfaces & HUDs > Scaleform GFx > Scaleform GFx ActionScript Best Practices

Scaleform GFx ActionScript Best Practices


Overview


ActionScript is not compiled to native machine code; instead it is converted to byte code, which is faster than an interpreted language but not as fast as compiled native code. Although ActionScript can be slow, in most multimedia presentations, the assets such as graphics, audio, and video - and not the code - are often the limiting performance factor.

Many optimization techniques are not specific to ActionScript but simply well-known techniques for writing code in any language without an optimizing compiler. For example, loops are faster if items that don't change with every loop iteration are removed from within the loop and placed outside the loop instead.

ALERT! Note: ActionScript is abbreviated as AS for the rest of this document.
ALERT! Note: Virtual Machine is abbreviated as VM for the rest of this document.

General ActionScript Guidelines


The following optimizations will increase AS execution speed.

  • Publish SWFs to Flash version 8 or better.
  • The less AS used, the better the file performance. Always minimize the amount of code written to perform a given task. AS should be used primarily for interactivity, not for creating graphical elements. If your code consists of heavy use of attachMovie calls, reconsider how the FLA file is constructed.
  • Use scripted animation sparingly; timeline animation will typically perform better.
  • Avoid heavy string manipulation.
  • Avoid too many looping movie clips that use an “if” statement to avoid termination.
  • Avoid using on() or onClipEvent() event handlers. Use onEnterFrame, onPress, etc instead.
  • Minimize placing primary AS logic on a frame. Instead, place large chunks of important code inside functions. The Flash AS compiler can generate significantly faster code for source located within the function body as compared to that located directly inside of frames or old-style event handlers (onClipEvent, on). It is however acceptable to keep simple logic in the frame, such as timeline control (gotoAndPlay, play, stop, etc.) and other non-critical logic.
  • Avoid using "long-distance" gotoAndPlay/gotoAndStop in movie clips with long timelines having several animations.
    1. In case of forward gotoAndPlay/gotoAndStop , the farther the target frame from the current one, the more expensive is the gotoAndPlay/gotoAndStop timeline control. Thus, the most expensive forward gotoAndPlay/gotoAndStop is from the first frame to the last one.
    2. In case of backward gotoAndPlay/gotoAndStop , farther the target frame from the beginning of the timeline, the more expensive is the timeline control. Thus the most expensive backward gotoAndPlay/gotoAndStop is from the last frame to the frame before the last.
  • Use movieclips with short timelines. The cost of the gotoAndPlay/gotoAndStop highly depends on the number of keyframes and the complexity of timeline animation. Thus, do not create long and complex timelines if you are planning to navigate by calling gotoAndPlay/gotoAndStop. Instead, split the movieclip's timeline into several independent movieclips with shorter timelines and less gotoAndPlay/gotoAndStop calls.
  • If updating many objects simultaneously, it is essential to develop a system in which these objects can be updated as a group. On the C++ side use a GFxMovie::SetVariableArray call to pass large amount of data from C++ to AS. This call can then be followed by a single invoke that updates multiple objects at once based on the uploaded array. Grouping multiple invokes into one call is usually several times faster than calling them for each individual object.
  • Don’t attempt to perform too much work in a single frame, or GFx may not have time to render the Stage, and the user may perceive a slowdown. Instead, break up the amount of work being performed into smaller chunks, allowing GFx to refresh the Stage at the prescribed frame rate with no perceived slowdown.
  • Don't overuse the Object type.
    1. Data-type annotations should be precise, because it allows compiler type checking to identify bugs. Use the Object type only when there is no reasonable alternative.
  • Avoid using the eval() function or array access operator. Often, setting the local reference once is preferable and more efficient.
  • Assign Array.length to a variable before a loop to use as its condition, rather than using myArr.length itself. For example:

Use the following code:

var fontArr:Array = TextField.getFontList();
var arrayLen:Number = fontArr.length;
for (var i:Number = 0; i < arrayLen; i++) {
  trace(fontArr[i]);
}

Instead of:

var fontArr:Array = TextField.getFontList();
for (var i:Number = 0; i < fontArr.length; i++) {
  trace(fontArr[i]);
}

  • Manage events wisely and specifically. Keep event listener arrays compacted by using conditions to check whether a listener exists (is not null) before calling it.
  • Explicitly remove listeners from objects by calling removeListener() before releasing the reference to the object.
  • Minimize the number of levels in package names to reduce startup time. At startup, the AS VM has to create the chain of objects, one object per level. Moreover, before the creation of each level object, the AS compiler adds an "if" conditional to check whether the level is already created or not. Thus, for the package "com.xxx.yyy.aaa.bbb" the VM will create objects "com", "xxx", "yyy", "aaa", "bbb" and before each creation there will be an "if" opcode that checks the object's existence. Accessing of such deeply nested objects/classes/functions is also slow, since resolving names requires parsing each level (resolve "com", then "xxx", then "yyy" inside the "xxx", etc). To avoid this additional overhead, some Flash developers use preprocessor software to reduce the path to a single-level unique identifier, such as c58923409876.functionName(), before compiling the SWF.
  • If an application consists of multiple SWF files that use the same AS classes, exclude those classes from select SWF files during compilation. This can help reduce runtime memory requirements.
  • If AS on a keyframe in the timeline requires a lot of time to complete, consider splitting that code up to execute over multiple keyframes.
  • Remove trace() statements from the code when publishing the final SWF file. To do this, select the Omit Trace Actions check box on the Flash tab in the Publish Settings dialog box, then comment them out or delete them. This is an efficient way to disable any trace statements used for debugging at runtime.
  • Inheritance increases the number of method calls and uses more memory: a class that includes all the functionality it needs is more efficient at runtime than a class that inherits some of its functionality from a superclass. Therefore, it may be necessary to make a design trade-off between extensibility of classes and efficiency of code.
  • When one SWF file loads another SWF file that contains a custom AS class (for example: foo.bar.CustomClass) and then unloads the SWF file, the class definition remains in memory. To save memory, explicitly delete any custom classes in unloaded SWF files. Use the delete statement and specify the fully qualified class name, as the following example shows: delete foo.bar.CustomCLass
  • Not all code has to run every frame, use flip flops (where portions of code are alternated each frame) for non-100% time critical items.
  • Try to use as few onEnterFrames as possible.
  • Precalculate data tables instead of using math functions.
    1. If doing a lot of math, then consider precalculating the values and storing them in a (pseudo) array of variables. Pulling those values from a data table can be much faster than having GFx to do it on the fly.

Loops

  • Focus on optimizing loops, and any repeating actions.
  • Limit the number of loops used and the amount of code that each loop contains.
  • Stop frame-based looping as soon as it is no longer needed.
  • Avoid calling a function multiple times from within a loop.
  • It is better to include the contents of a small function inside the loop.

Functions

  • Whenever possible, avoid deeply nested functions.
  • Do not use 'with' statements inside functions. This operator turns off optimizations.

Variables / Properties

  • Avoid referencing nonexistent variables, objects, or functions.
  • Use the “var” keyword whenever possible. The use of the “var” keyword inside functions is especially important since the ActionScript compiler optimizes access to local variables using internal registers with direct access by index rather than putting them into a hash-table and accessing by names.
  • Don't use class variables or global variables when local variables will suffice.
  • Limit the use of global variables, because they are not garbage collected if the movie clip that defined them was removed.
  • Delete variables or set them to null when no longer needed. Doing this marks the data for garbage collection. Deleting variables helps optimize memory use during runtime, because unneeded assets are removed from the SWF file. It is better to delete variables than to set them to null.
  • Always try to access properties directly rather than using AS getter and setter methods, which have more overhead than other method calls.

General Optimization Tips


The Flash Timeline

Timeline frames and layers are two important parts of the Flash authoring environment. These areas show where assets are placed and determine how your document works. How a timeline and the library are set up and used affect the entire FLA file and its overall usability and performance.

  • Use frame-based loops sparingly. A frame-based animation is dependent on the application’s framerate, as opposed to a time-based model which is not tied to FPS.
  • Stop frame-based loops as soon as they are not needed.
  • Distribute complex blocks of code across more than one frame if possible.
  • Scripts with hundreds of lines of code will be just as processor intensive as timelines with hundred of frames based tweens.
  • Evaluate content to determine whether animation/interaction can be easily achieved with the timeline or simplified and made modular using AS.
  • Avoid using the default layer names (such as Layer 1, Layer 2), because it can be confusing to remember or locate assets when working on complex files.

General Performance Optimization

  • There are ways to combine transforms for better performance. For example, instead of nesting three transforms, calculate one matrix manually.
  • If things slow down over time, check for memory leaks. Be sure to dispose of things which are no longer needed.
  • At authoring time, avoid a lot of trace() statements or updating text fields dynamically as these consume performance. Update them as infrequently as possible (i.e. only when something changes rather than constantly).
  • If applicable, place layers that include AS and a layer for frame labels at the top of the layer stack in the timeline. For example, it is a good and common practice to name the layer that contains AS actions.
  • Do not put frame actions in different layers; instead, concentrate all actions in one layer. This will simplify management of AS code and improve performance by eliminating the overhead incurred by multiple AS execution passes.

Advance

If advance is taking too long to execute, there are seven possible optimizations:

  • Do not execute AS code on every frame. Avoid onEnterFrame handlers as well, which invoke code on each frame.
  • Use event-driven programming practices, changing text field and UI state values through an explicit Invoke notification only when a change takes place.
  • Stop animation in invisible movie clips (_visible property should be set to true; use the stop() function to stop animation). This will exclude such movie clips from the Advance list. Note that it is necessary to stop every single movie clip in the hierarchy, including children, even if the parent movie clip has stopped.
  • There is an alternative technique that involves usage of _global.noInvisibleAdvance extension. This extension might be useful to exclude groups of invisible movie clips from the Advance list without stopping each of them. If this extension property is set to "true" then invisible movie clips are not added into the Advance list (including their children) and therefore performance is improved. Keep in mind that this technique is not fully Flash compatible. Ensure that the Flash file does not rely on any type of per frame processing within hidden movies. Don’t forget to turn on GFx extensions on by setting _global.gfxExtensions to true to use this (and any other) extension.
  • Reduce the number of movie clips on stage. Limit unnecessary nesting of movie clips, as each nested clip incurs a small amount of overhead during advance.
  • Reduce timeline animation, number of keyframes, and shape tweens.
  • In GFx 2.2 and earlier versions, it is possible to prevent advancing of static movie clips that are visible and on screen but don't require an advance or an onEnterFrame by setting MovieClip.noAdvance property to true. The GFx extensions should be enabled by setting _global.gfxExtensions to true to use this extension. Note that in most cases, this is not necessary in GFx 3.0 and later. GFx 3.0 doesn't advance or process stopped movie clips at all, as it no longer processes the entire rendering graph in Advance and instead maintains an active list. With this improvement, noAdvance/noInvisibleAdvance is rarely necessary as their main purpose is to skip processing parts of the graph. They can still perhaps be used to disable onEnterFrame or some animated objects, but this is rarely necessary and can be achieved in other ways such as by removing onEnterFrame handler itself.

onClipEvent and on Events

Avoid using onClipEvent() and on() events. Instead, use onEnterFrame, onPress, etc. There are several reasons for doing this:

  • Function-style event handlers are run-time installable and removable.
  • The byte-code inside functions is better optimized than inside old-style onClipEvent and on handlers. The main optimization is in precaching this, _global, arguments, super, etc., and using the 256 internal registers for local variables. This optimization works only for functions.

The only problem exists when you need an onLoad function-style handler to be installed before the first frame is executed. In this case, you may use the undocumented event handler onClipEvent(construct) to install the onEnterFrame:

onClipEvent(construct) {
  this.onLoad = function() {
    //function body
  }
}

Or, use onClipEvent(load) and call a regular function from it. This approach is less effective however, as there will be additional overhead for the extra function call.

onEnterFrame

Minimize the use of onEnterFrame event handlers, or at the very least install and remove them when necessary, rather than having them executed all the time. Having too many onEnterFrame handlers may drop performance significantly. As an alternative, consider using setInterval and setTimeout functions. When using setInterval:

  • Do not forget to call clearInterval when the handler is no longer needed.
  • setInterval and setTimeout handlers may be slower than onEnterFrame if they are executed more frequently than onEnterFrame. Use conservative values for the time intervals to avoid this.

To remove onEnterFrame handlers use the delete operator:

delete this.onEnterFrame;
delete mc.onEnterFrame;

Do not assign null or undefined to onEnterFrame (e.g., this.onEnterFrame = null), as this operation does not remove the onEnterFrame handler completely. GFx will still attempt to resolve this handler, as the member with the name onEnterFrame will still exist.

Var Keyword

Use the var keyword whenever possible. It is especially important to do so inside functions, as the AS compiler optimizes access to local variables by using internal registers with direct access by index rather than putting them into hash tables and accessing by names. Using the var keyword can double the AS function’s execution speed.

Un-optimized Code:

var i = 1000;
countIt = function() {
  num = 0;
  for(j=0; j<i; j++) {
    j++;
    num += Math.random();
  }
  displayNumber.text = num;
}

Optimized Code:

var i = 1000;
countIt = function() {
  var num = 0;
  var ii = i;
  for(var j=0; j<ii; j++) {
    j++;
    num += Math.random();
  }
  displayNumber.text = num;
}

Precaching

Precache frequently accessed read-only object members in local variables (with var keyword).

Un-optimized Code:

function foo(var obj:Object) {
  for (var i = 0; i < obj.size; i++) {
    obj.value[i] = obj.num1 * obj.num2;
  }
}

Optimized Code:

function foo(var obj:Object) {
  var sz = obj.size;
  var n1 = obj.num1;
  var n2 = obj.num1;
  for (var i = 0; i < sz; i++) {
    obj.value[i] = n1*n2;
  }
}

Precaching can be used effectively for other scenarios as well. Some further examples include:

var floor = Math.floor
var ceil = Math.ceil
num = floor(x) ? ceil(y);
var keyDown = Key.isDown;
var keyLeft = Key.LEFT;
if (keyDown(keyLeft)) {
  // do something;
}

Precache Long Paths

Avoid repeating usage of long paths, such as:

mc.ch1.hc3.djf3.jd9._x = 233;
mc.ch1.hc3.djf3._x = 455;

Precache parts of the file path in local variables:

var djf3 = mc.ch1.hc3.djf3;
djf3._x = 455;
var jd9 = djf3.jd9;
jd9._x = 223;

Complex Expressions

Avoid complex, C-style expressions, such as:

this[_global.mynames[queue]][_global.slots[i]].gosplash.text = _global.MyStrings[queue];

Split this expression into smaller parts, storing intermediate data in local variables:

var _splqueue = this[_global.mynames[queue]];
var _splstring = _global.MyStrings[queue];
var slot_i = _global.slots[i];
_splqueue[slot_i].gosplash.text = _splstring;

This is especially important if there are multiple references on those split parts, for example in the following loop:

for(i=0; i<3; i++) {
  this[_global.mynames[queue]][_global.slots[i]].gosplash.text = _global.MyStrings[queue];
  this[_global.mynames[queue]][_global.slots[i]].gosplash2.text = _global.MyStrings[queue];
   this[_global.mynames[queue]][_global.slots[i]].gosplash2.textColor = 0x000000;
}

An improved version of the previous loop would be as follows:

var _splqueue = this[_global.mynames[queue]];
var _splstring = _global.MyStrings[queue];
for (var i=0; i<3; i++) {
  var slot_i = _global.slots[i];
  _splqueue[slot_i].gosplash.text = _splstring;
  _splqueue[slot_i].gosplash2.text = splstring;
  _splqueue[slot_i].gosplash2.textColor = 0x000000;
}

The above code can be further optimized. Eliminate multiple references to the same element of array, if possible. Precache the resolved object in a local variable:

var _splqueue = this[_global.mynames[queue]];
var _splstring = _global.MyStrings[queue];
for (var i=0; i<3; i++) {
  var slot_i = _global.slots[i];
  var elem = _splqueue[slot_i];
  elem.gosplash.text = _splstring;
  var gspl2 = elem.gosplash2;
  gspl2.text = splstring;
  gspl2.textColor = 0x000000;
}