You’ve probably heard of the issue where System.nanoTime() can return strange results on dual-core AMD systems, because the timer is based off of CPU ticks which can be different on each CPU.
Okay, so I don’t have a dual-core AMD system to test, but one person said this code works, so I thought I’d go ahead and share it.
/**
The GameTimer is used to workaround the issue on some dual-core systems where timing
values between different CPUs are out of sync. In this case, the value of System.nanoTime()
can be different depending on what CPU the code is executed on.
This code attempts to guess what CPU the code is executed on, and adjusts accordingly.
*** For best results, only call nanoTime() once per frame. ***
*/
public class GameTimer {
private static final GameTimer INSTANCE = new GameTimer();
private static final int NUM_TIMERS = 8;
private static final long MAX_DIFF = 1000000000L; // 1 sec
private static final long NEVER_USED = -1;
private long[] lastTimeStamps = new long[NUM_TIMERS];
private long[] timeSinceLastUsed = new long[NUM_TIMERS];
private long virtualNanoTime = 0;
private int timesInARowNewTimerChosen = 0;
private long lastDiff = 0;
public static GameTimer getInstance() {
return INSTANCE;
}
private GameTimer() {
for (int i = 0; i < NUM_TIMERS; i++) {
timeSinceLastUsed[i] = NEVER_USED;
}
}
public long nanoTime() {
long diff;
if (timesInARowNewTimerChosen >= NUM_TIMERS) {
long nanoTime = System.currentTimeMillis() * 1000000;
diff = nanoTime - lastTimeStamps[0];
}
else {
long nanoTime = System.nanoTime();
// Find which timer the nanoTime value came from
int bestTimer = -1;
long bestDiff = 0;
for (int i = 0; i < NUM_TIMERS; i++) {
if (timeSinceLastUsed[i] != NEVER_USED) {
long t = lastTimeStamps[i] + timeSinceLastUsed[i];
long timerDiff = nanoTime - t;
if (timerDiff > 0 && timerDiff < MAX_DIFF) {
if (bestTimer == -1 || timerDiff < bestDiff) {
bestTimer = i;
bestDiff = timerDiff;
}
}
}
}
// No best timer found
if (bestTimer == -1) {
// Use last good diff
diff = lastDiff;
// Find a new timer
bestTimer = 0;
for (int i = 0; i < NUM_TIMERS; i++) {
if (timeSinceLastUsed[i] == NEVER_USED) {
// This timer never used - use it
bestTimer = i;
break;
}
else if (timeSinceLastUsed[i] > timeSinceLastUsed[bestTimer]) {
// Least used timer so far, but keep looking
bestTimer = i;
}
}
timesInARowNewTimerChosen++;
}
else {
timesInARowNewTimerChosen = 0;
diff = nanoTime - lastTimeStamps[bestTimer] - timeSinceLastUsed[bestTimer];
// Set lastDiff if this same timer used twice in a row
if (timeSinceLastUsed[bestTimer] == 0) {
lastDiff = diff;
}
}
lastTimeStamps[bestTimer] = nanoTime;
timeSinceLastUsed[bestTimer] = 0;
// Increment usage of all other timers
for (int i = 0; i < NUM_TIMERS; i++) {
if (i != bestTimer && timeSinceLastUsed[i] != NEVER_USED) {
timeSinceLastUsed[i] += diff;
}
}
// Check for total failure
if (timesInARowNewTimerChosen >= NUM_TIMERS) {
lastTimeStamps[0] = System.currentTimeMillis() * 1000000;
}
}
virtualNanoTime += diff;
return virtualNanoTime;
}
}