Forcing update rendering of MX ProgressBar? - actionscript-3

Would anyone know a method (or trick) to force a rendering update to an MX ProgressBar in manual mode when using setProgress?
I have a situation with a block of code containing a couple of for loops which take a bit of time to complete. It would be tedious to unwrap this code to generate events, etc.
Update
Let me expand on this with a bit of pseudo code. I want to update the progress bar during operations on the contents of an array. THe for loops blocks so the screen isn't updating. I've tried validateNow() but that had no effect.
Is there some non-convoluted way I can either unwrap the for loop or use AS3's event model to update a progress bar? (I'm more accustomed to multi-threaded environments where this sort of task is trivial).
private function doSomeWork():void {
progressBar.visible = true;
for(var n = 0; n < myArray.length; n++){
progressBar.setProgress(n, myArray.length);
progressBar.label = "Hello World " + n;
progressBar.validateNow(); // this has no apparent effect
var ba:ByteArray = someDummyFunction(myArray[i]);
someOtherFunction(ba);
}
progressBar.visible = false;
}

In Flex, the screen is never updating while Actionscript code is running. It basically works like this:
Execute all runnable Actionscript code.
Update the screen.
Repeat continuously.
To learn more details, google for [flex elastic racetrack]. But the above is the nut of what you need to understand.
If you don't want a long-running piece of code to freeze the screen, you'll have to break it up into chunks and execute them across multiple frames, perhaps within a FRAME_ENTER event handler.

I am not sure what exactly is the problem. I tried the following code and it works without any need to validateNow.
protected function button2_clickHandler(event:MouseEvent):void
{
for(var n:int = 0; n < 100; n = n+20){
progressBar.setProgress(n, 100);
progressBar.label = "Hello World " + n;
// progressBar.validateNow();
}
}
<mx:VBox width="100%" height="100%">
<mx:ProgressBar id="progressBar"/>
<mx:Button label="Update Progress" click="button2_clickHandler(event)"/>
</mx:VBox>

Related

How to Adjust Volume in an Audio loop?

How would someone change sound levels of a music playing in a loop? For example, I'm making a game and at a certain frame I want the music level (music.wav) to be decreased to half of its volume.
How could someone do this in AS3?
You are using the word "loop" in a confusing way. In programming, a loop usually refers to one of the "for" loops that looks like this:
for (var i:int = 0; i < 10; i++)
{
//do stuff 10 times
}
I surmise that this is not what you mean by loop, but rather that you would like a MovieClip or the main timeline to decrease the volume of a Sound object at the end of n frames. Or do you just mean the music itself is looping? Hopefully you see the value of asking well written questions. That being said..
Mind you, I haven't tried this, but according to my reference book (ActionScript 3.0 Cookbook by Lott, Schall & Peters) you need to use a SoundTransform object which specifies the volume at which you want the sound to be set. Try this:
var _sound:Sound = new Sound(music.wav); // creates a Sound object which has no internal volume control
var channel:SoundChannel = _sound.play(); // creates a SoundChannel which has a soundTransform property
var transform:SoundTransform = new SoundTransform(); // SoundTransform objects have a property called "volume". This is what you need to change volume.
Now in your loop (or on the frame event that you are using) do this:
transform.volume *= 0.9; // or whatever factor you want to have it decrease
//transform.volume /= 1.1; // or this if you prefer.
channel.soundTransform = transform; //
So anytime you want the volume to decrease by this incremental amount, run this bit of code. Of course, you need to make sure that any variables you set are accessible in the code that is referencing them. One way that comes to mind to do this is with a function.
private function soundDiminish(st:SoundTransform, c:SoundChannel, factor:Number = 0.9):void
{
st.volume *= factor;
c.soundTransform = st;
}
Now, whenever you want to diminish the volume just call the soundDiminish function.
Maybe your frame event looks like this:
function onLoadFrame(fe:Event):void
{
soundDiminish(transform, channel); // 3rd parameter optional
}
If you only want this function to be called every 20 frames then:
function onLoadFrame(fe:Event):void
{
// this is a counter that will count up each time this frame event happens
frameCount ++;
if (frameCount >= 20)
{
soundDiminish(transform, channel); // 3rd parameter optional
frameCount = 0; // reset the frame counter
}
}

Dynamically Generated Audio Garbling

I've created the standard player for dynamic sounds in a web app like so:
var _dynamicAudio:Vector.<Number> = new Vector.<Number>();
var _sampleIndex:uint = 0;
// ..Code to generate '_dynamicAudio' samples omitted
var _sound:Sound = new Sound();
_sound.addEventListener(SampleDataEvent.SAMPLE_DATA, onSampleDataEvent);
_sound.play();
function onSampleDataEvent(event:SampleDataEvent):void {
var samplesRead:int = 0;
while (samplesRead <= 8192 && _sampleIndex < _dynamicAudio.length) {
var sample:Number = _dynamicAudio[_sampleIndex];
event.data.writeFloat(sample);
event.data.writeFloat(sample);
_sampleIndex++;
_samplesRead++;
}
}
However, on slower computers (or if I stress my own by opening other applications) the audio tends to 'garble' occasionally. It sounds like it gets stuck looping over a block of the most recent samples. When this happens, I've noticed that event.position jumps by some multiple of 8192, which would also seem to imply that onSampleDataEvent isn't being called for a few consecutive chunks of samples. If that's true, it would seem this issue is unavoidable. What I'd like to know is if there's any way to detect the garbling before it happens so I can pause the audio in place of the awful noise? Thanks!
It might be that the while() loop is heavy and you don't always have the memory/CPU power to process it. So it freezes and gives you the effect you described.
It might be possible to break up the loop into smaller loops, thus evening out the CPU load. At the same time processing the event far ahead enough to seamlessly playback the audio.
The following code is not tested and is only an example of how breaking up a loop could look like:
var _dynamicAudio:Vector.<Number> = new Vector.<Number>();
var _sampleIndex:uint = 0;
// ..Code to generate '_dynamicAudio' samples omitted
var _sound:Sound = new Sound();
_sound.addEventListener(SampleDataEvent.SAMPLE_DATA, onSampleDataEvent);
_sound.play();
function onSampleDataEvent(event:SampleDataEvent):void {
var samplesRead:int = 0;
writeSamples(0, event);
}
function writeSamples(counter:int, eventReference:SampleDataEvent):void
{
var startTime:int = getTimer();
while (samplesRead <= (8192 - counter) && _sampleIndex < _dynamicAudio.length)
{
// check if the processing takes too long
if (getTimer() - startTime > 5)
{
writeSamples(counter, eventReference);
break;
}
counter++;
var sample:Number = _dynamicAudio[_sampleIndex];
event.data.writeFloat(sample);
event.data.writeFloat(sample);
_sampleIndex++;
_samplesRead++;
}
}
The main idea of the previewed code is to:
1) Check if the while loop is executed for too long. In my example 5ms is the limit
2) Break the loop if the execution is too long and start the loop again from the place it was stopped at.
If the whole process takes less than the limit, the loop will finish in one go.
You may want to refactor the code as it is not optimized and creates a lot of garbage instead of reusing objects.

How can I optimise this method?

I have been working on creating an assets class that can generate dynamic TextureAtlas objects whenever I need them. The specific method is Assets.generateTextureAtlas() and I am trying to optimise it as much as possible as I quite frequently need to regenerate texture atlas's and was hoping to get a better time than my 53ms average.
53ms is currently costing me about 3 frames which can add up quickly the more items I need to pack inside my texture atlas and the frequency I need to generate them. So an answer to all the pitfalls within my code would be great.
The entire class code is available here in a github gist.
The RectanglePacker class is simply used to pack rectangles as close together as possible (similar to Texture Packer) and can be found here.
For reference, here is the method:
public static function generateTextureAtlas(folder:String):void
{
if (!_initialised) throw new Error("Assets class not initialised.");
if (_renderTextureAtlases[folder] != null)
{
(_renderTextureAtlases[folder] as TextureAtlas).dispose();
}
var i:int;
var image:Image = new Image(_blankTexture);
var itemName:String;
var itemNames:Vector.<String> = Assets.getNames(folder + "/");
var itemsTexture:RenderTexture;
var itemTexture:Texture;
var itemTextures:Vector.<Texture> = Assets.getTextures(folder + "/");
var noOfRectangles:int;
var rect:Rectangle;
var rectanglePacker:RectanglePacker = new RectanglePacker();
var texture:Texture;
noOfRectangles = itemTextures.length;
if (noOfRectangles == 0)
{
return;
}
for (i = 0; i < noOfRectangles; i++)
{
rectanglePacker.insertRectangle(Math.round(itemTextures[i].width), Math.round(itemTextures[i].height), i);
}
rectanglePacker.packRectangles();
if (rectanglePacker.rectangleCount != noOfRectangles)
{
throw new Error("Only " + rectanglePacker.rectangleCount + " out of " + noOfRectangles + " rectangles packed for folder: " + folder);
}
itemsTexture = new RenderTexture(rectanglePacker.width, rectanglePacker.height);
itemsTexture.drawBundled(function():void
{
for (i = 0; i < noOfRectangles; i++)
{
itemTexture = itemTextures[rectanglePacker.getRectangleId(i)];
rect = rectanglePacker.getRectangle(i, rect);
image.texture = itemTexture;
image.readjustSize();
image.x = rect.x + itemTexture.frame.x;
image.y = rect.y + itemTexture.frame.y;
itemsTexture.draw(image);
}
});
_renderTextureAtlases[folder] = new TextureAtlas(itemsTexture);
for (i = 0; i < noOfRectangles; i++)
{
itemName = itemNames[rectanglePacker.getRectangleId(i)];
itemTexture = itemTextures[rectanglePacker.getRectangleId(i)];
rect = rectanglePacker.getRectangle(i);
(_renderTextureAtlases[folder] as TextureAtlas).addRegion(itemName, rect, itemTexture.frame);
}
}
Well reading the project & finding what all can be optimized would sure take time.
Start by removing multiple calls to rectanglePacker.getRectangle(i) inside loops.
For example :
itemName = itemNames[rectanglePacker.getRectangleId(i)];
itemTexture = itemTextures[rectanglePacker.getRectangleId(i)];
rect = rectanglePacker.getRectangle(i);
perhaps, could have been:
rect = rectanglePacker.getRectangle(i);
itemName = itemNames[rect];
itemTexture = itemTextures[rect];
If getRectangle does indeed just 'get a rectangle' & not set anything.
I think the bigger issue at hand is this, why oh why do you HAVE to do this during run-time, in a situation when this can't take more time? This IS an expansive operation, no matter how much you optimize this you will probably end up with it taking about 40ms or similar when done in AS3.
This is why these kind of operations should be done during compile time or during "loading screens" or other "transitions" when frame-rate is not critical and when you can afford it.
Alternatively create another system in c++ or some other language which can actually handle the number-crunching that gives you the finished result.
Also, when it comes to checking performance, yes the entire function takes 53ms, BUT, where are those milliseconds used? 53ms says nothing and is only the "overhead profiling thing" where you found the culprit, you need to break it down into smaller chunks to gather some reliable information about what it is that ACTUALLY takes time, inside that function.
I mean, inside that function, you have 3 for loops, several calls to other classes, casts, deletes, creations. It's not like you are doing one thing, that function probably results in ~500 lines of code and a bazillion cpu operations. And, you have no idea where it is used. I would guess that it is the rectanglePacker.packRectangles(); that takes 60% of that time, but without profiling, you and we don't know on what to optimize, we simply don't have sufficient data.
If you HAVE to do this during run-time in AS3, I would recommend doing this spread out during several frames and distributing workload evenly during 10 frames or so. You could also doing it with help of another thread and workers. But most of all, this seems like a design error since this could probably be done at another time. And if not, then in another language which is better at these kind of operations.
The easiest way to profile this is to add a couple of timestamps similar to:
var timestamps:Array = [];
And then push getTimer() at different places in code, and then print them out when function is done
As others said, it's unlikely that the reason of bad performance is non-optimized AS code. Output from the profiler (Scout, for example) wold be very helpful. However, if your purpose is just adding new textures, I can suggest several optimizations:
Why would you need to re-generate the whole atlas every time (calling Assets.getTextures() and creating new render texture)? Why don't you just add new items to the existing atlas? Creation of a new RenderTexture (and, thus, a new texture in GPU memory) is very costly operation, because it requires sync between CPU and GPU. On the other hand, drawing into RenderTexture is carried out entirely inside GPU, so it takes much less time.
If you place every item on a grid, then you can avoid using RectanglePacker as all of your rectangles can have the same dimensions matching the dimensions of a grid.
Edit:
To clarify, some time ago I had a similar problem: I had to add new items to the existing atlas on a regular basis. And the performance of this operation was quite acceptable (about 8ms on iPad3 using 1024x1024 dynamic texture). But I used the same RenderTexture and the same Sprite object that contained my dynamic atlas items. When I need to add a new item, I just create new Image with desired texture (stand-alone or from another static atlas), then place it inside the Sprite container, and then redraw this container to the RenderTexture. Similarly with deletion/modification of an item.

How to make smooth moving using as3?

I have loaded some images through XML and attached into dynamically created MovieClips named mc0,mc1,mc2...etc.
_loader.removeEventListener(ProgressEvent.PROGRESS, onLoadingAction);
count++;
var img:Bitmap = Bitmap(e.target.content);
img.cacheAsBitmap = true;
img.smoothing = true;
img.alpha = 0;
TweenLite.to(MovieClip(my_mc.getChildByName("mc"+count)).addChild(img),1, {alpha:1,ease:Quint.easeIn});
and within ENTER_FRAME handler
for (i=0; i < mc.numChildren; i++)
{
my_mc.getChildAt(i).x -= Math.round((mouseX-stage.stageWidth/2)*.006);
}
Everthing works fine. But it is shaking so that it was not looking good.
How do I achieve smooth movement?
One solution I've used is to round the (x,y) position to the closest integer. No matter that you've added smoothing to your bitmap and cached it, rounding could make it feel less choppy and way smoother.
Another thing you need to be careful is the dimensions of the images. Images that have an odd dimension won't be smoothed the same way as images with even dimensions. Check how to workaround this in my blog post Flash Smoothing Issue.
Since Flash has a variable frame rate (in the sense that it will drop frames), one shouldn't depend on the entering of a frame as a unit of action. Rather, it would be wiser to calculate the elapsed time explicitly.
For instance, in the enter frame handler:
var currentTime:Number = (new Date()).time;
for (i=0; i < mc.numChildren; i++)
{
my_mc.getChildAt(i).x -= speed * (currentTime - lastTime); // speed is in px/ms
}
lastTime = currentTime;
where you have the variable lastTime declared somewhere in a persistent scope:
var lastTime:Number = (new Date()).time;
I don't know if this addresses what you are calling "shaking", but it's at least something to consider.

For-loop variable scope confusion

I have noticed a weird behavior of the variables in for loops. It's not really a problem, but it disturbs me a lot.
Actually I've created two loops this way:
for (var i:uint; i<19; i++) SomeFunction (i);
for (var i:uint; i<26; i++) SomeOtherFunction (i);
What I received was a compilation warning:
Warning: Duplicate variable definition.
This warning really surprised me. Nothing like that ever happened to me in other languages.
It seems that the i variable gets into the scope that is higher in the hierarchy and becomes available out of the loop's block. I've also tried to embrace the loop block in a curly brace, but it didn't change anything.
Why does it happen? Is it normal? Is it possible to avoid it? For now I've just set different names for both of the variables, but that's not a real solution I think. I'd really like to use the i-named variable in most of my for-loops.
yes, the loop increment variable is in the scope of the loops parent, not inside the loop itself. This is intentional, for examples like this:
public function getPositionOfValue ( value:String ) : int
{
for ( var i:int = 0; i < someArray; i++ )
{
if (someArray[i] == value )
{
break;
}
}
return i;
}
this allows you to access the value of i once the loop is over. There are lots of cases where this is very useful.
What you should do in the cases where you have multiple loops inside the same scope is var the i outside of the loops:
public function getPositionOfValue ( value:String ) : int
{
var i:int;
for ( i = 0; i < 15; i++ )
{
//do something
}
for ( i = 0; i < 29; i++ )
{
//do something else
}
return i;
}
then you get rid of your warning. The other thing to consider is to name your loop increment variables something more descriptive.
Update: Two other things to consider:
1) you shouldn't use uints except for things like colors and places where Flex expects a uint. They are slower than int's to use. Source]1 Update: it looks like this may no longer be the case in newer versions of the flash player: source
2) when you var a loop increment variable inside of a loop declaration, you want to make sure you set it to the proper initialization value, usually 0. You can get some hard to track down bugs if you dont.
As mentioned here, as3 has global and local scope and that's about it.
It does not do block-level scoping (or for-level either). With hoisting, you can even write to variables before you define them. That's the bit that would do my head in :-)
Early versions of Visual C had this bug, leading to all sorts of wonderful funky macro workarounds but this is not a bug in as3, it's working as designed. You can either restrict your code to having the declaration in the first for only or move the declaration outside all the for statements.
Either way, it's a matter of accepting that the language works one way, even though you may think that's a bad way :-)
Declare the variable i outside the loops to avoid this. As long as you reset it (i=0) you can still use it in all loops.
var i : uint;
for (i=0; i<19; i++) SomeFunction(i);
for (i=0; i<26; i++) SomeOtherFunction(i);