lwjgl's Timer vs System.nanoTime?

Started by lainmaster, January 22, 2010, 06:09:13

Previous topic - Next topic

lainmaster

Which one should I use? I was using System.nanoTime since I thought that everyone would have the latest java version, but I just sent my game to a friend and, even though it was running fine, the FPS said to be Infinity... which is odd. I don't think it has anything to do with timers, but I have no idea what else it may be.
Anyhow, I made the this test:

Timer oTimer = new Timer();
		long iTime = System.nanoTime();
		while(_bGameRunning){
			Timer.tick();
			System.out.println(oTimer.getTime() + ", " + (System.nanoTime() - iTime) + ", " + Sys.getTime() + ", " + Sys.getTime() / Sys.getTimerResolution() + ", " + Sys.getTimerResolution());
			(etc)


Which gave this result:

0.0, 170925, 14796164, 14796, 1000
0.015, 4051128, 14796179, 14796, 1000
0.015, 5760382, 14796179, 14796, 1000
0.015, 15078763, 14796179, 14796, 1000
0.046, 43870851, 14796210, 14796, 1000
(etc)


Where I see that for many sequential frames I get the same value from lwjgl's Timer but different, more accurate values from System.nanoTime.

I guess that kind of precision isn't really relevant, but it does make a bit of a difference, and I don't really wanna change my code if I can rely on nanoTime.

Any thoughts?

elias4444

The problem is when you start going across different operating systems. Generally speaking, the LWJGL timer is best to use (if that's not the case anymore, someone please let us know!). System.nanoTime is a good improvement over the currentTimeMillis method, but fluctuates a bit too much still on some systems.

=-=-=-=-=-======-=-=-=-=-=-
http://www.tommytwisters.com

lainmaster

System.nanoTime works perfectly for me, and as seen, gives four times better precision than Timer. But it seems nanoTime may fail in some systems, as you said. I checked my FPS calculations, and I could find no way in my head that the FPS would be "Infinity" without the game crashing, but it happened! For now, I just added a System.out.println(System.nanoTime()); as one of the very first things in the game, I'll ask my friend to try again then, and give me the logs once again.

Matzon

The problem with nanotime is that it uses the HPC, which are affected by the clock frequency of the cpu. So for any system that uses scaling frequency the time will jutter.
The LWJGL timer is not affected by this. The price for this is 1ms granularity, instead of 1ns.

The LWJGL timer used to be based on HPC (QPC) , but we switched to the multimedia timer since its stable.

lainmaster

Quote from: Matzon on January 22, 2010, 19:10:26
The price for this is 1ms granularity, instead of 1ns.

Check out the output of the test I posted... it's not 1ms, it's 15! I'm using Windows 7, if it makes any difference.

Matzon

my local test on win7, 64bit showed 1ms resolution for both currentTimeMillis and Sys.getTime - java 6u18

lainmaster

Odd, I seem to be getting 1-3ms resolution from Sys.getTime / Timer.getTime, now.
I think I'll move on to lwjgl's Timer.
Does FPS limiting to lower CPU usage work well with that Timer? My game can run in 60FPS using very little CPU in modern computers, and it's meant to be played while doing other stuff like Firefox with many tabs, Windows Live Messenger, Winamp, or whatever.

elias4444

I think there may be something here with the timer on Windows 7. I just tested my engine using both the lwjgl timer and nanoTime(), and on Windows 7 (64-bit), nanotime ran MUCH smoother (visually noticeable).
=-=-=-=-=-======-=-=-=-=-=-
http://www.tommytwisters.com

lainmaster

Quote from: elias4444 on January 24, 2010, 22:18:47
I think there may be something here with the timer on Windows 7. I just tested my engine using both the lwjgl timer and nanoTime(), and on Windows 7 (64-bit), nanotime ran MUCH smoother (visually noticeable).

Indeed, nanoTime runs just fine for me. At least 5-10 times better than Timer, I think, and it doesn't have any inconsistency. I guess I'll have to try both for some time.

elias4444

Ok, I ran some tests on different machines.

Mac Pro, Mac OS 10.6: Sys.getTime runs perfectly smooth, so does System.nanoTime() (I'm really starting to like my Mac).
Mac Pro, Windows 7 64-bit: Sys.getTime runs choppy, System.nanoTime runs smooth
MacBook Pro, Mac OS 10.6: Sys.getTime runs perfectly smooth, so does System.nanoTime()
MacBook Pro, Windows 7 64-bit: Sys.getTime runs choopy, System.nanoTime runs even more choppy

This is fairly consistent with my previous experiences (particularly with Windows on a laptop). Only difference is that Sys.getTime runs choppy on Windows 7 now. In order to "smooth out" the user experience, I implemented a 5-sample average with Sys.getTime, which helped immensely on Windows, but destroyed the perfect smoothness on Mac OS. I now have a flag in my engine that detects if you're on Windows and then only implements the 5-sample system if it returns true.

I've read a few internet tidbits that other, non-java game developers have run into similar issues. The multisample-elapsed-milliseconds approach sounds like the best way to fix it.
=-=-=-=-=-======-=-=-=-=-=-
http://www.tommytwisters.com

Matzon


elias4444

Yeah, that's a little tricky. I'm trying to find a way to get some solid statistics going for this. Unfortunately, it's more of a "feel" thing right now, which is hard for me to trust.
=-=-=-=-=-======-=-=-=-=-=-
http://www.tommytwisters.com

elias4444

Ok, I scrounged up another laptop to test on. A sony VAIO, Windows 7 32-bit. Sys.getTime() runs really choppy, but nanoTime() does just fine.

I also retested my Mac Pro with a System.nanoTime timer on the Windows 7 64-bit side (bootcamp). nanoTime runs smoother than even the 5-sample getTime method.

I wish I could pull some numbers out and show them to you, but it's really just something you have to see. I've been using my little benchmarker I made just the other day: http://www.tommytwisters.com/games/tommybench/files/tommybench.jnlp

I pay attention to how smoothly the animated models rotate around the center. Right now, it's setup to use nanoTime. When I switch it back to Sys.getTime(), I can see the rotation get jumpy.
=-=-=-=-=-======-=-=-=-=-=-
http://www.tommytwisters.com

Matzon

Quote from: elias4444 on January 25, 2010, 19:22:17
Ok, I scrounged up another laptop to test on. A sony VAIO, Windows 7 32-bit. Sys.getTime() runs really choppy, but nanoTime() does just fine.

I also retested my Mac Pro with a System.nanoTime timer on the Windows 7 64-bit side (bootcamp). nanoTime runs smoother than even the 5-sample getTime method.

I wish I could pull some numbers out and show them to you, but it's really just something you have to see. I've been using my little benchmarker I made just the other day: http://www.tommytwisters.com/games/tommybench/files/tommybench.jnlp

I pay attention to how smoothly the animated models rotate around the center. Right now, it's setup to use nanoTime. When I switch it back to Sys.getTime(), I can see the rotation get jumpy.

tbh, some code might need to be shown - since it could be a fault in the actual code more so than the timer itself. Not that it IS but it COULD be.

elias4444

Code? No problem! This is my gamecontainer class that contains the timer function. The commented code is the Sys.getTime() version with the 5-sample method. Right now it's set to use nanoTime().

package com.tommyengine.game;

import com.tommyengine.managers.ScreenManagerInterface;


public class GameContainer {

	private GameState[] gameStates;
	private int activeGameState = -1;

	private long currentTime = 0;
	private long lastTime = 0;
	private long elapsedMillis = 0;
	private boolean timersInitialized = false;

	private ScreenManagerInterface screen;
	private boolean running = false;

	//private long[] timeSamples = new long[5];
	//private boolean timeFilled = false;
	//private long timeSampleAvg = 0;
	//private int timeSamplesPointer = 0;

	//private boolean windowsOS = false;

	public GameContainer(ScreenManagerInterface screen) {
		//if (System.getProperty("os.name").toLowerCase().indexOf("windows") != -1) {
		//	windowsOS = true;
		//	System.out.println("Timer system initialized for a Windows OS");
		//}
		this.screen = screen;
		//for (int i=0;i<timeSamples.length;i++) {
		//	timeSamples[i] = 0;
		//}
	}

	public int addGameState(GameState gameState) {
		gameState.init();

		if (gameStates == null) {
			gameStates = new GameState[1];
			gameStates[0] = gameState;
			gameStates[0].activate();
			activeGameState = 0;
			return 0;
		} else {
			GameState[] tempGameStates = new GameState[gameStates.length + 1];
			for (int i=0;i<gameStates.length;i++) {
				tempGameStates[i] = gameStates[i];
			}
			tempGameStates[gameStates.length] = gameState;

			gameStates = tempGameStates.clone();

			tempGameStates = null;
			return (gameStates.length - 1);
		}
	}

	public void setActiveGameState(int whichone) {
		if (gameStates != null) {
			if (whichone < gameStates.length) {
				if (gameStates[whichone] != null) {
					activeGameState = whichone;
					gameStates[whichone].activate();
				}
			}
		}

		if (!running) {
			run();
		}

	}

	public GameState getGameState(int whichone) {
		if (gameStates == null) {
			return null;
		} else {
			if (gameStates[whichone] != null) {
				return gameStates[whichone];
			} else {
				return null;
			}
		}
	}

	public void update() {

		updateElapsedMillis();

		if (gameStates != null && activeGameState != -1) {
			gameStates[activeGameState].update(elapsedMillis);
		}

	}

	public void render() {

		screen.clearscreen();

		if (gameStates != null && activeGameState != -1) {
			gameStates[activeGameState].draw();
		}

		screen.updatedisplay();


	}

	private void updateElapsedMillis() {
		if (!timersInitialized) {
			timersInitialized = true;
			lastTime = getTime();
		}

		currentTime = getTime();

		/*
		if (windowsOS) {
			//// Best to average last 5 frames for smooth animation with timer
			timeSamples[timeSamplesPointer] = currentTime - lastTime;
			timeSampleAvg = 0;
			if (timeFilled) {
				for (int i=0;i<timeSamples.length;i++) {
					timeSampleAvg += timeSamples[i];
				}
				elapsedMillis = (long)((double)timeSampleAvg / timeSamples.length);
			} else {
				for (int i=0;i<timeSamplesPointer;i++) {
					timeSampleAvg += timeSamples[i];
				}
				elapsedMillis = (long)((double)timeSampleAvg / timeSamplesPointer);
			}
			timeSamplesPointer++;
			if (timeSamplesPointer >= timeSamples.length) {
				timeSamplesPointer = 0;
				timeFilled = true;
			}
		} else {
		}
		*/

		elapsedMillis = currentTime - lastTime;

		lastTime = currentTime;

	}


	private long getTime() {
		return (System.nanoTime() / 1000000);
		//return (Sys.getTime() * 1000) / Sys.getTimerResolution();
	}

	public void run() {
		running = true;
		while (running) {
			update();
			render();
		}
		System.out.println("Shutting Down GameContainer Thread...");
		screen.shutdown();
		System.out.println("GameContainer Thread Shutdown Complete");
	}

	public boolean isRunning() {
		return running;
	}

	public void shutdown() {
		running = false;
	}


}
=-=-=-=-=-======-=-=-=-=-=-
http://www.tommytwisters.com