1
\$\begingroup\$

I'm sure this has probably been asked before (although I couldn't find a question akin to this one).

I have a canvas "app" that is refreshing persistently, but performance is awful on mobile devices. I just wondered if there was a nice alternative to requestAnimFrame() or the like.

Here is my "app": http://www.barriereader.co.uk/Principality/

Here is the main rendering methods (I've removed the actual rendering - you can view that here http://www.barriereader.co.uk/Principality/System/Engine.js)

var fps = 0;
var oldtime = +new Date();
var time;

var Engine = {
    /** FOR DEBUGGING **/
    FPS: true,
    /*******************/

    Canvas : null,
    CTX : null,
    GameLooper : null,
    NodeSize : 10,
    MapSize : 32,
    Init : function (w, h) {
        Engine.Canvas = document.createElement('canvas');
        document.body.appendChild(Engine.Canvas);
        Engine.CTX = Engine.Canvas.getContext('2d');
        Engine.Canvas.width = w;
        Engine.Canvas.height = h;
        Engine.GameLoop();
    },
    Update : function (time) {
        fps = 1000/(time-oldtime)
        oldtime = time;
        Engine.Draw();
    },
    Draw : function () {
        Engine.CTX.save();
        Engine.CTX.setTransform(1, 0, 0, 1, 0, 0);
        Engine.CTX.clearRect(0, 0, Engine.Canvas.width, Engine.Canvas.height);
        Engine.CTX.restore();

        /**************DRAW_SCENE_HERE*****************/
            //A BUNCH OF RENDERING GOES HERE...
            //Performance is bad on mobiles :(
        /**********************************************/

        Engine.GameLoop();
    },
    GameLoop : function () {
        Engine.GameLooper = setTimeout(function () {
            requestAnimFrame(Engine.Update, Engine.Canvas);
        }, 10);
    },
};

window.requestAnimFrame = (function () {
    return window.requestAnimationFrame ||
    window.webkitRequestAnimationFrame ||
    window.mozRequestAnimationFrame ||
    window.oRequestAnimationFrame ||
    window.msRequestAnimationFrame ||
    function (callback, element) {
        window.setTimeout(callback(+new Date), 1000 / 60);
    };
}());

window.onload = function () {
    Engine.Init(320, 320);
};
\$\endgroup\$
1
  • \$\begingroup\$ Are you sure your code is optimized and really appropriate for mobile? Maybe you could show us a little bit more. Accusing requestAnimationFrame is maybe an easy way to not review your own code. Because requestAnimationFrame is supposed to be the most optimized thing which will act according to your device capacities. \$\endgroup\$
    – dievardump
    Commented Jul 22, 2013 at 8:30

1 Answer 1

3
\$\begingroup\$

As far I understand your code, here's are some bumps:

Cache your values

Property access in objects causes a very minor overhead. A very common example of this case is a normal loop. In this version of the loop, you'd be accessing length i times during the course of the loop.

for(i = 0; i < someArray.length; ++i){

It may not matter in normal array use, where array length is less than a hundred records at best. But when you are aiming for at least 60fps and doing pixel operations in between, these minor optimizations do give you that edge.

Just to point you to the right perspective, a canvas of 1024x768 has 3,145,728 entries in the image data array! Imagine doing a loop at that scale, manipulating pixels on every iteration, do this on every frame for 60fps. I tell you, my CPU (fan) screamed.

So instead of the code above, the following code solves the problem by caching the length, and in effect, only accessing length on the object once.

var arrayLength = someArray.length;
for(i = 0; i < arrayLength; ++i){

Use a closure

Sure, cache the variables, but where? I assume you made the Engine object as your namespace to avoid globals. But if property access is an overhead, where should we place your code?

Well, you should create a closure for your code. That way, you have a scope to play with, where:

  • You can place your code freely without using a namespace (thus avoiding property access).

  • At the same time not polluting the global space.

Consider this:

(function(ns){

  var Update = function(){/*Engine.Update code*/}
  var Canvas = function(){/*Engine.Canvas code*/}
  var GameLooper;

  function GameLoop(){
    requestAnimationFrame(GameLoop);
    Update();
    Canvas();
  }

  //Expose init to our namespace
  var Init = ns.Init = function(){/*Init code*/}

}(this.Engine = this.Engine || {}));

window.onload = function(){
  Engine.Init(320, 320);
}

As you can see, you don't need to call Engine.something(). You can directly do something() directly. This solves your property access and namespacing problems, two birds with one stone.

rAF

requestAnimationFrame, often abbreviated as rAF, is a more optimized timer compared to setTimeout and setInterval. It's basically built with animations in mind, and is optimized to give you the best framerates as possible. It's also an efficient timer since it slows down if the tab is set to the background, thus not eating up processing time.

However, I notice that you are hampering the rAF call with a setTimeout.

GameLoop : function () {
  Engine.GameLooper = setTimeout(function () {
    requestAnimFrame(Engine.Update, Engine.Canvas);
  }, 10);
},

This code has several bad effects as well, aside from those mentioned earlier:

  • Your game loop runs in intervals of ~10ms. You are not reaping the benefits of rAF at all. You are basically slowing down the game loop.

  • Also, timer delays don't really reflect the actual time they execute, thus in this code, the delay isn't 10ms. It could be greater, depending on what JS is doing at the moment.

  • You are creating a timer and an anonymous function on every iteration. setInterval would have been a better option (but not better that rAF) because it creates only one timer and one anonymous function.

With rAF, try this instead:

// Aapplying our no-property access policy
var GameLoop = function () {

  // Schedule the next frame ASAP
  requestAnimFrame(GameLoop);

  // In the meantime, call the functions for this frame
  Update();
  Canvas();
}

Notice that the scheduled call is GameLoop. This way, your functions Update and Canvas don't need to call back GameLoop to run the next frame. The next frame is already scheduled with this call.

Date....now()!

Yes, in recent browsers, there's Date.now() which is an optimized substitute to +new Date() and new Date().getTime() to get the current timestamp.

In classical OOP terms, it's a "static" member, which means you don't need to create an instance of Date to use it. This way, you are not generating a new object every time just to get the current time, thus saving you memory.

\$\endgroup\$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.