AS3 Asynchronism problems - actionscript-3

I had a strange bug in my program which I fortunately found quite quickly but am still puzzled why it was happening. Essentially it was to do with the order of commands in the source code and event listeners, here is the example:
function detectFaces(loader:ImageLoader)
{
var detector:FaceDetector=new FaceDetector();
detector.addEventListener(FaceDetectorEvent.FACE_CROPPED,facesDetected);
detector.loadFaceImageFromBitmap(loader.bitmap);
var something:Number = stage.width;
function facesDetected(e:FaceDetectorEvent):void{
trace(something);
}
}
Operation that raise the event here is not important, only thing to note about it would be it takes around 100ms. What I get as trace output is NaN and I don't know why that is since line declaring the variable something will definitely be called before callback of facesDetected and it is in scope of the handler function declared under it. This problem was easy to solve by just moving var something:Number = stage.width; before loadFaceImageFromBitmap(..) method, but I would really like to know why this is happening?
I am suspecting this is not due to order of execution but has something to do with passingByValue and passingByRefrence deferences but don't know how would these cause an error like this.
EDIT: Now I am even more puzzled... This code works in any order of declaration:
timers();
function timers()
{
var timerTest:Timer = new Timer(100,1);
timerTest.addEventListener(TimerEvent.TIMER,onTime);
//BEFORE DECLARATION
timerTest.start();
var something:Number = stage.width;
function onTime(e:Event)
{
trace("SOMETHING :"+something);
}
}
timers();
function timers()
{
var timerTest:Timer = new Timer(100,1);
timerTest.addEventListener(TimerEvent.TIMER,onTime);
var something:Number = stage.width;
//AFTER DECLARATION
timerTest.start();
function onTime(e:Event)
{
trace("SOMETHING :"+something);
}
}

With regard to your initial question, actionscript will complete the execution of a block of code before it continues to execute subsequent lines. If there was nothing asynchronous happening in your loadFaceImageFromBitmap method (ie, if your weren't using a Loader or some other object that had to wait for an event to fire) then, however long the code takes to execute, the FACE_CROPPED event will still fire before 'something' is set to a value.
As for the other problem, it looks to me like the answer is simply that you're using a TimerEvent - Actionscript will acknowledge that it shouldn't wait for the event to fire before continuing to execute code; It will, therefore, declare 'something' before the 100 miliseconds passes. So, in this case, because you're using an event, the code WILL continue 'reading' and executing the lines following the event listener.

The code of the function loadFaceImageFromBitmap run on a sync way. The FaceDetectorEvent.FACE_CROPPED event listener is invoked inside of that function, it is not a callback declared to run after some response is returned for ie(http request).
In the case of the Timer it works as expected, because event listener is not invoked right at the start moment, it waits for X time.

Related

Create delay in ActionScript 3 function

I have a function in Adobe Flex 4 (ActionScript 3) that accepts an object and returns an ArrayCollection...
If a certain global variable is set to true, I want the function to delay itself for 3 seconds before running. Otherwise I want the function to run as normal.
The problem is, if I use a Timer, that timer calls a separate function, and that function cannot return anything to my calling function, nor can the function it calls accept any parameters, so it's not like I can call my own function recursively after the TimerComplete event fires... And a recursive call wouldn't work anyway, because it would return the ArrayCollection to the timer-result function, not to the original calling function...
I need a delay within the function, not a delay that causes me to go outside that function. But I cannot figure out how to do it.
Something like this is what I need to do:
private function createArrayCollection(myObject:Object):ArrayCollection {
var myArrayCollection:ArrayCollection = new ArrayCollection();
if (globalWaitBoolean) {
//delay here for 3 seconds, somehow
}
//Here I do the stuff that uses the info in myObject to figure out what to
//put into the ArrayCollection I want to return
return (myArrayCollection);
}
So... Any ideas on how to accomplish this without calling an external Timer function that cannot return an object back to my original function?
Thanks,
The way you want it you will have your whole application to lag for 3 seconds, unresponsive to any user input and external events. But it is possible, sure:
import flash.utils.getTimer;
private function createArrayCollection(myObject:Object):ArrayCollection
{
var myArrayCollection:ArrayCollection = new ArrayCollection;
if (globalWaitBoolean)
{
var waitUntil:int = getTimer() + 3000;
// Method getTimer() returns time in ms passed since app start.
// So you just have to wait until it is greater than appointed time.
while (getTimer() < waitUntil)
{
// Do nothing.
}
}
return (myArrayCollection);
}
Still, if you want to do it in a correct way of doing it:
import flash.utils.setTimeout;
private function callerMethod():void
{
// Blah blah blah.
// ...
// Finally.
createArrayCollection(sourceData, asyncResult);
}
private function createArrayCollection(myObject:Object, handler:Function):void
{
var result:ArrayCollection = new ArrayCollection;
if (globalWaitBoolean) setTimeout(handler, 3000, result);
else handler(result);
}
private function asyncResult(source:ArrayCollection):void
{
// The rest of your processing code.
}
Normal (synchronous) code flow would not return until the value is prepared, so should you desire to actually wait for 3 seconds while not allowing your app to do anything, use getTimer() approach from #Organis's answer. If you'll go for an asynchronus result, you'll need to face and overcome some more problems.
First, when do you expect your returned ArrayCollection to actually arrive. Speaking of code design, asynchronous code requires a whole lot of assumptions, thread safety etc etc, and even while AS3/Flash does not have true multithreading unless you count Workers, the code flow with events is not as obvious. So, whoever called your createArrayCollection() MUST NOT expect value returned from it right away. So, speaking about your direct question, NO, you can't avoid timers of some sort if you desire a responsive application. But you can use them with an approach that would involve an indirectly returned result.
Second, whether there might be concurring requests for more array collections from objects if your app would require these - you have to prepare for any kind of interference that might be caused by this. Say your function is triggered by a button click - what if that button would get clicked more than once in 3 seconds?
Third, actual route to processing code is not direct with asynchronous return. You need either a callback, an event handler (which is essentially a semi-native callback), a code that periodically checks for value presence (enter frame handler, etc) or a similar trick to gather the value that's returned asynchronously, and then transfer it to any relevant code that would process it further. Therefore, you would need to design an interface capable of receiving complex data (source object forward, array collection backward) and then carefully test it against all the possible cases and flaws.
An example of implementing all that is very long, I'll try to outline it somehow. Ler's assume you have a sort of "server" class that accepts requests for data and processes it synchronously (no wait) or asynchronously (wait). It accepts a source object of type "T" and provides a newly created object of type ArrayCollection, supplied as a parameter to whatever callback function sent to it. Also it accepts a delay (a simple way to show sync/async return would be a boolean, but why not getting an int?) as a parameter, and guarantees (to the extent of event model limitations) that after this delay the callback will be called ASAP. The architecture will then look like this:
class Processor {
Dictionary requests; // here all the requests that are delayed will be stored
public function dpr(source:T,callback:Function,delay:int=0):void{...}
// creates requests and stores them
private function syncProcess(source:T):ArrayCollection {...}
// whatever routine you want to get variably delayed
private function processTimeout(e:Event=null):void {...}
// processes events from "setTimeout()" and calls callbacks
}
Note that asynchronous approach forced to create three more entities than a synchronous one. First is the request holding structure (the dictionary here), second is timeout event handler, third is whatever callback you'll desire to get called when the data is ready. The code flow would go like this:
Synchronous call would result in the callback directly called from within the class: request->processTimeout->syncProcess()->callback. Asynchronous call will have the callback called from within Timer::timerComplete event handler via setTimeout called within request, with data that originally came from request stored in requests.
You could use an embedded/inline function:
private function createArrayCollection(myObject:Object):ArrayCollection {
var myArrayCollection:ArrayCollection = new ArrayCollection();
if (globalWaitBoolean) {
var milliseconds:int = 3000;
//delay here for 3 seconds
setTimeout(function()
{
//Here I do the stuff that uses the info in myObject to figure out what to
//put into the ArrayCollection I want to return
return (myArrayCollection);
},
milliseconds);
}
else
{
//Here I do the stuff that uses the info in myObject to figure out what to
//put into the ArrayCollection I want to return
return (myArrayCollection);
}
}
The inner function will have access to all local vars of the outer function.

in AS3, removeEventListener(Event.ENTER_FRAME) is not working

I have been dealing with this problem for days already. I am at my wits' end!
I can't seem to find a definitive answer anywhere on any of the forums, documentation, etc.
Everything looks fine at first run, or when I load a next level for the user to play. But if the user hits the ESC key to load a different level, the ENTER FRAME listener does not get removed and it duplicates all the triggers in it, showing the player going really fast, and all funky, because it builds on top of the previously instantiated ENTER FRAME listener.
I don't know if I have a problem of an anonymous function, or an unknown instance being referenced in my removeEvent... command... Bottom line, I give up and I need this working HELP!!!
Here's the code:
function initPlay():void
{
//code here determining what display object to add to the list and assign it to the currentLevel variable (a movieclip)
if(userIsLoadingOtherLevel){
removeEnterFrameListener();
addChild(currentLevel);
}
if(userIsGointToNextLevel)
addChild(currentLevel);
currentLevel.addEventListener(Event.ENTER_FRAME, onEnterFrame);
function onEnterFrame(event:Event):void
{
//collision detection, parallax scrolling, etc, etc is done here.
if(allCoinsCollected)
loadNextLevel();
if(ESCKeyPressed)
ESCKeyPressHandler();
}
function loadNextLevel():void
{
removeChild(currentLevel);
newLevelToLoad++
removeEnterFrameListener();
initPlay();
}
function ESCKeyPressHandler():void
{
removeChild(currentLevel);
initPlay();
}
function removeEnterFrameListener();
{
currentLevel.removeEventListener(Event.ENTER_FRAME,onEnterFrame)
trace("currentLevel.hasEventListener(Event.ENTER_FRAME) = "+currentLevel.hasEventListener(Event.ENTER_FRAME)); //outputs TRUE if called from loadNextLevel but FALSE if called from initPlay() !!!
}
}
I also tried to add and remove the eventListener to stage, MovieClip(Root), or nothing at all and the result is always the same.
I know that there may be other ways to design such a process, but please note I am not really flexible at the moment on doing this because the project is very long (about 4000 lines of code) and removing the ENTER FRAME this way, crazy or not should still work!!
THANK YOU in advance for anyone willing to help.
The problem appears to be the nested functions inside the initPlay() method.
Each time you call initPlay() you are defining new functions. Some of these nested functions call initPlay() themselves.
Functions are objects (memory references). So each time you call initPlay() you are making new references to new functions. So when you try to remove an event listener, you're only able to remove one of these event handlers (the one in the current scope of execution).
I'm not sure if I'm explaining this clearly, perhaps this example will help. I'll use numbers to represent the references to each function, and a simple scenario that is similar to yours:
function example():void
{
addEventListener(MouseEvent.CLICK, mouseClickHandler);
function mouseClickHandler(event:Event):void
{
if (someCondition)
{
example();
}
else
{
removeEventListener(MouseEvent.CLICK, mouseClickHandler);
}
}
}
When we run this function the first time, a new function is defined within the scope of the example() function. Lets use the number 1 to represent the reference to this nested function. someCondition is true on the first time around, and so the example() function is called again.
On the second execution of the example() function, a new reference to the mouse event handler is created (#2). We also add the event listener again. At this point, there are two event handling functions in memory, and both will be executed when the event is dispatched.
Let's say that in the second invocation of example() that someCondition is false and now we want to remove the listener. When we call:
removeEventListener(MouseEvent.CLICK, mouseClickHandler);
It's referring to event handler #2. Event handler #1 still exists, and because it's hidden in the scope of the first invocation of example() it can't be removed here.
My simple example breaks down after this... but I hope it makes it clear why your event handlers shouldn't be nested inside a function. Admittedly, this is difficult to describe and even more so in a real world example like yours. But I'm pretty confident that this is the source of most, if not all, of the issues you describe.
Here's how I was able to get around this without changing the scope of the nested functions (although I agree that would be the preferred solution) by creating a boolean variable called "loadingNewGame" and changing it to true from outside the onEnterFrame (in fact, this assignment was done from initPlay() and then from onEnterframe I called removeEnterFrameListener() function. This did the trick.
here's the code in case anybody is interested:
// package, and other code here.
var loadingNewGame:Boolean = new Boolean(false);
function initPlay():void
{
//code here determining what display object to add to the list and assign
//it to the currentLevel variable (a movieclip)
if(userIsLoadingOtherLevel)
{
loadingNewGame = true;
removeEnterFrameListener();
addChild(currentLevel);
}
if(userIsGointToNextLevel)
addChild(currentLevel);
loadingNewGame:Boolean = false;
currentLevel.addEventListener(Event.ENTER_FRAME, onEnterFrame);
function onEnterFrame(event:Event):void
{
if(loadingNewGame)
removeChild(currentLevel);
//collision detection, parallax scrolling, etc, etc is done here.
if(allCoinsCollected)
loadNextLevel();
if(ESCKeyPressed)
ESCKeyPressHandler();
}
function loadNextLevel():void
{
removeChild(currentLevel);
newLevelToLoad++
removeEnterFrameListener();
initPlay();
}
function ESCKeyPressHandler():void
{
initPlay();
}
function removeEnterFrameListener();
{
currentLevel.removeEventListener(Event.ENTER_FRAME,onEnterFrame)
trace("currentLevel.hasEventListener(Event.ENTER_FRAME) = "+currentLevel.hasEventListener(Event.ENTER_FRAME));
//outputs true
}

Calling certain functions whitout having the right arguments

I have two function on my AS3 program, one fires when the width and height changes:
stage.addEventListener(Event.RESIZE, resizeListener);
function resizeListener (e:Event):void {
//some commands
}
And the second one fires one a number of milliseconds pass:
var myTimer:Timer = new Timer(clockUpdate, 0);
myTimer.addEventListener(TimerEvent.TIMER, updateData);
myTimer.start();
function updateData(e:TimerEvent):void {
trace("AUTOUPDATE");
trace(e);
}
I need to fires those function also manually, lets say when the user press a button, but i don't know what parameters i have to send them when they are called manually.
I tried just resizeListener() and updateData() but of course it fails asking me for the parameter.
You can make parameters in a function optional by providing a default value. This is an example by taking your two functions above and making the event parameters optional:
function resizeListener(e:Event = null):void {
//some commands
}
and
function updateData(e:TimerEvent = null):void {
trace("AUTOUPDATE");
trace(e);
}
Calling, for example, resizeListener() will now execute the function and the value of e will default to null.
Making the Event parameter optional, resizeListener(e:Event=null), as in walkietokyo's answer, is a perfectly valid and often convenient solution. Another alternative is to put the stuff you want to be able to do without the event being triggered in a separate function, that can be called by the event handler and from anywhere else.
So assuming for example that what you want to do on resize is to rearrange the layout, and you also want to do that same layout setup at initialization, or at the click of a button, or anytime really, you could do something like this:
stage.addEventListener(Event.RESIZE, resizeListener);
function resizeListener(e:Event):void {
rearrangeLayout();
}
function rearrangeLayout():void {
// The actual rearrangement goes here, instead of in resizeListener. This can be called from anywhere.
}
Which way to do it is probably a matter of taste or can vary from case to case, really, both works fine.
A benefit of separating things in an event handler and another function is that there will not arise a situation where you would have to check if the e:Event parameter is null or not. In other words, you would have code that is dependent on the Event, if any, in the event handler, and code that is independent of the Event in a more general function (not an event handler).
So in a more general and schematic case, the structure would be something like this:
addEventListener(Event.SOME_EVENT, eventListener);
function eventListener(e:Event):void {
// Code that needs the Event parameter goes here (if any).
// Call other function(s), for the stuff that needs to be done when the event happens.
otherFunction();
}
function otherFunction():void {
// Stuff that is not dependent on the Event object goes here, an can be called from anywhere.
}

Passing e:MouseEvent as an argument via setInterval

So i have this function
capture_mc.buttonMode = true;
capture_mc.addEventListener(MouseEvent.CLICK,captureImage);
function captureImage(e:MouseEvent):void {
//lalalala
}
I want to call this function every 2 seconds (after mouse click event happens).
I tried using setInterval
setInterval(captureImage,2000,e:MouseEvent);
but it leads to following error
1084: Syntax error: expecting rightparen before colon.
What's wrong ?
And ya, i am new to AS.
First, since this is AS3 you should be using Timer and TimerEvent. I'll show you how in the example.
Now you'll need to separate your functions:
edit: I've updated this to be safer based on #(Juan Pablo Califano) suggestions. I would keep the same timer for ever if the amount of time isn't going to change.
// first param is milliseconds, second is repeat count (with 0 for infinite)
private var captureTimer:Timer = new Timer(2000, 0);
captureTimer.addEventListener(TimerEvent.TIMER, handleInterval);
function handleClick(event:MouseEvent):void
{
// call here if you want the first capture to happen immediately
captureImage();
// start it
captureTimer.start();
}
function handleInterval(event:TimerEvent):void
{
captureImage();
}
function captureImage():void
{
// lalalala
}
You can also stop the timer with captureTimer.stop() whenever you want.
The problem is that you should use the parameterName:ParameterType syntax only when declaring formal parameters (or when declaring vars and consts). Meaning, this is valid only when you are defining a function:
function func(paramName:Type){
}
When you call the function, you don't have to put the type of the arguments.
So, your function call should look like this:
setInterval(captureImage,2000,e);

Clearing eventListeners on a FileReference object

I have a strange issue! I am trying to remove an event listener on a FileReference object by calling a function, but it seems not to be removed, and I do not understand why.
Here is the code:
private function clearFileUploadListeners(file:FileReference, index:String):void {
var dispatchEvent:Function = function(event:Event):void {
dispatch(event.type, event, index);
};
file.removeEventListener(Event.COMPLETE, dispatchEvent);
var bool:Boolean = file.hasEventListener(Event.COMPLETE);
if (bool)
trace("ERROR");
}
When I run this code, the trace actually happens. I don't understand why this boolean returns true, when I just tried to remove the eventListener just above! I guess I am probably doing something really stupid because it seems like a strange error.
I hope someone can please help me on this issue.
EDIT:
I believe it has to do with the fact that the dispatchEvent function is defined inside another function when I add the listener:
private function upload(file:FileReference, index:String):void {
var dispatchEvent:Function = function(event:Event):void {
dispatch(event.type, event, index);
};
file.addEventListener(Event.COMPLETE, dispatchEvent);
}
The problem is that I need to access this "index" variable from the listener, and I can't set it as a global variable as each file has it's own index and it's a burden if I have to extend each event class to keep track of the index (Event, ProgressEvent, ..). I hope someone can please help me on this.
EDIT2:
I actually found a temporary solution, I am not sure if it is the best! I put my removeListener method actually inside the upload method, but made it a variable. As AS3 allows dynamic object, I attached this method to one of my object, and so I just call the reference to the method when necessary. The event is actually removed. Is this a good solution please?
Thank you very much,
Rudy
You're right, it has to do with the fact that you're defining a function inside another function, then using it to handle events.
Each time the function upload is called, it creates a new closure, and assigns a reference to it to the dispatchEvent variable, which is then passed to the addEventListener class. So each time upload is called, it is using a new, different closure in the call to addEventListener. Similarly, in the clearFileUploadListeners function, a new closure is being created on each call (which happens to have the same code each time, but isn't the same function object). The call to removeEventListener does nothing if the given callback has not been added as an event listener for the given event, which is the case here.
To solve your problem, you need to store a reference to the closure that you pass to the addEventListener function. This way, you can get a reference to the same closure that was added when you need to remove it later in clearFileUploadListeners.
You can try something along the lines of the following code (untested):
import flash.utils.Dictionary;
var callbackRegistry:* = new Dictionary();
private function upload(file:FileReference, index:String):void {
var dispatchEvent:Function = generateFileUploadCompleteCallback();
callbackRegistry[file] = dispatchEvent;
file.addEventListener(Event.COMPLETE, dispatchEvent);
}
private function clearFileUploadListeners(file:FileReference, index:String):void {
var dispatchEvent:Function = callbackRegistry[file];
callbackRegistry[file] = null;
file.removeEventListener(Event.COMPLETE, dispatchEvent);
var bool:Boolean = file.hasEventListener(Event.COMPLETE);
if (bool)
trace("ERROR");
else
trace("YAY, ALL OK!");
}
private function generateFileUploadCompleteCallback(index:String):Function {
return function(event:Event):void {
dispatch(event.type, event, index);
};
}
Two other things to note on this subject.
If you must utilize a native Event directly then you should pretty much always make sure and use these last three optional params :
myObject.addEventListener( Event.COMPLETE, myFunction, false, 0, true );
Check Grant Skinner's post on the subject here :
http://gskinner.com/blog/archives/2006/07/as3_weakly_refe.html
And the very best practice of all is to ALWAYS (seriously always) use Robert Penner's Signals (instead of custom events) and his NativeSignals (to wrap needed native Flash events).
Five times faster than Flash's native events.
Always safe with weak references.
Any number of typed payload(s) in each Signal.
Get the SWC here :
https://github.com/robertpenner/as3-signals
Signals were designed to solve the very problem you are having.
Imagine instead of creating an array and managing that to remove all listeners if you could just call :
signalBtnClicked.removeAll();
or
signalBtnClicked.addOnce( function( e : MouseEvent ) : void { /* do stuff */ } );
Knowing that the closure you just created will immediately be dereferenced once it is called and happily go night night when the GC makes its rounds.