[java] Strictly typed Java Event Manager

Started by
6 comments, last by newera 13 years, 10 months ago
Hello! Since I've been programming a little bit at my first game I've found one major problem which confuses me. I have a general Event Manager class which broadcasts every event to all listeners. I implemented a Hashmap (String -> Object) into the Event class to broadcast additional data with the Events. But now everytime I want to access the Data I have to cast it first and it really annoys me and seems extremly bad to do. Imagine an ENTITY_SPAWNED Event. There is an int (the entity id), doubles (x, y, size) and maybe other data later on. Do any of you have a better idea for that than to cast the keys you know are ints into ints? I've read http://www.gamedev.net/reference/programming/features/effeventcpp/ for the eventmanager class, but a "onEvent(CLASS event)" Method for every event an object wants to listen to seems silly. Imagine a view listening to 50+ events? (On the other hand I now have a switch event type construct in one big "handleEvent" method). So my general Question would be: how would you implement an event system with data which can change in every event case? Greetings, rhnz P.S. Since my game is open source you can check it out at http://adlez.sourceforge.net/. But don't expect to much, I'm a beginner and my time is limited. (It's not even a game yet)
Advertisement
Quote:Original post by rhnz
Imagine an ENTITY_SPAWNED Event. There is an int (the entity id), doubles (x, y, size) and maybe other data later on. Do any of you have a better idea for that than to cast the keys you know are ints into ints?
Instead of a hashmap (slow and type-unsafe), send an event-specific structure, or customize the function call to the type parameters.

Quote:I've read http://www.gamedev.net/reference/programming/features/effeventcpp/ for the eventmanager class, but a "onEvent(CLASS event)" Method for every event an object wants to listen to seems silly. Imagine a view listening to 50+ events?
A view listening to 50 events sounds like a God object no matter how it gets its messages.
Quote:(On the other hand I now have a switch event type construct in one big "handleEvent" method).
Exactly. The code length is unavoidable; you either need a lot of functions or a lot of switch cases (or to stop listening to so many messages). Your IDE is set up to help you out with writing a bunch of functions, and the language is set up to help you keep them type-safe. Throw a big ugly switch statement with a bunch of typecasts and string literal keys in there, and your IDE and language will shrug and walk off to the pub without you.
Sounds reasonable to me. I think I always think to much ahead (or to less, however you want to put it). At the moment I only got 2 events (ENTITY_SPAWNED and ENTITY_MOVED) so 50 different events is maybe just me wanting to be on the save side.

I guess I just needed to get a push towards using the type safe functions, thanks!

Edit: Ok, one last problem: For my Eventmanager I use a Hashmap mapping a "Event.getType()" String to a List of listeners... When I stop using a String to represent the type... how can I save all listeners which are registered for an event type? Event.getClass().getCanonicalName() is quite expensive I assume?
Ok, I'm afraid I have to push this. I now use Event classes which extend the abstract Event class. My "listener" interface defines a method "handleEvent(Event e)".

Now I got the following problem: If I define this method in a class which implements that interface every event uses that method. Say I have an EntitySpawned event and the two methods "handleEvent(Event e)" and "handleEvent(EntitySpawned e)" everytime an EntitySpawned event is triggered it calls the "handleEvent(Event e)" method, not the right one.

If I do not define the handleEvent(Event e) method I can't implement the interface. But I don't want to create an interface which defines methods for every new eventclass because each implementing class whould have to implement useless methods... I'm a bit confused. My first idea would be to implement private methods "onEvent(EventClass e)" and use the "handleEvent(Event e)" method to call this private methods (which should take the "right" one for the actual event class)?
Does that make sense?


I updated my false code into the sourceforge svn rep if anyone wants to check.
This sounds like a place for cunning use of reflection, but i'm too lazy to dig through sourceforge to find your code. Could you post a some minimal code (of both the event system and some example usage) here?
Turns out I'm not too lazy after all. Here's a quick stab at a solution:

private class EntityMovedEvent	{		public float newX, newY;	}		private interface EventListener {} // Marker interface		private class EventManager	{		private Map<Class, LinkedList<EventListener>> listeners;				public EventManager()		{			listeners = new HashMap<Class, LinkedList<EventListener>>();		}				public void register(Class eventType, EventListener listener)		{			// validate			if (findMethod(listener, eventType) == null)			{				throw new RuntimeException("listener does not implement handle*() method for "+eventType);			}						// insert			if (!listeners.containsKey(eventType))				listeners.put(eventType, new LinkedList<EventListener>());						listeners.get(eventType).add(listener);		}				public void broadcast(Object event)		{			LinkedList<EventListener> currentListeners = listeners.get(event.getClass());			if (currentListeners != null)			{				for (EventListener l : currentListeners)				{					Method m = findMethod(l, event.getClass());					try					{						m.invoke(l, event);					}					catch (Exception e) { e.printStackTrace(); }				}			}		}				private Method findMethod(EventListener listener, Class<?> eventType)		{			Class<?> clazz = listener.getClass();						Method[] methods = clazz.getMethods();			for (Method m : methods)			{				if (m.getName().startsWith("handle"))				{					Class<?>[] params = m.getParameterTypes();					if (params.length == 1 && params[0] == eventType)					{						return m;					}				}			}			return null;		}	}		private class Test implements EventListener	{		public static void main(String[] args)		{			EventManager man = new EventManager();						Test listener = new Test();						man.register(EntityMovedEvent.class, listener);						man.broadcast( new EntityMovedEvent() );		}				public void handleMoveEvent(EntityMovedEvent event)		{			// ..		}	}


(Disclaimer: untested, and is sloppy with it's exception handling. But you should get the idea).

Basically you'd register for events based on an event's class, and you'd implement a method beginning with "handle" that takes a single parameter of the event type (eg. handleMove(EntityMovedEvent event)). The event manager looks up that method by reflection and calls it.

Since we've traded some compile-time checking for runtime checking, we check that the listener actually implements a handle* method when we add it, so we can catch misspelled methods earlier.

The most obvious place for improvement would be the findMethod() method - it should probably do more checking to make sure the method is suitable (like checking that it's public, and not static). It might also be good to store the result of the first findMethod() in the listener map to avoid having to call it when actually broadcasting an event.
Thank you very much for your fast and good reply, I'll see what I can do. I'm always amazed of how much there is to learn :D
I used annotations for this. You can see the implementation here: http://myriad.cvs.sourceforge.net/viewvc/myriad/Myriad/src/org/myriad/core/listener/

Essentially it is the equivalent to OrangyTang implementation. Rather than searching for a particular method name, it scans for an Annotation (EventMethod).

This topic is closed to new replies.

Advertisement