Slight jerkiness

This has been bothering me all morning.

I have created the base of a custom game engine, and a test program using it that simply has some circles bouncing around the screen. It seems like there is some slight jerkiness to the movement, every half second or so, and I have no idea what is causing it.

If you have any thoughts, it would be much appreciated. Also, if you notice any glaring inefficiencies that I’d want to take care of, please do let me know.

Since the code is still relatively small, I’ll post it in its entirety:


package game;

import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.image.BufferStrategy;

/**
 * Base component for games and other similar graphics intensive
 * applications.
 * 
 * Usage: Create instance of a GameComponent subclass, add to a 
 * Container, and call the GameComponent's start() method. Parent 
 * must be made visible prior to calling start(). Override update() 
 * and draw() for custom game content.
 * 
 * @author removed for forum
 *
 */
public abstract class GameComponent extends Canvas implements Runnable {

	private static final long nanosecondsPerSecond = 1000000000L;
	private static final long millisecondsPerNanosecond = 1000000L;

	protected int frameRate; // how many times per second to (ideally) run the game loop
	private long timePerFrame; // nanoseconds per frame based on framerate

	protected Dimension resolution; // holds the width/height of game's canvas

	private Image drawImage;
	protected Graphics2D drawGraphics; // reference to drawImage's graphics

	protected Color backgroundColor = Color.BLACK;

	protected boolean running;

	// creates GameComponent with default resolution of 800x600 at 60 fps
	public GameComponent() {
		this(new Dimension(800,600), 60);
	}

	// creates GameComponent with passed resolution at 60 fps
	public GameComponent(Dimension resolution) {
		this(resolution, 60);
	}

	// creates GameComponent with passed resolution at passed fps
	public GameComponent(Dimension resolution, int frameRate) {
		this.resolution = resolution;
		this.frameRate = frameRate;

		timePerFrame = nanosecondsPerSecond / frameRate;
		setPreferredSize(resolution);
	}

	// final set up and starts game loop
	public void start() {
		createBufferStrategy(2); // double buffering
		running = true;
		new Thread(this).start(); // start game loop
	}

	@Override public void run() {
		// game loop
		while(running) {
			long timeBeforeLoop = System.nanoTime(); // time when game loop starts

			update();
			draw();
			render();

			try { 
				Thread.sleep(calculateSleepTime(timeBeforeLoop)); 
			} catch (InterruptedException e) {}
		}
	}

	// update everything in the game, should be overridden
	public void update() {}

	// draw everything in the game, should be overridden
	public void draw() {
		if(drawImage == null) 
			drawImage = createImage((int)resolution.getWidth(), (int)resolution.getHeight());
		
		drawGraphics = (Graphics2D) drawImage.getGraphics();
		drawGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);

		drawGraphics.setColor(backgroundColor); 
		drawGraphics.fillRect(0, 0, getWidth(), getHeight()); // draw background
	}

	// render the graphics, buffer strategy uses volatile images, so deal with possible content loss
	private void render() {
		drawGraphics.dispose();
		BufferStrategy strategy = getBufferStrategy();
		do {
			do {
				Graphics2D g = (Graphics2D) strategy.getDrawGraphics();
				g.drawImage(drawImage, 0, 0, null);
				g.dispose();
			} while (strategy.contentsRestored());

			strategy.show(); // Display the buffer
		} while (strategy.contentsLost()); // Repeat the rendering if the drawing buffer was lost
		Toolkit.getDefaultToolkit().sync();
	}

	// figure out how long the thread should sleep to keep a consistent frame rate
	private long calculateSleepTime(long beforeLoop) {
		long afterLoop = System.nanoTime(); // get time after loop
		long difference = afterLoop - beforeLoop; // calculate the difference
		long timeToSleep = (timePerFrame - difference) / millisecondsPerNanosecond; // calculate time to sleep in milliseconds

		// if you see this in the console when running, it means the game loop isn't keeping up with frame rate
		if(timeToSleep < 0)
			System.out.println("sleep time is < 0");

		return (timeToSleep > 0) ? timeToSleep : 0; // if timeToSleep is < 0, frame took longer than timePerFrame, so sleep time is 0
	}
}

And here is the test game using the above component:


package game.test;

import java.awt.Color;
import java.awt.Shape;
import java.awt.geom.Ellipse2D;
import java.util.Random;
import game.GameComponent;
import javax.swing.JFrame;

public class GameTest extends GameComponent {
	private static final int NUM_CIRCLES = 5;
	private static final Circle[] circles = new Circle[NUM_CIRCLES];
	
	private static Random random = new Random();
	
	public static void main(String[] args) {
		JFrame frame = new JFrame();
		GameComponent game = new GameTest();
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.add(game);
		frame.pack();
		frame.setVisible(true);
		game.start();
	}
	
	public GameTest() {
		// create some random circles
		for(int i=0; i<circles.length; i++) {
			Color color = new Color(random.nextInt(256),random.nextInt(256),random.nextInt(256),random.nextInt(256));
			int radius = random.nextInt(100) + 30;
			int x = random.nextInt(800-radius);
			int y = random.nextInt(600-radius);
			circles[i] = new Circle(x, y, radius, color);
		}
	}
	
	@Override public void update() {
		super.update();
		
		for(Circle circle : circles)
			circle.update();
	}
	
	@Override public void draw() {
		super.draw();
		
		for(Circle circle : circles)
			circle.draw();
	}
	
	class Circle {
		private int x, y, width, height, xVelocity, yVelocity;
		private Color color;
		
		public Circle(int x, int y, int radius, Color color) {
			this.x = x;
			this.y = y;
			width = height = radius;
			this.color = color;
			xVelocity = random.nextInt(6) + 4;
			//yVelocity = random.nextInt(6) + 4;
		}
		
		public void update() {
			if(x < 0 || x > getWidth()-width) // check left/right bounds
				xVelocity *= -1; // reverse horizontal velocity
			if(y < 0 || y > getHeight()-height) // check top/bottom bounds
				yVelocity *= -1; // reverse vertical velocity
			
			x += xVelocity;
			y += yVelocity;
		}
		
		public void draw() {
			drawGraphics.setColor(color);
			Shape circle = new Ellipse2D.Float(x, y, width, height);
			drawGraphics.fill(circle);
		}
	}
}

I didn’t read any further than this


        while(running) {
            long timeBeforeLoop = System.nanoTime(); // time when game loop starts

I’m pretty sure you want to switch the order of those 2 lines.

There are several good game loops in the tutorial section.http://www.java-gaming.org/index.php/board,65.0.html

Nope, I need the time before each loop begins. I use it, and the time after the render, to figure out how long my sleep should be. If I were to just sleep at a constant rate, say, 1000/60, thinking it is 60 frames per second, that would be ignoring how much time it actually takes to run the game loop, which will vary depending upon what is happening in the game.

sleep() is inaccurate, System.currentTimeMillies(), too. System.nanoTime() has it’s own merrits. Best way currently seems to use a native timer like in LWJGL and use a while loop with sleep(1) or yield until the desired time has passed. System.nanoTime() mostly also works if you don’t want a native timer. In all cases make sure you stay in the same thread.

@cylab
Using a busy while loop with Thread.yield() maxes out your CPU. The best approach is a busy while loop with Thread.sleep(1).

Depends. Thread.sleep(1) certainly is more CPU-friendly, but Thread.yield() gives you a smoother result. I tried to mix the two (using Thread.yield() for the last 5ms), but I didn’t get better results than using only sleep(1) alone.

Yes that is tradeoff: smoother yet hogs CPU or a tiny bit choppy and very little CPU.

Isn’t this triple buffering? Is there a purpose for that? It’s a lot faster to just double buffer?

Triple buffering just keeps things smooth if there’s a little jitter in frame rendering times and you’re vsyncing.

Cas :slight_smile:

Thanks for posting the example code. I was able to run it and take a look. I can see the jitter.

I have a really stripped down game loop I wrote that also just bounces a ball on the x axis (but includes a wrap-around for the y axis). I’m using a util.Timer to run the loop, and am being scrupulous about managing the concurrency issues presented by there being an update thread that is not on the EDT. It has EXACTLY THE SAME JITTER.

I have also run into a problem with mouseEvents presenting themselves with a similar jitter, rather than being as smooth as expected or desired, making the use of sliders or other audio controls annoyingly bumpy.

I would love to get to the bottom of this. I’ve been using my meagre understanding to explore possibilities like GC, the management of multiple threads, and the possibility of “clogging” the EDT.

While we are at it, why does the leading edge of these solid graphics tend to look so blurry compared to the backside? In terms of the eye and expectation, a moving graphic should have a tail blur (like a comet) rather than a blurry front end.

Hmm. I’ve been using a separate thread that sleeps forever (this keeps the sleep timer accurate) and using a single sleep() in the rendering loop.

Having tested on various windows systems (XP, 7, Vista) I’ve yet to see a case where that wasn’t completely smooth. Is it just that I haven’t run into a configuration where that won’t help yet?

Linux and Mac have been reliably smooth to begin with, even before the sleep-forever thread business.

I think I have something of an answer, though it may only pertain to my lame Win XP operating system. The class that I used for timing measurements is included at the end of this post: TimeMetric.peg() to start the clock, TimeMetric.dur() to get the duration from the “peg” point.

When I set the util.timer for my game loop at 20msec as follows

timer.scheduleAtFixedRate(new GameLoopIteration(), 0, 20);

…and measure the time between the starts of my game loop, I get a mix of readings at approximately either 15.5 msec or 31 msec per game loop, with the bulk at 15.5. When I set the timer to repeat at 30msec, I got mostly 31 msec Game loop durations with occasional 15 msec loops. In other words, the loops were stuck at increments of the Windows XP 15 msec resolution (“Killer Game Programming in Java” pg 24 refers to this), but the Java Timer did its best to compensate.

Similarly, when I ran yours and timed the time between the start and the end of the game loop (including the Thread.sleep() command), I got a mix of 15.5 msec and 31 readings that worked out to roughly 60 fps. I’ll post the data below. I noticed that the longer pauses tend to come in pairs (separated by two or three 15.5 loops) at a frequency of about 4 clusters per second. This matches my perceived jerkiness of about 3-5 bumps per (what I was estimating prior to doing this).

And, I’d have to say, I looked at several other Java games, and for me they all have this same stutter. So, at least in my case, I’m guessing the answer is that Java is doing it’s best to compensate for an OS with poor clock resolution.

[quote]start:17951013775514 dur:15535733
start:17951029435694 dur:15439018
start:17951044956122 dur:15571914
start:17951060634078 dur:15549750
start:17951076309664 dur:31100061
start:17951107517505 dur:15507826
start:17951123147504 dur:15479966
start:17951138735660 dur:31144651
start:17951170000132 dur:15567785
start:17951185691570 dur:15952033
start:17951201742825 dur:15005712
start:17951216853654 dur:15549195
start:17951232525669 dur:15494434
start:17951248125585 dur:15520256
start:17951263756321 dur:15550797
start:17951279418365 dur:15451638
start:17951294959202 dur:15590079
start:17951310698093 dur:15461905
start:17951326271357 dur:15530569
start:17951341931612 dur:31084578
start:17951373135364 dur:15478907
start:17951388723047 dur:15567234
start:17951404409199 dur:31085252
start:17951435597126 dur:15545870
start:17951451262391 dur:15504640
start:17951466889126 dur:15468215
start:17951482462848 dur:15520638
start:17951498089775 dur:15538512
start:17951513713065 dur:15535259
start:17951529349579 dur:15534044
start:17951545004410 dur:15510105
start:17951560659569 dur:31066870
start:17951591838966 dur:15513725
start:17951607465439 dur:15559739
start:17951623133790 dur:31107611
start:17951654372607 dur:15494078
start:17951669986572 dur:15541525
start:17951685641459 dur:15449603
start:17951701284910 dur:15452468
start:17951716855600 dur:15546052
start:17951732509152 dur:15492988
start:17951748117717 dur:15490963
start:17951763726059 dur:15504638
start:17951779348092 dur:15640477
start:17951795073502 dur:15407284
start:17951810600979 dur:16019505
start:17951826738113 dur:30600544
start:17951857446447 dur:15529390
start:17951873097578 dur:15503299
start:17951888720095 dur:15529971
start:17951904354950 dur:31121213
start:17951935559119 dur:15559268
start:17951951317991 dur:15434383
start:17951966859334 dur:15598349
start:17951982562367 dur:15421149
start:17951998101050 dur:15474929
+++++++++++++++++++++++++++++++++
start:17952013683309 dur:15502181
start:17952029267850 dur:15579682
start:17952044969119 dur:15498882
start:17952060588450 dur:16147580
start:17952076840662 dur:30475345
start:17952107419790 dur:15521123
start:17952123042274 dur:15547140
start:17952138707796 dur:15549590
start:17952154381245 dur:31106044
start:17952185600130 dur:15617912
start:17952201330292 dur:15410410
start:17952216827223 dur:15510369
start:17952232455097 dur:15499749
start:17952248082604 dur:15536524
start:17952263717056 dur:15547939
start:17952279378055 dur:15469383
start:17952294956500 dur:15493189
start:17952310559366 dur:15499110
start:17952326168088 dur:15566256
start:17952341841654 dur:31100731
start:17952373045504 dur:15512082
start:17952388667126 dur:15554054
start:17952404345229 dur:31092671
start:17952435540390 dur:15509769
start:17952451158909 dur:15570220
start:17952466976751 dur:15317243
start:17952482395065 dur:15527459
start:17952498036021 dur:15565272
start:17952513724490 dur:15460837
start:17952529297548 dur:15651442
start:17952545056267 dur:15421376
start:17952560589391 dur:16010731
start:17952576712442 dur:30582573
start:17952607403550 dur:15540779
start:17952623030995 dur:31162899
start:17952654316339 dur:15491144
start:17952669925501 dur:16005352
start:17952686052660 dur:14999360
start:17952701163666 dur:15487302
start:17952716741088 dur:15563166
start:17952732424676 dur:15522604
start:17952748071221 dur:15460587
start:17952763620177 dur:15545694
start:17952779261927 dur:15570279
start:17952794937189 dur:15492809
start:17952810535486 dur:31113102
start:17952841732280 dur:15605118
start:17952857433559 dur:15466607
start:17952873009158 dur:15545133
start:17952888664015 dur:31116474
start:17952919905289 dur:15516536
start:17952935529240 dur:16959620
start:17952952579638 dur:14061165
start:17952966741828 dur:15546348
start:17952982395094 dur:15712390
start:17952998208814 dur:15943423
[/quote]

public class TimeMetric {

    private long start;
    private int fpsCounter;
    
    public void fps()
    {
        if (System.nanoTime() - start > 1000000000)
        {
            start = System.nanoTime();
            System.out.println("fps:" + fpsCounter);
            fpsCounter = 0;
        }
        else
        {
            fpsCounter++;
        }
    }
    
    public void peg()
    {
        start = System.nanoTime();
    }
    
    public void dur()
    {
        System.out.println("start:" + start 
                + " dur:" + (System.nanoTime() - start));
    }
}

EDIT: In case it wasn’t clear, the big string of numbers is from Cilution’s game loop, and comprises 2 complete seconds.

EDIT2: I am also wondering if I’m getting fooled or confused by the faultiness in the system time. I mean, I could be reporting about flaws in the attempt to measure rather than anything else. In other words, the loop may be operating in a regular fashion, but the attempt to measure it is hampered by the system OS clock granularity. (But if the code is ALSO using nano time…?)

++++++++++

I’ve also did some testing on things like the frequency of events from the event stream when doing a MouseMotionListener. I’m getting up about to 120 events per second. But the number of events goes down dramatically if I slow the mouse motion down. I’m not sure what the mechanism is there. But on some of the “granularity” or “lag” I’ve been complaining about with JSliders and such, maybe the MouseMotionListener event stream is not the bottleneck after all. It IS going 2x the rate of my game loops.

Did I mention that I verified that the contents of the game loops (e.g. the update and renders) are significantly shorter (like a 10:1 ratio) than the time allotted for the game loop for both Cilution and my own test app. So I’m pretty sure now that “clogging the EDT” is not happening in this instance. Nor are there concurrency issues or thread switching issues involved.

Nor do I see evidence of where GC is coming into significant play. I think the OS is the culprit and I don’t know if anything can be done about it except get a better OS.

Another EDIT: avm1979 points out that this is not so much a problem on Linux and Mac. This is consistent with what Killer Game Programming says about the system clocks.

I think the nature of inaccuracy of Cilution’s game loop resides in the Thread.sleep() command. In other words, this is the long form of cylab’s first answer. Here is further evidence. I can kind of see a pattern that the sleep() waits for periodic triggers (from OS) to determine when to check the time again. And these triggers are coming roughly every 15 msec and are independent of the start time. Yes? No?

	public static void main(String[] args) throws InterruptedException 
	{
		long start = 0;
		long end = 0;
		for (int i = 5; i < 100; i += 5)
		{
			System.out.print("Sleep Int: " + i + "   ");
			start = System.currentTimeMillis();
			Thread.sleep(i);
			end = System.currentTimeMillis();
			System.out.println("  difMillis: " + (end - start));

			int j = 0;
			while ( j < Math.random()* 10000) j++;

			start = System.nanoTime();
			Thread.sleep(i);
			end = System.nanoTime();
			System.out.print("                   difNanos: "); 
			System.out.format("%.1f%n", (end - start)/1000000.0);	
		}
	}

Results: [quote]Sleep Int: 5 difMillis: 16
difNanos: 14.8

Sleep Int: 10 difMillis: 15
difNanos: 14.8

Sleep Int: 15 difMillis: 31
difNanos: 15.3

Sleep Int: 20 difMillis: 31
difNanos: 31.4

Sleep Int: 25 difMillis: 32
difNanos: 15.4

Sleep Int: 30 difMillis: 31
difNanos: 31.5

Sleep Int: 35 difMillis: 31
difNanos: 34.9

Sleep Int: 40 difMillis: 46
difNanos: 46.2

Sleep Int: 45 difMillis: 47
difNanos: 45.8

Sleep Int: 50 difMillis: 63
difNanos: 62.3

Sleep Int: 55 difMillis: 63
difNanos: 55.5

Sleep Int: 60 difMillis: 63
difNanos: 62.8

Sleep Int: 65 difMillis: 79
difNanos: 65.3

Sleep Int: 70 difMillis: 78
difNanos: 77.8

Sleep Int: 75 difMillis: 78
difNanos: 75.0

Sleep Int: 80 difMillis: 78
difNanos: 93.5

Sleep Int: 85 difMillis: 79
difNanos: 85.7

Sleep Int: 90 difMillis: 94
difNanos: 93.6

Sleep Int: 95 difMillis: 94
difNanos: 95.6

I tried putting an enclosing sleep around the short sleep tests. It doesn’t have any effect as far as I can see on my Windows XP OS (nor did I expect it to).

public class TimeMetric 
{
	public static void main(String[] args) throws InterruptedException 
	{
		Runnable s = new Sleeper();
		Thread t = new Thread(s);
		t.start();
		System.out.println("Sleeper started");
		long start = 0;
		long end = 0;
		for (int i = 5; i < 100; i += 5)
		{
			System.out.print("Sleep Int: " + i + "   ");
			start = System.currentTimeMillis();
			Thread.sleep(i);
			end = System.currentTimeMillis();
			System.out.println("  difMillis: " + (end - start));
			int j = 0;
			while ( j < Math.random()* 10000) j++;
			start = System.nanoTime();
			Thread.sleep(i);
			end = System.nanoTime();
			System.out.print("                    difNanos: "); 
			System.out.format("%.6f%n", (end - start)/1000000.0);
			System.out.println();
		}
	}
	
}

class Sleeper implements Runnable
{

	@Override
	public void run() {
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("sleep done");
	}
}

[quote]Sleeper started
Sleep Int: 5 difMillis: 16
difNanos: 15.182449

Sleep Int: 10 difMillis: 15
difNanos: 15.102607

Sleep Int: 15 difMillis: 16
difNanos: 0.259092

Sleep Int: 20 difMillis: 32
difNanos: 30.665284

Sleep Int: 25 difMillis: 31
difNanos: 14.293454

Sleep Int: 30 difMillis: 32
difNanos: 30.546560

Sleep Int: 35 difMillis: 47
difNanos: 34.720362

Sleep Int: 40 difMillis: 47
difNanos: 46.554222

Sleep Int: 45 difMillis: 47
difNanos: 45.686950

Sleep Int: 50 difMillis: 63
difNanos: 61.923440

Sleep Int: 55 difMillis: 63
difNanos: 55.459901

Sleep Int: 60 difMillis: 62
difNanos: 62.908718

Sleep Int: 65 difMillis: 62
difNanos: 65.218988

Sleep Int: 70 difMillis: 79
difNanos: 77.842012

Sleep Int: 75 difMillis: 78
difNanos: 75.033194

Sleep Int: 80 difMillis: 94
difNanos: 93.482963

Sleep Int: 85 difMillis: 78
difNanos: 85.759138

Sleep Int: 90 difMillis: 93
difNanos: 93.525450

Sleep Int: 95 difMillis: 94
difNanos: 95.498452

sleep done
[/quote]

FYI, some nano measurements of Thread.sleep(0) and Thread.sleep(1) follow. Nothing conclusive as the samples are minute compared to their use in the real world. My prediction is that the issue for WinXP is still whether or not the clock-change occurs during the sleep command. Shortening the sleep duration lessens the odds but the possibility still exists of a sleep bridging the changeover moment.

First 20 sleep(0) then forty sleep(1) follow. It seems sleep(0) is reliably short, but can one guarantee that it won’t occasionally land on a cusp? (If it does so, rarely, does it perhaps not matter?)

[quote]Sleep Int: 0 difMillis: 0
difNanos: 0.008538

Sleep Int: 0 difMillis: 0
difNanos: 0.002973

Sleep Int: 0 difMillis: 0
difNanos: 0.002764

Sleep Int: 0 difMillis: 0
difNanos: 0.004156

Sleep Int: 0 difMillis: 0
difNanos: 0.007941

Sleep Int: 0 difMillis: 0
difNanos: 0.006129

Sleep Int: 0 difMillis: 0
difNanos: 0.001793

Sleep Int: 0 difMillis: 0
difNanos: 0.002782

Sleep Int: 0 difMillis: 0
difNanos: 0.003863

Sleep Int: 0 difMillis: 0
difNanos: 0.002957

Sleep Int: 0 difMillis: 0
difNanos: 0.007223

Sleep Int: 0 difMillis: 0
difNanos: 0.124464

Sleep Int: 0 difMillis: 0
difNanos: 0.006578

Sleep Int: 0 difMillis: 0
difNanos: 0.004079

Sleep Int: 0 difMillis: 0
difNanos: 0.003970

Sleep Int: 0 difMillis: 0
difNanos: 0.004483

Sleep Int: 0 difMillis: 0
difNanos: 0.004786

Sleep Int: 0 difMillis: 0
difNanos: 0.004425

Sleep Int: 0 difMillis: 0
difNanos: 0.160109

===============================
Sleep Int: 1 difMillis: 15
difNanos: 14.548917

Sleep Int: 1 difMillis: 15
difNanos: 15.004544

Sleep Int: 1 difMillis: 0
difNanos: 1.736384

Sleep Int: 1 difMillis: 0
difNanos: 1.500087

Sleep Int: 1 difMillis: 0
difNanos: 1.616483

Sleep Int: 1 difMillis: 0
difNanos: 1.649363

Sleep Int: 1 difMillis: 0
difNanos: 1.409210

Sleep Int: 1 difMillis: 0
difNanos: 1.790007

Sleep Int: 1 difMillis: 0
difNanos: 1.779866

Sleep Int: 1 difMillis: 0
difNanos: 1.695479

Sleep Int: 1 difMillis: 0
difNanos: 1.670195

Sleep Int: 1 difMillis: 0
difNanos: 1.767697

Sleep Int: 1 difMillis: 0
difNanos: 1.768557

Sleep Int: 1 difMillis: 0
difNanos: 1.549912

Sleep Int: 1 difMillis: 0
difNanos: 1.704298

Sleep Int: 1 difMillis: 0
difNanos: 1.859985

Sleep Int: 1 difMillis: 0
difNanos: 1.862011

Sleep Int: 1 difMillis: 0
difNanos: 1.819516

Sleep Int: 1 difMillis: 0
difNanos: 1.808976

Sleep Int: 1 difMillis: 16
difNanos: 14.706409

Sleep Int: 1 difMillis: 15
difNanos: 2.657054

Sleep Int: 1 difMillis: 0
difNanos: 1.351128

Sleep Int: 1 difMillis: 0
difNanos: 1.680380

Sleep Int: 1 difMillis: 0
difNanos: 1.354159

Sleep Int: 1 difMillis: 0
difNanos: 1.823933

Sleep Int: 1 difMillis: 0
difNanos: 1.657025

Sleep Int: 1 difMillis: 0
difNanos: 1.425515

Sleep Int: 1 difMillis: 0
difNanos: 1.829088

Sleep Int: 1 difMillis: 0
difNanos: 1.541853

Sleep Int: 1 difMillis: 0
difNanos: 1.705467

Sleep Int: 1 difMillis: 0
difNanos: 1.771106

Sleep Int: 1 difMillis: 0
difNanos: 1.776398

Sleep Int: 1 difMillis: 0
difNanos: 1.849793

Sleep Int: 1 difMillis: 0
difNanos: 2.009125

Sleep Int: 1 difMillis: 0
difNanos: 1.849230

Sleep Int: 1 difMillis: 0
difNanos: 1.868470

Sleep Int: 1 difMillis: 0
difNanos: 1.809419

Sleep Int: 1 difMillis: 0
difNanos: 9.286436

Sleep Int: 1 difMillis: 16
difNanos: 14.225241

Sleep Int: 1 difMillis: 16
difNanos: 1.525491

Sleep Int: 1 difMillis: 15
difNanos: 10.706745

Sleep Int: 1 difMillis: 0
difNanos: 1.304070

Sleep Int: 1 difMillis: 0
difNanos: 1.782193

Sleep Int: 1 difMillis: 0
difNanos: 1.783912

Sleep Int: 1 difMillis: 16
difNanos: 1.795144

Sleep Int: 1 difMillis: 0
difNanos: 1.735910

Sleep Int: 1 difMillis: 0
difNanos: 1.669325

Sleep Int: 1 difMillis: 0
difNanos: 1.725354

Sleep Int: 1 difMillis: 15
difNanos: 1.550583

Sleep Int: 1 difMillis: 16
difNanos: 1.750630

Sleep Int: 1 difMillis: 0
difNanos: 1.825378

Sleep Int: 1 difMillis: 0
difNanos: 1.864846

Sleep Int: 1 difMillis: 16
difNanos: 1.832447

Sleep Int: 1 difMillis: 0
difNanos: 1.814229

Sleep Int: 1 difMillis: 0
difNanos: 1.835635

Sleep Int: 1 difMillis: 0
difNanos: 1.820322

Sleep Int: 1 difMillis: 15
difNanos: 1.790005
[/quote]

Can you try putting


Thread timerAccuracyThread = new Thread(new Runnable() {
	public void run() {
		while (true) {
			try {
				Thread.sleep(Long.MAX_VALUE);
			} catch (Exception e) {
			}
		}
	}
});
timerAccuracyThread.setDaemon(true);
timerAccuracyThread.start();

in the beginning of your program? In my experience, that gets rid of the stutter and makes the sleep() calls accurate, for any sleep duration.

Windows XP has a high-accuracy timer, you just have to tell it to use it - by default, it will not. Due to whatever JVM vagaries, having a sleeping thread forces it to keep using the high-res system timer. What’s even more peculiar is the timer setting seems to be cross-application, so you may get a high-accuracy timer just because another app you’re also running at the time asked for it.

For example, keeping gmail open in Chrome will improve the accuracy of Thread.sleep() calls perceptibly :smiley:

@avm1979

Much better! So, I wonder what the significance is between the difference of what I first tried and this method. (EDIT: I kept adding zeros to my sleep interval, on the code example I had used. The increase in accuracy kicked in when the sleep increment passed out of the range of integers and became a long! Putting the sleep in a while loop made no difference though, even though technicaly the integer value repeated indefinitely could go longer. Also, designating an int-range number a Long does not work. The number has to be outside of the int range.) Also, a ScheduledThreadPoolExecutor was cited as a viable alternative to a Timer. More things to test!

Check out the highly improved data! (System.currentTimeMillis() remains inaccurate, but the nano measurements show the time spent sleeping is very accurate. So, avm1979, now you have a verification it works for Windows XP as well!

Also, while doing some research, I found references to sun.misc.Perf. I thought maybe it went obsolete, but there it is in rt.jar. I’m going to test that too. (http://www.java-gaming.org/index.php?topic=1071.0 is the “ancient” JGO thread on the subject).

[quote]Sleeper started
Sleep Int: 5 difMillis: 16
difNanos: 5.716885

Sleep Int: 10 difMillis: 0
difNanos: 10.581502

Sleep Int: 15 difMillis: 16
difNanos: 15.121859

Sleep Int: 20 difMillis: 16
difNanos: 19.955972

Sleep Int: 25 difMillis: 16
difNanos: 25.162573

Sleep Int: 30 difMillis: 31
difNanos: 29.875896

Sleep Int: 35 difMillis: 32
difNanos: 34.902788

Sleep Int: 40 difMillis: 47
difNanos: 39.573018

Sleep Int: 45 difMillis: 47
difNanos: 45.376527

Sleep Int: 50 difMillis: 47
difNanos: 50.621342

Sleep Int: 55 difMillis: 47
difNanos: 55.598972

Sleep Int: 60 difMillis: 62
difNanos: 60.391710

Sleep Int: 65 difMillis: 62
difNanos: 64.997980

Sleep Int: 70 difMillis: 62
difNanos: 70.128119

Sleep Int: 75 difMillis: 78
difNanos: 75.001686

Sleep Int: 80 difMillis: 79
difNanos: 79.620334

Sleep Int: 85 difMillis: 78
difNanos: 85.704742

Sleep Int: 90 difMillis: 93
difNanos: 90.674762

Sleep Int: 95 difMillis: 110
difNanos: 95.413701
[/quote]

On windows System.nanoTime() uses the same timer as sun.misc.Perf so you will get the same results.

I’ve had this problem, and apparently it’s a bug in Windows ever since 98.

This is how I fixed it:


new Thread() {
{ setDaemon(true); start(); }
public void run() { while(true) {try {Thread.sleep(Integer.MAX_VALUE);}catch(Throwable t){} } }
};

add this in main, before anything loads.