diff --git a/common/src/main/java/com/dfsek/terra/api/core/event/Event.java b/common/src/main/java/com/dfsek/terra/api/core/event/Event.java deleted file mode 100644 index abb98126f..000000000 --- a/common/src/main/java/com/dfsek/terra/api/core/event/Event.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.dfsek.terra.api.core.event; - -public interface Event { -} diff --git a/common/src/main/java/com/dfsek/terra/api/core/event/EventManager.java b/common/src/main/java/com/dfsek/terra/api/core/event/EventManager.java index abb2d7ba6..9f3e7e993 100644 --- a/common/src/main/java/com/dfsek/terra/api/core/event/EventManager.java +++ b/common/src/main/java/com/dfsek/terra/api/core/event/EventManager.java @@ -1,7 +1,14 @@ package com.dfsek.terra.api.core.event; +import com.dfsek.terra.api.core.event.events.Event; + public interface EventManager { - void callEvent(Event event); + /** + * Call an event, and return the execution status. + * @param event Event to pass to all registered EventListeners. + * @return False if the event is cancellable and has been cancelled, otherwise true. + */ + boolean callEvent(Event event); void registerListener(EventListener listener); } diff --git a/common/src/main/java/com/dfsek/terra/api/core/event/TerraEventManager.java b/common/src/main/java/com/dfsek/terra/api/core/event/TerraEventManager.java index 5030eb9f2..2bb14ebd6 100644 --- a/common/src/main/java/com/dfsek/terra/api/core/event/TerraEventManager.java +++ b/common/src/main/java/com/dfsek/terra/api/core/event/TerraEventManager.java @@ -1,6 +1,9 @@ package com.dfsek.terra.api.core.event; import com.dfsek.terra.api.core.TerraPlugin; +import com.dfsek.terra.api.core.event.annotations.Priority; +import com.dfsek.terra.api.core.event.events.Cancellable; +import com.dfsek.terra.api.core.event.events.Event; import com.dfsek.terra.api.util.ReflectionUtil; import java.io.PrintWriter; @@ -8,12 +11,14 @@ import java.io.StringWriter; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; public class TerraEventManager implements EventManager { - private final Map, Map>> listeners = new HashMap<>(); + private final Map, List> listeners = new HashMap<>(); private final TerraPlugin main; public TerraEventManager(TerraPlugin main) { @@ -21,26 +26,27 @@ public class TerraEventManager implements EventManager { } @Override - public void callEvent(Event event) { - if(!listeners.containsKey(event.getClass())) return; - listeners.get(event.getClass()).forEach((eventListener, methods) -> methods.forEach(method -> { + public boolean callEvent(Event event) { + listeners.getOrDefault(event.getClass(), Collections.emptyList()).forEach(listenerHolder -> { try { - method.invoke(eventListener, event); + listenerHolder.method.invoke(listenerHolder.listener, event); } catch(InvocationTargetException e) { StringWriter writer = new StringWriter(); e.getTargetException().printStackTrace(new PrintWriter(writer)); main.getLogger().warning("Exception occurred during event handling:"); main.getLogger().warning(writer.toString()); - main.getLogger().warning("Report this to the maintainers of " + eventListener.getClass().getCanonicalName()); + main.getLogger().warning("Report this to the maintainers of " + listenerHolder.method.getName()); } catch(Exception e) { StringWriter writer = new StringWriter(); e.printStackTrace(new PrintWriter(writer)); main.getLogger().warning("Exception occurred during event handling:"); main.getLogger().warning(writer.toString()); - main.getLogger().warning("Report this to the maintainers of " + eventListener.getClass().getCanonicalName()); + main.getLogger().warning("Report this to the maintainers of " + listenerHolder.method.getName()); } - }) + } ); + if(event instanceof Cancellable) return !((Cancellable) event).isCancelled(); + return true; } @SuppressWarnings("unchecked") @@ -53,8 +59,34 @@ public class TerraEventManager implements EventManager { if(method.getParameterCount() != 1) continue; // Check that parameter count is only 1. Class eventParam = method.getParameterTypes()[0]; if(!Event.class.isAssignableFrom(eventParam)) continue; // Check that parameter is an Event. + + Priority p = method.getAnnotation(Priority.class); + + int priority = p == null ? 0 : p.value(); + method.setAccessible(true); - listeners.computeIfAbsent((Class) eventParam, e -> new HashMap<>()).computeIfAbsent(listener, l -> new ArrayList<>()).add(method); + + List holders = listeners.computeIfAbsent((Class) eventParam, e -> new ArrayList<>()); + + holders.add(new ListenerHolder(method, listener, priority)); + + holders.sort(Comparator.comparingInt(ListenerHolder::getPriority)); // Sort priorities. + } + } + + private static final class ListenerHolder { + private final Method method; + private final EventListener listener; + private final int priority; + + private ListenerHolder(Method method, EventListener listener, int priority) { + this.method = method; + this.listener = listener; + this.priority = priority; + } + + public int getPriority() { + return priority; } } } diff --git a/common/src/main/java/com/dfsek/terra/api/core/event/annotations/Priority.java b/common/src/main/java/com/dfsek/terra/api/core/event/annotations/Priority.java new file mode 100644 index 000000000..7ee84aecd --- /dev/null +++ b/common/src/main/java/com/dfsek/terra/api/core/event/annotations/Priority.java @@ -0,0 +1,38 @@ +package com.dfsek.terra.api.core.event.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotated listener methods will have a specific priority set. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface Priority { + /** + * Highest possible priority. Listeners with this priority will always be invoked first. + */ + int HIGHEST = Integer.MAX_VALUE; + /** + * Lowest possible priority. Listeners with this priority will always be invoked last. + */ + int LOWEST = Integer.MIN_VALUE; + /** + * Default priority. + */ + int NORMAL = 0; + /** + * High priority. + */ + int HIGH = 1; + /** + * Low Priority. + */ + int LOW = -1; + /** + * @return Priority of this event. Events are executed from lowest to highest priorities. + */ + int value(); +} diff --git a/common/src/main/java/com/dfsek/terra/api/core/event/events/Cancellable.java b/common/src/main/java/com/dfsek/terra/api/core/event/events/Cancellable.java new file mode 100644 index 000000000..0d8a548c0 --- /dev/null +++ b/common/src/main/java/com/dfsek/terra/api/core/event/events/Cancellable.java @@ -0,0 +1,11 @@ +package com.dfsek.terra.api.core.event.events; + +/** + * Events that implement this interface may be cancelled. + * + * Cancelling an event is assumed to stop the execution of whatever action triggered the event. + */ +public interface Cancellable extends Event { + boolean isCancelled(); + void setCancelled(); +} diff --git a/common/src/main/java/com/dfsek/terra/api/core/event/events/Event.java b/common/src/main/java/com/dfsek/terra/api/core/event/events/Event.java new file mode 100644 index 000000000..62a6e07f2 --- /dev/null +++ b/common/src/main/java/com/dfsek/terra/api/core/event/events/Event.java @@ -0,0 +1,4 @@ +package com.dfsek.terra.api.core.event.events; + +public interface Event { +} diff --git a/common/src/main/java/com/dfsek/terra/api/core/event/events/config/ConfigPackLoadEvent.java b/common/src/main/java/com/dfsek/terra/api/core/event/events/config/ConfigPackLoadEvent.java index 9b0766e5a..a69eba172 100644 --- a/common/src/main/java/com/dfsek/terra/api/core/event/events/config/ConfigPackLoadEvent.java +++ b/common/src/main/java/com/dfsek/terra/api/core/event/events/config/ConfigPackLoadEvent.java @@ -1,6 +1,6 @@ package com.dfsek.terra.api.core.event.events.config; -import com.dfsek.terra.api.core.event.Event; +import com.dfsek.terra.api.core.event.events.Event; import com.dfsek.terra.config.pack.ConfigPack; public abstract class ConfigPackLoadEvent implements Event { diff --git a/common/src/main/java/com/dfsek/terra/api/core/event/events/world/TerraWorldLoadEvent.java b/common/src/main/java/com/dfsek/terra/api/core/event/events/world/TerraWorldLoadEvent.java index 51b01d290..e68d174c3 100644 --- a/common/src/main/java/com/dfsek/terra/api/core/event/events/world/TerraWorldLoadEvent.java +++ b/common/src/main/java/com/dfsek/terra/api/core/event/events/world/TerraWorldLoadEvent.java @@ -1,6 +1,6 @@ package com.dfsek.terra.api.core.event.events.world; -import com.dfsek.terra.api.core.event.Event; +import com.dfsek.terra.api.core.event.events.Event; import com.dfsek.terra.world.TerraWorld; /** diff --git a/common/src/test/java/event/EventTest.java b/common/src/test/java/event/EventTest.java index 1e9e8a0f7..00f16f1ad 100644 --- a/common/src/test/java/event/EventTest.java +++ b/common/src/test/java/event/EventTest.java @@ -2,7 +2,8 @@ package event; import com.dfsek.tectonic.loading.TypeRegistry; import com.dfsek.terra.api.core.TerraPlugin; -import com.dfsek.terra.api.core.event.Event; +import com.dfsek.terra.api.core.event.annotations.Priority; +import com.dfsek.terra.api.core.event.events.Event; import com.dfsek.terra.api.core.event.EventListener; import com.dfsek.terra.api.core.event.EventManager; import com.dfsek.terra.api.core.event.TerraEventManager; @@ -127,6 +128,7 @@ public class EventTest { } static class TestListener2 implements EventListener { + @Priority(Priority.LOWEST) public void doThing(TestEvent event) { System.out.println("Event value 2: " + event.value); }