Key handling

I’m more-or-less done with my first game and I’m thinking about a second. This would use keys, and I want to ensure that the key handling code is thread-safe. The topic of handling keys is discussed a bit in the tips thread, but I don’t want to pollute that one, hence this thread. It might be appropriate to link to this thread, or a post in it, from that one once the dust settles.

The issue I have is this: the standard key handling discussed there is basically to detect key pressed and released events and update a boolean. That’s fine if my game logic only cares whether keys are up or down, but not so fine if I care about detecting key presses: e.g. for jumping. If the pressed and released events both occur before the logic call then the character won’t jump and may meet a frustrating doom.

My idea for approaching this is to borrow a trick from lock-free synchronisation (e.g. lock-free single-producer single-consumer, which is essentially what we have here: an event thread which produces events and another thread which does updates and probably renders). I would be grateful for critiques and improvements on this implementation, and suggestions of cheaper ways to obtain the same result.

public class Game4k extends Applet
{
	// Constants. You can make this slightly larger if it will compress better, but making it
	// smaller risks ArrayIndexOutOfBoundsExceptions in the event thread unless you add
	// bounds testing to processKeyEvent(...).
	private static final int NUM_KEYS = 0x10000;

	// Shared state. In the spirit of lock-free synchronisation, this is written
	// only by the event thread, and it advances values rather than setting /
	// resetting.
	private int[] keyEventCount = new int[NUM_KEYS];

	// This can be adapted if you do stuff properly rather than hackily, or if you use a JFrame
	@Override
	public void start()
	{
		// Set up event handling. Note: keyDown[key] and keyPressed[key] are 0
		// for false; any other value should be interpreted as true.
		enableEvents(KeyEvent.KEY_EVENT_MASK);
		int[] prevKeyEventCounts = new int[NUM_KEYS];
		int[] keyDown = new int[NUM_KEYS];
		int[] keyPressed = new int[NUM_KEYS];

		// Other setup...

		// Main game loop. Against, adapt as necessary.
		while (isActive())
		{
			// Handle events.
			for (int i = 0; i < NUM_KEYS; i++)
			{
				int eventCount = keyEventCount[i];
				// An odd number of events means that it's down. Simple!
				keyDown[i] = eventCount & 1;
				// The key was pressed since the last loop if the number of events
				// since then is at least two, by the pigeonhole principle, or if
				// the number of events since then is positive and the key is down.
				// Since we only need to catch the second case when delta == 1,
				// we can simplify the key-is-down test a bit.
				int delta = eventCount - prevKeyEventCounts[i];
				keyPressed[i] = (delta / 2) + (delta & eventCount);
				prevKeyEventCounts[i] = eventCount;
			}

			// Do game logic...
			// Render...
			// Sleep...
		}
	}

	@Override
	public void processKeyEvent(KeyEvent evt)
	{
		// The valid key event ids are 400, 401, 402.
		// Of those we care about 401 (KEY_PRESSED) and 402 (KEY_RELEASED).
		int id = evt.getID() & 3;
		if (id != 0)
		{
			keyEventCount[evt.getKeyCode()]++;
		}
	}
}

Ideally I’d like to debounce both keyDown and keyPressed as well, to work around the issue with some window managers (e.g. KDE) doing auto-repeat. However, there’s no sure-fire way of telling whether multiple key presses come from the user or the WM, so I think I’ll have to live with that issue.

I prefer keeping things simple. I use a key state array, but it is int, not boolean, and pressed keys set the int value of 1. This way, I can immediately use pressed key values in computations, e.g. (in pseudocode)


rotation+=keystate[KEY_LEFT]*somevalue

To handle typed keys, I store the keycode of the most recently typed key in array entry 0 and I process this entry in the game loop, and reset it’s value to 0, if its non-0.

hth Wolfgang

“I’m a ****ing starship, I’m allowed to cheat!”
GCU Arbitrary, Culture Craft