Animator with nanoTime precision

hello, a while back we discussed how the FPSAnimator is working with java’s Timer and TimerTask and found out that they work with Object.wait() which is only millisecond accurate.

for games high-precision accuracy is very important, so System.currentMillis() is just not doing the job, so System.nanoTime() would be better, IMO.

here is a small class which targets a certain framerate and uses nanoSecond precision. we have been using it for quite a while now in our game and are very satisfied.
please note that it has far less features than the FPSAnimator that comes with jogl, but maybe it is useful for someone of you:

package com.avaloop.papermint.common;

import javax.media.opengl.GLAutoDrawable;

/**
 * this is a nanotime-precision animator
 * @author emzic
 */
public class NanoAnimator implements Runnable {

	private GLAutoDrawable drawable;
	private Thread thread;
	private float targetfps;
	private long targetduration;	
	private boolean keeprunning;
	
	public NanoAnimator(GLAutoDrawable drawable) {
		this.drawable = drawable;
		setTargetfps(999);		
	}
	
	
	public void start() {
		if (thread == null || thread.isAlive()) {
			keeprunning = true;
			thread = new Thread(this, "NewAnimator");
			thread.start();
		}
	}
	
	public void stop() {
		keeprunning = false;
	}

	public void run() {
		
		while(keeprunning) {
			
			try {
				long t1 = System.nanoTime();
				drawable.display();				
				long t2 = System.nanoTime();
				
				long duration = t2 - t1;
				while (duration < targetduration) {
					Thread.yield();
					t2 = System.nanoTime();
					duration = t2 - t1;
				}
				
			} catch (Exception e) {
				e.printStackTrace();
			}
			
		}
		
	}
	

	public float getTargetfps() {
		return targetfps;
	}


	public void setTargetfps(float targetfps) {
		this.targetfps = targetfps;
		targetduration = (long) ((1f / targetfps) * Timing.NANOSECONDS);
	}
	
}

feedback appreciated. i dont know if the while-Thread.yield loop is a good idea.

Thread.yield kinda sucks.

You need to resort to native code to implement nano/micro-sleeps

(see my forum-thread on sandboxed-jvm-as-a-service for sourcecode)

yeah, i too think it sucks, but it is quite ok for an acceptable solution.

(i am in no mood to maintain native code for win, mac and linux :wink: )

where exactly is the thread you mentioned?



#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <time.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>

//  i don't know which are required...



void microsleep(const int x)
{
    struct timespec sleeptime;
    struct timespec remaining;

    remaining.tv_sec = x/1000000;
    remaining.tv_nsec = (x%1000000)*1000;
    do
    {                                 
        sleeptime.tv_sec  = remaining.tv_sec;
        sleeptime.tv_nsec = remaining.tv_nsec;
    } while(nanosleep(&sleeptime,&remaining)<0 && errno==EINTR);
}

unfortunately it turned out, that the while-Thread.yield loop is taking up all of the CPU, so actually this solution really sucks. XD

seems like i really have to look into using native code. :slight_smile:

What about System.sleep(long, int)? But I haven’t tested it.

yeah i tried it with

Thread.sleep(0,1)

which i think is the smallest possible time to sleep (1 nanosecond)
but then, this gives actually quite irregular framerates. it seems to jump form half the target-fps to the full target-fps up and down.

ok, my last post was pretty stupid…

here is a slightly better version to wait:


				long t1 = System.nanoTime();
				drawable.display();				
				long t2 = System.nanoTime();
				
				long duration = t2 - t1;
				
				long nanostowait = targetduration - duration;
				if ( nanostowait > 0) {
					long millis = nanostowait / 1000000;
					int nanos = (int) (nanostowait % 1000000);
					
					Thread.sleep(millis, nanos);
					
				}

Thread.sleep(ms, ns) is implemented like:


Thread.sleep(ms + (ns<500000?0:1), 0);

So there is no use in passing nanoseconds.

oh my, this is quite disappointing. thanks for the hint though.

why does this function even exist?

ok, here is a version with a semaphore, which seems to work quite ok. what do you think?


	public void run() {
		
		sem = new Semaphore(1);
		try {
			sem.acquire();
		} catch (InterruptedException e1) {
			e1.printStackTrace();
		}
		
		
		
		while(keeprunning) {
			
			try {
				long t1 = System.nanoTime();
				drawable.display();				
				long t2 = System.nanoTime();
				
				long duration = t2 - t1;
				

				long nanostowait = targetduration - duration;
				if ( nanostowait > 0) {

					sem.tryAcquire(nanostowait, TimeUnit.NANOSECONDS);
					
				}
			
				
			} catch (Exception e) {
				e.printStackTrace();
			}
			
		}
		
	}

On my computer (jdk1.6.0_05, WinXP, C2Q6600) Semaphore.tryAcquire has the same granularity as Thread.sleep (sleep(1) is about 1.9ms, sleep(2) is 2.9ms). Measure it yourself with this code:


        long start, end;
        Semaphore sem = new Semaphore(0);
        for (int i = 0; i < 100; i++) {
            start = System.nanoTime();
            sem.tryAcquire(10000, TimeUnit.NANOSECONDS);
            end = System.nanoTime();
            System.out.println(end - start);
        }

Looking through the source, Semaphore is implemented using:

http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/locks/LockSupport.html#parkNanos(long)

(which in-turn falls through to some utility method in sun.misc.Unsafe)

And sun.misc.Unsafe is implemented in \hotspot\src\share\vm\prims\unsafe.cpp which uses the Parker::park() function (defined in \hotspot\src\share\vm\runtime\mutex.hpp) whose implementation is platform dependant. Here are the implementations and the system functions used for the waiting.

\hotspot\src\os\win32\vm\mutex_win32.cpp - WaitForSingleObject
\hotspot\src\os\linux\vm\mutex_linux.cpp - pthread_cond_timedwait
\hotspot\src\os\solaris\vm\mutex_solaris.cpp - _lwp_cond_timedwait or pthread_cond_timedwait or cond_timedwait

The function on Windows uses milliseconds. The function on Linux is on one site said to usually use millisecond granularity. I don’t know about the Solaris functions.

thanks everybody for your information and sorry for the late reply.

still the semaphore-version gives quite realiable timing on vista and xp for me. maybe it is because it is not only milli-second-precise but also milli-second-accurate?

thanks!