The computer timer always has a certain granularity. I use the millisecond timer (System.currentTimeMillis), and it has a 10-ms granularity on Windows XP, meaning that you always sleep for multiples of 10 ms. On Linux and Mac, I believe the granularity is 1 ms.
When you use the sleep method you’re using, it uses the millisecond timer. There is a sleep method that uses milliseconds AND nanoseconds, but the real problem is that you’re always sleeping the same amount of time. What you need to do is sleep for the amount of time that’s left in the frame. Some of the time is already used up by the time you spend drawing and updating. My own main loop code looks like this:
package cg.puzzlecarnival;
import cg.main.Time;
import cg.puzzlecarnival.gui.GuiAccess;
/**This class executes the main loop.
* @author Steven Fletcher
* @since 2007/08/14
* @version 2008/02/21
*/
public class MainLoop {
/**Executes the game's main loop.*/
public static void execute() {
//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(true) {
//if the application isn't visible, sleep for half a second
if(!GuiAccess.isApplicationActive()) {
try {
Thread.sleep(500);
} catch(InterruptedException exception) {}
continue;
} //end if the frame isn't visible
//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
GuiAccess.updateLogic(period);
lastUpdatePhase = Time.currentTimeMillis();
//render the game
GuiAccess.showFrame();
//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 execute
//PRIVATE CONSTANTS//////////////////////////////////////////////////////////////////////
//the maximum number of consecutive cycles that can skip the yield
private static final int maximumDelaysPerYield = 16;
//private static final int maximumMillisecondsPerUpdate = 40;
//private static final int millisecondsPerFrameRedraw = 10;
//1000/period frames per second
private static final int period = 40;
} //end class MainLoop
The call to the Time method just calls System.currentTimeMillis. GuiAccess is the class my paint and update methods are in.
As an aside, I wouldn’t suggest switching to System.nanotime. That particularly timer doesn’t work reliably on processors with energy saving capabilities (which is common, especially on laptops) and older dual core AMD processors.