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.