Problem with input events in Java6?

I’ve made a little demo that shows that the input handling in Java 6 seems to freeze at times. Although I’m using a rather perculiar input-handling schema, it is not the source of the problem (works fine in Java 5).

It seems that events get bottlenecked somewhere, and then they start to flow again. As you can see in my application, the ACTIONS text contains a integer defining what keys are currently pressed…they sometimes get “stuck” at some value.

The application ran fine in many cases, but as I kept running it, I kept noticing this “input lag”. It’s like the application stutters.

(If you don’t get it, try running it, close it, and run it again immediatly).

My test application (a basic template for a 4k game):


import java.awt.AWTEvent;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Transparency;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.util.Random;

import javax.swing.JFrame;

public class InputTest extends JFrame {
	static final int W = 1024;
	static final int H = 768;
	
	static int ACTIONS = 0;
	static final int A_LEFT = 1;
	static final int A_RIGHT = 2;
	static final int A_FORWARD = 4;
	static final int A_REVERSE = 8;
	static final int A_MOVERIGHT = 16;
	static final int A_MOVELEFT = 32;
	static final int A_WARP = 64;
	static final int[] GRAP_KEY_TYPES = {A_FORWARD,A_MOVELEFT,A_REVERSE,A_MOVERIGHT,A_WARP};
	static final int[] GRAP_KEY_EVENTS = { KeyEvent.VK_W, KeyEvent.VK_A, KeyEvent.VK_S, KeyEvent.VK_D, KeyEvent.VK_E} ;

	public InputTest() {
		
		setTitle("Input Test");
		setSize(W,H);
		setResizable(false);
		setDefaultCloseOperation(EXIT_ON_CLOSE);
		enableEvents(AWTEvent.KEY_EVENT_MASK|AWTEvent.MOUSE_EVENT_MASK);
		show();

		createBufferStrategy(2);
		BufferStrategy strategy = getBufferStrategy();
		Graphics2D g = (Graphics2D) strategy.getDrawGraphics();
		
		long lastLoopTime = System.nanoTime();
		int frames = 0;
		int acc = 0;
		int fps = 0;
		int MOUSE_X = 0, MOUSE_Y = 0;
		Random rand = new Random(System.nanoTime());
		
		BufferedImage background = new BufferedImage(W,H,Transparency.OPAQUE);
		((Graphics2D)background.getGraphics()).setBackground(Color.BLACK);
		
		while(true) {
			acc += (System.nanoTime() - lastLoopTime) / 1000000f;
			lastLoopTime = System.nanoTime();
			if(acc >= 1000) {
				acc -= 1000;
				fps = frames;
				frames = 0;
			}
			frames++;
			
			
			try {
				MOUSE_X = getMousePosition().x;
				MOUSE_Y = getMousePosition().y;
			} catch(Exception e) { 
			}
			
			g.drawImage(background,0,0,null);

			g.setColor(Color.darkGray);
			for(int i = 0; 100 > i; i++) {
				g.drawRect(rand.nextInt(W),rand.nextInt(H),10,10);
			}
			
			g.setColor(Color.WHITE);
			g.drawString("Actions: " + ACTIONS, 100, 100);
			g.drawString("FPS: " + fps, 250,100);
			g.drawString("Mouse: " + MOUSE_X+","+MOUSE_Y, 450,100);
			
			
			strategy.show();
			
			if (!isVisible()) {
				System.exit(0);
			}
			
			
		}
		
	}
	
	public static void main(String[] args) {
		new InputTest();
	}
	
	protected void processKeyEvent(KeyEvent e) {
		for(int i = 0; GRAP_KEY_EVENTS.length > i; i++) {
			if(e.getKeyCode() == GRAP_KEY_EVENTS[i]) {
				ACTIONS|=(e.getID()==KeyEvent.KEY_PRESSED)?GRAP_KEY_TYPES[i]:0;
				ACTIONS^=(e.getID()==KeyEvent.KEY_RELEASED)?GRAP_KEY_TYPES[i]:0;
				break;
			}
		}
	}
	
	protected void processMouseEvent(MouseEvent e) {
		if(e.getButton() == MouseEvent.BUTTON1) {
			ACTIONS|=(e.getID()==MouseEvent.MOUSE_PRESSED)?A_LEFT:0;
			ACTIONS^=(e.getID()==MouseEvent.MOUSE_RELEASED)?A_LEFT:0;
		}
		else if(e.getButton() == MouseEvent.BUTTON3) {
			ACTIONS|=(e.getID()==MouseEvent.MOUSE_PRESSED)?A_RIGHT:0;
			ACTIONS^=(e.getID()==MouseEvent.MOUSE_RELEASED)?A_RIGHT:0;
		}
	}

}

Here is the same application, using boolean flags for the input keys.


import java.awt.AWTEvent;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Transparency;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.util.Random;

import javax.swing.JFrame;

public class InputTest2 extends JFrame {
	static final int W = 1024;
	static final int H = 768;
	

	public boolean forward;
	public boolean reverse;
	public boolean right;
	public boolean left;
	

	public InputTest2() {
		
		setTitle("Input Test");
		setSize(W,H);
		setResizable(false);
		setDefaultCloseOperation(EXIT_ON_CLOSE);
		enableEvents(AWTEvent.KEY_EVENT_MASK|AWTEvent.MOUSE_EVENT_MASK);
		show();

		createBufferStrategy(2);
		BufferStrategy strategy = getBufferStrategy();
		Graphics2D g = (Graphics2D) strategy.getDrawGraphics();
		
		long lastLoopTime = System.nanoTime();
		int frames = 0;
		int acc = 0;
		int fps = 0;
		int MOUSE_X = 0, MOUSE_Y = 0;
		Random rand = new Random(System.nanoTime());
		
		BufferedImage background = new BufferedImage(W,H,Transparency.OPAQUE);
		((Graphics2D)background.getGraphics()).setBackground(Color.BLACK);
		
		while(true) {
			acc += (System.nanoTime() - lastLoopTime) / 1000000f;
			lastLoopTime = System.nanoTime();
			if(acc >= 1000) {
				acc -= 1000;
				fps = frames;
				frames = 0;
			}
			frames++;
			
			
			try {
				MOUSE_X = getMousePosition().x;
				MOUSE_Y = getMousePosition().y;
			} catch(Exception e) { 
			}
			
			g.drawImage(background,0,0,null);

			g.setColor(Color.darkGray);
			for(int i = 0; 100 > i; i++) {
				g.drawRect(rand.nextInt(W),rand.nextInt(H),10,10);
			}
			
			g.setColor(Color.WHITE);
			g.drawString("Forward: " + forward, 100, 100);
			g.drawString("Reverse: " + reverse, 100, 120);
			g.drawString("Right: " + right, 100, 140);
			g.drawString("Left: " + left, 100, 160);
			g.drawString("FPS: " + fps, 250,100);
			g.drawString("Mouse: " + MOUSE_X+","+MOUSE_Y, 450,100);
			
			
			strategy.show();
			
			if (!isVisible()) {
				System.exit(0);
			}
			
			
		}
		
	}
	
	public static void main(String[] args) {
		new InputTest2();
	}
	
	protected void processKeyEvent(KeyEvent e) {
		
		if(e.getKeyCode() == KeyEvent.VK_A) {
			if(e.getID() == KeyEvent.KEY_PRESSED) {
				left = true;
			} else if(e.getID() == KeyEvent.KEY_RELEASED) {
				left = false;
			}
		} else if(e.getKeyCode() == KeyEvent.VK_D) {
			if(e.getID() == KeyEvent.KEY_PRESSED) {
				right = true;
			} else if(e.getID() == KeyEvent.KEY_RELEASED) {
				right = false;
			}
		} else if(e.getKeyCode() == KeyEvent.VK_W) {
			if(e.getID() == KeyEvent.KEY_PRESSED) {
				forward = true;
			} else if(e.getID() == KeyEvent.KEY_RELEASED) {
				forward = false;
			}
		} else if(e.getKeyCode() == KeyEvent.VK_S) {
			if(e.getID() == KeyEvent.KEY_PRESSED) {
				reverse = true;
			} else if(e.getID() == KeyEvent.KEY_RELEASED) {
				reverse = false;
			}
		}
		

	}
	
	protected void processMouseEvent(MouseEvent e) {
	}

}


I’d say you just have a threading issue, but its odd that it doesn’t stuff up similarly on Java 5.

Your game loop thread never sleeps so its just hogging processor time so the Event Dispatch Thread (EDT) never has time to process any events. It will happen with many computers on the java 5 VM too. You need to call Thread.sleep(1) in every frame (some people get away with Thread.yield() but I’ve found that some computers don’t respond to it), then I guarantee that the events will be processed and won’t build up in the EDT :).

Keith

I’m experiencing the same problem in my game, but not nearly as bad as what’s described here, and it does occur in Java 5 and 6, but moreso in Java 6. Does this Thread wait stuff actually solve it? The problem is that it’s so slightly unresponsive that it’s hard for me to detect whether it’s really there or not.

Edit: Tried it, and I honestly don’t notice a difference one way or the other.

Now, the problem is even worse in the J2D version of my project where I see exactly what the original poster was complaining about, but when I use LWJGL, everything is perfect. The fix posted above doesn’t work at all. What’s going on?

I feel it’s kind of a hack to use Thread.sleep() … it just doesn’t feel “right”. And if the game is running at rather low FPS, then the fps is even going to be worse using Thread.sleep().

Well u definitely have to make the preemption of the rendering thread…u could possibly achieve that by lowering the rendering thread’s priority, tho i doubt that any1 has ever used this apporach…yield of sleep would do just fine…

[quote=“SluX,post:7,topic:28997”]
I’ve come across the same problem appel has, however, it has nothing to do with the key events. It’s the rendering Thread (as stated by CommanderKeith). And, SluX, yield() does not do a thing for me here. Thread.sleep(1); works here but causes a considerable large amount of speed drop. In Java 1.6, I can get 2,700+ frames per second, but whening using Thread.sleep() I get about 500 frames per second. However, after doing a couple of speed tests, I can render quite a lot of stuff and still maintain that same framerate. And if you figure it, what game have you ever played on JGF in which had an FPS counter that ran at the maximum, did it ever exceed about 500 FPS? For me, I almost always get between 400-600 frames per second running other people’s games (not just on JGF either), with even just a basic window structure with double buffering. The reason I always got that framerate a lot was probally because they had to use Thread.sleep() to prevent problems. It also prevents the CPU from running at 100%, which frees system memory for other applications. One thing that would increase the speed more when using Thread.sleep() is to set your Thread’s priority to maximum. On my system, when using max. priority, I can get about 30-40 more frames per second than when using normal Thread priority.

Hope my information helped. (Oh, by the way, sorry for such a late reply…)