AS2 Timer class

One of the new features included with ActionScript 3.0 is the Timer class. The new Timer class is essentially an encapsulated setInterval command with an intuitive stopwatch-like interface. Creating a new Timer object requires two parameters: delay and repeat count. Once instantiated, you assign one or more listeners to the Timer which listen for events. After assigning listeners, you use the Timer object by calling it’s start(), reset() and stop() methods.

My first thought when I saw the Timer class was “Sweet, I wish this was available in AS2.” Well, it didn’t take long for me to start thinking about how I could recreate this class in AS2. A short while later I had something similiar.

com.jor.utils.Timer

/**
 * An AS2 implementation of the AS3 Timer class.
 * See http://livedocs.macromedia.com/labs/as3preview/langref/flash/utils/Timer.html
 */
import mx.events.EventDispatcher;
import com.jor.events.TimerEvent;

class com.jor.utils.Timer
{
  // Event dispatcher required functions
  private static var mixit = mx.events.EventDispatcher.initialize(com.jor.utils.Timer.prototype);
  function dispatchEvent() {};
  function addEventListener() {};
  function removeEventListener() {};
  /**
   * The total number of times the timer has fired since it
   * started at zero. If the timer has been reset, only the
   * fires since the reset are counted.
   */
  private var _currentCount:Number;
  public function get currentCount ():Number {
    return _currentCount;
  }
  /**
   * The delay, in milliseconds, between timer events. If you
   * set the delay interval while the timer is running, the
   * timer will restart at the same repeatCount iteration.
   */
  private var _delay:Number;
  public function get delay ():Number {
    return _delay;
  }
  public function set delay (d:Number):Void {
    _delay = (isNaN(d))?0:d;
  }
  /**
   * The total number of times the timer is set to run. If the
   * repeat count is set to 0, the timer continues forever or
   * until the stop() method is invoked or the program stops.
   * If the repeat count is nonzero, the timer runs the specified
   * number of times. If repeatCount is set to a total that is
   * the same or less then currentCount  the timer stops and
   * will not fire again.
   */
  private var _repeatCount:Number;
  public function get repeatCount ():Number {
    return _repeatCount;
  }
  public function set repeatCount (c:Number):Void {
    if (isFinite(c) && c>=0) {
      _repeatCount = c;
    } else {
      throw new Error ("repeatCount must be either zero or a positive finite number");
    }
  }
  // The timer’s current state; true if the timer is running, otherwise false.
  private var _running:Boolean;
  public function get running():Boolean {
    return _running;
  }
  // The timer’s setInterval ID holder
  private var _intervalID:Number;
  /**
   * Constructs a new Timer object with the specified delay
   * and repeatCount states.
   * <P>The timer does not start automatically; you must
   * call the start() method to start it.</P>
   * @param   delay         The delay between timer events, in milliseconds.
   * @param   repeatCount   Specifies the number of repetitions. If zero,
   *                        the timer repeats infinitely. If nonzero, the
   *                        timer runs the specified number of times and then stops.
   * @throws                Error — if the delay specified is negative or not
   *                        a finite number
   */
  public function Timer (delay:Number, repeatCount:Number)
  {
    this.delay = delay;
    this.repeatCount = repeatCount;
    _currentCount = 0;
  }
  /**
   * Stops the timer, if it is running, and sets the currentCount
   * property back to 0, like the reset button of a stopwatch.
   * Then, when start() is called, the timer instance runs for the
   * specified number of repetitions, as set by the repeatCount value.
   */
  public function reset ():Void
  {
    stop();
    _currentCount = 0;
  }
  /**
   * Starts the timer, if it is not already running.
   */
  public function start ():Void
  {
    if (!_running) {
      clearInterval(_intervalID);
      _intervalID = setInterval(this, "onTimer", _delay);
      _running = true;
    }
  }
  /**
   * Stops the timer. When start() is called after stop(), the timer
   * instance runs for the remaining number of repetitions, as set
   * by the repeatCount property.
   */
  public function stop ():Void
  {
    clearInterval(_intervalID);
    _running = false;
  }
  /**
   * Called on every interation of the setInterval call and
   * dispatches timer and timerComplete events.  When the timer
   * has executed the expected number of repitions, the timer
   * is stopped
   */
  private function onTimer ():Void
  {
    _currentCount++;
    dispatchEvent(new TimerEvent(this, TimerEvent.TIMER));
    if (_repeatCount != 0) {
      if (_currentCount == _repeatCount) {
        this.stop();
        dispatchEvent(new TimerEvent(this, TimerEvent.TIMER_COMPLETE));
      }
    }
  }
  /**
   * Trace friendly output
   */
  public function toString ():String
  {
    return "[Timer " +
        "current=" + _currentCount + " " +
        "repeat=" + _repeatCount + " " +
        "delay=" + _delay + "]";
  }
}
/**
 * com.jor.utils.Timer
 * An AS2 implementation of the AS3 Timer class.
 *
 * Author: James O’Reilly - JOR
 *   www.jamesor.com
 *
 * This work is licensed under the Creative Commons attribution 2.5
 * This comment block must remain intact.
 */

com.jor.events.TimerEvent

/**
 * A simple TimerEvent Object used for dispatching by the Timer Object
 */
class com.jor.events.TimerEvent
{
  public static var TIMER:String = "timer";
  public static var TIMER_COMPLETE:String = "timerComplete";
  // Target Object dispatching the TimerEvent
  private var _target:Object;
  public function get target ():Object {
    return _target;
  }
  // Type of TimerEvent being dispatched
  private var _type:String;
  public function get type ():String {
    return _type;
  }
  /**
   * Constructor
   * @param   target   Target Object dispatching the TimerEvent
   * @param   type     Type of TimerEvent being dispatched
   */
  public function TimerEvent (target:Object, type:String)
  {
    _target = target;
    _type = type;
  }
  /**
   * Trace friendly output
   */
  public function toString ():String
  {
    return "[TimerEvent target=" + _target + " type=" + _type + "]";
  }
}
/**
 * com.jor.events.TimerEvent
 *
 * Author: James O’Reilly - JOR
 *   www.jamesor.com
 *
 * This work is licensed under the Creative Commons attribution 2.5
 * This comment block must remain intact.
 */

Testing the Timer class:

import mx.utils.Delegate;
import com.jor.utils.Timer;
import com.jor.events.TimerEvent;

var myTimer:Timer = new Timer(1000, 3);
myTimer.addEventListener(TimerEvent.TIMER, Delegate.create(this, timerHandler));
myTimer.addEventListener(TimerEvent.TIMER_COMPLETE, Delegate.create(this, timerHandler));
myTimer.start();

function timerHandler(event:TimerEvent):Void {
  trace("timerHandler: " + event);
}

12 Responses to “AS2 Timer class”

  1. JesterXL Says:

    Nice, and very clean! Might I suggest 1 small change.

    This:

    mx.events.EventDispatcher.initialize(this);

    to this:

    mx.events.EventDispatcher.initialize(com.jor.utils.Timer.prototype);

    That way, you don’t waste RAM and un-necessarely give each instance a copy of the same methods.

    BTW, you have some really nice code formatting on your blog here; are you using a plug-in or something?

  2. JesterXL Says:

    Er, one mod; put it up top so it only runs once:

    private static var mixit = mx.events.EventDispatcher.initialize(com.jor.utils.Timer.prototype);

  3. James O'Reilly Says:

    Thanks Jesse, and very interesting suggestion. I’ve never initialized the EventDispatcher that way before but it sounds like a good way to do it. I’ve included it in my code above.

    I use the site http://www.shockwave-india.com/blog/services/as-highlight2/index.php to format the text and redefined the style classes it creates to colors I prefer. You can probably check my source for the color codes I changed them to. If not, I can email them to you.

  4. JesterXL Says:

    Dammit… naw, I use that same exact link too, but your divs are just better. :: scours view source ::

  5. Actionscript Classes » Timer/Timer Events Says:

    […] http://www.jamesor.com/2006/10/18/as2-timer-class/  […]

  6. Bob Stoute Says:

    I like your class very much, as it provides a very convenient way of scripting with time in AS2.
    However, I found that it is not accurate, especially if the used intervals are set lower then 1000ms.
    I’ve written a test script to reveal the inaccuracy.
    These are the results:


    interval = 1000 :: repeat = 200
    * expected elapsed seconds = 200
    * real elapsed seconds = 210
    * difference = 5%

    interval = 1000 :: repeat = 100
    * expected elapsed seconds = 100
    * real elapsed seconds = 104
    * difference = 4%

    interval = 50 :: repeat = 200
    * expected elapsed seconds = 10
    * real elapsed seconds = 11
    * difference = 10%

    interval = 25 :: repeat = 400
    * expected elapsed seconds = 10
    * real elapsed seconds = 15
    * difference = 50%

    interval = 10 :: repeat = 1000
    * expected elapsed seconds = 10
    * real elapsed seconds = 17
    * difference = 70%

    As you can see the results become dramatic as the milliseconds decrease. I really hope that you can find out how to make the class more accurate, as I intend to use it mainly for very short repeat loops.

    Kind regards / Bob Stoute

    ACTIONSCRIPT USED FOR TESTING =

    import mx.utils.Delegate;
    import com.jor.utils.Timer;
    import com.jor.events.TimerEvent;
    //
    var start_time = int(getTimer() / 1000);
    //
    var interval = 500;
    var repeat = 20;
    //
    var myTimer:Timer = new Timer(interval, repeat);
    myTimer.addEventListener(TimerEvent.TIMER, Delegate.create(this, timerHandler));
    myTimer.addEventListener(TimerEvent.TIMER_COMPLETE, Delegate.create(this, timerHandler));
    myTimer.start();
    //
    function timerHandler(event:TimerEvent):Void {
    if (event.type == “timer”) {
    t = myTimer.currentCount;
    g = (int(getTimer() / interval));
    info = “myTimer.currentCount = ” + t + ” :: expected currentCount = ” + g + ” :: difference = ” + (g - t);
    trace(info);
    }
    if (event.type == “timerComplete”) {
    var real_seconds = int(getTimer() / 1000) - start_time;
    var expected_seconds = (interval * repeat) / 1000;
    //
    trace(”—”);
    trace(”interval = ” + interval + ” :: repeat = ” + repeat);
    trace(”* expected elapsed seconds = ” + expected_seconds);
    trace(”* real elapsed seconds = ” + real_seconds);
    trace(”* difference = ” + (real_seconds - expected_seconds) / (expected_seconds / 100) + “%”);
    trace(”—”);
    }
    }
    stop();

  7. Matt Says:

    I had to rename the stop function to ‘kill’ to get this to work properly for me. Perhaps because stop is a reserved word?

  8. James O'Reilly Says:

    Bob, the problem is setInterval and since the Timer class is a wrapper class for setInterval it inherits its problems. Intervals less than 300 ms or so become unreliable.

    Try adjusting the framerate of your movie up or down. I found that 60 FPS yielded 0% increase with 10ms x 1000. But 12 FPS yielded 50% increase and 30 FPS yielded 10% increase.

    From the docs: “If interval is greater than the SWF file’s frame rate, the interval function is only called each time the playhead enters a frame; this minimizes the impact each time the screen is refreshed.” Also, “the interval function is called as close to interval as possible.”

  9. James O'Reilly Says:

    Matt, please send me some code that you were using to show myTimer.stop() not working. The Timer class does not inherit from MovieClip so the word stop as a function name should not be a problem.

  10. Bob Stoute Says:

    So I guess that means there’s no way to get absolute timing with AS2. What’s the situation with AS3? Does that provide accurate timing?

  11. Joshua Mostafa Says:

    Why not use the total milliseconds elapsed (ignoring any time stopped) to calculate when next to stop, rather than simply within the current repetition? Then the number of repetitions would no longer have a negative affect on accuracy. You would not have 100% accurate time - without running the player at an impossible speed (i.e. 1000 FPS, or 1 frame per millisecond) - but you would get a lot closer.

  12. Antti Kupila Says:

    About the accurate timing: I had the very same problem in a project i did some time ago. Accurency was far off, especially when i had multiple separate intervals on at the same time. Since this was a local kiosk style application I was able to use other stuff than flash and ended up learning C# and doing in it with a flash interface :) Was friggin impossible to get flash to work fast enough (didn’t try as3 though)
    Joshua: I also tried your approach, but had the same problems there with flash, execution was off by several milliseconds, which was clearly noticable. Anyway, that’s the method i used in C# and it worked perfect

Leave a Reply


Bad Behavior has blocked 1216 access attempts in the last 7 days.