This is also posted here on the SDN, but seeing as I’m not sure how active those forums are, and that I can not do anything until this is fixed, I’m also posting here.
I am having some problems with the event handeling in the game engine I am writing:
I need to listen to key press events and release events and put them in to some Sets, which are read from the step()
method every frame and transalated into platform-independent GFKeyEvents for inturnal use.
The reason for doing so is to get rid of key repeating so the game objects just see “press, hold, hold, hold, release” no mater what the OS is doing.
As you will see the key event methods are synchronized as is step() yet some how I still get
Exception in thread "Event thread 1" java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextEntry(HashMap.java:810)
at java.util.HashMap$KeyIterator.next(HashMap.java:845)
at com.glassFlame.event.EventSystem.step(EventSystem.java:77)
at com.glassFlame.event.EventThread.run(EventThread.java:36)
I have also wrapped the Sets using
Collections.synchronizedSet()
here is the class with some of the irelivent methods removed:
package com.glassFlame.event;
import java.awt.Component;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.HashSet;
import java.util.Vector;
import java.util.Collections;
import java.util.Set;
import com.glassFlame.GFObject;
import com.glassFlame.renderer.RenderManager;
import com.glassFlame.renderer.Renderer;
import com.glassFlame.scene.Scene;
import com.glassFlame.scene.bounding.BoundingStructure;
public class EventSystem implements KeyListener, MouseListener, MouseMotionListener{
private BoundingStructure graph;
private EventThread pump;
private Component source;
private Scene scene;
private RenderManager ren;
private boolean clear;
private Vector<GFObject> objs;
private Vector<GFKeyListener> klobjs;
private Set<GFKeyEvent> keysHeld, keysPressedThisFrame, keysReleasedThisFrame, keysReleasedLastFrame, keysPressedLastFrame;
private Vector<GFKeyEvent> keyPresses, keyHolds, keyReleases;
private Object keyLock = new Object();
public EventSystem(BoundingStructure graph,Component source,RenderManager ren,int millsPerFrame)
{
this.clear = true;
this.graph = graph;
this.source = source;
this.ren = ren;
pump = new EventThread(this,millsPerFrame);
objs = new Vector<GFObject>(100);
klobjs = new Vector<GFKeyListener>(100);
keysHeld = Collections.synchronizedSet( new HashSet<GFKeyEvent>());
keysPressedThisFrame = Collections.synchronizedSet( new HashSet<GFKeyEvent>());
keysReleasedThisFrame = Collections.synchronizedSet( new HashSet<GFKeyEvent>());
keysReleasedLastFrame = Collections.synchronizedSet( new HashSet<GFKeyEvent>());
keysPressedLastFrame = Collections.synchronizedSet( new HashSet<GFKeyEvent>());
keyPresses = new Vector<GFKeyEvent>();
keyHolds = new Vector<GFKeyEvent>();
keyReleases = new Vector<GFKeyEvent>();
this.source.addKeyListener(this);
this.source.addMouseListener(this);
this.source.addMouseMotionListener(this);
}
public synchronized void addObject(GFObject obj)
{
graph.addObject(obj);
objs.add(obj);
if(obj instanceof GFKeyListener)
klobjs.add((GFKeyListener) obj);
}
public synchronized void step(long millsPassed)
{
//TODO:event handling goes here
//Process key events
//this is required because they behave diferant on diferent platforms
//remove keys that have been released for the held keys list
for(GFKeyEvent ke : keysHeld)
{
//if the key in question was not touched this frame
//and was released last frame
if( (!keysPressedThisFrame.contains(ke) && !keysReleasedThisFrame.contains(ke))
&&
keysReleasedLastFrame.contains(ke))
//the key has been released
{
System.out.println(ke.toString());
keysHeld.remove(ke);
keyReleases.add(ke);
}
else
keyHolds.add(ke);
}
//ditect key presses by checking if a key pressed this frame
//was not also presses the previous frame.
//This makes it work on linux and turns extreamly fast key taps into
//a key hold
for(GFKeyEvent ke : keysPressedThisFrame)
{
if(keysPressedLastFrame.contains(ke) == false)
keyPresses.add(ke);
}
//fire the key releases
for(GFKeyListener kl : klobjs)
{
for(GFKeyEvent ke : keyReleases)
{
kl.keyReleased(ke);
}
}
//fire the key holds
for(GFKeyListener kl : klobjs)
{
for(GFKeyEvent ke : keyHolds)
{
kl.keyHeld(ke);
}
}
//fire the key presses and transfer the events into the keysHeld array
for(GFKeyListener kl : klobjs)
{
for(GFKeyEvent ke : keyPresses)
{
if(!keysHeld.contains(ke))
{
kl.keyPressed(ke);
ke.setID(GFKeyEvent.KEY_TYPED);
keysHeld.add(ke);
}
}
}
//System.out.println("keys presses " + keyPresses.size());
//System.out.println("keys holds " + keyHolds.size());
//System.out.println("keys releases " + keyReleases.size());
//System.out.println(keysHeld.toString());
//swap the last-frame and this-frame sets;
Set<GFKeyEvent> tem;
tem = keysPressedLastFrame;
keysPressedLastFrame = keysPressedThisFrame;
keysPressedThisFrame = tem;
tem = keysReleasedLastFrame;
keysReleasedLastFrame = keysReleasedThisFrame;
keysReleasedThisFrame = tem;
//clear all the events from last time
keysPressedThisFrame.clear();
keysReleasedThisFrame.clear();
keyPresses.clear();
keyHolds.clear();
keyReleases.clear();
//clear the buffer before drawing
if(clear)
ren.setBuffer(Renderer.BUFFER_DEFAULT);
ren.clear();
//call the draw events of all the objects
ren.start();
for(int i = 0; i < objs.size();i++)
{
objs.get(i).draw(ren);
}
ren.stop();
ren.done();
}
public void start()
{
pump.start();
}
public void stop()
{
pump.pause();
}
public boolean running()
{
return pump.running();
}
@Override
public synchronized void keyPressed(KeyEvent e) {
keysPressedThisFrame.add(new GFKeyEvent(e,KeyEvent.KEY_PRESSED));
System.out.println("press " + KeyEvent.getKeyText(e.getKeyCode()) );
}
@Override
public synchronized void keyReleased(KeyEvent e) {
keysReleasedThisFrame.add(new GFKeyEvent(e,KeyEvent.KEY_RELEASED));
System.out.println("release " + KeyEvent.getKeyText(e.getKeyCode()) );
}
@Override
public synchronized void keyTyped(KeyEvent e) {
//not used
}
}
The class is construted and then the method start() is called which calls start() on the EventThread which in turn calls step about 30 times a second.