0

I've been trying to make a simple game in HTML5/JS. I've managed to get a basic skeleton ready for the state machine and game loop, but even making a simple rectangle move around results in some 'tangible' choppiness.

What's more, after leaving it idle for some time (tabbing out), it seems to tick extremely fast, making a simple key-press move the rectangle far more than it should.

I'm using setTimeout for the game's updates. During each 'tick', I call the update function for the current state, which is

State.prototype.update = function(ms) {
    this.ticks += ms;

    var updates = 0;
    while(this.ticks >= State.DELTA_TIME && updates < State.MAX_UPDATES) {
          this.updateState();

          this.updateFrameTicks += State.DELTA_TIME;
          this.updateFrames++;

          if(this.updateFrameTicks >= 1000) {
              this.ups = this.updateFrames;
              this.updateFrames = 0;
              this.updateFrameTicks -= 1000;
          }

        this.ticks -= State.DELTA_TIME;
        updates++;
    }   

    if(updates > 0) {
          this.renderFrameTicks += updates*State.DELTA_TIME;
          this.renderFrames++;

          if(this.renderFrameTicks >= 1000) {
              this.rps = this.renderFrames;
              this.renderFrames = 0;
              this.renderFrameTicks -= 1000;
          }

          this.renderState(updates*State.DELTA_TIME);
    }

};

The idea is to call Game.update as frequently as possible using setTimeout, and then pass the elapsed time to State.update. State.update will update the state, only if the time accumulated by the state is greater than or equal to the fixed update time-step. If the state is actually updated, State.update will also render/redraw the current state, ensuring that the state presentation matches the state simulation.

Now, I know that requestAnimationFrame works better than setTimeout, but theoretically the current version should work, unless I've made a fundamental mistake.

This is what I have so far: http://jsbin.com/ogicec/1 (Edit)

You can clearly observe that it has fits of choppiness, and if you tab out for a long period and come back in, it seems to be running 'faster' than normal.

I can't really pinpoint what the problem is, so help would be really appreciated!

EDIT: I separated my update and render parts for the state, using setTimeout for update and requestAnimationFrame for render. It took care of the tabbing out problem, and the whole thing feels more consistent. But, at the same time, the performance is still choppy and I'd like to ensure it is smooth enough before going on to add more game related code.

Here is the updated JSBin: http://jsbin.com/eyarod/1 (Edit)

Rikonator
  • 1,830
  • 16
  • 27
  • Regarding your "catchup" behavior: I know Chrome (and possibly other browsers) slow calls to `setInterval` (and I guess `setTimeout`) to running once per second, so you may have `setTimeout` operations that were throttled and are now finally getting to run. – apsillers Dec 10 '12 at 17:56
  • If pausing the engine when the end-user's busy in another tab or window is acceptable, you can use the Page Visibility API to detect when your app is visible and to prevent queuing additional timeout's when not. See http://www.html5rocks.com/en/tutorials/pagevisibility/intro/ – psema4 Dec 10 '12 at 18:07
  • don't use `setTimeout()` use `window.requestAnimationFrame()` it will give you ~60fps and only runs when the tab is being rendered. so if you click to another tab or if the browser is minimized it will not be running. `setTimeout()` is running all the time eating up valuable CPU cycles when the user may not even have the window showing. Don't upset your user by slowing down the users computer. – Patrick W. McMahon Sep 02 '14 at 17:42

2 Answers2

1

On my system, the JSBin test actually runs with UPS: 60 and FPS: 60 pretty much all the time (note I'm running it in chrome 25.0.1349.2 dev on an i7-2600k, and I have VSync turned on, so that's why the framerate would be limited). When I switch to a different tab and back the FPS goes haywire for a bit: I suspect that's related to the way browsers throttle setTimeout calls when the tab isn't active.

More generally, you seem to be trying to shoehorn a message-polling busy wait loop into JavaScript, which is an event-driven language. I suspect (but do not know for sure), that adding an event handler that updates a shared object with the current x and y, and then having a separate loop to update the screen at a rate fixed to the monitor refresh rate would perform much better.

Brenton Fletcher
  • 1,280
  • 10
  • 15
  • What you're describing is a pretty good system: you have one loop (or set of event listeners) that update the "world model" (i.e., the x/y of all objects) and then have a separate loop (possibly run by `requestAnimationFrame`) that reads the world model and updates what is shown on the screen. – apsillers Dec 10 '12 at 18:06
  • Following your suggestion coupled with @apsillers, I updated my code to separate updating and rendering. It took care of the 'catching up' problem when tabbing out, but it still feels choppy. Here is the updated JSBin: http://jsbin.com/eyarod/1/edit – Rikonator Dec 10 '12 at 19:10
  • This is moving out of my area of expertise; I'm a web applications developer by day, not really a games programmer :). But I *think* the way you're processing user input is still problematic: you should switch to a kind of 'threaded' architecture where you have an input event handler and a requestanimframe loop. Also the way you're using requestanimrame *might* not be correct, I'd look up the docs for that. Also, I think you might be clearing/redrawing the canvas more than once per frame, which might be slowing things down. Sorry for the hedging, I don't want to cast aspersions without cause. – Brenton Fletcher Dec 12 '12 at 21:59
0

Now, I know that requestAnimationFrame works better than setTimeout, but theoretically the current version should work, unless I've made a fundamental mistake.

User agents are allowed to wait an additional amount of time, as long as they like, above the milliseconds argument provided to setTimeout (and generally non-realtime APIs that perform delayed execution guarantee that code does not run before your specified delay, but it may run an arbitrary amount of time after your specified delay). Further, you might observe choppy motion when one call to update comes while this.ticks is very close to, but less than a frame and the next call comes at a time that triggers two updates, skipping a frame.

The mistake is that setTimeout can never provide the guarantee that requestAnimationFrame does.

If you use requestAnimationFrame and keep on having issues, you can dig into them with Chrome dev tools; here are slides about avoiding animation chop

ellisbben
  • 6,352
  • 26
  • 43