Jump to content

How do you guys write your timestep stuff? Any advice?


Will
 Share

Recommended Posts

Hey there, I searched about and couldn't find much about this, so here I am.

 

With Pixi, how do you guys write your timestep (in specific the update/logic and physics part of it)? Any advice?

 

I would prefer not to have a variable update loop, though of course I use requestAnimationFrame for the render loop.

 

setTimeout seems to sometimes jump a bit, and isn't very smooth. Same thing if I multiply values by the delta time.

Link to comment
Share on other sites

I base mine entirely on delta time, and I have no problems with it. As long as your update requires less time than the requestAnimationFrame interval it should work fine. I would recommend using delta in most cases.

 

But, if like you say, you don't want a variable update loop, I would still keep track of delta time, and I would sum it until you reach your desired minimum time step and then update (for example, you would update your game world only if more than 1/60th of a second has passed... essentially limiting it to 60 fps or fewer).

 

I would never, ever, use setTimeout if requestAnimationFrame is available.

Link to comment
Share on other sites

I base mine entirely on delta time, and I have no problems with it. As long as your update requires less time than the requestAnimationFrame interval it should work fine. I would recommend using delta in most cases.

 

But, if like you say, you don't want a variable update loop, I would still keep track of delta time, and I would sum it until you reach your desired minimum time step and then update (for example, you would update your game world only if more than 1/60th of a second has passed... essentially limiting it to 60 fps or fewer).

 

I would never, ever, use setTimeout if requestAnimationFrame is available.

 

Okay, though seeing as I use requestAnimationFrame anyway for the render loop, and very very few people have 120hz monitors, then surely it would be limited to 60fps or lower either way? I would prefer it to be a constant value. 

 

They both have use cases, setTimeout is good if you need your loop to continue running when the window is blurred. It is also fine if you are not syncing with the monitor refresh rate, or have a low fps need.

 

I'm using node-webkit, so I guess the window blurring would be less likely to occur.

 

Is it not possible to "smooth" the update? With setTimeout, there still appears to be small variations in the update speed (not my PC, it is fast ;)), resulting in a tiny jittering effect by the time rendering happens.

Link to comment
Share on other sites

Well, requestAnimationFrame is a kind of "smooth" update ;)

If you want to avoid problems caused by variations in update speed you need to make your game logic fps independent, even with setTimeout. Little tipp btw: call setTimeout at the beginning at your loop function (maybe you already know it, I just want to make sure :) )

Link to comment
Share on other sites

Okay, though seeing as I use requestAnimationFrame anyway for the render loop, and very very few people have 120hz monitors, then surely it would be limited to 60fps or lower either way? I would prefer it to be a constant value. 

You need to make your calculations based on the delta, even if requestAnimationFrame will most likely be ~60fps, because not ALL devices are 60fps. And in the future, when all devices are 120hz, people might still be playing your game! :)

 

The logic for frame limiting in requestAnimationFrame is as simple as this:

elapsedTimeSinceUpdate = elapsedTimeSinceUpdate + deltaTime; // add the time since last refresh to the elapsed time since the last updateif (elapsedTimeSinceUpdate > 16.6666666) { //60fps = ~16.666666 milisecs    update(elapsedTimeSinceUpdate); //update logic is called here    elapsedTimeSinceUpdate = 0; // reset the time because we are calling the logic}render(deltaTime); // call the render code every frame

You will still need to use the delta/elapsedTime in your render/update calculations, because while this code guarantees that update will not be called faster than 60fps, it could be slower than 60fps.

 

 

They both have use cases, setTimeout is good if you need your loop to continue running when the window is blurred. It is also fine if you are not syncing with the monitor refresh rate, or have a low fps need.

Yeah, I guess that I am a little bit guilty of using hyperbole when saying never, never, never use setTimeout... But from what I understand about the question, I would say in this case it isn't a good idea.

 

In fact, if he is using node-webkit, he could even use something like workers: http://www.w3schools.com/html/html5_webworkers.asp

Link to comment
Share on other sites

...

 

Is it not possible to "smooth" the update? With setTimeout, there still appears to be small variations in the update speed (not my PC, it is fast ;)), resulting in a tiny jittering effect by the time rendering happens.

 

You should be basing updates on the delta time, linear interpolation is your friend here.

Link to comment
Share on other sites

Well, requestAnimationFrame is a kind of "smooth" update ;)

If you want to avoid problems caused by variations in update speed you need to make your game logic fps independent, even with setTimeout. Little tipp btw: call setTimeout at the beginning at your loop function (maybe you already know it, I just want to make sure :) )

 

Thanks, I have seen that before, but never really knew why. Care to enlighten me? :)

 

You need to make your calculations based on the delta, even if requestAnimationFrame will most likely be ~60fps, because not ALL devices are 60fps. And in the future, when all devices are 120hz, people might still be playing your game! :)

 

The logic for frame limiting in requestAnimationFrame is as simple as this:

elapsedTimeSinceUpdate = elapsedTimeSinceUpdate + deltaTime; // add the time since last refresh to the elapsed time since the last updateif (elapsedTimeSinceUpdate > 16.6666666) { //60fps = ~16.666666 milisecs    update(elapsedTimeSinceUpdate); //update logic is called here    elapsedTimeSinceUpdate = 0; // reset the time because we are calling the logic}render(deltaTime); // call the render code every frame

You will still need to use the delta/elapsedTime in your render/update calculations, because while this code guarantees that update will not be called faster than 60fps, it could be slower than 60fps.

 

 

Yeah, I guess that I am a little bit guilty of using hyperbole when saying never, never, never use setTimeout... But from what I understand about the question, I would say in this case it isn't a good idea.

 

In fact, if he is using node-webkit, he could even use something like workers: http://www.w3schools.com/html/html5_webworkers.asp

 

Thanks, that works! But, the jittering occurs as soon as I start multiplying values by delta time.

 

You should be basing updates on the delta time, linear interpolation is your friend here.

 

I know how to lerp, but what values should I lerp? And by how much?

Link to comment
Share on other sites

Can you post some of the code where you are multiplying the delta time?

core.logic = (function(){	var logic = {};	var target     = 1000 / 60;	logic.leftOver = 0;	logic.buffer   = 0;	logic.tick = function(dt){		core.time.tick();//Update the delta time.		requestAnimationFrame(function(){ core.logic.tick(core.time.delta); });				logic.buffer += dt;		if(logic.buffer >= target){			logic.update(dt / 1000);			logic.buffer = 0;		}		core.render();		};	logic.update = function(dt){		core.square.x += 75 * dt;	};	logic.lerp = function(a, b, x){		return a + x * (b - a);	};	return logic;})();

It doesn't jitter by very much (at all), and perhaps animation would make it barely noticeable, but it annoys the hell out of me. (Though, my 11 year old brother noticed it without me pointing it out, so I do need to fix it really.)

Link to comment
Share on other sites

Just out of curiosity what browsers are you testing this in? Are you noticing the same issue in all of them?

 

Node Webkit, happens in Chrome too.

 

Though, I think I might have fixed it now!

core.logic = (function(){	var logic = {};	var target     = 1000 / 60;	logic.leftOver = 0;	logic.buffer   = 0;	logic.tick = function(dt){		core.time.tick();//Update the delta time.		requestAnimationFrame(function(){ core.logic.tick(core.time.delta); });		if(1 / (core.time.delta / 1000) < 30){ console.warn("FPS DIP!  " + 1 / (core.time.delta / 1000) + " " + Date() + " " + logic.buffer + " " + core.time.delta) }				logic.buffer += dt;		while(logic.buffer >= target){			logic.update(dt / 1000);			logic.buffer -= target;		}		core.render();		};	logic.update = function(dt){		//core.square.rotation += 0.01;		if(core.square.x < 1240){ core.square.x += 1; }		else{ core.square.x = 0; }	};	logic.lerp = function(a, b, x){		return a + x * (b - a);	};	return logic;})();

However, the framerate goes down a fair bit when the buffer gets to the point of having to update twice, though only every couple of minutes, also barely noticeable as it goes right up again. The motion of the square is silky smooth! :)

Link to comment
Share on other sites

Thanks, I have seen that before, but never really knew why. Care to enlighten me?  :)

 

Sure :) it's quite simple, just imagine following: Your loop-function needs 10ms to execute and you want it to run every 20ms. If you call setTimeout at the end of the function the function will start just every 30ms, because after you call the loop-function it will calculate for 10ms and after its finished it will call setTimeout (needs further 20ms). If you call setTimeout at the beginning the browser plans to start the loop 20ms later again and will calculate the function finish

Didn't help you now, but now you are enlightened :D

Link to comment
Share on other sites

Sure :) it's quite simple, just imagine following: Your loop-function needs 10ms to execute and you want it to run every 20ms. If you call setTimeout at the end of the function the function will start just every 30ms, because after you call the loop-function it will calculate for 10ms and after its finished it will call setTimeout (needs further 20ms). If you call setTimeout at the beginning the browser plans to start the loop 20ms later again and will calculate the function finish

Didn't help you now, but now you are enlightened :D

 

I see, thanks! Have a cookie :)

Link to comment
Share on other sites

Few things to note from your code above.

requestAnimationFrame(function(){ core.logic.tick(core.time.delta); });

Every time your calling requestAnimationFrame your assigning it a new function, this will get garbage collected fast (probably the FPS drop your noticing every couple minutes). In your case you should be able to call it like this:

requestAnimationFrame(core.logic.tick);

I don't mess with pixi.js so I'm not sure how everything is set up, but just from your code I don't think you need to pass the core.time.delta since your calling the function to update it just prior. That variable should be available to anything that can access core.time.

Link to comment
Share on other sites

Few things to note from your code above.

requestAnimationFrame(function(){ core.logic.tick(core.time.delta); });

Every time your calling requestAnimationFrame your assigning it a new function, this will get garbage collected fast (probably the FPS drop your noticing every couple minutes). In your case you should be able to call it like this:

requestAnimationFrame(core.logic.tick);

I don't mess with pixi.js so I'm not sure how everything is set up, but just from your code I don't think you need to pass the core.time.delta since your calling the function to update it just prior. That variable should be available to anything that can access core.time.

 

Thanks for that! The FPS dip does still happen - though I know it is when the leftover time in the buffer gets to the point where the while loop runs twice - though it also happens less often. Can you recommend anything for learning how to keep the garbage collector happy?

 

Yeah, I was just doing that so I can refer to the delta time as "dt" instead of core.time.delta. It's no big deal. With Pixi, you have to write all the loop stuff, it doesn't appear to matter how, as long as 

core.renderer.render(core.stage);

gets called to render all the stuff.

Link to comment
Share on other sites

Here are a few good links to look at.

 

http://buildnewgames.com/garbage-collector-friendly-code/

https://www.scirra.com/blog/76/how-to-write-low-garbage-real-time-javascript

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management

http://coding.smashingmagazine.com/2012/11/05/writing-fast-memory-efficient-javascript/

 

The big thing to know with the garbage collector is that it will collect anything thats no longer being referenced. So functions in a callback are bad, but a reference to a function are good (the reference keeps it alive). Also know that passing in an object/array, simply passes in a reference/pointer to the object, but passing in a variable actually passes in the values which will then be garbage collected.

var myObject = {x:0,y:0}; var updateXY = function(x,y){  var x = x+1; // var x will be collected  var y = y+1; // var y will be collected  return ([x,y]); // will be collected as a new array, very expensive.}var newXY = updateXY(myObject.x,myObject.y);// The above function creates a lot of garbage, and when run 1000 times, // that garbage will add up.var updateXY = function(a){ // a = a pointer to your object.  a.x = a.x+1; // nothing to collect  a.y = a.y+1; // nothing to collect  // no need to return values, your code actually changed the referenced object.}updateXY(myObject);// The above actually uses the fact that JS only passes in references to objects to it's// advantage.

My example code could probably be better, but hopefully it helps a little.

Link to comment
Share on other sites

Here are a few good links to look at.

 

http://buildnewgames.com/garbage-collector-friendly-code/

https://www.scirra.com/blog/76/how-to-write-low-garbage-real-time-javascript

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management

http://coding.smashingmagazine.com/2012/11/05/writing-fast-memory-efficient-javascript/

 

The big thing to know with the garbage collector is that it will collect anything thats no longer being referenced. So functions in a callback are bad, but a reference to a function are good (the reference keeps it alive). Also know that passing in an object/array, simply passes in a reference/pointer to the object, but passing in a variable actually passes in the values which will then be garbage collected.

var myObject = {x:0,y:0}; var updateXY = function(x,y){  var x = x+1; // var x will be collected  var y = y+1; // var y will be collected  return ([x,y]); // will be collected as a new array, very expensive.}var newXY = updateXY(myObject.x,myObject.y);// The above function creates a lot of garbage, and when run 1000 times, // that garbage will add up.var updateXY = function(a){ // a = a pointer to your object.  a.x = a.x+1; // nothing to collect  a.y = a.y+1; // nothing to collect  // no need to return values, your code actually changed the referenced object.}updateXY(myObject);// The above actually uses the fact that JS only passes in references to objects to it's// advantage.

My example code could probably be better, but hopefully it helps a little.

 

Certainly does help! Thanks a bunch, I understand things a bit better now, though I will give those articles a more thorough read through when I'm less tired.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...