addEventListeners and best practices - actionscript-3

I'm using this simple code to retrieve a value from a PHP script:
package
{
import flash.display.MovieClip;
import flash.net.URLLoader;
import flash.net.URLVariables;
import flash.net.URLRequest;
import flash.net.URLRequestMethod;
import flash.events.Event;
import flash.events.IOErrorEvent;
//
public class URLTest extends MovieClip
{
//
var scriptLoader:URLLoader;
//
public function URLTest()
{
scriptLoader = new URLLoader();
scriptLoader.addEventListener(Event.COMPLETE, handleLoadSuccessful);
scriptLoader.addEventListener(IOErrorEvent.IO_ERROR, handleLoadError);
}
//;
public function handleLoadSuccessful($evt:Event):void
{
trace("Message sent.");
trace($evt.target.data);
}
//
public function handleLoadError($evt:IOErrorEvent):void
{
trace("Message failed.");
}
//
public function sendLoad(variable):void
{
var scriptRequest:URLRequest = new URLRequest("http://mydomain/myapp/my.php");
var scriptVars:URLVariables = new URLVariables();
scriptVars.var1 = variable;
scriptRequest.method = URLRequestMethod.POST;
scriptRequest.data = scriptVars;
scriptLoader.load(scriptRequest);
trace(scriptLoader);
}
}
}
If you look at the URLTest constructor you'll find I initialize the listeners of an instance of URLLoader.
Is it a better practice to declare a method initListeners and call it everytime I call sendAndLoad? As a consequence, would it be a better practice that of removing the listeners inside the handleLoadError and handleLoadSuccesful methods? Are listeners cpu-intensive?
Btw, I don't understand why this question "appears to be subjective", since we're talking about performances, not programming style.

As your class is designed to be able to make multiple calls to the same webservice, I would definitely go for option 2. Consider this:
var loader:URLTest = new URLTest();
loader.sendLoad("a");
loader.sendLoad("b");
Now, if you re-use the same URLLoader for both calls, there is no way you can know which COMPLETE event is going to correspond to which call.
To solve this issue you would create a URLLoader (+ event listeners) for each call and put it in an Array to be able to identify it. When an answer comes from the server - be it a COMPLETE or an IOERROR - you can remove the event listeners and remove the identified URLLoader (the one that matches event.currentTarget) from the Array.
The performance issue is trivial here. There will be no performance hit for creating a bunch of event listeners as long as you remember to remove them when you're done with them. Otherwise the corresponding URLLoaders will not be garbage collected and will keep piling up in memory.
On a side note: there's absolutely no reason to extend from MovieClip. If you want your class to dispatch events, just extend EventDispatcher.

Related

do I really need to have an event handler for URLLoader?

I am new to actionscript, and I am following a tutorial by Joseph Devore. I understand that when I want to do a network query I can create a URLRequest, and send this with a URLLoader. I expected to find the response data in the URLLoader.data attribute, but after making the query this was still null in my case. Then it seemed that I must create an event handling to actually access the response. Is this true?
I wrote this small example code, and wonder how I (the easiest way) can access the response in this get request.
package
{
import flash.events.Event;
import flash.net.URLLoader;
import flash.net.URLRequest;
public class stackQuery
{
public function stackQuery()
{
}
public function makeQuery(): String {
var request: URLRequest = URLRequest("http://www.google.com");
var loader: URLLoader = new URLLoader();
loader.addEventListener(Event.COMPLETE, handleResponse);
try{
loader.load(request);
}
if(loader.data == null){
return "Empty"
}
return "data not empty"
}
private function handleResponse(event:Event):void
{
//something with event.target.data here??
}
}
}
There isn't any nice way to do a synchronous load (like you can in some other languages) with AS3. Your data is null because in AS3 the load command is asynchronous - meaning it doesn't halt the rest of the application (locking it up) until the load completes, it moves on to the next line of code and dispatches an event once the load is complete.
Here is a code example of how you would want to handle this based off of the code you supplied:
package
{
import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.events.SecurityErrorEvent;
import flash.net.URLLoader;
import flash.net.URLRequest;
public class stackQuery
{
private var loader: URLLoader = new URLLoader(); //let's move it out to the class level so you can easily reference it from other methods (though this is purely optional)
public function stackQuery()
{
}
public function makeQuery(): String {
var request:URLRequest = URLRequest("http://www.google.com");
//add you listeners before loading
loader.addEventListener(Event.COMPLETE, handleResponse);
loader.addEventListener(IOErrorEvent.IO_ERROR, handleResponse); //it's good practice to also listen for errors
loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, handleResponse); //this kind of error will happen if you're trying to load a local resource but have published as network only (or vice-versa)
//load the request, any code after this will run before the load completes.
loader.load(request);
}
//this function will run once the load completes
private function handleResponse(event:Event):void
{
if (event is IOErrorEvent) {
//you had an IO error, better do something
return;
}
if (event is SecurityErrorEvent) {
//url wasn't allowed to load
return;
}
trace(loader.data); //this is what was returned from the page
//you could also get it by referencing the currentTarget of the event
trace(URLLoader(event.currentTarget).data);
}
}
}
Yes, of course it is true. Imagine you load one megabyte of data. You need time in order to load it, right? So there's no way that you will have this data on the very next line of code.
The listener will get executed when the target dispatches the event - in your case when that megabyte is loaded.
This is a normal behavior.
While the answers given are correct, the real explanation is that Actionscript 3 is an asynchronous language as opposed to other language like Python for example that are synchronous. In python after loading something like an url the next line of code will not be executed until the url is loaded (which can create lag or idling execution), in Actionscript 3 the code execution doesn't wait and move on to the next line. That is the reason you need to use events (Actionscript 3 is an event based language) so you can find out when the data you are waiting for is available all that while your code is still executing.

Actionscript 3.0 method keeps repeating, cant figure out why

I'm having difficulty debugging my audio slider. I'm pretty sure my problems lies in the fact that one of my methods, changeVolumeRedFireball is just constantly repeating at a very fast rate. I get a glitchy sound every once in a while in my game, so it seems to correlate. I traced "output" inside the method and quickly found out it's repeating at a high rate.
Problem is, I cannot figure out WHERE this is coming from! One other note. This only starts repeating once I hold down my slider, hence activating the changeVolumeRedFireball from dragSliderRedFireball
I do have other methods from other classes referencing methods in this class. They only access playSoundRedFireball and stopSoundRedFireball though, so I don't see why that would have any effect. Also, this class is instantiated by my document class upon start up of the game. I suppose I'll put in the relevant code from the document class if requested, but I just didn't think it would affect this problem at all.
package {
import flash.display.Sprite;
import flash.display.Graphics;
import flash.events.MouseEvent;
import flash.events.Event;
import flash.net.URLRequest;
import flash.media.Sound;
import flash.media.SoundChannel;
import flash.media.SoundTransform;
import flash.geom.Rectangle;
public class VolumeRedFireball extends Sprite {
public var redFireballSnd:Sound = new Sound();
public var redFireballChannel:SoundChannel = new SoundChannel();
//URLRequest=new URLRequest("solitude.wav");
//Make sure you pass URLRequest an audio file on your computer.
public var reqRedFireball:EnemyAppearSound = new EnemyAppearSound();
public var boundaryRedFireball:Rectangle;
public var spriteRedFireball:Sprite;
public var sliderRedFireball:Sprite;
public var xPosRedFireball:Number;
public var yPosRedFireball:Number;
public static var volRedFireball:Number = 1;
public function VolumeRedFireball() {
this.addEventListener(Event.ADDED_TO_STAGE, onStageRedFireball,false,0,true);
volRedFireball=1;
redFireballChannel.soundTransform=new SoundTransform(volRedFireball)
}
public function onStageRedFireball(e:Event):void
{
//We remove it immediately so that it doesn't get called multiple times
//As the instance is added to the display list tree
this.removeEventListener(Event.ADDED_TO_STAGE, onStageRedFireball);
xPosRedFireball = 320;
yPosRedFireball = 170;
initRedFireball();
}
public function initRedFireball():void {
spriteRedFireball = new Sprite();
redFireballChannel.stop();
spriteRedFireball.graphics.beginFill(0x999999);
spriteRedFireball.graphics.drawRect(xPosRedFireball,yPosRedFireball,100,5);
spriteRedFireball.graphics.endFill();
addChild(spriteRedFireball);
spriteRedFireball.x-=spriteRedFireball.width/2;
sliderRedFireball = new Sprite();
sliderRedFireball.graphics.beginFill(0xFF0000);
sliderRedFireball.graphics.drawCircle(xPosRedFireball+50,yPosRedFireball, 15);
sliderRedFireball.graphics.endFill();
addChild(sliderRedFireball);
sliderRedFireball.addEventListener(MouseEvent.MOUSE_DOWN, dragsliderRedFireball);
stage.addEventListener(MouseEvent.MOUSE_UP, stopsliderRedFireball);
boundaryRedFireball=new Rectangle(-100,0,100,0);
}
public function dragsliderRedFireball(event:MouseEvent):void {
sliderRedFireball.startDrag(false,boundaryRedFireball);
sliderRedFireball.removeEventListener(MouseEvent.CLICK, dragsliderRedFireball);
sliderRedFireball.addEventListener(Event.ENTER_FRAME, changeVolumeRedFireball);
}
public function stopsliderRedFireball(event:MouseEvent):void {
sliderRedFireball.stopDrag();
sliderRedFireball.removeEventListener(MouseEvent.MOUSE_UP, stopsliderRedFireball);
}
public function changeVolumeRedFireball(event:Event):void {
volRedFireball=1+Math.round(sliderRedFireball.x)/100;
redFireballChannel.soundTransform=new SoundTransform(volRedFireball);
trace("output");
}
public function playSoundRedFireball():void
{
redFireballChannel = reqRedFireball.play();
}
public function stopSoundRedFireball():void
{
redFireballChannel.stop();
}
}
}
Changing a SoundTransform during every frame isn't good, as you are essentially undermining the audio channel. It's better if you use MouseEvent.MOUSE_MOVE to trigger volume change, as if mouse is moved, and volume slider is being dragged, then the SWF user apparently wants the volume to change. But if a user starts dragging the slider but does not move it, why changing the volume?
public function dragsliderRedFireball(event:MouseEvent):void {
sliderRedFireball.startDrag(false,boundaryRedFireball);
sliderRedFireball.removeEventListener(MouseEvent.MOUSE_DOWN, dragsliderRedFireball);
sliderRedFireball.addEventListener(MouseEvent.MOUSE_MOVE, changeVolumeRedFireball);
sliderRedFireball.removeEventListener(MouseEvent.MOUSE_UP, stopsliderRedFireball);
}
public function stopsliderRedFireball(event:MouseEvent):void {
sliderRedFireball.stopDrag();
sliderRedFireball.removeEventListener(MouseEvent.MOUSE_UP, stopsliderRedFireball);
sliderRedFireball.removeEventListener(MouseEvent.MOUSE_MOVE, changeVolumeRedFireball);
sliderRedFireball.addEventListener(MouseEvent.MOUSE_DOWN, dragsliderRedFireball);
}
Also, you have messed up your listeners. First, you are not removing the enterframe listener after you stop dragging the fireball. Second, you are not adding a start-drag listener back after the fireball has been released. And third, in your initRedFireball you are adding stopsliderRedFireball as listener to stage, for a really strange reason, but you are attempting to remove it from sliderRedFireball. Please pay precise attention on where your listeners go, what do they listen and where do you remove them and from which objects. Misuse of an enterframe listener can build up pretty quickly, and spoil you all the fun.

How can I test to see if a variable has been garbage collected?

I'm trying to set up a test that will tell me whether a variable exists in memory or not. I'm running into the problem of my nested function preserving the local variable it uses, called "shouldBeDead". Here's my best effort, which doesn't work for me because the "shouldBeDead" variable is still alive:
addEventListener(Event.ENTER_FRAME, isDeadYet);
function isDeadYet ($):void {
var shouldBeDead = "not dead";
if (!stage.hasEventListener(KeyboardEvent.KEY_DOWN))
stage.addEventListener(KeyboardEvent.KEY_DOWN, test);
function test($):void {
trace("variable is " + shouldBeDead); // outputs: "variable is not dead"
}
}
Is there a way to test if something exists in memory?
Your String cannot be garbaged collected as there is no new instance created but the value is taken from the constant pool that take care of String, Number, int,etc..
If you create a new Class, Object, Array, etc... these can be garbaged collected and you can trace them with a simple method : putting your instance as a weak key into a dictionnary.
So when garbaged collect occured the key will be deleted from the dictionnary.
Here an example of code for testing, and there live code : http://wonderfl.net/c/uP5T :
import flash.utils.Dictionary;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.system.System;
var notGC:Dictionary=new Dictionary(true)
stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown)
function traceNotGC():void{
var cnt:int=0
for (var key:Object in notGC) {
cnt++
trace("not garbaged : " + key)
}
if (cnt==0) trace("All garbaged")
}
function onKeyDown(e:Event):void{
System.gc()
traceNotGC()
}
function test():void{
var str:String="not dead" // string taken from the constant pool
// there is no allocation done
var obj:Object={foo:"bar"} // creation of a new object that can be garbaged
var arr:Array=[0,1,2] // creation of a new array that can be garbaged
notGC[str]=true
notGC[obj]=true
notGC[arr]=true
traceNotGC()
}
test()
Simple technique, similar to Patrick's:
Test with useWeakReferences to See When Object is Garbage Collected
Instantiate custom class containing event handler listening to 'ENTER_FRAME' (use weak reference).
Instance's handler traces output until the instance reference is set to null from the instantiating object.
package {
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
public class Main extends Sprite {
private var livingObject:LivingObject;
public function Main () {
livingObject = new LivingObject(this, true);
stage.addEventListener(MouseEvent.CLICK, killIt);
}
function killIt ($) {
livingObject = null;
trace('Attempted murder of livingObject.');
}
}
}
import flash.events.Event;
class LivingObject {
public function LivingObject ($main:Main, $immortal:Boolean) {
$main.addEventListener(Event.ENTER_FRAME, proveImAlive, false, 0, $immortal ? false : true);
}
private function proveImAlive ($) {
trace(this + ' LIVES!!!!!'); // Output stops when garbage collected.
}
}
Well first shouldBeDead is not out of scope in the code you listed. the output "variable is not dead" is it's correct state.
In AS3 it is correct for your nested function to preserve that variable.
for your other question.
If the var points at an object you can always check it for object == null (if the garbage collector has gotten it it will be null)
There is something else but I can't remember.
Here is a very clear answer on the scope of that nested function.
Nested Functions, how are they garbage collected in flash actionscript 3?
I don't believe there is a way to do this (or I'm at least not aware of it), because garbage collection is so arbitrary. Here is a good article about the flash player garbage collection. http://www.adobe.com/devnet/flashplayer/articles/garbage_collection.html

AS3 driving me nuts

Ok here is what I am currently trying to do. I have a class called vdata.as which takes 2 paramaters both are strings sent from the main stage. Parameter one is the location for an XML file that I need to open and read. The second parameter is the name of the video I am currently looking for.
Now I can get the data from the XML file and display it with out any issue if its called from my class but when I try to access any of it from the stage I get undefined.
import flash.net.*;
import flash.display.*;
import flash.events.*;
public class videoData
{
private var mName:String;
private var mLink:String;
private var mCategory:String;
public static var elementArray:Array;
// Constructor
public function videoData(xmlPath:String,xmlVidSrc:String,pMC:MovieClip)
{
pXmlPath = xmlPath;
pXmlVidSrc = xmlVidSrc;
xmlloader = new URLLoader();
elementArray = new Array();
}
public function getXML()
{
XMLData();
}
private function XMLData()
{
xmlloader.load(new URLRequest(pXmlPath));
xmlloader.addEventListener(Event.COMPLETE,parseXMLData);
}
private function parseXMLData():void
{
var x:XML = new XML(xmlloader.data);
Init(x);
}
private function Init(m:XML):*
{
var i:Number;
for(i=0; i<m.videos.videoname.length(); i++)
{
if(m.videos.videoname[i].#name == pXmlVidSrc)
{
videoData.elementArray.push(m.videos.videoname[i].#name);
videoData.elementArray.push(m.videos.videoname[i].#category);
videoData.elementArray.push(m.videos.videoname[i].link.#url);
}
}
}
}
When I call it from the main stage the code is as follows.
var xData:videoData = new videoData(xmlPath,vidSrc,this);
xData.getXML();
then when I try to access any elements of videoData.elementArray they come up undefined...
Im just smacking my head on my desk trying to figure this out any help would be great.
Why is elementArray a static var, you only need to make it public to use it outside the function.
I'm quite confusing but you may want to try a debugging tool like "De MonsterDebugger", I would start by tracing xmlloader.data in the parseXMLData function.
"addEventListener" doesn't "fire"...the event does. You'll need to add a boolean to state for the stage that elementArray has been populated and set that after the init function.
Is elementArray something that needs to be true across all instances of videoData? If not, it shouldn't be static. You can use MovieClip(this.root).xData to access that instance of the video class from one of your other classes.
If the event has completed and the array is still empty - then it wasn't populated by your parser. You can also do checks to see if the elementArray.length > 0.
EDIT in response to comment:
as a public member or preferably a read-only property make a boolean variable:
var parseComplete:Boolean;
Set it to false in your constructor.
Then, after your call to "Init" in your Event.COMPLETE callback set:
parseComplete=true;
Then make sure parseComplete == true before you ever access elementArray. If you're waiting for the parser to complete you might want to set a timeout or some sort of try/catch mechanism just in case there are any unforeseen errors that would cause some sort of:
while( !xData.parseComplete ) { }
To loop indefinitely. It all depends on the usage. Personally I'd probably add a callback from the event listener to the stage to trigger whatever is supposed to happen next.

Confused about garbage collection and events with weak references in actionscript 3

I have a reference to an object. This object has a timer event with a weak reference. Example:
timer.addEventListener(TimerEvent.TIMER, timerHandler, false, 0, true);
Now I remove this reference (test is the variable containing the reference):
test = null;
And yet, the timerHandler keeps being fired. Is this impossible, so that I must have somekind of mistake in my code, without any other possibility?
Or is this indeed not supposed to stop the timer function from being run all the time?
The garbage collector doesn't operate continually, so it's likely that it just hasn't run yet. When it finally does, your handler should stop being called. If not, there is probably another reference to it.
When I run the example below, I see timer traced indefinitely, even though handler has been set to null and the EventDispatcher has a weak reference. However, if I force the garbage collector to run by uncommenting the System.gc() line (using the debug player), the handler is never called.
package {
import flash.display.Sprite;
import flash.events.Event;
import flash.events.TimerEvent;
import flash.system.System;
import flash.utils.Timer;
public class TimerTest extends Sprite {
private var timer:Timer;
public function TimerTest() {
var handler:Function = createHandler();
timer = new Timer(1000);
timer.addEventListener(TimerEvent.TIMER, handler, false, 0, true);
timer.start();
handler = null;
//System.gc();
}
private function createHandler():Function {
return function(e:Event):void {
trace('timer');
};
}
}
}
In general, you shouldn't rely on the garbage collector for the correct operation of your program.