I was making a video of gameplay for a game I made previously when I noticed that the level timer was horribly slow. For a 23-minute video, it said that it took me 6-minutes to play the level! First, I looked at the code for outputting the time. That was fine. Then I traced the method calls that set the time all the way back to the main loop.
I’ve had various problems with the game running sluggishly since before the game was released. On newer computers, it wasn’t much of a problem, but it was horrible on older computers. It was odd because the game didn’t seem like it should use much in the way of system resources. I’ve “fixed” this many times without the problem really being fixed. I decided to take a long hard look at my game loop.
Here was my old code:
protected final void runSafely() {
//verify that the loop will continue
shouldContinueLoop = true;
//This variable is used to only skip yielding a certain number of times in a row.
byte numDelays = 0;
//This variable stores how much time the loop overslept by in the last cycle.
//Negative values probably won't occur because Thread.sleep shouldn't undersleep.
long overSleepTime = 0L;
//This variable is used for computing the length of an update phase.
long lastUpdatePhase = Time.currentTimeMillis();
while(shouldContinueLoop) {
//if the application isn't active, sleep for half a second
if(!isApplicationActive()) {
try {
Thread.sleep(500);
} catch(InterruptedException exception) {}
continue;
} //end if the frame isn't active
//figure out the start of the current phase
long currentCycle = Time.currentTimeMillis();
long updatePhaseLength = currentCycle - lastUpdatePhase;
if(updatePhaseLength > period)
updatePhaseLength = period;
//update the current model
doUpdatePhase(updatePhaseLength);
lastUpdatePhase = Time.currentTimeMillis();
//render the game
render();
//figure out how long the cycle took
long timeAfterCycle = Time.currentTimeMillis();
long cycleLength = timeAfterCycle - currentCycle;
long sleepTime = period - cycleLength - overSleepTime;
//if some time is left in this cycle
if(sleepTime > 0) {
//sleep
try {
Thread.sleep(sleepTime);
} catch(InterruptedException exception) {}
overSleepTime = Time.currentTimeMillis() - timeAfterCycle - sleepTime;
} //end if some time is left in this cycle
//else no time is left in this cycle
else {
overSleepTime = 0L;
//don't sleep, but yield if absolutely necessary
numDelays++;
if(numDelays >= maximumDelaysPerYield) {
//yield to let other threads execute
Thread.yield();
numDelays = 0;
} //end if there have been too many delays
} //end else no time is left in this cycle
} //end while forever
} //end runSafely
The code was heavily based upon the game loop code in Killer Game Programming in Java, though it wasn’t quite identical. The point of most of the code was to allow other processes to run at the same time as the game rather than hogging all the system’s resources. I deleted most of that code.
Suddenly, the sluggishness problems were less, though the bug with the time wasn’t fixed. It increased the “period” constant from 40 to 250 because it is effectively the maximum length of the update phase in milliseconds. The problem was solved.
I also changed one other line of code
lastUpdatePhase = Time.currentTimeMillis();
to
lastUpdatePhase = currentCycle;
. This didn’t change anything for me, but I figure I was losing the time that update phase took, which wouldn’t show up on the timer. Since my computer is way more powerful than what the program needs, the update phase just happened to be almost instantaneous.
This is now my game loop for a game I’m updating:
protected final void runSafely() {
//verify that the loop will continue
shouldContinueLoop = true;
//This variable is used for computing the length of an update phase.
long lastUpdatePhase = Time.currentTimeMillis();
while(shouldContinueLoop) {
//if the application isn't active, sleep for half a second
if(!isApplicationActive()) {
try {
Thread.sleep(500);
} catch(InterruptedException exception) {}
continue;
} //end if the frame isn't active
//figure out the start of the current phase
long currentCycle = Time.currentTimeMillis();
long updatePhaseLength = currentCycle - lastUpdatePhase;
if(updatePhaseLength > maximumUpdateLength)
updatePhaseLength = maximumUpdateLength;
//update the current model
doUpdatePhase(updatePhaseLength);
lastUpdatePhase = currentCycle;
//render the game
render();
} //end while forever
} //end runSafely
I won’t go so far as to say that the code from Killer Game Programming in Java was wrong because I didn’t copy it exactly. I’m just saying that you have to be careful what you do with your main loop code. My program has always been somewhat sluggish because of the main loop code, and I didn’t notice the timer but until I made a gameplay video.
The problem is probably caused by how the rest of my code worked. In KGPiJ, the update phase seems like it does a specific amount of updating in each phase. My own code sends the number of milliseconds elapsed to the update method, which updates different amounts based upon how much time has passed. This caused me to change parts of KGPiJ’s game loop, which is probably how things got screwed up.
I have some concerns that my program might run at 100% CPU usage now, but that doesn’t seem to actually happen for some reason. In any case, I would rather use 100% of the CPU than have the game run slowly with an incorrect timer.