Don’t Let Your Timers Creep!
It’s been a while since I’ve done a pure programming post, so here we go. A co-worker just dropped by to ask whether I would expect timers in ActionScript 3.0 to be accurate. I said, “why yes, of course!” But there’s a wrinkle: if you set a Timer to go off indefinitely (i.e., with a repeatCount of 0), it appears that the timer doesn’t start the next interval until the listener function returns. So if you don’t correct for this, then your timer function will creep by the duration of the listener function. If your listener function does something that’s potentially lengthy, you might want to execute it with a callLater() so that the timer isn’t affected. But the timer will still creep unless you apply a correction. Here’s some sample code that demonstrates the effect, side-by-side with a correction.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | <?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" creationComplete="{onCreationComplete(event)}"> <mx:Script> <![CDATA[ import mx.utils.StringUtil; import mx.events.FlexEvent; private const BASE_INTERVAL:Number = 1000; private var _creepingTimer:Timer = new Timer(BASE_INTERVAL); private var _steadyTimer:Timer = new Timer(BASE_INTERVAL); private var _startTime:Date; private function onCreationComplete(e:FlexEvent) : void { _creepingTimer.addEventListener(TimerEvent.TIMER, onCreepingTimer); _creepingTimer.start(); _steadyTimer.addEventListener(TimerEvent.TIMER, onSteadyTimer); _steadyTimer.start(); _startTime = new Date(); } private function onCreepingTimer(e:TimerEvent) : void { var now:Date = new Date(); var deltaMS:int = now.time - _startTime.time; uiCreepingLog.text += StringUtil.substitute("\r{0}", deltaMS); } private function onSteadyTimer(e:TimerEvent) : void { var now:Date = new Date(); var deltaMS:int = now.time - _startTime.time; uiSteadyLog.text += StringUtil.substitute("\r{0}", deltaMS); var offset:int = deltaMS % BASE_INTERVAL; _steadyTimer.delay = offset < 500 ? BASE_INTERVAL - offset : BASE_INTERVAL; } ]]> </mx:Script> <mx:HBox width="100%" height="100%"> <mx:TextArea id="uiCreepingLog" width="50%" height="100%" text="Creeping timer:"/> <mx:TextArea id="uiSteadyLog" width="50%" height="100%" text="Steady timer:"/> </mx:HBox> </mx:Application> |
id="CreepingTimer" width="100%" height="100%"
codebase="http://fpdownload.macromedia.com/get/flashplayer/current/swflash.cab">
width="100%" height="100%" name="CreepingTimer" align="middle"
play="true"
loop="false"
quality="high"
allowScriptAccess="sameDomain"
type="application/x-shockwave-flash"
pluginspage="http://www.adobe.com/go/getflashplayer">




Inspired programming, Very useful info for new users of AS like me.
Hi David, great discovery and workaround!
However, I can’t seem figure to figure out why you do this:
_steadyTimer.delay = offset < 500 ? BASE_INTERVAL – offset : BASE_INTERVAL;
and not simply
_steadyTimer.delay = BASE_INTERVAL – offset;
What’s the reason for checking if offset is smaller than 500?
Just wanted to say thank you very very much for this. I was dealing with a lagging timer and it wasn’t apparent that this was the cause. Thanks for the enlightenment and workaround.
Thank you! I’d been struggling with this problem for weeks, and thanks to your post I managed to fix it one day before the deadline