From 1456a8c1ee5d29d54ec769f4807edf50a559259f Mon Sep 17 00:00:00 2001 From: Daniil Date: Wed, 25 Jul 2018 20:30:49 +0700 Subject: [PATCH] Simple bukkit-like event loop implementation --- build.gradle | 2 + .../java/mc/core/events/EventHandler.java | 13 ++++ .../main/java/mc/core/events/EventLoop.java | 7 ++ .../java/mc/core/events/EventPriority.java | 19 +++++ .../java/mc/core/events/SimpleEventLoop.java | 74 +++++++++++++++++++ .../ru/core/events/SampleEventHandler.java | 20 +++++ .../ru/core/events/SimpleEventLoopTest.java | 22 ++++++ settings.gradle | 2 + 8 files changed, 159 insertions(+) create mode 100644 event-loop/src/main/java/mc/core/events/EventHandler.java create mode 100644 event-loop/src/main/java/mc/core/events/EventLoop.java create mode 100644 event-loop/src/main/java/mc/core/events/EventPriority.java create mode 100644 event-loop/src/main/java/mc/core/events/SimpleEventLoop.java create mode 100644 event-loop/src/test/java/ru/core/events/SampleEventHandler.java create mode 100644 event-loop/src/test/java/ru/core/events/SimpleEventLoopTest.java diff --git a/build.gradle b/build.gradle index 74a7f0e..529d214 100644 --- a/build.gradle +++ b/build.gradle @@ -33,6 +33,8 @@ subprojects { /* Components */ compile (group: 'org.projectlombok', name: 'lombok', version: '1.16.16') + + testCompile 'junit:junit:4.12' } task copyDep(type: Copy) { diff --git a/event-loop/src/main/java/mc/core/events/EventHandler.java b/event-loop/src/main/java/mc/core/events/EventHandler.java new file mode 100644 index 0000000..096b52e --- /dev/null +++ b/event-loop/src/main/java/mc/core/events/EventHandler.java @@ -0,0 +1,13 @@ +package mc.core.events; + +import java.lang.annotation.*; + +@Documented +@Target(ElementType.METHOD) +@Inherited +@Retention(RetentionPolicy.RUNTIME) +public @interface EventHandler { + EventPriority priority() default EventPriority.NORMAL; + + boolean ignoreCancelled() default false; +} diff --git a/event-loop/src/main/java/mc/core/events/EventLoop.java b/event-loop/src/main/java/mc/core/events/EventLoop.java new file mode 100644 index 0000000..63fa506 --- /dev/null +++ b/event-loop/src/main/java/mc/core/events/EventLoop.java @@ -0,0 +1,7 @@ +package mc.core.events; + +public interface EventLoop { + void callEvent(Event event); + + void addEventHandler(Object object); +} diff --git a/event-loop/src/main/java/mc/core/events/EventPriority.java b/event-loop/src/main/java/mc/core/events/EventPriority.java new file mode 100644 index 0000000..5a889eb --- /dev/null +++ b/event-loop/src/main/java/mc/core/events/EventPriority.java @@ -0,0 +1,19 @@ +package mc.core.events; + +import lombok.Getter; + +public enum EventPriority { + LOWEST(0), + LOW(1), + NORMAL(2), + HIGH(3), + HIGHEST(4), + MONITOR(5); + + @Getter + private int value; + + EventPriority(int value) { + this.value = value; + } +} diff --git a/event-loop/src/main/java/mc/core/events/SimpleEventLoop.java b/event-loop/src/main/java/mc/core/events/SimpleEventLoop.java new file mode 100644 index 0000000..47e2ff3 --- /dev/null +++ b/event-loop/src/main/java/mc/core/events/SimpleEventLoop.java @@ -0,0 +1,74 @@ +package mc.core.events; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.*; + +@Slf4j +public class SimpleEventLoop implements EventLoop { + private Map, List> handlers = new HashMap<>(); + + @Override + public void callEvent(Event event) { + Class eventType = event.getClass(); + if (handlers.containsKey(eventType)) { + for (ExecutorLink link : handlers.get(eventType)) { + try { + link.getMethod().invoke(link.object, event); + } catch (IllegalAccessException | InvocationTargetException e) { + log.error("Exception caught while attempting to dispatch {}.", eventType.getSimpleName(), e); + } + } + } + } + + @Override + public void addEventHandler(Object object) { + for (Method method : object.getClass().getDeclaredMethods()) { + EventHandler annotation = method.getAnnotation(EventHandler.class); + if (annotation == null) + continue; // We are not interested in methods without @EventHandler annotation + + if (!Modifier.isPublic(method.getModifiers())) { + log.error("Unable to register {} as an EventHandler. Method must have a 'private' access modifier.", method.toString()); + continue; + + } + + if (method.getParameterCount() != 1) { + log.error("Unable to register {} as an EventHandler. Method must have exactly one argument.", method.toString()); + continue; + } + + Class firstParamType = method.getParameterTypes()[0]; + if (!Event.class.isAssignableFrom(firstParamType)) { + log.error("Unable to register {} as an EventHandler. First parameter type must implement 'Event' interface.", method.toString()); + continue; + } + + @SuppressWarnings("unchecked") Class eventType = (Class) firstParamType; + + List eventHandlers = handlers.computeIfAbsent(eventType, s -> new ArrayList<>()); + eventHandlers.add(new ExecutorLink(annotation.priority().getValue(), annotation.ignoreCancelled(), method, object)); + eventHandlers.sort(Comparator.comparingInt(o -> o.priority)); + } + } + + + /** + * This class describes + */ + @RequiredArgsConstructor + @Getter + private static class ExecutorLink { + private final int priority; + private final boolean ignoreCancelled; + private final Method method; + private final Object object; + } +} diff --git a/event-loop/src/test/java/ru/core/events/SampleEventHandler.java b/event-loop/src/test/java/ru/core/events/SampleEventHandler.java new file mode 100644 index 0000000..d0d8411 --- /dev/null +++ b/event-loop/src/test/java/ru/core/events/SampleEventHandler.java @@ -0,0 +1,20 @@ +package ru.core.events; + +import mc.core.events.EventHandler; +import mc.core.events.LoginEvent; + +public class SampleEventHandler { + @EventHandler + public void onPlayerLogin(LoginEvent event) { + event.setDenyReason("Hello from SampleEventHandler!"); + } + + public void notHandler(LoginEvent event){ + + } + + @EventHandler + public void invalidParam(Object object){ + + } +} diff --git a/event-loop/src/test/java/ru/core/events/SimpleEventLoopTest.java b/event-loop/src/test/java/ru/core/events/SimpleEventLoopTest.java new file mode 100644 index 0000000..38ae6ba --- /dev/null +++ b/event-loop/src/test/java/ru/core/events/SimpleEventLoopTest.java @@ -0,0 +1,22 @@ +package ru.core.events; + +import mc.core.events.LoginEvent; +import mc.core.events.SimpleEventLoop; +import org.junit.Assert; +import org.junit.Test; + +public class SimpleEventLoopTest { + + @Test + public void loopWorks() { + SimpleEventLoop simpleEventLoop = new SimpleEventLoop(); + simpleEventLoop.addEventHandler(new SampleEventHandler()); + LoginEvent testEvent = new LoginEvent(null); + testEvent.setDenyReason("none"); + + simpleEventLoop.callEvent(testEvent); + + Assert.assertEquals("Event handler was not called", "Hello from SampleEventHandler!", testEvent.getDenyReason()); + + } +} diff --git a/settings.gradle b/settings.gradle index 52ad5e4..5f84ba5 100644 --- a/settings.gradle +++ b/settings.gradle @@ -5,3 +5,5 @@ include('flat_world') include('vanilla_commands') include('proto_1.12.2') // Protocol 1.12.2 include('proto_1.12.2_netty') // Protocol 1.12.2 (Netty impl.) +include('event-loop') +