Blog

Posts tagged with “as3”, “flash”, and “code”

Object Pool Class for AS3

Object pools aren't really rocket science. At their simplest, they can be implemented with an array, as follows:

var pool:Array = new Array();

function getFromPool():* {
return (pool.length?pool.pop():new [YourPooledObjectClass]());
}

function returnToPool(o:*):void {
if (o is [YourPooledObjectClass]) {
pool.push(o);
}
}

… and that's it really. The only problem is that you need to ensure that your code then manually resets any default property values of an aquired object. It also means you need to "one off" each pool you use. To that end, I created the following Object pool class.

Its main features:

  • keeps an internal list of default properties to be set at aquisition time.
  • ensures that only declared properties can be set (this can be turned of if desired)
  • allows for per-instance property overrides as needed
  • allows a max count to be set and pool flushing for memory management
  • allows pool pre-population so that potentially expensive creation routines can be managed

The only caveat is that any poolable object cannot require arguments at creation time — unless someone can point me to the class constuctor equivilent of funtion.apply();

package ca.atomicnoggin.collection {

/**
*
* @author Patrick Denny heythere@atomicnoggin.ca
*
*/
public class ObjectPool {
private var objType:Class;
private var props:Object = {};
private var pool:Array = [];
private var max:uint;
private var dyno:Boolean;
/**
* Constructor
*
* @param type The object class to use in the pool
*
* @param defaultProperties A simple name:value object that
* lists any property presets for objects taken from the pool.
*
* @param allowDynamic Whether or not to allow default properties that
* are not explicitly defined within the Class.
*
* @param initialCount Number of objects to pre-populate the pool with.
*
* @param maxCount Maximum number of objects allowed to populate the pool.
*/
public function ObjectPool(type:Class = null,
defaultProperties:Object = null,
allowDynamic:Boolean=false,
initialCount:uint=0,
maxCount:uint=uint.MAX_VALUE)
{
objType = (type is Class)?type:Object;
dyno = allowDynamic;
max = maxCount;
if ((defaultProperties is Object)) {
loadDefaultProperties(defaultProperties);
}
this.length = initialCount;
}

private function getNextObj(removeFromPool:Boolean=false):* {
//either grab the first object added or create a new one.
var obj = pool.length?pool[0]:(new objType());
if (removeFromPool) pool.shift();
return obj;
}

/**
* Get an object from the pool
*
* @param properties A simple name:value object that
* lists any instance specific properties to be set for
* the object taken from the pool. If allowDynamic is
* false, Properties not explicitly defined within the Class
* will be ignored.
*
* @return an instance of the Class specified in the constructor
*
*/
public function pop(properties:Object = null):* {
var newObj = getNextObj(true);
//make a copy of the instance specific properties provided (if any)
var newP:Object = (properties is Object?properties?{});
//track instance properties already set
var used:Object = {};
//iterate through the default properties
for (var p:String in props) {
if (dyno || newObj.hasOwnProperty(p)) {
//if an instance property name matches the default property
if (newP.hasOwnProperty(p)) {
// use the provided property
newObj[p] = newP[p];
// and add it to the used list
used[p] = true;
}
//otherwise use the default property
else {
newObj[p] = props[p];
}
}
}
//iterate through the remaining instance properties and apply them.
for (p in newP) {
if ((dyno || newObj.hasOwnProperty(p)) && !used[p]) {
newObj[p] = newP[p];
}
}
return newObj;
}
/**
* Return an object to the pool
*
* @param object The object being returned. Any object provided
* that is not an instance of the Class will be ignored.
*
*/
public function push(object:*):void {
if ((object is objType)) {
pool.push(object);
//this forces the max value check
this.length = pool.length;
}
else {
//should throw error
}
}
/**
* The Class used to create pool objects
*
* setting this property will also clear the pool.
*/
public function get poolClass():Class {
return objType;
}

public function set poolClass(value:Class):void {
this.length = 0;
objType = value;
}

public function loadDefaultProperties(defaultProperties:Object, clearPrevious:Boolean=true) {
if (clearPrevious) {
props = {};
}
var obj = getNextObj();
for (var p:* in defaultProperties) {
if (dyno || obj.hasOwnProperty(p)) {
props[String(p)] = defaultProperties[p];
}
}
}
/**
* Get the value of a default property
*
* @param name The property to be looked up.
*
* @return The default value of the named property.
* If the property hasn't been set in the pool's default
* property list, then 'undefined' will be returned instead.
*/
public function getDefaultProperty(name:String):* {
var obj = getNextObj();
if (dyno || obj.hasOwnProperty(name)) {
return props[name];
}
else {
return undefined
}
}
/**
* Set the value of a default property
*
* @param name The name of the property to be set.
*
* @param value The value to be used. If allowDynamic is
* false, Properties not explicitly defined within the Class
* will be ignored.
*/
public function setDefaultProperty(name:String,value:*):void {
var obj = getNextObj();
if (dyno || obj.hasOwnProperty(name)) {
props[name] = value;
}
else {
//should throw error
}
}
/**
* Remove a property from the default properties list
*
* @param name The name of the default property to remove.
*
*/
public function removeDefaultProperty(name:String):void {
delete props[name];
}
/**
* Whether or not dynamic properties will be allowed to
* be added to the pool's objects. Setting this to true
* may cause Errors.
*
* default is false
*/
public function get allowDynamic():Boolean {
return dyno;
}

public function set allowDynamic(value:Boolean):void {
dyno = value;
if(!dyno) {
//reload properties to clear out dynamic ones
loadDefaultProperties(Object(props));
}
}
/**
* The maximum number of objects allowed to populate the pool
*
* default is uint.MAX_VALUE
*/
public function get maxCount():uint {
return max;
}

public function set maxCount(value:uint):void {
max = value;
if (pool.length > max) {
this.length = max;
}
}

/**
* The number of objects currently in the pool.
*
* If length is set to a value less than what is currently available,
* any objects beyond the new value will be deleted. If set to a value
* greater than what is available, new objects will be created
* to pre-populate the pool to match the amount provided.
*
*/
public function get length():uint {
return pool.length;
}
/**
*
*/
public function set length(value:uint):void {
var v:uint = (value > max)?max:value;
if (v < pool.length) {
delete pool.splice(v);
}
else if (v > pool.length) {
for(var a:uint = v - pool.length;a--;) {
pool.push(new objType());
}
}
}
}
}

The simplest usage would be:

var myPool:ObjectPool = new ObjectPool(Sprite);

To create a Sprite pool.

And as a bonus, you can even attach a pool directly to an object to create a factory, as follows:

package {
import ca.atomicnoggin.collections.ObjectPool;

public class MyClass {

static private var _pool:ObjectPool;

static public function get pool():ObjectPool {
if (!(_pool is ObjectPool)) {
_pool = new ObjectPool(MyClass);
}
return _pool;
}

public function MyClass() {
//constuctor code
}
}
}
Tags: , ,
2010.12.01 01:06 PM | Permalink 0 Comments

Fun with static methods in Flash AS3 : controlling instances

Every once and a while there are times, especially when creating a public API, when you want to be able to hide settings and actions from plain view. Here's a fun little trick: using public static methods to control instances of a class. By creating internal interfaces, you can use static methods to control various aspects of a class that wouldn't be accessible through "normal means". Now, the following code is obviously an over simplified example, but it does show the concept. It shows how to access normally inaccessible properties, do extended actions during set up, or even simulate Constructor overloading.

package {

public class TestStatic {

private var _readOnly:boolean = false;

private var _name:String;
private var _color:String;


public function TestStatic(name:String,color:String) {
_name = name;
_color = color;
}

//secondary constructors
public static function BlueTestStatic(name:String):TestStatic {
return new StaticTest(name,'Blue');
}

//access advanced settings
public static function makeReadOnly(instance:TestStatic):void {
instance.readOnly = true;
}

//change 'read only' properties
public static function rename(instance:TestStatic,name:String):void {
instance.name = name;
}


public function get color():String {
return _color;
}

public function set color(value:String):void {
if(!_readOnly) {
_color = value;
}
else {
throw new ReferenceError("this property is read only");
}
}

public function get name():String {
return _name;
}

internal function set name(value:String):void {
if(!_readOnly) {
_name = value;
}
}

internal function set readOnly(value:Boolean):void {
_readOnly = value;
}
}
}


var ts:TestStatic = new TestStatic("Henry","Orange");
trace(tsN.name); //returns "Henry";
TestStatic.rename("Hank");
trace(tsN.name); //returns "Hank";
Tags: , ,
2009.08.19 10:26 PM | Permalink 0 Comments

Difference between using Object as associative array in Flash AS3 and JavaScript

As I was porting a Roman number converter (I will post it shortly) I had written in JavaScript to AS3 and ran into an interesting "quirk" of the AS3 Object class. I doesn't keep the keys in the same order that they were declared. The code used an Object literal as an associative array; holding each roman "digit" along with it's decimal equivalent in descending order by value. I'd then use a for ... in loop to cycle through the object. In JavaScript I'd get the values in the same order they had been generated. AS3 seems to be a little more cavalier. The following code in Javascript:

var o = {
"name1":1,
"name2":1,
"name3":1,
"name4":1,
"name5":1
};
var a=0;
for (var n in o) {
document.write(" o " + a + ":"+n);
a++;
}

produces this result in all major browsers (Firefox 3.5, IE 8, Safari 4, Opera 9.54, Chrome 2):
o 0:name1 o 1:name2
o 2:name3
o 3:name4
o 4:name5

However, using the following code in the Flash IDE

var o:Object = {
"name1":1,
"name2":1,
"name3":1,
"name4":1,
"name5":1
};
var a:uint = 0
for (var n:String in o) {
trace(" o " + a + ":"+n);
a++;
}

resulted in the following trace result:
o 0:name4
o 1:name5
o 2:name1
o 3:name2
o 4:name3

Oddly, it seemed to produce the exact same order each time I ran it. I tried it with a Dictionary, to see if the made a difference, but results were similar. I tried different values, to try and find a reason for the ordering -- to no avail. If someone knows why it's consistently picks the same random order, I'd love to know.

Tags: , , ,
2009.08.16 11:25 PM | Permalink 0 Comments

Flash AS3 Showdown: Object vs. Dictionary

I decided to see what the difference was between using a Dictionary and an Object to store simple name:value pairs, where name was always a String. Fired up the Flash IDE and typed out the following code:

import flash.sampler.getSize;
import flash.utils.Dictionary;
var a:uint;
var s:Date;
var find:Array = new Array()
var val:*;

trace('creating 20000 entries');
s = new Date();
var d:Dictionary = new Dictionary();
for (a = 0;a<20000;a++) {
d[String('name'+a)] = a;
}
trace('Dictionary creation: '
+ (new Date().valueOf() - s.valueOf()) + 'ms'
+ ', size: ' + getSize(d) + ' bytes');
s = new Date();
var o:Object = {};
for (a = 0;a<20000;a++) {
o[String('name'+a)] = a;
}
trace('Object creation: '
+ (new Date().valueOf() - s.valueOf()) + 'ms'
+ ', size: ' + getSize(o) + ' bytes');

for (a = 1;a <1000;a++) {
find.push(20000/a);
}
trace('reading 1000 keys');

s = new Date();
for (a = 0;a<1000;a++) {
val = d['name' + find[a]];
}
trace('Dictionary read: '
+ (new Date().valueOf() - s.valueOf()) + 'ms');

s = new Date();
for (a = 0;a<1000;a++) {
val = o['name' + find[a]];
}
trace('Object read: '
+ (new Date().valueOf() - s.valueOf()) + 'ms');


trace('search for 1000 values');
s = new Date();
for (a = 0;a<1000;a++) {
for each(val in d) {
if (val == find[a]) break;
}
}
trace('Dictionary search: '
+ (new Date().valueOf() - s.valueOf()) + 'ms');

s = new Date();
for (a = 0;a<1000;a++) {
for each(val in o) {
if (val == find[a]) break;
}
}
trace('Object search: '
+ (new Date().valueOf() - s.valueOf()) + 'ms');

Which produced the following trace results:

creating 20000 entries
Dictionary creation: 47ms, size: 160032 bytes
Object creation: 47ms, size: 160024 bytes

So, same speed, and Object is a tiny bit smaller. In multiple runs this was almost always the same value - often enough to be a reliable value.

reading 1000 keys
Dictionary read: 16ms
Object read: 16ms

Again, for direct key reads, the two are same speed. In multiple runs this was always the same value.

search for 1000 values
Dictionary search: 2047ms
Object search: 2219ms

Now here, Object falls down bit. The interesting thing is that, despite having the same values created for the search each time, the search ms were all over the map. Each time I fired this, I got a different value. I'm assuming this much churn was causing the garbage collector to kick in sporadically. That said, the difference between the values would vary from 100 to 400 ms. The one constant though, was that Object searches were almost always slower than Dictionary searches, and the instances where Object was faster were few and far between. However, the time was always just over 2000ms for each. So, the average speed per search was still aprox. 2-2.5ms per search for both Object and Dictionary.

Conclusion: unless you really need to use an strict type checking for your keys, then there's no discernible difference between the two, except that using Object saves you an import.

Tags: , ,
2009.08.14 11:14 PM | Permalink 0 Comments