Every once in a while, you might have some operation that goes into a loop that simply takes more than a few seconds to complete. While your application is in processing the task, the Flash Player just seems to come to a complete halt. Even worse, if the task takes more than 20 seconds, the browser assumes that the Flash Player crashed, which is not the case, it’s just busy processing the task.
Why does this happen? Well, it’s quite simple, when it comes to executing ActionScript, the Flash player is not multithreaded nor it performs any kind of preemptive multitasking. In other words, when the Flash Player is executing a particular function of ActionScript, it will not do anything else, nor it will interrupt that execution, it will simply continue to execute that function until it is done. The Flash player will not execute two event handlers at the same time. It will execute one, when that one is finish, it will move to the next and so on… When that is finished, it will then render the graphics corresponding to that frame.
You might wonder why it is designed this way. Well, that’s an easy answer, to keep things simple for developers. Believe it or not, writing multithreaded multitasking applications is significantly more complex than writing single threaded non multitasking applications. In Flex or Flash you do not have to worry about having some other event of function change the values of anything while your code is executing. When you create a multithreaded application, you must be careful not to change the value of something that is being used by some other thread/task at the same time.
When the Flash Player was originally created, it wasn’t envisioned that it would be used to create really complex applications, well that is changing, and the Flash/Flex team is actually considering adding the capability to execute multiple threads to the Flash Player, as mentioned in the "Flex Roadmap" sesion at Max 2010. Here is a link to the video of that session, the multithreaded part is just past the 44 minute mark: http://tv.adobe.com/watch/max-2010-develop/flex-roadmap-/ I do hope that they add this capability.
Coming back to today, up to version 10.2, the Flash Player is still a one threaded non-multitasking platform. So what do you do, when you actually have a long task and you do not want your application to lose responsiveness, even worse, look like it crashed?
The Elastic Racetrack, how Flash Player timings work.
Well, first off, it helps to understand how the Flash player works in regards to timing and frames. Back in 2005, Ted Patric wrote a great explanation for Flash Player 8 and older (AVM1) often referred as The Elastic Racetrack, then Sean Christmann took that and updated it to Flash Player 9+ and the AVM2 (ActionScript 3). If you have some minutes I suggest that you read it, but I’ll summarize it to what I consider important to understand.
For Flex developers, the Flex framework helps you forget that Flash is executed in frames. Flex usually let’s you not have to think about that, but the fact is that it IS executed in Frames. Flex application are Flash movies that are set to a default value of 24 frames per second, which you can easily change if you want in the opening Application tag with the frameRate="24" tag.
The flash player renders at a specified value of frames per second also referred as frame rate. All movies (motion pictures) and videos do the same. To represent motion, you must show a picture, and replace it with the next picture, and so on… To keep a consistent value of speed, you must set a target amount of Frames (pictures) to be executed in a specified amount of time, usually a second, which is referred to as the Frames Per Second or FPS for short.
How those this affect Flash? Well, since a core element of Flash is animation, it must swap Frames at a consistent time, but keep in mind that Flash not only renders Frame images, it also executes ActionScript and responds to events.
How and when does ActionScript get executed? Well, let’s look at the following diagram.
For example purposes, let’s assume that the Flash movie/application is set to run at 20 FPS (frames per second). It will therefore, divide that 1 second in 20 frames and each frame it will divide into a time allowed to execute ActionScript, and a time allowed to Render graphics to screen.
I did mention that the flash player also responds to events, right? Well, the flash player will sub divide that one frame into little segments by what is usually referred to as a Marshall. As mentioned in the Elastic Racetrack article, the amount of sub-segments varies depending on the FPS the movie is set to, and the operating system, so you really can’t count on how often those subdivisions are called, but you can count on the value of the frame rate.
Something important to note is that if there isn’t anything or little to render, or there are no or little ActionScript to be executed, then the Flash Player becomes “Idle”, so it doesn’t become a CPU hog.
So in brief, you have an internal clock that is tied to the Flash movie’s FPS setting that is checking to see if there is any ActionScript or rendering to be done.
Now… what happens if you do have ActionScript to be executed and executing it takes longer than the time “Reserved” for that frame? Well, simple… it goes beyond that time and extends the length of the frame and when it finishes; it goes and performs the rendering associated with that frame (if any). It then will try to make up for it on the next frame if it can, giving it a shorter time span. This is where the Elastic Racetrack term came from. If it can’t make up for it on the next frame, then you start “dropping” frames. The Flash player will try its hardest to keep the FPS, but if it can’t, it will simply drop frames. Dropping frames means that rather than executing at 20 FPS, the movie will start executing at 15 FPS (for example) or any value lower than 20 FPS, and your animations start looking jerky and the app looses responsiveness.
So what happens if the execution of an ActionScript function takes 5 seconds or more? Well, during that time, the flash player will not do anything else, it will not move to the next frame, which means the application gives the impression to the user and the browser that it came to a complete halt, which is bad.
So basically you do not want the execution of ANY ActionScript to take longer than the time that a Frame would cycle. So for example, if your Application is set to 24 FPS, you don’t want ActionScript execution to take longer than 40 milliseconds (0.04 seconds). Don’t worry, with today’s computers and the current versions of the Flash player, a lot of ActionScript code can be executed in that timeframe. You start running into trouble when you have long running loops, and when I say long, I really mean long. In the example below and old 2.6 GHz Pentium 4 can run over 700,000 times through the loop in 0.04 seconds on debug, it’s more than twice as fast when the compiled swf is set to Release build. The Flash player isn’t as slow as Steve Jobs would like you to think.
The Solution
Now that you know the basics, let’s solve the problem of how can you execute a very long running loop without losing application responsiveness. Let’s say you DO have something that will require a long lasting loop run. How do you manage to keep the Flash Player responsive, and avoid dropping frames? Well it’s simple; don’t execute code longer than the time it will take to do a frame. You also have to consider rendering time, and time to execute other events, if there are any.
Great, so how do you implement this? Change you loop so that it doesn’t run the full length all at once. Have it be capable of leaving the loop, and continue where it left off at another time.
Now there are two basic approaches you can take to come back to the loop at a later time. One is to use the FlashPlayer’s Enter_Frame event. This event is dispatched automatically every time the Flash player starts a new frame. It will usually be executed at the rate the movie is set to, but it can execute slower than that. It also can execute a little faster, but only if it’s trying to catch up from long running previous frames and it will be faster until it decides to drop the frame, or actually catches up.
The other option is to use a Timer, and have the timer be executed at a specific rate, usually shorter than the time it will take to run a full frame.
Both approaches have their positive side and a drawback.
The Enter_Frame approach allows you to almost guarantee that the frame will be used entirely either executing ActionScript and rendering, at the risk of dropping frames, this is the aggressive approach.
The Timer based approach, allows you keep a more consistent Frame rate, and rarely drop a frame, at the risk of not utilizing the CPU at 100% and let the Flash Player spend some time doing nothing.
In other words, the task will probably take longer using the timer event method, but it will be much friendlier with other processes running on the machine at the same time, plus the user will have a consistent and smoother experience.
You can, of course, control the aggressiveness factor of the Enter_Frame approach by controlling for how long you wish to hog the CPU, but the Flash Player doesn’t have an API that tells you how “busy” it is. You can, however, detect if you are dropping frames and you could adjust your timings based on that. In my example I did go as far to adjust the trimming based on detecting if it's dropping frames and it does some adjustments if it’s too busy.
The Example
This application does a very inefficient method of telling you if a number is a prime number or not. Even if it figures out it is not a prime number, it continues checking. This is intentional so that it will take a consistent long time to execute.
IMPORTANT NOTE: I have prefilled the box with 12345678. If you have something slower than a 2.6 Pentium 4, take the 8 off. If you have something faster, add one digit at a time. A factor of ten makes a big difference on how long it takes to execute. Add a digit, try it, if it runs too fast, add another digit, and try it, until it takes somewhere between 3 – 20 seconds, your choice. This is particularly important when using the Sync Normal method, because if it takes too long, the browser will think that the Flash Player crashed.
Now here is an interesting note. It should always execute the Synch normal method faster than any of the Async methods; it simply has a lot less to do. But it is possible that it may actually take longer to execute. This is because of the Flash Player’s JIT (Just In Time) compiler. The Flash Player JIT doesn’t compile everything; it never compiles Constructors for example. If it thinks that something will not be executed multiple times, it will not compile it, as compiling takes time, but when it does compile something the performance gain is often over twice as fast. So it may decide not to compile the Sync method, which it did to me on one occasion. The Flash Player’s JIT acts at runtime taking ActionScript compiled bytecode and translates it into Native Machine code that runs "natively" on the computer's procesor.
Well, here is the App Running with View Source code enabled.
It has 4 methods of execution.
The first is the standard synchronous method. It goes into the loop and will not exit until it has finished. This is how usually loops are built.
The second method is the same formula adjusted so that it can exit after a specified amount of time, and continue in the next Enter_Frame event. This method can be adjusted manually. Since this movie is set to run at 24 fps, then the sweet spot regarding the timing should be about 40 milliseconds (1000 / 24 = 41.6666). The other value (loop count) is the inner loop runs. When you look at the code you will notice that I have two loops. I don’t what to check the time for every loop run, because it is not worth it. It does a loop in far less than one millisecond, doing the time check is a complete waste of processor time, so I run the calculations for a specified amount of loop runs, before I check the time again. In theory, the larger this number, the more efficient it will be, but if it’s too large, you risk extending beyond the allowed time.
The third method is pretty much like the second, except that it decides automatically for how long it should run the loop (based on the movies FPS setting) and it adjusts the loop count parameter automatically to be more efficient.
The forth method is like the third, but rather than working with Enter_Frame events, it uses Timer events, and fires 3 times for each frame, rather than one as the third method. With this method you let the Flash Player decide if it should execute the task routine on the current frame or on the next frame, and you don’t force it to execute the routine on the current frame. The main benefit is that it will most likely maintain the frame rate specified in the movie, and it will be friendlier to other flash player instances or other things the player might be doing. The main drawback is that it’s harder to keep the processor pegged at 100% which means that it will take longer to finish the task.
Now let’s take a look at the project structure.
As you can see, it’s a fairly simple project. It has a class that calculates the frame rate and displays them on the screen which is the FPSCalculator and there is also a bouncing-ball.swf which contains the animation shown on the left side of a bouncing ball. Following that, there are four classes that correspond to the four methods, and finally the main executable class.
Let’s start with the main class. The LongTimeExecution.mxml file:
<?xml version="1.0" encoding="utf-8"?> <!--- This is a sample application that demonstrates how to take a long time consuming operation that will usually make the Flash player stop responding and change it so that it can perform the operation but keep the player responsive. For more Flex examples and Flex learning guides visit: http:www.artonflex.com --> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" height="332" width="259" frameRate="24" enterFrame="fpsCalculator.enterFrameHandler(event)" creationComplete="creationCompleteHandler()" horizontalScrollPolicy="off" verticalScrollPolicy="off" viewSourceURL="srcview/index.html"> <mx:Script> <![CDATA[ import testprime.TestPrimeNumberAsyncTimerSmart; import testprime.TestPrimeNumberAsyncSmart; import fps.FPSCalculator; import flash.utils.getTimer; import testprime.TestPrimeNumberAsyncSimple; import mx.controls.Alert; import testprime.TestPrimeNumberSync; /******* * The regular synchronous non responsive method */ private var primeTesterSync:TestPrimeNumberSync = new TestPrimeNumberSync(); /******* * A simple adjustable asynchronous method that keeps the application responsive. */ [Bindable] private var primeTesterAsyncSimple:TestPrimeNumberAsyncSimple = new TestPrimeNumberAsyncSimple(); /******* * An auto-configurable asynchronous method that keeps the application responsive. */ private var primeTesterAsyncSmart:TestPrimeNumberAsyncSmart = new TestPrimeNumberAsyncSmart(); /******* * A timer based auto-configurable asynchronous method that keeps the application responsive. */ private var primeTesterAsyncTimerSmart:TestPrimeNumberAsyncTimerSmart = new TestPrimeNumberAsyncTimerSmart(); private var startTime:int; [Bindable] private var isRunningCalculations:Boolean = false; private var fpsCalculator:FPSCalculator = new FPSCalculator(); /******** * Peformes the calculation based on the option selected */ private function doCalculation():void { startTime = getTimer(); isRunningCalculations = true; if (operationSync.selected) { handleResult(primeTesterSync.isPrime(Number(enteredValueTextBox.text))); } else if (operationAsyncSimple.selected) { primeTesterAsyncSimple.isPrime( Number(enteredValueTextBox.text),handleResult); } else if (OperationAsyncSmart.selected) { primeTesterAsyncSmart.stage = stage; primeTesterAsyncSmart.isPrime( Number(enteredValueTextBox.text),handleResult); } else if (OperationAsyncTimer.selected) { primeTesterAsyncTimerSmart.stage = stage; primeTesterAsyncTimerSmart.isPrime( Number(enteredValueTextBox.text),handleResult); } } /********** * Receives the result for the Asyncronous operations */ private function handleResult(result:Boolean):void { var endTime:int = getTimer(); isRunningCalculations = false; statusLabel.text = "Finished"; var outText:String = "The number you entered is"; if (!result) outText += " NOT"; outText += " prime. Calculations took " + Number(Number(endTime - startTime)/1000.0).toFixed(3) + " Seconds"; Alert.show(outText,"Result"); } /************** * Stops the calculations. Not that the Synchronous operation is not stopable */ private function stopHandler():void { if (operationSync.selected) { // Trying to stop it is pointless } else if (operationAsyncSimple.selected) { primeTesterAsyncSimple.stop = true; } else if (OperationAsyncSmart.selected) { primeTesterAsyncSmart.stop = true; } else if (OperationAsyncTimer.selected) { primeTesterAsyncTimerSmart.stop = true; } } /******** * Called when the application is complete and assigns * the text box to the FPS calculator */ private function creationCompleteHandler():void { fpsCalculator.updatedTextField = framteRateBox; } ]]> </mx:Script> <mx:SWFLoader x="4" y="10" source="images/bouncing-ball.swf"/> <mx:Label x="75" y="3" text="Enter a value to check if prime:"/> <mx:TextInput x="105" y="21" width="144" id="enteredValueTextBox" text="12345678"/> <mx:Button x="114" y="52" label="Calculate" id="calculateButton" click="doCalculation()" width="81" enabled="{!isRunningCalculations}"/> <mx:Button x="200" y="52" label="Stop" id="stopBtn" click="stopHandler()" enabled="{isRunningCalculations}"/> <mx:Label x="133" y="80" id="statusLabel" text="{(isRunningCalculations)?'Calculating':'Idle'}"/> <mx:RadioButtonGroup id="operationTypeSelection" /> <mx:RadioButton x="114" y="102" label="Sync Normal" id="operationSync" groupName="operationTypeSelection" enabled="{!isRunningCalculations}" selected="true"/> <mx:RadioButton x="114" y="120" label="Async Simple" id="operationAsyncSimple" groupName="operationTypeSelection" enabled="{!isRunningCalculations}" /> <mx:RadioButton x="114" y="199" label="Async Smart" id="OperationAsyncSmart" groupName="operationTypeSelection" enabled="{!isRunningCalculations}"/> <mx:RadioButton x="114" y="219" label="Async Radio" groupName="operationTypeSelection" id="OperationAsyncTimer" enabled="{!isRunningCalculations}"/> <mx:Label x="82" y="149" text="Loop Count:"/> <mx:Binding destination="primeTesterAsyncSimple.continousLoopAmout" source="loopCountValue.value" /> <mx:NumericStepper x="156" y="145" id="loopCountValue" minimum="1000" maximum="999900" stepSize="100" enabled="{operationAsyncSimple.selected }" value="{primeTesterAsyncSimple.continousLoopAmout}" /> <mx:Label x="106" y="176" text="Run time:"/> <mx:Binding destination="primeTesterAsyncSimple.maxContinousRunTime" source="maxRunTimeValue.value" /> <mx:NumericStepper x="171" y="175" id="maxRunTimeValue" minimum="5" maximum="1000" stepSize="5" enabled="{operationAsyncSimple.selected}" value="{primeTesterAsyncSimple.maxContinousRunTime}" /> <mx:Text x="9" y="245" width="240" id="framteRateBox"/> </mx:Application>
That class basically sets everything to be called according to the user requests.
The next class is the one you would typically write, which performs synchronous operations. Here is the TestPrimeNumberSync.as file:
package testprime { /*************************************** * Class used to calculate if a number is prime or not. * The calculation method is VERY inefficient on purpose. For * starters, if it knows it's not a prime it should quit immediately, * but it was designed this way to create a long and simple task. * * This class performs a synchronous operation, which means that once * the isPrime function is invoked it will complete the task and return * the result to the caller. The caller will not execute the next line * of code until the task is complete, unlike asynchronous operations * which are set to be run in the background or at a later time so that * the caller can continue its execution. You probably write most of * your classes to perform synchronous operations. */ public class TestPrimeNumberSync { /*************** * Calculates synchronously if the number supplied is a Prime * number (divisible only by itself and by one). * @param value The number that will be tested * @return true if it's prime of false if it's not prime */ public function isPrime(valueToTest:Number):Boolean { var currentDivisor:Number; var isPrime:Boolean = true; valueToTest = Math.round(valueToTest); valueToTest = Math.abs(valueToTest); if (valueToTest == 1) return true; else if ((valueToTest == 0)||(valueToTest == 2)) return false; for (currentDivisor = 2; currentDivisor < valueToTest; currentDivisor++) { if ((valueToTest % currentDivisor) == 0) { isPrime = false; } } return isPrime; } } }
Great, let's start looking at the asynchronous implementation which help keep the application responsive. Let's start with the simplest one using the Enter_Frame event. Here is the TestPrimeNumberAsyncSimple.as file:
package testprime { import flash.display.Shape; import flash.events.Event; import flash.utils.getTimer; /*************************************** * Class used to calculate if a number is prime or not. * The calculation method is VERY inefficient on purpose. For * starters, if it knows it's not a prime it should quit immediately, * but it was designed this way to create a long and simple task. * * This is a simple async class that peforms a long computation. * Rather than going into a loop until it finishes the loop, it will go * into the loop for a specified amount of time, if it exceeds that time * it will break out of the loop and will continue in the next enter * frame event until it completes the task or the user requests to stop * the operation. Since it is asynchronous, it does not return a value * immediately, a callback function must be supplied so that it can be * called when the task is complete. Alternatively, you could use an * event to send the result. * * For more Flex examples visit: http://www.artonflex.com */ public class TestPrimeNumberAsyncSimple { /*************** * This is a non-visual class, therefore it does not * generate a enter frame event, but by instantiating a * Shape, without having to add it to the stage you can * attach a ENTER_FRAME event to the shape. */ private var enterFrameShape:Shape = new Shape(); /************** * The current divisor we are testing. */ private var currentDivisor:Number; /************** * Flag that indicates if our value is Prime or not. */ private var isItPrimeFlag:Boolean; /************* * The value which we are testing to see if it's prime or not. */ private var valueToTest:Number; /************* * In order to not waste time checking the elapsed time for * every calculation, we run an internal loop that performs * the calculations without checking the time. This is the * amount of runs the internal loop performs before checking * the time. */ private var continousLoopAmountNumber:Number = 1000; // loop interations [Bindable] public function get continousLoopAmout():Number { return continousLoopAmountNumber; } public function set continousLoopAmout(value:Number):void { continousLoopAmountNumber = value; } /************** * This is the amount of time in milliseconds that the loop will * run uninterrupted. Once it exceeds this time, it will exit the * loop and will pick up the task on the next frame. For “optimal” * results it should be close to the amount of time it takes to * perform on frame. For example, if the FPS is set to 24, this * value should be set to approximately 40. */ private var maxContinousRunTimeInt:int = 40; // in milliseconds [Bindable] public function get maxContinousRunTime():Number { return maxContinousRunTimeInt; } public function set maxContinousRunTime(value:Number):void { maxContinousRunTimeInt = int(value); } /************** * Flag used to indicate that the user wishes to stop the task. */ public var stop:Boolean = false; /************** * This is the callback function that will be called when the task * is complete. It will pass one parameter it being a Boolean * (true/false) that indicates if the number is prime or not. */ private var finishedFunction:Function = null; // Since it's async a function to call /*************** * Calculates asynchronously if the number supplied is a Prime * number (divisible only by itself and by one). * @param value The number that will be tested * @param finishedFunctionCallback the function that will be called * when the task is complete. It should expect one boolean parameter * that indicates if the value suplied is prime(true) or not(false). */ public function isPrime(value:Number, finishedFunctionCallback:Function):void { isItPrimeFlag = true; valueToTest = value; valueToTest = Math.round(valueToTest); valueToTest = Math.abs(valueToTest); finishedFunction = finishedFunctionCallback; if (finishedFunction == null) throw new Error("Finished Callback may not be null"); stop = false; if (valueToTest == 1) { finishedFunction(true); return; } else if ((valueToTest == 0)||(valueToTest == 2)) { finishedFunction(false); return; } currentDivisor = 2; // Proceed to set the enter frame event to the shape so that // the runTestIsPrime function is called on every enter frame event. if (!enterFrameShape.hasEventListener(Event.ENTER_FRAME)) { enterFrameShape.addEventListener(Event.ENTER_FRAME,runTestIsPrime,false,0,true); } } /***************** * Function that actually will calculate if the number is prime or not. * It will run for a specified time, and will quit and continue on the * next frame event. * @param event the ENTER_FRAME event */ private function runTestIsPrime(event:Event):void { // We check if the user has requested the task to be stopped once // per frame entry. It is pointless to check in the loop, as the // Flash player not being multithreaded, there is absolutely no chance // that this value will change will the loop is being run. if (stop) { // must remove the ENTER_FRAME handler enterFrameShape.removeEventListener(Event.ENTER_FRAME,runTestIsPrime); finishedFunction(false); // Should pass something more informative. return; } var beginTime:int = getTimer(); var totalRuns:int = 0; var currentLoopCount:int = 0; // We have two loops, one inside the other. The outer loop is the one that // checks to see if the time elapsed since we entered this function is longer // that the time set to run the function. It is a waste of calculations to // test this for every loop run, so the internal loop will run for a specified // amount of times before checking the time again. This means that it is possible // to go beyond the time, but if the internal loop iterations are small enough it // will not be “noticeable”. On a Pentium 4 it will do over 300,000 loop runs in 100ms. // This function is inefficient on purpose to make the task take a longer period of time. while ((currentDivisor < valueToTest)&& ((getTimer() - beginTime) < maxContinousRunTimeInt)) { currentLoopCount = 0; while ((currentDivisor < valueToTest) && (currentLoopCount < continousLoopAmountNumber)) { if ((valueToTest % currentDivisor) == 0) { isItPrimeFlag = false; } currentDivisor++; currentLoopCount++; } totalRuns += currentLoopCount; } trace ("Total Runs: " + totalRuns + " Time: " + (getTimer() - beginTime)); if (currentDivisor >= valueToTest) // Check to see if we are done. { enterFrameShape.removeEventListener(Event.ENTER_FRAME,runTestIsPrime); finishedFunction(isItPrimeFlag); } } } }
Now let's take a look at a more automated and smarter version. Here is the TestPrimeNumberAsyncSmart.as file:
package testprime { import flash.display.Shape; import flash.display.Stage; import flash.events.Event; import flash.sampler.startSampling; import flash.utils.getTimer; /*************************************** * Class used to calculate if a number is prime or not. * The calculation method is VERY inefficient on purpose. For * starters, if it knows it's not a prime it should quit immediately, * but it was designed this way to create a long and simple task. * * This is a simple async class that peforms a long computation. * Rather than going into a loop until it finishes the loop, it will go * into the loop for a specified amount of time, if it exceeds that time * it will break out of the loop and will continue in the next enter * frame event until it completes the task or the user requests to stop * the operation. Since it is asynchronous, it does not return a value * immediately, a callback function must be supplied so that it can be * called when the task is complete. Alternatively, you could use an * event to send the result. * * Unlike the TestPrimeNumberAsyncSimple class, it calculates automatically * the time it should run the loop based on the current Frames Per Second set * on the current movie. It also adjust on the fly the number of internal * loops it will execute to try to be more efficient, and to try to keep the * current movie’s FPS consistent if the CPU load changes. * * For more Flex examples visit: http://www.artonflex.com */ public class TestPrimeNumberAsyncSmart { /*************** * This is a non-visual class, therefore it does not * generate a enter frame event, but by instantiating a * Shape, without having to add it to the stage you can * attach a ENTER_FRAME event to the shape. */ private var enterFrameShape:Shape = new Shape(); /************** * The current divisor we are testing. */ private var currentDivisor:Number; /************** * Flag that indicates if our value is Prime or not. */ private var isPrimeCalculated:Boolean; /************* * The value which we are testing to see if it's prime or not. */ private var valueToTest:Number; /************* * In order to not waste time checking the elapsed time for * every calculation, we run an internal loop that performs * the calculations without checking the time. This is the * amount of runs the internal loop performs before checking * the time. * * This is read-only because it will be calculated and adjusted * automatically by the class itself. */ private var continousLoopAmountNumber:Number = 1000; // loop interations public function get continousLoopAmout():Number { return continousLoopAmountNumber; } /************** * This is the amount of time in milliseconds that the loop will * run uninterrupted. Once it exceeds this time, it will exit the * loop and will pick up the task on the next frame. For “optimal” * results it should be close to the amount of time it takes to * perform on frame. For example, if the FPS is set to 24, this * value should be set to approximately 40. * * This is read-only because it will be calculated and adjusted * automatically by the class itself. */ private var maxContinousRunTimeInt:int = 40; // in milliseconds public function get maxContinousRunTime():Number { return maxContinousRunTimeInt; } /****************** * This time is used to adjunst the time based on the frame rate being * achived. If Frames are being dropped, the time is adjusted to * try to maintain the specified FrameRate because the procesor is busy. */ private var calculatedContinousRunTime:int = 40; /***************** * This is a non-visual class, so we need the stage to obtain the * current Frames Per Second value that the movie is set to, so we * can calculate how long the function should run. */ private var _stage:Stage; public function get stage():Stage { return _stage; } public function set stage(value:Stage):void { var targetFPS:int _stage = value; if (_stage !=null) { targetFPS = _stage.frameRate; } // We would like to utilized most of the processor time, but we also // want to give time to perform some other tasks, so we subtract a // milliseconds of the exact one frame time to give a chance for // other tasks to be performed and keep as close to the FPS as possible. maxContinousRunTimeInt = int( Math.floor(1000 / targetFPS)) - 1; calculatedContinousRunTime = maxContinousRunTimeInt; } /************** * Flag used to indicate that the user wishes to stop the task. */ public var stop:Boolean = false; /************** * The time captured at the begginning of the last frame time. */ private var lastFrameTime:int; /************** * This is the callback function that will be called when the task * is complete. It will pass one parameter it being a Boolean * (true/false) that indicates if the number is prime or not. */ private var finishedFunction:Function = null; // Since it's async a function to call /*************** * Calculates asynchronously if the number supplied is a Prime * number (divisible only by itself and by one). * @param value The number that will be tested * @param finishedFunctionCallback the function that will be called * when the task is complete. It should expect one boolean parameter * that indicates if the value suplied is prime(true) or not(false). */ public function isPrime(value:Number, finishedFunctionCallback:Function):void { isPrimeCalculated = true; valueToTest = value; valueToTest = Math.round(valueToTest); valueToTest = Math.abs(valueToTest); finishedFunction = finishedFunctionCallback; if (finishedFunction == null) throw new Error("Finished Callback may not be null"); stop = false; if (valueToTest == 1) { finishedFunction(true); return; } else if ((valueToTest == 0)||(valueToTest == 2)) { finishedFunction(false); return; } currentDivisor = 2; // Occasionally on the very last run, it is peformed so fast that the // continousLoopAmountNumber ends up having a wild value. If so, let's // set it back to something manageable. if ((continousLoopAmountNumber < 200) || (continousLoopAmountNumber > 50000)) continousLoopAmountNumber = 1000; // set the timmings calculatedContinousRunTime = maxContinousRunTimeInt; lastFrameTime = getTimer(); // Proceed to set the enter frame event to the shape so that // the runTestIsPrime function is called on every enter frame event. if (!enterFrameShape.hasEventListener(Event.ENTER_FRAME)) { enterFrameShape.addEventListener(Event.ENTER_FRAME,runTestIsPrime,false,0,true); } } /***************** * Function that actually will calculate if the number is prime or not. * It will run for a specified time, and will quit and continue on the * next frame event. * @param event the ENTER_FRAME event */ private function runTestIsPrime(event:Event):void { // We check if the user has requested the task to be stopped once // per frame entry. It is pointless to check in the loop, as the // Flash player not being multithreaded, there is absolutely no chance // that this value will change will the loop is being run. if (stop) { enterFrameShape.removeEventListener(Event.ENTER_FRAME,runTestIsPrime); finishedFunction(false); return; } var beginTime:int = getTimer(); var elapsedTime:int; var currentLoopCount:int=0; var totalRuns:int =0; // Check to see if we are taking longer than we should to execute this // frame. If it's taking longer than a frame we need to shorten // the time a little bit. If we are executing it too fast then // let's start with the frame time. if ((beginTime - lastFrameTime ) > (maxContinousRunTimeInt + 1)) { calculatedContinousRunTime--; } else if ((beginTime - lastFrameTime ) < (maxContinousRunTimeInt - 4)) { calculatedContinousRunTime = maxContinousRunTimeInt; } lastFrameTime = beginTime; // We have two loops, one inside the other. The outer loop is the one that // checks to see if the time elapsed since we entered this function is longer // that the time set to run the function. It is a waste of calculations to // test this for every loop run, so the internal loop will run for a specified // amount of times before checking the time again. This means that it is possible // to go beyond the time, but if the internal loop iterations are small enough it // will not be “noticeable”. On a Pentium 4 it will do over 300,000 loop runs in 100ms. // This function is inefficient on purpose to make the task take a longer period of time. while ((currentDivisor < valueToTest) && ((getTimer() - beginTime) < calculatedContinousRunTime)) { currentLoopCount = 0; while ((currentDivisor < valueToTest) && (currentLoopCount < continousLoopAmountNumber)) { if ((valueToTest % currentDivisor) == 0) { isPrimeCalculated = false; } currentDivisor++; currentLoopCount++; } totalRuns += currentLoopCount; } elapsedTime = getTimer() - beginTime; trace ("Total Runs: " + totalRuns + " Time: " + elapsedTime + " LoopCount: " + continousLoopAmountNumber + " Target Time " + maxContinousRunTimeInt); if (currentDivisor >= valueToTest) // Check to see if we are done. { enterFrameShape.removeEventListener(Event.ENTER_FRAME,runTestIsPrime); finishedFunction(isPrimeCalculated); return; } // Calculate how many inner loop runs should be performed so that the // least amount of outer loop runs is performed. We figure out how many // loops it does per millisecond and we multiply this by the amount of time // it should run. This value we divide by the amount of outer loop runs // that we want, I chose 10 in this example. The reason to have at least // 20 outer loop runs is that I want to check the time at least 20 times. // The least often I check the time, the bigger the chance of running beyond // the time, and start dropping frames and have the application become less // responsive. I take that value and average it with the previous value // giving more weight to the previous value, so that it doesn’t jump around // too much. var loopsPerMilisecond:Number = totalRuns / elapsedTime; continousLoopAmountNumber = Math.floor((((loopsPerMilisecond * (maxContinousRunTimeInt)) /20) + continousLoopAmountNumber*4)/5); } } }
Last but not least is the Timer based approach. Here is the TestPrimeNumberAsyncTimerSmart.as file:
package testprime { import flash.display.Stage; import flash.events.TimerEvent; import flash.utils.Timer; import flash.utils.getTimer; /*************************************** * Class used to calculate if a number is prime or not. * The calculation method is VERY inefficient on purpose. For * starters, if it knows it's not a prime it should quit immediately, * but it was designed this way to create a long and simple task. * * This is a simple async class that peforms a long computation. * Rather than going into a loop until it finishes the loop, it will go * into the loop for a specified amount of time, if it exceeds that time * it will break out of the loop and will continue in the next timer * event until it completes the task or the user requests to stop * the operation. Since it is asynchronous, it does not return a value * immediately, a callback function must be supplied so that it can be * called when the task is complete. Alternatively, you could use an * event to send the result. * * Unlike the TestPrimeNumberAsyncSimple and the TestPrimeNumberAsyncSmart, * this class uses a timer to call the main task function, rather than * using the enter frame event. The benefit of using the timer is that you * have the timer be called more often during a frame, giving chance for * other events to fire more often and letting the application feel more * responsive. The drawback is that more time is spent firing events and * not trying to finish the task faster. This class, like the * TestPrimNumberAsynSmart, calculates it’s values automatically based on * the Frames Per Second that the movie is set to. * * For more Flex examples visit: http://www.artonflex.com */ public class TestPrimeNumberAsyncTimerSmart { /********** * Timer used to run the loop and call the task function. */ private var mainLoopTimer:Timer = null; /************** * The current divisor we are testing. */ private var currentDivisor:Number; /************** * Flag that indicates if our value is Prime or not. */ private var isPrimeCalculated:Boolean; /************* * The value which we are testing to see if it's prime or not. */ private var valueToTest:Number; /************* * In order to not waste time checking the elapsed time for * every calculation, we run an internal loop that performs * the calculations without checking the time. This is the * amount of runs the internal loop performs before checking * the time. * * This is read-only because it will be calculated and adjusted * automatically by the class itself. */ private var continousLoopAmountNumber:Number = 500; // loop interations public function get continousLoopAmout():Number { return continousLoopAmountNumber; } /************** * This is the amount of time in milliseconds that the loop will * run uninterrupted. Once it exceeds this time, it will exit the * loop and will pick up the task on the next frame. For “optimal” * results it should be close to the amount of time it takes to * perform on frame. For example, if the FPS is set to 24, this * value should be set to approximately 10. * * This is read-only because it will be calculated and adjusted * automatically by the class itself. */ private var maxContinousRunTimeInt:int = 10; // in milliseconds public function get maxContinousRunTime():Number { return maxContinousRunTimeInt; } /***************** * This is a non-visual class, so we need the stage to obtain the * current Frames Per Second value that the movie is set to, so we * can calculate how long the function should run. */ private var _stage:Stage; public function get stage():Stage { return _stage; } public function set stage(value:Stage):void { var targetFPS:int _stage = value; if (_stage !=null) { targetFPS = _stage.frameRate; } // We would like to utilized most of the processor time, but we also // want to give time to perform some other tasks, so we divide it by 3 // to have 3 task function calls per frame. maxContinousRunTimeInt = int(( Math.floor(1000 / targetFPS)) / 3); if (mainLoopTimer != null) { mainLoopTimer.stop(); if (mainLoopTimer.hasEventListener(TimerEvent.TIMER)) { mainLoopTimer.removeEventListener(TimerEvent.TIMER,runTestIsPrime); } mainLoopTimer = null; } mainLoopTimer = new Timer(maxContinousRunTimeInt); maxContinousRunTimeInt -= 1; // Want the task to finish just before time runs out. } /************** * Flag used to indicate that the user wishes to stop the task. */ public var stop:Boolean = false; /************** * This is the callback function that will be called when the task * is complete. It will pass one parameter it being a Boolean * (true/false) that indicates if the number is prime or not. */ private var finishedFunction:Function = null; // Since it's async a function to call /*************** * Calculates asynchronously if the number supplied is a Prime * number (divisible only by itself and by one). * @param value The number that will be tested * @param finishedFunctionCallback the function that will be called * when the task is complete. It should expect one boolean parameter * that indicates if the value suplied is prime(true) or not(false). */ public function isPrime(value:Number, finishedFunctionCallback:Function):void { isPrimeCalculated = true; valueToTest = value; valueToTest = Math.round(valueToTest); valueToTest = Math.abs(valueToTest); finishedFunction = finishedFunctionCallback; if (finishedFunction == null) throw new Error("Finished Callback may not be null"); if (mainLoopTimer == null) throw new Error("Stage must be set prior to running this function"); stop = false; if (valueToTest == 1) { finishedFunction(true); return; } else if ((valueToTest == 0)||(valueToTest == 2)) { finishedFunction(false); return; } currentDivisor = 2; // Occasionally on the very last run, it is peformed so fast that the // continousLoopAmountNumber ends up having a wild value. If so, let's // set it back to something manageable. if ((continousLoopAmountNumber < 200) || (continousLoopAmountNumber > 50000)) continousLoopAmountNumber = 500; // Proceed to set the enter frame event to the shape so that // the runTestIsPrime function is called on every enter frame event. if (!mainLoopTimer.hasEventListener(TimerEvent.TIMER)) { mainLoopTimer.addEventListener(TimerEvent.TIMER,runTestIsPrime,false,0,true); } mainLoopTimer.start(); } /***************** * Function that actually will calculate if the number is prime or not. * It will run for a specified time, and will quit and continue on the * next frame event. * @param event the ENTER_FRAME event */ private function runTestIsPrime(event:TimerEvent):void { // We check if the user has requested the task to be stopped once // per frame entry. It is pointless to check in the loop, as the // Flash player not being multithreaded, there is absolutely no chance // that this value will change will the loop is being run. if (stop) { mainLoopTimer.stop(); mainLoopTimer.removeEventListener(TimerEvent.TIMER,runTestIsPrime); finishedFunction(false); return; } var beginTime:int = getTimer(); var elapsedTime:int; var currentLoopCount:int=0; var totalRuns:int =0; // We have two loops, one inside the other. The outer loop is the one that // checks to see if the time elapsed since we entered this function is longer // that the time set to run the function. It is a waste of calculations to // test this for every loop run, so the internal loop will run for a specified // amount of times before checking the time again. This means that it is possible // to go beyond the time, but if the internal loop iterations are small enough it // will not be “noticeable”. On a Pentium 4 it will do over 300,000 loop runs in 100ms. // This function is inefficient on purpose to make the task take a longer period of time. while ((currentDivisor < valueToTest) && ((getTimer() - beginTime) < maxContinousRunTimeInt)) { currentLoopCount = 0; while ((currentDivisor < valueToTest) && (currentLoopCount < continousLoopAmountNumber)) { if ((valueToTest % currentDivisor) == 0) { isPrimeCalculated = false; } currentDivisor++; currentLoopCount++; } totalRuns += currentLoopCount; } elapsedTime = getTimer() - beginTime; trace ("Total Runs: " + totalRuns + " Time: " + elapsedTime + " LoopCount: " + continousLoopAmountNumber + " Target Time " + maxContinousRunTimeInt); if (currentDivisor >= valueToTest) // Check to see if we are done. { mainLoopTimer.stop(); mainLoopTimer.removeEventListener(TimerEvent.TIMER,runTestIsPrime); finishedFunction(isPrimeCalculated); return; } // Calculate how many inner loop runs should be performed so that the // least amount of outer loop runs is performed. We figure out how many // loops it does per millisecond and we multiply this by the amount of time // it should run. This value we divide by the amount of outer loop runs // that we want, I chose 10 in this example. The reason to have at least // 10 outer loop runs is that I want to check the time at least 10 times. // The least often I check the time, the bigger the chance of running beyond // the time, and start dropping frames and have the application become less // responsive. I take that value and average it with the previous value // giving more weight to the previous value, so that it doesn’t jump around // too much. var loopsPerMilisecond:Number = totalRuns / elapsedTime; continousLoopAmountNumber = Math.floor((((loopsPerMilisecond * (maxContinousRunTimeInt)) /10) + continousLoopAmountNumber*4)/5); } } }
I'll throw in the class that calculates and displays the framerate. Here is the FPSCalculator.as file:
package fps { import flash.events.Event; import flash.utils.getTimer; import mx.controls.Text; /*************************************** * Class used to calculate the current Frames per second. * It also calculates the average frames per second. * The constant MAX_AVERAGE_COUNT decides how many frames * will be used to calculate the average. * * For more Flex examples visit: http://www.artonflex.com */ public class FPSCalculator { private var lastFrameTimesArray:Array; private var lastFrameTime:int; /************************** * Field that will be updated with the value. * Rather than using binding, it will update the info directly to be * a bit more efficient. */ public var updatedTextField:Text; /********************* * The average will be calculated using the last amount * of frames indicated in this constant. */ private const MAX_AVERAGE_COUNT:int = 24; /********************* * Constructor */ public function FPSCalculator() { lastFrameTimesArray = new Array(); lastFrameTime = getTimer(); lastFrameTimesArray.push(lastFrameTime); } /************************************** * Does the FPS calculations and updates the text field if assigned. */ public function enterFrameHandler(event:Event):void { var now:int = getTimer(); var instantElapsed:int = now - lastFrameTime; var instantFramesPerSecond:Number = Math.round(1000/instantElapsed); var capturedFrames:int = lastFrameTimesArray.length; var averageElapsed:int = now - (lastFrameTimesArray[0] as int); var averageFramesPerSecond:Number = Math.round((1000 * capturedFrames)/averageElapsed); lastFrameTime = now; lastFrameTimesArray.push(now); if (capturedFrames > MAX_AVERAGE_COUNT) { lastFrameTimesArray.shift(); //remove the first value } if (updatedTextField != null) { updatedTextField.text = "Time since last frame: " + instantElapsed + "\nInstant actual frame rate: " + instantFramesPerSecond + "\nAverage actual frame rate: " + averageFramesPerSecond + "\nDesignated frame rate: " + updatedTextField.stage.frameRate; } } } }
That's pretty much it. I do hope you found this entry informative and useful.
Merry Christmas!