Why is this stuttering?

Here’s a demo of the applet

http://www.joeysturgis.com/roguelike

Here’s the game loop:


	private void gameLoop3(long ufreq) {
		long nanosInSecond = 1000L * 1000L * 1000L;
		long updateFrequency = ufreq;
		long updateInterval = nanosInSecond / updateFrequency;
		long nextUpdateTick = System.nanoTime();
		while (true) {
			long start = System.nanoTime();
			for (long now = System.nanoTime(); now > nextUpdateTick; nextUpdateTick += updateInterval) {
				this.update();
			}
			process_time += System.nanoTime() - start;
			render();
			Sync.sync(60);
		}
	}

Here’s sync:


/*
 * Copyright (c) 2002-2012 LWJGL Project
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'LWJGL' nor the names of
 *   its contributors may be used to endorse or promote products derived
 *   from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.ingamedev.util;

/**
* A highly accurate sync method that continually adapts to the system 
* it runs on to provide reliable results.
*
* @author Riven
* @author kappaOne
*/
public class Sync {

	/** number of nano seconds in a second */
	private static final long NANOS_IN_SECOND = 1000L * 1000L * 1000L;

	/** The time to sleep/yield until the next frame */
	private static long nextFrame = 0;
	
	/** whether the initialisation code has run */
	private static boolean initialised = false;
	
	/** for calculating the averages the previous sleep/yield times are stored */
	private static RunningAvg sleepDurations = new RunningAvg(10);
	private static RunningAvg yieldDurations = new RunningAvg(10);
	
	
	/**
	 * An accurate sync method that will attempt to run at a constant frame rate.
	 * It should be called once every frame.
	 * 
	 * @param fps - the desired frame rate, in frames per second
	 */
	public static void sync(int fps) {
		if (fps <= 0) return;
		if (!initialised) initialise();
		
		try {
			// sleep until the average sleep time is greater than the time remaining till nextFrame
			for (long t0 = getTime(), t1; (nextFrame - t0) > sleepDurations.avg(); t0 = t1) {
				Thread.sleep(1);
				sleepDurations.add((t1 = getTime()) - t0); // update average sleep time
			}
	
			// slowly dampen sleep average if too high to avoid yielding too much
			sleepDurations.dampenForLowResTicker();
	
			// yield until the average yield time is greater than the time remaining till nextFrame
			for (long t0 = getTime(), t1; (nextFrame - t0) > yieldDurations.avg(); t0 = t1) {
				Thread.yield();
				yieldDurations.add((t1 = getTime()) - t0); // update average yield time
			}
		} catch (InterruptedException e) {
			
		}
		
		// schedule next frame, drop frame(s) if already too late for next frame
		nextFrame = Math.max(nextFrame + NANOS_IN_SECOND / fps, getTime());
	}
	
	/**
	 * This method will initialise the sync method by setting initial
	 * values for sleepDurations/yieldDurations and nextFrame.
	 * 
	 * If running on windows it will start the sleep timer fix.
	 */
	private static void initialise() {
		initialised = true;
		
		sleepDurations.init(1000 * 1000);
		yieldDurations.init((int) (-(getTime() - getTime()) * 1.333));
		
		nextFrame = getTime();
		
		String osName = System.getProperty("os.name");
		
		if (osName.startsWith("Win")) {
			// On windows the sleep functions can be highly inaccurate by 
			// over 10ms making in unusable. However it can be forced to 
			// be a bit more accurate by running a separate sleeping daemon
			// thread.
			Thread timerAccuracyThread = new Thread(new Runnable() {
				public void run() {
					try {
						Thread.sleep(Long.MAX_VALUE);
					} catch (Exception e) {}
				}
			});
			
			timerAccuracyThread.setDaemon(true);
			timerAccuracyThread.start();
		}
	}

	/**
	 * Get the system time in nano seconds
	 * 
	 * @return will return the current time in nano's
	 */
	private static long getTime() {
		//return (Sys.getTime() * NANOS_IN_SECOND) / Sys.getTimerResolution();
		return System.nanoTime();
	}

	private static class RunningAvg {
		private final long[] slots;
		private int offset;
		
		private static final long DAMPEN_THRESHOLD = 10 * 1000L * 1000L; // 10ms
		private static final float DAMPEN_FACTOR = 0.9f; // don't change: 0.9f is exactly right!

		public RunningAvg(int slotCount) {
			this.slots = new long[slotCount];
			this.offset = 0;
		}

		public void init(long value) {
			while (this.offset < this.slots.length) {
				this.slots[this.offset++] = value;
			}
		}

		public void add(long value) {
			this.slots[this.offset++ % this.slots.length] = value;
			this.offset %= this.slots.length;
		}

		public long avg() {
			long sum = 0;
			for (int i = 0; i < this.slots.length; i++) {
				sum += this.slots[i];
			}
			return sum / this.slots.length;
		}
		
		public void dampenForLowResTicker() {
			if (this.avg() > DAMPEN_THRESHOLD) {
				for (int i = 0; i < this.slots.length; i++) {
					this.slots[i] *= DAMPEN_FACTOR;
				}
			}
		}
	}
}

Here’s render:


	public void render() {
		frames++;
		frame_count++;
		
		long start = System.nanoTime();

		Graphics2D g = tmp.createGraphics();
		g.setColor(Color.black);
		g.fillRect(0, 0, WIDTH, HEIGHT);
		
		level.draw_edge_persepctive(g, SpriteType.ranger, x, y, pixel_x, pixel_y, 13, 9);
		
		for(Entity e : entities) {
			e.render(g, level.p_screen_x, level.p_screen_y);
		}
		
		Font.printString(tmp, "Rogu3", 4, 4, 5, 0x65DE31, 0x454545);
		Font.printString(tmp, "Power: " + score, 4, Font.h * 2 + 4, 24, 0xFA1122, 0x911122);
		Font.printString(tmp, "Entities: " + Level.ent_count, 4, Game.HEIGHT - (Font.h * 2) - 4, 24, 0xFFFFFF, 0x000000);
		Font.printString(tmp, "FPS: " + fps, 800 - 80 - 16, 4, 50, 0xABABAB, 0x232323);
		
		announce(g);
		
		g.drawImage(Art.border, 0, 0, 8, 8, 0, 0, 8, 8, null);
		g.drawImage(Art.border, Game.WIDTH - 8, 0, Game.WIDTH, 8, 24, 0, 32, 8, null);
		g.drawImage(Art.border, 0, Game.HEIGHT - 8, 8, Game.HEIGHT, 0, 24, 8, 32, null);
		g.drawImage(Art.border, Game.WIDTH - 8, Game.HEIGHT - 8, Game.WIDTH, Game.HEIGHT, 24, 24, 32, 32, null);
		
		for(int ix = 0; ix <= 50; ix ++) {
			g.drawImage(Art.border, 8 + (ix * 16), 0, 8 + (ix * 16) + 16, 8, 8, 0, 24, 8, null);
		}
		for(int ix = 0; ix <= 50; ix ++) {
			g.drawImage(Art.border, 8 + (ix * 16), Game.HEIGHT - 8, 8 + (ix * 16) + 16, Game.HEIGHT, 8, 24, 24, 32, null);
		}
		for(int iy = 0; iy <= 34; iy ++) {
			g.drawImage(Art.border, 0, 8 + (iy * 16), 8, 8 + (iy * 16) + 16, 0, 8, 8, 24, null);
		}
		for(int iy = 0; iy <= 34; iy ++) {
			g.drawImage(Art.border, Game.WIDTH - 8, 8 + (iy * 16), Game.WIDTH, 8 + (iy * 16) + 16, 24, 8, 32, 24, null);
		}

		g.dispose();
		g = getGraphics();
		if(g != null) {
			g.drawImage(tmp, 0, 0, null);
			g.dispose();
			flip();//show's the bufferstrategy
		}
		
		render_time = System.nanoTime() - start;
		
		if (System.nanoTime() > next_frame_count) {
			next_frame_count = System.nanoTime() + 1000000000;
			fps = frame_count;
			frame_count = 0;
			System.out.println("FPS: " + fps + ", Process Time: " + process_time / 60 + " Render Time: " + render_time);
			process_time = 0;
		}
	}

A few seconds of running…
FPS: 1, Process Time: 7992 Render Time: 138639760 FPS: 57, Process Time: 161622 Render Time: 9624389 FPS: 60, Process Time: 104913 Render Time: 11918064 FPS: 61, Process Time: 103605 Render Time: 8334624 FPS: 60, Process Time: 101535 Render Time: 12660010 FPS: 61, Process Time: 107089 Render Time: 8015489 FPS: 42, Process Time: 142956 Render Time: 18470568 FPS: 62, Process Time: 129872 Render Time: 8149031 FPS: 60, Process Time: 93706 Render Time: 10992659 FPS: 60, Process Time: 93977 Render Time: 11397550 FPS: 60, Process Time: 89091 Render Time: 12375007 FPS: 60, Process Time: 88103 Render Time: 12378847 FPS: 61, Process Time: 90599 Render Time: 10045067 FPS: 60, Process Time: 105866 Render Time: 11264862 FPS: 61, Process Time: 79250 Render Time: 11252489 FPS: 59, Process Time: 96743 Render Time: 16203345 FPS: 61, Process Time: 104095 Render Time: 13690799 FPS: 61, Process Time: 107302 Render Time: 10396627 FPS: 60, Process Time: 95676 Render Time: 12034540 FPS: 61, Process Time: 95185 Render Time: 12393353 FPS: 61, Process Time: 88039 Render Time: 12382261 FPS: 61, Process Time: 86411 Render Time: 12071659 FPS: 60, Process Time: 91651 Render Time: 13107567 FPS: 61, Process Time: 96309 Render Time: 10673097

Are you using a canvas or JPanel?

I see very little stuttering and what little I do see is a slight slowness on refreshing when moving vertically.

I have the same issue when I use a JPanel for rendering instead of a canvas but it does not always happen. I think I might have something to do with monitor refresh rate.

That is just a guess though.

Using canvas. Its really driving me crazy, considering both the update rate and render rate are well within the target rates.

Smooth as butter for me.

Why don’t you tell us about your OS, JRE, browser, etc? :slight_smile:

Windows 7 Pro,

, Google Chrome

I just tried adding this

Toolkit.getDefaultToolkit().sync();

and it made it much better O_o

EDIT: spoke too soon! so scratch that, still the same

I looked at the stuttering I get which looks identical to yours and tried that line and it still stutters slightly.

I think this has to do with the display because my apps do not stutter on my laptop.

Laptop is win 7 with java6

Desktop is XP java6

yeah it stutters sometimes

but honestly, I have tried every single gameloop in our big gameloop thread, and none is perfect

Try out this game loop! (And don’t forget the sleep trick)

Love your smoke effect. Run smoothly 33fps here, 7 java 7u1 and java 6u24 <-- lazy to update.

reserved. (how do delete posts)

Thanks! I really owe it to my artist. :'D

@ReBirth, you should update, from 24-31 there were significant security holes found with fixes applied. Your system is very vulnerable, no matter if you use Java or not.

VSync.

For anyone curious, this is what ended up working O_o


	private void gameLoop5() {

		if (System.getProperty("os.name").startsWith("Win")) {
			new Thread()
			{

				{
					setDaemon(true);
					start();
				}

				public void run() {
					while (true) {
						try {
							Thread.sleep(Long.MAX_VALUE);
						}
						catch (Exception exc) {
						}
					}
				}
			};
		}

		long lastTime = 0;
		long nanosInSecond = 1000L * 1000L * 1000L;
		long updateFrequency = 60;
		long updateInterval = nanosInSecond / updateFrequency;
		long nextUpdateTick = System.nanoTime();
		long now = 0;
		long start = 0;
		
		long FPS = 60;

		while (running) {
			long deltaTime = System.nanoTime() - lastTime;
			lastTime += deltaTime;
			start = System.nanoTime();
			for (now = System.nanoTime(); now > nextUpdateTick; nextUpdateTick += updateInterval) {
				this.update();
			}
			process_time += System.nanoTime() - start;
			
			render();
			Toolkit.getDefaultToolkit().sync();
			
			long sleepTime = Math.round((1000000000 / FPS - (System.nanoTime() - lastTime)) / 1000000);
			if (sleepTime < 0) continue;
			
			// this sleeping method uses Thread.sleep(1) for the first 4/5 of
			// the sleep loop, and Thread.yield() for the rest. This gives me an
			// accuracy of about 3-4 microseconds
			long prev = System.nanoTime(), diff;
			while ((diff = System.nanoTime() - prev) < sleepTime) {
				if (diff < sleepTime * 0.8)
					try {
						Thread.sleep(1);
					}
					catch (Exception exc) {
					}
				else
					Thread.yield();
			}
		}
	}

At line 46, use “Math.round((1e9/FPS - (System.nanoTime() - lastTime)) / 1e6)”, 1e9 and 1e6 are doubles, making the calculation more accurate.

thanks. btw why does it render over 60 fps tho???

The good ol’ Matt Daemon;

Should be added to Spronglies FAQ imo.