Apply a Function Dynamically in AS3 - actionscript-3

I'm trying to set up some default callbacks for listeners dynamically, and I'm not having much success at the moment.
Array:
var URLLoader_SharedCallbacks:Array = new Array();
URLLoader_SharedCallbacks.push(new Array (HTTPStatusEvent, HTTPStatusEvent.HTTP_STATUS, "URLLoader_HTTPStatus"));
URLLoader_SharedCallbacks.push(new Array (IOErrorEvent, IOErrorEvent.IO_ERROR, "URLLoader_IOError"));
URLLoader_SharedCallbacks.push(new Array (Event, Event.OPEN, "URLLoader_Open"));
And then:
function URLLoader_SharedCallbacks_Add(ul:URLLoader):void
{
for each(var arr:Array in this.URLLoader_SharedCallbacks)
{
var fnc:Function = function(evt:arr[0])
{
trace("evt = " + evt)
}
if(!this[ul].hasEventListener(arr[2]))
{
this[ul].addEventListener(fnc);
}
}
}
Suggestions?

even if you need to get anything very specific from this events imho the best choice is to the following:
private function handler(e:Event): void{
switch(e.type){
case IOErrorEvent.IO_ERROR:
//treat it like IOErrorEvent
break;
case Event.CLOSE:
//treat it like Event.CLOSE
break;
case HTTPStatusEvent.HTTP_STATUS:
//treat it like HTTPStatusEvent
break;
}
}
and about dynamical generation, if it's really the only solution:
create 2 arrays - for dispatchers and for listener functions. and one more to hold descriptions of dispatcher-listener pairs. or you may store dispatcher with listener and description in an Object and have an array of such objects, or maybe develop a specific data structure...
anyway:
private var funcArray: Array = new Array();
private var loaderArray: Array = new Array();
private var infoArray: Array = new Array();
private function createListener():Function {
var fn:Function = function(e:Event): void { switch((e as Event).type) { case IOErrorEvent.IO_ERROR: /*treat it like IOErrorEvent*/ break; case Event.CLOSE:/*treat it like Event.CLOSE*/ break; case HTTPStatusEvent.HTTP_STATUS: /*treat it like HTTPStatusEvent*/ break; }};
return fn;
}
private function createURLLoader(url: String, description: String = 'i never care'):void{
var urlo:URLLoader = new URLLoader();
var fun: Function = createListener();
urlo.addEventListener(IOErrorEvent.IO_ERROR, fun);
urlo.addEventListener(Event.CLOSE, fun);
urlo.addEventListener(HTTPStatusEvent.HTTP_STATUS, fun);
var info: Object = { 'url' : url , 'description' : description );
urlo.load(new URLRequest(info['url']))
funcArray.push(fun);
loaderArray.push(urlo);
infoArray.push(info);//mention that arrays have always equal length and dispatcher, listener and descrition are stored under the same index
}
/*when data is loaded/loading timeout is over/loading failed: we need to be careful
killing our anonimous vars: */
private function killEmAll(i:int):void{//i is the index for arrays
(loaderArray[i] as URLLoader).removeEventListener(IOErrorEvent.IO_ERROR, (funcArray[i] as Function));
(loaderArray[i] as URLLoader).removeEventListener(Event.CLOSE, (funcArray[i] as Function));
(loaderArray[i] as URLLoader).removeEventListener(HTTPStatusEvent.HTTP_STATUS, (funcArray[i] as Function));
(loaderArray[i] as URLLoader).close();//just to be sure ;)
loaderArray.splice(i, 1);
funcArray.splice(i, 1);
infoArray.splice(i, 1);
}

Related

How to deserialize array collection in flex

I am stuck with an issue:
In which I am serializing an array collection to a file, the array collection has two type of items an Image and a class object.
It serialize successfully, but when I de-serialize it, It simply returns array collection of objects and I found my self unable to convert these objects into Images and the class object(this class is defined by myself and having three members, two images and a shape).
I am adding the code which I used:
[Bindable] var objcnvs:ClassCanvas;
protected function btnSave_clickHandler(event:MouseEvent):void
{
//for saving data serializaion
var arrAllSave:ArrayCollection = new ArrayCollection();
for(var i:int = 0;i<arrAll.length; i++)
{
try
{
var tempCon:ConnectImage = arrAll[i];
arrAllSave.addItem({item:arrAll[i], type:"ConnectImage" });
}
catch(er:Error)
{
var tempImage:Image = arrAll[i];
var objImage:ClassImage = new ClassImage(arrAll[i]);
arrAllSave.addItem({item:objImage, type:"Image" });
}
}
// First, generate your ByteArray from the VO.
var byteArray : ByteArray = new ByteArray();
byteArray.writeObject( arrAll );
// Resolve your file location.
//var file : File = File.applicationStorageDirectory.resolvePath( "testFile.ri" );
var mapName:String = txtMapTitle.text;
var file : File = File.applicationStorageDirectory.resolvePath( 'Saved Maps/'+mapName+'.imm' );
if(file.exists == true)
{
lblWarn.text = "Map already Exists. Please enter Defferent Map Title";
}
else if(mapName == "")
{
lblWarn.text = "Please enter a title for Map.";
}
else
{
var fileStream:FileStream = new FileStream();
// Save the file to the given location.
fileStream.open(file, FileMode.WRITE);
fileStream.writeBytes( byteArray );
fileStream.close();
lblWarn.text = "Map Saved successfully";
}
}
protected function btnLoadMap_clickHandler(event:MouseEvent):void
{
// Execute the file load.
var loadFileName:String = "t1";
var request : URLRequest = new URLRequest ( "app-storage:/"+"Saved Maps/"+loadFileName+".imm" );
var receptor : URLLoader = new URLLoader( request );
// Make sure our content is interpreted as a ByteArray.
receptor.dataFormat = URLLoaderDataFormat.BINARY;
receptor.addEventListener( Event.COMPLETE, fileLoadedHandler );
}
private function fileLoadedHandler ( event : Event ) : void
{
// Retrieve the event target, cast as the URLLoader we just created
var loader : URLLoader = event.target as URLLoader;
// Retrieve the loaded data. We know it's a ByteArray, so let's cast it as well.
var data : ByteArray = loader.data as ByteArray;
// Use the ByteArray.readObject method to reconstruct the object.
var obj : Object = data.readObject();
// Cast the object and assign it to our data container.
var loadArrAll:ArrayCollection = obj as ArrayCollection;
}
The last line
var loadArrAll:ArrayCollection = obj as ArrayCollection;
this is the issue. I get the array collection but it has only a list of objects no images and no my class objects (although these are there but I found no way to convert this array collection in previous form when I serialized it.)
class that needs to be serialized/deserialised needs to implement flash.utils.IExternalizable.
public function readExternal(input:IDataInput):void - here you restore object
public function writeExternal(output:IDataOutput):void - here you saving object
best regards
Zain, you have to declare your classes like that:
package com
{
[RemoteClass]
public class myClass
{
public function myClass()
{
}
}
}

AS3 Creating an array of objects

I would like to add a bunch of cars to the stage, and store them in an array as objects. The problem is I hate using external AS files and would like to keep it as simple as possible.
I tried doing :
var car:Object = {carcolor:String,carscale:Number,carpower:Number};
var test:Array = new Array()
for (var i:Number=0; i<10; i++) {
test.push(car)
}
The problem is if I try to set a value of one object in the like
test[1].carscale = 5
Every object in the array gets their attribute carscale set to 5.
Is there any way I can do this without using external class files?
While you should use external AS files (its a good practice), here's the reason why you are having the issue, and I'm going to explain line-by-line
var car:Object = {carcolor:String,carscale:Number,carpower:Number};
//This creates an object called car. Suppose it saves it in memory at "location" 0x12345
var test:Array = new Array();
//This creates an empty array
for (var i:Number=0; i<10; i++) {
test.push(car);
//This adds the object "car" to the array
//Since Object is a reference type, its memory location is actually added to the array
//This means you added 0x12345 to the array (10 times over the loop)
}
//The array now contains
[0x12345, 0x12345, 0x12345, .......];
//So now
test[1]; //returns the object at 0x12345
test[1].carscale=5; //sets the carscale property of the object at 0x12345
Since all objects in the array point to the same location, getting any of them will actually return the same object. This means that all of them will show carscale as 5
A solution to this would be:
var test:Array = new Array();
for (var i:Number=0; i<10; i++) {
var car:Object = {carcolor:String,carscale:Number,carpower:Number};
test.push(car);
}
A better, REAL Object oriented solution would be to create a class called Car and then instead of doing
var car:Object = {carcolor:String,carscale:Number,carpower:Number};
you use
var car:Car = new Car();
The Car.as class would be like this:
public class Car {
public function Car() {
//this is the constructor, initialize the object here
//Suppose the default values of the car are as follows:
carcolor="red";
carscale=5;
carpower=1000;
}
public var carcolor:String;
public var carscale:Number, carpower:Number;
}
In fact, you could even use another constructor that automatically sets the properties based on arguments:
public function Car(_color:String, _scale:Number, _power:Number) {
carcolor=_color;
carscale=_scale;
carpower=_power;
}
and call it as
var car:Car=new Car("red", 5, 1000);
In fact, the car before carcolor, carscale and carpower is not even necessary because it is obvious when you put them in a class called Car.
Like TheDarkIn1978 said you're pushing a reference of your car instance into your array. When you change the value of one instance's property the same happens for each reference.
The simple answer is to create a new object upon each interation of your for loop like in the following:
var test:Array = [];
for (var i:Number = 0; i < 10; i++)
{
var car:Object = {carcolor:String, carscale:Number, carpower:Number};
test.push(car);
}// end for
[UPDATE]
I know you said that you didn't want to use "external classes" but there are advantages to using a custom class object to store values as opposed to a Object object. Here is an example:
package
{
import flash.display.Sprite;
import flash.events.Event;
public class Main extends Sprite
{
public function Main():void
{
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}// end function
private function init(e:Event = null):void
{
removeEventListener(Event.ADDED_TO_STAGE, init);
var cars:Vector.<Car> = new Vector.<Car>();
cars.push(new Car("red", 1, 1));
cars.push(new Car("blue", 2, 2));
cars.push(new Car("green", 3, 3));
trace(cars[2].color); // output: green
}// end function
}// class
}// end package
internal class Car
{
private var _color:String;
private var _scale:Number;
private var _power:Number;
public function get color():String { return color; }
public function get scale():String { return scale; }
public function get power():String { return power; }
public function Car(color:String, scale:Number, power:Number)
{
_color = color;
_scale = scale;
_power = power;
}// end function
}// end class
This is a good example of creating an object for the sole purpose of storing values that never change by only allowing the object's properties to be set upon initiation and using getter methods to make the values read only.
I feel dumb, I found the answer here :
http://board.flashkit.com/board/showthread.php?t=792345
You're pushing the Object reference to the array, not a unique Object each time. You have to do something like:
for(var temp=0;temp<100;temp++){
var roomData:Object=new Object;
roomData.first_time=true;
rooms.push(roomData);
}
you're adding the same object to the array multiple times. you need to create new instances of your car object.
EDIT:
although it would be a best practice to create your own "Car" class and create new instances of it, even if it's only a small object with 3 properties, here's a quick example that should get you started.
package
{
//Imports
import flash.display.Sprite;
//Class
public class Main extends Sprite
{
//Constants
private static const DEFAULT_CAR_COLOR:Number = 0x000000;
private static const DEFAULT_CAR_SCALE:Number = 1.0;
private static const DEFAULT_CAR_POWER:int = 50;
//Properties
private var carsArray:Array;
//Constructor
public function Main():void
{
init();
outputCarColors();
}
//Initialize
private function init():void
{
carsArray = new Array();
for (var i:int = 0; i < 10; i++)
{
carsArray.push(CreateCar(Math.random() * 0xFFFFFF));
}
}
//Output Car Colors
private function outputCarColors():void
{
for (var i:int = 0; i < carsArray.length; i++)
{
trace("Color of car " + i + " : " + carsArray[i].carColor);
}
}
//Create Car Object
private function CreateCar(carColor:Number = DEFAULT_CAR_COLOR, carScale:Number = DEFAULT_CAR_SCALE, carPower:int = DEFAULT_CAR_POWER):Object
{
var result:Object = new Object();
result.carColor = carColor;
result.carScale = carScale;
result.carPower = carPower;
return result;
}
}
}

as3 error loading "good" "bad" array

I am creating a "good" "bad" array to display on screen which i can later use simple if statements to do something upon if the player has collided with the "good" "bad" objects.
I cant get the objects to randomly generate on screen with the following code.
// Create and Set good/bad random Word objects
public function newObject(e:Event)
{
var goodObjects:Array = ["WordObject1"];
var badObjects:Array = ["WordObject2"];
if (Math.random() < .5)
{
var r:int = Math.floor(Math.random()*goodObjects.length);
var classRef:Class = getDefinitionByName(goodObjects[r]) as Class;
var newObject:MovieClip = new classRef();
newObject.typestr = "good";
} else
{
r = Math.floor(Math.random()*badObjects.length);
classRef = getDefinitionByName(badObjects[r]) as Class;
newObject = new classRef();
newObject.typestr = "bad";
}
newObject.x = Math.random();
newObject.y = Math.random();
addChild(newObject);
objects.push(newObject);
placeWords();
}
// create random Word objects
public function placeWords() {
objects = new Array();
for(var i:int=0;i<numWordObjects;i++) {
// loop forever
while (true) {
// random location
var x:Number = Math.floor(Math.random()*mapRect.width)+mapRect.x;
var y:Number = Math.floor(Math.random()*mapRect.height)+mapRect.y;
// check all blocks to see if it is over any
var isOnBlock:Boolean = false;
for(var j:int=0;j<blocks.length;j++) {
if (blocks[j].hitTestPoint(x+gamesprite.x,y+gamesprite.y)) {
isOnBlock = true;
break;
}
}
// not over any, so use location
if (!isOnBlock) {
newObject.x = x;
newObject.y = y;
newObject.gotoAndStop(Math.floor(Math.random()*1)+1);
gamesprite.addChild(newObject);
objects.splice(newObject);
break;
}
}
}
}
i get the following errors:
1119: Access of possibly undefined property x through a reference with static type Function.
1119: Access of possibly undefined property y through a reference with static type Function.
1067: Implicit coercion of a value of type Function to an unrelated type flash.display:DisplayObject.
Try renaming var x and var y to something else. Those are public properties in any class that extends as a display object (Sprite/MovieClip/Shape).

Making Timers available for GC?

In a function that creates a new timer every time it is called, is this the correct way to dispose of it?
private var _timers:Vector.<Timer> = new Vector.<Timer>;
private var _timer:Timer
private function timer():void
{
_timer = new Timer(10000, 1);
_timers.push(_timer);
_timer.addEventListener(TimerEvent.TIMER_COMPLETE, onTimer, false,0,true);
_timer.start();
}
private function onTimer(e:TimerEvent):void
{
e.target.removeEventListener(TimerEvent.TIMER_COMPLETE, onTimer);
_timers[0] = null;
_timers.shift();
}
Maybe you can add a
_timers[0].stop();
Just in case your times change for whatever reason, it's best to deal with the object directly instead of assuming the Timer object is at the 0 index.
Just a small adjustment required for onTimer():
private function onTimer(e:TimerEvent):void
{
var timer:Timer = e.currentTarget as Timer;
timer.removeEventListener(TimerEvent.TIMER_COMPLETE, onTimer);
_timers.splice(_timers.indexOf(timer), 1);
}
You don't need to set the index to null and it's never good practice to assume your item will be at a specified index, always make sure. In this case you make sure by using the indexOf() method available in the Array type object (_timers).
Hope it helps.
I've made function "delay" that handles delay calls. I made it for my game in which I have used delay calls alot and I had to find a way to handle delays efficiently. Still AS3 guru's out there may still find more efficient ways, please let know if any.
public static var timer_stack:Vector.<Timer> = new Vector.<Timer>();
public static function delay(delaytime:Number, func_name:Function, repeat:Number = 1)
{
var timer:Timer = new Timer(delaytime, repeat);
timer_stack.push(timer);
timer_stack[timer_stack.length-1].addEventListener(TimerEvent.TIMER, func_name ,false,0,true);
timer_stack[timer_stack.length-1].addEventListener(TimerEvent.TIMER_COMPLETE,
function(e:TimerEvent){ delay_complete(e, func_name); });
timer_stack[timer_stack.length-1].start();
}
public static function delay_complete(e, func_name:Functio):void
{
e.target.stop();
e.target.removeEventListener(TimerEvent.TIMER, func_name);
timer_stack[timer_stack.length-1].removeEventListener(TimerEvent.TIMER_COMPLETE,
function(){ func_name_complete(e, func_name);} );
for(var i=0; i < timer_stack.length; i++)
{
if(timer_stack[i].running == true)
trace("timer # "+i+" is running");
if(timer_stack[i].running == false)
{
timer_stack[i] = null;
timer_stack.splice(i,1);
trace("remove timer # "+i);
}
}
}

as3 operator= /(object assign )

I know that AS3 does not have pointer or reference. Every object is pass by reference already. (I supposed?)
What should I do if I want to do with pointer?
e.g. all object point to one target, I only need to change target's value, then all other object will access different value.
You can effectively get the same behavior by using a helper object to simulate a pointer -- in other words using it to carry the target reference. For instance:
public class PseudoPointer
{
private var obj:Object;
private var prop:String;
public function PseudoPointer(obj:Object, prop:String)
{
// Point to property with name 'prop' on object 'obj'.
this.obj = obj;
this.prop = prop;
}
public function get value():* {
return obj[prop];
}
public function set value(value:*):void {
obj[prop] = value;
}
}
Then you could do this -- assume there's a magicNumber property on an object named target:
var numberPtr = new PseudoPointer(target, "magicNumber");
myDynamicObjectA.numberPtr = numberPtr;
myDynamicObjectB.numberPtr = numberPtr;
myDynamicObjectC.numberPtr = numberPtr;
Now any object that has a reference to the pseudo-pointer can read and write the target property:
numberPtr.value = 42;
You could create a function and in which you give it the value and then subsequently assign it to all of those other variables.
Something like below:
var objectA:Number;
var objectB:Number;
...
function myFunction(newValue:Number):void
{
objectA = newValue;
objectB = newValue;
...
}
You could try setting a variable reference to a function. Then if you update that reference, it would return a different function.
var myFunc:Function;
function funcOne():int {
return 1;
}
function funcTwo():int {
return 2;
}
function getFunc():Function {
return myFunc;
}
myFunc = funcOne;
var myObjOne:Object = new Object();
myObjOne.objFunc = getFunc;
var myObjTwo:Object = new Object();
myObjTwo.objFunc = getFunc;
trace(myObjOne.objFunc.call().call()); // 1
trace(myObjTwo.objFunc.call().call()); // 1
myFunc = funcTwo;
trace(myObjOne.objFunc.call().call()); // 2
trace(myObjTwo.objFunc.call().call()); // 2
This allows any object to point at a single function and have what that returns be updateable for all of them simultaneously. It's not the prettiest code and it's not as type-safe as if ActionScript had delegates to enforce the signatures of what's set for myFunc, but it is still a pretty flexible model if you get creative with it.
Have those pointers point to something that contains the object or has the object as a property on it.
So
var myArr:Array = [new YourObject()];
var client1:ArrayClient = new ArrayClient();
client1.array = myArr;
var client2:ArrayClient = new ArrayClient();
client2.array = myArr;
myArr[0] = new YourObject();
//client1.array[0]==client2.array[0]==the new object
However, this seems to be a great way to get yourself into a lot of trouble really quickly. What's the use case?