views:

621

answers:

4

With ActionScript3 for Flash Player 9+, what is the nicest way to call a "once-off" function after exactly one frame?

I am aware of the Timer class and it's uses, and the callLater method on Flex UIComponents (which, glancing through the source, doesn't look very efficient in this scenario). I'm also aware of setTimeout (in flash.utils).

The Timer class and setTimeout utility are both time based, so how would we guarantee that our function will get called after exactly one frame?

From my limited testing it seems that functions passed to setTimeout only execute after at least one frame (try setting the delay to 0). But this is not guaranteed.

Of course, we could listen for Event.ENTER_FRAME events from a DisplayObject, but that seems like overkill for a once-off delayed function call.

A: 

One way would be to see how many frames per second your project is set for and let the setTimeout function delay for 1 frame time within that setting.

So if your project is set at 24 frames per second, you delay for 42 millisec in setTimeout.

Ólafur Waage
+2  A: 

Flex was intended to abstract away the frame-based nature of the Flash Player so you will not find much to help you with your problem. The best approach is to listen for ENTER_FRAME as you suggest. If that's overkill (and I'm not sure why you think it is), you could create a helper class which takes a DisplayObject and Function as arguments which will automatically add/remove the ENTER_FRAME event listener for you.

public class NextFrameCommand() {

    private var functionToCall : Function;

    public function NextFrameCommand(dObj: DisplayObject, func : Function) {
        functionToCall = func;
    }

    public function start() : void {
        dObj.addEventListener(Event.ENTER_FRAME, onEnterFrame);
    }

    private function onEnterFrame(e : Event) : void {
        e.target.removeEventListener(Event.ENTER_FRAME, onEnterFrame);
        functionToCall.call();
    }

}

I haven't tested that code but hopefully you get the idea....

cliff.meyers
Oops posted in same time, I think the best would be your structure (reusability) but with the automatic instantiation of a dummy sprite as in my example (I just wanted to quickly illustrate the idea).
Theo.T
A: 
var timerSprite:Sprite = new Sprite();

timerSprite.addEventListener(Event.ENTER_FRAME, oneFrameHandeler);

function oneFrameHandeler(e:Event):void
{
   timerSprite.removeEventListener(Event.ENTER_FRAME, oneFrameHandeler);
   timerSprite = null;

   trace("one frame later!")
}
Theo.T
A: 

Playing with the solutions provided by Theo and brd6644 I came us with this. It allows multiple functions (with parameters) to be queued and executed in order at the next frame.

public class DelayedFunctionQueue
{
    protected var queue:Array;
    protected var dispatcher:Sprite;

    public function DelayedFunctionQueue()
    {
        queue = new Array();
        dispatcher = new Sprite();
    }

    public function add( func:Function, ... args ):void
    {
        var delegateFn:Function = function():void
        {
            func.apply( null, args );
        }
        queue.push( delegateFn );
        if ( queue.length == 1 )
        {
            dispatcher.addEventListener( Event.ENTER_FRAME, onEF, false, 0, true );
        }
    }

    protected function onEF( event:Event ):void
    {
        dispatcher.removeEventListener( Event.ENTER_FRAME, onEF, false );
        queue = queue.reverse();
        while ( queue.length > 0 )
        {
            var delegateFn:Function = queue.pop();
            delegateFn();
        }
    }
}

Might be useful to someone.

Darscan