Tips for efficient AS3 coding [closed]
AS3, like many OOP languages, contains many optimization techniques, that may be unheard of to inexperience users. These can range from micro-macro coding difference, to file structures. To help the less experienced ones : 开发者_高级运维What are the tips worth knowing, that will enrich an AS3 programmer.
The core idea : Is to introduce optimization methods to newer programmers who may know the basics. But not the various techniques of 'seasoned' programmers (Especially within loops).
Since most as3 programmer, will use either flash or flex, programming tips involving them are naturally welcomed too.
Also please limit each answer to one tip, so the best ones will naturally float to the top =)
However : AS3, being a "compile language" does much optimization on its own, so for the new guys, that work too hard on following the tips. Learn from it, not be a slave to it. The first step is always to "complete the app".
If you're serious into game programming : evolve your hierarchy, don't use deep hierarchies, teach yourself entity/component architecture (agregation/composition vs inheritance), dig into PushButton Engine source code.
Another optimization on advanced game programming: use bitmap blitting techniques (setPixels, BitmapData, as opposed to MovieClip/Sprites) like described here : http://mikegrundvig.blogspot.com/2007/05/copypixel-is-faster-then-sprites.html. Dig into Flixel source code.
Be provident, when creating objects. Reuse miscelaneous objects, when possible.
Better said: reuse what you can, but reusing misc objects is easy to start with.
This saves a lot of work for garbage collector and thus affects overall performance of your application. This way you will also save time that would be otherwise lost on creating redundant instances.
These efforts are not always necessary, but, the more intense your app activities are, the more valuable are the following techniques.
Several tips below illustrate this idea on matrices, points, arrays, dictionaries, ...rest, filters, color transforms.
Tip 1: matrices.
// DO: reusing a matrix.
private static const MATRIX:Matrix = new Matrix();
// <...>
var bmp:BitmapData = new BitmapData(width, height, true, 0);
MATRIX.identity();
MATRIX.scale(0.5, 0.5);
bmp.draw(source, MATRIX);
// DON'T
var bmp:BitmapData = new BitmapData(width, height, true, 0);
var matrix:Matrix = new Matrix();
matrix.scale(0.5, 0.5);
bmp.draw(source, matrix);
Tip 2: points.
// DO: reusing a point.
private static const ZERO_POINT:Point = new Point();
// <...>
var thresholdTest:BitmapData = new BitmapData(bmp.width, bmp.height, true, 0);
thresholdTest.threshold(bmp, bmp.rect, ZERO_POINT, ">", 0x10000000, 0xFFFF0000, 0xFF000000);
// DON'T
var thresholdTest:BitmapData = new BitmapData(bmp.width, bmp.height, true, 0);
thresholdTest.threshold(bmp, bmp.rect, new Point(), ">", 0x10000000, 0xFFFF0000, 0xFF000000);
Tip 3: avoid heavily using methods, that create redundant instances for you, unless it's absolutely necessary and convenient. Or at least reduce usage of such methods to single calls during you app session.
localToGlobal()
andglobalToLocal()
: try to figure whether you can calculate coordinates by yourself. Otherwise, Point instance is created on each call.getRect()
andgetBounds()
: see if you can structure your display list in a way that you don't need detect complicated displacements. Otherwise, Rectangle instance is created on each call....rest:Array
: obviously on each method call this creates an array that is being disposed of on method return. Is this absolutely necessary? May be you can use array created outside of the method and reuse it each time (see next tip)?
Tip 4: when possible, create single Array
or Dictionary
instances when you are only using them as containers for generating some results in a method.
// DO: reuse array that holds results
public function generateMeSomeResults(results:Array):void
{
results.length = 0;
// <...>
// fill results array with what you need
// and reuse this reference next time
}
// DON'T
public function generateMeSomeResults():Array
{
var results:Array = [];
// <...>
return results;
}
Tip 5: don't create new array and new filter object each time you want to change someDisplayObject.filters. This is especially important if you animate filters and they have changes in each frame.
// DO: reuse filters array and filter objects
private static const EMPTY_FILTERS:Array = [];
private static const GLOW:GlowFilter = new GlowFilter(0xFF0000, 1, 12, 12);
private static const HOVER_FILTERS:Array = [GLOW];
private function onRollOver(event:MouseEvent):void
{
// it's Ok to change constant here
// because once filters property is set,
// you are free to change filter object: it will not affect
// filters that were set before.
GLOW.color = 0xFF0000+Math.random()*0xFF00;
this.filters = HOVER_FILTERS;
}
private function onRollOut(event:MouseEvent):void
{
this.filters = EMPTY_FILTERS;
}
// DON'T
private function onRollOver(event:MouseEvent):void
{
this.filters = [new GlowFilter(0xFF0000, 1, 12, 12)];
}
private function onRollOut(event:MouseEvent):void
{
this.filters = [];
}
One more important thing to note: DisplayObject.filters
a getter/setter. When you get DisplayObject.filters
value, you effectively create a new object which is not what you want. Hence, reusing an array of filters is a good idea: you have but one instance all the time.
Tip 6: the same Tip 5 goes for ColorTransform
objects. You don't need a separate ColorTransform
instance for each change of a display object's tint. Once you set DisplayObject.transform.colorTransform = someColorTransform;
, you can change your someColorTransform object freely: it will not affect already applied color transforms.
Like in the case with filters
, this property is a getter/setter. When you get DisplayObject.transform.colorTransform
value, you effectively create a new object which is not what you want. See the test below.
import flash.geom.ColorTransform;
var test1:ColorTransform = transform.colorTransform;
var test2:ColorTransform = transform.colorTransform;
trace(test1 == test2); // always false. this object gets created each time on access.
Use vectors instead of arrays when possible. It has ~60% performance improvement over Array.
Vectors are arrays with variable type and/or array length limitations.
http://www.mikechambers.com/blog/2008/08/19/using-vectors-in-actionscript-3-and-flash-player-10/
Generally though, don't bother with optimizing everything, optimize only when and what you need to optimize, if you optimize 3% of your code that accounts for 95% of your performance, you have achieved a similar level of performance to optimizing 100% of code at 3% of time spent or 33 times less time overall...
Use [] and new Object(), instead of, new Array() and {}. It is at best 3 times faster. And is extremely common for the more costly operation to occur in loops.
Generally speaking, the new keyword is just plain expensive.
EDIT: Updated to reflect the change : Surprise surprise, it seems that the updated version of flash, has drastically improved the performance of "new Object();" statement. This seems to apply to CS5 and above. Cause the test i did in CS4 showed the other way. [of {} being better then new Object() ]
Anyway for those who want to see it themselves
import flash.utils.getTimer;
public var _time:Number;
public var _simpleArrayTime:Number;
public var buffer:Array;
public function testArray():void
{
trace( "----------------Array Test--------------");
_time = getTimer();
for ( var a:int = 0; a < 100000; a++ )
buffer = [];
_simpleArrayTime = getTimer() - _time;
trace( "[] * 100000 :"+_simpleArrayTime.toPrecision(21) );
_time = getTimer();
for ( var b:int = 0; b < 100000; b++ )
buffer = new Array();
_simpleArrayTime = getTimer() - _time;
trace( "new Array() * 100000 :"+_simpleArrayTime.toPrecision(21) );
_time = getTimer();
for ( var c:int = 0; c < 100000; c++ )
buffer = [];
_simpleArrayTime = getTimer() - _time;
trace( "[] * 100000 :"+_simpleArrayTime.toPrecision(21) );
}
public var objBuffer:Object;
public function testObject():void
{
trace( "----------------Object Test--------------");
_time = getTimer();
for ( var a:int = 0; a < 100000; a++ )
objBuffer = {};
_simpleArrayTime = getTimer() - _time;
trace( "{} * 100000 :"+_simpleArrayTime.toPrecision(21) );
_time = getTimer();
for ( var b:int = 0; b < 100000; b++ )
objBuffer = new Object();
_simpleArrayTime = getTimer() - _time;
trace( "new Object() * 100000 :"+_simpleArrayTime.toPrecision(21) );
_time = getTimer();
for ( var c:int = 0; c < 100000; c++ )
objBuffer = {};
_simpleArrayTime = getTimer() - _time;
trace( "{} * 100000 :"+_simpleArrayTime.toPrecision(21) );
}
public function runTests(event:Event = null):void
{
testArray();
testObject();
}
And the following are my reasults
----------------Array Test--------------
[] * 100000 :82.0000000000000000000
new Array() * 100000 :152.000000000000000000
[] * 100000 :53.0000000000000000000
----------------Object Test--------------
{} * 100000 :53.0000000000000000000
new Object() * 100000 :36.0000000000000000000
{} * 100000 :53.0000000000000000000
When you need several copies of an image displayed on one screen, don't create multiple BitmapDatas: use a single BitmapData
instance and just pass its reference to separate Bitmap
objects.
import flash.display.BitmapData;
import flash.display.BitmapDataChannel;
import flash.display.Bitmap;
// create one image
var bmp:BitmapData = new BitmapData(100, 100, true, 0);
bmp.perlinNoise(36, 36, 3, 100, false, false, BitmapDataChannel.BLUE|BitmapDataChannel.GREEN);
// *** display the same image multiple times ***
var test1:Bitmap = new Bitmap(bmp);
test1.x = 0;
test1.y = 0;
addChild(test1);
var test2:Bitmap = new Bitmap(bmp);
test2.x = 30;
test2.y = 30;
addChild(test2);
var test3:Bitmap = new Bitmap(bmp);
test3.x = 60;
test3.y = 60;
addChild(test3);
It may be especially useful, when you are loading an external assets library (an SWF) and get BitmapData
items by applicationDomain.getDefinition()
and new
. Caching what you get may prove very useful.
Teach yourself bitwise operations. Use it in your main rendering loop, bitmap or audio processing, and learn to keep a programmer-friendly code by NOT using it when it's not needed (that's the difficult part).
there is, of course, the new StageVideo class since Flash Player 10.2 which is much more performant than rendering with the traditional Video object.
Adobe MAX 2010 Stage Video Preview (video)
Getting Started With Stage Video (tutorial)
Objects instantiation is expensive. Use object pooling for objects creation/destruction needs in critical code (rendering, game loop). You can find an efficient out-of-the-box library here : http://lab.polygonal.de/2008/06/18/using-object-pools/
The usual EventDispatcher used massively in the Flash Platform API has a cost: instantiating several objects, usually inheriting Event type.
The signal-and-slots pattern is a reliable replacement, well-known (see Boost::Signals, Qt), and will result in a huge performance gain, benchmarks here, enjoy.
TurboSignals and as3-signal are reliable out-of-the-box implementations. I consider as3-signals as production-ready and use it in my day job.
Split up your app as much as you can!
Split up all functionality into classes, sub-classes, etc. Avoid cluttering the MXML include script / initializing script with code, keeping it to a minimum. It will help in the not-so-long run, especially on bigger RIA projects, when you app plans may change. It also makes changes easier, by swapping classes, and running search-replace.
While making your code easier to understand, to yourself in the future, or others.
If you're coding with the Flex Ffamework, be sure to memorize the Flex Component LifeCycle and make use of it when building your own components. Here is info on the MX Lifecycle and the Spark Lifecycle.
The lifecycle makes use of the render events of the Flash Player and you save time and performance by grouping similar changes into a single place in your code.
Learn about how the Flash Player balances rendering events on screen with execution code behind those events. The approach is sometimes referred to as the Elastic Racetrack. Here are some relevant links describing how it works:
http://ted.onflash.org/2005/07/flash-player-mental-model-elastic.php
http://www.craftymind.com/2008/04/18/updated-elastic-racetrack-for-flash-9-and-avm2/
http://ted.onflash.org/2008/04/flash-player-mental-model-elastic.php
Once you know how this works, you can use it to optimize your code
When optimizing Flex projects for mobile devices, be sure to build your custom Skins in ActionScript, not in MXML. Since MXML is turned into AS3 by the Flex Compiler, it is not always the most performance intensive. Building in ActionScript can avoid
Most likely you'll want to extend the MobileSkin class, which includes extra methods that are either helper methods or add additional hooks to standard UIComponent lifecycle methods.
Some of the additional hooks methods:
- layoutContents()
- drawBackground()
- commitCurrentState()
You'd extend these methods to perform your own functionality. layoutContents and drawBackground are called from updateDisplayList. commitCurrentState is called from the set currentState method.
Some of the helper methods include:
- getElementPreferredHeight()
- getElementPreferredWidth()
- setElementSize()
- setElementPosition()
You wouldn't extend these methods, but just call them as needed.
精彩评论