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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
| package cg.puzzlecarnival; import cg.main.Time; import cg.puzzlecarnival.gui.GuiAccess;
public class MainLoop {
public static void execute() { byte numDelays = 0; long overSleepTime = 0L; long lastUpdatePhase = Time.currentTimeMillis();
while(true) { if(!GuiAccess.isApplicationActive()) { try { Thread.sleep(500); } catch(InterruptedException exception) {} continue; } long currentCycle = Time.currentTimeMillis(); long updatePhaseLength = currentCycle - lastUpdatePhase; if(updatePhaseLength > period) updatePhaseLength = period; GuiAccess.updateLogic(period); lastUpdatePhase = Time.currentTimeMillis(); GuiAccess.showFrame(); long timeAfterCycle = Time.currentTimeMillis(); long cycleLength = timeAfterCycle - currentCycle; long sleepTime = period - cycleLength - overSleepTime; if(sleepTime > 0) { try { Thread.sleep(sleepTime); } catch(InterruptedException exception) {} overSleepTime = Time.currentTimeMillis() - timeAfterCycle - sleepTime; } else { overSleepTime = 0L; numDelays++; if(numDelays >= maximumDelaysPerYield) { Thread.yield(); numDelays = 0; } } } } private static final int maximumDelaysPerYield = 16;
private static final int period = 40; } |
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.