Tuesday, February 7, 2012

Flash thread ( Pseudo Thread solution )

Flash (Action Script) doesn't have (yet) Threads. The answer is simple: the concurency management too complex to an average Flash (or designer) developer. Threads will be available in Flash Player 12, but what we can do until? We can emulate the Threads ( called PseudoThread ).

The Flash Player has only limited thread ( example: request from server with async, event handling, video rendering ), but how can we delegate (or emulate) a Thread? (Not) Simply :) If you run a huge while or for each, you experienced that the UI froze.... Because the Flash Player has frames (and frame times too), so if your code don't fit in a frame time, the Flash can't update the UI.

The solution is: Use PseudoThread, and slice your function to. What is function slicing? If your program do A->B->C->D->E you can split "virtually" 5 segment. If you know, what is the actual segment that need to run, and rebuild the function state ( the variables that use common each segment ), you can pass the A, and some frametime later you can pass the B segment and so on.



protected function button2_clickHandler( event : MouseEvent ) : void {
  var thread :
PseudoThread = new PseudoThread( systemManager, primeGen, { 'from' : 0, 'to' : 10000 } );
  thread.addEventListener( "threadComplete", threadCompleteHandler );
}



protected function primeGen(obj:Object):Boolean {
  var divs : Number = 0;
  var fromPrime : Number = 2;
  if ( obj.currentPrime == null ) {
    obj.currentPrime = Number( obj.from );
  }
  var i : Number = Number( obj.currentPrime );
  divs = 0;
  for ( var j : Number = 1; j <= i; j++ ) {
    if ( i % j == 0 ) { divs++; }
    if ( divs > 2 ) break;
  }
  if ( divs == 2 ) {
    primes.text += i.toString() + "\n";
  }
  obj.currentPrime++;
  return ( obj.currentPrime < obj.to );
}

protected function threadCompleteHandler( event : Event ) : void {
  trace( 'finished' );
}


What are we doing? Simple: pass an object ( with from and to in this example ), and each frametime we calculate only one prime. There is a currentPrime value that tell us, what is the number that we will test ( increment the value each time ), and the function return value always TRUE ( in the end, the return value will be FALSE ).

How can we slice other program codes? If you split your code to segment ( code blocks ), and pass the value currentSegment=1..5... and the program check the currentSegment with a switch() case ( the shared values need to save! ). That's all, until the Flash Player 12 released. :)

Note: The primeGen function is not the worst algoritms to test the number, only demonstrate it slow function not froze the whole application. ( you can optimize the function to check the div only the sqrt of the actual number).

The PseusoThread code is:

package
{
 import flash.display.DisplayObjectContainer;
 import flash.events.Event;
 import flash.events.EventDispatcher;
 import flash.events.KeyboardEvent;
 import flash.events.MouseEvent;
 import flash.utils.getTimer;
 import mx.core.UIComponent;
 import mx.managers.ISystemManager;

 public class PseudoThread extends EventDispatcher
 {
     public function PseudoThread(sm:ISystemManager, threadFunction:Function, threadObject:Object)
     {
         fn = threadFunction;
         obj = threadObject;

         // add high priority listener for ENTER_FRAME
         sm.stage.addEventListener(Event.ENTER_FRAME, enterFrameHandler, false, 0);
         sm.stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
         sm.stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler);
        
         thread = new UIComponent();
         sm.addChild(thread);
         thread.addEventListener(Event.RENDER, renderHandler);
     }

     // number of milliseconds we think it takes to render the screen
     public var RENDER_DEDUCTION:int = 10;

     private var fn:Function;
     private var obj:Object;
     private var thread:UIComponent;
     private var start:Number;
     private var due:Number;

     private var mouseEvent:Boolean;
     private var keyEvent:Boolean;

     private function enterFrameHandler(event:Event):void
     {
        start = getTimer();
        var fr:Number = Math.floor(1000 / thread.systemManager.stage.frameRate);
        due = start + fr;

        thread.systemManager.stage.invalidate();
        thread.graphics.clear();
        thread.graphics.moveTo(0, 0);
        thread.graphics.lineTo(0, 0);  
     }

     private function renderHandler(event:Event):void
     {
         if (mouseEvent || keyEvent)
             due -= RENDER_DEDUCTION;

         while (getTimer() < due)
         {
            if (!fn(obj))
            {
                if (!thread.parent)
                    return;

                var sm:ISystemManager = thread.systemManager;
                sm.stage.removeEventListener(Event.ENTER_FRAME, enterFrameHandler);
                sm.stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
                sm.stage.removeEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler);
                sm.removeChild(thread);
                thread.removeEventListener(Event.RENDER, renderHandler);
                dispatchEvent(new Event("threadComplete"));
            }
         }

         mouseEvent = false;
         keyEvent = false;
     }

     private function mouseMoveHandler(event:Event):void
     {
        mouseEvent = true;
     }

     private function keyDownHandler(event:Event):void
     {
        keyEvent = true;
     }
 }
}


No comments:

Post a Comment