The curious case of Multi Table Tennis

Started by
2 comments, last by Sik_the_hedgehog 8 years, 1 month ago
Multi table tennis is a ping pong clone that I developed for the sake of learning game development on the android platform. Right after completing the game I realized that there was something strange going on with it.
The frame rate of the would drop to around 30fps, about 4 days after installation. Uninstalling and reinstalling the game or killing the game process using a task manager didn't seem to work. The only way to temporarily fix the issue was to reboot the android device.
The only possible reason I could come with up with for this behavior was some kind of a strange memory leak. So I carefully reviewed the code and actually found some pieces of code that could produce memory leaks. But fixing these didn't really work. Eventually, after 2 months of random bug fix attempts, I decided to move on.
After an year or so I decided port the code to use a new simple android game library I created and started reordering the code. While doing so I came across the following lines.

float curT, prevT=0, deltaTime;
void onDrawFrame(GL10 gl) {
      curT=System.nanoTime();  <- nanoTime() returns a 'long'
      deltaTime=curT-prevT;
      prevT=curT;
      gameScreen.update(deltaTime/1000000);
      .
      .
      .
}

And I realized the careless mistake I had made. Basically I was using a bunch of 'float' variables to store the 'long' value of Sytem.nanoTime() in order to calculate the time elapsed between the previous frame and current one. Java will implicitly convert 'long's to 'float's. But when doing so, the least significant bits(LSBs) that are outside the capacity of 'float's will be discarded.

Now this code will work fine as long as the value returned by nanoTime() is less than the max value a float can hold. As soon as the long value gets past this point there will some information lose.
And thus deltaTime value will be calculated as zero for 'long' values which differ only in the LSBs that are outside the float's range, resulting in drawing a frame which would look exactly the previous frame. This will end up creating a virtual frame rate drop in the game.
This mistake taught me to actively contemplate possible bugs right when writing code rather than passively executing the code and waiting for any possible bugs to reveal themselves.
Advertisement

The other, specific, lesson is to never store absolute time values in floats. Absolute time goes in a 64bit integer of small ticks, or a double-precision float of seconds or ticks wink.png

The other, specific, lesson is to never store absolute time values in floats. Absolute time goes in a 64bit integer of small ticks, or a double-precision float of seconds or ticks wink.png

True. I also learned to take type conversion seriously. There were times before that when I wrote code that looked something like this

int a=1,b=2;
float f = a/b; //f will be 0 instead of .5

The other, specific, lesson is to never store absolute time values in floats. Absolute time goes in a 64bit integer of small ticks, or a double-precision float of seconds or ticks wink.png

Especially when you consider ticks are integers and should be handled as such. And in the case of time, you should contemplate the very likely possibility of overflow and account for it in your calculations, as well as the possibility of there being huge skips forward or even also skips backwards, as timers aren't necessarily perfect and can be also influenced by external factors. This stuff is not as obvious as it sounds.

Don't pay much attention to "the hedgehog" in my nick, it's just because "Sik" was already taken =/ By the way, Sik is pronounced like seek, not like sick.

This topic is closed to new replies.

Advertisement