Search code examples
javaeventsevent-handlinggame-engine

Is there a way to make interface code run at runtime?


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();
}

Solution

  • 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));
        }
    }