Confused about garbage collection and events with weak references in actionscript 3 - 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.

Related

TypeError: Error #1009 with loading external SWF file

I was making 3D File Viewer in Flash Player with AS3.0
and i found AWD Viewer from Away3D Example File.
(http://awaytools.com/awaybuilder/tutorial-01/AwayBuilderTutorial01_SampleFiles.zip)
it works fine.
and i loaded it in my 'Main' swf file. but it's not work. it kept showing error to me.
error message is below
TypeError: Error #1009: Cannot access a property or method of a null object reference.
at AWDViewer/initEngine()[C:\Users\wintec\Desktop\Flash3DViewer\source\AWDViewer.as:74]
at AWDViewer/init()[C:\Users\wintec\Desktop\Flash3DViewer\source\AWDViewer.as:57]
at AWDViewer()[C:\Users\wintec\Desktop\Flash3DViewer\source\AWDViewer.as:49]
and that error line is just this
line 74 : stage.scaleMode = StageScaleMode.NO_SCALE;
line 57 : initEngine();
line 49 : init();
and I know that error message mean there's no properties that name.
I checked that, there's nothing wrong.
also, when I loading another swf file in my 'Main'swf, that's works. weird...
I don't understand why this error kept showing.
please help me.
below code is from Main.as
package
{
import flash.display.MovieClip;
import flash.display.DisplayObject;
import flash.display.Loader;
import flash.net.URLRequest;
import flash.events.Event;
public class Flash3DViewer extends MovieClip
{
private var _request:URLRequest = new URLRequest("AWDViewer.swf");
private var _loader:Loader = new Loader()
public function Flash3DViewer()
{
init();
}
private function init():void
{
stop();
_loader.load(_request);
_loader.contentLoaderInfo.addEventListener(Event.COMPLETE, initLoaded);
}
private function initLoaded():void
{
_loader.contentLoaderInfo.removeEventListener(Event.COMPLETE, initLoaded);
var extSwf = _loader.content as MovieClip;
swfContainer.addChild(extSwf);
trace("contents loaded");
}
}
}
I found the issue with your application based on the code you provided via DropBox. And just as I suspected, the stage property was being referenced before the object's addition to the stage was completed, which is why the null reference error was being generated.
The AWDViewer class was prematurely calling the stage property from one of the functions that is being called when the init function is called. I've updated the Flash3DViewer.as and AWDViewer.as files with the proper usage of the ADDED_TO_STAGE event so that this does not occur. I have also added comments to the code for you to follow along. Also, I had to modify the init function in the AWDViewer class to take a parameter of type Event to account for the fact that the function is now called when the ADDED_TO_STAGE event fires.
Flash3DViewer.as:
package
{
import flash.display.MovieClip;
import flash.display.Sprite;
import flash.display.DisplayObject;
import flash.display.Loader;
import flash.net.URLRequest;
import flash.events.Event;
public class Flash3DViewer extends MovieClip
{
private var loader:Loader;
public function Flash3DViewer():void
{
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}
private function init(e:Event = null):void
{
removeEventListener(Event.ADDED_TO_STAGE, init);
loadSWF("AWDViewer.swf");
}
private function loadSWF(url:String):void
{
var urlRequest:URLRequest = new URLRequest(url);
loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoaded, false, 0, true);
loader.load(urlRequest);
addChild(loader);
}
private function onLoaded(e:Event):void
{
var target:AWDViewer = e.currentTarget.loader.content as AWDViewer;
trace(target);
//target.init(); // no longer need to call the init function manually as the AWDViewer calls it when it 'knows' it has been added to the stage. This line can be deleted.
addChild(target);
}
}
}
AWDViewer.as:
public function AWDViewer()
{
/* Used ADDED_TO_STAGE event to know when to trigger the init
function call to avoid the null reference errors when accessing
the 'stage' property */
addEventListener(Event.ADDED_TO_STAGE,init);
// init(); // no longer need to manually call the init function as the ADDED_TO_STAGE event listener will take care of this. This line can be deleted.
}
/**
* Global initialise function
*/
public function init(e:Event):void
{
initEngine();
initListeners();
AssetLibrary.enableParser(AWD2Parser);
//kickoff asset loading
var loader:Loader3D = new Loader3D();
loader.load(new URLRequest("assets/monkey.awd"));
_view.scene.addChild(loader);
}
While I did attempt to compile the code above, and the null reference errors ceased to generate with my corrected code, there were some compiler errors on my machine because of the different configurations of our computers. You'll just need to ensure that these compiler errors do not appear on your machine.
Warning: Library path "C:\Users\wintec\Desktop\3D_VR\source\libs" does not resolve to a valid directory.
Warning: Library path "$(FlexSDK)/frameworks/libs/flex.swc" does not resolve to a valid file.
If you have any other questions, just let me know.
Cheers.
That error means that you are trying to access something which has not been instantiated. You should put some breakpoints and run your app in debug mode when you are not sure what exactly is null.
It's very likely that stage is null in your case. Stage property of DisplayObject is set to the instance of app's stage when this display object is added to stage. However, all parents of this display object should be added to the stage too. Thus, make sure that the instance of Flash3DViewer has the stage before loading AWDViewer.

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.

addEventListeners and best practices

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.

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