From synchronous flow to asynchronous flow dilemma - actionscript-3

As a PHP programmer I'm very habituated to the fact that the program flow goes line by line, if I call function1() that have to return some value I know the program wont continue until that function makes a return.
Now, im developing a AS3 app using AIR, I need to download some images if some conditions are met, don't know how many, so im using a For like this:
for (var w:int=0; w < newData_array[2]['content'].length; w++ ) {
for (var j:int=0; j < oldData_array[2]['content'].length; j++ ) {
if((oldData_array[2]['content'][j]['id'] == newData_array[2]['content'][w]['id']) && (oldData_array[2]['content'][j]['last_mod'] != newData_array[2]['content'][w]['last_mod'])){
//need to download a image...
}
}
}
As you can see im just comparing each element from each array (newData_array and oldData_array). If the condition met, I need to download something, for that I'm using URLloader and as you know this function is asynchronous, adding a listener, an event will be triggered when the download is complete, the problem is very clear, since the urlloader wont stop the for cycle (like I would expect on a PHP alike language) I'm just going to download a bunch of images at the same time creating a disaster because I wont know when to save. So I need to use by any mean the listener, but since I'm not very habituated to this kind of procedures I'm pretty munch stuck.
Im not interested on the save to disk routines, that part is pretty much done, I just want to understand how I should structure this algorithm to make this work.

This annoyed me too, but one has to realize that you can't do something to something that doesn't exist in memory yet, and these loading operations can take quite some time. Being a single-threaded stack, Flash Player's load operations would require that the entire interface to freeze during the load (if restricted to the loop).
Obviously there's data available to the loop that dictates what you do with the image. The solution I've been using is create a custom load function that starts the load, and returns a proxy image . Your loop gets a container back and you can freely place your image where you want.
In the meantime, your loader listener has kept track of the active images loading, and their associated proxies. When loaded, swap the image in.
Your loop would look something like this...
for (var w:int in newData_array[2]['content']) {
for (var j:int in oldData_array[2]['content']) {
if ((oldData_array[2]['content'][j]['id'] == newData_array[2]['content'][w]['id']) && (oldData_array[2]['content'][j]['last_mod'] != newData_array[2]['content'][w]['last_mod'])){
var proxy:MovieClip = loadImage("image/path.jpg");
// Do stuff to proxy.
}
}
}
And your function + listener would look something like this...
var proxies:Object = {};
function loadImage(path:String):MovieClip {
// load Image
var proxy:MovieClip = new MovieClip();
proxies.path = proxy;
// Load Image
}
function imageLoaded(e:Event):void {
// Once loaded, use the filename as the proxy object's key to find the MovieClip to attach the image.
proxies[e.data.loaderInfo.filename].addChild(e.data);
}
This is just off the top of my head, so you'll need to find the actual variable names, but I hope that makes sense.

I am not really sure if I understood your problem but it seems to me that you simply add the according listener in your inner loop.
//take care this is pseudocode
var parent = ... // some stage object
for (var w:int=0; w < size1; w++ ) {
for (var j:int=0; j < size2; j++ ) {
if(some_condition){
request = new URLRequest(getNextImagePath();
urlLoader = new URLLoader();
urlLoader.addEventListener(Event.COMPLETE, onComplete);
urlLoader.load(request);
}
}
}
function onComplete(event:Event) {
parent.addChild(createImageFromData(this.data));
}

I solved the problem using an third party library that pretty munch handles this problem.
https://github.com/arthur-debert/BulkLoader

Related

AS3 tween object not working with .hitTestObject()

I am having a major problem in my new browser app.
Okay so I made game where different cubes (squares) spawn at the top of the screen and I use the Tween class to make them go down the screen and then disappear.
However I want to detect a collision when a cube hits the player (that is also a flying cube).
I tried everything, truly everything but it does not seem to work. The problematic thing is that when I remove the "Tween" function it does detect collision with the hitTestObject method but when I add the "Tween" line collision won't be detected anymore.
It looks like this:
function enemiesTimer (e:TimerEvent):void
{
newEnemy = new Enemy1();
layer2.addChild(newEnemy);
newEnemy.x = Math.random() * 700;
newEnemy.y = 10;
if (enemiesThere == 0)
{
enemiesThere = true;
player.addEventListener(Event.ENTER_FRAME, collisionDetection)
}
var Tween1:Tween = new Tween(newEnemy, "y", null, newEnemy.y, newEnemy.y+distance, movingTime, true);
}
And the collision detection part:
private function collisionDetection (e:Event):void
{
if (player.hitTestObject(newEnemy))
{
trace("aaa");
}
}
I am desperate for some information/help on the topic, it's been bugging me for days.
Thanks for your time, I would be very happy if someone could help me out^^
First, make sure the "newEnemy" instance and the "player" instance are within the same container. If they are not, their coordinate systems might not match up and could be the source of your problem.
Otherwise, you need to keep a reference to each enemy instance you create. It looks like you are only checking against a single "newEnemy" variable which is being overwritten every time you create a new enemy. This might be why you can successfully detect collision between the player and the most recent "enemy" instance.
So... you need a list of the enemies, you can use an Array for that.
private var enemyList:Array = [];
Every time you create an enemy, push it to the Array.
enemyList.push(newEnemy);
In your "collisionDetection" function, you need to loop through all of the enemies and check if the player is touching any of them.
for(var i:int = 0; i < enemyList.length; i++)
{
var enemy = enemies[i];
if (player.hitTestObject(enemy))
{
trace("Collision Detected!");
enemy.parent.removeChild(enemy); // remove the enemy from the stage
enemies.splice(i, 1); // remove the enemy from the list
}
}
I'd suggest that you move to TweenMax, it just might solve your problem, and in my experience it's much better in every possible way.
Scroll down the following page to see a few variations of this library, I myself use TweenNano, they're completely free of charge:
https://greensock.com/gsap-as
I think some plugins cost money, but I doubt you'll ever need them.

[AS3]How to quickly mix two audio tracks

There are few answers which mix two audio tracks with sampleDataEvent listener while playing the entire sound. Is there any way to mix the tracks as fast as possible? (The tracks are of equal duration and is in the form of ByteArray)
In the post below
Programatically Mixdown of audio tracks (no playback)
the author suggests using Event.EnterFrame. However, I'm not quite familar with AS3's API. Can anyone give some example code? Thanks!
If you don't need to output the mix in binary format, just do double play.
track1.play();
track2.play();
Yep, as your tracks are in ByteArrays, first make two Sound object and get the data from bytearrays by loadPCMDataFromByteArray().
UPDATE: Since you don't want playback at all, the most simple way to mix the two tracks will be reading one float out of each ByteArray, then write their average into the resultant ByteArray. Using the ENTER_FRAME listener is worth it if you don't want to have your SWF stall while doing the conversion. You've said your arrays are of equal length, if so, the following code snippet should do your "simple mix" of those wavs:
var f1:Number;
var f2:Number;
b1.position=0;
b2.position=0;
var result:ByteArray=new ByteArray();
var l:int=b1.length; // cache property
var i:int=0;
while (i<l) {
i=i+4; // size of float
f1=b1.readFloat();
f2=b2.readFloat();
result.writeFloat(0.5*f1+0.5*f2);
}
Doing an enterframe approach requires your result be available between listener calls, and positions unaltered, with a temporary counter running in the loop which will control "enough converting in this frame". Like this:
var result:ByteArray;
var tracks:Vector.<ByteArray>=[];
var mixFinished:Function; // a callback
function startMixing():void {
// just make it start mixing
for (var i:int=tracks.length;i>=0;i--) tracks[i].position=0;
addEventListener(Event.ENTER_FRAME,doMixing);
result=new ByteArray();
}
function doMixing(e:Event):void {
if (tracks.length==0) {
removeEventListener(Event.ENTER_FRAME,doMixing);
return;
} // sanity check
var mixrate:Number=1.0/tracks.length;
for (var i:int=0;i<2048;i++) { // adjust number accordingly
var tm:int=0; // how many tracks mixed
var f:int=0;
for (var j:int=tracks.length-1;j>=0;j--) {
if (tracks[j].position<tracks[j].length) {
// this track isn't finished
tm++;
f+=tracks[j].readFloat();
}
}
if (tm==0) { // all tracks reached end, stop mixing
removeEventListener(Event.ENTER_FRAME,doMixing);
if (mixFinished!=null) mixFinished(); // do a callback
return;
}
result.writeFloat(f*mixrate);
}
}
With this, you fill tracks, set up mixFinished and call startMixing, then wait until mixFinished would get called, by that time your sound should be mixed properly. If you feel your mixing process should go faster, increase the 2048 value in code appropriately.

I can't seem to access automatically named objects (instance##) placed on the stage in AS3, am I missing something?

I have a movieclip in the library that is added to the stage dynamically in the document class's actionscript. This movieclip contains many many child images that were imported directly from photoshop at their original positions (which must be preserved).
I do not want to manually name every single image instance, as there are dozens upon dozens.
I have already gone through and manually converted the images to symbols, as apparently flash won't recognize the "bitmap" objects as children of a parent movieclip in AS3 (numChildren doesn't see the bitmaps, but it sees the symbols).
I have an array filled with references to the dozens of children, and I loop through it, checking if each one is under the mouse when clicked. However, somehow, it is not detecting when I click over the items unless I manually name the child symbols (I tested by manually naming a few of them -- those ones became click-sensitive.)
I have already done trace() debugging all throughout the code, verifying that my array is full of data, that the data is, in fact, the names of the instances (automatically named, IE instance45, instance46, instance47, etc.), verifying that the function is running on click, verifying that the code works properly if I manually name the symbols.
Can any one see what's going wrong, or what aspect of flash I am failing to understand?
Here is the code:
//check each animal to see if it was clicked on
private function check_animal_hits():void
{
var i:int = 0;
var animal:Object = this.animal_container;
for (i=0; i<animal.mussels.length; i++)
{
if (this.instance_under_cursor(animal.mussels[i].name))
{
var animal_data = new Object();
animal_data.animal = "mussel";
this.send_data(animal_data);
}
}
}
Here is the code for the instance_under_cursor() method:
// Used for finding out if a certain instance is underneath the cursor the instance name is a string
private function instance_under_cursor(instance_name)
{
var i:Number;
var pt:Point = new Point(mouseX,mouseY);
var objects:Array = stage.getObjectsUnderPoint(pt);
var buttons:Array = new Array ;
var o:DisplayObject;
var myMovieClip:MovieClip;
// add items under mouseclick to an array
for (i = 0; i < objects.length; i++)
{
o = objects[i];
while (! o.parent is MovieClip)
{
o = o.parent;
}
myMovieClip = o.parent as MovieClip;
buttons.push(myMovieClip.name);
}
if (buttons.indexOf(instance_name) >= 0)
{
return true;
}
return false;
}
Update:
I believe I have narrowed it down to a problem with getObjectsUnderPoint() not detecting the objects unless they are named manually.
That is the most bizarre way to find objects under mouse pointer... There is a built-in function that does exactly that. But, that aside, you shouldn't probably rely on instance names as they are irrelevant / can be changed / kept solely for historical reasons. The code that makes use of this property is a subject to refactoring.
However, what you have observed might be this: when you put images on the scene in Flash CS, Flash will try to optimize it by reducing them all to a shape with a bitmap fill. Once you convert them to symbols, it won't be able to do it (as it assumes you want to use them later), but it will create Bitmpas instead - Bitmap is not an interactive object - i.e. it doesn't register mouse events - no point in adding it into what's returned from getObjectsUnderPoint(). Obviously, what you want to do, is to make them something interactive - like Sprite for example. Thus, your testing for parent being a MovieClip misses the point - as the parent needs not be MovieClip (could be Sprite or SimpleButton or Loader).
But, if you could explain what did you need the instance_under_cursor function for, there may be a better way to do what it was meant to do.

Using AS3WavSound to play WAV - cannot stop instantly

I'm using the AS3WavSound (http://code.google.com/p/as3wavsound/) class to playback externally loaded wavs. This is working successfully. The library is simple and effective.
After decoding the Wav ByteArray the method the library employs for playback is using the SampleDataEvent.SAMPLE_DATA event and then writing the mixed samples to the output stream.
player.addEventListener(SampleDataEvent.SAMPLE_DATA, onSamplesCallback);
private function onSamplesCallback( evt : SampleDataEvent ):void
{
for (var i:int = 0; i < samplesLength; i++)
{
if(_mute == false){
outputStream.writeFloat(samplesLeft[i]);
outputStream.writeFloat(samplesRight[i]);
}
}
}
My problem is that I need to silence this audio output immediately but whatever method I have tried there is a distinct (1 second approx) delay before the silence takes effect.
As you can see I've attempted to add a boolean to block any samples being written to the output stream but this has had no effect on the delay.
My suspicion is that this is a fundamental part of how the samples are buffered and then written out. Essentially by the time a user action on screen (clicking a mute button) has been called and the _mute boolean is set to true there are already samples waiting to be written to the output that cannot be affected.
Any advice or confirmation of my suspicion would be greatly appreciated.
Thanks,
gfte.
Your suspicion is probably right - but why stop it at that level? If you want to turn off the sound, would it not be better to set the volume on the soundTransform-property on the SoundChannel-object returned by the play method? (I assume the wav library returns this in some way)
It looks like the library you are using has a similar design to the native Flash Sound API wherein a SoundChannel object is returned from the play() method. This SoundChannel instance has a stop() method which should stop the sound right away.
var sound:WavSoundPlayer = new WavSoundPlayer();
var channel:WavSoundChannel = new WavSoundChannel();
sound.addEventListener( SampleDataEvent.SAMPLE_DATA, onSampleData );
channel = sound.play();
private function onSamplesData( evt : SampleDataEvent ):void
{
for (var i:int = 0; i < samplesLength; i++)
{
outputStream.writeFloat(samplesLeft[i]);
outputStream.writeFloat(samplesRight[i]);
}
}
channel.stop()
The _mute variable in your example will only be able to change either before or after the loop, not while it is looping.

Passing local variable to loader anonymous handler function

Why isn't this working as I am thinking it would:
var i:int=-1;
for each(obj in myData)
{
i++;
var loader:Loader=new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE,function(event:Event)
{
trace(i);
});
}
There are 3 objects in myData and the trace statement looks like:
2
2
2
Instead of:
0
1
2
If I add i to an array (like myArr.push(i)) it will have 3 elements, 0, 1 and 2.
Any ideas?
Thank you.
That's a very bad approach you've taken... Just don't do any of those things you are trying to do, and it'll be fine... No point in using anonymous function here (it's never actually in AS3), no point to use for-each, because what you need is for(;;). You use dynamic typing for no benefit what so ever (there's no benefit in dynamic typing in AS3 and never was anyway). And, yeah, the closure will capture the context, the context has only one i, and it's value is 2, so the first trace is what you should expect.
What you should be doing - store the loaders in some data structure and fetch them from that data structure later (when you need that identifier). And please, for the sake of us users, load whatever you are trying to load sequentially - because if you don't, we'll get the IO errors you aren't handling...
First let me tell you why it doesn't work as you expect.
What is happening is, the for is looping through your elements, and creates all the loaders, incrementing i, but the Event.COMPLETE happens sometime later, where the i is already at the value 2, so that's why you get that output.
As wvxvw suggested, you need some more data structure, something like this:
class MyLoader {
private var i: int;
private var loader: Loader;
function MyLoader(i:int) {
this.i = i;
loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoaded);
}
function onLoaded(event:Event)
{
trace(i);
}
}
And you will use it in your loop:
var i:int = 0;
for each(obj in myData) {
var loader:MyLoader=new MyLoader(i++);
}
Of course, you will need to add lots more to that MyLoader, like handling the errors, and pass more meaningful things to make everything work.