views:

1821

answers:

3

I'm building a game that got some moving things all the time, so I'm using a lot of Timer instances to control repetition and trigger motion.

Now the thing is that I started to notice some performance "lags". Is this due to the timers? and do you suggest using ENTER_FRAME event instead?

Related: Do you suggest any other library/method for such games that could enhance performance? Simple Tween libraries are not enough per se.

+1  A: 

if you aren't using a tween library yet i would look at tweenlite or tweenmax. it includes a delayed called timer as well as grouping tweens together. it has great performance and is simple to use.

take a look here at the performance tests

http://blog.greensock.com/tweening-speed-test/

Josh

Josh
Will give that a try.
Makram Saleh
+6  A: 

maybe it would make more sense, to have only one timer running for that matter ... as far as i know, a running Timer needs a whole thread ... to put it in pseudo-code, the Timer thread's main code is something like that ...

while (input.isEmpty()) {
    wait(interval);
    output.add({timerId:thisId, tickId: tickId++});
}

output being a dequeue the main thread (which executes the ABC) checks every now an then ... having many Timers, you will have many threads, which is an unnecessary overhead ... also, for every event, the message sent from the timer to the main thread needs to be popped of the deque, which is expensive, since it has to be thread safe ... and then the corresponding timer has to be found, a timer event has to be created (allocation is also quite costy) and then dispatched, which also is a matter of multiple calls ...

so try to have ONE timer, or use setInterval ... also, consider, that the Event model in flash is quite nice, but expensive ... it is used for decoupling, to ensure a nice architecture ... for the same reason, it is not good for performance critical situations ... once again, dispatching an event is expensive ...

i've made a little class, that is a little more manual (it's just to make my point, although it oculd be used in theory):

package  {
    import flash.utils.*;
    public class Ticker {
     //{ region private vars
      private var _interval:int;
      private var _tick:uint = 0;
      private var _tickLength:Number;
      private var _callBacks:Dictionary;
     //} endregion
     public function Ticker(tickLength:Number = 0) {
      this.tickLength = tickLength;
      this._callBacks = new Dictionary();
     }
     //{ region accessors
      /**
       * the current tick
       */
      public function get tick():uint { return _tick; }
      /**
       * the tick length. set to a non-positive value, to stop ticking
       */
      public function get tickLength():Number { return _tickLength; }
      public function set tickLength(value:Number):void {
       if (this._tickLength > 0) clearInterval(this._interval);
       if ((this._tickLength = value) > 0) this._interval = setInterval(this.doTick, value);
      }  
     //} endregion
     /**
      * add a callback, to be called with every tick
      * @param callback function (tick:int):*
      */
     public function addCallback(callback:Function):void {
      this._callBacks[callback] = callback;
     }
     /**
      * removes a callback previously added and returns true on success, false otherwise
      * @param callback
      * @return
      */
     public function removeCallback(callback:Function):Boolean {
      return delete this._callBacks[callback];
     }
     /**
      * executes a tick. actually this happens automatically, but if you want to, you can set tickLength to a non-positive value and then execute ticks manually, if needed
      */
     public function doTick():void {
      var tick:uint = this._tick++;//actually, this is only superspicion ... amazingly, this makes no difference really ... :D
      for each (var callback:* in this._callBacks) callback(tick);
     }
    }
}

it performs quite well ... here a benchmarking class (you should be able to simply use it as document class in a fla, if you use CS3/CS4):

package {
    //{ region imports
     import flash.display.*;
     import flash.events.*;
     import flash.sampler.getSize;
     import flash.system.System;
     import flash.text.*;
     import flash.utils.*; 
    //} endregion
    public class Main extends MovieClip {
     //{ region configuration
      private const timers:Boolean = false;//true for Timer, false for Ticker
      private const delay:Number = 500;
      private const baseCount:uint = 10000;//base count of functions to be called
      private const factor:Number = 20;//factor for Ticker, which is a little more performant  
     //} endregion
     //{ region vars/consts
      private const count:uint = baseCount * (timers ? 1 : factor);
      private const nullMem:uint = System.totalMemory;//this is the footprint of the VM ... we'll subtract it ... ok, the textfield is not taken into account, but that should be alright ... i guess ...
      private var monitor:TextField;
      private var frameCount:uint = 0;
      private var secCount:uint = 0;  
     //} endregion
     public function Main():void { 
      var t:Ticker = new Ticker(delay);
      var genHandler:Function = function ():Function {
       return function (e:TimerEvent):void { };
      }
      var genCallback:Function = function ():Function {
       return function (tick:uint):void { };
      }
      for (var i:uint = 0; i < count; i++) {
       if (timers) {
        var timer:Timer = new Timer(delay, 0);
        timer.addEventListener(TimerEvent.TIMER, genHandler());
        timer.start();     
       }
       else {
        t.addCallback(genCallback());
       }
      }
      this.addChild(this.monitor = new TextField());
      this.monitor.autoSize = TextFieldAutoSize.LEFT;
      this.monitor.defaultTextFormat = new TextFormat("_typewriter");
      this.addEventListener(Event.ENTER_FRAME, function (e:Event):void { frameCount++ });
      setInterval(function ():void { 
        monitor.text = "Memory usage: " 
         + groupDidgits(System.totalMemory - nullMem) 
         + " B\navg. FPS: " + (frameCount /++secCount).toPrecision(3) 
         + "\nuptime: " + secCount + "\nwith " + count + " functions"; 
       }, 1000);
     }
     private function groupDidgits(n:int,sep:String = " "):String {
      return n.toString().split("").reverse().map(function (c:String, i:int, ...rest):String { return c + ((i % 3 == 0 && i > 0) ? sep : ""); } ).reverse().join("");
     }
    }
}

on my machine, with 60 FPS targetet, i get an average FPS of 6.4 (after 3 minutes) and 10-14 MB memory usage (fluctuation comes from the fact that TimerEvent objects need to be garbage collected) for 10000 functions being called with timers ... using the other class, i get 55.2 FPS with 95.0 MB memory usage (very constant, fluctuations are unter 1%) with 200000 functions being called directly ... this means, at factor 20 you get a framerate that is 9 times higher, and you use only 8 times the memory ... this should get you an idea of how much footprint a timer creates ...

this should get you a rough idea, in which direction to go ...

[edit] i've been asked, why i use private vars ... matter of philosophy ... my rule: never ever let anyone from outside change the state of your object directly ... imagine Ticker::_tickLength was protected ... someone subclasses it, and writes to that variable ... with what effect? the value of Ticker::tickLength will be different from the interval length ... i don't really see an advantage ...

also, private fields are only valid in a class ... which means anyone can redefine them within subclasses without any collisions ...

if i think, that subclasses should have a protected way to take effect on the state defined in the superclass, i make a protected setter ... but still, i can react ... i can change/validate/clamp the value, throw argument and range errors at will, dispatch events, and so on ... if you write a class, you yourself are responsible for maintaining the integrity of its state and the effects on its behaviour ...

do not expose the inner workings of your class ... you may need to change them, breaking dependant code ... and also: subclassing is hugely overrrated ... :)

so that's why ... [/edit]

back2dos
What's with the 'region' stuff?
Luke
Oh. And why do you make members variables private instead of protected?
Luke
Thanks for the invaluable info regarding timers. I gotta try your code; the ticker sounds promising!
Makram Saleh
@luke about regions: i am using flashdevelop for actionscript developement ... regions allow custom folding, so i can fold away different parts of the class ... plus it gives the code extra structure ... just a coding convention of mine, so to speak ...
back2dos
@luke: answer two the second question merged into the post ...
back2dos
@back2dos. Thanks for that. However. I disagree with just about anything you say in your update. It seems that you think that everything is hostile and you need to protect yourself and other programmers from your class implementations. My philosophy is exactly the opposite. Every class member should be available for sub-classing unless there is a very specific reason to make it private. Your approach seems a bit bureaucratic to me. Ah well. Glad we don't have to work on projects together. :)
Luke
it's about encapsulation and writing relyable code, that'll either always work as expected or throw runtime errors, if so. does stupid things with it. to me, a good API is powerful, small and problem oriented. don't care about the how, as long as it the what is reliable. that's what i expect, and that's why i do it myself.btw, the point of subclassing is not fiddling around with some superclass properties, but for concrete implementation of abstract behaviour and is a very good tool to establish IOC.you can ask a question on all this, if you really want to discuss that issue seriously.
back2dos
The explanation about Timers and threads is completely inaccurate. All ActionScript in the Flash Player executes in a single thread, regardless of timers, etc. It's important to remember this and to not abstract it as "thread-like" if you want to understand what's really going on.
Troy Gilbert
if you read my post, you would have realized, i stated that ABC is executed by a single thread. flash player as such is not. pixel bender is the best example, where you can even run code parallely.
back2dos
+5  A: 

I'd recommend using ENTER_FRAME as the master "tick" for your game engine. ENTER_FRAME lines up exactly with the Flash Player's framerate, which is the true maximum framerate your code will run at. Timers, etc., are just approximations and cannot execute any faster than ENTER_FRAME.

In fact, while I originally used Timers for all of my stuff, I'm slowly moving away from them because of aliasing issues. If you set your Timer for 30fps, but the Flash Player ends up running at 15fps, then the Timer will end up dispatching it's TIMER event twice between ENTER_FRAME events. If these TIMER events lead to expensive code (which they would if it's your game engine's tick), then it has the potential of pushing the Player's actual framerate lower (because now you're ticking twice per ENTER_FRAME).

So, Timer is good if you've got something that you want to run periodically, but for running anything close to your SWF's actual framerate I'd recommend just using the SWF's framerate and adjusting your logic as necessary.

One approach is to calculate time deltas on each ENTER_FRAME. If you've got time-based logic, this is the best approach. Another approach, if your SWF is assuming a fixed update rate (like Timer-based code), is to call your game's tick method if-and-only-if you've exceeded the time delta on any given ENTER_FRAME.

I would not recommend doing two ticks per ENTER_FRAME if you fall behind (or you'll end up with the same situation as the Timers). At a certain point, your game has to slow down or it becomes unplayable (because the deltas get too big). Doing more than one tick per ENTER_FRAME when you're already slowed down will only slow you down further. Users can better handle slowed gameplay than they can skipping gameplay.

Troy Gilbert
Thanks for the valuable info!The thing is that I'm using a combination of both. ENTER_FRAME for my mouse-moved character, and Timer for moving cars... The good thing though, is that the 'lag' I mentioned in my above question was only experienced in the Flash authoring tool. When I opened the swf alone (running in standalone flash player) the speed was perfect 30 fps.
Makram Saleh