I am working on implementing event handling in a simplistic game engine that I am creating in Java. I am now struggling to figure out how I should register an object's event handlers. I am thinking that I might be able to make an interface that runs a line of code (public void registerListener(Object listener)
) when the object is created. I am aware that interfaces cannot define a constructor, so I am seeking guidance as to how I should approach this. Right now, I have to call the previously mentioned line of code in the constructor of all the objects that have event handlers in them, and I think that using an interface or something similar to reduce repetition would be an ideal solution.
Below is the rest of the event-system code in case you would like to take a look. Thanks!
EventManager class:
In charge of maintaining the list of methods that will respond to an event. When registering a EventHandler, you pass a class in as the parameter, and it will find all methods with an @EventHandler() annotation and use the annotation value as the event type.
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
public class EventManager
{
private HashMap<Class<? extends Event>, ArrayList<Method>> eventCallbackMap;
public EventManager()
{
this.eventCallbackMap = new HashMap<>();
}
public void dispatchEvent(Event event)
{
ArrayList<Method> callbacks = eventCallbackMap.get(event.getClass());
if(callbacks == null)
{
return;
}
for(Method callback : callbacks)
{
//TODO: Invoke Method
}
}
public void registerListener(Object listener)
{
for(Method method : listener.getClass().getMethods())
{
if(method.isAnnotationPresent(EventHandler.class))
{
Class<? extends Event> value = method.getAnnotation(EventHandler.class).value();
if(eventCallbackMap.containsKey(value))
{
eventCallbackMap.get(value).add(method);
}
else
{
ArrayList<Method> callbacks = new ArrayList<>();
callbacks.add(method);
eventCallbackMap.put(value, callbacks);
}
}
}
}
}
Event class:
Very barebones abstract class that all custom events will extend from. I would appreciate if you could let me know how I can improve this class, I would appreciate that.
package events;
public abstract class Event
{
public Event()
{
}
}
EventHandler class:
The annotation that defines a method as an event responding method.
package events;
import java.lang.annotation.*;
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EventHandler
{
Class<? extends Event> value();
}
Yes. You should use an interface, and not annotations. A very quick sample might look like this. It's just a code sketch, not sure whether it will compile. There's room for improvement but it's the basic idea:
interface EventHandler
{
Set<Event> eventTypes();
void onEvent(Event event);
}
interface Event
{
// used as a marker interface, but probably add some methods
}
enum MouseEvent implements Event
{
ON_CLICK
//... and whatever else
}
class OnClickEventHandler implements EventHandler
{
public Set<Event> eventTypes()
{
Set<Event> events = new HashSet<>();
events.add(MouseEvent.ON_CLICK);
return events;
}
public void onEvent(Event event)
{
if (event == MouseEvent.ON_CLICK)
{
System.out.println("Mouse clicked");
}
}
}
class EventManager
{
private final Map<Event, List<EventHandler>> handlers = new HashMap<>();
public void registerListener(EventHandler handler)
{
for (Event eventType : handler.eventTypes())
{
handlers.putIfAbsent(eventType, new ArrayList<>());
handlers.get(eventType).add(handler);
}
}
public void dispatchEvent(Event event)
{
handlers.getOrDefault(event, Collections.emptyList())
.forEach(handler -> handler.onEvent(event));
}
}