2
\$\begingroup\$

How can I achieve a more performant tick code keeping my minimum frame rate high?

installLoops() {
        window.requestAnimationFrame(this.loop.bind(this));
    }


    loop() {
        this.now = Date.now();
        var delta  = this.now - this.last;
        this.last = this.now;

        this.dt = this.dt + delta;

        if (this.dt < this.rate) {
            window.requestAnimationFrame(this.loop.bind(this));
            return;
        } else {
            this.game.loop();
            this.draw();
            this.dt = this.dt - this.rate;
            this.game.loopKeyboardInput(this.key_pressed_map);
            this.key_pressed_map = {};
        }

        window.requestAnimationFrame(this.loop.bind(this));
    }

I moved

this.game.loopKeyboardInput(this.key_pressed_map);
    this.key_pressed_map = {};

to inside the block where the game loop is called, which improved my average from rate from 12.56 to 56+. However, in performance testing my framerate still drops down to 13 (lowest). (I'm not sure if this is happening because of the performance sampling).

\$\endgroup\$
3
\$\begingroup\$

One issue I see is you're using bind. bind creates a copy of the function but with a bound context. You appear to call it on each iteration, which will cause memory hogging, which causes the GC to kick in more often, thus lagging. You can simply just drop the use of bind and write your code without relying on context.

If you need to store state, store it somewhere else, like a single module holding your game state. It's also good to isolate state into one location instead of having it everywhere.

The render loop should just be a render loop. It should not be aware of anything else except a queue of things to do on each turn. On the other hand, the rest of the code shouldn't be aware of a render loop. All they need to do is to register operations to the queue, with only the assumption that the function they register gets executed per frame. This keeps code relatively decoupled with the scheduling mechanism.

In the following, we create a jobQueue which is just a list of things to do when the loop executes.

// The render loop is only aware of a job queue full of functions
const jobQueue = [];

function renderLoop(elapsed){
  requestAnimationFrame(renderLoop);

  // Consume tasks in jobQueue
  jobQueue.forEach( task => task.call(elapsed / (1000/60)) );
}

// Start the loop
renderLoop();

// All your code needs to do now is register
jobQueue.push(scale => { update some state });
jobQueue.push(scale => { render something });
jobQueue.push(scale => { check keyboard });

Also, it's best if you schedule the next frame prior to doing stuff. That way, anything that causes the loop to lag doesn't lag scheduling the next frame.

Another thing is timing. requestAnimationFrame's callback accepts 1 argument, a timestamp. Use that instead of Date.now. Now instead of using delta time (a time value) in your calculation, consider using a scale (a unit-less value). I've answered a similar review here. With delta time, you have to change your calculation to take in a time value. With a scale, you simply calculate output values, then multiply against the scale value. It's like a percentage value.

Lastly, the example above uses forEach for jobQueue. It's known to be slow. Feel free to replace it with a regular for or while loop.

\$\endgroup\$
  • \$\begingroup\$ So using scale lets the action update at any rate instead of forcing a set framerate, interesting. What is the timestamp value that you pass to requestAnimationFrame? I see you pass renderLoop, a function, not a timestamp. Is scale calculated by deltaTime/60? \$\endgroup\$ – quantumpotato Feb 10 '16 at 2:19
  • \$\begingroup\$ This looks reasonable. I was able to clean up some other parts of my code so I'm still using bind, but I'm making a note to try this out. \$\endgroup\$ – quantumpotato Feb 20 '16 at 0:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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