From 1456a8c1ee5d29d54ec769f4807edf50a559259f Mon Sep 17 00:00:00 2001 From: Daniil Date: Wed, 25 Jul 2018 20:30:49 +0700 Subject: [PATCH 01/62] 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') + From 944cd61c209d07a3bf189a9840b628e29d7e28bd Mon Sep 17 00:00:00 2001 From: Daniil Date: Wed, 25 Jul 2018 20:56:43 +0700 Subject: [PATCH 02/62] Event loop benchmark --- .../core/events/SimpleEventLoopBenchmark.java | 30 +++++++++++++++++++ .../ru/core/events/SimpleEventLoopTest.java | 5 ++-- .../events/handlers/FastEventHandler.java | 11 +++++++ .../{ => handlers}/SampleEventHandler.java | 2 +- 4 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 event-loop/src/test/java/ru/core/events/SimpleEventLoopBenchmark.java create mode 100644 event-loop/src/test/java/ru/core/events/handlers/FastEventHandler.java rename event-loop/src/test/java/ru/core/events/{ => handlers}/SampleEventHandler.java (91%) diff --git a/event-loop/src/test/java/ru/core/events/SimpleEventLoopBenchmark.java b/event-loop/src/test/java/ru/core/events/SimpleEventLoopBenchmark.java new file mode 100644 index 0000000..7fc0a9e --- /dev/null +++ b/event-loop/src/test/java/ru/core/events/SimpleEventLoopBenchmark.java @@ -0,0 +1,30 @@ +package ru.core.events; + +import com.carrotsearch.junitbenchmarks.AbstractBenchmark; +import com.carrotsearch.junitbenchmarks.BenchmarkOptions; +import mc.core.events.LoginEvent; +import mc.core.events.SimpleEventLoop; +import org.junit.BeforeClass; +import org.junit.Test; +import ru.core.events.handlers.SampleEventHandler; + +public class SimpleEventLoopBenchmark extends AbstractBenchmark { + private static SimpleEventLoop simpleEventLoop; + private static LoginEvent testEvent; + + @BeforeClass + public static void setup(){ + simpleEventLoop = new SimpleEventLoop(); + simpleEventLoop.addEventHandler(new SampleEventHandler()); + testEvent = new LoginEvent(null); + testEvent.setDenyReason("none"); + } + + @Test + @BenchmarkOptions(warmupRounds = 5, benchmarkRounds = 15) + public void benchmark() { + for (int i = 0; i < 50_000_000; i++) { + simpleEventLoop.callEvent(testEvent); + } + } +} diff --git a/event-loop/src/test/java/ru/core/events/SimpleEventLoopTest.java b/event-loop/src/test/java/ru/core/events/SimpleEventLoopTest.java index 38ae6ba..768c702 100644 --- a/event-loop/src/test/java/ru/core/events/SimpleEventLoopTest.java +++ b/event-loop/src/test/java/ru/core/events/SimpleEventLoopTest.java @@ -4,19 +4,20 @@ import mc.core.events.LoginEvent; import mc.core.events.SimpleEventLoop; import org.junit.Assert; import org.junit.Test; +import ru.core.events.handlers.FastEventHandler; +import ru.core.events.handlers.SampleEventHandler; public class SimpleEventLoopTest { @Test public void loopWorks() { SimpleEventLoop simpleEventLoop = new SimpleEventLoop(); - simpleEventLoop.addEventHandler(new SampleEventHandler()); + simpleEventLoop.addEventHandler(new FastEventHandler()); 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/event-loop/src/test/java/ru/core/events/handlers/FastEventHandler.java b/event-loop/src/test/java/ru/core/events/handlers/FastEventHandler.java new file mode 100644 index 0000000..733180d --- /dev/null +++ b/event-loop/src/test/java/ru/core/events/handlers/FastEventHandler.java @@ -0,0 +1,11 @@ +package ru.core.events.handlers; + +import mc.core.events.EventHandler; +import mc.core.events.LoginEvent; + +public class FastEventHandler { + @EventHandler + public void onPlayerLogin(LoginEvent event) { + + } +} diff --git a/event-loop/src/test/java/ru/core/events/SampleEventHandler.java b/event-loop/src/test/java/ru/core/events/handlers/SampleEventHandler.java similarity index 91% rename from event-loop/src/test/java/ru/core/events/SampleEventHandler.java rename to event-loop/src/test/java/ru/core/events/handlers/SampleEventHandler.java index d0d8411..36a7ec4 100644 --- a/event-loop/src/test/java/ru/core/events/SampleEventHandler.java +++ b/event-loop/src/test/java/ru/core/events/handlers/SampleEventHandler.java @@ -1,4 +1,4 @@ -package ru.core.events; +package ru.core.events.handlers; import mc.core.events.EventHandler; import mc.core.events.LoginEvent; From 5afd8d98711797f109c913541d8a2e99d8db3a85 Mon Sep 17 00:00:00 2001 From: Daniil Date: Wed, 25 Jul 2018 21:06:58 +0700 Subject: [PATCH 03/62] Extracted base EventLoop class --- .../java/mc/core/events/BaseEventLoop.java | 41 +++++++++++++++++++ .../java/mc/core/events/SimpleEventLoop.java | 37 +++-------------- 2 files changed, 46 insertions(+), 32 deletions(-) create mode 100644 event-loop/src/main/java/mc/core/events/BaseEventLoop.java diff --git a/event-loop/src/main/java/mc/core/events/BaseEventLoop.java b/event-loop/src/main/java/mc/core/events/BaseEventLoop.java new file mode 100644 index 0000000..59231d6 --- /dev/null +++ b/event-loop/src/main/java/mc/core/events/BaseEventLoop.java @@ -0,0 +1,41 @@ +package mc.core.events; + +import lombok.extern.slf4j.Slf4j; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +@Slf4j +public abstract class BaseEventLoop implements EventLoop { + @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; + + registerMethod(object, method, annotation, eventType); + } + } + + protected abstract void registerMethod(Object object, Method method, EventHandler annotation, Class eventType); +} diff --git a/event-loop/src/main/java/mc/core/events/SimpleEventLoop.java b/event-loop/src/main/java/mc/core/events/SimpleEventLoop.java index 47e2ff3..5963a38 100644 --- a/event-loop/src/main/java/mc/core/events/SimpleEventLoop.java +++ b/event-loop/src/main/java/mc/core/events/SimpleEventLoop.java @@ -10,7 +10,7 @@ import java.lang.reflect.Modifier; import java.util.*; @Slf4j -public class SimpleEventLoop implements EventLoop { +public class SimpleEventLoop extends BaseEventLoop { private Map, List> handlers = new HashMap<>(); @Override @@ -27,39 +27,12 @@ public class SimpleEventLoop implements EventLoop { } } - @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)); - } + protected void registerMethod(Object object, Method method, EventHandler annotation, Class eventType) { + List eventHandlers = handlers.computeIfAbsent(eventType, s -> new ArrayList<>()); + eventHandlers.add(new SimpleEventLoop.ExecutorLink(annotation.priority().getValue(), annotation.ignoreCancelled(), method, object)); + eventHandlers.sort(Comparator.comparingInt(o -> o.priority)); } - /** * This class describes */ From 36af2dbd7af3bc54af7092ac7cba8236fd13bf6a Mon Sep 17 00:00:00 2001 From: Daniil Date: Wed, 25 Jul 2018 22:07:47 +0700 Subject: [PATCH 04/62] Preprocessing concept implementation --- .../java/mc/core/events/BaseEventLoop.java | 45 ++++---- .../java/mc/core/events/SimpleEventLoop.java | 19 +++- .../mc/core/events/async/AsyncEventLoop.java | 105 ++++++++++++++++++ .../core/events/async/EventPreprocessor.java | 12 ++ .../ru/core/events/AsyncEventLoopTest.java | 22 ++++ .../events/handlers/AsyncEventHandler.java | 18 +++ 6 files changed, 190 insertions(+), 31 deletions(-) create mode 100644 event-loop/src/main/java/mc/core/events/async/AsyncEventLoop.java create mode 100644 event-loop/src/main/java/mc/core/events/async/EventPreprocessor.java create mode 100644 event-loop/src/test/java/ru/core/events/AsyncEventLoopTest.java create mode 100644 event-loop/src/test/java/ru/core/events/handlers/AsyncEventHandler.java diff --git a/event-loop/src/main/java/mc/core/events/BaseEventLoop.java b/event-loop/src/main/java/mc/core/events/BaseEventLoop.java index 59231d6..9aff373 100644 --- a/event-loop/src/main/java/mc/core/events/BaseEventLoop.java +++ b/event-loop/src/main/java/mc/core/events/BaseEventLoop.java @@ -7,35 +7,28 @@ import java.lang.reflect.Modifier; @Slf4j public abstract class BaseEventLoop implements EventLoop { - @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; + protected boolean notValidEventHandler(EventHandler annotation, Method method) { + if (annotation == null) + return true; - } + if (!Modifier.isPublic(method.getModifiers())) { + log.error("Unable to register {} as an EventHandler. Method must have a 'private' access modifier.", method.toString()); + return true; - 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; - - registerMethod(object, method, annotation, eventType); } - } - protected abstract void registerMethod(Object object, Method method, EventHandler annotation, Class eventType); + if (method.getParameterCount() != 1) { + log.error("Unable to register {} as an EventHandler. Method must have exactly one argument.", method.toString()); + return true; + } + + 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()); + return true; + } + + return false; + } } diff --git a/event-loop/src/main/java/mc/core/events/SimpleEventLoop.java b/event-loop/src/main/java/mc/core/events/SimpleEventLoop.java index 5963a38..7f22112 100644 --- a/event-loop/src/main/java/mc/core/events/SimpleEventLoop.java +++ b/event-loop/src/main/java/mc/core/events/SimpleEventLoop.java @@ -6,7 +6,6 @@ import lombok.extern.slf4j.Slf4j; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.lang.reflect.Modifier; import java.util.*; @Slf4j @@ -27,10 +26,20 @@ public class SimpleEventLoop extends BaseEventLoop { } } - protected void registerMethod(Object object, Method method, EventHandler annotation, Class eventType) { - List eventHandlers = handlers.computeIfAbsent(eventType, s -> new ArrayList<>()); - eventHandlers.add(new SimpleEventLoop.ExecutorLink(annotation.priority().getValue(), annotation.ignoreCancelled(), method, object)); - eventHandlers.sort(Comparator.comparingInt(o -> o.priority)); + @Override + public void addEventHandler(Object object) { + for (Method method : object.getClass().getDeclaredMethods()) { + EventHandler annotation = method.getAnnotation(EventHandler.class); + + if (notValidEventHandler(annotation, method)) + continue; + + @SuppressWarnings("unchecked") Class eventType = (Class) method.getParameterTypes()[0]; + + List eventHandlers = handlers.computeIfAbsent(eventType, s -> new ArrayList<>()); + eventHandlers.add(new SimpleEventLoop.ExecutorLink(annotation.priority().getValue(), annotation.ignoreCancelled(), method, object)); + eventHandlers.sort(Comparator.comparingInt(o -> o.priority)); + } } /** diff --git a/event-loop/src/main/java/mc/core/events/async/AsyncEventLoop.java b/event-loop/src/main/java/mc/core/events/async/AsyncEventLoop.java new file mode 100644 index 0000000..283d9dc --- /dev/null +++ b/event-loop/src/main/java/mc/core/events/async/AsyncEventLoop.java @@ -0,0 +1,105 @@ +package mc.core.events.async; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import mc.core.events.BaseEventLoop; +import mc.core.events.Event; +import mc.core.events.EventHandler; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.*; + +@SuppressWarnings("Duplicates") +@Slf4j +public class AsyncEventLoop extends BaseEventLoop { + 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)) { + if (link.getPreprocessMethod() != null) { + try { + link.getPreprocessMethod().invoke(link.object, event); + } catch (IllegalAccessException | InvocationTargetException e) { + log.error("Exception caught while attempting to run event preprocessing for {}.", eventType.getSimpleName(), e); + } + } + + 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) { + Map preprocessors = new HashMap<>(); + + for (Method method : object.getClass().getDeclaredMethods()) { + EventPreprocessor annotation = method.getAnnotation(EventPreprocessor.class); + if (annotation == null) + continue; + + if (!Modifier.isPublic(method.getModifiers())) { + log.error("Unable to register {} as an EventPreprocessor. Method must have a 'private' access modifier.", method.toString()); + continue; + + } + + if (method.getParameterCount() != 1) { + log.error("Unable to register {} as an EventPreprocessor. 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 EventPreprocessor. First parameter type must implement 'Event' interface.", method.toString()); + continue; + } + + if (preprocessors.containsKey(annotation.eventName())) + log.warn("Attempted to register multiple EventPreprocessors for the event {}. Please note, that only the last one will be registered.", annotation.eventName()); + preprocessors.put(annotation.eventName(), method); + } + + + for (Method method : object.getClass().getDeclaredMethods()) { + EventHandler annotation = method.getAnnotation(EventHandler.class); + + if (notValidEventHandler(annotation, method)) + continue; + + @SuppressWarnings("unchecked") Class eventType = (Class) method.getParameterTypes()[0]; + + List eventHandlers = handlers.computeIfAbsent(eventType, s -> new ArrayList<>()); + eventHandlers.add(new ExecutorLink(annotation.priority().getValue(), annotation.ignoreCancelled(), method, object, preprocessors.remove(method.getName()))); + eventHandlers.sort(Comparator.comparingInt(o -> o.priority)); + } + + for (Map.Entry preprocessor : preprocessors.entrySet()) + log.error("EventPreprocessor ({}) missing target: unable to find target method ({})", preprocessor.getValue(), preprocessor.getKey()); + + } + + /** + * 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; + private final Method preprocessMethod; + } +} + diff --git a/event-loop/src/main/java/mc/core/events/async/EventPreprocessor.java b/event-loop/src/main/java/mc/core/events/async/EventPreprocessor.java new file mode 100644 index 0000000..905d661 --- /dev/null +++ b/event-loop/src/main/java/mc/core/events/async/EventPreprocessor.java @@ -0,0 +1,12 @@ +package mc.core.events.async; + + +import java.lang.annotation.*; + +@Documented +@Target(ElementType.METHOD) +@Inherited +@Retention(RetentionPolicy.RUNTIME) +public @interface EventPreprocessor { + String eventName(); +} diff --git a/event-loop/src/test/java/ru/core/events/AsyncEventLoopTest.java b/event-loop/src/test/java/ru/core/events/AsyncEventLoopTest.java new file mode 100644 index 0000000..5d5f70d --- /dev/null +++ b/event-loop/src/test/java/ru/core/events/AsyncEventLoopTest.java @@ -0,0 +1,22 @@ +package ru.core.events; + +import mc.core.events.LoginEvent; +import mc.core.events.async.AsyncEventLoop; +import org.junit.Assert; +import org.junit.Test; +import ru.core.events.handlers.AsyncEventHandler; + +public class AsyncEventLoopTest { + + @Test + public void loopWorks() { + AsyncEventLoop asyncEventLoop = new AsyncEventLoop(); + asyncEventLoop.addEventHandler(new AsyncEventHandler()); + LoginEvent testEvent = new LoginEvent(null); + testEvent.setDenyReason("none"); + + asyncEventLoop.callEvent(testEvent); + + Assert.assertEquals("Event handler was not called", "Hello from Async Event Handler!", testEvent.getDenyReason()); + } +} diff --git a/event-loop/src/test/java/ru/core/events/handlers/AsyncEventHandler.java b/event-loop/src/test/java/ru/core/events/handlers/AsyncEventHandler.java new file mode 100644 index 0000000..634b9a1 --- /dev/null +++ b/event-loop/src/test/java/ru/core/events/handlers/AsyncEventHandler.java @@ -0,0 +1,18 @@ +package ru.core.events.handlers; + +import mc.core.events.EventHandler; +import mc.core.events.LoginEvent; +import mc.core.events.async.EventPreprocessor; + +public class AsyncEventHandler { + + @EventHandler + public void onLogin(LoginEvent event) { + event.setDenyReason("Hello from Async Event Handler!"); + } + + @EventPreprocessor(eventName = "onLogin") + public void onLoginPreprocess(LoginEvent event) { + + } +} From 8fef2840192f72590670aaf7ef85dfe20c1490d8 Mon Sep 17 00:00:00 2001 From: Daniil Date: Wed, 25 Jul 2018 22:58:30 +0700 Subject: [PATCH 05/62] Implementation of Return Injection --- .../mc/core/events/async/AsyncEventLoop.java | 51 ++++++++++++++++--- .../core/events/async/EventPreprocessor.java | 2 +- .../ru/core/events/AsyncEventLoopTest.java | 2 +- .../events/handlers/AsyncEventHandler.java | 18 ++++--- 4 files changed, 58 insertions(+), 15 deletions(-) diff --git a/event-loop/src/main/java/mc/core/events/async/AsyncEventLoop.java b/event-loop/src/main/java/mc/core/events/async/AsyncEventLoop.java index 283d9dc..66936e5 100644 --- a/event-loop/src/main/java/mc/core/events/async/AsyncEventLoop.java +++ b/event-loop/src/main/java/mc/core/events/async/AsyncEventLoop.java @@ -22,16 +22,20 @@ public class AsyncEventLoop extends BaseEventLoop { Class eventType = event.getClass(); if (handlers.containsKey(eventType)) { for (ExecutorLink link : handlers.get(eventType)) { + Object preprocessResult = null; if (link.getPreprocessMethod() != null) { try { - link.getPreprocessMethod().invoke(link.object, event); + preprocessResult = link.getPreprocessMethod().invoke(link.object, event); } catch (IllegalAccessException | InvocationTargetException e) { log.error("Exception caught while attempting to run event preprocessing for {}.", eventType.getSimpleName(), e); } } try { - link.getMethod().invoke(link.object, event); + if (link.getResultInjection() != null) + link.getMethod().invoke(link.object, event, preprocessResult); + else + link.getMethod().invoke(link.object, event); } catch (IllegalAccessException | InvocationTargetException e) { log.error("Exception caught while attempting to dispatch {}.", eventType.getSimpleName(), e); } @@ -65,23 +69,55 @@ public class AsyncEventLoop extends BaseEventLoop { continue; } - if (preprocessors.containsKey(annotation.eventName())) - log.warn("Attempted to register multiple EventPreprocessors for the event {}. Please note, that only the last one will be registered.", annotation.eventName()); - preprocessors.put(annotation.eventName(), method); + if (preprocessors.containsKey(annotation.methodName())) + log.warn("Attempted to register multiple EventPreprocessors for the event {}. Please note, that only the last one will be registered.", annotation.methodName()); + preprocessors.put(annotation.methodName() + ":" + firstParamType.getSimpleName(), method); } for (Method method : object.getClass().getDeclaredMethods()) { EventHandler annotation = method.getAnnotation(EventHandler.class); - if (notValidEventHandler(annotation, method)) + if (annotation == null) continue; + 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() == 0) { + log.error("Unable to register {} as an EventHandler. Method must at least 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) method.getParameterTypes()[0]; + Method preprocessMethod = preprocessors.get(method.getName() + ":" + eventType.getSimpleName()); + Class resultInjection = null; + if (preprocessMethod.getReturnType() != void.class && method.getParameterCount() == 2 && preprocessMethod.getReturnType().equals(method.getParameterTypes()[1])) + resultInjection = preprocessMethod.getReturnType(); + + if (resultInjection == null && method.getParameterCount() > 1) { + log.error("Unable to register {} as an EventHandler. Method has {} arguments, but no EventPreprocessors found to provide DI for type {}", method.toString(), method.getParameterCount(), method.getParameterTypes()[1].toString()); + continue; + } + + if (resultInjection == null && preprocessMethod.getReturnType() != void.class) { + log.warn("DI registration for EventHandler {} failed. Injection target not found", method.toString()); + } + List eventHandlers = handlers.computeIfAbsent(eventType, s -> new ArrayList<>()); - eventHandlers.add(new ExecutorLink(annotation.priority().getValue(), annotation.ignoreCancelled(), method, object, preprocessors.remove(method.getName()))); + eventHandlers.add(new ExecutorLink(annotation.priority().getValue(), annotation.ignoreCancelled(), method, object, preprocessMethod, resultInjection)); eventHandlers.sort(Comparator.comparingInt(o -> o.priority)); + + preprocessors.remove(method.getName() + ":" + eventType.getSimpleName()); } for (Map.Entry preprocessor : preprocessors.entrySet()) @@ -100,6 +136,7 @@ public class AsyncEventLoop extends BaseEventLoop { private final Method method; private final Object object; private final Method preprocessMethod; + private final Class resultInjection; } } diff --git a/event-loop/src/main/java/mc/core/events/async/EventPreprocessor.java b/event-loop/src/main/java/mc/core/events/async/EventPreprocessor.java index 905d661..107628a 100644 --- a/event-loop/src/main/java/mc/core/events/async/EventPreprocessor.java +++ b/event-loop/src/main/java/mc/core/events/async/EventPreprocessor.java @@ -8,5 +8,5 @@ import java.lang.annotation.*; @Inherited @Retention(RetentionPolicy.RUNTIME) public @interface EventPreprocessor { - String eventName(); + String methodName(); } diff --git a/event-loop/src/test/java/ru/core/events/AsyncEventLoopTest.java b/event-loop/src/test/java/ru/core/events/AsyncEventLoopTest.java index 5d5f70d..f112174 100644 --- a/event-loop/src/test/java/ru/core/events/AsyncEventLoopTest.java +++ b/event-loop/src/test/java/ru/core/events/AsyncEventLoopTest.java @@ -17,6 +17,6 @@ public class AsyncEventLoopTest { asyncEventLoop.callEvent(testEvent); - Assert.assertEquals("Event handler was not called", "Hello from Async Event Handler!", testEvent.getDenyReason()); + Assert.assertEquals("Event handler was not called", "Hello! This is a message from Sync portion of the event", testEvent.getDenyReason()); } } diff --git a/event-loop/src/test/java/ru/core/events/handlers/AsyncEventHandler.java b/event-loop/src/test/java/ru/core/events/handlers/AsyncEventHandler.java index 634b9a1..371e0cf 100644 --- a/event-loop/src/test/java/ru/core/events/handlers/AsyncEventHandler.java +++ b/event-loop/src/test/java/ru/core/events/handlers/AsyncEventHandler.java @@ -1,18 +1,24 @@ package ru.core.events.handlers; +import lombok.extern.slf4j.Slf4j; import mc.core.events.EventHandler; import mc.core.events.LoginEvent; import mc.core.events.async.EventPreprocessor; +@Slf4j public class AsyncEventHandler { - @EventHandler - public void onLogin(LoginEvent event) { - event.setDenyReason("Hello from Async Event Handler!"); + @EventPreprocessor(methodName = "onLogin") + public void onLoginPreprocess(LoginEvent event) { + event.setDenyReason("Hello! This is a message from Async event preprocessor."); + + // Представим, что здесь мы выполнили запрос к БД + // и это значение - кол-во денег у игрока + /*return 20D;*/ } - @EventPreprocessor(eventName = "onLogin") - public void onLoginPreprocess(LoginEvent event) { - + @EventHandler + public void onLogin(LoginEvent event) { // money здесь будет равно тому, что вернул onLoginPreprocess + event.setDenyReason("Hello! This is a message from Sync portion of the event"); } } From 0ab539d4ef38ad7ecde9aced575ceea0a925eab6 Mon Sep 17 00:00:00 2001 From: Daniil Date: Wed, 25 Jul 2018 23:17:14 +0700 Subject: [PATCH 06/62] Implemented proper Async Event Loop --- .../java/mc/core/events/SimpleEventLoop.java | 2 + .../mc/core/events/async/AsyncEventLoop.java | 38 ++++++++++++++----- .../java/mc/core/events/async/EventBatch.java | 17 +++++++++ .../mc/core/events/async/PreprocessTask.java | 32 ++++++++++++++++ 4 files changed, 79 insertions(+), 10 deletions(-) create mode 100644 event-loop/src/main/java/mc/core/events/async/EventBatch.java create mode 100644 event-loop/src/main/java/mc/core/events/async/PreprocessTask.java diff --git a/event-loop/src/main/java/mc/core/events/SimpleEventLoop.java b/event-loop/src/main/java/mc/core/events/SimpleEventLoop.java index 7f22112..cb63d49 100644 --- a/event-loop/src/main/java/mc/core/events/SimpleEventLoop.java +++ b/event-loop/src/main/java/mc/core/events/SimpleEventLoop.java @@ -17,6 +17,8 @@ public class SimpleEventLoop extends BaseEventLoop { Class eventType = event.getClass(); if (handlers.containsKey(eventType)) { for (ExecutorLink link : handlers.get(eventType)) { + if (link.isIgnoreCancelled() && event.isCanceled()) + continue; try { link.getMethod().invoke(link.object, event); } catch (IllegalAccessException | InvocationTargetException e) { diff --git a/event-loop/src/main/java/mc/core/events/async/AsyncEventLoop.java b/event-loop/src/main/java/mc/core/events/async/AsyncEventLoop.java index 66936e5..8ee1ab6 100644 --- a/event-loop/src/main/java/mc/core/events/async/AsyncEventLoop.java +++ b/event-loop/src/main/java/mc/core/events/async/AsyncEventLoop.java @@ -11,29 +11,47 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.*; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; @SuppressWarnings("Duplicates") @Slf4j public class AsyncEventLoop extends BaseEventLoop { private Map, List> handlers = new HashMap<>(); + private ExecutorService preEventExecutor = Executors.newSingleThreadExecutor(); @Override public void callEvent(Event event) { Class eventType = event.getClass(); + if (handlers.containsKey(eventType)) { - for (ExecutorLink link : handlers.get(eventType)) { - Object preprocessResult = null; - if (link.getPreprocessMethod() != null) { - try { - preprocessResult = link.getPreprocessMethod().invoke(link.object, event); - } catch (IllegalAccessException | InvocationTargetException e) { - log.error("Exception caught while attempting to run event preprocessing for {}.", eventType.getSimpleName(), e); - } + List handlerList = handlers.get(eventType); + EventBatch eventBatch = new EventBatch(handlerList.size()); + CountDownLatch latch = new CountDownLatch(handlerList.size()); + + for (int i = 0; i < handlerList.size(); i++) { + if (handlerList.get(i).getPreprocessMethod() == null) { + latch.countDown(); + continue; } + preEventExecutor.submit(new PreprocessTask(i, eventBatch, latch, handlerList.get(i), event)); + } + + try { + latch.await(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + for (int i = 0; i < handlerList.size(); i++) { + ExecutorLink link = handlerList.get(i); + if (link.isIgnoreCancelled() && event.isCanceled()) + continue; try { if (link.getResultInjection() != null) - link.getMethod().invoke(link.object, event, preprocessResult); + link.getMethod().invoke(link.object, event, eventBatch.getInjectionObject(i)); else link.getMethod().invoke(link.object, event); } catch (IllegalAccessException | InvocationTargetException e) { @@ -130,7 +148,7 @@ public class AsyncEventLoop extends BaseEventLoop { */ @RequiredArgsConstructor @Getter - private static class ExecutorLink { + static class ExecutorLink { private final int priority; private final boolean ignoreCancelled; private final Method method; diff --git a/event-loop/src/main/java/mc/core/events/async/EventBatch.java b/event-loop/src/main/java/mc/core/events/async/EventBatch.java new file mode 100644 index 0000000..6eaade1 --- /dev/null +++ b/event-loop/src/main/java/mc/core/events/async/EventBatch.java @@ -0,0 +1,17 @@ +package mc.core.events.async; + +public class EventBatch { + private Object[] returnInject; + + public EventBatch(int size) { + this.returnInject = new Object[size]; + } + + public Object getInjectionObject(int id) { + return returnInject[id]; + } + + public void setInjectionObject(int id, Object value) { + returnInject[id] = value; + } +} diff --git a/event-loop/src/main/java/mc/core/events/async/PreprocessTask.java b/event-loop/src/main/java/mc/core/events/async/PreprocessTask.java new file mode 100644 index 0000000..e38a8c3 --- /dev/null +++ b/event-loop/src/main/java/mc/core/events/async/PreprocessTask.java @@ -0,0 +1,32 @@ +package mc.core.events.async; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import mc.core.events.Event; + +import java.lang.reflect.InvocationTargetException; +import java.util.concurrent.CountDownLatch; + +@RequiredArgsConstructor +@Slf4j +public class PreprocessTask implements Runnable { + private final int id; + private final EventBatch eventBatch; + private final CountDownLatch latch; + private final AsyncEventLoop.ExecutorLink link; + private final Event event; + + @Override + public void run() { + try { + Object result = link.getPreprocessMethod().invoke(link.getObject(), event); + if (result != null) + eventBatch.setInjectionObject(id, result); + } catch (IllegalAccessException | InvocationTargetException e) { + log.error("Exception caught while attempting to run event preprocessing.", e); + } finally { + latch.countDown(); + } + + } +} From b74292c336e4a2b75d26d0f7aa945785f88ce1cc Mon Sep 17 00:00:00 2001 From: Daniil Date: Thu, 26 Jul 2018 11:08:56 +0700 Subject: [PATCH 07/62] Splitting AdvancedEventLoop and AsyncEventLoop for the sake of comparision --- .../core/events/async/AdvancedEventLoop.java | 145 ++++++++++++++++++ .../mc/core/events/async/AsyncEventLoop.java | 111 +------------- 2 files changed, 149 insertions(+), 107 deletions(-) create mode 100644 event-loop/src/main/java/mc/core/events/async/AdvancedEventLoop.java diff --git a/event-loop/src/main/java/mc/core/events/async/AdvancedEventLoop.java b/event-loop/src/main/java/mc/core/events/async/AdvancedEventLoop.java new file mode 100644 index 0000000..9b8a4e2 --- /dev/null +++ b/event-loop/src/main/java/mc/core/events/async/AdvancedEventLoop.java @@ -0,0 +1,145 @@ +package mc.core.events.async; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import mc.core.events.BaseEventLoop; +import mc.core.events.Event; +import mc.core.events.EventHandler; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.*; + +@SuppressWarnings("Duplicates") +@Slf4j +public class AdvancedEventLoop extends BaseEventLoop { + 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)) { + if (link.isIgnoreCancelled() && event.isCanceled()) + continue; + + Object preprocessResult = null; + if (link.getPreprocessMethod() != null) { + try { + preprocessResult = link.getPreprocessMethod().invoke(link.object, event); + } catch (IllegalAccessException | InvocationTargetException e) { + log.error("Exception caught while attempting to run event preprocessing for {}.", eventType.getSimpleName(), e); + } + } + + try { + if (link.getResultInjection() != null) + link.getMethod().invoke(link.object, event, preprocessResult); + else + 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) { + Map preprocessors = new HashMap<>(); + + for (Method method : object.getClass().getDeclaredMethods()) { + EventPreprocessor annotation = method.getAnnotation(EventPreprocessor.class); + if (annotation == null) + continue; + + if (!Modifier.isPublic(method.getModifiers())) { + log.error("Unable to register {} as an EventPreprocessor. Method must have a 'private' access modifier.", method.toString()); + continue; + + } + + if (method.getParameterCount() != 1) { + log.error("Unable to register {} as an EventPreprocessor. 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 EventPreprocessor. First parameter type must implement 'Event' interface.", method.toString()); + continue; + } + + if (preprocessors.containsKey(annotation.methodName())) + log.warn("Attempted to register multiple EventPreprocessors for the event {}. Please note, that only the last one will be registered.", annotation.methodName()); + preprocessors.put(annotation.methodName() + ":" + firstParamType.getSimpleName(), method); + } + + + for (Method method : object.getClass().getDeclaredMethods()) { + EventHandler annotation = method.getAnnotation(EventHandler.class); + + if (annotation == null) + continue; + + 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() == 0) { + log.error("Unable to register {} as an EventHandler. Method must at least 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) method.getParameterTypes()[0]; + + Method preprocessMethod = preprocessors.get(method.getName() + ":" + eventType.getSimpleName()); + Class resultInjection = null; + if (preprocessMethod.getReturnType() != void.class && method.getParameterCount() == 2 && preprocessMethod.getReturnType().equals(method.getParameterTypes()[1])) + resultInjection = preprocessMethod.getReturnType(); + + if (resultInjection == null && method.getParameterCount() > 1) { + log.error("Unable to register {} as an EventHandler. Method has {} arguments, but no EventPreprocessors found to provide DI for type {}", method.toString(), method.getParameterCount(), method.getParameterTypes()[1].toString()); + continue; + } + + if (resultInjection == null && preprocessMethod.getReturnType() != void.class) { + log.warn("DI registration for EventHandler {} failed. Injection target not found", method.toString()); + } + + List eventHandlers = handlers.computeIfAbsent(eventType, s -> new ArrayList<>()); + eventHandlers.add(new ExecutorLink(annotation.priority().getValue(), annotation.ignoreCancelled(), method, object, preprocessMethod, resultInjection)); + eventHandlers.sort(Comparator.comparingInt(o -> o.priority)); + + preprocessors.remove(method.getName() + ":" + eventType.getSimpleName()); + } + + for (Map.Entry preprocessor : preprocessors.entrySet()) + log.error("EventPreprocessor ({}) missing target: unable to find target method ({})", preprocessor.getValue(), preprocessor.getKey()); + + } + + /** + * This class describes + */ + @RequiredArgsConstructor + @Getter + static class ExecutorLink { + private final int priority; + private final boolean ignoreCancelled; + private final Method method; + private final Object object; + private final Method preprocessMethod; + private final Class resultInjection; + } +} + diff --git a/event-loop/src/main/java/mc/core/events/async/AsyncEventLoop.java b/event-loop/src/main/java/mc/core/events/async/AsyncEventLoop.java index 8ee1ab6..61a12f9 100644 --- a/event-loop/src/main/java/mc/core/events/async/AsyncEventLoop.java +++ b/event-loop/src/main/java/mc/core/events/async/AsyncEventLoop.java @@ -1,24 +1,17 @@ package mc.core.events.async; -import lombok.Getter; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import mc.core.events.BaseEventLoop; import mc.core.events.Event; -import mc.core.events.EventHandler; import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.*; +import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @SuppressWarnings("Duplicates") @Slf4j -public class AsyncEventLoop extends BaseEventLoop { - private Map, List> handlers = new HashMap<>(); +public class AsyncEventLoop extends AdvancedEventLoop { private ExecutorService preEventExecutor = Executors.newSingleThreadExecutor(); @Override @@ -51,110 +44,14 @@ public class AsyncEventLoop extends BaseEventLoop { try { if (link.getResultInjection() != null) - link.getMethod().invoke(link.object, event, eventBatch.getInjectionObject(i)); + link.getMethod().invoke(link.getObject(), event, eventBatch.getInjectionObject(i)); else - link.getMethod().invoke(link.object, event); + link.getMethod().invoke(link.getObject(), event); } catch (IllegalAccessException | InvocationTargetException e) { log.error("Exception caught while attempting to dispatch {}.", eventType.getSimpleName(), e); } } } } - - @Override - public void addEventHandler(Object object) { - Map preprocessors = new HashMap<>(); - - for (Method method : object.getClass().getDeclaredMethods()) { - EventPreprocessor annotation = method.getAnnotation(EventPreprocessor.class); - if (annotation == null) - continue; - - if (!Modifier.isPublic(method.getModifiers())) { - log.error("Unable to register {} as an EventPreprocessor. Method must have a 'private' access modifier.", method.toString()); - continue; - - } - - if (method.getParameterCount() != 1) { - log.error("Unable to register {} as an EventPreprocessor. 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 EventPreprocessor. First parameter type must implement 'Event' interface.", method.toString()); - continue; - } - - if (preprocessors.containsKey(annotation.methodName())) - log.warn("Attempted to register multiple EventPreprocessors for the event {}. Please note, that only the last one will be registered.", annotation.methodName()); - preprocessors.put(annotation.methodName() + ":" + firstParamType.getSimpleName(), method); - } - - - for (Method method : object.getClass().getDeclaredMethods()) { - EventHandler annotation = method.getAnnotation(EventHandler.class); - - if (annotation == null) - continue; - - 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() == 0) { - log.error("Unable to register {} as an EventHandler. Method must at least 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) method.getParameterTypes()[0]; - - Method preprocessMethod = preprocessors.get(method.getName() + ":" + eventType.getSimpleName()); - Class resultInjection = null; - if (preprocessMethod.getReturnType() != void.class && method.getParameterCount() == 2 && preprocessMethod.getReturnType().equals(method.getParameterTypes()[1])) - resultInjection = preprocessMethod.getReturnType(); - - if (resultInjection == null && method.getParameterCount() > 1) { - log.error("Unable to register {} as an EventHandler. Method has {} arguments, but no EventPreprocessors found to provide DI for type {}", method.toString(), method.getParameterCount(), method.getParameterTypes()[1].toString()); - continue; - } - - if (resultInjection == null && preprocessMethod.getReturnType() != void.class) { - log.warn("DI registration for EventHandler {} failed. Injection target not found", method.toString()); - } - - List eventHandlers = handlers.computeIfAbsent(eventType, s -> new ArrayList<>()); - eventHandlers.add(new ExecutorLink(annotation.priority().getValue(), annotation.ignoreCancelled(), method, object, preprocessMethod, resultInjection)); - eventHandlers.sort(Comparator.comparingInt(o -> o.priority)); - - preprocessors.remove(method.getName() + ":" + eventType.getSimpleName()); - } - - for (Map.Entry preprocessor : preprocessors.entrySet()) - log.error("EventPreprocessor ({}) missing target: unable to find target method ({})", preprocessor.getValue(), preprocessor.getKey()); - - } - - /** - * This class describes - */ - @RequiredArgsConstructor - @Getter - static class ExecutorLink { - private final int priority; - private final boolean ignoreCancelled; - private final Method method; - private final Object object; - private final Method preprocessMethod; - private final Class resultInjection; - } } From 190749b73915a4e71af17166e748b4631af7acd8 Mon Sep 17 00:00:00 2001 From: Daniil Date: Thu, 26 Jul 2018 11:09:30 +0700 Subject: [PATCH 08/62] Benchmark Async vs Advanced event loops --- .../core/events/AsyncEventLoopBenchmark.java | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 event-loop/src/test/java/ru/core/events/AsyncEventLoopBenchmark.java diff --git a/event-loop/src/test/java/ru/core/events/AsyncEventLoopBenchmark.java b/event-loop/src/test/java/ru/core/events/AsyncEventLoopBenchmark.java new file mode 100644 index 0000000..50b1d9f --- /dev/null +++ b/event-loop/src/test/java/ru/core/events/AsyncEventLoopBenchmark.java @@ -0,0 +1,52 @@ +package ru.core.events; + +import com.carrotsearch.junitbenchmarks.AbstractBenchmark; +import mc.core.events.LoginEvent; +import mc.core.events.async.AdvancedEventLoop; +import mc.core.events.async.AsyncEventLoop; +import org.junit.BeforeClass; +import org.junit.Test; +import ru.core.events.handlers.AsyncEventHandler; + +public class AsyncEventLoopBenchmark extends AbstractBenchmark { + private static final int ITERATIONS = 50_000; + private static AsyncEventLoop asyncEventLoop; + private static AdvancedEventLoop advancedEventLoop; + private static LoginEvent testEvent; + + @BeforeClass + public static void setup() { + asyncEventLoop = new AsyncEventLoop(); + asyncEventLoop.addEventHandler(new AsyncEventHandler()); + advancedEventLoop = new AdvancedEventLoop(); + advancedEventLoop.addEventHandler(new AsyncEventHandler()); + testEvent = new LoginEvent(null); + testEvent.setDenyReason("none"); + } + + @Test + public void measure() { + for (int i = 0; i < 10000; i++) { + asyncEventLoop.callEvent(testEvent); + } + } + +/* @Test + @BenchmarkOptions(warmupRounds = 5, benchmarkRounds = 15) + public void async() { + while (true) + asyncEventLoop.callEvent(testEvent); + *//*for (int i = 0; i < ITERATIONS; i++) { + asyncEventLoop.callEvent(testEvent); + + }*//* + }*/ +/* + @Test + @BenchmarkOptions(warmupRounds = 5, benchmarkRounds = 15) + public void advanced() { + for (int i = 0; i < ITERATIONS; i++) { + advancedEventLoop.callEvent(testEvent); + } + }*/ +} From abae528d0b257ce921a2e24cbf42146c25789f94 Mon Sep 17 00:00:00 2001 From: Daniil Date: Thu, 26 Jul 2018 11:18:42 +0700 Subject: [PATCH 09/62] Commenting --- .../src/main/java/mc/core/events/async/AsyncEventLoop.java | 5 +++++ .../src/main/java/mc/core/events/async/EventBatch.java | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/event-loop/src/main/java/mc/core/events/async/AsyncEventLoop.java b/event-loop/src/main/java/mc/core/events/async/AsyncEventLoop.java index 61a12f9..b4a4356 100644 --- a/event-loop/src/main/java/mc/core/events/async/AsyncEventLoop.java +++ b/event-loop/src/main/java/mc/core/events/async/AsyncEventLoop.java @@ -19,10 +19,12 @@ public class AsyncEventLoop extends AdvancedEventLoop { Class eventType = event.getClass(); if (handlers.containsKey(eventType)) { + // Create inter-thread state List handlerList = handlers.get(eventType); EventBatch eventBatch = new EventBatch(handlerList.size()); CountDownLatch latch = new CountDownLatch(handlerList.size()); + // Submit all defined preprocessing methods as async tasks for (int i = 0; i < handlerList.size(); i++) { if (handlerList.get(i).getPreprocessMethod() == null) { latch.countDown(); @@ -31,12 +33,15 @@ public class AsyncEventLoop extends AdvancedEventLoop { preEventExecutor.submit(new PreprocessTask(i, eventBatch, latch, handlerList.get(i), event)); } + // Await for them to complete try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } + // Synchronously invoke EventHandlers with + // data obtained from EventBatch for (int i = 0; i < handlerList.size(); i++) { ExecutorLink link = handlerList.get(i); if (link.isIgnoreCancelled() && event.isCanceled()) diff --git a/event-loop/src/main/java/mc/core/events/async/EventBatch.java b/event-loop/src/main/java/mc/core/events/async/EventBatch.java index 6eaade1..7a6d35d 100644 --- a/event-loop/src/main/java/mc/core/events/async/EventBatch.java +++ b/event-loop/src/main/java/mc/core/events/async/EventBatch.java @@ -1,5 +1,10 @@ package mc.core.events.async; +/** + * Stores state to pass from async executors to sync + * + * TODO: Change name, misleading + */ public class EventBatch { private Object[] returnInject; From 8c6c6bde6c8716e843cd7db8e50c2da6ee7ea228 Mon Sep 17 00:00:00 2001 From: Daniil Date: Thu, 26 Jul 2018 11:23:11 +0700 Subject: [PATCH 10/62] Some more commenting for AsyncEventLoop --- .../mc/core/events/async/AsyncEventLoop.java | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/event-loop/src/main/java/mc/core/events/async/AsyncEventLoop.java b/event-loop/src/main/java/mc/core/events/async/AsyncEventLoop.java index b4a4356..4a6a1e6 100644 --- a/event-loop/src/main/java/mc/core/events/async/AsyncEventLoop.java +++ b/event-loop/src/main/java/mc/core/events/async/AsyncEventLoop.java @@ -27,10 +27,13 @@ public class AsyncEventLoop extends AdvancedEventLoop { // Submit all defined preprocessing methods as async tasks for (int i = 0; i < handlerList.size(); i++) { if (handlerList.get(i).getPreprocessMethod() == null) { + // We have already "allocated" a space for this task in CountDownLatch, + // but since there is no actual preprocess method defined for this handler + // we just skip it by ticking the latch down manually latch.countDown(); - continue; + } else { + preEventExecutor.submit(new PreprocessTask(i, eventBatch, latch, handlerList.get(i), event)); } - preEventExecutor.submit(new PreprocessTask(i, eventBatch, latch, handlerList.get(i), event)); } // Await for them to complete @@ -44,16 +47,15 @@ public class AsyncEventLoop extends AdvancedEventLoop { // data obtained from EventBatch for (int i = 0; i < handlerList.size(); i++) { ExecutorLink link = handlerList.get(i); - if (link.isIgnoreCancelled() && event.isCanceled()) - continue; - - try { - if (link.getResultInjection() != null) - link.getMethod().invoke(link.getObject(), event, eventBatch.getInjectionObject(i)); - else - link.getMethod().invoke(link.getObject(), event); - } catch (IllegalAccessException | InvocationTargetException e) { - log.error("Exception caught while attempting to dispatch {}.", eventType.getSimpleName(), e); + if (!link.isIgnoreCancelled() || !event.isCanceled()) { + try { + if (link.getResultInjection() != null) + link.getMethod().invoke(link.getObject(), event, eventBatch.getInjectionObject(i)); + else + link.getMethod().invoke(link.getObject(), event); + } catch (IllegalAccessException | InvocationTargetException e) { + log.error("Exception caught while attempting to dispatch {}.", eventType.getSimpleName(), e); + } } } } From b732a0c3d10792a7eb3f790f74ecb5743acd8763 Mon Sep 17 00:00:00 2001 From: Daniil Date: Thu, 26 Jul 2018 17:18:32 +0700 Subject: [PATCH 11/62] Added average overhead metrics --- .../mc/core/events/async/AsyncEventLoop.java | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/event-loop/src/main/java/mc/core/events/async/AsyncEventLoop.java b/event-loop/src/main/java/mc/core/events/async/AsyncEventLoop.java index 4a6a1e6..9a0b190 100644 --- a/event-loop/src/main/java/mc/core/events/async/AsyncEventLoop.java +++ b/event-loop/src/main/java/mc/core/events/async/AsyncEventLoop.java @@ -1,5 +1,6 @@ package mc.core.events.async; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import mc.core.events.Event; @@ -9,13 +10,17 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -@SuppressWarnings("Duplicates") @Slf4j public class AsyncEventLoop extends AdvancedEventLoop { + private static final double EMAPeriod = (2D / (50D + 1D)); + @Getter + private double avgOverhead = 0; private ExecutorService preEventExecutor = Executors.newSingleThreadExecutor(); @Override public void callEvent(Event event) { + long wholeMethodBenchmark = System.nanoTime(); + long asyncExecutionWaitTime = 0, syncExecutionTime = 0, tempTime; Class eventType = event.getClass(); if (handlers.containsKey(eventType)) { @@ -37,11 +42,13 @@ public class AsyncEventLoop extends AdvancedEventLoop { } // Await for them to complete + tempTime = System.nanoTime(); try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } + asyncExecutionWaitTime = System.nanoTime() - tempTime; // Synchronously invoke EventHandlers with // data obtained from EventBatch @@ -49,16 +56,28 @@ public class AsyncEventLoop extends AdvancedEventLoop { ExecutorLink link = handlerList.get(i); if (!link.isIgnoreCancelled() || !event.isCanceled()) { try { + tempTime = System.nanoTime(); if (link.getResultInjection() != null) link.getMethod().invoke(link.getObject(), event, eventBatch.getInjectionObject(i)); else link.getMethod().invoke(link.getObject(), event); + syncExecutionTime += System.nanoTime() - tempTime; } catch (IllegalAccessException | InvocationTargetException e) { log.error("Exception caught while attempting to dispatch {}.", eventType.getSimpleName(), e); } } } } + + wholeMethodBenchmark = System.nanoTime() - wholeMethodBenchmark; + long overhead = wholeMethodBenchmark - asyncExecutionWaitTime - syncExecutionTime; + + // Now we calculate exponential moving average of + // overhead timings + if (avgOverhead == 0) + avgOverhead = overhead; + else + avgOverhead = (overhead - avgOverhead) * EMAPeriod + avgOverhead; } } From b30c4ac97ba5b5e924ad8e514b28cbee7cb71036 Mon Sep 17 00:00:00 2001 From: Daniil Date: Thu, 26 Jul 2018 17:32:32 +0700 Subject: [PATCH 12/62] Reorganized test cases --- .../java/mc/core/events/SimpleEventLoop.java | 4 ++++ .../core/events/AsyncEventLoopBenchmark.java | 21 ++++++------------- .../ru/core/events/SimpleEventLoopTest.java | 5 ++--- .../events/handlers/AsyncEventHandler.java | 5 +++++ .../HelloWorldSimpleEventHandler.java | 11 ++++++++++ 5 files changed, 28 insertions(+), 18 deletions(-) create mode 100644 event-loop/src/test/java/ru/core/events/handlers/HelloWorldSimpleEventHandler.java diff --git a/event-loop/src/main/java/mc/core/events/SimpleEventLoop.java b/event-loop/src/main/java/mc/core/events/SimpleEventLoop.java index cb63d49..d93632e 100644 --- a/event-loop/src/main/java/mc/core/events/SimpleEventLoop.java +++ b/event-loop/src/main/java/mc/core/events/SimpleEventLoop.java @@ -12,6 +12,10 @@ import java.util.*; public class SimpleEventLoop extends BaseEventLoop { private Map, List> handlers = new HashMap<>(); + public SimpleEventLoop() { + log.warn("Warning! SimpleEventLoop doesn't support EventPreprocessors and DI. Code annotated @EventProcessor will not be executed at all."); + } + @Override public void callEvent(Event event) { Class eventType = event.getClass(); diff --git a/event-loop/src/test/java/ru/core/events/AsyncEventLoopBenchmark.java b/event-loop/src/test/java/ru/core/events/AsyncEventLoopBenchmark.java index 50b1d9f..37ebe83 100644 --- a/event-loop/src/test/java/ru/core/events/AsyncEventLoopBenchmark.java +++ b/event-loop/src/test/java/ru/core/events/AsyncEventLoopBenchmark.java @@ -1,6 +1,7 @@ package ru.core.events; import com.carrotsearch.junitbenchmarks.AbstractBenchmark; +import com.carrotsearch.junitbenchmarks.BenchmarkOptions; import mc.core.events.LoginEvent; import mc.core.events.async.AdvancedEventLoop; import mc.core.events.async.AsyncEventLoop; @@ -9,7 +10,7 @@ import org.junit.Test; import ru.core.events.handlers.AsyncEventHandler; public class AsyncEventLoopBenchmark extends AbstractBenchmark { - private static final int ITERATIONS = 50_000; + private static final int ITERATIONS = 600; private static AsyncEventLoop asyncEventLoop; private static AdvancedEventLoop advancedEventLoop; private static LoginEvent testEvent; @@ -25,28 +26,18 @@ public class AsyncEventLoopBenchmark extends AbstractBenchmark { } @Test - public void measure() { - for (int i = 0; i < 10000; i++) { + @BenchmarkOptions(warmupRounds = 5, benchmarkRounds = 15) + public void async() { + for (int i = 0; i < ITERATIONS; i++) { asyncEventLoop.callEvent(testEvent); } } -/* @Test - @BenchmarkOptions(warmupRounds = 5, benchmarkRounds = 15) - public void async() { - while (true) - asyncEventLoop.callEvent(testEvent); - *//*for (int i = 0; i < ITERATIONS; i++) { - asyncEventLoop.callEvent(testEvent); - - }*//* - }*/ -/* @Test @BenchmarkOptions(warmupRounds = 5, benchmarkRounds = 15) public void advanced() { for (int i = 0; i < ITERATIONS; i++) { advancedEventLoop.callEvent(testEvent); } - }*/ + } } diff --git a/event-loop/src/test/java/ru/core/events/SimpleEventLoopTest.java b/event-loop/src/test/java/ru/core/events/SimpleEventLoopTest.java index 768c702..4429def 100644 --- a/event-loop/src/test/java/ru/core/events/SimpleEventLoopTest.java +++ b/event-loop/src/test/java/ru/core/events/SimpleEventLoopTest.java @@ -4,15 +4,14 @@ import mc.core.events.LoginEvent; import mc.core.events.SimpleEventLoop; import org.junit.Assert; import org.junit.Test; -import ru.core.events.handlers.FastEventHandler; -import ru.core.events.handlers.SampleEventHandler; +import ru.core.events.handlers.HelloWorldSimpleEventHandler; public class SimpleEventLoopTest { @Test public void loopWorks() { SimpleEventLoop simpleEventLoop = new SimpleEventLoop(); - simpleEventLoop.addEventHandler(new FastEventHandler()); + simpleEventLoop.addEventHandler(new HelloWorldSimpleEventHandler()); LoginEvent testEvent = new LoginEvent(null); testEvent.setDenyReason("none"); diff --git a/event-loop/src/test/java/ru/core/events/handlers/AsyncEventHandler.java b/event-loop/src/test/java/ru/core/events/handlers/AsyncEventHandler.java index 371e0cf..d2891ad 100644 --- a/event-loop/src/test/java/ru/core/events/handlers/AsyncEventHandler.java +++ b/event-loop/src/test/java/ru/core/events/handlers/AsyncEventHandler.java @@ -12,6 +12,11 @@ public class AsyncEventHandler { public void onLoginPreprocess(LoginEvent event) { event.setDenyReason("Hello! This is a message from Async event preprocessor."); + try { + Thread.sleep(1); + } catch (InterruptedException e) { + e.printStackTrace(); + } // Представим, что здесь мы выполнили запрос к БД // и это значение - кол-во денег у игрока /*return 20D;*/ diff --git a/event-loop/src/test/java/ru/core/events/handlers/HelloWorldSimpleEventHandler.java b/event-loop/src/test/java/ru/core/events/handlers/HelloWorldSimpleEventHandler.java new file mode 100644 index 0000000..640e944 --- /dev/null +++ b/event-loop/src/test/java/ru/core/events/handlers/HelloWorldSimpleEventHandler.java @@ -0,0 +1,11 @@ +package ru.core.events.handlers; + +import mc.core.events.EventHandler; +import mc.core.events.LoginEvent; + +public class HelloWorldSimpleEventHandler { + @EventHandler + public void onPlayerLogin(LoginEvent event) { + event.setDenyReason("Hello from SampleEventHandler!"); + } +} From 43321465565a85b507477ad6303d04be21f197c7 Mon Sep 17 00:00:00 2001 From: Daniil Date: Thu, 26 Jul 2018 19:49:00 +0700 Subject: [PATCH 13/62] Tweaked executor service settings --- .../main/java/mc/core/events/async/AsyncEventLoop.java | 10 ++++------ .../java/ru/core/events/AsyncEventLoopBenchmark.java | 6 +++++- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/event-loop/src/main/java/mc/core/events/async/AsyncEventLoop.java b/event-loop/src/main/java/mc/core/events/async/AsyncEventLoop.java index 9a0b190..3145785 100644 --- a/event-loop/src/main/java/mc/core/events/async/AsyncEventLoop.java +++ b/event-loop/src/main/java/mc/core/events/async/AsyncEventLoop.java @@ -6,16 +6,14 @@ import mc.core.events.Event; import java.lang.reflect.InvocationTargetException; import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; +import java.util.concurrent.*; @Slf4j public class AsyncEventLoop extends AdvancedEventLoop { - private static final double EMAPeriod = (2D / (50D + 1D)); + private static final double emaPeriod = (2D / (50D + 1D)); + private final ExecutorService preEventExecutor = new ThreadPoolExecutor(2, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<>()); @Getter private double avgOverhead = 0; - private ExecutorService preEventExecutor = Executors.newSingleThreadExecutor(); @Override public void callEvent(Event event) { @@ -77,7 +75,7 @@ public class AsyncEventLoop extends AdvancedEventLoop { if (avgOverhead == 0) avgOverhead = overhead; else - avgOverhead = (overhead - avgOverhead) * EMAPeriod + avgOverhead; + avgOverhead = (overhead - avgOverhead) * emaPeriod + avgOverhead; } } diff --git a/event-loop/src/test/java/ru/core/events/AsyncEventLoopBenchmark.java b/event-loop/src/test/java/ru/core/events/AsyncEventLoopBenchmark.java index 37ebe83..193fc6f 100644 --- a/event-loop/src/test/java/ru/core/events/AsyncEventLoopBenchmark.java +++ b/event-loop/src/test/java/ru/core/events/AsyncEventLoopBenchmark.java @@ -10,7 +10,7 @@ import org.junit.Test; import ru.core.events.handlers.AsyncEventHandler; public class AsyncEventLoopBenchmark extends AbstractBenchmark { - private static final int ITERATIONS = 600; + private static final int ITERATIONS = 200; private static AsyncEventLoop asyncEventLoop; private static AdvancedEventLoop advancedEventLoop; private static LoginEvent testEvent; @@ -19,8 +19,12 @@ public class AsyncEventLoopBenchmark extends AbstractBenchmark { public static void setup() { asyncEventLoop = new AsyncEventLoop(); asyncEventLoop.addEventHandler(new AsyncEventHandler()); + asyncEventLoop.addEventHandler(new AsyncEventHandler()); + asyncEventLoop.addEventHandler(new AsyncEventHandler()); advancedEventLoop = new AdvancedEventLoop(); advancedEventLoop.addEventHandler(new AsyncEventHandler()); + advancedEventLoop.addEventHandler(new AsyncEventHandler()); + advancedEventLoop.addEventHandler(new AsyncEventHandler()); testEvent = new LoginEvent(null); testEvent.setDenyReason("none"); } From 03974934a0a23272723b89a3037c1670196c660e Mon Sep 17 00:00:00 2001 From: Daniil Date: Tue, 31 Jul 2018 14:19:46 +0700 Subject: [PATCH 14/62] Test code for cache-like event loop --- .../java/mc/core/events/cachelike/Handler.java | 16 ++++++++++++++++ .../mc/core/events/cachelike/Preprocessor.java | 5 +++++ .../events/cachelike/PreprocessorContext.java | 17 +++++++++++++++++ 3 files changed, 38 insertions(+) create mode 100644 event-loop/src/main/java/mc/core/events/cachelike/Handler.java create mode 100644 event-loop/src/main/java/mc/core/events/cachelike/Preprocessor.java create mode 100644 event-loop/src/main/java/mc/core/events/cachelike/PreprocessorContext.java diff --git a/event-loop/src/main/java/mc/core/events/cachelike/Handler.java b/event-loop/src/main/java/mc/core/events/cachelike/Handler.java new file mode 100644 index 0000000..0208662 --- /dev/null +++ b/event-loop/src/main/java/mc/core/events/cachelike/Handler.java @@ -0,0 +1,16 @@ +package mc.core.events.cachelike; + +import mc.core.events.LoginEvent; + +public class SampleHandler { + + @Preprocessor(preprocessor = (new PreprocessorContext() { + @Override + protected void init() { + + } + }).class) + public void onLogin(LoginEvent event){ + + } +} diff --git a/event-loop/src/main/java/mc/core/events/cachelike/Preprocessor.java b/event-loop/src/main/java/mc/core/events/cachelike/Preprocessor.java new file mode 100644 index 0000000..36bd1f3 --- /dev/null +++ b/event-loop/src/main/java/mc/core/events/cachelike/Preprocessor.java @@ -0,0 +1,5 @@ +package mc.core.events.cachelike; + +public @interface Preprocessor { + Class preprocessor(); +} diff --git a/event-loop/src/main/java/mc/core/events/cachelike/PreprocessorContext.java b/event-loop/src/main/java/mc/core/events/cachelike/PreprocessorContext.java new file mode 100644 index 0000000..99292fc --- /dev/null +++ b/event-loop/src/main/java/mc/core/events/cachelike/PreprocessorContext.java @@ -0,0 +1,17 @@ +package mc.core.events.cachelike; + +import com.google.common.base.Function; + +import java.util.ArrayList; +import java.util.List; + +public abstract class PreprocessorContext { + private List> fetchers = new ArrayList<>(); + + protected void push(Function fetcher) { + fetchers.add(fetcher); + } + + protected abstract void init() ; + +} From 1a03c517f6c127081c15321c5a4904e17585610c Mon Sep 17 00:00:00 2001 From: Daniil Date: Tue, 31 Jul 2018 14:32:53 +0700 Subject: [PATCH 15/62] New version of cache-like syntax approach --- .../events/cachelike/EventHandlerBase.java | 11 ++++++++++ .../mc/core/events/cachelike/Handler.java | 16 -------------- .../core/events/cachelike/Preprocessor.java | 2 +- .../core/events/cachelike/SampleHandler.java | 21 +++++++++++++++++++ 4 files changed, 33 insertions(+), 17 deletions(-) create mode 100644 event-loop/src/main/java/mc/core/events/cachelike/EventHandlerBase.java delete mode 100644 event-loop/src/main/java/mc/core/events/cachelike/Handler.java create mode 100644 event-loop/src/main/java/mc/core/events/cachelike/SampleHandler.java diff --git a/event-loop/src/main/java/mc/core/events/cachelike/EventHandlerBase.java b/event-loop/src/main/java/mc/core/events/cachelike/EventHandlerBase.java new file mode 100644 index 0000000..b4eecc5 --- /dev/null +++ b/event-loop/src/main/java/mc/core/events/cachelike/EventHandlerBase.java @@ -0,0 +1,11 @@ +package mc.core.events.cachelike; + +import java.util.List; + +public class EventHandlerBase { + private List contextList; + + protected void push(PreprocessorContext context){ + contextList.add(context); + } +} diff --git a/event-loop/src/main/java/mc/core/events/cachelike/Handler.java b/event-loop/src/main/java/mc/core/events/cachelike/Handler.java deleted file mode 100644 index 0208662..0000000 --- a/event-loop/src/main/java/mc/core/events/cachelike/Handler.java +++ /dev/null @@ -1,16 +0,0 @@ -package mc.core.events.cachelike; - -import mc.core.events.LoginEvent; - -public class SampleHandler { - - @Preprocessor(preprocessor = (new PreprocessorContext() { - @Override - protected void init() { - - } - }).class) - public void onLogin(LoginEvent event){ - - } -} diff --git a/event-loop/src/main/java/mc/core/events/cachelike/Preprocessor.java b/event-loop/src/main/java/mc/core/events/cachelike/Preprocessor.java index 36bd1f3..28c7c49 100644 --- a/event-loop/src/main/java/mc/core/events/cachelike/Preprocessor.java +++ b/event-loop/src/main/java/mc/core/events/cachelike/Preprocessor.java @@ -1,5 +1,5 @@ package mc.core.events.cachelike; public @interface Preprocessor { - Class preprocessor(); + int index(); } diff --git a/event-loop/src/main/java/mc/core/events/cachelike/SampleHandler.java b/event-loop/src/main/java/mc/core/events/cachelike/SampleHandler.java new file mode 100644 index 0000000..61241b1 --- /dev/null +++ b/event-loop/src/main/java/mc/core/events/cachelike/SampleHandler.java @@ -0,0 +1,21 @@ +package mc.core.events.cachelike; + +import mc.core.events.LoginEvent; + +public class SampleHandler extends EventHandlerBase { + + public SampleHandler() { + push(new PreprocessorContext() { + @Override + protected void init() { + System.out.println("I am context #0!"); + } + }); + + } + + @Preprocessor(index = 0) // Map constructor #0 to this event handler + public void onLogin(LoginEvent event){ + + } +} From c0341fd273deefed29c237f7964aa0af10c3b34b Mon Sep 17 00:00:00 2001 From: Daniil Date: Wed, 1 Aug 2018 21:49:00 +0700 Subject: [PATCH 16/62] Loop v3 implementation started --- event-loop/build.gradle | 15 +++++ .../java/mc/core/events/BaseEventLoop.java | 2 +- .../java/mc/core/events/EventHandler.java | 4 ++ .../java/mc/core/events/LockableResource.java | 6 ++ .../events/cachelike/EventHandlerBase.java | 11 ---- .../core/events/cachelike/Preprocessor.java | 5 -- .../events/cachelike/PreprocessorContext.java | 17 ------ .../core/events/cachelike/SampleHandler.java | 21 ------- .../mc/core/events/v3/FullAsyncEventLoop.java | 58 +++++++++++++++++++ .../main/java/mc/core/events/v3/Plugin.java | 4 ++ .../java/mc/core/events/v3/QueueManager.java | 4 ++ .../events/v3/RegisteredEventHandler.java | 20 +++++++ 12 files changed, 112 insertions(+), 55 deletions(-) create mode 100644 event-loop/build.gradle create mode 100644 event-loop/src/main/java/mc/core/events/LockableResource.java delete mode 100644 event-loop/src/main/java/mc/core/events/cachelike/EventHandlerBase.java delete mode 100644 event-loop/src/main/java/mc/core/events/cachelike/Preprocessor.java delete mode 100644 event-loop/src/main/java/mc/core/events/cachelike/PreprocessorContext.java delete mode 100644 event-loop/src/main/java/mc/core/events/cachelike/SampleHandler.java create mode 100644 event-loop/src/main/java/mc/core/events/v3/FullAsyncEventLoop.java create mode 100644 event-loop/src/main/java/mc/core/events/v3/Plugin.java create mode 100644 event-loop/src/main/java/mc/core/events/v3/QueueManager.java create mode 100644 event-loop/src/main/java/mc/core/events/v3/RegisteredEventHandler.java diff --git a/event-loop/build.gradle b/event-loop/build.gradle new file mode 100644 index 0000000..0a1c7d0 --- /dev/null +++ b/event-loop/build.gradle @@ -0,0 +1,15 @@ +group 'mc' +version '1.0-SNAPSHOT' + +dependencies { + /* Core */ + compile_excludeCopy project(':core') + + testCompile group: 'org.slf4j', name: 'slf4j-simple', version: '1.6.1' + testCompile group: 'com.carrotsearch', name: 'junit-benchmarks', version: '0.7.0' +} + + +test { + exclude "ru/core/events/*Benchmark.class" +} \ No newline at end of file diff --git a/event-loop/src/main/java/mc/core/events/BaseEventLoop.java b/event-loop/src/main/java/mc/core/events/BaseEventLoop.java index 9aff373..7945d61 100644 --- a/event-loop/src/main/java/mc/core/events/BaseEventLoop.java +++ b/event-loop/src/main/java/mc/core/events/BaseEventLoop.java @@ -13,7 +13,7 @@ public abstract class BaseEventLoop implements EventLoop { return true; if (!Modifier.isPublic(method.getModifiers())) { - log.error("Unable to register {} as an EventHandler. Method must have a 'private' access modifier.", method.toString()); + log.error("Unable to register {} as an EventHandler. Method must have a 'public' access modifier.", method.toString()); return true; } diff --git a/event-loop/src/main/java/mc/core/events/EventHandler.java b/event-loop/src/main/java/mc/core/events/EventHandler.java index 096b52e..0def90c 100644 --- a/event-loop/src/main/java/mc/core/events/EventHandler.java +++ b/event-loop/src/main/java/mc/core/events/EventHandler.java @@ -10,4 +10,8 @@ public @interface EventHandler { EventPriority priority() default EventPriority.NORMAL; boolean ignoreCancelled() default false; + + boolean pluginSynchronize() default true; + + LockableResource[] lock() default {}; } diff --git a/event-loop/src/main/java/mc/core/events/LockableResource.java b/event-loop/src/main/java/mc/core/events/LockableResource.java new file mode 100644 index 0000000..3d8c459 --- /dev/null +++ b/event-loop/src/main/java/mc/core/events/LockableResource.java @@ -0,0 +1,6 @@ +package mc.core.events; + +public enum LockableResource { + PLAYER, + WORLD; +} diff --git a/event-loop/src/main/java/mc/core/events/cachelike/EventHandlerBase.java b/event-loop/src/main/java/mc/core/events/cachelike/EventHandlerBase.java deleted file mode 100644 index b4eecc5..0000000 --- a/event-loop/src/main/java/mc/core/events/cachelike/EventHandlerBase.java +++ /dev/null @@ -1,11 +0,0 @@ -package mc.core.events.cachelike; - -import java.util.List; - -public class EventHandlerBase { - private List contextList; - - protected void push(PreprocessorContext context){ - contextList.add(context); - } -} diff --git a/event-loop/src/main/java/mc/core/events/cachelike/Preprocessor.java b/event-loop/src/main/java/mc/core/events/cachelike/Preprocessor.java deleted file mode 100644 index 28c7c49..0000000 --- a/event-loop/src/main/java/mc/core/events/cachelike/Preprocessor.java +++ /dev/null @@ -1,5 +0,0 @@ -package mc.core.events.cachelike; - -public @interface Preprocessor { - int index(); -} diff --git a/event-loop/src/main/java/mc/core/events/cachelike/PreprocessorContext.java b/event-loop/src/main/java/mc/core/events/cachelike/PreprocessorContext.java deleted file mode 100644 index 99292fc..0000000 --- a/event-loop/src/main/java/mc/core/events/cachelike/PreprocessorContext.java +++ /dev/null @@ -1,17 +0,0 @@ -package mc.core.events.cachelike; - -import com.google.common.base.Function; - -import java.util.ArrayList; -import java.util.List; - -public abstract class PreprocessorContext { - private List> fetchers = new ArrayList<>(); - - protected void push(Function fetcher) { - fetchers.add(fetcher); - } - - protected abstract void init() ; - -} diff --git a/event-loop/src/main/java/mc/core/events/cachelike/SampleHandler.java b/event-loop/src/main/java/mc/core/events/cachelike/SampleHandler.java deleted file mode 100644 index 61241b1..0000000 --- a/event-loop/src/main/java/mc/core/events/cachelike/SampleHandler.java +++ /dev/null @@ -1,21 +0,0 @@ -package mc.core.events.cachelike; - -import mc.core.events.LoginEvent; - -public class SampleHandler extends EventHandlerBase { - - public SampleHandler() { - push(new PreprocessorContext() { - @Override - protected void init() { - System.out.println("I am context #0!"); - } - }); - - } - - @Preprocessor(index = 0) // Map constructor #0 to this event handler - public void onLogin(LoginEvent event){ - - } -} diff --git a/event-loop/src/main/java/mc/core/events/v3/FullAsyncEventLoop.java b/event-loop/src/main/java/mc/core/events/v3/FullAsyncEventLoop.java new file mode 100644 index 0000000..4b01936 --- /dev/null +++ b/event-loop/src/main/java/mc/core/events/v3/FullAsyncEventLoop.java @@ -0,0 +1,58 @@ +package mc.core.events.v3; + +import lombok.extern.slf4j.Slf4j; +import mc.core.events.Event; +import mc.core.events.EventHandler; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.*; + +@Slf4j +public class FullAsyncEventLoop { + Map, List> handlers = new HashMap<>(); + + public void addEventHandler(Plugin plugin, Object object) { + Map candidates = getEventHandlerCandidates(object); + + for (Map.Entry pair : candidates.entrySet()) { + @SuppressWarnings("unchecked") Class eventType = (Class) pair.getKey().getParameterTypes()[0]; + List handlers = this.handlers.computeIfAbsent(eventType, e -> new ArrayList<>()); + handlers.add(new RegisteredEventHandler(plugin, object, pair.getKey(), pair.getValue().lock(), pair.getValue().pluginSynchronize(), pair.getValue().priority().getValue(), pair.getValue().ignoreCancelled())); + handlers.sort(Comparator.comparingInt(RegisteredEventHandler::getPriority)); + } + } + + + + private Map getEventHandlerCandidates(Object object) { + Map candidates; + candidates = new HashMap<>(); + for (Method method : object.getClass().getDeclaredMethods()) { + EventHandler annotation = method.getAnnotation(EventHandler.class); + if (annotation == null) + continue; + + if (!Modifier.isPublic(method.getModifiers())) { + log.error("Unable to register {} as an EventHandler. Method must have a 'public' 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; + } + + candidates.put(method, annotation); + } + return candidates; + } + +} diff --git a/event-loop/src/main/java/mc/core/events/v3/Plugin.java b/event-loop/src/main/java/mc/core/events/v3/Plugin.java new file mode 100644 index 0000000..670bd82 --- /dev/null +++ b/event-loop/src/main/java/mc/core/events/v3/Plugin.java @@ -0,0 +1,4 @@ +package mc.core.events.v3; + +public interface Plugin { +} diff --git a/event-loop/src/main/java/mc/core/events/v3/QueueManager.java b/event-loop/src/main/java/mc/core/events/v3/QueueManager.java new file mode 100644 index 0000000..b395844 --- /dev/null +++ b/event-loop/src/main/java/mc/core/events/v3/QueueManager.java @@ -0,0 +1,4 @@ +package mc.core.events.v3; + +public class QueueManager { +} diff --git a/event-loop/src/main/java/mc/core/events/v3/RegisteredEventHandler.java b/event-loop/src/main/java/mc/core/events/v3/RegisteredEventHandler.java new file mode 100644 index 0000000..ae708e2 --- /dev/null +++ b/event-loop/src/main/java/mc/core/events/v3/RegisteredEventHandler.java @@ -0,0 +1,20 @@ +package mc.core.events.v3; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import mc.core.events.LockableResource; + +import java.lang.reflect.Method; + +@RequiredArgsConstructor +@Getter +public class RegisteredEventHandler { + private final Plugin plugin; + private final Object object; + private final Method method; + private final LockableResource[] lock; + private final boolean pluginSynchronize; + private final int priority; + private final boolean ignoreCancelled; + +} From 4e6f834a7a102614c0f8b3a1ef0f70078386dc2d Mon Sep 17 00:00:00 2001 From: Daniil Date: Fri, 3 Aug 2018 18:12:49 +0700 Subject: [PATCH 17/62] Custom Execution Service for event loop basic implementation --- .../v3/runner/EventExecutorService.java | 62 +++++++++++++++++++ .../core/events/v3/runner/ExecutorThread.java | 33 ++++++++++ .../events/v3/runner/ResourceRunnable.java | 7 +++ .../events/v3/runner/ScheduleStrategy.java | 5 ++ .../ru/core/events/v3/EventExecutorTest.java | 43 +++++++++++++ 5 files changed, 150 insertions(+) create mode 100644 event-loop/src/main/java/mc/core/events/v3/runner/EventExecutorService.java create mode 100644 event-loop/src/main/java/mc/core/events/v3/runner/ExecutorThread.java create mode 100644 event-loop/src/main/java/mc/core/events/v3/runner/ResourceRunnable.java create mode 100644 event-loop/src/main/java/mc/core/events/v3/runner/ScheduleStrategy.java create mode 100644 event-loop/src/test/java/ru/core/events/v3/EventExecutorTest.java diff --git a/event-loop/src/main/java/mc/core/events/v3/runner/EventExecutorService.java b/event-loop/src/main/java/mc/core/events/v3/runner/EventExecutorService.java new file mode 100644 index 0000000..0a5a852 --- /dev/null +++ b/event-loop/src/main/java/mc/core/events/v3/runner/EventExecutorService.java @@ -0,0 +1,62 @@ +package mc.core.events.v3.runner; + +import sun.plugin.dom.exception.InvalidStateException; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; + +public class EventExecutorService { + private BlockingQueue queue = new ArrayBlockingQueue<>(100); + private ScheduleStrategy strategy = new DefaultScheduleStrategy(); + private Set executorThreads = new HashSet<>(); + private int threadCount; + + public EventExecutorService(int threadCount) { + this.threadCount = threadCount; + } + + public void start() { + if (executorThreads.size() > 0) + throw new InvalidStateException("This executor service was already started."); + + for (int i = 0; i < threadCount; i++) { + Thread thread = new ExecutorThread("Event Loop #" + i, this); + executorThreads.add(thread); + thread.start(); + } + } + + public void stop() { + if (executorThreads.size() == 0) + throw new InvalidStateException("This executor service was not initialized yet."); + + Iterator iterator = executorThreads.iterator(); + while (iterator.hasNext()) { + Thread thread = iterator.next(); + thread.interrupt(); + iterator.remove(); + } + } + + public void addTask(ResourceRunnable task) { + if (Thread.currentThread() instanceof ExecutorThread) { + // TODO: Do we really need instant execution? + ((ExecutorThread) Thread.currentThread()).executeTask(task); + } else + queue.offer(task); + } + + + public ScheduleStrategy getStrategy() { + return strategy; + } + + private class DefaultScheduleStrategy implements ScheduleStrategy { + public ResourceRunnable getTask() throws InterruptedException { + return queue.take(); + } + } +} diff --git a/event-loop/src/main/java/mc/core/events/v3/runner/ExecutorThread.java b/event-loop/src/main/java/mc/core/events/v3/runner/ExecutorThread.java new file mode 100644 index 0000000..271ea7f --- /dev/null +++ b/event-loop/src/main/java/mc/core/events/v3/runner/ExecutorThread.java @@ -0,0 +1,33 @@ +package mc.core.events.v3.runner; + +public class ExecutorThread extends Thread { + private EventExecutorService service; + + public ExecutorThread(String name, EventExecutorService service) { + super(name); + this.service = service; + } + + @Override + public void run() { + while (!isInterrupted() && isAlive()) { + ResourceRunnable runnable; + try { + runnable = service.getStrategy().getTask(); + } catch (InterruptedException e) { + return; + } + + executeTask(runnable); + } + } + + void executeTask(ResourceRunnable runnable) { + runnable.lock(); + try { + runnable.run(); + } finally { + runnable.unlock(); + } + } +} diff --git a/event-loop/src/main/java/mc/core/events/v3/runner/ResourceRunnable.java b/event-loop/src/main/java/mc/core/events/v3/runner/ResourceRunnable.java new file mode 100644 index 0000000..9fd30f8 --- /dev/null +++ b/event-loop/src/main/java/mc/core/events/v3/runner/ResourceRunnable.java @@ -0,0 +1,7 @@ +package mc.core.events.v3.runner; + +public interface ResourceRunnable extends Runnable { + void lock(); + + void unlock(); +} diff --git a/event-loop/src/main/java/mc/core/events/v3/runner/ScheduleStrategy.java b/event-loop/src/main/java/mc/core/events/v3/runner/ScheduleStrategy.java new file mode 100644 index 0000000..f901d85 --- /dev/null +++ b/event-loop/src/main/java/mc/core/events/v3/runner/ScheduleStrategy.java @@ -0,0 +1,5 @@ +package mc.core.events.v3.runner; + +public interface ScheduleStrategy { + ResourceRunnable getTask() throws InterruptedException; +} diff --git a/event-loop/src/test/java/ru/core/events/v3/EventExecutorTest.java b/event-loop/src/test/java/ru/core/events/v3/EventExecutorTest.java new file mode 100644 index 0000000..a478f49 --- /dev/null +++ b/event-loop/src/test/java/ru/core/events/v3/EventExecutorTest.java @@ -0,0 +1,43 @@ +package ru.core.events.v3; + +import mc.core.events.v3.runner.EventExecutorService; +import mc.core.events.v3.runner.ResourceRunnable; +import org.junit.Assert; +import org.junit.Test; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +public class EventExecutorTest { + + @Test + public void basicTest() throws InterruptedException { + AtomicBoolean testVariable = new AtomicBoolean(false); + CountDownLatch latch = new CountDownLatch(1); + EventExecutorService service = new EventExecutorService(1); + service.start(); + service.addTask(new ResourceRunnable() { + @Override + public void lock() { + + } + + @Override + public void unlock() { + + } + + @Override + public void run() { + testVariable.set(true); + latch.countDown(); + } + }); + + latch.await(1, TimeUnit.SECONDS); + service.stop(); + Assert.assertTrue("Scheduled task was not executed", testVariable.get()); + + } +} From 969b2bbb447e343e746baf1b05526a1c97ceecf9 Mon Sep 17 00:00:00 2001 From: Daniil Date: Fri, 3 Aug 2018 20:09:46 +0700 Subject: [PATCH 18/62] Runnable update --- .../mc/core/events/v3/runner/ExecutorThread.java | 1 + .../mc/core/events/v3/runner/ResourceRunnable.java | 12 ++++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/event-loop/src/main/java/mc/core/events/v3/runner/ExecutorThread.java b/event-loop/src/main/java/mc/core/events/v3/runner/ExecutorThread.java index 271ea7f..3c18421 100644 --- a/event-loop/src/main/java/mc/core/events/v3/runner/ExecutorThread.java +++ b/event-loop/src/main/java/mc/core/events/v3/runner/ExecutorThread.java @@ -29,5 +29,6 @@ public class ExecutorThread extends Thread { } finally { runnable.unlock(); } + runnable.after(); } } diff --git a/event-loop/src/main/java/mc/core/events/v3/runner/ResourceRunnable.java b/event-loop/src/main/java/mc/core/events/v3/runner/ResourceRunnable.java index 9fd30f8..3c452ac 100644 --- a/event-loop/src/main/java/mc/core/events/v3/runner/ResourceRunnable.java +++ b/event-loop/src/main/java/mc/core/events/v3/runner/ResourceRunnable.java @@ -1,7 +1,15 @@ package mc.core.events.v3.runner; public interface ResourceRunnable extends Runnable { - void lock(); + default void lock() { - void unlock(); + } + + default void unlock() { + + } + + default void after() { + + } } From c5c5acee8f5a02c90965cd2f14fe6f67301f2a50 Mon Sep 17 00:00:00 2001 From: Daniil Date: Fri, 3 Aug 2018 20:59:47 +0700 Subject: [PATCH 19/62] First working implementation of fully async event loop --- .../java/mc/core/events/v3/EventPipeline.java | 65 +++++++ .../mc/core/events/v3/EventQueueOwner.java | 4 + .../mc/core/events/v3/FullAsyncEventLoop.java | 56 +++++- .../java/mc/core/events/v3/QueueManager.java | 4 - .../java/ru/core/events/v3/EventLoopTest.java | 171 ++++++++++++++++++ 5 files changed, 295 insertions(+), 5 deletions(-) create mode 100644 event-loop/src/main/java/mc/core/events/v3/EventPipeline.java create mode 100644 event-loop/src/main/java/mc/core/events/v3/EventQueueOwner.java delete mode 100644 event-loop/src/main/java/mc/core/events/v3/QueueManager.java create mode 100644 event-loop/src/test/java/ru/core/events/v3/EventLoopTest.java diff --git a/event-loop/src/main/java/mc/core/events/v3/EventPipeline.java b/event-loop/src/main/java/mc/core/events/v3/EventPipeline.java new file mode 100644 index 0000000..d0773a3 --- /dev/null +++ b/event-loop/src/main/java/mc/core/events/v3/EventPipeline.java @@ -0,0 +1,65 @@ +package mc.core.events.v3; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import mc.core.events.Event; +import mc.core.events.v3.runner.EventExecutorService; +import mc.core.events.v3.runner.ResourceRunnable; + +import java.lang.reflect.InvocationTargetException; +import java.util.List; + +@RequiredArgsConstructor +@Getter +@Slf4j +public class EventPipeline { + private final List handlers; + private final FullAsyncEventLoop manager; + private final Event event; + private final EventQueueOwner owner; + private int currentIndex = 0; + @Setter + private PipelineState state = PipelineState.IDLE; + + public void next(EventExecutorService service) { + if (state == PipelineState.IDLE) { + state = PipelineState.WORKING; + } + if (currentIndex >= handlers.size() && state == PipelineState.WORKING) { + state = PipelineState.FINISHED; + manager.update(owner); + return; + } + + if (state == PipelineState.FINISHED) { + throw new IllegalStateException("Attempted to call next step on a FINISHED pipeline"); + } + + RegisteredEventHandler handler = handlers.get(currentIndex); + service.addTask(new ResourceRunnable() { + @Override + public void run() { + // TODO: Do we really need to process this in an async thread? + if (!event.isCanceled() || !handler.isIgnoreCancelled()) { + try { + handler.getMethod().invoke(handler.getObject(), event); + } catch (IllegalAccessException | InvocationTargetException e) { + log.error("Unable to dispatch event " + event.getClass().getSimpleName() + " to handler " + event.getClass().getName(), e); + } + } + } + + @Override + public void after() { + currentIndex++; + next(service); + } + }); + } + + public enum PipelineState { + IDLE, WORKING, FINISHED + } +} diff --git a/event-loop/src/main/java/mc/core/events/v3/EventQueueOwner.java b/event-loop/src/main/java/mc/core/events/v3/EventQueueOwner.java new file mode 100644 index 0000000..996b159 --- /dev/null +++ b/event-loop/src/main/java/mc/core/events/v3/EventQueueOwner.java @@ -0,0 +1,4 @@ +package mc.core.events.v3; + +public interface EventQueueOwner { +} diff --git a/event-loop/src/main/java/mc/core/events/v3/FullAsyncEventLoop.java b/event-loop/src/main/java/mc/core/events/v3/FullAsyncEventLoop.java index 4b01936..4001869 100644 --- a/event-loop/src/main/java/mc/core/events/v3/FullAsyncEventLoop.java +++ b/event-loop/src/main/java/mc/core/events/v3/FullAsyncEventLoop.java @@ -1,16 +1,25 @@ package mc.core.events.v3; +import lombok.Setter; import lombok.extern.slf4j.Slf4j; import mc.core.events.Event; import mc.core.events.EventHandler; +import mc.core.events.v3.runner.EventExecutorService; +import org.springframework.beans.factory.annotation.Autowired; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; @Slf4j public class FullAsyncEventLoop { Map, List> handlers = new HashMap<>(); + // Item leaves this queue only when EventPipeline is fully executed + private Map> eventQueue = new ConcurrentHashMap<>(); + @Autowired + @Setter + private EventExecutorService eventExecutorService; public void addEventHandler(Plugin plugin, Object object) { Map candidates = getEventHandlerCandidates(object); @@ -23,7 +32,9 @@ public class FullAsyncEventLoop { } } - + public List getPipelineForEvent(Event event) { + return handlers.get(event.getClass()); + } private Map getEventHandlerCandidates(Object object) { Map candidates; @@ -50,9 +61,52 @@ public class FullAsyncEventLoop { continue; } + method.setAccessible(true); candidates.put(method, annotation); } return candidates; } + public void asyncFireEvent(EventQueueOwner owner, Event event) { + List handlers = getPipelineForEvent(event); + if (handlers == null) + return; + + Queue queue = eventQueue.computeIfAbsent(owner, s -> new ArrayDeque<>()); + queue.add(new EventPipeline(handlers, this, event, owner)); + update(owner); + } + + /** + * Updates queue state for a given owner: + *

+ * - Removes first element of a queue if it is marked as FINISHED + * - Starts executing first pipeline from the queue if it is marked with IDLE + * + * @param owner queue owner + */ + public synchronized void update(EventQueueOwner owner) { + if (!eventQueue.containsKey(owner)) { + log.warn("Unable to update pipeline executor: unable to find queue"); + return; + } + Queue queue = eventQueue.get(owner); + if (queue.isEmpty()) { + log.warn("Unable to update pipeline executor: queue is empty"); + return; + } + + if (queue.peek().getState() == EventPipeline.PipelineState.FINISHED) { + // TODO: Post-event callback? + queue.poll(); + } + + EventPipeline pipeline; + if ((pipeline = queue.peek()) != null + && pipeline.getState() == EventPipeline.PipelineState.IDLE) { + pipeline.next(eventExecutorService); + } + } + + } diff --git a/event-loop/src/main/java/mc/core/events/v3/QueueManager.java b/event-loop/src/main/java/mc/core/events/v3/QueueManager.java deleted file mode 100644 index b395844..0000000 --- a/event-loop/src/main/java/mc/core/events/v3/QueueManager.java +++ /dev/null @@ -1,4 +0,0 @@ -package mc.core.events.v3; - -public class QueueManager { -} diff --git a/event-loop/src/test/java/ru/core/events/v3/EventLoopTest.java b/event-loop/src/test/java/ru/core/events/v3/EventLoopTest.java new file mode 100644 index 0000000..8dc75d9 --- /dev/null +++ b/event-loop/src/test/java/ru/core/events/v3/EventLoopTest.java @@ -0,0 +1,171 @@ +package ru.core.events.v3; + +import mc.core.events.EventHandler; +import mc.core.events.EventPriority; +import mc.core.events.LoginEvent; +import mc.core.events.v3.EventQueueOwner; +import mc.core.events.v3.FullAsyncEventLoop; +import mc.core.events.v3.Plugin; +import mc.core.events.v3.runner.EventExecutorService; +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +@SuppressWarnings("Duplicates") +public class EventLoopTest { + + @Test + public void basicTest() throws InterruptedException { + Plugin plugin = new Plugin() { + }; + + EventQueueOwner queueOwner = new EventQueueOwner() { + }; + + + CountDownLatch latch = new CountDownLatch(1); + FullAsyncEventLoop eventLoop = new FullAsyncEventLoop(); + eventLoop.addEventHandler(plugin, new Object() { + @EventHandler + public void onLoginEvent(LoginEvent event) { + + latch.countDown(); + } + }); + + EventExecutorService service = new EventExecutorService(1); + service.start(); + + eventLoop.setEventExecutorService(service); + eventLoop.asyncFireEvent(queueOwner, new LoginEvent(null)); + + latch.await(1, TimeUnit.SECONDS); + Assert.assertEquals("Event was not called", 0, latch.getCount()); + } + + @Test + public void consecutiveExecutionTest() throws InterruptedException { + Plugin plugin = new Plugin() { + }; + + EventQueueOwner queueOwner = new EventQueueOwner() { + }; + + + CountDownLatch latch = new CountDownLatch(2); + FullAsyncEventLoop eventLoop = new FullAsyncEventLoop(); + eventLoop.addEventHandler(plugin, new Object() { + @EventHandler + public void onLoginEvent(LoginEvent event) { + + latch.countDown(); + } + }); + + EventExecutorService service = new EventExecutorService(1); + service.start(); + + eventLoop.setEventExecutorService(service); + + eventLoop.asyncFireEvent(queueOwner, new LoginEvent(null)); + eventLoop.asyncFireEvent(queueOwner, new LoginEvent(null)); + + latch.await(1, TimeUnit.SECONDS); + Assert.assertEquals("Event was not called", 0, latch.getCount()); + } + + @Test + public void prioritySystemTest() throws InterruptedException { + Plugin plugin = new Plugin() { + }; + + EventQueueOwner queueOwner = new EventQueueOwner() { + }; + + + CountDownLatch latch = new CountDownLatch(3); + FullAsyncEventLoop eventLoop = new FullAsyncEventLoop(); + List priorities = new ArrayList<>(3); + + eventLoop.addEventHandler(plugin, new Object() { + @EventHandler(priority = EventPriority.NORMAL) + public void login1(LoginEvent event) { + priorities.add(0); + latch.countDown(); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void login2(LoginEvent event) { + priorities.add(1); + latch.countDown(); + } + + @EventHandler(priority = EventPriority.LOWEST) + public void login3(LoginEvent event) { + priorities.add(2); + latch.countDown(); + } + }); + + EventExecutorService service = new EventExecutorService(1); + service.start(); + + eventLoop.setEventExecutorService(service); + + eventLoop.asyncFireEvent(queueOwner, new LoginEvent(null)); + + latch.await(1, TimeUnit.SECONDS); + Assert.assertEquals("Incorrect call sequence", "[2, 0, 1]", priorities.toString()); + } + + @Test + public void ignoreCancelledTest() throws InterruptedException { + Plugin plugin = new Plugin() { + }; + + EventQueueOwner queueOwner = new EventQueueOwner() { + }; + + + CountDownLatch latch = new CountDownLatch(1); + FullAsyncEventLoop eventLoop = new FullAsyncEventLoop(); + List priorities = new ArrayList<>(2); + + eventLoop.addEventHandler(plugin, new Object() { + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void login1(LoginEvent event) { + priorities.add(0); + event.setCanceled(true); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void login2(LoginEvent event) { + priorities.add(1); + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void login3(LoginEvent event) { + priorities.add(2); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void monitor(LoginEvent event) { + latch.countDown(); + } + }); + + EventExecutorService service = new EventExecutorService(1); + service.start(); + + eventLoop.setEventExecutorService(service); + + eventLoop.asyncFireEvent(queueOwner, new LoginEvent(null)); + + latch.await(1, TimeUnit.SECONDS); + Assert.assertEquals("Incorrect call sequence", "[2, 0]", priorities.toString()); + } +} From 38f69c3dfa90bf802e0168ce75ab05ffa75875bd Mon Sep 17 00:00:00 2001 From: Daniil Date: Fri, 3 Aug 2018 21:03:32 +0700 Subject: [PATCH 20/62] Completed some TODOs --- .../java/mc/core/events/v3/EventPipeline.java | 26 ++++++++++--------- .../mc/core/events/v3/FullAsyncEventLoop.java | 1 - .../v3/runner/EventExecutorService.java | 4 +-- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/event-loop/src/main/java/mc/core/events/v3/EventPipeline.java b/event-loop/src/main/java/mc/core/events/v3/EventPipeline.java index d0773a3..f2aaa68 100644 --- a/event-loop/src/main/java/mc/core/events/v3/EventPipeline.java +++ b/event-loop/src/main/java/mc/core/events/v3/EventPipeline.java @@ -38,25 +38,27 @@ public class EventPipeline { } RegisteredEventHandler handler = handlers.get(currentIndex); - service.addTask(new ResourceRunnable() { - @Override - public void run() { - // TODO: Do we really need to process this in an async thread? - if (!event.isCanceled() || !handler.isIgnoreCancelled()) { + if (!event.isCanceled() || !handler.isIgnoreCancelled()) { + service.addTask(new ResourceRunnable() { + @Override + public void run() { try { handler.getMethod().invoke(handler.getObject(), event); } catch (IllegalAccessException | InvocationTargetException e) { log.error("Unable to dispatch event " + event.getClass().getSimpleName() + " to handler " + event.getClass().getName(), e); } } - } - @Override - public void after() { - currentIndex++; - next(service); - } - }); + @Override + public void after() { + currentIndex++; + next(service); + } + }); + } else { + currentIndex++; + next(service); + } } public enum PipelineState { diff --git a/event-loop/src/main/java/mc/core/events/v3/FullAsyncEventLoop.java b/event-loop/src/main/java/mc/core/events/v3/FullAsyncEventLoop.java index 4001869..a88e2e9 100644 --- a/event-loop/src/main/java/mc/core/events/v3/FullAsyncEventLoop.java +++ b/event-loop/src/main/java/mc/core/events/v3/FullAsyncEventLoop.java @@ -97,7 +97,6 @@ public class FullAsyncEventLoop { } if (queue.peek().getState() == EventPipeline.PipelineState.FINISHED) { - // TODO: Post-event callback? queue.poll(); } diff --git a/event-loop/src/main/java/mc/core/events/v3/runner/EventExecutorService.java b/event-loop/src/main/java/mc/core/events/v3/runner/EventExecutorService.java index 0a5a852..93bbee7 100644 --- a/event-loop/src/main/java/mc/core/events/v3/runner/EventExecutorService.java +++ b/event-loop/src/main/java/mc/core/events/v3/runner/EventExecutorService.java @@ -9,6 +9,7 @@ import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; public class EventExecutorService { + private static final boolean WORKER_INSTANT_EXECUTE = false; private BlockingQueue queue = new ArrayBlockingQueue<>(100); private ScheduleStrategy strategy = new DefaultScheduleStrategy(); private Set executorThreads = new HashSet<>(); @@ -42,8 +43,7 @@ public class EventExecutorService { } public void addTask(ResourceRunnable task) { - if (Thread.currentThread() instanceof ExecutorThread) { - // TODO: Do we really need instant execution? + if (WORKER_INSTANT_EXECUTE && Thread.currentThread() instanceof ExecutorThread) { ((ExecutorThread) Thread.currentThread()).executeTask(task); } else queue.offer(task); From 227deac6f07921fd28bb719530b5b0207e271b42 Mon Sep 17 00:00:00 2001 From: Daniil Date: Fri, 3 Aug 2018 21:14:28 +0700 Subject: [PATCH 21/62] Prefab for actual resource lock --- .../events/v3/lock/CustomReentrantLock.java | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 event-loop/src/main/java/mc/core/events/v3/lock/CustomReentrantLock.java diff --git a/event-loop/src/main/java/mc/core/events/v3/lock/CustomReentrantLock.java b/event-loop/src/main/java/mc/core/events/v3/lock/CustomReentrantLock.java new file mode 100644 index 0000000..35bbbc2 --- /dev/null +++ b/event-loop/src/main/java/mc/core/events/v3/lock/CustomReentrantLock.java @@ -0,0 +1,43 @@ +package mc.core.events.v3.lock; + +import mc.core.events.v3.runner.ExecutorThread; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; + +public class CustomReentrantLock extends ReentrantLock { + private void checkThread() { + if (!(Thread.currentThread() instanceof ExecutorThread)) + throw new RuntimeException("Unable to obtain this resource outside Async Executor"); + } + + @Override + public void lock() { + checkThread(); + super.lock(); + } + + @Override + public void lockInterruptibly() throws InterruptedException { + checkThread(); + super.lockInterruptibly(); + } + + @Override + public boolean tryLock() { + checkThread(); + return super.tryLock(); + } + + @Override + public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { + checkThread(); + return super.tryLock(timeout, unit); + } + + @Override + public void unlock() { + checkThread(); + super.unlock(); + } +} From 8f912f7e40a4255b43b49b3a3ff8b662edd7fc6f Mon Sep 17 00:00:00 2001 From: Daniil Date: Fri, 3 Aug 2018 21:16:24 +0700 Subject: [PATCH 22/62] Removed legacy event loops --- .../java/mc/core/events/BaseEventLoop.java | 34 ---- .../main/java/mc/core/events/EventLoop.java | 7 - .../java/mc/core/events/SimpleEventLoop.java | 62 -------- .../core/events/async/AdvancedEventLoop.java | 145 ------------------ .../mc/core/events/async/AsyncEventLoop.java | 81 ---------- .../java/mc/core/events/async/EventBatch.java | 22 --- .../core/events/async/EventPreprocessor.java | 12 -- .../mc/core/events/async/PreprocessTask.java | 32 ---- .../core/events/AsyncEventLoopBenchmark.java | 47 ------ .../ru/core/events/AsyncEventLoopTest.java | 22 --- .../core/events/SimpleEventLoopBenchmark.java | 30 ---- .../ru/core/events/SimpleEventLoopTest.java | 22 --- .../events/handlers/AsyncEventHandler.java | 29 ---- .../events/handlers/FastEventHandler.java | 11 -- .../HelloWorldSimpleEventHandler.java | 11 -- .../events/handlers/SampleEventHandler.java | 20 --- 16 files changed, 587 deletions(-) delete mode 100644 event-loop/src/main/java/mc/core/events/BaseEventLoop.java delete mode 100644 event-loop/src/main/java/mc/core/events/EventLoop.java delete mode 100644 event-loop/src/main/java/mc/core/events/SimpleEventLoop.java delete mode 100644 event-loop/src/main/java/mc/core/events/async/AdvancedEventLoop.java delete mode 100644 event-loop/src/main/java/mc/core/events/async/AsyncEventLoop.java delete mode 100644 event-loop/src/main/java/mc/core/events/async/EventBatch.java delete mode 100644 event-loop/src/main/java/mc/core/events/async/EventPreprocessor.java delete mode 100644 event-loop/src/main/java/mc/core/events/async/PreprocessTask.java delete mode 100644 event-loop/src/test/java/ru/core/events/AsyncEventLoopBenchmark.java delete mode 100644 event-loop/src/test/java/ru/core/events/AsyncEventLoopTest.java delete mode 100644 event-loop/src/test/java/ru/core/events/SimpleEventLoopBenchmark.java delete mode 100644 event-loop/src/test/java/ru/core/events/SimpleEventLoopTest.java delete mode 100644 event-loop/src/test/java/ru/core/events/handlers/AsyncEventHandler.java delete mode 100644 event-loop/src/test/java/ru/core/events/handlers/FastEventHandler.java delete mode 100644 event-loop/src/test/java/ru/core/events/handlers/HelloWorldSimpleEventHandler.java delete mode 100644 event-loop/src/test/java/ru/core/events/handlers/SampleEventHandler.java diff --git a/event-loop/src/main/java/mc/core/events/BaseEventLoop.java b/event-loop/src/main/java/mc/core/events/BaseEventLoop.java deleted file mode 100644 index 7945d61..0000000 --- a/event-loop/src/main/java/mc/core/events/BaseEventLoop.java +++ /dev/null @@ -1,34 +0,0 @@ -package mc.core.events; - -import lombok.extern.slf4j.Slf4j; - -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; - -@Slf4j -public abstract class BaseEventLoop implements EventLoop { - - protected boolean notValidEventHandler(EventHandler annotation, Method method) { - if (annotation == null) - return true; - - if (!Modifier.isPublic(method.getModifiers())) { - log.error("Unable to register {} as an EventHandler. Method must have a 'public' access modifier.", method.toString()); - return true; - - } - - if (method.getParameterCount() != 1) { - log.error("Unable to register {} as an EventHandler. Method must have exactly one argument.", method.toString()); - return true; - } - - 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()); - return true; - } - - return 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 deleted file mode 100644 index 63fa506..0000000 --- a/event-loop/src/main/java/mc/core/events/EventLoop.java +++ /dev/null @@ -1,7 +0,0 @@ -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/SimpleEventLoop.java b/event-loop/src/main/java/mc/core/events/SimpleEventLoop.java deleted file mode 100644 index d93632e..0000000 --- a/event-loop/src/main/java/mc/core/events/SimpleEventLoop.java +++ /dev/null @@ -1,62 +0,0 @@ -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.util.*; - -@Slf4j -public class SimpleEventLoop extends BaseEventLoop { - private Map, List> handlers = new HashMap<>(); - - public SimpleEventLoop() { - log.warn("Warning! SimpleEventLoop doesn't support EventPreprocessors and DI. Code annotated @EventProcessor will not be executed at all."); - } - - @Override - public void callEvent(Event event) { - Class eventType = event.getClass(); - if (handlers.containsKey(eventType)) { - for (ExecutorLink link : handlers.get(eventType)) { - if (link.isIgnoreCancelled() && event.isCanceled()) - continue; - 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 (notValidEventHandler(annotation, method)) - continue; - - @SuppressWarnings("unchecked") Class eventType = (Class) method.getParameterTypes()[0]; - - List eventHandlers = handlers.computeIfAbsent(eventType, s -> new ArrayList<>()); - eventHandlers.add(new SimpleEventLoop.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/main/java/mc/core/events/async/AdvancedEventLoop.java b/event-loop/src/main/java/mc/core/events/async/AdvancedEventLoop.java deleted file mode 100644 index 9b8a4e2..0000000 --- a/event-loop/src/main/java/mc/core/events/async/AdvancedEventLoop.java +++ /dev/null @@ -1,145 +0,0 @@ -package mc.core.events.async; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import mc.core.events.BaseEventLoop; -import mc.core.events.Event; -import mc.core.events.EventHandler; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.*; - -@SuppressWarnings("Duplicates") -@Slf4j -public class AdvancedEventLoop extends BaseEventLoop { - 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)) { - if (link.isIgnoreCancelled() && event.isCanceled()) - continue; - - Object preprocessResult = null; - if (link.getPreprocessMethod() != null) { - try { - preprocessResult = link.getPreprocessMethod().invoke(link.object, event); - } catch (IllegalAccessException | InvocationTargetException e) { - log.error("Exception caught while attempting to run event preprocessing for {}.", eventType.getSimpleName(), e); - } - } - - try { - if (link.getResultInjection() != null) - link.getMethod().invoke(link.object, event, preprocessResult); - else - 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) { - Map preprocessors = new HashMap<>(); - - for (Method method : object.getClass().getDeclaredMethods()) { - EventPreprocessor annotation = method.getAnnotation(EventPreprocessor.class); - if (annotation == null) - continue; - - if (!Modifier.isPublic(method.getModifiers())) { - log.error("Unable to register {} as an EventPreprocessor. Method must have a 'private' access modifier.", method.toString()); - continue; - - } - - if (method.getParameterCount() != 1) { - log.error("Unable to register {} as an EventPreprocessor. 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 EventPreprocessor. First parameter type must implement 'Event' interface.", method.toString()); - continue; - } - - if (preprocessors.containsKey(annotation.methodName())) - log.warn("Attempted to register multiple EventPreprocessors for the event {}. Please note, that only the last one will be registered.", annotation.methodName()); - preprocessors.put(annotation.methodName() + ":" + firstParamType.getSimpleName(), method); - } - - - for (Method method : object.getClass().getDeclaredMethods()) { - EventHandler annotation = method.getAnnotation(EventHandler.class); - - if (annotation == null) - continue; - - 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() == 0) { - log.error("Unable to register {} as an EventHandler. Method must at least 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) method.getParameterTypes()[0]; - - Method preprocessMethod = preprocessors.get(method.getName() + ":" + eventType.getSimpleName()); - Class resultInjection = null; - if (preprocessMethod.getReturnType() != void.class && method.getParameterCount() == 2 && preprocessMethod.getReturnType().equals(method.getParameterTypes()[1])) - resultInjection = preprocessMethod.getReturnType(); - - if (resultInjection == null && method.getParameterCount() > 1) { - log.error("Unable to register {} as an EventHandler. Method has {} arguments, but no EventPreprocessors found to provide DI for type {}", method.toString(), method.getParameterCount(), method.getParameterTypes()[1].toString()); - continue; - } - - if (resultInjection == null && preprocessMethod.getReturnType() != void.class) { - log.warn("DI registration for EventHandler {} failed. Injection target not found", method.toString()); - } - - List eventHandlers = handlers.computeIfAbsent(eventType, s -> new ArrayList<>()); - eventHandlers.add(new ExecutorLink(annotation.priority().getValue(), annotation.ignoreCancelled(), method, object, preprocessMethod, resultInjection)); - eventHandlers.sort(Comparator.comparingInt(o -> o.priority)); - - preprocessors.remove(method.getName() + ":" + eventType.getSimpleName()); - } - - for (Map.Entry preprocessor : preprocessors.entrySet()) - log.error("EventPreprocessor ({}) missing target: unable to find target method ({})", preprocessor.getValue(), preprocessor.getKey()); - - } - - /** - * This class describes - */ - @RequiredArgsConstructor - @Getter - static class ExecutorLink { - private final int priority; - private final boolean ignoreCancelled; - private final Method method; - private final Object object; - private final Method preprocessMethod; - private final Class resultInjection; - } -} - diff --git a/event-loop/src/main/java/mc/core/events/async/AsyncEventLoop.java b/event-loop/src/main/java/mc/core/events/async/AsyncEventLoop.java deleted file mode 100644 index 3145785..0000000 --- a/event-loop/src/main/java/mc/core/events/async/AsyncEventLoop.java +++ /dev/null @@ -1,81 +0,0 @@ -package mc.core.events.async; - -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import mc.core.events.Event; - -import java.lang.reflect.InvocationTargetException; -import java.util.List; -import java.util.concurrent.*; - -@Slf4j -public class AsyncEventLoop extends AdvancedEventLoop { - private static final double emaPeriod = (2D / (50D + 1D)); - private final ExecutorService preEventExecutor = new ThreadPoolExecutor(2, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<>()); - @Getter - private double avgOverhead = 0; - - @Override - public void callEvent(Event event) { - long wholeMethodBenchmark = System.nanoTime(); - long asyncExecutionWaitTime = 0, syncExecutionTime = 0, tempTime; - Class eventType = event.getClass(); - - if (handlers.containsKey(eventType)) { - // Create inter-thread state - List handlerList = handlers.get(eventType); - EventBatch eventBatch = new EventBatch(handlerList.size()); - CountDownLatch latch = new CountDownLatch(handlerList.size()); - - // Submit all defined preprocessing methods as async tasks - for (int i = 0; i < handlerList.size(); i++) { - if (handlerList.get(i).getPreprocessMethod() == null) { - // We have already "allocated" a space for this task in CountDownLatch, - // but since there is no actual preprocess method defined for this handler - // we just skip it by ticking the latch down manually - latch.countDown(); - } else { - preEventExecutor.submit(new PreprocessTask(i, eventBatch, latch, handlerList.get(i), event)); - } - } - - // Await for them to complete - tempTime = System.nanoTime(); - try { - latch.await(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - asyncExecutionWaitTime = System.nanoTime() - tempTime; - - // Synchronously invoke EventHandlers with - // data obtained from EventBatch - for (int i = 0; i < handlerList.size(); i++) { - ExecutorLink link = handlerList.get(i); - if (!link.isIgnoreCancelled() || !event.isCanceled()) { - try { - tempTime = System.nanoTime(); - if (link.getResultInjection() != null) - link.getMethod().invoke(link.getObject(), event, eventBatch.getInjectionObject(i)); - else - link.getMethod().invoke(link.getObject(), event); - syncExecutionTime += System.nanoTime() - tempTime; - } catch (IllegalAccessException | InvocationTargetException e) { - log.error("Exception caught while attempting to dispatch {}.", eventType.getSimpleName(), e); - } - } - } - } - - wholeMethodBenchmark = System.nanoTime() - wholeMethodBenchmark; - long overhead = wholeMethodBenchmark - asyncExecutionWaitTime - syncExecutionTime; - - // Now we calculate exponential moving average of - // overhead timings - if (avgOverhead == 0) - avgOverhead = overhead; - else - avgOverhead = (overhead - avgOverhead) * emaPeriod + avgOverhead; - } -} - diff --git a/event-loop/src/main/java/mc/core/events/async/EventBatch.java b/event-loop/src/main/java/mc/core/events/async/EventBatch.java deleted file mode 100644 index 7a6d35d..0000000 --- a/event-loop/src/main/java/mc/core/events/async/EventBatch.java +++ /dev/null @@ -1,22 +0,0 @@ -package mc.core.events.async; - -/** - * Stores state to pass from async executors to sync - * - * TODO: Change name, misleading - */ -public class EventBatch { - private Object[] returnInject; - - public EventBatch(int size) { - this.returnInject = new Object[size]; - } - - public Object getInjectionObject(int id) { - return returnInject[id]; - } - - public void setInjectionObject(int id, Object value) { - returnInject[id] = value; - } -} diff --git a/event-loop/src/main/java/mc/core/events/async/EventPreprocessor.java b/event-loop/src/main/java/mc/core/events/async/EventPreprocessor.java deleted file mode 100644 index 107628a..0000000 --- a/event-loop/src/main/java/mc/core/events/async/EventPreprocessor.java +++ /dev/null @@ -1,12 +0,0 @@ -package mc.core.events.async; - - -import java.lang.annotation.*; - -@Documented -@Target(ElementType.METHOD) -@Inherited -@Retention(RetentionPolicy.RUNTIME) -public @interface EventPreprocessor { - String methodName(); -} diff --git a/event-loop/src/main/java/mc/core/events/async/PreprocessTask.java b/event-loop/src/main/java/mc/core/events/async/PreprocessTask.java deleted file mode 100644 index e38a8c3..0000000 --- a/event-loop/src/main/java/mc/core/events/async/PreprocessTask.java +++ /dev/null @@ -1,32 +0,0 @@ -package mc.core.events.async; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import mc.core.events.Event; - -import java.lang.reflect.InvocationTargetException; -import java.util.concurrent.CountDownLatch; - -@RequiredArgsConstructor -@Slf4j -public class PreprocessTask implements Runnable { - private final int id; - private final EventBatch eventBatch; - private final CountDownLatch latch; - private final AsyncEventLoop.ExecutorLink link; - private final Event event; - - @Override - public void run() { - try { - Object result = link.getPreprocessMethod().invoke(link.getObject(), event); - if (result != null) - eventBatch.setInjectionObject(id, result); - } catch (IllegalAccessException | InvocationTargetException e) { - log.error("Exception caught while attempting to run event preprocessing.", e); - } finally { - latch.countDown(); - } - - } -} diff --git a/event-loop/src/test/java/ru/core/events/AsyncEventLoopBenchmark.java b/event-loop/src/test/java/ru/core/events/AsyncEventLoopBenchmark.java deleted file mode 100644 index 193fc6f..0000000 --- a/event-loop/src/test/java/ru/core/events/AsyncEventLoopBenchmark.java +++ /dev/null @@ -1,47 +0,0 @@ -package ru.core.events; - -import com.carrotsearch.junitbenchmarks.AbstractBenchmark; -import com.carrotsearch.junitbenchmarks.BenchmarkOptions; -import mc.core.events.LoginEvent; -import mc.core.events.async.AdvancedEventLoop; -import mc.core.events.async.AsyncEventLoop; -import org.junit.BeforeClass; -import org.junit.Test; -import ru.core.events.handlers.AsyncEventHandler; - -public class AsyncEventLoopBenchmark extends AbstractBenchmark { - private static final int ITERATIONS = 200; - private static AsyncEventLoop asyncEventLoop; - private static AdvancedEventLoop advancedEventLoop; - private static LoginEvent testEvent; - - @BeforeClass - public static void setup() { - asyncEventLoop = new AsyncEventLoop(); - asyncEventLoop.addEventHandler(new AsyncEventHandler()); - asyncEventLoop.addEventHandler(new AsyncEventHandler()); - asyncEventLoop.addEventHandler(new AsyncEventHandler()); - advancedEventLoop = new AdvancedEventLoop(); - advancedEventLoop.addEventHandler(new AsyncEventHandler()); - advancedEventLoop.addEventHandler(new AsyncEventHandler()); - advancedEventLoop.addEventHandler(new AsyncEventHandler()); - testEvent = new LoginEvent(null); - testEvent.setDenyReason("none"); - } - - @Test - @BenchmarkOptions(warmupRounds = 5, benchmarkRounds = 15) - public void async() { - for (int i = 0; i < ITERATIONS; i++) { - asyncEventLoop.callEvent(testEvent); - } - } - - @Test - @BenchmarkOptions(warmupRounds = 5, benchmarkRounds = 15) - public void advanced() { - for (int i = 0; i < ITERATIONS; i++) { - advancedEventLoop.callEvent(testEvent); - } - } -} diff --git a/event-loop/src/test/java/ru/core/events/AsyncEventLoopTest.java b/event-loop/src/test/java/ru/core/events/AsyncEventLoopTest.java deleted file mode 100644 index f112174..0000000 --- a/event-loop/src/test/java/ru/core/events/AsyncEventLoopTest.java +++ /dev/null @@ -1,22 +0,0 @@ -package ru.core.events; - -import mc.core.events.LoginEvent; -import mc.core.events.async.AsyncEventLoop; -import org.junit.Assert; -import org.junit.Test; -import ru.core.events.handlers.AsyncEventHandler; - -public class AsyncEventLoopTest { - - @Test - public void loopWorks() { - AsyncEventLoop asyncEventLoop = new AsyncEventLoop(); - asyncEventLoop.addEventHandler(new AsyncEventHandler()); - LoginEvent testEvent = new LoginEvent(null); - testEvent.setDenyReason("none"); - - asyncEventLoop.callEvent(testEvent); - - Assert.assertEquals("Event handler was not called", "Hello! This is a message from Sync portion of the event", testEvent.getDenyReason()); - } -} diff --git a/event-loop/src/test/java/ru/core/events/SimpleEventLoopBenchmark.java b/event-loop/src/test/java/ru/core/events/SimpleEventLoopBenchmark.java deleted file mode 100644 index 7fc0a9e..0000000 --- a/event-loop/src/test/java/ru/core/events/SimpleEventLoopBenchmark.java +++ /dev/null @@ -1,30 +0,0 @@ -package ru.core.events; - -import com.carrotsearch.junitbenchmarks.AbstractBenchmark; -import com.carrotsearch.junitbenchmarks.BenchmarkOptions; -import mc.core.events.LoginEvent; -import mc.core.events.SimpleEventLoop; -import org.junit.BeforeClass; -import org.junit.Test; -import ru.core.events.handlers.SampleEventHandler; - -public class SimpleEventLoopBenchmark extends AbstractBenchmark { - private static SimpleEventLoop simpleEventLoop; - private static LoginEvent testEvent; - - @BeforeClass - public static void setup(){ - simpleEventLoop = new SimpleEventLoop(); - simpleEventLoop.addEventHandler(new SampleEventHandler()); - testEvent = new LoginEvent(null); - testEvent.setDenyReason("none"); - } - - @Test - @BenchmarkOptions(warmupRounds = 5, benchmarkRounds = 15) - public void benchmark() { - for (int i = 0; i < 50_000_000; i++) { - simpleEventLoop.callEvent(testEvent); - } - } -} diff --git a/event-loop/src/test/java/ru/core/events/SimpleEventLoopTest.java b/event-loop/src/test/java/ru/core/events/SimpleEventLoopTest.java deleted file mode 100644 index 4429def..0000000 --- a/event-loop/src/test/java/ru/core/events/SimpleEventLoopTest.java +++ /dev/null @@ -1,22 +0,0 @@ -package ru.core.events; - -import mc.core.events.LoginEvent; -import mc.core.events.SimpleEventLoop; -import org.junit.Assert; -import org.junit.Test; -import ru.core.events.handlers.HelloWorldSimpleEventHandler; - -public class SimpleEventLoopTest { - - @Test - public void loopWorks() { - SimpleEventLoop simpleEventLoop = new SimpleEventLoop(); - simpleEventLoop.addEventHandler(new HelloWorldSimpleEventHandler()); - 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/event-loop/src/test/java/ru/core/events/handlers/AsyncEventHandler.java b/event-loop/src/test/java/ru/core/events/handlers/AsyncEventHandler.java deleted file mode 100644 index d2891ad..0000000 --- a/event-loop/src/test/java/ru/core/events/handlers/AsyncEventHandler.java +++ /dev/null @@ -1,29 +0,0 @@ -package ru.core.events.handlers; - -import lombok.extern.slf4j.Slf4j; -import mc.core.events.EventHandler; -import mc.core.events.LoginEvent; -import mc.core.events.async.EventPreprocessor; - -@Slf4j -public class AsyncEventHandler { - - @EventPreprocessor(methodName = "onLogin") - public void onLoginPreprocess(LoginEvent event) { - event.setDenyReason("Hello! This is a message from Async event preprocessor."); - - try { - Thread.sleep(1); - } catch (InterruptedException e) { - e.printStackTrace(); - } - // Представим, что здесь мы выполнили запрос к БД - // и это значение - кол-во денег у игрока - /*return 20D;*/ - } - - @EventHandler - public void onLogin(LoginEvent event) { // money здесь будет равно тому, что вернул onLoginPreprocess - event.setDenyReason("Hello! This is a message from Sync portion of the event"); - } -} diff --git a/event-loop/src/test/java/ru/core/events/handlers/FastEventHandler.java b/event-loop/src/test/java/ru/core/events/handlers/FastEventHandler.java deleted file mode 100644 index 733180d..0000000 --- a/event-loop/src/test/java/ru/core/events/handlers/FastEventHandler.java +++ /dev/null @@ -1,11 +0,0 @@ -package ru.core.events.handlers; - -import mc.core.events.EventHandler; -import mc.core.events.LoginEvent; - -public class FastEventHandler { - @EventHandler - public void onPlayerLogin(LoginEvent event) { - - } -} diff --git a/event-loop/src/test/java/ru/core/events/handlers/HelloWorldSimpleEventHandler.java b/event-loop/src/test/java/ru/core/events/handlers/HelloWorldSimpleEventHandler.java deleted file mode 100644 index 640e944..0000000 --- a/event-loop/src/test/java/ru/core/events/handlers/HelloWorldSimpleEventHandler.java +++ /dev/null @@ -1,11 +0,0 @@ -package ru.core.events.handlers; - -import mc.core.events.EventHandler; -import mc.core.events.LoginEvent; - -public class HelloWorldSimpleEventHandler { - @EventHandler - public void onPlayerLogin(LoginEvent event) { - event.setDenyReason("Hello from SampleEventHandler!"); - } -} diff --git a/event-loop/src/test/java/ru/core/events/handlers/SampleEventHandler.java b/event-loop/src/test/java/ru/core/events/handlers/SampleEventHandler.java deleted file mode 100644 index 36a7ec4..0000000 --- a/event-loop/src/test/java/ru/core/events/handlers/SampleEventHandler.java +++ /dev/null @@ -1,20 +0,0 @@ -package ru.core.events.handlers; - -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){ - - } -} From 932682b6e12f49b959be98b57d6f7656035ce89c Mon Sep 17 00:00:00 2001 From: Daniil Date: Fri, 3 Aug 2018 21:18:34 +0700 Subject: [PATCH 23/62] Event loop module restructured --- .../mc/core/events/{v3 => }/EventPipeline.java | 8 ++++---- .../core/events/{v3 => }/FullAsyncEventLoop.java | 9 +++++---- .../events/{v3 => }/RegisteredEventHandler.java | 5 +++-- .../mc/core/events/{ => api}/EventHandler.java | 2 +- .../mc/core/events/{ => api}/EventPriority.java | 2 +- .../core/events/{v3 => api}/EventQueueOwner.java | 2 +- .../mc/core/events/{ => api}/LockableResource.java | 2 +- .../java/mc/core/events/{v3 => api}/Plugin.java | 2 +- .../events/{v3 => }/lock/CustomReentrantLock.java | 4 ++-- .../{v3 => }/runner/EventExecutorService.java | 2 +- .../events/{v3 => }/runner/ExecutorThread.java | 2 +- .../events/{v3 => }/runner/ResourceRunnable.java | 2 +- .../events/{v3 => }/runner/ScheduleStrategy.java | 2 +- .../v3 => mc/core/events}/EventExecutorTest.java | 6 +++--- .../v3 => mc/core/events}/EventLoopTest.java | 14 ++++++-------- 15 files changed, 32 insertions(+), 32 deletions(-) rename event-loop/src/main/java/mc/core/events/{v3 => }/EventPipeline.java (92%) rename event-loop/src/main/java/mc/core/events/{v3 => }/FullAsyncEventLoop.java (95%) rename event-loop/src/main/java/mc/core/events/{v3 => }/RegisteredEventHandler.java (80%) rename event-loop/src/main/java/mc/core/events/{ => api}/EventHandler.java (92%) rename event-loop/src/main/java/mc/core/events/{ => api}/EventPriority.java (89%) rename event-loop/src/main/java/mc/core/events/{v3 => api}/EventQueueOwner.java (57%) rename event-loop/src/main/java/mc/core/events/{ => api}/LockableResource.java (67%) rename event-loop/src/main/java/mc/core/events/{v3 => api}/Plugin.java (50%) rename event-loop/src/main/java/mc/core/events/{v3 => }/lock/CustomReentrantLock.java (92%) rename event-loop/src/main/java/mc/core/events/{v3 => }/runner/EventExecutorService.java (98%) rename event-loop/src/main/java/mc/core/events/{v3 => }/runner/ExecutorThread.java (95%) rename event-loop/src/main/java/mc/core/events/{v3 => }/runner/ResourceRunnable.java (82%) rename event-loop/src/main/java/mc/core/events/{v3 => }/runner/ScheduleStrategy.java (74%) rename event-loop/src/test/java/{ru/core/events/v3 => mc/core/events}/EventExecutorTest.java (88%) rename event-loop/src/test/java/{ru/core/events/v3 => mc/core/events}/EventLoopTest.java (94%) diff --git a/event-loop/src/main/java/mc/core/events/v3/EventPipeline.java b/event-loop/src/main/java/mc/core/events/EventPipeline.java similarity index 92% rename from event-loop/src/main/java/mc/core/events/v3/EventPipeline.java rename to event-loop/src/main/java/mc/core/events/EventPipeline.java index f2aaa68..e3a2910 100644 --- a/event-loop/src/main/java/mc/core/events/v3/EventPipeline.java +++ b/event-loop/src/main/java/mc/core/events/EventPipeline.java @@ -1,12 +1,12 @@ -package mc.core.events.v3; +package mc.core.events; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.Setter; import lombok.extern.slf4j.Slf4j; -import mc.core.events.Event; -import mc.core.events.v3.runner.EventExecutorService; -import mc.core.events.v3.runner.ResourceRunnable; +import mc.core.events.api.EventQueueOwner; +import mc.core.events.runner.EventExecutorService; +import mc.core.events.runner.ResourceRunnable; import java.lang.reflect.InvocationTargetException; import java.util.List; diff --git a/event-loop/src/main/java/mc/core/events/v3/FullAsyncEventLoop.java b/event-loop/src/main/java/mc/core/events/FullAsyncEventLoop.java similarity index 95% rename from event-loop/src/main/java/mc/core/events/v3/FullAsyncEventLoop.java rename to event-loop/src/main/java/mc/core/events/FullAsyncEventLoop.java index a88e2e9..2b8db05 100644 --- a/event-loop/src/main/java/mc/core/events/v3/FullAsyncEventLoop.java +++ b/event-loop/src/main/java/mc/core/events/FullAsyncEventLoop.java @@ -1,10 +1,11 @@ -package mc.core.events.v3; +package mc.core.events; import lombok.Setter; import lombok.extern.slf4j.Slf4j; -import mc.core.events.Event; -import mc.core.events.EventHandler; -import mc.core.events.v3.runner.EventExecutorService; +import mc.core.events.api.EventHandler; +import mc.core.events.api.EventQueueOwner; +import mc.core.events.api.Plugin; +import mc.core.events.runner.EventExecutorService; import org.springframework.beans.factory.annotation.Autowired; import java.lang.reflect.Method; diff --git a/event-loop/src/main/java/mc/core/events/v3/RegisteredEventHandler.java b/event-loop/src/main/java/mc/core/events/RegisteredEventHandler.java similarity index 80% rename from event-loop/src/main/java/mc/core/events/v3/RegisteredEventHandler.java rename to event-loop/src/main/java/mc/core/events/RegisteredEventHandler.java index ae708e2..bae22ff 100644 --- a/event-loop/src/main/java/mc/core/events/v3/RegisteredEventHandler.java +++ b/event-loop/src/main/java/mc/core/events/RegisteredEventHandler.java @@ -1,8 +1,9 @@ -package mc.core.events.v3; +package mc.core.events; import lombok.Getter; import lombok.RequiredArgsConstructor; -import mc.core.events.LockableResource; +import mc.core.events.api.LockableResource; +import mc.core.events.api.Plugin; import java.lang.reflect.Method; diff --git a/event-loop/src/main/java/mc/core/events/EventHandler.java b/event-loop/src/main/java/mc/core/events/api/EventHandler.java similarity index 92% rename from event-loop/src/main/java/mc/core/events/EventHandler.java rename to event-loop/src/main/java/mc/core/events/api/EventHandler.java index 0def90c..dbf255d 100644 --- a/event-loop/src/main/java/mc/core/events/EventHandler.java +++ b/event-loop/src/main/java/mc/core/events/api/EventHandler.java @@ -1,4 +1,4 @@ -package mc.core.events; +package mc.core.events.api; import java.lang.annotation.*; diff --git a/event-loop/src/main/java/mc/core/events/EventPriority.java b/event-loop/src/main/java/mc/core/events/api/EventPriority.java similarity index 89% rename from event-loop/src/main/java/mc/core/events/EventPriority.java rename to event-loop/src/main/java/mc/core/events/api/EventPriority.java index 5a889eb..0c6fdce 100644 --- a/event-loop/src/main/java/mc/core/events/EventPriority.java +++ b/event-loop/src/main/java/mc/core/events/api/EventPriority.java @@ -1,4 +1,4 @@ -package mc.core.events; +package mc.core.events.api; import lombok.Getter; diff --git a/event-loop/src/main/java/mc/core/events/v3/EventQueueOwner.java b/event-loop/src/main/java/mc/core/events/api/EventQueueOwner.java similarity index 57% rename from event-loop/src/main/java/mc/core/events/v3/EventQueueOwner.java rename to event-loop/src/main/java/mc/core/events/api/EventQueueOwner.java index 996b159..49f4fd4 100644 --- a/event-loop/src/main/java/mc/core/events/v3/EventQueueOwner.java +++ b/event-loop/src/main/java/mc/core/events/api/EventQueueOwner.java @@ -1,4 +1,4 @@ -package mc.core.events.v3; +package mc.core.events.api; public interface EventQueueOwner { } diff --git a/event-loop/src/main/java/mc/core/events/LockableResource.java b/event-loop/src/main/java/mc/core/events/api/LockableResource.java similarity index 67% rename from event-loop/src/main/java/mc/core/events/LockableResource.java rename to event-loop/src/main/java/mc/core/events/api/LockableResource.java index 3d8c459..f6408c7 100644 --- a/event-loop/src/main/java/mc/core/events/LockableResource.java +++ b/event-loop/src/main/java/mc/core/events/api/LockableResource.java @@ -1,4 +1,4 @@ -package mc.core.events; +package mc.core.events.api; public enum LockableResource { PLAYER, diff --git a/event-loop/src/main/java/mc/core/events/v3/Plugin.java b/event-loop/src/main/java/mc/core/events/api/Plugin.java similarity index 50% rename from event-loop/src/main/java/mc/core/events/v3/Plugin.java rename to event-loop/src/main/java/mc/core/events/api/Plugin.java index 670bd82..040ab60 100644 --- a/event-loop/src/main/java/mc/core/events/v3/Plugin.java +++ b/event-loop/src/main/java/mc/core/events/api/Plugin.java @@ -1,4 +1,4 @@ -package mc.core.events.v3; +package mc.core.events.api; public interface Plugin { } diff --git a/event-loop/src/main/java/mc/core/events/v3/lock/CustomReentrantLock.java b/event-loop/src/main/java/mc/core/events/lock/CustomReentrantLock.java similarity index 92% rename from event-loop/src/main/java/mc/core/events/v3/lock/CustomReentrantLock.java rename to event-loop/src/main/java/mc/core/events/lock/CustomReentrantLock.java index 35bbbc2..a485f53 100644 --- a/event-loop/src/main/java/mc/core/events/v3/lock/CustomReentrantLock.java +++ b/event-loop/src/main/java/mc/core/events/lock/CustomReentrantLock.java @@ -1,6 +1,6 @@ -package mc.core.events.v3.lock; +package mc.core.events.lock; -import mc.core.events.v3.runner.ExecutorThread; +import mc.core.events.runner.ExecutorThread; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; diff --git a/event-loop/src/main/java/mc/core/events/v3/runner/EventExecutorService.java b/event-loop/src/main/java/mc/core/events/runner/EventExecutorService.java similarity index 98% rename from event-loop/src/main/java/mc/core/events/v3/runner/EventExecutorService.java rename to event-loop/src/main/java/mc/core/events/runner/EventExecutorService.java index 93bbee7..95f1eac 100644 --- a/event-loop/src/main/java/mc/core/events/v3/runner/EventExecutorService.java +++ b/event-loop/src/main/java/mc/core/events/runner/EventExecutorService.java @@ -1,4 +1,4 @@ -package mc.core.events.v3.runner; +package mc.core.events.runner; import sun.plugin.dom.exception.InvalidStateException; diff --git a/event-loop/src/main/java/mc/core/events/v3/runner/ExecutorThread.java b/event-loop/src/main/java/mc/core/events/runner/ExecutorThread.java similarity index 95% rename from event-loop/src/main/java/mc/core/events/v3/runner/ExecutorThread.java rename to event-loop/src/main/java/mc/core/events/runner/ExecutorThread.java index 3c18421..4c12b26 100644 --- a/event-loop/src/main/java/mc/core/events/v3/runner/ExecutorThread.java +++ b/event-loop/src/main/java/mc/core/events/runner/ExecutorThread.java @@ -1,4 +1,4 @@ -package mc.core.events.v3.runner; +package mc.core.events.runner; public class ExecutorThread extends Thread { private EventExecutorService service; diff --git a/event-loop/src/main/java/mc/core/events/v3/runner/ResourceRunnable.java b/event-loop/src/main/java/mc/core/events/runner/ResourceRunnable.java similarity index 82% rename from event-loop/src/main/java/mc/core/events/v3/runner/ResourceRunnable.java rename to event-loop/src/main/java/mc/core/events/runner/ResourceRunnable.java index 3c452ac..74d9528 100644 --- a/event-loop/src/main/java/mc/core/events/v3/runner/ResourceRunnable.java +++ b/event-loop/src/main/java/mc/core/events/runner/ResourceRunnable.java @@ -1,4 +1,4 @@ -package mc.core.events.v3.runner; +package mc.core.events.runner; public interface ResourceRunnable extends Runnable { default void lock() { diff --git a/event-loop/src/main/java/mc/core/events/v3/runner/ScheduleStrategy.java b/event-loop/src/main/java/mc/core/events/runner/ScheduleStrategy.java similarity index 74% rename from event-loop/src/main/java/mc/core/events/v3/runner/ScheduleStrategy.java rename to event-loop/src/main/java/mc/core/events/runner/ScheduleStrategy.java index f901d85..9d4ff50 100644 --- a/event-loop/src/main/java/mc/core/events/v3/runner/ScheduleStrategy.java +++ b/event-loop/src/main/java/mc/core/events/runner/ScheduleStrategy.java @@ -1,4 +1,4 @@ -package mc.core.events.v3.runner; +package mc.core.events.runner; public interface ScheduleStrategy { ResourceRunnable getTask() throws InterruptedException; diff --git a/event-loop/src/test/java/ru/core/events/v3/EventExecutorTest.java b/event-loop/src/test/java/mc/core/events/EventExecutorTest.java similarity index 88% rename from event-loop/src/test/java/ru/core/events/v3/EventExecutorTest.java rename to event-loop/src/test/java/mc/core/events/EventExecutorTest.java index a478f49..871f02e 100644 --- a/event-loop/src/test/java/ru/core/events/v3/EventExecutorTest.java +++ b/event-loop/src/test/java/mc/core/events/EventExecutorTest.java @@ -1,7 +1,7 @@ -package ru.core.events.v3; +package mc.core.events; -import mc.core.events.v3.runner.EventExecutorService; -import mc.core.events.v3.runner.ResourceRunnable; +import mc.core.events.runner.EventExecutorService; +import mc.core.events.runner.ResourceRunnable; import org.junit.Assert; import org.junit.Test; diff --git a/event-loop/src/test/java/ru/core/events/v3/EventLoopTest.java b/event-loop/src/test/java/mc/core/events/EventLoopTest.java similarity index 94% rename from event-loop/src/test/java/ru/core/events/v3/EventLoopTest.java rename to event-loop/src/test/java/mc/core/events/EventLoopTest.java index 8dc75d9..bb43297 100644 --- a/event-loop/src/test/java/ru/core/events/v3/EventLoopTest.java +++ b/event-loop/src/test/java/mc/core/events/EventLoopTest.java @@ -1,12 +1,10 @@ -package ru.core.events.v3; +package mc.core.events; -import mc.core.events.EventHandler; -import mc.core.events.EventPriority; -import mc.core.events.LoginEvent; -import mc.core.events.v3.EventQueueOwner; -import mc.core.events.v3.FullAsyncEventLoop; -import mc.core.events.v3.Plugin; -import mc.core.events.v3.runner.EventExecutorService; +import mc.core.events.api.EventHandler; +import mc.core.events.api.EventPriority; +import mc.core.events.api.EventQueueOwner; +import mc.core.events.api.Plugin; +import mc.core.events.runner.EventExecutorService; import org.junit.Assert; import org.junit.Test; From d84e6ca7499d473eb56c50a67efcc9ea0f378799 Mon Sep 17 00:00:00 2001 From: Forwolk Date: Sat, 4 Aug 2018 11:46:28 +0300 Subject: [PATCH 24/62] Location world reference --- core/src/main/java/mc/core/Location.java | 39 ++++++++++++++++--- .../generated_world/chunk/ChunkImpl.java | 1 + .../generator/SeedBasedWorldGenerator.java | 2 +- .../netty/handlers/LoginHandler.java | 6 +-- 4 files changed, 39 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/mc/core/Location.java b/core/src/main/java/mc/core/Location.java index 7e560f3..fc7ea47 100644 --- a/core/src/main/java/mc/core/Location.java +++ b/core/src/main/java/mc/core/Location.java @@ -4,15 +4,16 @@ */ package mc.core; -import lombok.AllArgsConstructor; import lombok.Data; +import mc.core.world.World; import java.io.Serializable; +import java.lang.ref.WeakReference; -@AllArgsConstructor @Data public class Location implements Serializable{ private double x, y, z; + private WeakReference world; private static int floor_double(double value) { int i = (int)value; @@ -23,16 +24,35 @@ public class Location implements Serializable{ return new Location( location.x, location.y, - location.z + location.z, + location.getWorld() ); } + public Location (double x, double y, double z, World world) { + this.x = x; + this.y = y; + this.z = z; + this.world = new WeakReference<>(world); + } + + public Location (double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + public static Location startPointLocation () { - return new Location(0,10,0); + return new Location(0,10,0, null); } public Location(long compactValue) { set(compactValue); + this.world = new WeakReference<>(null); + } + + public Location(long compactValue, World world) { + set(compactValue); } public void set(Location location) { @@ -51,7 +71,8 @@ public class Location implements Serializable{ return new Location( this.x - location.x, this.y - location.y, - this.z - location.z + this.z - location.z, + this.getWorld().equals(location.getWorld()) ? this.getWorld() : null ); } @@ -72,4 +93,12 @@ public class Location implements Serializable{ | ((floor_double(y) & 0xFFF) << 26) | (floor_double(z) & 0x3FFFFFF); } + + public World getWorld () { + return this.world.get(); + } + + public void setWorld (World world) { + this.world = new WeakReference<>(world); + } } diff --git a/generated_world/src/main/java/mc/world/generated_world/chunk/ChunkImpl.java b/generated_world/src/main/java/mc/world/generated_world/chunk/ChunkImpl.java index 464b980..f9abc9a 100644 --- a/generated_world/src/main/java/mc/world/generated_world/chunk/ChunkImpl.java +++ b/generated_world/src/main/java/mc/world/generated_world/chunk/ChunkImpl.java @@ -85,6 +85,7 @@ public class ChunkImpl implements Chunk{ public void setBlock(int x, int y, int z, Block block) { if (block.getBlockType() == BlockType.AIR) { blocks[x][y][z] = null; + return; } blocks[x][y][z] = block; } diff --git a/generated_world/src/main/java/mc/world/generated_world/generator/SeedBasedWorldGenerator.java b/generated_world/src/main/java/mc/world/generated_world/generator/SeedBasedWorldGenerator.java index 8253165..381173f 100644 --- a/generated_world/src/main/java/mc/world/generated_world/generator/SeedBasedWorldGenerator.java +++ b/generated_world/src/main/java/mc/world/generated_world/generator/SeedBasedWorldGenerator.java @@ -28,7 +28,7 @@ public class SeedBasedWorldGenerator implements WorldGenerator { World world = new CubicWorld(UUID.fromString("00000000-0000-0000-C000-000000000046"), 2626949); Region region = worldGenerator.generateRegion(0, 0, world); region.save(new ChunkSerializer(), new RegionReaderWriter(new File("worlds", world.getWorldId().toString()))); - new WorldReaderWriter(new File("worlds")).writeWorldInfo(world); +// new WorldReaderWriter(new File("worlds")).writeWorldInfo(world); /*worldGenerator.generateRegion(1, 0, world); worldGenerator.generateRegion(-1, 0, world); worldGenerator.generateRegion(0, 1, world); diff --git a/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/handlers/LoginHandler.java b/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/handlers/LoginHandler.java index eb5cd65..bbbbb6d 100644 --- a/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/handlers/LoginHandler.java +++ b/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/handlers/LoginHandler.java @@ -48,8 +48,8 @@ public class LoginHandler extends AbstractStateHandler implements LoginStateHand Player player = playerManager.getPlayer(packet.getPlayerName()) .orElseGet(() -> playerManager.createPlayer( packet.getPlayerName(), - world.getSpawn(), - new Look(0f, 0f))); + world.getSpawn().getLocation(), + world.getSpawn().getLook())); channel.writeAndFlush(new LoginSuccessPacket( player.getUUID(), @@ -68,7 +68,7 @@ public class LoginHandler extends AbstractStateHandler implements LoginStateHand // Spawn Position SpawnPositionPacket pkt2 = new SpawnPositionPacket(); - pkt2.setLocation(world.getSpawn()); + pkt2.setLocation(world.getSpawn().getLocation()); channel.write(pkt2); // Player Abilities From 610981b7b8e305695ec9ec739abfc9038cfa8d98 Mon Sep 17 00:00:00 2001 From: Forwolk Date: Sat, 4 Aug 2018 13:19:08 +0300 Subject: [PATCH 25/62] World <-- Region <-- Chunk Weak references --- core/src/main/java/mc/core/world/Chunk.java | 7 +++-- core/src/main/java/mc/core/world/Region.java | 2 ++ .../main/java/mc/world/flat/SimpleChunk.java | 12 ++++++++ .../generated_world/chunk/ChunkImpl.java | 28 +++++++++++++++---- .../generated_world/chunk/ChunkProxy.java | 14 ++++++++++ .../generated_world/region/RegionImpl.java | 20 +++++++++---- 6 files changed, 71 insertions(+), 12 deletions(-) diff --git a/core/src/main/java/mc/core/world/Chunk.java b/core/src/main/java/mc/core/world/Chunk.java index 0b712b0..6b63845 100644 --- a/core/src/main/java/mc/core/world/Chunk.java +++ b/core/src/main/java/mc/core/world/Chunk.java @@ -45,6 +45,9 @@ public interface Chunk extends Serializable{ int getY(); int getZ(); - void setBlock (int x, int y, int z, Block block); - Block getBlock (int x, int y, int z); + void setBlock(int x, int y, int z, Block block); + Block getBlock(int x, int y, int z); + + Region getRegion(); + World getWorld(); } diff --git a/core/src/main/java/mc/core/world/Region.java b/core/src/main/java/mc/core/world/Region.java index dec2730..e2f1371 100644 --- a/core/src/main/java/mc/core/world/Region.java +++ b/core/src/main/java/mc/core/world/Region.java @@ -30,5 +30,7 @@ public interface Region extends Serializable{ Biome getBiomeAt (int x, int z); void setBiome (int x, int z, Biome biome); + World getWorld(); + void save(Serializer chunkSerializer, IRegionReaderWriter regionReaderWritter) throws IOException; } diff --git a/flat_world/src/main/java/mc/world/flat/SimpleChunk.java b/flat_world/src/main/java/mc/world/flat/SimpleChunk.java index 03ad6ad..ee5eaab 100644 --- a/flat_world/src/main/java/mc/world/flat/SimpleChunk.java +++ b/flat_world/src/main/java/mc/world/flat/SimpleChunk.java @@ -9,6 +9,8 @@ import mc.core.block.BlockFactory; import mc.core.block.BlockType; import mc.core.world.Biome; import mc.core.world.Chunk; +import mc.core.world.Region; +import mc.core.world.World; public class SimpleChunk implements Chunk { @Override @@ -104,4 +106,14 @@ public class SimpleChunk implements Chunk { else if (y == 3) return blockFactory.create(BlockType.GRASS, 0); else return Block.airBlock(x, y, z); } + + @Override + public Region getRegion() { + throw new UnsupportedOperationException(); + } + + @Override + public World getWorld() { + throw new UnsupportedOperationException(); + } } diff --git a/generated_world/src/main/java/mc/world/generated_world/chunk/ChunkImpl.java b/generated_world/src/main/java/mc/world/generated_world/chunk/ChunkImpl.java index f9abc9a..fad881c 100644 --- a/generated_world/src/main/java/mc/world/generated_world/chunk/ChunkImpl.java +++ b/generated_world/src/main/java/mc/world/generated_world/chunk/ChunkImpl.java @@ -1,16 +1,17 @@ package mc.world.generated_world.chunk; import lombok.Getter; -import lombok.RequiredArgsConstructor; import mc.core.block.Block; import mc.core.block.BlockType; import mc.core.world.Biome; import mc.core.world.Chunk; import mc.core.world.Region; +import mc.core.world.World; + +import java.lang.ref.WeakReference; import static mc.world.generated_world.WorldConstants.WORLD_CHUNK_SIZE; -@RequiredArgsConstructor public class ChunkImpl implements Chunk{ @Getter private final int x; @@ -19,7 +20,14 @@ public class ChunkImpl implements Chunk{ @Getter private final int z; private final Block[][][] blocks = new Block[WORLD_CHUNK_SIZE][WORLD_CHUNK_SIZE][WORLD_CHUNK_SIZE]; - private final transient Region region; + private final transient WeakReference region; + + public ChunkImpl(int x, int y, int z, Region region) { + this.x = x; + this.y = y; + this.z = z; + this.region = new WeakReference<>(region); + } @Override public int getBlockType(int x, int y, int z) { @@ -73,12 +81,12 @@ public class ChunkImpl implements Chunk{ @Override public Biome getBiome(int x, int z) { - return region.getBiomeAt(x + this.x * WORLD_CHUNK_SIZE,z + this.z * WORLD_CHUNK_SIZE); + return getRegion().getBiomeAt(x + this.x * WORLD_CHUNK_SIZE,z + this.z * WORLD_CHUNK_SIZE); } @Override public void setBiome(int x, int z, Biome biome) { - region.setBiome(x + this.x * WORLD_CHUNK_SIZE,z + this.z * WORLD_CHUNK_SIZE, biome); + getRegion().setBiome(x + this.x * WORLD_CHUNK_SIZE,z + this.z * WORLD_CHUNK_SIZE, biome); } @Override @@ -98,4 +106,14 @@ public class ChunkImpl implements Chunk{ } return blocks[x][y][z]; } + + @Override + public Region getRegion() { + return region.get(); + } + + @Override + public World getWorld() { + return getRegion().getWorld(); + } } diff --git a/generated_world/src/main/java/mc/world/generated_world/chunk/ChunkProxy.java b/generated_world/src/main/java/mc/world/generated_world/chunk/ChunkProxy.java index 4490001..fb03d6d 100644 --- a/generated_world/src/main/java/mc/world/generated_world/chunk/ChunkProxy.java +++ b/generated_world/src/main/java/mc/world/generated_world/chunk/ChunkProxy.java @@ -3,6 +3,8 @@ package mc.world.generated_world.chunk; import mc.core.block.Block; import mc.core.world.Biome; import mc.core.world.Chunk; +import mc.core.world.Region; +import mc.core.world.World; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; @@ -129,4 +131,16 @@ public class ChunkProxy implements Chunk { use(); return chunk.getBlock(x, y, z); } + + @Override + public Region getRegion() { + use(); + return chunk.getRegion(); + } + + @Override + public World getWorld() { + use(); + return chunk.getWorld(); + } } diff --git a/generated_world/src/main/java/mc/world/generated_world/region/RegionImpl.java b/generated_world/src/main/java/mc/world/generated_world/region/RegionImpl.java index 812512f..643644d 100644 --- a/generated_world/src/main/java/mc/world/generated_world/region/RegionImpl.java +++ b/generated_world/src/main/java/mc/world/generated_world/region/RegionImpl.java @@ -14,12 +14,12 @@ import org.springframework.beans.factory.annotation.Autowired; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.lang.ref.WeakReference; import java.text.MessageFormat; import static mc.world.generated_world.WorldConstants.*; @Slf4j -@RequiredArgsConstructor public class RegionImpl implements Region{ @Getter private final int x; @@ -27,18 +27,23 @@ public class RegionImpl implements Region{ private final int z; private final ChunkProxy[][][] chunks = new ChunkProxy[WORLD_REGION_SIZE/WORLD_CHUNK_SIZE][WORLD_REGION_SIZE/WORLD_CHUNK_SIZE][WORLD_REGION_SIZE/WORLD_CHUNK_SIZE]; private final Biome[][] biomes = new Biome[WORLD_REGION_SIZE][WORLD_REGION_SIZE]; - @Getter - private final transient World world; + private final transient WeakReference world; @Autowired private ChunkLoader chunkLoader; + public RegionImpl (int x, int z, World world) { + this.x = x; + this.z = z; + this.world = new WeakReference<>(world); + } + @Override public Chunk getChunkAt(int x, int y, int z) { if (x < 0 || y < 0 || z < 0 || x >= 16 || y >= 16 || z >= 16) { throw new RuntimeException(MessageFormat.format("Invalid chunk coordinates [{0} {1} {2}]", x, y, z)); } if (chunkLoader == null) { - chunkLoader = new InMemoryCacheChunkLoader(world); + chunkLoader = new InMemoryCacheChunkLoader(getWorld()); } Chunk chunk = chunks[x][y][z]; if (chunk == null) { @@ -72,10 +77,15 @@ public class RegionImpl implements Region{ biomes[x][z] = biome; } + @Override + public World getWorld() { + return world.get(); + } + @Override public void save(Serializer chunkSerializer, IRegionReaderWriter regionReaderWriter) throws IOException { String worldPath = System.getProperty("worlds.folder", "worlds"); - File worldFile = new File(worldPath, world.getWorldId().toString()); + File worldFile = new File(worldPath, getWorld().getWorldId().toString()); File regionFile = new File(worldFile, MessageFormat.format(REGION_FILE_NAME_TEMPLATE, this.getX(), this.getZ())); if (!regionFile.exists()) { regionFile.mkdirs(); From eab9947aa9b06dbbe9a34a2a75b8e56e0097b19d Mon Sep 17 00:00:00 2001 From: Forwolk Date: Sat, 4 Aug 2018 14:31:43 +0300 Subject: [PATCH 26/62] Checking on unloading --- core/src/main/java/mc/core/Location.java | 8 +++++++- .../mc/core/exception/McCoreUncheckedException.java | 12 ++++++++++++ .../mc/core/exception/ResourceUnloadException.java | 8 ++++++++ .../mc/world/generated_world/chunk/ChunkImpl.java | 7 ++++++- .../mc/world/generated_world/region/RegionImpl.java | 7 ++++++- 5 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 core/src/main/java/mc/core/exception/McCoreUncheckedException.java create mode 100644 core/src/main/java/mc/core/exception/ResourceUnloadException.java diff --git a/core/src/main/java/mc/core/Location.java b/core/src/main/java/mc/core/Location.java index fc7ea47..2816f25 100644 --- a/core/src/main/java/mc/core/Location.java +++ b/core/src/main/java/mc/core/Location.java @@ -5,15 +5,17 @@ package mc.core; import lombok.Data; +import mc.core.exception.ResourceUnloadException; import mc.core.world.World; import java.io.Serializable; +import java.lang.ref.Reference; import java.lang.ref.WeakReference; @Data public class Location implements Serializable{ private double x, y, z; - private WeakReference world; + private Reference world; private static int floor_double(double value) { int i = (int)value; @@ -53,6 +55,7 @@ public class Location implements Serializable{ public Location(long compactValue, World world) { set(compactValue); + this.world = new WeakReference<>(world); } public void set(Location location) { @@ -95,6 +98,9 @@ public class Location implements Serializable{ } public World getWorld () { + if (world.get() == null) { + throw new ResourceUnloadException("You're trying to get unloaded world"); + } return this.world.get(); } diff --git a/core/src/main/java/mc/core/exception/McCoreUncheckedException.java b/core/src/main/java/mc/core/exception/McCoreUncheckedException.java new file mode 100644 index 0000000..15313f3 --- /dev/null +++ b/core/src/main/java/mc/core/exception/McCoreUncheckedException.java @@ -0,0 +1,12 @@ +package mc.core.exception; + +public abstract class McCoreUncheckedException extends RuntimeException { + + public McCoreUncheckedException() { + super(); + } + + public McCoreUncheckedException(String msg) { + super(msg); + } +} diff --git a/core/src/main/java/mc/core/exception/ResourceUnloadException.java b/core/src/main/java/mc/core/exception/ResourceUnloadException.java new file mode 100644 index 0000000..b277411 --- /dev/null +++ b/core/src/main/java/mc/core/exception/ResourceUnloadException.java @@ -0,0 +1,8 @@ +package mc.core.exception; + +public class ResourceUnloadException extends McCoreUncheckedException { + + public ResourceUnloadException(String msg) { + super(msg); + } +} diff --git a/generated_world/src/main/java/mc/world/generated_world/chunk/ChunkImpl.java b/generated_world/src/main/java/mc/world/generated_world/chunk/ChunkImpl.java index fad881c..5dd5951 100644 --- a/generated_world/src/main/java/mc/world/generated_world/chunk/ChunkImpl.java +++ b/generated_world/src/main/java/mc/world/generated_world/chunk/ChunkImpl.java @@ -3,11 +3,13 @@ package mc.world.generated_world.chunk; import lombok.Getter; import mc.core.block.Block; import mc.core.block.BlockType; +import mc.core.exception.ResourceUnloadException; import mc.core.world.Biome; import mc.core.world.Chunk; import mc.core.world.Region; import mc.core.world.World; +import java.lang.ref.Reference; import java.lang.ref.WeakReference; import static mc.world.generated_world.WorldConstants.WORLD_CHUNK_SIZE; @@ -20,7 +22,7 @@ public class ChunkImpl implements Chunk{ @Getter private final int z; private final Block[][][] blocks = new Block[WORLD_CHUNK_SIZE][WORLD_CHUNK_SIZE][WORLD_CHUNK_SIZE]; - private final transient WeakReference region; + private final transient Reference region; public ChunkImpl(int x, int y, int z, Region region) { this.x = x; @@ -109,6 +111,9 @@ public class ChunkImpl implements Chunk{ @Override public Region getRegion() { + if (region.get() == null) { + throw new ResourceUnloadException("Region is unloaded"); + } return region.get(); } diff --git a/generated_world/src/main/java/mc/world/generated_world/region/RegionImpl.java b/generated_world/src/main/java/mc/world/generated_world/region/RegionImpl.java index 643644d..c5aea75 100644 --- a/generated_world/src/main/java/mc/world/generated_world/region/RegionImpl.java +++ b/generated_world/src/main/java/mc/world/generated_world/region/RegionImpl.java @@ -3,6 +3,7 @@ package mc.world.generated_world.region; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import mc.core.exception.ResourceUnloadException; import mc.core.serialization.IRegionReaderWriter; import mc.core.serialization.Serializer; import mc.core.world.*; @@ -14,6 +15,7 @@ import org.springframework.beans.factory.annotation.Autowired; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.lang.ref.Reference; import java.lang.ref.WeakReference; import java.text.MessageFormat; @@ -27,7 +29,7 @@ public class RegionImpl implements Region{ private final int z; private final ChunkProxy[][][] chunks = new ChunkProxy[WORLD_REGION_SIZE/WORLD_CHUNK_SIZE][WORLD_REGION_SIZE/WORLD_CHUNK_SIZE][WORLD_REGION_SIZE/WORLD_CHUNK_SIZE]; private final Biome[][] biomes = new Biome[WORLD_REGION_SIZE][WORLD_REGION_SIZE]; - private final transient WeakReference world; + private final transient Reference world; @Autowired private ChunkLoader chunkLoader; @@ -79,6 +81,9 @@ public class RegionImpl implements Region{ @Override public World getWorld() { + if (world.get() == null) { + throw new ResourceUnloadException("World is unloaded"); + } return world.get(); } From 72989c60b7476f96ed694811df812def152738f5 Mon Sep 17 00:00:00 2001 From: Forwolk Date: Sat, 4 Aug 2018 14:56:03 +0300 Subject: [PATCH 27/62] Checking reference is not initialized --- core/src/main/java/mc/core/Location.java | 7 +++++-- .../mc/core/exception/ResourceUnloadException.java | 8 -------- .../mc/core/exception/ResourceUnloadedException.java | 8 ++++++++ .../java/mc/world/generated_world/chunk/ChunkImpl.java | 7 +++++-- .../mc/world/generated_world/region/RegionImpl.java | 10 ++++++---- 5 files changed, 24 insertions(+), 16 deletions(-) delete mode 100644 core/src/main/java/mc/core/exception/ResourceUnloadException.java create mode 100644 core/src/main/java/mc/core/exception/ResourceUnloadedException.java diff --git a/core/src/main/java/mc/core/Location.java b/core/src/main/java/mc/core/Location.java index 2816f25..6f5d7d7 100644 --- a/core/src/main/java/mc/core/Location.java +++ b/core/src/main/java/mc/core/Location.java @@ -5,7 +5,7 @@ package mc.core; import lombok.Data; -import mc.core.exception.ResourceUnloadException; +import mc.core.exception.ResourceUnloadedException; import mc.core.world.World; import java.io.Serializable; @@ -98,8 +98,11 @@ public class Location implements Serializable{ } public World getWorld () { + if (world == null) { + throw new IllegalStateException("World is not initialized"); + } if (world.get() == null) { - throw new ResourceUnloadException("You're trying to get unloaded world"); + throw new ResourceUnloadedException("You're trying to get unloaded world"); } return this.world.get(); } diff --git a/core/src/main/java/mc/core/exception/ResourceUnloadException.java b/core/src/main/java/mc/core/exception/ResourceUnloadException.java deleted file mode 100644 index b277411..0000000 --- a/core/src/main/java/mc/core/exception/ResourceUnloadException.java +++ /dev/null @@ -1,8 +0,0 @@ -package mc.core.exception; - -public class ResourceUnloadException extends McCoreUncheckedException { - - public ResourceUnloadException(String msg) { - super(msg); - } -} diff --git a/core/src/main/java/mc/core/exception/ResourceUnloadedException.java b/core/src/main/java/mc/core/exception/ResourceUnloadedException.java new file mode 100644 index 0000000..07ac21f --- /dev/null +++ b/core/src/main/java/mc/core/exception/ResourceUnloadedException.java @@ -0,0 +1,8 @@ +package mc.core.exception; + +public class ResourceUnloadedException extends McCoreUncheckedException { + + public ResourceUnloadedException(String msg) { + super(msg); + } +} diff --git a/generated_world/src/main/java/mc/world/generated_world/chunk/ChunkImpl.java b/generated_world/src/main/java/mc/world/generated_world/chunk/ChunkImpl.java index 5dd5951..d47cb1f 100644 --- a/generated_world/src/main/java/mc/world/generated_world/chunk/ChunkImpl.java +++ b/generated_world/src/main/java/mc/world/generated_world/chunk/ChunkImpl.java @@ -3,7 +3,7 @@ package mc.world.generated_world.chunk; import lombok.Getter; import mc.core.block.Block; import mc.core.block.BlockType; -import mc.core.exception.ResourceUnloadException; +import mc.core.exception.ResourceUnloadedException; import mc.core.world.Biome; import mc.core.world.Chunk; import mc.core.world.Region; @@ -111,8 +111,11 @@ public class ChunkImpl implements Chunk{ @Override public Region getRegion() { + if (region == null) { + throw new IllegalStateException("Region is not initialized"); + } if (region.get() == null) { - throw new ResourceUnloadException("Region is unloaded"); + throw new ResourceUnloadedException("Region is unloaded"); } return region.get(); } diff --git a/generated_world/src/main/java/mc/world/generated_world/region/RegionImpl.java b/generated_world/src/main/java/mc/world/generated_world/region/RegionImpl.java index c5aea75..69bfbcb 100644 --- a/generated_world/src/main/java/mc/world/generated_world/region/RegionImpl.java +++ b/generated_world/src/main/java/mc/world/generated_world/region/RegionImpl.java @@ -1,15 +1,14 @@ package mc.world.generated_world.region; import lombok.Getter; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import mc.core.exception.ResourceUnloadException; +import mc.core.exception.ResourceUnloadedException; import mc.core.serialization.IRegionReaderWriter; import mc.core.serialization.Serializer; import mc.core.world.*; -import mc.world.generated_world.chunk.InMemoryCacheChunkLoader; import mc.world.generated_world.chunk.ChunkImpl; import mc.world.generated_world.chunk.ChunkProxy; +import mc.world.generated_world.chunk.InMemoryCacheChunkLoader; import org.springframework.beans.factory.annotation.Autowired; import java.io.File; @@ -81,8 +80,11 @@ public class RegionImpl implements Region{ @Override public World getWorld() { + if (world == null) { + throw new IllegalStateException("World is not initialized"); + } if (world.get() == null) { - throw new ResourceUnloadException("World is unloaded"); + throw new ResourceUnloadedException("World is unloaded"); } return world.get(); } From aa001b5fe2b894793776d4c8b1f8e11919d3035a Mon Sep 17 00:00:00 2001 From: Forwolk Date: Sat, 4 Aug 2018 16:17:31 +0300 Subject: [PATCH 28/62] fix: return null if reference is null --- core/src/main/java/mc/core/Location.java | 2 +- .../src/main/java/mc/world/generated_world/chunk/ChunkImpl.java | 2 +- .../main/java/mc/world/generated_world/region/RegionImpl.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/mc/core/Location.java b/core/src/main/java/mc/core/Location.java index 6f5d7d7..6f58136 100644 --- a/core/src/main/java/mc/core/Location.java +++ b/core/src/main/java/mc/core/Location.java @@ -99,7 +99,7 @@ public class Location implements Serializable{ public World getWorld () { if (world == null) { - throw new IllegalStateException("World is not initialized"); + return null; } if (world.get() == null) { throw new ResourceUnloadedException("You're trying to get unloaded world"); diff --git a/generated_world/src/main/java/mc/world/generated_world/chunk/ChunkImpl.java b/generated_world/src/main/java/mc/world/generated_world/chunk/ChunkImpl.java index d47cb1f..2eb78a0 100644 --- a/generated_world/src/main/java/mc/world/generated_world/chunk/ChunkImpl.java +++ b/generated_world/src/main/java/mc/world/generated_world/chunk/ChunkImpl.java @@ -112,7 +112,7 @@ public class ChunkImpl implements Chunk{ @Override public Region getRegion() { if (region == null) { - throw new IllegalStateException("Region is not initialized"); + return null; } if (region.get() == null) { throw new ResourceUnloadedException("Region is unloaded"); diff --git a/generated_world/src/main/java/mc/world/generated_world/region/RegionImpl.java b/generated_world/src/main/java/mc/world/generated_world/region/RegionImpl.java index 69bfbcb..1c48196 100644 --- a/generated_world/src/main/java/mc/world/generated_world/region/RegionImpl.java +++ b/generated_world/src/main/java/mc/world/generated_world/region/RegionImpl.java @@ -81,7 +81,7 @@ public class RegionImpl implements Region{ @Override public World getWorld() { if (world == null) { - throw new IllegalStateException("World is not initialized"); + return null; } if (world.get() == null) { throw new ResourceUnloadedException("World is unloaded"); From 60cbec2119e9eb51fedad6c0946c424a98c9b8fc Mon Sep 17 00:00:00 2001 From: Daniil Date: Sat, 4 Aug 2018 23:46:58 +0700 Subject: [PATCH 29/62] Changed runner blocking mechanism --- .../mc/core/events/runner/ExecutorThread.java | 10 ++++++++-- .../core/events/runner/ResourceRunnable.java | 12 +++++------ .../mc/core/events/EventExecutorTest.java | 20 +++---------------- 3 files changed, 17 insertions(+), 25 deletions(-) diff --git a/event-loop/src/main/java/mc/core/events/runner/ExecutorThread.java b/event-loop/src/main/java/mc/core/events/runner/ExecutorThread.java index 4c12b26..9797fde 100644 --- a/event-loop/src/main/java/mc/core/events/runner/ExecutorThread.java +++ b/event-loop/src/main/java/mc/core/events/runner/ExecutorThread.java @@ -1,5 +1,7 @@ package mc.core.events.runner; +import java.util.concurrent.locks.Lock; + public class ExecutorThread extends Thread { private EventExecutorService service; @@ -23,11 +25,15 @@ public class ExecutorThread extends Thread { } void executeTask(ResourceRunnable runnable) { - runnable.lock(); + for (Lock lock : runnable.getLocks()) { + lock.lock(); + } try { runnable.run(); } finally { - runnable.unlock(); + for (Lock lock : runnable.getLocks()) { + lock.unlock(); + } } runnable.after(); } diff --git a/event-loop/src/main/java/mc/core/events/runner/ResourceRunnable.java b/event-loop/src/main/java/mc/core/events/runner/ResourceRunnable.java index 74d9528..3a7c54e 100644 --- a/event-loop/src/main/java/mc/core/events/runner/ResourceRunnable.java +++ b/event-loop/src/main/java/mc/core/events/runner/ResourceRunnable.java @@ -1,12 +1,12 @@ package mc.core.events.runner; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.locks.Lock; + public interface ResourceRunnable extends Runnable { - default void lock() { - - } - - default void unlock() { - + default List getLocks() { + return Collections.emptyList(); } default void after() { diff --git a/event-loop/src/test/java/mc/core/events/EventExecutorTest.java b/event-loop/src/test/java/mc/core/events/EventExecutorTest.java index 871f02e..99556de 100644 --- a/event-loop/src/test/java/mc/core/events/EventExecutorTest.java +++ b/event-loop/src/test/java/mc/core/events/EventExecutorTest.java @@ -1,7 +1,6 @@ package mc.core.events; import mc.core.events.runner.EventExecutorService; -import mc.core.events.runner.ResourceRunnable; import org.junit.Assert; import org.junit.Test; @@ -17,22 +16,9 @@ public class EventExecutorTest { CountDownLatch latch = new CountDownLatch(1); EventExecutorService service = new EventExecutorService(1); service.start(); - service.addTask(new ResourceRunnable() { - @Override - public void lock() { - - } - - @Override - public void unlock() { - - } - - @Override - public void run() { - testVariable.set(true); - latch.countDown(); - } + service.addTask(() -> { + testVariable.set(true); + latch.countDown(); }); latch.await(1, TimeUnit.SECONDS); From 5928cb8913700126ac6519801a46a4b20a96316a Mon Sep 17 00:00:00 2001 From: Daniil Date: Sat, 4 Aug 2018 23:47:38 +0700 Subject: [PATCH 30/62] Introduced event-based resource getter interfaces --- .../interfaces/LocationProvidingEvent.java | 9 ++++++ .../api/interfaces/PlayerProvidingEvent.java | 22 +++++++++++++++ .../api/interfaces/WorldProvidingEvent.java | 10 +++++++ .../events/api/samples/BlockBreakEvent.java | 28 +++++++++++++++++++ 4 files changed, 69 insertions(+) create mode 100644 event-loop/src/main/java/mc/core/events/api/interfaces/LocationProvidingEvent.java create mode 100644 event-loop/src/main/java/mc/core/events/api/interfaces/PlayerProvidingEvent.java create mode 100644 event-loop/src/main/java/mc/core/events/api/interfaces/WorldProvidingEvent.java create mode 100644 event-loop/src/main/java/mc/core/events/api/samples/BlockBreakEvent.java diff --git a/event-loop/src/main/java/mc/core/events/api/interfaces/LocationProvidingEvent.java b/event-loop/src/main/java/mc/core/events/api/interfaces/LocationProvidingEvent.java new file mode 100644 index 0000000..bec7040 --- /dev/null +++ b/event-loop/src/main/java/mc/core/events/api/interfaces/LocationProvidingEvent.java @@ -0,0 +1,9 @@ +package mc.core.events.api.interfaces; + +import mc.core.Location; + +import java.util.Collection; + +public interface LocationProvidingEvent { + Collection getAssociatedLocations(); +} diff --git a/event-loop/src/main/java/mc/core/events/api/interfaces/PlayerProvidingEvent.java b/event-loop/src/main/java/mc/core/events/api/interfaces/PlayerProvidingEvent.java new file mode 100644 index 0000000..b434061 --- /dev/null +++ b/event-loop/src/main/java/mc/core/events/api/interfaces/PlayerProvidingEvent.java @@ -0,0 +1,22 @@ +package mc.core.events.api.interfaces; + +import mc.core.Location; +import mc.core.player.Player; +import sun.reflect.generics.reflectiveObjects.NotImplementedException; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +public interface PlayerProvidingEvent extends LocationProvidingEvent { + List getAssociatedPlayers(); + + @Override + default Collection getAssociatedLocations() { + List players = getAssociatedPlayers(); + if (players.size() == 1) + return Collections.singletonList(players.get(0).getLocation()); + else + throw new NotImplementedException(); + } +} diff --git a/event-loop/src/main/java/mc/core/events/api/interfaces/WorldProvidingEvent.java b/event-loop/src/main/java/mc/core/events/api/interfaces/WorldProvidingEvent.java new file mode 100644 index 0000000..9f561e5 --- /dev/null +++ b/event-loop/src/main/java/mc/core/events/api/interfaces/WorldProvidingEvent.java @@ -0,0 +1,10 @@ +package mc.core.events.api.interfaces; + +import mc.core.world.World; + +import java.util.Collection; + +public interface WorldProvidingEvent { + Collection getAssociatedWorlds(); + +} diff --git a/event-loop/src/main/java/mc/core/events/api/samples/BlockBreakEvent.java b/event-loop/src/main/java/mc/core/events/api/samples/BlockBreakEvent.java new file mode 100644 index 0000000..b0f3044 --- /dev/null +++ b/event-loop/src/main/java/mc/core/events/api/samples/BlockBreakEvent.java @@ -0,0 +1,28 @@ +package mc.core.events.api.samples; + +import mc.core.Location; +import mc.core.events.EventBase; +import mc.core.events.api.interfaces.LocationProvidingEvent; +import mc.core.events.api.interfaces.PlayerProvidingEvent; +import mc.core.player.Player; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +public class BlockBreakEvent extends EventBase implements PlayerProvidingEvent, LocationProvidingEvent { + private Player player; + // TODO: There should be a proper block reference + private Location blockLocation; + + + @Override + public List getAssociatedPlayers() { + return Collections.singletonList(player); + } + + @Override + public Collection getAssociatedLocations() { + return Collections.singletonList(blockLocation); + } +} From 8a2b2eb1f500342e1140e26a543c5b0f8ee73b20 Mon Sep 17 00:00:00 2001 From: Daniil Date: Sat, 4 Aug 2018 23:54:50 +0700 Subject: [PATCH 31/62] First implementation for plugin synchronization --- ...ntPipeline.java => EventPipelineTask.java} | 14 +++++++++++++- .../mc/core/events/FullAsyncEventLoop.java | 19 +++++++++++-------- .../mc/core/events/SharedResourceManager.java | 17 +++++++++++++++++ 3 files changed, 41 insertions(+), 9 deletions(-) rename event-loop/src/main/java/mc/core/events/{EventPipeline.java => EventPipelineTask.java} (83%) create mode 100644 event-loop/src/main/java/mc/core/events/SharedResourceManager.java diff --git a/event-loop/src/main/java/mc/core/events/EventPipeline.java b/event-loop/src/main/java/mc/core/events/EventPipelineTask.java similarity index 83% rename from event-loop/src/main/java/mc/core/events/EventPipeline.java rename to event-loop/src/main/java/mc/core/events/EventPipelineTask.java index e3a2910..f8303c1 100644 --- a/event-loop/src/main/java/mc/core/events/EventPipeline.java +++ b/event-loop/src/main/java/mc/core/events/EventPipelineTask.java @@ -9,12 +9,14 @@ import mc.core.events.runner.EventExecutorService; import mc.core.events.runner.ResourceRunnable; import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; import java.util.List; +import java.util.concurrent.locks.Lock; @RequiredArgsConstructor @Getter @Slf4j -public class EventPipeline { +public class EventPipelineTask { private final List handlers; private final FullAsyncEventLoop manager; private final Event event; @@ -39,6 +41,11 @@ public class EventPipeline { RegisteredEventHandler handler = handlers.get(currentIndex); if (!event.isCanceled() || !handler.isIgnoreCancelled()) { + List locks = new ArrayList<>(); + + if (handler.isPluginSynchronize()) + locks.add(manager.getResourceManager().getPluginLock(handler.getPlugin())); + service.addTask(new ResourceRunnable() { @Override public void run() { @@ -54,6 +61,11 @@ public class EventPipeline { currentIndex++; next(service); } + + @Override + public List getLocks() { + return locks; + } }); } else { currentIndex++; diff --git a/event-loop/src/main/java/mc/core/events/FullAsyncEventLoop.java b/event-loop/src/main/java/mc/core/events/FullAsyncEventLoop.java index 2b8db05..89e6281 100644 --- a/event-loop/src/main/java/mc/core/events/FullAsyncEventLoop.java +++ b/event-loop/src/main/java/mc/core/events/FullAsyncEventLoop.java @@ -17,10 +17,11 @@ import java.util.concurrent.ConcurrentHashMap; public class FullAsyncEventLoop { Map, List> handlers = new HashMap<>(); // Item leaves this queue only when EventPipeline is fully executed - private Map> eventQueue = new ConcurrentHashMap<>(); + private Map> eventQueue = new ConcurrentHashMap<>(); @Autowired @Setter private EventExecutorService eventExecutorService; + private SharedResourceManager resourceManager = new SharedResourceManager(); public void addEventHandler(Plugin plugin, Object object) { Map candidates = getEventHandlerCandidates(object); @@ -73,8 +74,8 @@ public class FullAsyncEventLoop { if (handlers == null) return; - Queue queue = eventQueue.computeIfAbsent(owner, s -> new ArrayDeque<>()); - queue.add(new EventPipeline(handlers, this, event, owner)); + Queue queue = eventQueue.computeIfAbsent(owner, s -> new ArrayDeque<>()); + queue.add(new EventPipelineTask(handlers, this, event, owner)); update(owner); } @@ -91,22 +92,24 @@ public class FullAsyncEventLoop { log.warn("Unable to update pipeline executor: unable to find queue"); return; } - Queue queue = eventQueue.get(owner); + Queue queue = eventQueue.get(owner); if (queue.isEmpty()) { log.warn("Unable to update pipeline executor: queue is empty"); return; } - if (queue.peek().getState() == EventPipeline.PipelineState.FINISHED) { + if (queue.peek().getState() == EventPipelineTask.PipelineState.FINISHED) { queue.poll(); } - EventPipeline pipeline; + EventPipelineTask pipeline; if ((pipeline = queue.peek()) != null - && pipeline.getState() == EventPipeline.PipelineState.IDLE) { + && pipeline.getState() == EventPipelineTask.PipelineState.IDLE) { pipeline.next(eventExecutorService); } } - + public SharedResourceManager getResourceManager() { + return resourceManager; + } } diff --git a/event-loop/src/main/java/mc/core/events/SharedResourceManager.java b/event-loop/src/main/java/mc/core/events/SharedResourceManager.java new file mode 100644 index 0000000..12f6812 --- /dev/null +++ b/event-loop/src/main/java/mc/core/events/SharedResourceManager.java @@ -0,0 +1,17 @@ +package mc.core.events; + +import mc.core.events.api.Plugin; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +public class SharedResourceManager { + private Map pluginLocks = new ConcurrentHashMap<>(); + + public Lock getPluginLock(Plugin plugin) { + return pluginLocks.computeIfAbsent(plugin, s -> new ReentrantLock()); + } + +} From 581fad36c291c7d476a338c865e02287f80e00e9 Mon Sep 17 00:00:00 2001 From: Daniil Date: Sat, 4 Aug 2018 23:57:59 +0700 Subject: [PATCH 32/62] Log dispatch refactoring --- .../java/mc/core/events/EventPipelineTask.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/event-loop/src/main/java/mc/core/events/EventPipelineTask.java b/event-loop/src/main/java/mc/core/events/EventPipelineTask.java index f8303c1..bed7d5d 100644 --- a/event-loop/src/main/java/mc/core/events/EventPipelineTask.java +++ b/event-loop/src/main/java/mc/core/events/EventPipelineTask.java @@ -41,10 +41,7 @@ public class EventPipelineTask { RegisteredEventHandler handler = handlers.get(currentIndex); if (!event.isCanceled() || !handler.isIgnoreCancelled()) { - List locks = new ArrayList<>(); - - if (handler.isPluginSynchronize()) - locks.add(manager.getResourceManager().getPluginLock(handler.getPlugin())); + List locks = getLocks(handler); service.addTask(new ResourceRunnable() { @Override @@ -73,6 +70,16 @@ public class EventPipelineTask { } } + private List getLocks(RegisteredEventHandler handler) { + List locks = new ArrayList<>(); + + if (handler.isPluginSynchronize()) + locks.add(manager.getResourceManager().getPluginLock(handler.getPlugin())); + + + return locks; + } + public enum PipelineState { IDLE, WORKING, FINISHED } From 32fa42bdc3c16381d616a0e5271ff8f282ea6c47 Mon Sep 17 00:00:00 2001 From: Daniil Date: Sun, 5 Aug 2018 00:12:05 +0700 Subject: [PATCH 33/62] Merge tweak --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 529d214..3226f44 100644 --- a/build.gradle +++ b/build.gradle @@ -33,7 +33,7 @@ subprojects { /* Components */ compile (group: 'org.projectlombok', name: 'lombok', version: '1.16.16') - + compile 'com.flowpowered:flow-nbt:1.0.0' //Named Binary Tags testCompile 'junit:junit:4.12' } From be98784668fbaabc03989dafb485ba50f521573b Mon Sep 17 00:00:00 2001 From: Forwolk Date: Sat, 4 Aug 2018 20:43:07 +0300 Subject: [PATCH 34/62] renamed: Chunk --> ChunkSection --- core/src/main/java/mc/core/Location.java | 1 + .../mc/core/serialization/IChunkReader.java | 4 +- .../main/java/mc/core/world/ChunkLoader.java | 4 +- .../world/{Chunk.java => ChunkSection.java} | 2 +- core/src/main/java/mc/core/world/Region.java | 6 +- core/src/main/java/mc/core/world/World.java | 5 +- .../main/java/mc/world/flat/FlatWorld.java | 8 +- .../main/java/mc/world/flat/SimpleChunk.java | 119 -------------- .../generated_world/chunk/ChunkImpl.java | 127 --------------- .../generated_world/chunk/ChunkProxy.java | 146 ------------------ .../chunk/InMemoryCacheChunkLoader.java | 20 +-- .../generator/SeedBasedWorldGenerator.java | 23 ++- .../generated_world/region/RegionImpl.java | 32 ++-- .../BlockSerializerDeserializer.java | 14 +- .../serialization/ChunkReader.java | 12 +- .../serialization/ChunkSerializer.java | 10 +- .../generated_world/world/CubicWorld.java | 10 +- 17 files changed, 75 insertions(+), 468 deletions(-) rename core/src/main/java/mc/core/world/{Chunk.java => ChunkSection.java} (96%) delete mode 100644 flat_world/src/main/java/mc/world/flat/SimpleChunk.java delete mode 100644 generated_world/src/main/java/mc/world/generated_world/chunk/ChunkImpl.java delete mode 100644 generated_world/src/main/java/mc/world/generated_world/chunk/ChunkProxy.java diff --git a/core/src/main/java/mc/core/Location.java b/core/src/main/java/mc/core/Location.java index 6f58136..191aa99 100644 --- a/core/src/main/java/mc/core/Location.java +++ b/core/src/main/java/mc/core/Location.java @@ -110,4 +110,5 @@ public class Location implements Serializable{ public void setWorld (World world) { this.world = new WeakReference<>(world); } + } diff --git a/core/src/main/java/mc/core/serialization/IChunkReader.java b/core/src/main/java/mc/core/serialization/IChunkReader.java index 0ecf126..a332385 100644 --- a/core/src/main/java/mc/core/serialization/IChunkReader.java +++ b/core/src/main/java/mc/core/serialization/IChunkReader.java @@ -1,10 +1,10 @@ package mc.core.serialization; -import mc.core.world.Chunk; +import mc.core.world.ChunkSection; import mc.core.world.Region; import java.io.IOException; public interface IChunkReader { - Chunk read (Region region, int x, int y, int z) throws IOException; + ChunkSection read (Region region, int x, int y, int z) throws IOException; } diff --git a/core/src/main/java/mc/core/world/ChunkLoader.java b/core/src/main/java/mc/core/world/ChunkLoader.java index a8213e4..4a0c7b3 100644 --- a/core/src/main/java/mc/core/world/ChunkLoader.java +++ b/core/src/main/java/mc/core/world/ChunkLoader.java @@ -12,7 +12,7 @@ public interface ChunkLoader { * @param z chunk position * @return optional of chunk (nullable) */ - Optional loadChunk (int x, int y, int z); + Optional loadChunk (int x, int y, int z); /** * Tries to load chunk like {@link #loadChunk(int, int, int)} @@ -23,5 +23,5 @@ public interface ChunkLoader { * @param z chunk position * @return chunk */ - Chunk loadOrGenerateChunk (int x, int y, int z); + ChunkSection loadOrGenerateChunk (int x, int y, int z); } diff --git a/core/src/main/java/mc/core/world/Chunk.java b/core/src/main/java/mc/core/world/ChunkSection.java similarity index 96% rename from core/src/main/java/mc/core/world/Chunk.java rename to core/src/main/java/mc/core/world/ChunkSection.java index 6b63845..3cd2d59 100644 --- a/core/src/main/java/mc/core/world/Chunk.java +++ b/core/src/main/java/mc/core/world/ChunkSection.java @@ -22,7 +22,7 @@ import java.io.Serializable; * */ /* 16x16x16 */ -public interface Chunk extends Serializable{ +public interface ChunkSection extends Serializable{ int getBlockType(int x, int y, int z); void setBlockType(int x, int y, int z, int type); diff --git a/core/src/main/java/mc/core/world/Region.java b/core/src/main/java/mc/core/world/Region.java index e2f1371..17e936b 100644 --- a/core/src/main/java/mc/core/world/Region.java +++ b/core/src/main/java/mc/core/world/Region.java @@ -21,8 +21,8 @@ import java.io.Serializable; * */ public interface Region extends Serializable{ - Chunk getChunkAt(int x, int y, int z); - void setChunk(int x, int y, int z, Chunk chunk); + ChunkSection getChunkAt(int x, int y, int z); + void setChunk(int x, int y, int z, ChunkSection chunkSection); int getX(); int getZ(); @@ -32,5 +32,5 @@ public interface Region extends Serializable{ World getWorld(); - void save(Serializer chunkSerializer, IRegionReaderWriter regionReaderWritter) throws IOException; + void save(Serializer chunkSerializer, IRegionReaderWriter regionReaderWritter) throws IOException; } diff --git a/core/src/main/java/mc/core/world/World.java b/core/src/main/java/mc/core/world/World.java index bc18bf8..1202b4c 100644 --- a/core/src/main/java/mc/core/world/World.java +++ b/core/src/main/java/mc/core/world/World.java @@ -4,7 +4,6 @@ */ package mc.core.world; -import mc.core.Location; import mc.core.WarpPosition; import mc.core.nbt.Taggable; @@ -49,8 +48,8 @@ public interface World extends Taggable, Serializable{ WarpPosition getSpawn(); void setSpawn(WarpPosition location); - Chunk getChunk(int x, int y, int z); - void setChunk(int x, int y, int z, Chunk chunk); + ChunkSection getChunk(int x, int y, int z); + void setChunk(int x, int y, int z, ChunkSection chunkSection); Region getRegion(int x, int z); void setRegion(int x, int z, Region region); diff --git a/flat_world/src/main/java/mc/world/flat/FlatWorld.java b/flat_world/src/main/java/mc/world/flat/FlatWorld.java index 4f7ef22..62c32fe 100644 --- a/flat_world/src/main/java/mc/world/flat/FlatWorld.java +++ b/flat_world/src/main/java/mc/world/flat/FlatWorld.java @@ -25,7 +25,7 @@ public class FlatWorld implements World { @Getter @Setter private WarpPosition spawn = new WarpPosition(new Location(0, 6, 0), new Look(0, 0)); - private Chunk chunk = new SimpleChunk(); + private ChunkSection chunkSection = new SimpleChunkSection(); @Override public IWorldType getWorldType() { @@ -33,12 +33,12 @@ public class FlatWorld implements World { } @Override - public Chunk getChunk(int x, int y, int z) { - return chunk; + public ChunkSection getChunk(int x, int y, int z) { + return chunkSection; } @Override - public void setChunk(int x, int y, int z, Chunk chunk) { + public void setChunk(int x, int y, int z, ChunkSection chunkSection) { throw new UnsupportedOperationException(); } diff --git a/flat_world/src/main/java/mc/world/flat/SimpleChunk.java b/flat_world/src/main/java/mc/world/flat/SimpleChunk.java deleted file mode 100644 index ee5eaab..0000000 --- a/flat_world/src/main/java/mc/world/flat/SimpleChunk.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * DmitriyMX - * 2018-04-28 - */ -package mc.world.flat; - -import mc.core.block.Block; -import mc.core.block.BlockFactory; -import mc.core.block.BlockType; -import mc.core.world.Biome; -import mc.core.world.Chunk; -import mc.core.world.Region; -import mc.core.world.World; - -public class SimpleChunk implements Chunk { - @Override - public int getBlockType(int x, int y, int z) { - if (y == 0) return 7; - else if (y >= 1 && y <= 2) return 3; - else if (y == 3) return 2; - else return 0; - } - - @Override - public void setBlockType(int x, int y, int z, int type) { - - } - - @Override - public int getBlockMetadata(int x, int y, int z) { - return 0; - } - - @Override - public void setBlockMetadata(int x, int y, int z, int metadata) { - - } - - @Override - public int getBlockLight(int x, int y, int z) { - return 0; - } - - @Override - public void setBlockLight(int x, int y, int z, int lightLevel) { - - } - - @Override - public int getSkyLight(int x, int y, int z) { - if (y <= 3) return 0; - else return 15; - } - - @Override - public void setSkyLight(int x, int y, int z, int lightLevel) { - - } - - @Override - public int getAddition(int x, int y, int z) { - return 0; - } - - @Override - public void setAddition(int x, int y, int z, int value) { - - } - - @Override - public Biome getBiome(int x, int z) { - return Biome.PLAINS; - } - - @Override - public void setBiome(int x, int z, Biome biome) { - - } - - @Override - public int getX() { - return 0; - } - - @Override - public int getY() { - return 0; - } - - @Override - public int getZ() { - return 0; - } - - @Override - public void setBlock(int x, int y, int z, Block block) { - - } - - @Override - public Block getBlock(int x, int y, int z) { - BlockFactory blockFactory = new BlockFactory(); - - if (y == 0) return blockFactory.create(BlockType.BEDROCK, 0); - else if (y >= 1 && y <= 2) return blockFactory.create(BlockType.DIRT, 0); - else if (y == 3) return blockFactory.create(BlockType.GRASS, 0); - else return Block.airBlock(x, y, z); - } - - @Override - public Region getRegion() { - throw new UnsupportedOperationException(); - } - - @Override - public World getWorld() { - throw new UnsupportedOperationException(); - } -} diff --git a/generated_world/src/main/java/mc/world/generated_world/chunk/ChunkImpl.java b/generated_world/src/main/java/mc/world/generated_world/chunk/ChunkImpl.java deleted file mode 100644 index 2eb78a0..0000000 --- a/generated_world/src/main/java/mc/world/generated_world/chunk/ChunkImpl.java +++ /dev/null @@ -1,127 +0,0 @@ -package mc.world.generated_world.chunk; - -import lombok.Getter; -import mc.core.block.Block; -import mc.core.block.BlockType; -import mc.core.exception.ResourceUnloadedException; -import mc.core.world.Biome; -import mc.core.world.Chunk; -import mc.core.world.Region; -import mc.core.world.World; - -import java.lang.ref.Reference; -import java.lang.ref.WeakReference; - -import static mc.world.generated_world.WorldConstants.WORLD_CHUNK_SIZE; - -public class ChunkImpl implements Chunk{ - @Getter - private final int x; - @Getter - private final int y; - @Getter - private final int z; - private final Block[][][] blocks = new Block[WORLD_CHUNK_SIZE][WORLD_CHUNK_SIZE][WORLD_CHUNK_SIZE]; - private final transient Reference region; - - public ChunkImpl(int x, int y, int z, Region region) { - this.x = x; - this.y = y; - this.z = z; - this.region = new WeakReference<>(region); - } - - @Override - public int getBlockType(int x, int y, int z) { - return blocks[x][y][z].getId(); - } - - @Override - public void setBlockType(int x, int y, int z, int type) { - - } - - @Override - public int getBlockMetadata(int x, int y, int z) { - return 0; - } - - @Override - public void setBlockMetadata(int x, int y, int z, int metadata) { - - } - - @Override - public int getBlockLight(int x, int y, int z) { - return 15; - } - - @Override - public void setBlockLight(int x, int y, int z, int lightLevel) { - - } - - @Override - public int getSkyLight(int x, int y, int z) { - return 15; - } - - @Override - public void setSkyLight(int x, int y, int z, int lightLevel) { - - } - - @Override - public int getAddition(int x, int y, int z) { - return 0; - } - - @Override - public void setAddition(int x, int y, int z, int value) { - - } - - @Override - public Biome getBiome(int x, int z) { - return getRegion().getBiomeAt(x + this.x * WORLD_CHUNK_SIZE,z + this.z * WORLD_CHUNK_SIZE); - } - - @Override - public void setBiome(int x, int z, Biome biome) { - getRegion().setBiome(x + this.x * WORLD_CHUNK_SIZE,z + this.z * WORLD_CHUNK_SIZE, biome); - } - - @Override - public void setBlock(int x, int y, int z, Block block) { - if (block.getBlockType() == BlockType.AIR) { - blocks[x][y][z] = null; - return; - } - blocks[x][y][z] = block; - } - - @Override - public Block getBlock(int x, int y, int z) { - Block block = blocks[x][y][z]; - if (block == null) { - return Block.airBlock(x, y, z); - } - return blocks[x][y][z]; - } - - @Override - public Region getRegion() { - if (region == null) { - return null; - } - if (region.get() == null) { - throw new ResourceUnloadedException("Region is unloaded"); - } - return region.get(); - } - - @Override - public World getWorld() { - return getRegion().getWorld(); - } -} diff --git a/generated_world/src/main/java/mc/world/generated_world/chunk/ChunkProxy.java b/generated_world/src/main/java/mc/world/generated_world/chunk/ChunkProxy.java deleted file mode 100644 index fb03d6d..0000000 --- a/generated_world/src/main/java/mc/world/generated_world/chunk/ChunkProxy.java +++ /dev/null @@ -1,146 +0,0 @@ -package mc.world.generated_world.chunk; - -import mc.core.block.Block; -import mc.core.world.Biome; -import mc.core.world.Chunk; -import mc.core.world.Region; -import mc.core.world.World; - -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -public class ChunkProxy implements Chunk { - private final Chunk chunk; - private volatile transient long lastUsage = System.currentTimeMillis(); - private final transient ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); - - public ChunkProxy(Chunk chunk) { - this.chunk = chunk; - } - - public long getLastUsage() { - synchronized (chunk) { - return lastUsage; - } - } - - private final void use () { - synchronized (chunk) { - lastUsage = System.currentTimeMillis(); - } - } - - @Override - public int getBlockType(int x, int y, int z) { - use(); - return chunk.getBlockType(x, y, z); - } - - @Override - public void setBlockType(int x, int y, int z, int type) { - use(); - chunk.setBlockType(x, y, z, type); - } - - @Override - public int getBlockMetadata(int x, int y, int z) { - use(); - return chunk.getBlockMetadata(x, y, z); - } - - @Override - public void setBlockMetadata(int x, int y, int z, int metadata) { - use(); - chunk.setBlockMetadata(x, y, z, metadata); - } - - @Override - public int getBlockLight(int x, int y, int z) { - use(); - return chunk.getBlockLight(x, y, z); - } - - @Override - public void setBlockLight(int x, int y, int z, int lightLevel) { - use(); - chunk.setBlockLight(x, y, z, lightLevel); - } - - @Override - public int getSkyLight(int x, int y, int z) { - use(); - return chunk.getSkyLight(x, y, z); - } - - @Override - public void setSkyLight(int x, int y, int z, int lightLevel) { - use(); - chunk.setSkyLight(x, y, z, lightLevel); - } - - @Override - public int getAddition(int x, int y, int z) { - use(); - return chunk.getAddition(x, y, z); - } - - @Override - public void setAddition(int x, int y, int z, int value) { - use(); - chunk.setAddition(x, y, z, value); - } - - @Override - public Biome getBiome(int x, int z) { - use(); - return chunk.getBiome(x, z); - } - - @Override - public void setBiome(int x, int z, Biome biome) { - use(); - chunk.setBiome(x, z, biome); - } - - @Override - public int getX() { - use(); - return chunk.getX(); - } - - @Override - public int getY() { - use(); - return chunk.getY(); - } - - @Override - public int getZ() { - use(); - return chunk.getZ(); - } - - @Override - public void setBlock(int x, int y, int z, Block block) { - use(); - chunk.setBlock(x, y, z, block); - } - - @Override - public Block getBlock(int x, int y, int z) { - use(); - return chunk.getBlock(x, y, z); - } - - @Override - public Region getRegion() { - use(); - return chunk.getRegion(); - } - - @Override - public World getWorld() { - use(); - return chunk.getWorld(); - } -} diff --git a/generated_world/src/main/java/mc/world/generated_world/chunk/InMemoryCacheChunkLoader.java b/generated_world/src/main/java/mc/world/generated_world/chunk/InMemoryCacheChunkLoader.java index 2724497..f304f84 100644 --- a/generated_world/src/main/java/mc/world/generated_world/chunk/InMemoryCacheChunkLoader.java +++ b/generated_world/src/main/java/mc/world/generated_world/chunk/InMemoryCacheChunkLoader.java @@ -25,7 +25,7 @@ public class InMemoryCacheChunkLoader implements ChunkLoader { @Autowired private ChunkReader chunkReader; @Autowired - private Serializer chunkSerializer; + private Serializer chunkSerializer; @Autowired private RegionReaderWriter regionReaderWritter; @@ -44,14 +44,14 @@ public class InMemoryCacheChunkLoader implements ChunkLoader { } @Override - public Optional loadChunk(int x, int y, int z) { + public Optional loadChunk(int x, int y, int z) { File file = getChuckFile(x, y, z); if (!file.exists()) { return Optional.empty(); } else { try { - Chunk chunk = chunkReader.read(world.getRegion(x / WORLD_CHUNK_SIZE, z / WORLD_CHUNK_SIZE), x, y, z); - return Optional.of(chunk); + ChunkSection chunkSection = chunkReader.read(world.getRegion(x / WORLD_CHUNK_SIZE, z / WORLD_CHUNK_SIZE), x, y, z); + return Optional.of(chunkSection); } catch (IOException e) { log.error("Error occurred while reading chunk file: " + file.getAbsolutePath(), e); return Optional.empty(); @@ -60,12 +60,12 @@ public class InMemoryCacheChunkLoader implements ChunkLoader { } @Override - public Chunk loadOrGenerateChunk(int x, int y, int z) { + public ChunkSection loadOrGenerateChunk(int x, int y, int z) { int regX = x / WORLD_CHUNK_SIZE; int regZ = z / WORLD_CHUNK_SIZE; File regionFile = new File(worldFolder, MessageFormat.format(REGION_FILE_NAME_TEMPLATE, regX, regZ)); Region region; - Chunk chunk; + ChunkSection chunkSection; if (!regionFile.exists()) { log.debug("Region [{}, {}] not found. Generating!", regX, regZ); regionFile.mkdirs(); @@ -76,17 +76,17 @@ public class InMemoryCacheChunkLoader implements ChunkLoader { log.error("Error occurred while writting biome file", e); } saveRegion(region); - chunk = region.getChunkAt(x % WORLD_CHUNK_SIZE, y % WORLD_CHUNK_SIZE, z % WORLD_CHUNK_SIZE); + chunkSection = region.getChunkAt(x % WORLD_CHUNK_SIZE, y % WORLD_CHUNK_SIZE, z % WORLD_CHUNK_SIZE); } else { try { region = regionReaderWritter.read(regX, regZ, world); - chunk = chunkReader.read(region, x, y, z); + chunkSection = chunkReader.read(region, x, y, z); } catch (IOException e) { - log.error("Error occurred while reading chunk file", e); + log.error("Error occurred while reading chunkSection file", e); return null; } } - return chunk; + return chunkSection; } private void saveRegion (Region region) { diff --git a/generated_world/src/main/java/mc/world/generated_world/generator/SeedBasedWorldGenerator.java b/generated_world/src/main/java/mc/world/generated_world/generator/SeedBasedWorldGenerator.java index 381173f..1b5d8dd 100644 --- a/generated_world/src/main/java/mc/world/generated_world/generator/SeedBasedWorldGenerator.java +++ b/generated_world/src/main/java/mc/world/generated_world/generator/SeedBasedWorldGenerator.java @@ -8,7 +8,6 @@ import mc.core.world.*; import mc.world.generated_world.region.RegionImpl; import mc.world.generated_world.serialization.ChunkSerializer; import mc.world.generated_world.serialization.RegionReaderWriter; -import mc.world.generated_world.serialization.WorldReaderWriter; import mc.world.generated_world.world.CubicWorld; import mc.world.generated_world.world.Temperature; import mc.world.generated_world.world.Wetness; @@ -306,37 +305,37 @@ public class SeedBasedWorldGenerator implements WorldGenerator { region.setBiome(x, z, biomes[x][z]); if (heightMap[x][z] < WORLD_SEA_LEVEL) { for (int y = 0; y < WORLD_SEA_LEVEL; y ++) { - Chunk chunk = region.getChunkAt(x / 16, y / 16, z / 16); + ChunkSection chunkSection = region.getChunkAt(x / 16, y / 16, z / 16); if (y == 0) { - chunk.setBlock(x % 16, y % 16, z % 16, blockFactory.create(BlockType.BEDROCK, 0)); + chunkSection.setBlock(x % 16, y % 16, z % 16, blockFactory.create(BlockType.BEDROCK, 0)); continue; } if (y < heightMap[x][z]) { if (y < heightMap[x][z] - grassMap[x][z]) { - chunk.setBlock(x % 16, y % 16, z % 16, blockFactory.create(BlockType.STONE, 0)); + chunkSection.setBlock(x % 16, y % 16, z % 16, blockFactory.create(BlockType.STONE, 0)); } else { - chunk.setBlock(x % 16, y % 16, z % 16, blockFactory.create(BlockType.SAND, 0)); + chunkSection.setBlock(x % 16, y % 16, z % 16, blockFactory.create(BlockType.SAND, 0)); } } else { - chunk.setBlock(x % 16, y % 16, z % 16, blockFactory.create(BlockType.WATER, 0)); + chunkSection.setBlock(x % 16, y % 16, z % 16, blockFactory.create(BlockType.WATER, 0)); } } } else { for (int y = 0; y < heightMap[x][z]; y++) { - Chunk chunk = region.getChunkAt(x / 16, y / 16, z / 16); + ChunkSection chunkSection = region.getChunkAt(x / 16, y / 16, z / 16); if (y == 0) { - chunk.setBlock(x % 16, y % 16, z % 16, blockFactory.create(BlockType.BEDROCK, 0)); + chunkSection.setBlock(x % 16, y % 16, z % 16, blockFactory.create(BlockType.BEDROCK, 0)); continue; } if (y < heightMap[x][z] - grassMap[x][z]) { - chunk.setBlock(x % 16, y % 16, z % 16, blockFactory.create(BlockType.STONE, 0)); + chunkSection.setBlock(x % 16, y % 16, z % 16, blockFactory.create(BlockType.STONE, 0)); } else { if (biomes[x][z] == Biome.DESERT || biomes[x][z] == Biome.DESERT_HILLS) { - chunk.setBlock(x % 16, y % 16, z % 16, blockFactory.create(BlockType.SAND, 0)); + chunkSection.setBlock(x % 16, y % 16, z % 16, blockFactory.create(BlockType.SAND, 0)); } else if (biomes[x][z] == Biome.TAIGA || biomes[x][z] == Biome.TAIGA_HILLS) { - chunk.setBlock(x % 16, y % 16, z % 16, blockFactory.create(BlockType.DIRT, 0)); + chunkSection.setBlock(x % 16, y % 16, z % 16, blockFactory.create(BlockType.DIRT, 0)); } else { - chunk.setBlock(x % 16, y % 16, z % 16, blockFactory.create(BlockType.GRASS, 0)); + chunkSection.setBlock(x % 16, y % 16, z % 16, blockFactory.create(BlockType.GRASS, 0)); } } } diff --git a/generated_world/src/main/java/mc/world/generated_world/region/RegionImpl.java b/generated_world/src/main/java/mc/world/generated_world/region/RegionImpl.java index 1c48196..acd8e57 100644 --- a/generated_world/src/main/java/mc/world/generated_world/region/RegionImpl.java +++ b/generated_world/src/main/java/mc/world/generated_world/region/RegionImpl.java @@ -6,8 +6,8 @@ import mc.core.exception.ResourceUnloadedException; import mc.core.serialization.IRegionReaderWriter; import mc.core.serialization.Serializer; import mc.core.world.*; -import mc.world.generated_world.chunk.ChunkImpl; -import mc.world.generated_world.chunk.ChunkProxy; +import mc.world.generated_world.chunk.ChunkSectionImpl; +import mc.world.generated_world.chunk.ChunkSectionProxy; import mc.world.generated_world.chunk.InMemoryCacheChunkLoader; import org.springframework.beans.factory.annotation.Autowired; @@ -26,7 +26,7 @@ public class RegionImpl implements Region{ private final int x; @Getter private final int z; - private final ChunkProxy[][][] chunks = new ChunkProxy[WORLD_REGION_SIZE/WORLD_CHUNK_SIZE][WORLD_REGION_SIZE/WORLD_CHUNK_SIZE][WORLD_REGION_SIZE/WORLD_CHUNK_SIZE]; + private final ChunkSectionProxy[][][] chunks = new ChunkSectionProxy[WORLD_REGION_SIZE/WORLD_CHUNK_SIZE][WORLD_REGION_SIZE/WORLD_CHUNK_SIZE][WORLD_REGION_SIZE/WORLD_CHUNK_SIZE]; private final Biome[][] biomes = new Biome[WORLD_REGION_SIZE][WORLD_REGION_SIZE]; private final transient Reference world; @Autowired @@ -39,27 +39,27 @@ public class RegionImpl implements Region{ } @Override - public Chunk getChunkAt(int x, int y, int z) { + public ChunkSection getChunkAt(int x, int y, int z) { if (x < 0 || y < 0 || z < 0 || x >= 16 || y >= 16 || z >= 16) { - throw new RuntimeException(MessageFormat.format("Invalid chunk coordinates [{0} {1} {2}]", x, y, z)); + throw new RuntimeException(MessageFormat.format("Invalid chunkSection coordinates [{0} {1} {2}]", x, y, z)); } if (chunkLoader == null) { chunkLoader = new InMemoryCacheChunkLoader(getWorld()); } - Chunk chunk = chunks[x][y][z]; - if (chunk == null) { - chunk = chunkLoader.loadChunk(x + this.x * WORLD_REGION_SIZE, y, this.z * WORLD_REGION_SIZE).orElse(new ChunkImpl(x, y, z, this)); - chunks[x][y][z] = new ChunkProxy(chunk); + ChunkSection chunkSection = chunks[x][y][z]; + if (chunkSection == null) { + chunkSection = chunkLoader.loadChunk(x + this.x * WORLD_REGION_SIZE, y, this.z * WORLD_REGION_SIZE).orElse(new ChunkSectionImpl(x, y, z, this)); + chunks[x][y][z] = new ChunkSectionProxy(chunkSection); } - return chunk; + return chunkSection; } @Override - public void setChunk(int x, int y, int z, Chunk chunk) { + public void setChunk(int x, int y, int z, ChunkSection chunkSection) { if (x < 0 || y < 0 || z < 0 || x >= 16 || y >= 16 || z >= 16) { - throw new RuntimeException(MessageFormat.format("Invalid chunk coordinates [{0} {1} {2}]", x, y, z)); + throw new RuntimeException(MessageFormat.format("Invalid chunkSection coordinates [{0} {1} {2}]", x, y, z)); } - chunks[x][y][z] = new ChunkProxy(chunk); + chunks[x][y][z] = new ChunkSectionProxy(chunkSection); } @Override @@ -90,7 +90,7 @@ public class RegionImpl implements Region{ } @Override - public void save(Serializer chunkSerializer, IRegionReaderWriter regionReaderWriter) throws IOException { + public void save(Serializer chunkSerializer, IRegionReaderWriter regionReaderWriter) throws IOException { String worldPath = System.getProperty("worlds.folder", "worlds"); File worldFile = new File(worldPath, getWorld().getWorldId().toString()); File regionFile = new File(worldFile, MessageFormat.format(REGION_FILE_NAME_TEMPLATE, this.getX(), this.getZ())); @@ -101,8 +101,8 @@ public class RegionImpl implements Region{ for (int x = 0; x < WORLD_CHUNK_SIZE; x ++) { for (int z = 0; z < WORLD_CHUNK_SIZE; z ++) { for (int y = 0; y < WORLD_CHUNK_SIZE; y++) { - Chunk chunk = this.getChunkAt(x, y, z); - byte[] chunkBytes = chunkSerializer.serialize(chunk); + ChunkSection chunkSection = this.getChunkAt(x, y, z); + byte[] chunkBytes = chunkSerializer.serialize(chunkSection); if (chunkBytes.length > 0) { File chunkFile = new File(regionFile, MessageFormat.format(CHUNK_FILE_NAME_TEMPLATE, x, y, z)); try (FileOutputStream fileOutputStream = new FileOutputStream(chunkFile)) { diff --git a/generated_world/src/main/java/mc/world/generated_world/serialization/BlockSerializerDeserializer.java b/generated_world/src/main/java/mc/world/generated_world/serialization/BlockSerializerDeserializer.java index a176afe..ae308fb 100644 --- a/generated_world/src/main/java/mc/world/generated_world/serialization/BlockSerializerDeserializer.java +++ b/generated_world/src/main/java/mc/world/generated_world/serialization/BlockSerializerDeserializer.java @@ -5,7 +5,7 @@ import mc.core.block.BlockFactory; import mc.core.block.BlockType; import mc.core.serialization.Deserializer; import mc.core.serialization.Serializer; -import mc.core.world.Chunk; +import mc.core.world.ChunkSection; /** * Prototype @@ -13,20 +13,20 @@ import mc.core.world.Chunk; public class BlockSerializerDeserializer implements Serializer, Deserializer { private BlockFactory blockFactory; - private Chunk chunk; + private ChunkSection chunkSection; - public BlockSerializerDeserializer(BlockFactory blockFactory, Chunk chunk) { + public BlockSerializerDeserializer(BlockFactory blockFactory, ChunkSection chunkSection) { this.blockFactory = blockFactory; - this.chunk = chunk; + this.chunkSection = chunkSection; } @Override public Block deserialize(byte[] bytes) { int id = bytes[0] + 128; int meta = bytes[1] >> 4; - int x = (bytes[1] & 0xf) + chunk.getX() * 16; - int y = bytes[2] >> 4 + chunk.getY() * 16; - int z = (bytes[2] & 0xf) + chunk.getZ() * 16; + int x = (bytes[1] & 0xf) + chunkSection.getX() * 16; + int y = bytes[2] >> 4 + chunkSection.getY() * 16; + int z = (bytes[2] & 0xf) + chunkSection.getZ() * 16; BlockType type = BlockType.values()[id]; Block block = blockFactory.create(type, meta); block.getLocation().setX(x); diff --git a/generated_world/src/main/java/mc/world/generated_world/serialization/ChunkReader.java b/generated_world/src/main/java/mc/world/generated_world/serialization/ChunkReader.java index 8b77dde..c117150 100644 --- a/generated_world/src/main/java/mc/world/generated_world/serialization/ChunkReader.java +++ b/generated_world/src/main/java/mc/world/generated_world/serialization/ChunkReader.java @@ -4,9 +4,9 @@ import mc.core.Location; import mc.core.block.Block; import mc.core.serialization.Deserializer; import mc.core.serialization.IChunkReader; -import mc.core.world.Chunk; +import mc.core.world.ChunkSection; import mc.core.world.Region; -import mc.world.generated_world.chunk.ChunkImpl; +import mc.world.generated_world.chunk.ChunkSectionImpl; import org.springframework.beans.factory.annotation.Autowired; import java.io.File; @@ -27,14 +27,14 @@ public class ChunkReader implements IChunkReader{ } @Override - public Chunk read (Region region, int x, int y, int z) throws IOException { + public ChunkSection read (Region region, int x, int y, int z) throws IOException { x %= WORLD_REGION_SIZE; y %= WORLD_REGION_SIZE; z %= WORLD_REGION_SIZE; File chunkFile = new File(new File(worldFolder, MessageFormat.format(REGION_FILE_NAME_TEMPLATE, region.getX(), region.getZ())), MessageFormat.format(CHUNK_FILE_NAME_TEMPLATE, x, y, z)); byte[] chunkBytes = Files.readAllBytes(Paths.get(chunkFile.toURI())); int blocks = (chunkBytes.length) / 3; - Chunk chunk = new ChunkImpl(x, y, z, region); + ChunkSection chunkSection = new ChunkSectionImpl(x, y, z, region); for (int i = 0; i < blocks; i ++) { byte[] blockBytes = new byte[3]; blockBytes[0] = chunkBytes[3 * i]; @@ -42,8 +42,8 @@ public class ChunkReader implements IChunkReader{ blockBytes[2] = chunkBytes[2 + 3 * i]; Block block = blockDeserializer.deserialize(blockBytes); Location blockLocation = block.getLocation(); - chunk.setBlock(blockLocation.getBlockX(), blockLocation.getBlockY(), blockLocation.getBlockZ(), block); + chunkSection.setBlock(blockLocation.getBlockX(), blockLocation.getBlockY(), blockLocation.getBlockZ(), block); } - return chunk; + return chunkSection; } } diff --git a/generated_world/src/main/java/mc/world/generated_world/serialization/ChunkSerializer.java b/generated_world/src/main/java/mc/world/generated_world/serialization/ChunkSerializer.java index aae910b..ce592ce 100644 --- a/generated_world/src/main/java/mc/world/generated_world/serialization/ChunkSerializer.java +++ b/generated_world/src/main/java/mc/world/generated_world/serialization/ChunkSerializer.java @@ -5,7 +5,7 @@ import mc.core.block.Block; import mc.core.block.BlockFactory; import mc.core.block.BlockType; import mc.core.serialization.Serializer; -import mc.core.world.Chunk; +import mc.core.world.ChunkSection; import org.springframework.beans.factory.annotation.Autowired; import java.io.ByteArrayOutputStream; @@ -14,20 +14,20 @@ import java.io.IOException; import static mc.world.generated_world.WorldConstants.WORLD_CHUNK_SIZE; @Slf4j -public class ChunkSerializer implements Serializer { +public class ChunkSerializer implements Serializer { @Autowired private Serializer blockSerializer; @Override - public byte[] serialize(Chunk chunk) { - Serializer blockSerializer = new BlockSerializerDeserializer(new BlockFactory(), chunk); + public byte[] serialize(ChunkSection chunkSection) { + Serializer blockSerializer = new BlockSerializerDeserializer(new BlockFactory(), chunkSection); ByteArrayOutputStream baos = new ByteArrayOutputStream(); Block current; for (int x = 0; x < WORLD_CHUNK_SIZE; x ++) { for (int y = 0; y < WORLD_CHUNK_SIZE; y ++) { for (int z = 0; z < WORLD_CHUNK_SIZE; z ++) { - current = chunk.getBlock(x, y, z); + current = chunkSection.getBlock(x, y, z); if (current != null && current.getBlockType() != BlockType.AIR) { try { baos.write(blockSerializer.serialize(current)); diff --git a/generated_world/src/main/java/mc/world/generated_world/world/CubicWorld.java b/generated_world/src/main/java/mc/world/generated_world/world/CubicWorld.java index 6c39781..8dc502c 100644 --- a/generated_world/src/main/java/mc/world/generated_world/world/CubicWorld.java +++ b/generated_world/src/main/java/mc/world/generated_world/world/CubicWorld.java @@ -8,7 +8,7 @@ import mc.core.Location; import mc.core.WarpPosition; import mc.core.block.BlockType; import mc.core.player.Look; -import mc.core.world.Chunk; +import mc.core.world.ChunkSection; import mc.core.world.IWorldType; import mc.core.world.Region; import mc.core.world.World; @@ -68,8 +68,8 @@ public class CubicWorld implements World { log.warn("Spawn location is not defined. Trying to select best location"); warpPosition = new WarpPosition(Location.startPointLocation(), new Look(0, 0)); for (int y = WORLD_MAX_HEIGHT; y > 0; y --) { - Chunk chunk = getChunk(0,y / WORLD_CHUNK_SIZE, 0); - if (chunk.getBlock(0, y, 0).getBlockType() != BlockType.AIR) { + ChunkSection chunkSection = getChunk(0,y / WORLD_CHUNK_SIZE, 0); + if (chunkSection.getBlock(0, y, 0).getBlockType() != BlockType.AIR) { warpPosition = new WarpPosition(new Location(0, y + 1, 0), new Look(0, 0)); break; } @@ -89,12 +89,12 @@ public class CubicWorld implements World { } @Override - public Chunk getChunk(int x, int y, int z) { + public ChunkSection getChunk(int x, int y, int z) { return null; } @Override - public void setChunk(int x, int y, int z, Chunk chunk) { + public void setChunk(int x, int y, int z, ChunkSection chunkSection) { } From 895f59189d8ece7d05aa21bdfdffda3026d46124 Mon Sep 17 00:00:00 2001 From: Forwolk Date: Sat, 4 Aug 2018 21:03:05 +0300 Subject: [PATCH 35/62] Chunk 16x256x16 --- core/src/main/java/mc/core/world/Chunk.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 core/src/main/java/mc/core/world/Chunk.java diff --git a/core/src/main/java/mc/core/world/Chunk.java b/core/src/main/java/mc/core/world/Chunk.java new file mode 100644 index 0000000..694ce54 --- /dev/null +++ b/core/src/main/java/mc/core/world/Chunk.java @@ -0,0 +1,12 @@ +package mc.core.world; + +public interface Chunk { + + World getWorld(); + ChunkSection getChunkSection(int height); + ChunkSection setChunkSection(int height, ChunkSection chunkSection); + Region getRegion(); + + int getX(); + int getZ(); +} From a1ee3a240ea632e5a208fcda74697385d514a12a Mon Sep 17 00:00:00 2001 From: Forwolk Date: Sat, 4 Aug 2018 21:05:16 +0300 Subject: [PATCH 36/62] Location.getChunk(); --- core/src/main/java/mc/core/Location.java | 6 ++++++ core/src/main/java/mc/core/world/Region.java | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/mc/core/Location.java b/core/src/main/java/mc/core/Location.java index 191aa99..1171d68 100644 --- a/core/src/main/java/mc/core/Location.java +++ b/core/src/main/java/mc/core/Location.java @@ -6,7 +6,9 @@ package mc.core; import lombok.Data; import mc.core.exception.ResourceUnloadedException; +import mc.core.world.Chunk; import mc.core.world.World; +import sun.reflect.generics.reflectiveObjects.NotImplementedException; import java.io.Serializable; import java.lang.ref.Reference; @@ -111,4 +113,8 @@ public class Location implements Serializable{ this.world = new WeakReference<>(world); } + public Chunk getChunk() { + throw new NotImplementedException(); + } + } diff --git a/core/src/main/java/mc/core/world/Region.java b/core/src/main/java/mc/core/world/Region.java index 17e936b..fc91637 100644 --- a/core/src/main/java/mc/core/world/Region.java +++ b/core/src/main/java/mc/core/world/Region.java @@ -21,8 +21,8 @@ import java.io.Serializable; * */ public interface Region extends Serializable{ - ChunkSection getChunkAt(int x, int y, int z); - void setChunk(int x, int y, int z, ChunkSection chunkSection); + Chunk getChunk (int x, int z); + void setChunk(int x, int z, Chunk chunk); int getX(); int getZ(); From a23f7b15b55b47d5a7bb2866c9d01e13e5bc8a5c Mon Sep 17 00:00:00 2001 From: Forwolk Date: Sat, 4 Aug 2018 21:56:04 +0300 Subject: [PATCH 37/62] Chunk implementation --- core/src/main/java/mc/core/Location.java | 4 +- core/src/main/java/mc/core/world/Region.java | 5 + .../generated_world/chunk/ChunkImpl.java | 61 ++++++++ .../chunk/ChunkSectionImpl.java | 127 +++++++++++++++ .../chunk/ChunkSectionProxy.java | 146 ++++++++++++++++++ .../generated_world/region/RegionImpl.java | 31 +++- 6 files changed, 369 insertions(+), 5 deletions(-) create mode 100644 generated_world/src/main/java/mc/world/generated_world/chunk/ChunkImpl.java create mode 100644 generated_world/src/main/java/mc/world/generated_world/chunk/ChunkSectionImpl.java create mode 100644 generated_world/src/main/java/mc/world/generated_world/chunk/ChunkSectionProxy.java diff --git a/core/src/main/java/mc/core/Location.java b/core/src/main/java/mc/core/Location.java index 1171d68..9332165 100644 --- a/core/src/main/java/mc/core/Location.java +++ b/core/src/main/java/mc/core/Location.java @@ -7,6 +7,7 @@ package mc.core; import lombok.Data; import mc.core.exception.ResourceUnloadedException; import mc.core.world.Chunk; +import mc.core.world.Region; import mc.core.world.World; import sun.reflect.generics.reflectiveObjects.NotImplementedException; @@ -114,7 +115,8 @@ public class Location implements Serializable{ } public Chunk getChunk() { - throw new NotImplementedException(); + Region region = getWorld().getRegion((int) (x / 256), (int) (z / 256)); + return region.getChunk((int) ((x % 256) / 16), (int) ((z % 256) / 16)); } } diff --git a/core/src/main/java/mc/core/world/Region.java b/core/src/main/java/mc/core/world/Region.java index fc91637..7b57123 100644 --- a/core/src/main/java/mc/core/world/Region.java +++ b/core/src/main/java/mc/core/world/Region.java @@ -24,6 +24,11 @@ public interface Region extends Serializable{ Chunk getChunk (int x, int z); void setChunk(int x, int z, Chunk chunk); + @Deprecated + ChunkSection getChunkAt(int x, int y, int z); + @Deprecated + void setChunk(int x, int y, int z, ChunkSection chunkSection); + int getX(); int getZ(); diff --git a/generated_world/src/main/java/mc/world/generated_world/chunk/ChunkImpl.java b/generated_world/src/main/java/mc/world/generated_world/chunk/ChunkImpl.java new file mode 100644 index 0000000..7954603 --- /dev/null +++ b/generated_world/src/main/java/mc/world/generated_world/chunk/ChunkImpl.java @@ -0,0 +1,61 @@ +package mc.world.generated_world.chunk; + +import lombok.Getter; +import mc.core.exception.ResourceUnloadedException; +import mc.core.world.Chunk; +import mc.core.world.ChunkSection; +import mc.core.world.Region; +import mc.core.world.World; + +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; + +import static mc.world.generated_world.WorldConstants.WORLD_CHUNK_SIZE; + +public class ChunkImpl implements Chunk { + @Getter + private final int x; + @Getter + private final int z; + private Reference regionReference; + private ChunkSection[] sections = new ChunkSection[WORLD_CHUNK_SIZE]; + + public ChunkImpl (int x, int z, Region region) { + this.x = x; + this.z = z; + this.regionReference = new WeakReference<>(region); + } + + @Override + public World getWorld() { + Region region = getRegion(); + if (region == null) { + throw new ResourceUnloadedException("Region is unloaded"); + } + return region.getWorld(); + } + + @Override + public ChunkSection getChunkSection(int height) { + return sections[height]; + } + + @Override + public ChunkSection setChunkSection(int height, ChunkSection chunkSection) { + sections[height] = chunkSection; + return chunkSection; + } + + @Override + public Region getRegion() { + if (regionReference == null) { + return null; + } + + if (regionReference.get() == null) { + throw new ResourceUnloadedException("Region is unloaded"); + } + + return regionReference.get(); + } +} diff --git a/generated_world/src/main/java/mc/world/generated_world/chunk/ChunkSectionImpl.java b/generated_world/src/main/java/mc/world/generated_world/chunk/ChunkSectionImpl.java new file mode 100644 index 0000000..dce85eb --- /dev/null +++ b/generated_world/src/main/java/mc/world/generated_world/chunk/ChunkSectionImpl.java @@ -0,0 +1,127 @@ +package mc.world.generated_world.chunk; + +import lombok.Getter; +import mc.core.block.Block; +import mc.core.block.BlockType; +import mc.core.exception.ResourceUnloadedException; +import mc.core.world.Biome; +import mc.core.world.ChunkSection; +import mc.core.world.Region; +import mc.core.world.World; + +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; + +import static mc.world.generated_world.WorldConstants.WORLD_CHUNK_SIZE; + +public class ChunkSectionImpl implements ChunkSection { + @Getter + private final int x; + @Getter + private final int y; + @Getter + private final int z; + private final Block[][][] blocks = new Block[WORLD_CHUNK_SIZE][WORLD_CHUNK_SIZE][WORLD_CHUNK_SIZE]; + private final transient Reference region; + + public ChunkSectionImpl(int x, int y, int z, Region region) { + this.x = x; + this.y = y; + this.z = z; + this.region = new WeakReference<>(region); + } + + @Override + public int getBlockType(int x, int y, int z) { + return blocks[x][y][z].getId(); + } + + @Override + public void setBlockType(int x, int y, int z, int type) { + + } + + @Override + public int getBlockMetadata(int x, int y, int z) { + return 0; + } + + @Override + public void setBlockMetadata(int x, int y, int z, int metadata) { + + } + + @Override + public int getBlockLight(int x, int y, int z) { + return 15; + } + + @Override + public void setBlockLight(int x, int y, int z, int lightLevel) { + + } + + @Override + public int getSkyLight(int x, int y, int z) { + return 15; + } + + @Override + public void setSkyLight(int x, int y, int z, int lightLevel) { + + } + + @Override + public int getAddition(int x, int y, int z) { + return 0; + } + + @Override + public void setAddition(int x, int y, int z, int value) { + + } + + @Override + public Biome getBiome(int x, int z) { + return getRegion().getBiomeAt(x + this.x * WORLD_CHUNK_SIZE,z + this.z * WORLD_CHUNK_SIZE); + } + + @Override + public void setBiome(int x, int z, Biome biome) { + getRegion().setBiome(x + this.x * WORLD_CHUNK_SIZE,z + this.z * WORLD_CHUNK_SIZE, biome); + } + + @Override + public void setBlock(int x, int y, int z, Block block) { + if (block.getBlockType() == BlockType.AIR) { + blocks[x][y][z] = null; + return; + } + blocks[x][y][z] = block; + } + + @Override + public Block getBlock(int x, int y, int z) { + Block block = blocks[x][y][z]; + if (block == null) { + return Block.airBlock(x, y, z); + } + return blocks[x][y][z]; + } + + @Override + public Region getRegion() { + if (region == null) { + return null; + } + if (region.get() == null) { + throw new ResourceUnloadedException("Region is unloaded"); + } + return region.get(); + } + + @Override + public World getWorld() { + return getRegion().getWorld(); + } +} diff --git a/generated_world/src/main/java/mc/world/generated_world/chunk/ChunkSectionProxy.java b/generated_world/src/main/java/mc/world/generated_world/chunk/ChunkSectionProxy.java new file mode 100644 index 0000000..c945af8 --- /dev/null +++ b/generated_world/src/main/java/mc/world/generated_world/chunk/ChunkSectionProxy.java @@ -0,0 +1,146 @@ +package mc.world.generated_world.chunk; + +import mc.core.block.Block; +import mc.core.world.Biome; +import mc.core.world.ChunkSection; +import mc.core.world.Region; +import mc.core.world.World; + +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +public class ChunkSectionProxy implements ChunkSection { + private final ChunkSection chunkSection; + private volatile transient long lastUsage = System.currentTimeMillis(); + private final transient ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); + + public ChunkSectionProxy(ChunkSection chunkSection) { + this.chunkSection = chunkSection; + } + + public long getLastUsage() { + synchronized (chunkSection) { + return lastUsage; + } + } + + private final void use () { + synchronized (chunkSection) { + lastUsage = System.currentTimeMillis(); + } + } + + @Override + public int getBlockType(int x, int y, int z) { + use(); + return chunkSection.getBlockType(x, y, z); + } + + @Override + public void setBlockType(int x, int y, int z, int type) { + use(); + chunkSection.setBlockType(x, y, z, type); + } + + @Override + public int getBlockMetadata(int x, int y, int z) { + use(); + return chunkSection.getBlockMetadata(x, y, z); + } + + @Override + public void setBlockMetadata(int x, int y, int z, int metadata) { + use(); + chunkSection.setBlockMetadata(x, y, z, metadata); + } + + @Override + public int getBlockLight(int x, int y, int z) { + use(); + return chunkSection.getBlockLight(x, y, z); + } + + @Override + public void setBlockLight(int x, int y, int z, int lightLevel) { + use(); + chunkSection.setBlockLight(x, y, z, lightLevel); + } + + @Override + public int getSkyLight(int x, int y, int z) { + use(); + return chunkSection.getSkyLight(x, y, z); + } + + @Override + public void setSkyLight(int x, int y, int z, int lightLevel) { + use(); + chunkSection.setSkyLight(x, y, z, lightLevel); + } + + @Override + public int getAddition(int x, int y, int z) { + use(); + return chunkSection.getAddition(x, y, z); + } + + @Override + public void setAddition(int x, int y, int z, int value) { + use(); + chunkSection.setAddition(x, y, z, value); + } + + @Override + public Biome getBiome(int x, int z) { + use(); + return chunkSection.getBiome(x, z); + } + + @Override + public void setBiome(int x, int z, Biome biome) { + use(); + chunkSection.setBiome(x, z, biome); + } + + @Override + public int getX() { + use(); + return chunkSection.getX(); + } + + @Override + public int getY() { + use(); + return chunkSection.getY(); + } + + @Override + public int getZ() { + use(); + return chunkSection.getZ(); + } + + @Override + public void setBlock(int x, int y, int z, Block block) { + use(); + chunkSection.setBlock(x, y, z, block); + } + + @Override + public Block getBlock(int x, int y, int z) { + use(); + return chunkSection.getBlock(x, y, z); + } + + @Override + public Region getRegion() { + use(); + return chunkSection.getRegion(); + } + + @Override + public World getWorld() { + use(); + return chunkSection.getWorld(); + } +} diff --git a/generated_world/src/main/java/mc/world/generated_world/region/RegionImpl.java b/generated_world/src/main/java/mc/world/generated_world/region/RegionImpl.java index acd8e57..9f96d49 100644 --- a/generated_world/src/main/java/mc/world/generated_world/region/RegionImpl.java +++ b/generated_world/src/main/java/mc/world/generated_world/region/RegionImpl.java @@ -6,6 +6,7 @@ import mc.core.exception.ResourceUnloadedException; import mc.core.serialization.IRegionReaderWriter; import mc.core.serialization.Serializer; import mc.core.world.*; +import mc.world.generated_world.chunk.ChunkImpl; import mc.world.generated_world.chunk.ChunkSectionImpl; import mc.world.generated_world.chunk.ChunkSectionProxy; import mc.world.generated_world.chunk.InMemoryCacheChunkLoader; @@ -26,9 +27,10 @@ public class RegionImpl implements Region{ private final int x; @Getter private final int z; - private final ChunkSectionProxy[][][] chunks = new ChunkSectionProxy[WORLD_REGION_SIZE/WORLD_CHUNK_SIZE][WORLD_REGION_SIZE/WORLD_CHUNK_SIZE][WORLD_REGION_SIZE/WORLD_CHUNK_SIZE]; + private final ChunkSection[][][] chunkSectionProxies = new ChunkSectionProxy[WORLD_REGION_SIZE/WORLD_CHUNK_SIZE][WORLD_REGION_SIZE/WORLD_CHUNK_SIZE][WORLD_REGION_SIZE/WORLD_CHUNK_SIZE]; private final Biome[][] biomes = new Biome[WORLD_REGION_SIZE][WORLD_REGION_SIZE]; private final transient Reference world; + private final Chunk[][] chunks = new Chunk[WORLD_REGION_SIZE/WORLD_CHUNK_SIZE][WORLD_REGION_SIZE/WORLD_CHUNK_SIZE]; @Autowired private ChunkLoader chunkLoader; @@ -38,6 +40,27 @@ public class RegionImpl implements Region{ this.world = new WeakReference<>(world); } + @Override + public Chunk getChunk(int x, int z) { + if (x < 0 || z < 0 || x >= 16 || z >= 16) { + throw new RuntimeException(MessageFormat.format("Invalid chunk coordinates [{0} {1}]", x, z)); + } + + Chunk chunk = chunks[x][z]; + if (chunk == null) { + chunk = new ChunkImpl(x, z, this); + for (int y = 0; y < WORLD_CHUNK_SIZE; y ++) { + chunk.setChunkSection(y, getChunkAt(x, y, z)); + } + } + return chunk; + } + + @Override + public void setChunk(int x, int z, Chunk chunk) { + chunks[x][z] = chunk; + } + @Override public ChunkSection getChunkAt(int x, int y, int z) { if (x < 0 || y < 0 || z < 0 || x >= 16 || y >= 16 || z >= 16) { @@ -46,10 +69,10 @@ public class RegionImpl implements Region{ if (chunkLoader == null) { chunkLoader = new InMemoryCacheChunkLoader(getWorld()); } - ChunkSection chunkSection = chunks[x][y][z]; + ChunkSection chunkSection = chunkSectionProxies[x][y][z]; if (chunkSection == null) { chunkSection = chunkLoader.loadChunk(x + this.x * WORLD_REGION_SIZE, y, this.z * WORLD_REGION_SIZE).orElse(new ChunkSectionImpl(x, y, z, this)); - chunks[x][y][z] = new ChunkSectionProxy(chunkSection); + chunkSectionProxies[x][y][z] = new ChunkSectionProxy(chunkSection); } return chunkSection; } @@ -59,7 +82,7 @@ public class RegionImpl implements Region{ if (x < 0 || y < 0 || z < 0 || x >= 16 || y >= 16 || z >= 16) { throw new RuntimeException(MessageFormat.format("Invalid chunkSection coordinates [{0} {1} {2}]", x, y, z)); } - chunks[x][y][z] = new ChunkSectionProxy(chunkSection); + chunkSectionProxies[x][y][z] = new ChunkSectionProxy(chunkSection); } @Override From afe88e260c5b95d5372f7393302923e6410028da Mon Sep 17 00:00:00 2001 From: Daniil Date: Sun, 5 Aug 2018 13:09:54 +0700 Subject: [PATCH 38/62] PoorMansLock implementation --- .../events/api/samples/BlockBreakEvent.java | 12 +++-- .../mc/core/events/lock/PoorMansLock.java | 50 +++++++++++++++++++ 2 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 event-loop/src/main/java/mc/core/events/lock/PoorMansLock.java diff --git a/event-loop/src/main/java/mc/core/events/api/samples/BlockBreakEvent.java b/event-loop/src/main/java/mc/core/events/api/samples/BlockBreakEvent.java index b0f3044..5753491 100644 --- a/event-loop/src/main/java/mc/core/events/api/samples/BlockBreakEvent.java +++ b/event-loop/src/main/java/mc/core/events/api/samples/BlockBreakEvent.java @@ -1,6 +1,9 @@ package mc.core.events.api.samples; +import lombok.Getter; +import lombok.RequiredArgsConstructor; import mc.core.Location; +import mc.core.block.Block; import mc.core.events.EventBase; import mc.core.events.api.interfaces.LocationProvidingEvent; import mc.core.events.api.interfaces.PlayerProvidingEvent; @@ -10,10 +13,11 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +@RequiredArgsConstructor +@Getter public class BlockBreakEvent extends EventBase implements PlayerProvidingEvent, LocationProvidingEvent { - private Player player; - // TODO: There should be a proper block reference - private Location blockLocation; + private final Player player; + private final Block block; @Override @@ -23,6 +27,6 @@ public class BlockBreakEvent extends EventBase implements PlayerProvidingEvent, @Override public Collection getAssociatedLocations() { - return Collections.singletonList(blockLocation); + return Collections.singletonList(block.getLocation()); } } diff --git a/event-loop/src/main/java/mc/core/events/lock/PoorMansLock.java b/event-loop/src/main/java/mc/core/events/lock/PoorMansLock.java new file mode 100644 index 0000000..e8d13e5 --- /dev/null +++ b/event-loop/src/main/java/mc/core/events/lock/PoorMansLock.java @@ -0,0 +1,50 @@ +package mc.core.events.lock; + +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.function.Consumer; + +public class PoorMansLock { + private Thread owner = null; + private Set> callbacks = new CopyOnWriteArraySet<>(); + + public void addCallback(Consumer callback) { + callbacks.add(callback); + } + + public void removeCallback(Consumer callback) { + callbacks.remove(callback); + } + + + public boolean isLocked() { + return owner != null; + } + + private void triggerUpdate() { + for (Consumer consumer : callbacks) + consumer.accept(this); + } + + public synchronized void lock() { + if (owner != null && owner != Thread.currentThread()) { + // ToDo: do we need to await for unlock? + throw new RuntimeException("Unable to lock this resource: already in use"); + } + + owner = Thread.currentThread(); + triggerUpdate(); + } + + public synchronized void unlock() { + if (owner == null) + return; + + if (owner != Thread.currentThread()) { + throw new RuntimeException("Attempt to unlock resource from non-owning thread"); + } + + owner = null; + triggerUpdate(); + } +} From b5a7942b67bf660b1e8d607106f46707d8f1bf2a Mon Sep 17 00:00:00 2001 From: Daniil Date: Sun, 5 Aug 2018 13:15:31 +0700 Subject: [PATCH 39/62] Fixed reentrant locking mechanism for PoorMansLock --- .../src/main/java/mc/core/events/lock/PoorMansLock.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/event-loop/src/main/java/mc/core/events/lock/PoorMansLock.java b/event-loop/src/main/java/mc/core/events/lock/PoorMansLock.java index e8d13e5..a694b17 100644 --- a/event-loop/src/main/java/mc/core/events/lock/PoorMansLock.java +++ b/event-loop/src/main/java/mc/core/events/lock/PoorMansLock.java @@ -27,8 +27,10 @@ public class PoorMansLock { } public synchronized void lock() { - if (owner != null && owner != Thread.currentThread()) { - // ToDo: do we need to await for unlock? + if(owner == Thread.currentThread()) + return; + + if (owner != null) { throw new RuntimeException("Unable to lock this resource: already in use"); } From 6ab86583999ed761c81109388620449ad1c3d53a Mon Sep 17 00:00:00 2001 From: Daniil Date: Sun, 5 Aug 2018 13:40:02 +0700 Subject: [PATCH 40/62] LockObserveList implementation, tests for Lock and List --- core/src/main/java/mc/core/Location.java | 25 ++++-- .../mc/core/events/lock/LockObserveList.java | 44 +++++++++ .../test/java/mc/core/events/LockTest.java | 90 +++++++++++++++++++ 3 files changed, 150 insertions(+), 9 deletions(-) create mode 100644 event-loop/src/main/java/mc/core/events/lock/LockObserveList.java create mode 100644 event-loop/src/test/java/mc/core/events/LockTest.java diff --git a/core/src/main/java/mc/core/Location.java b/core/src/main/java/mc/core/Location.java index 7e560f3..608d764 100644 --- a/core/src/main/java/mc/core/Location.java +++ b/core/src/main/java/mc/core/Location.java @@ -6,17 +6,23 @@ package mc.core; import lombok.AllArgsConstructor; import lombok.Data; +import mc.core.world.Chunk; +import sun.reflect.generics.reflectiveObjects.NotImplementedException; import java.io.Serializable; @AllArgsConstructor @Data -public class Location implements Serializable{ +public class Location implements Serializable { private double x, y, z; + public Location(long compactValue) { + set(compactValue); + } + private static int floor_double(double value) { - int i = (int)value; - return value < (double)i ? i - 1 : i; + int i = (int) value; + return value < (double) i ? i - 1 : i; } public static Location copyOf(Location location) { @@ -27,12 +33,8 @@ public class Location implements Serializable{ ); } - public static Location startPointLocation () { - return new Location(0,10,0); - } - - public Location(long compactValue) { - set(compactValue); + public static Location startPointLocation() { + return new Location(0, 10, 0); } public void set(Location location) { @@ -55,6 +57,11 @@ public class Location implements Serializable{ ); } + public Chunk getChunk() { + // TODO: Implement + throw new NotImplementedException(); + } + public int getBlockX() { return (int) x; } diff --git a/event-loop/src/main/java/mc/core/events/lock/LockObserveList.java b/event-loop/src/main/java/mc/core/events/lock/LockObserveList.java new file mode 100644 index 0000000..800a1b6 --- /dev/null +++ b/event-loop/src/main/java/mc/core/events/lock/LockObserveList.java @@ -0,0 +1,44 @@ +package mc.core.events.lock; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +public class LockObserveList implements Consumer { + private List locks = new ArrayList<>(); + private Runnable callback; + + public void setCallback(Runnable callback) { + this.callback = callback; + } + + public void add(PoorMansLock lock) { + locks.add(lock); + lock.addCallback(this); + } + + public void release() { + for (PoorMansLock lock : locks) { + lock.removeCallback(this); + } + locks.clear(); + } + + public boolean isReady() { + for (PoorMansLock lock : locks) { + if (lock.isLocked()) + return false; + } + return true; + } + + @Override + public void accept(PoorMansLock lock) { + if (!lock.isLocked()) { + if (isReady()) { + if (callback != null) + callback.run(); + } + } + } +} diff --git a/event-loop/src/test/java/mc/core/events/LockTest.java b/event-loop/src/test/java/mc/core/events/LockTest.java new file mode 100644 index 0000000..97b22e2 --- /dev/null +++ b/event-loop/src/test/java/mc/core/events/LockTest.java @@ -0,0 +1,90 @@ +package mc.core.events; + +import mc.core.events.lock.LockObserveList; +import mc.core.events.lock.PoorMansLock; +import org.junit.Assert; +import org.junit.Test; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; + +public class LockTest { + @Test + public void basicTest() throws InterruptedException { + AtomicBoolean engageCallbackCalled = new AtomicBoolean(false); + AtomicBoolean disengageCallbackCalled = new AtomicBoolean(false); + + PoorMansLock lock = new PoorMansLock(); + lock.addCallback(lock1 -> { + if (lock1.isLocked()) + engageCallbackCalled.set(true); + else + disengageCallbackCalled.set(true); + }); + lock.lock(); + Assert.assertTrue("Lock is not locked", lock.isLocked()); + Assert.assertTrue("Engage callback was not called", engageCallbackCalled.get()); + + engageCallbackCalled.set(false); + try { + lock.lock(); + Assert.assertFalse("Engage callback was called from attempt to block from the same thread", engageCallbackCalled.get()); + } catch (Exception ex) { + Assert.fail("Exception fired while attempting to lock from the same thread"); + return; + } + + Assert.assertFalse("Disengage callback was called while not actually disengaging [x1]", disengageCallbackCalled.get()); + + AtomicBoolean lockExceptionFired = new AtomicBoolean(false); + AtomicBoolean unlockExceptionFired = new AtomicBoolean(false); + CountDownLatch latch = new CountDownLatch(1); + new Thread(() -> { + try { + lock.lock(); + } catch (Exception ex) { + lockExceptionFired.set(true); + } + try { + lock.unlock(); + } catch (Exception ex) { + unlockExceptionFired.set(true); + } + latch.countDown(); + }).start(); + + latch.await(); + Assert.assertTrue("Exception was not fired on concurrent lock attempt", lockExceptionFired.get()); + Assert.assertTrue("Exception was not fired on non-owner unlock attempt", unlockExceptionFired.get()); + Assert.assertFalse("Disengage callback was called while not actually disengaging [x2]", disengageCallbackCalled.get()); + + lock.unlock(); + Assert.assertTrue("Disengage callback was on called on lock disengage", disengageCallbackCalled.get()); + } + + @Test + public void observeListTest() { + PoorMansLock lock1 = new PoorMansLock(); + PoorMansLock lock2 = new PoorMansLock(); + + LockObserveList list = new LockObserveList(); + list.add(lock1); + list.add(lock2); + + Assert.assertTrue("LockObserveList was no able to correctly identify lock states for unlocked locks", list.isReady()); + lock1.lock(); + Assert.assertFalse("LockObserveList was no able to correctly identify lock states for list with one locked lock", list.isReady()); + + + AtomicBoolean listReadyCallbackCalled = new AtomicBoolean(false); + list.setCallback(() -> listReadyCallbackCalled.set(true)); + lock2.lock(); + + Assert.assertFalse("Callback was called when another lock got engaged", listReadyCallbackCalled.get()); + lock1.unlock(); + Assert.assertFalse("Callback was called while one lock is still locked", listReadyCallbackCalled.get()); + lock2.unlock(); + Assert.assertTrue("Callback was not called when both locks are actually free", listReadyCallbackCalled.get()); + + } +} From 147b2ff28d5f02a1a0a5619d2d4e834d38812b44 Mon Sep 17 00:00:00 2001 From: Daniil Date: Sun, 5 Aug 2018 13:46:00 +0700 Subject: [PATCH 41/62] Migrated ResourceRunnable to use PoorMansLock --- .../main/java/mc/core/events/EventPipelineTask.java | 13 +++++++------ .../java/mc/core/events/lock/LockObserveList.java | 11 +++++++++++ .../java/mc/core/events/runner/ExecutorThread.java | 11 +++-------- .../mc/core/events/runner/ResourceRunnable.java | 8 +++----- 4 files changed, 24 insertions(+), 19 deletions(-) diff --git a/event-loop/src/main/java/mc/core/events/EventPipelineTask.java b/event-loop/src/main/java/mc/core/events/EventPipelineTask.java index bed7d5d..b2af902 100644 --- a/event-loop/src/main/java/mc/core/events/EventPipelineTask.java +++ b/event-loop/src/main/java/mc/core/events/EventPipelineTask.java @@ -5,13 +5,12 @@ import lombok.RequiredArgsConstructor; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import mc.core.events.api.EventQueueOwner; +import mc.core.events.lock.LockObserveList; import mc.core.events.runner.EventExecutorService; import mc.core.events.runner.ResourceRunnable; import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; import java.util.List; -import java.util.concurrent.locks.Lock; @RequiredArgsConstructor @Getter @@ -41,7 +40,7 @@ public class EventPipelineTask { RegisteredEventHandler handler = handlers.get(currentIndex); if (!event.isCanceled() || !handler.isIgnoreCancelled()) { - List locks = getLocks(handler); + LockObserveList locks = getLocks(handler); service.addTask(new ResourceRunnable() { @Override @@ -60,7 +59,7 @@ public class EventPipelineTask { } @Override - public List getLocks() { + public LockObserveList getLocks() { return locks; } }); @@ -70,12 +69,14 @@ public class EventPipelineTask { } } - private List getLocks(RegisteredEventHandler handler) { - List locks = new ArrayList<>(); + private LockObserveList getLocks(RegisteredEventHandler handler) { + LockObserveList locks = new LockObserveList(); +/* if (handler.isPluginSynchronize()) locks.add(manager.getResourceManager().getPluginLock(handler.getPlugin())); +*/ return locks; } diff --git a/event-loop/src/main/java/mc/core/events/lock/LockObserveList.java b/event-loop/src/main/java/mc/core/events/lock/LockObserveList.java index 800a1b6..cf3ba5d 100644 --- a/event-loop/src/main/java/mc/core/events/lock/LockObserveList.java +++ b/event-loop/src/main/java/mc/core/events/lock/LockObserveList.java @@ -5,6 +5,7 @@ import java.util.List; import java.util.function.Consumer; public class LockObserveList implements Consumer { + public static LockObserveList EMPTY_LIST = new LockObserveList(); private List locks = new ArrayList<>(); private Runnable callback; @@ -32,6 +33,16 @@ public class LockObserveList implements Consumer { return true; } + public void lockAll() { + for (PoorMansLock lock : locks) + lock.lock(); + } + + public void unlockAll() { + for (PoorMansLock lock : locks) + lock.unlock(); + } + @Override public void accept(PoorMansLock lock) { if (!lock.isLocked()) { diff --git a/event-loop/src/main/java/mc/core/events/runner/ExecutorThread.java b/event-loop/src/main/java/mc/core/events/runner/ExecutorThread.java index 9797fde..8ff52f0 100644 --- a/event-loop/src/main/java/mc/core/events/runner/ExecutorThread.java +++ b/event-loop/src/main/java/mc/core/events/runner/ExecutorThread.java @@ -1,7 +1,5 @@ package mc.core.events.runner; -import java.util.concurrent.locks.Lock; - public class ExecutorThread extends Thread { private EventExecutorService service; @@ -25,15 +23,12 @@ public class ExecutorThread extends Thread { } void executeTask(ResourceRunnable runnable) { - for (Lock lock : runnable.getLocks()) { - lock.lock(); - } + runnable.getLocks().lockAll(); try { runnable.run(); } finally { - for (Lock lock : runnable.getLocks()) { - lock.unlock(); - } + runnable.getLocks().unlockAll(); + runnable.getLocks().release(); } runnable.after(); } diff --git a/event-loop/src/main/java/mc/core/events/runner/ResourceRunnable.java b/event-loop/src/main/java/mc/core/events/runner/ResourceRunnable.java index 3a7c54e..6bf3492 100644 --- a/event-loop/src/main/java/mc/core/events/runner/ResourceRunnable.java +++ b/event-loop/src/main/java/mc/core/events/runner/ResourceRunnable.java @@ -1,12 +1,10 @@ package mc.core.events.runner; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.locks.Lock; +import mc.core.events.lock.LockObserveList; public interface ResourceRunnable extends Runnable { - default List getLocks() { - return Collections.emptyList(); + default LockObserveList getLocks() { + return LockObserveList.EMPTY_LIST; } default void after() { From ba558ea7d14b0f8ac7cf2881d65756661859412f Mon Sep 17 00:00:00 2001 From: Daniil Date: Sun, 5 Aug 2018 13:50:34 +0700 Subject: [PATCH 42/62] Fixed plugin synchronization --- .../src/main/java/mc/core/events/EventPipelineTask.java | 2 -- .../main/java/mc/core/events/SharedResourceManager.java | 9 ++++----- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/event-loop/src/main/java/mc/core/events/EventPipelineTask.java b/event-loop/src/main/java/mc/core/events/EventPipelineTask.java index b2af902..a071f58 100644 --- a/event-loop/src/main/java/mc/core/events/EventPipelineTask.java +++ b/event-loop/src/main/java/mc/core/events/EventPipelineTask.java @@ -71,12 +71,10 @@ public class EventPipelineTask { private LockObserveList getLocks(RegisteredEventHandler handler) { LockObserveList locks = new LockObserveList(); -/* if (handler.isPluginSynchronize()) locks.add(manager.getResourceManager().getPluginLock(handler.getPlugin())); -*/ return locks; } diff --git a/event-loop/src/main/java/mc/core/events/SharedResourceManager.java b/event-loop/src/main/java/mc/core/events/SharedResourceManager.java index 12f6812..45f6e1a 100644 --- a/event-loop/src/main/java/mc/core/events/SharedResourceManager.java +++ b/event-loop/src/main/java/mc/core/events/SharedResourceManager.java @@ -1,17 +1,16 @@ package mc.core.events; import mc.core.events.api.Plugin; +import mc.core.events.lock.PoorMansLock; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; public class SharedResourceManager { - private Map pluginLocks = new ConcurrentHashMap<>(); + private Map pluginLocks = new ConcurrentHashMap<>(); - public Lock getPluginLock(Plugin plugin) { - return pluginLocks.computeIfAbsent(plugin, s -> new ReentrantLock()); + public PoorMansLock getPluginLock(Plugin plugin) { + return pluginLocks.computeIfAbsent(plugin, s -> new PoorMansLock()); } } From 561dc3a1ce57a251d3dfbbbd631e3b1203b8e801 Mon Sep 17 00:00:00 2001 From: Daniil Date: Sun, 5 Aug 2018 16:13:42 +0700 Subject: [PATCH 43/62] Fixed plugin synchronization --- .../src/main/java/mc/core/events/EventPipelineTask.java | 4 ++++ .../main/java/mc/core/events/SharedResourceManager.java | 8 ++++++++ .../main/java/mc/core/events/api/LockableResource.java | 6 +++++- .../main/java/mc/core/events/lock/LockObserveList.java | 5 +++++ 4 files changed, 22 insertions(+), 1 deletion(-) diff --git a/event-loop/src/main/java/mc/core/events/EventPipelineTask.java b/event-loop/src/main/java/mc/core/events/EventPipelineTask.java index a071f58..53eafe4 100644 --- a/event-loop/src/main/java/mc/core/events/EventPipelineTask.java +++ b/event-loop/src/main/java/mc/core/events/EventPipelineTask.java @@ -5,6 +5,7 @@ import lombok.RequiredArgsConstructor; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import mc.core.events.api.EventQueueOwner; +import mc.core.events.api.LockableResource; import mc.core.events.lock.LockObserveList; import mc.core.events.runner.EventExecutorService; import mc.core.events.runner.ResourceRunnable; @@ -75,6 +76,9 @@ public class EventPipelineTask { if (handler.isPluginSynchronize()) locks.add(manager.getResourceManager().getPluginLock(handler.getPlugin())); + for (LockableResource resource : handler.getLock()) { + locks.addAll(manager.getResourceManager().getAnnotationLocks(resource, event)); + } return locks; } diff --git a/event-loop/src/main/java/mc/core/events/SharedResourceManager.java b/event-loop/src/main/java/mc/core/events/SharedResourceManager.java index 45f6e1a..8fb486f 100644 --- a/event-loop/src/main/java/mc/core/events/SharedResourceManager.java +++ b/event-loop/src/main/java/mc/core/events/SharedResourceManager.java @@ -1,8 +1,11 @@ package mc.core.events; +import mc.core.events.api.LockableResource; import mc.core.events.api.Plugin; import mc.core.events.lock.PoorMansLock; +import java.util.Collection; +import java.util.Collections; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -13,4 +16,9 @@ public class SharedResourceManager { return pluginLocks.computeIfAbsent(plugin, s -> new PoorMansLock()); } + public Collection getAnnotationLocks(LockableResource resource, Event event) { + // TODO: Implement + return Collections.emptyList(); + } + } diff --git a/event-loop/src/main/java/mc/core/events/api/LockableResource.java b/event-loop/src/main/java/mc/core/events/api/LockableResource.java index f6408c7..5b86b0a 100644 --- a/event-loop/src/main/java/mc/core/events/api/LockableResource.java +++ b/event-loop/src/main/java/mc/core/events/api/LockableResource.java @@ -2,5 +2,9 @@ package mc.core.events.api; public enum LockableResource { PLAYER, - WORLD; + PLAYER_WORLD, + EVENT_LOCATION_WORLD, + EVENT_WORLD + + // TODO: Add entity-related constants } diff --git a/event-loop/src/main/java/mc/core/events/lock/LockObserveList.java b/event-loop/src/main/java/mc/core/events/lock/LockObserveList.java index cf3ba5d..6fe8efa 100644 --- a/event-loop/src/main/java/mc/core/events/lock/LockObserveList.java +++ b/event-loop/src/main/java/mc/core/events/lock/LockObserveList.java @@ -18,6 +18,11 @@ public class LockObserveList implements Consumer { lock.addCallback(this); } + public void addAll(Iterable locks) { + for (PoorMansLock lock : locks) + add(lock); + } + public void release() { for (PoorMansLock lock : locks) { lock.removeCallback(this); From 3b24c2fed4b42381a2cd59bae9ead24bd3d5aa70 Mon Sep 17 00:00:00 2001 From: Daniil Date: Sun, 5 Aug 2018 22:33:39 +0700 Subject: [PATCH 44/62] Fixed task scheduling --- .../mc/core/events/lock/LockObserveList.java | 1 + .../events/runner/AllInScheduleStrategy.java | 44 +++++++++++++++++++ .../events/runner/EventExecutorService.java | 8 +++- .../mc/core/events/runner/ExecutorThread.java | 6 +++ 4 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 event-loop/src/main/java/mc/core/events/runner/AllInScheduleStrategy.java diff --git a/event-loop/src/main/java/mc/core/events/lock/LockObserveList.java b/event-loop/src/main/java/mc/core/events/lock/LockObserveList.java index 6fe8efa..5dd2985 100644 --- a/event-loop/src/main/java/mc/core/events/lock/LockObserveList.java +++ b/event-loop/src/main/java/mc/core/events/lock/LockObserveList.java @@ -24,6 +24,7 @@ public class LockObserveList implements Consumer { } public void release() { + callback = null; for (PoorMansLock lock : locks) { lock.removeCallback(this); } diff --git a/event-loop/src/main/java/mc/core/events/runner/AllInScheduleStrategy.java b/event-loop/src/main/java/mc/core/events/runner/AllInScheduleStrategy.java new file mode 100644 index 0000000..6de1556 --- /dev/null +++ b/event-loop/src/main/java/mc/core/events/runner/AllInScheduleStrategy.java @@ -0,0 +1,44 @@ +package mc.core.events.runner; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CountDownLatch; + +public class AllInScheduleStrategy implements ScheduleStrategy { + private BlockingQueue globalQueue; + private EventExecutorService eventExecutorService; + + public AllInScheduleStrategy(EventExecutorService eventExecutorService) { + this.globalQueue = eventExecutorService.queue; + this.eventExecutorService = eventExecutorService; + } + + + @Override + public synchronized ResourceRunnable getTask() throws InterruptedException { + + // Wait for last task to finish locking up + // the resources + synchronized (eventExecutorService.waitForLock) { + while (eventExecutorService.waitForLock.get()) { + eventExecutorService.wait(); + } + } + + // Wait for new task in queue + ResourceRunnable runnable = globalQueue.take(); + while (!runnable.getLocks().isReady()) { + CountDownLatch latch = new CountDownLatch(1); + runnable.getLocks().setCallback(latch::countDown); + // Prevent situations where dependencies were resolved + // while we were setting up the callback + if (runnable.getLocks().isReady()) + continue; + latch.await(); + } + + // Lock execution for the next thread + // (wait until resources for previous task will be blocked) + eventExecutorService.waitForLock.set(true); + return runnable; + } +} diff --git a/event-loop/src/main/java/mc/core/events/runner/EventExecutorService.java b/event-loop/src/main/java/mc/core/events/runner/EventExecutorService.java index 95f1eac..3f018ab 100644 --- a/event-loop/src/main/java/mc/core/events/runner/EventExecutorService.java +++ b/event-loop/src/main/java/mc/core/events/runner/EventExecutorService.java @@ -7,11 +7,15 @@ import java.util.Iterator; import java.util.Set; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; +import java.util.concurrent.atomic.AtomicBoolean; public class EventExecutorService { private static final boolean WORKER_INSTANT_EXECUTE = false; - private BlockingQueue queue = new ArrayBlockingQueue<>(100); - private ScheduleStrategy strategy = new DefaultScheduleStrategy(); + BlockingQueue queue = new ArrayBlockingQueue<>(100); + // A synchronize aid, that prevents ScheduleStrategy from returning + // wrong tasks when executor is late in blocking resources + final AtomicBoolean waitForLock = new AtomicBoolean(false); + private ScheduleStrategy strategy = new AllInScheduleStrategy(this); private Set executorThreads = new HashSet<>(); private int threadCount; diff --git a/event-loop/src/main/java/mc/core/events/runner/ExecutorThread.java b/event-loop/src/main/java/mc/core/events/runner/ExecutorThread.java index 8ff52f0..3e1ecc2 100644 --- a/event-loop/src/main/java/mc/core/events/runner/ExecutorThread.java +++ b/event-loop/src/main/java/mc/core/events/runner/ExecutorThread.java @@ -24,6 +24,12 @@ public class ExecutorThread extends Thread { void executeTask(ResourceRunnable runnable) { runnable.getLocks().lockAll(); + synchronized (service.waitForLock) { + if (service.waitForLock.get()) { + service.waitForLock.set(false); + service.waitForLock.notifyAll(); + } + } try { runnable.run(); } finally { From e6dfc1861bba50e6c940b3015b9625640d68831d Mon Sep 17 00:00:00 2001 From: Daniil Date: Sun, 5 Aug 2018 22:56:16 +0700 Subject: [PATCH 45/62] Implemented simple resource lock fetchers --- .../mc/core/events/SharedResourceManager.java | 44 ++++++++++++++++++- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/event-loop/src/main/java/mc/core/events/SharedResourceManager.java b/event-loop/src/main/java/mc/core/events/SharedResourceManager.java index 8fb486f..23bbabd 100644 --- a/event-loop/src/main/java/mc/core/events/SharedResourceManager.java +++ b/event-loop/src/main/java/mc/core/events/SharedResourceManager.java @@ -1,24 +1,64 @@ package mc.core.events; +import lombok.extern.slf4j.Slf4j; +import mc.core.Location; import mc.core.events.api.LockableResource; import mc.core.events.api.Plugin; +import mc.core.events.api.interfaces.LocationProvidingEvent; +import mc.core.events.api.interfaces.PlayerProvidingEvent; +import mc.core.events.api.interfaces.WorldProvidingEvent; import mc.core.events.lock.PoorMansLock; +import mc.core.player.Player; +import mc.core.world.World; import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; +@Slf4j public class SharedResourceManager { private Map pluginLocks = new ConcurrentHashMap<>(); + // TODO: Memory leak HERE. Fix with introducing field to Player class + private Map playerLocks = new ConcurrentHashMap<>(); + // TODO: Memory leak HERE. Fix with introducing field to World class + private Map worldLocks = new ConcurrentHashMap<>(); public PoorMansLock getPluginLock(Plugin plugin) { return pluginLocks.computeIfAbsent(plugin, s -> new PoorMansLock()); } + public PoorMansLock getPlayerLock(Player player) { + return playerLocks.computeIfAbsent(player, s -> new PoorMansLock()); + } + + public PoorMansLock getWorldLock(World world) { + return worldLocks.computeIfAbsent(world, s -> new PoorMansLock()); + } + + private T require(LockableResource resource, Event event, Class inter) { + if (inter.isInstance(event)) { + //noinspection unchecked + return (T) event; + } else + throw new IllegalArgumentException("Unable to lock " + resource + " while attempting to process event. Event " + event.getClass().getSimpleName() + " must implement " + inter); + } + public Collection getAnnotationLocks(LockableResource resource, Event event) { - // TODO: Implement - return Collections.emptyList(); + switch (resource) { + case PLAYER: + return require(resource, event, PlayerProvidingEvent.class).getAssociatedPlayers().stream().map(this::getPlayerLock).collect(Collectors.toList()); + case PLAYER_WORLD: + return require(resource, event, PlayerProvidingEvent.class).getAssociatedPlayers().stream().map(s -> s.getLocation().getWorld()).map(this::getWorldLock).collect(Collectors.toList()); + case EVENT_LOCATION_WORLD: + return require(resource, event, LocationProvidingEvent.class).getAssociatedLocations().stream().map(Location::getWorld).map(this::getWorldLock).collect(Collectors.toList()); + case EVENT_WORLD: + return require(resource, event, WorldProvidingEvent.class).getAssociatedWorlds().stream().map(this::getWorldLock).collect(Collectors.toList()); + default: + log.warn("Unable to find action for " + resource + " resource definition."); + return Collections.emptyList(); + } } } From 94c8baa61399cc924dc514f2ce8d1f9599d46827 Mon Sep 17 00:00:00 2001 From: Forwolk Date: Sun, 5 Aug 2018 19:36:59 +0300 Subject: [PATCH 46/62] fix: Simple chunk section --- .../mc/world/flat/SimpleChunkSection.java | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 flat_world/src/main/java/mc/world/flat/SimpleChunkSection.java diff --git a/flat_world/src/main/java/mc/world/flat/SimpleChunkSection.java b/flat_world/src/main/java/mc/world/flat/SimpleChunkSection.java new file mode 100644 index 0000000..1355c18 --- /dev/null +++ b/flat_world/src/main/java/mc/world/flat/SimpleChunkSection.java @@ -0,0 +1,119 @@ +/* + * DmitriyMX + * 2018-04-28 + */ +package mc.world.flat; + +import mc.core.block.Block; +import mc.core.block.BlockFactory; +import mc.core.block.BlockType; +import mc.core.world.Biome; +import mc.core.world.ChunkSection; +import mc.core.world.Region; +import mc.core.world.World; + +public class SimpleChunkSection implements ChunkSection { + @Override + public int getBlockType(int x, int y, int z) { + if (y == 0) return 7; + else if (y >= 1 && y <= 2) return 3; + else if (y == 3) return 2; + else return 0; + } + + @Override + public void setBlockType(int x, int y, int z, int type) { + + } + + @Override + public int getBlockMetadata(int x, int y, int z) { + return 0; + } + + @Override + public void setBlockMetadata(int x, int y, int z, int metadata) { + + } + + @Override + public int getBlockLight(int x, int y, int z) { + return 0; + } + + @Override + public void setBlockLight(int x, int y, int z, int lightLevel) { + + } + + @Override + public int getSkyLight(int x, int y, int z) { + if (y <= 3) return 0; + else return 15; + } + + @Override + public void setSkyLight(int x, int y, int z, int lightLevel) { + + } + + @Override + public int getAddition(int x, int y, int z) { + return 0; + } + + @Override + public void setAddition(int x, int y, int z, int value) { + + } + + @Override + public Biome getBiome(int x, int z) { + return Biome.PLAINS; + } + + @Override + public void setBiome(int x, int z, Biome biome) { + + } + + @Override + public int getX() { + return 0; + } + + @Override + public int getY() { + return 0; + } + + @Override + public int getZ() { + return 0; + } + + @Override + public void setBlock(int x, int y, int z, Block block) { + + } + + @Override + public Block getBlock(int x, int y, int z) { + BlockFactory blockFactory = new BlockFactory(); + + if (y == 0) return blockFactory.create(BlockType.BEDROCK, 0); + else if (y >= 1 && y <= 2) return blockFactory.create(BlockType.DIRT, 0); + else if (y == 3) return blockFactory.create(BlockType.GRASS, 0); + else return Block.airBlock(x, y, z); + } + + @Override + public Region getRegion() { + return null; + } + + @Override + public World getWorld() { + return null; + } +} From 993d9a82934027786246f4e519481a12a7bbcb42 Mon Sep 17 00:00:00 2001 From: Daniil Date: Mon, 6 Aug 2018 00:07:57 +0700 Subject: [PATCH 47/62] WarpPosition structure rework --- core/src/main/java/mc/core/WarpPosition.java | 90 ++++++++++++++++++-- core/src/main/java/mc/core/player/ILook.java | 15 ++++ core/src/main/java/mc/core/player/Look.java | 5 +- 3 files changed, 100 insertions(+), 10 deletions(-) create mode 100644 core/src/main/java/mc/core/player/ILook.java diff --git a/core/src/main/java/mc/core/WarpPosition.java b/core/src/main/java/mc/core/WarpPosition.java index 6e1aacf..4a0e501 100644 --- a/core/src/main/java/mc/core/WarpPosition.java +++ b/core/src/main/java/mc/core/WarpPosition.java @@ -1,14 +1,90 @@ package mc.core; -import lombok.AllArgsConstructor; -import lombok.Data; +import mc.core.player.ILook; import mc.core.player.Look; +import mc.core.world.World; import java.io.Serializable; +import java.util.Objects; -@Data -@AllArgsConstructor -public class WarpPosition implements Serializable { - private Location location; - private Look look; +public class WarpPosition extends Location implements Serializable, ILook { + private ILook look; + + public WarpPosition(double x, double y, double z, World world) { + super(x, y, z, world); + } + + public WarpPosition(double x, double y, double z, float yaw, float pitch, World world) { + super(x, y, z, world); + this.look = new Look(yaw, pitch); + } + + public WarpPosition(double x, double y, double z) { + super(x, y, z); + } + + public WarpPosition(double x, double y, double z, float yaw, float pitch) { + super(x, y, z); + this.look = new Look(yaw, pitch); + } + + public WarpPosition(long compactValue) { + super(compactValue); + } + + public WarpPosition(Location location) { + super(location.getX(), location.getY(), location.getZ()); + } + + public WarpPosition(Location location, Look look) { + super(location.getX(), location.getY(), location.getZ()); + this.look = look; + } + + public WarpPosition(long compactValue, World world) { + super(compactValue, world); + } + + @Override + public void set(Look look) { + this.look = look; + } + + @Override + public float getYaw() { + return this.look.getYaw(); + } + + @Override + public void setYaw(float yaw) { + this.look.setYaw(yaw); + } + + @Override + public float getPitch() { + return this.look.getPitch(); + } + + @Override + public void setPitch(float pitch) { + this.look.setPitch(pitch); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + WarpPosition that = (WarpPosition) o; + return Objects.equals(look, that.look); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), look); + } + + public boolean hasLook() { + return look != null; + } } diff --git a/core/src/main/java/mc/core/player/ILook.java b/core/src/main/java/mc/core/player/ILook.java new file mode 100644 index 0000000..fdebd51 --- /dev/null +++ b/core/src/main/java/mc/core/player/ILook.java @@ -0,0 +1,15 @@ +package mc.core.player; + +import java.io.Serializable; + +public interface ILook extends Serializable { + void set(Look look); + + float getYaw(); + + float getPitch(); + + void setYaw(float yaw); + + void setPitch(float pitch); +} diff --git a/core/src/main/java/mc/core/player/Look.java b/core/src/main/java/mc/core/player/Look.java index 1c0f7f4..f15f6f7 100644 --- a/core/src/main/java/mc/core/player/Look.java +++ b/core/src/main/java/mc/core/player/Look.java @@ -7,13 +7,12 @@ package mc.core.player; import lombok.AllArgsConstructor; import lombok.Data; -import java.io.Serializable; - @Data @AllArgsConstructor -public class Look implements Serializable{ +public class Look implements ILook { private float yaw, pitch; + @Override public void set(Look look) { this.yaw = look.yaw; this.pitch = look.pitch; From 0ca3e4db6f975ba23c1cc4260e615684be5dea46 Mon Sep 17 00:00:00 2001 From: Daniil Date: Mon, 6 Aug 2018 00:23:28 +0700 Subject: [PATCH 48/62] Proper Look structure refactoring --- core/src/main/java/mc/core/WarpPosition.java | 4 ++-- core/src/main/java/mc/core/embedded/FakePlayerManager.java | 4 ++-- core/src/main/java/mc/core/events/PlayerLookEvent.java | 4 ++-- core/src/main/java/mc/core/player/ILook.java | 6 ++---- .../main/java/mc/core/player/InMemoryPlayerManager.java | 2 +- core/src/main/java/mc/core/player/Look.java | 6 +++--- core/src/main/java/mc/core/player/Player.java | 2 +- core/src/main/java/mc/core/player/PlayerManager.java | 2 +- core/src/main/java/mc/core/player/SimplePlayer.java | 4 ++-- .../proto_1_12_2/packets/PlayerPositionAndLookPacket.java | 3 ++- .../network/proto_1_12_2/netty/handlers/LoginHandler.java | 7 +++---- 11 files changed, 21 insertions(+), 23 deletions(-) diff --git a/core/src/main/java/mc/core/WarpPosition.java b/core/src/main/java/mc/core/WarpPosition.java index 4a0e501..c23325e 100644 --- a/core/src/main/java/mc/core/WarpPosition.java +++ b/core/src/main/java/mc/core/WarpPosition.java @@ -36,7 +36,7 @@ public class WarpPosition extends Location implements Serializable, ILook { super(location.getX(), location.getY(), location.getZ()); } - public WarpPosition(Location location, Look look) { + public WarpPosition(Location location, ILook look) { super(location.getX(), location.getY(), location.getZ()); this.look = look; } @@ -46,7 +46,7 @@ public class WarpPosition extends Location implements Serializable, ILook { } @Override - public void set(Look look) { + public void set(ILook look) { this.look = look; } diff --git a/core/src/main/java/mc/core/embedded/FakePlayerManager.java b/core/src/main/java/mc/core/embedded/FakePlayerManager.java index 10a8ea5..061497a 100644 --- a/core/src/main/java/mc/core/embedded/FakePlayerManager.java +++ b/core/src/main/java/mc/core/embedded/FakePlayerManager.java @@ -8,7 +8,7 @@ import mc.core.Location; import mc.core.chat.MessageType; import mc.core.network.NetChannel; import mc.core.network.SCPacket; -import mc.core.player.Look; +import mc.core.player.ILook; import mc.core.player.Player; import mc.core.player.PlayerManager; import mc.core.text.Text; @@ -52,7 +52,7 @@ public class FakePlayerManager implements PlayerManager { private static final NetChannel FAKE_NET_CHANNEL = new FakeNetChannet(); @Override - public Player createPlayer(String name, Location defaultLocation, Look defaultLook) { + public Player createPlayer(String name, Location defaultLocation, ILook defaultLook) { return null; } diff --git a/core/src/main/java/mc/core/events/PlayerLookEvent.java b/core/src/main/java/mc/core/events/PlayerLookEvent.java index 2d03b0b..fc4734f 100644 --- a/core/src/main/java/mc/core/events/PlayerLookEvent.java +++ b/core/src/main/java/mc/core/events/PlayerLookEvent.java @@ -7,7 +7,7 @@ package mc.core.events; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.Setter; -import mc.core.player.Look; +import mc.core.player.ILook; import mc.core.player.Player; @RequiredArgsConstructor @@ -15,5 +15,5 @@ import mc.core.player.Player; @Setter public class PlayerLookEvent extends EventBase { private final Player player; - private Look newLook; + private ILook newLook; } diff --git a/core/src/main/java/mc/core/player/ILook.java b/core/src/main/java/mc/core/player/ILook.java index fdebd51..e0981bf 100644 --- a/core/src/main/java/mc/core/player/ILook.java +++ b/core/src/main/java/mc/core/player/ILook.java @@ -1,9 +1,7 @@ package mc.core.player; -import java.io.Serializable; - -public interface ILook extends Serializable { - void set(Look look); +public interface ILook { + void set(ILook look); float getYaw(); diff --git a/core/src/main/java/mc/core/player/InMemoryPlayerManager.java b/core/src/main/java/mc/core/player/InMemoryPlayerManager.java index 0ba717c..bf62814 100644 --- a/core/src/main/java/mc/core/player/InMemoryPlayerManager.java +++ b/core/src/main/java/mc/core/player/InMemoryPlayerManager.java @@ -31,7 +31,7 @@ public class InMemoryPlayerManager implements PlayerManager, Runnable { } @Override - public Player createPlayer(String name, Location defaultLocation, Look defaultLook) { + public Player createPlayer(String name, Location defaultLocation, ILook defaultLook) { SimplePlayer player = new SimplePlayer(); player.setId(rand.nextInt(10000)); player.setUUID(UUID.nameUUIDFromBytes(name.getBytes())); diff --git a/core/src/main/java/mc/core/player/Look.java b/core/src/main/java/mc/core/player/Look.java index f15f6f7..a13da39 100644 --- a/core/src/main/java/mc/core/player/Look.java +++ b/core/src/main/java/mc/core/player/Look.java @@ -13,8 +13,8 @@ public class Look implements ILook { private float yaw, pitch; @Override - public void set(Look look) { - this.yaw = look.yaw; - this.pitch = look.pitch; + public void set(ILook look) { + this.yaw = look.getYaw(); + this.pitch = look.getPitch(); } } diff --git a/core/src/main/java/mc/core/player/Player.java b/core/src/main/java/mc/core/player/Player.java index 0b0e2c2..ababfb0 100644 --- a/core/src/main/java/mc/core/player/Player.java +++ b/core/src/main/java/mc/core/player/Player.java @@ -20,7 +20,7 @@ public interface Player { Location getLocation(); - Look getLook(); + ILook getLook(); boolean isFlying(); void setFlying(boolean value); diff --git a/core/src/main/java/mc/core/player/PlayerManager.java b/core/src/main/java/mc/core/player/PlayerManager.java index 8dd1c7a..aa63e66 100644 --- a/core/src/main/java/mc/core/player/PlayerManager.java +++ b/core/src/main/java/mc/core/player/PlayerManager.java @@ -11,7 +11,7 @@ import java.util.List; import java.util.Optional; public interface PlayerManager { - Player createPlayer(String name, Location defaultLocation, Look defaultLook); + Player createPlayer(String name, Location defaultLocation, ILook defaultLook); void joinServer(Player player); void leftServer(Player player); Optional getPlayer(String name); diff --git a/core/src/main/java/mc/core/player/SimplePlayer.java b/core/src/main/java/mc/core/player/SimplePlayer.java index f841595..a918971 100644 --- a/core/src/main/java/mc/core/player/SimplePlayer.java +++ b/core/src/main/java/mc/core/player/SimplePlayer.java @@ -18,7 +18,7 @@ public class SimplePlayer implements Player { private boolean online = false; private NetChannel channel; private Location location = new Location(0, 0, 0); - private Look look = new Look(0, 0); + private ILook look = new Look(0, 0); private boolean flying = false; private PlayerSettings settings; @@ -26,7 +26,7 @@ public class SimplePlayer implements Player { this.location.set(location); } - public void setLook(Look look) { + public void setLook(ILook look) { this.look.set(look); } diff --git a/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/PlayerPositionAndLookPacket.java b/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/PlayerPositionAndLookPacket.java index 4133bbe..1255e0c 100644 --- a/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/PlayerPositionAndLookPacket.java +++ b/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/PlayerPositionAndLookPacket.java @@ -10,6 +10,7 @@ import lombok.Setter; import lombok.ToString; import mc.core.Location; import mc.core.network.*; +import mc.core.player.ILook; import mc.core.player.Look; @NoArgsConstructor @@ -18,7 +19,7 @@ import mc.core.player.Look; @ToString public class PlayerPositionAndLookPacket implements SCPacket, CSPacket { private Location location; - private Look look; + private ILook look; private int teleportId; private boolean onGround = false; diff --git a/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/handlers/LoginHandler.java b/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/handlers/LoginHandler.java index bbbbb6d..a136bd2 100644 --- a/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/handlers/LoginHandler.java +++ b/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/handlers/LoginHandler.java @@ -10,7 +10,6 @@ import mc.core.network.proto_1_12_2.State; import mc.core.network.proto_1_12_2.TeleportManager; import mc.core.network.proto_1_12_2.netty.wrappers.WrapperNetChannel; import mc.core.network.proto_1_12_2.packets.*; -import mc.core.player.Look; import mc.core.player.Player; import mc.core.player.PlayerManager; import mc.core.player.PlayerMode; @@ -48,8 +47,8 @@ public class LoginHandler extends AbstractStateHandler implements LoginStateHand Player player = playerManager.getPlayer(packet.getPlayerName()) .orElseGet(() -> playerManager.createPlayer( packet.getPlayerName(), - world.getSpawn().getLocation(), - world.getSpawn().getLook())); + world.getSpawn(), + world.getSpawn())); channel.writeAndFlush(new LoginSuccessPacket( player.getUUID(), @@ -68,7 +67,7 @@ public class LoginHandler extends AbstractStateHandler implements LoginStateHand // Spawn Position SpawnPositionPacket pkt2 = new SpawnPositionPacket(); - pkt2.setLocation(world.getSpawn().getLocation()); + pkt2.setLocation(world.getSpawn()); channel.write(pkt2); // Player Abilities From a41b0d24480072612178b0304fa85cb3c147bacc Mon Sep 17 00:00:00 2001 From: Daniil Date: Mon, 6 Aug 2018 23:39:54 +0700 Subject: [PATCH 49/62] TODO commit --- event-loop/TODO | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 event-loop/TODO diff --git a/event-loop/TODO b/event-loop/TODO new file mode 100644 index 0000000..373999f --- /dev/null +++ b/event-loop/TODO @@ -0,0 +1,7 @@ +- Система иерархических блокировок ресурсов (чанки в мире) + - Нужно что-то делать с подгрузкой отсутсвующих чанков для таких блокировок +- Возможность вызвать событие из EventHandler +- Performance Monitor +- Возможная проблема с переполнением очереди при спаме пакетами от игрока +- Добавить поля с замками для ресурсов (Player, World, Chunk) +- Time Scheduler \ No newline at end of file From 000816f110d5689a8567ba161a1fd6826c3762f3 Mon Sep 17 00:00:00 2001 From: Daniil Date: Tue, 7 Aug 2018 13:29:05 +0700 Subject: [PATCH 50/62] Timings foundation --- .../mc/core/timings/MeasurableThread.java | 5 ++ .../java/mc/core/timings/ThreadTimings.java | 33 ++++++++++++ .../main/java/mc/core/timings/Timings.java | 51 +++++++++++++++++++ .../java/mc/core/timings/TimingsManager.java | 19 +++++++ 4 files changed, 108 insertions(+) create mode 100644 event-loop/src/main/java/mc/core/timings/MeasurableThread.java create mode 100644 event-loop/src/main/java/mc/core/timings/ThreadTimings.java create mode 100644 event-loop/src/main/java/mc/core/timings/Timings.java create mode 100644 event-loop/src/main/java/mc/core/timings/TimingsManager.java diff --git a/event-loop/src/main/java/mc/core/timings/MeasurableThread.java b/event-loop/src/main/java/mc/core/timings/MeasurableThread.java new file mode 100644 index 0000000..9cd2ac3 --- /dev/null +++ b/event-loop/src/main/java/mc/core/timings/MeasurableThread.java @@ -0,0 +1,5 @@ +package mc.core.timings; + +public interface MeasurableThread { + ThreadTimings getTimings(); +} diff --git a/event-loop/src/main/java/mc/core/timings/ThreadTimings.java b/event-loop/src/main/java/mc/core/timings/ThreadTimings.java new file mode 100644 index 0000000..f05ad22 --- /dev/null +++ b/event-loop/src/main/java/mc/core/timings/ThreadTimings.java @@ -0,0 +1,33 @@ +package mc.core.timings; + +import java.util.Stack; +import java.util.concurrent.atomic.AtomicInteger; + +public class ThreadTimings { + private static AtomicInteger IDS = new AtomicInteger(); + private int threadId; + private Stack stack = new Stack<>(); + + public ThreadTimings() { + this.threadId = IDS.getAndIncrement(); + } + + public int getThreadId() { + return threadId; + } + + public Timings start() { + Timings timings = new Timings(this, stack.size()); + stack.push(timings); + return timings; + } + + public void end(Timings finished) { + Timings timings = null; + while (!stack.isEmpty() && timings != finished) { + timings = stack.pop(); + if (!timings.hasFinished()) + timings.finish(); + } + } +} diff --git a/event-loop/src/main/java/mc/core/timings/Timings.java b/event-loop/src/main/java/mc/core/timings/Timings.java new file mode 100644 index 0000000..8c537cd --- /dev/null +++ b/event-loop/src/main/java/mc/core/timings/Timings.java @@ -0,0 +1,51 @@ +package mc.core.timings; + +public class Timings implements AutoCloseable { + private ThreadTimings threadTimings; + private long acquireTime; + @SuppressWarnings("FieldCanBeLocal") + private long endTime = -1; + private int id; + + public Timings(ThreadTimings threadTimings, int id) { + this.id = id; + this.threadTimings = threadTimings; + this.acquireTime = System.nanoTime(); + } + + public static Timings start() { + return TimingsManager.TIMINGS_MANAGER.start(); + } + + public static TimingsManager getTimingsManager() { + return TimingsManager.TIMINGS_MANAGER; + } + + public int getId() { + return id; + } + + public long getEndTime() { + return endTime; + } + + public long getAcquireTime() { + return acquireTime; + } + + public boolean hasFinished() { + return endTime != -1; + } + + public void finish() { + if (hasFinished()) + throw new IllegalStateException("This timing was already finished"); + this.endTime = System.nanoTime(); + } + + @Override + public void close() { + finish(); + this.threadTimings.end(this); + } +} diff --git a/event-loop/src/main/java/mc/core/timings/TimingsManager.java b/event-loop/src/main/java/mc/core/timings/TimingsManager.java new file mode 100644 index 0000000..05a49d5 --- /dev/null +++ b/event-loop/src/main/java/mc/core/timings/TimingsManager.java @@ -0,0 +1,19 @@ +package mc.core.timings; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class TimingsManager { + static TimingsManager TIMINGS_MANAGER = new TimingsManager(); + private Map threadTimings = new ConcurrentHashMap<>(); + + public Timings start() { + return getCurrentThreadTimings().start(); + } + + public ThreadTimings getCurrentThreadTimings() { + if (Thread.currentThread() instanceof MeasurableThread) { + return ((MeasurableThread) Thread.currentThread()).getTimings(); + } else return this.threadTimings.computeIfAbsent(Thread.currentThread(), s -> new ThreadTimings()); + } +} From b2d3792384d8f76bf401696b8734f67c976bfd1d Mon Sep 17 00:00:00 2001 From: Daniil Date: Tue, 7 Aug 2018 16:06:34 +0700 Subject: [PATCH 51/62] Timings IO foundation --- .../java/mc/core/timings/ThreadTimings.java | 6 + .../main/java/mc/core/timings/Timings.java | 2 +- .../mc/core/timings/TimingsFileWriter.java | 72 +++++++++++ .../java/mc/core/timings/TimingsManager.java | 112 +++++++++++++++++- .../java/mc/core/timings/TimingsRecord.java | 31 +++++ 5 files changed, 218 insertions(+), 5 deletions(-) create mode 100644 event-loop/src/main/java/mc/core/timings/TimingsFileWriter.java create mode 100644 event-loop/src/main/java/mc/core/timings/TimingsRecord.java diff --git a/event-loop/src/main/java/mc/core/timings/ThreadTimings.java b/event-loop/src/main/java/mc/core/timings/ThreadTimings.java index f05ad22..2973839 100644 --- a/event-loop/src/main/java/mc/core/timings/ThreadTimings.java +++ b/event-loop/src/main/java/mc/core/timings/ThreadTimings.java @@ -8,6 +8,10 @@ public class ThreadTimings { private int threadId; private Stack stack = new Stack<>(); + public Stack getStack() { + return stack; + } + public ThreadTimings() { this.threadId = IDS.getAndIncrement(); } @@ -18,6 +22,7 @@ public class ThreadTimings { public Timings start() { Timings timings = new Timings(this, stack.size()); + Timings.getTimingsManager().waitForTimingsInitialize(); stack.push(timings); return timings; } @@ -25,6 +30,7 @@ public class ThreadTimings { public void end(Timings finished) { Timings timings = null; while (!stack.isEmpty() && timings != finished) { + Timings.getTimingsManager().waitForTimingsInitialize(); timings = stack.pop(); if (!timings.hasFinished()) timings.finish(); diff --git a/event-loop/src/main/java/mc/core/timings/Timings.java b/event-loop/src/main/java/mc/core/timings/Timings.java index 8c537cd..3824b3c 100644 --- a/event-loop/src/main/java/mc/core/timings/Timings.java +++ b/event-loop/src/main/java/mc/core/timings/Timings.java @@ -14,7 +14,7 @@ public class Timings implements AutoCloseable { } public static Timings start() { - return TimingsManager.TIMINGS_MANAGER.start(); + return TimingsManager.TIMINGS_MANAGER.getCurrentThreadTimings().start(); } public static TimingsManager getTimingsManager() { diff --git a/event-loop/src/main/java/mc/core/timings/TimingsFileWriter.java b/event-loop/src/main/java/mc/core/timings/TimingsFileWriter.java new file mode 100644 index 0000000..451fb88 --- /dev/null +++ b/event-loop/src/main/java/mc/core/timings/TimingsFileWriter.java @@ -0,0 +1,72 @@ +package mc.core.timings; + + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.util.concurrent.locks.ReentrantLock; + +@SuppressWarnings("Duplicates") +@Slf4j +public class TimingsFileWriter { + private FileOutputStream fileOutputStream; + private ObjectOutputStream writer; + private ReentrantLock lock = new ReentrantLock(); + + public TimingsFileWriter(File saveFile) throws IOException { + fileOutputStream = new FileOutputStream(saveFile); + writer = new ObjectOutputStream(fileOutputStream); + } + + public void writeEvent(int threadId, int stackId, long time, TimingsEventType type) { + lock.lock(); + try { + writer.write(threadId); + writer.write(stackId); + writer.writeLong(time); + writer.writeShort(type.getId()); + } catch (IOException e) { + log.error("Unable to write timings record", e); + } finally { + lock.unlock(); + } + } + + public void writeEvent(int threadId, int stackId, long time, TimingsEventType type, String data) { + lock.lock(); + try { + writer.write(threadId); + writer.write(stackId); + writer.writeLong(time); + writer.writeShort(type.getId()); + writer.writeUTF(data); + } catch (IOException e) { + log.error("Unable to write timings record", e); + } finally { + lock.unlock(); + } + } + + public void close() throws IOException { + writer.close(); + fileOutputStream.close(); + } + + @RequiredArgsConstructor + public enum TimingsEventType { + TIMINGS_START((short) 0), + TIMINGS_END((short) 1), + TIMINGS_FILE_INITIALIZING((short) 2), + TIMINGS_FILE_INITIALIZED((short) 3), + TIMINGS_CHANGE_THREAD_OPTIONS((short) 4), + TIMINGS_FILE_END((short) 5); + + @Getter + private final short id; + } +} diff --git a/event-loop/src/main/java/mc/core/timings/TimingsManager.java b/event-loop/src/main/java/mc/core/timings/TimingsManager.java index 05a49d5..f5105ec 100644 --- a/event-loop/src/main/java/mc/core/timings/TimingsManager.java +++ b/event-loop/src/main/java/mc/core/timings/TimingsManager.java @@ -1,14 +1,118 @@ package mc.core.timings; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; +import lombok.extern.slf4j.Slf4j; +import java.io.File; +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.ReentrantLock; + +@Slf4j public class TimingsManager { static TimingsManager TIMINGS_MANAGER = new TimingsManager(); + private final AtomicBoolean waitForFile = new AtomicBoolean(false); private Map threadTimings = new ConcurrentHashMap<>(); + private TimingsFileWriter writer; + private Thread timingsIoThread; + private CountDownLatch ioThreadStopMutex; + private BlockingQueue queue; + private ReentrantLock queueAccessLock = new ReentrantLock(); + + public void startRecording(File file) { + synchronized (waitForFile) { + waitForFile.set(true); + } + try { + writer = new TimingsFileWriter(file); + writer.writeEvent(0, 0, System.nanoTime(), TimingsFileWriter.TimingsEventType.TIMINGS_FILE_INITIALIZING); + // Synchronize current thread state + for (Map.Entry pair : threadTimings.entrySet()) { + writer.writeEvent(pair.getValue().getThreadId(), 0, System.nanoTime(), TimingsFileWriter.TimingsEventType.TIMINGS_CHANGE_THREAD_OPTIONS, "name: " + pair.getKey().getName()); + for (Timings timings : pair.getValue().getStack()) { + writer.writeEvent(pair.getValue().getThreadId(), timings.getId(), timings.getAcquireTime(), TimingsFileWriter.TimingsEventType.TIMINGS_START); + } + } + writer.writeEvent(0, 0, System.nanoTime(), TimingsFileWriter.TimingsEventType.TIMINGS_FILE_INITIALIZED); + queue = new ArrayBlockingQueue<>(200); + ioThreadStopMutex = new CountDownLatch(1); + timingsIoThread = new Thread() { + @Override + public void run() { + try { + while (!isInterrupted() && isAlive()) { + TimingsRecord record; + try { + record = queue.take(); + } catch (InterruptedException e) { + return; + } + record.writeToFile(writer); + } + } finally { + ioThreadStopMutex.countDown(); + } + } + }; + } catch (IOException e) { + log.error("Unable to start timings recording", e); + } + synchronized (waitForFile) { + waitForFile.set(false); + waitForFile.notifyAll(); + } + } + + public void stopRecording() { + // Disable write queue + queueAccessLock.lock(); + queue = null; + queueAccessLock.unlock(); + // Interrupt thread and wait until in finished writing the last task + timingsIoThread.interrupt(); + try { + ioThreadStopMutex.await(); + } catch (InterruptedException e) { + log.error("Unable to wait until last record would be written to file", e); + } + // Write EOF event + writer.writeEvent(0, 0, System.nanoTime(), TimingsFileWriter.TimingsEventType.TIMINGS_FILE_END); + // Unload file + try { + writer.close(); + } catch (IOException e) { + log.error("Unable to close timings file", e); + } + writer = null; + } + + void addToQueue(TimingsRecord record) { + if (queue == null) + return; + queueAccessLock.lock(); + try { + if (queue != null) + queue.offer(record); + } finally { + queueAccessLock.unlock(); + } + } + + void waitForTimingsInitialize() { + synchronized (waitForFile) { + while (waitForFile.get()) { + try { + waitForFile.wait(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } - public Timings start() { - return getCurrentThreadTimings().start(); } public ThreadTimings getCurrentThreadTimings() { diff --git a/event-loop/src/main/java/mc/core/timings/TimingsRecord.java b/event-loop/src/main/java/mc/core/timings/TimingsRecord.java new file mode 100644 index 0000000..38eef49 --- /dev/null +++ b/event-loop/src/main/java/mc/core/timings/TimingsRecord.java @@ -0,0 +1,31 @@ +package mc.core.timings; + +public class TimingsRecord { + private int threadId; + private int stackId; + private long time; + private TimingsFileWriter.TimingsEventType eventType; + private String data; + + public TimingsRecord(int threadId, int stackId, long time, TimingsFileWriter.TimingsEventType eventType) { + this.threadId = threadId; + this.stackId = stackId; + this.time = time; + this.eventType = eventType; + } + + public TimingsRecord(int threadId, int stackId, long time, TimingsFileWriter.TimingsEventType eventType, String data) { + this.threadId = threadId; + this.stackId = stackId; + this.time = time; + this.eventType = eventType; + this.data = data; + } + + public void writeToFile(TimingsFileWriter fileWriter) { + if (data == null) + fileWriter.writeEvent(threadId, stackId, time, eventType); + else + fileWriter.writeEvent(threadId, stackId, time, eventType, data); + } +} From 22dbf8caa84a132659d588f08177054c71e7b91a Mon Sep 17 00:00:00 2001 From: Daniil Date: Tue, 7 Aug 2018 16:11:27 +0700 Subject: [PATCH 52/62] Timings are now written to the file --- .../java/mc/core/timings/ThreadTimings.java | 18 ++++++++++++------ .../java/mc/core/timings/TimingsManager.java | 10 ++++++++-- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/event-loop/src/main/java/mc/core/timings/ThreadTimings.java b/event-loop/src/main/java/mc/core/timings/ThreadTimings.java index 2973839..408842b 100644 --- a/event-loop/src/main/java/mc/core/timings/ThreadTimings.java +++ b/event-loop/src/main/java/mc/core/timings/ThreadTimings.java @@ -8,32 +8,38 @@ public class ThreadTimings { private int threadId; private Stack stack = new Stack<>(); - public Stack getStack() { - return stack; - } - public ThreadTimings() { this.threadId = IDS.getAndIncrement(); } + public Stack getStack() { + return stack; + } + public int getThreadId() { return threadId; } public Timings start() { Timings timings = new Timings(this, stack.size()); - Timings.getTimingsManager().waitForTimingsInitialize(); + getTimingsManager().waitForTimingsInitialize(); stack.push(timings); + getTimingsManager().notifyTimings(this, timings, true); return timings; } + private TimingsManager getTimingsManager() { + return Timings.getTimingsManager(); + } + public void end(Timings finished) { Timings timings = null; while (!stack.isEmpty() && timings != finished) { - Timings.getTimingsManager().waitForTimingsInitialize(); + getTimingsManager().waitForTimingsInitialize(); timings = stack.pop(); if (!timings.hasFinished()) timings.finish(); + getTimingsManager().notifyTimings(this, timings, false); } } } diff --git a/event-loop/src/main/java/mc/core/timings/TimingsManager.java b/event-loop/src/main/java/mc/core/timings/TimingsManager.java index f5105ec..77e36a0 100644 --- a/event-loop/src/main/java/mc/core/timings/TimingsManager.java +++ b/event-loop/src/main/java/mc/core/timings/TimingsManager.java @@ -90,13 +90,19 @@ public class TimingsManager { writer = null; } - void addToQueue(TimingsRecord record) { + void notifyTimings(ThreadTimings thread, Timings timings, boolean start) { if (queue == null) return; queueAccessLock.lock(); try { if (queue != null) - queue.offer(record); + queue.offer( + new TimingsRecord(thread.getThreadId(), + timings.getId(), + start ? timings.getAcquireTime() : timings.getEndTime(), + start ? TimingsFileWriter.TimingsEventType.TIMINGS_START : TimingsFileWriter.TimingsEventType.TIMINGS_END + ) + ); } finally { queueAccessLock.unlock(); } From 6bd8b608321d1a08c42d126d6acbd9a2c9aeadde Mon Sep 17 00:00:00 2001 From: Daniil Date: Tue, 7 Aug 2018 16:27:44 +0700 Subject: [PATCH 53/62] Timings submodule refactoring --- .../main/java/mc/core/timings/Timings.java | 4 +- .../mc/core/timings/TimingsEventType.java | 17 +++++++++ .../java/mc/core/timings/TimingsManager.java | 37 ++++++++++++++----- .../java/mc/core/timings/TimingsRecord.java | 12 +++--- .../core/timings/TimingsStaticAccessor.java | 9 +++++ .../core/timings/io/DefaultWriterFactory.java | 11 ++++++ .../timings/{ => io}/TimingsFileWriter.java | 23 +++--------- .../mc/core/timings/io/TimingsLogWriter.java | 24 ++++++++++++ .../mc/core/timings/io/TimingsWriter.java | 13 +++++++ .../core/timings/io/TimingsWriterFactory.java | 8 ++++ 10 files changed, 124 insertions(+), 34 deletions(-) create mode 100644 event-loop/src/main/java/mc/core/timings/TimingsEventType.java create mode 100644 event-loop/src/main/java/mc/core/timings/TimingsStaticAccessor.java create mode 100644 event-loop/src/main/java/mc/core/timings/io/DefaultWriterFactory.java rename event-loop/src/main/java/mc/core/timings/{ => io}/TimingsFileWriter.java (76%) create mode 100644 event-loop/src/main/java/mc/core/timings/io/TimingsLogWriter.java create mode 100644 event-loop/src/main/java/mc/core/timings/io/TimingsWriter.java create mode 100644 event-loop/src/main/java/mc/core/timings/io/TimingsWriterFactory.java diff --git a/event-loop/src/main/java/mc/core/timings/Timings.java b/event-loop/src/main/java/mc/core/timings/Timings.java index 3824b3c..b9d1252 100644 --- a/event-loop/src/main/java/mc/core/timings/Timings.java +++ b/event-loop/src/main/java/mc/core/timings/Timings.java @@ -14,11 +14,11 @@ public class Timings implements AutoCloseable { } public static Timings start() { - return TimingsManager.TIMINGS_MANAGER.getCurrentThreadTimings().start(); + return TimingsStaticAccessor.getTimingsManager().getCurrentThreadTimings().start(); } public static TimingsManager getTimingsManager() { - return TimingsManager.TIMINGS_MANAGER; + return TimingsStaticAccessor.getTimingsManager(); } public int getId() { diff --git a/event-loop/src/main/java/mc/core/timings/TimingsEventType.java b/event-loop/src/main/java/mc/core/timings/TimingsEventType.java new file mode 100644 index 0000000..181643a --- /dev/null +++ b/event-loop/src/main/java/mc/core/timings/TimingsEventType.java @@ -0,0 +1,17 @@ +package mc.core.timings; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public enum TimingsEventType { + TIMINGS_START((short) 0), + TIMINGS_END((short) 1), + TIMINGS_FILE_INITIALIZING((short) 2), + TIMINGS_FILE_INITIALIZED((short) 3), + TIMINGS_CHANGE_THREAD_OPTIONS((short) 4), + TIMINGS_FILE_END((short) 5); + + @Getter + private final short id; +} \ No newline at end of file diff --git a/event-loop/src/main/java/mc/core/timings/TimingsManager.java b/event-loop/src/main/java/mc/core/timings/TimingsManager.java index 77e36a0..07ac32a 100644 --- a/event-loop/src/main/java/mc/core/timings/TimingsManager.java +++ b/event-loop/src/main/java/mc/core/timings/TimingsManager.java @@ -1,6 +1,11 @@ package mc.core.timings; +import lombok.Setter; import lombok.extern.slf4j.Slf4j; +import mc.core.timings.io.DefaultWriterFactory; +import mc.core.timings.io.TimingsWriter; +import mc.core.timings.io.TimingsWriterFactory; +import org.springframework.beans.factory.annotation.Autowired; import java.io.File; import java.io.IOException; @@ -14,30 +19,38 @@ import java.util.concurrent.locks.ReentrantLock; @Slf4j public class TimingsManager { - static TimingsManager TIMINGS_MANAGER = new TimingsManager(); + // These variables are essential in Timings thread synchronization private final AtomicBoolean waitForFile = new AtomicBoolean(false); private Map threadTimings = new ConcurrentHashMap<>(); - private TimingsFileWriter writer; + private TimingsWriter writer; private Thread timingsIoThread; private CountDownLatch ioThreadStopMutex; private BlockingQueue queue; private ReentrantLock queueAccessLock = new ReentrantLock(); + // For modularity purposes + @Autowired + @Setter + private TimingsWriterFactory writerFactory = new DefaultWriterFactory(); + + public TimingsManager() { + TimingsStaticAccessor.TIMINGS_MANAGER = this; + } public void startRecording(File file) { synchronized (waitForFile) { waitForFile.set(true); } try { - writer = new TimingsFileWriter(file); - writer.writeEvent(0, 0, System.nanoTime(), TimingsFileWriter.TimingsEventType.TIMINGS_FILE_INITIALIZING); + writer = writerFactory.newInstance(file); + writer.writeEvent(0, 0, System.nanoTime(), TimingsEventType.TIMINGS_FILE_INITIALIZING); // Synchronize current thread state for (Map.Entry pair : threadTimings.entrySet()) { - writer.writeEvent(pair.getValue().getThreadId(), 0, System.nanoTime(), TimingsFileWriter.TimingsEventType.TIMINGS_CHANGE_THREAD_OPTIONS, "name: " + pair.getKey().getName()); + writer.writeEvent(pair.getValue().getThreadId(), 0, System.nanoTime(), TimingsEventType.TIMINGS_CHANGE_THREAD_OPTIONS, "name: " + pair.getKey().getName()); for (Timings timings : pair.getValue().getStack()) { - writer.writeEvent(pair.getValue().getThreadId(), timings.getId(), timings.getAcquireTime(), TimingsFileWriter.TimingsEventType.TIMINGS_START); + writer.writeEvent(pair.getValue().getThreadId(), timings.getId(), timings.getAcquireTime(), TimingsEventType.TIMINGS_START); } } - writer.writeEvent(0, 0, System.nanoTime(), TimingsFileWriter.TimingsEventType.TIMINGS_FILE_INITIALIZED); + writer.writeEvent(0, 0, System.nanoTime(), TimingsEventType.TIMINGS_FILE_INITIALIZED); queue = new ArrayBlockingQueue<>(200); ioThreadStopMutex = new CountDownLatch(1); timingsIoThread = new Thread() { @@ -47,6 +60,8 @@ public class TimingsManager { while (!isInterrupted() && isAlive()) { TimingsRecord record; try { + if (queue == null) + return; record = queue.take(); } catch (InterruptedException e) { return; @@ -58,7 +73,9 @@ public class TimingsManager { } } }; - } catch (IOException e) { + timingsIoThread.setName("Timings IO thread"); + timingsIoThread.start(); + } catch (Exception e) { log.error("Unable to start timings recording", e); } synchronized (waitForFile) { @@ -80,7 +97,7 @@ public class TimingsManager { log.error("Unable to wait until last record would be written to file", e); } // Write EOF event - writer.writeEvent(0, 0, System.nanoTime(), TimingsFileWriter.TimingsEventType.TIMINGS_FILE_END); + writer.writeEvent(0, 0, System.nanoTime(), TimingsEventType.TIMINGS_FILE_END); // Unload file try { writer.close(); @@ -100,7 +117,7 @@ public class TimingsManager { new TimingsRecord(thread.getThreadId(), timings.getId(), start ? timings.getAcquireTime() : timings.getEndTime(), - start ? TimingsFileWriter.TimingsEventType.TIMINGS_START : TimingsFileWriter.TimingsEventType.TIMINGS_END + start ? TimingsEventType.TIMINGS_START : TimingsEventType.TIMINGS_END ) ); } finally { diff --git a/event-loop/src/main/java/mc/core/timings/TimingsRecord.java b/event-loop/src/main/java/mc/core/timings/TimingsRecord.java index 38eef49..3d3e3f6 100644 --- a/event-loop/src/main/java/mc/core/timings/TimingsRecord.java +++ b/event-loop/src/main/java/mc/core/timings/TimingsRecord.java @@ -1,20 +1,22 @@ package mc.core.timings; -public class TimingsRecord { +import mc.core.timings.io.TimingsWriter; + +class TimingsRecord { private int threadId; private int stackId; private long time; - private TimingsFileWriter.TimingsEventType eventType; + private TimingsEventType eventType; private String data; - public TimingsRecord(int threadId, int stackId, long time, TimingsFileWriter.TimingsEventType eventType) { + public TimingsRecord(int threadId, int stackId, long time, TimingsEventType eventType) { this.threadId = threadId; this.stackId = stackId; this.time = time; this.eventType = eventType; } - public TimingsRecord(int threadId, int stackId, long time, TimingsFileWriter.TimingsEventType eventType, String data) { + public TimingsRecord(int threadId, int stackId, long time, TimingsEventType eventType, String data) { this.threadId = threadId; this.stackId = stackId; this.time = time; @@ -22,7 +24,7 @@ public class TimingsRecord { this.data = data; } - public void writeToFile(TimingsFileWriter fileWriter) { + public void writeToFile(TimingsWriter fileWriter) { if (data == null) fileWriter.writeEvent(threadId, stackId, time, eventType); else diff --git a/event-loop/src/main/java/mc/core/timings/TimingsStaticAccessor.java b/event-loop/src/main/java/mc/core/timings/TimingsStaticAccessor.java new file mode 100644 index 0000000..35d0ed4 --- /dev/null +++ b/event-loop/src/main/java/mc/core/timings/TimingsStaticAccessor.java @@ -0,0 +1,9 @@ +package mc.core.timings; + +public class TimingsStaticAccessor { + static TimingsManager TIMINGS_MANAGER; + + public static TimingsManager getTimingsManager() { + return TIMINGS_MANAGER != null ? TIMINGS_MANAGER : (TIMINGS_MANAGER = new TimingsManager()); + } +} diff --git a/event-loop/src/main/java/mc/core/timings/io/DefaultWriterFactory.java b/event-loop/src/main/java/mc/core/timings/io/DefaultWriterFactory.java new file mode 100644 index 0000000..192ee03 --- /dev/null +++ b/event-loop/src/main/java/mc/core/timings/io/DefaultWriterFactory.java @@ -0,0 +1,11 @@ +package mc.core.timings.io; + +import java.io.File; +import java.io.IOException; + +public class DefaultWriterFactory implements TimingsWriterFactory { + @Override + public TimingsWriter newInstance(File file) throws IOException { + return new TimingsFileWriter(file); + } +} diff --git a/event-loop/src/main/java/mc/core/timings/TimingsFileWriter.java b/event-loop/src/main/java/mc/core/timings/io/TimingsFileWriter.java similarity index 76% rename from event-loop/src/main/java/mc/core/timings/TimingsFileWriter.java rename to event-loop/src/main/java/mc/core/timings/io/TimingsFileWriter.java index 451fb88..f7bf92c 100644 --- a/event-loop/src/main/java/mc/core/timings/TimingsFileWriter.java +++ b/event-loop/src/main/java/mc/core/timings/io/TimingsFileWriter.java @@ -1,9 +1,8 @@ -package mc.core.timings; +package mc.core.timings.io; -import lombok.Getter; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import mc.core.timings.TimingsEventType; import java.io.File; import java.io.FileOutputStream; @@ -13,7 +12,7 @@ import java.util.concurrent.locks.ReentrantLock; @SuppressWarnings("Duplicates") @Slf4j -public class TimingsFileWriter { +public class TimingsFileWriter implements TimingsWriter { private FileOutputStream fileOutputStream; private ObjectOutputStream writer; private ReentrantLock lock = new ReentrantLock(); @@ -23,6 +22,7 @@ public class TimingsFileWriter { writer = new ObjectOutputStream(fileOutputStream); } + @Override public void writeEvent(int threadId, int stackId, long time, TimingsEventType type) { lock.lock(); try { @@ -37,6 +37,7 @@ public class TimingsFileWriter { } } + @Override public void writeEvent(int threadId, int stackId, long time, TimingsEventType type, String data) { lock.lock(); try { @@ -52,21 +53,9 @@ public class TimingsFileWriter { } } + @Override public void close() throws IOException { writer.close(); fileOutputStream.close(); } - - @RequiredArgsConstructor - public enum TimingsEventType { - TIMINGS_START((short) 0), - TIMINGS_END((short) 1), - TIMINGS_FILE_INITIALIZING((short) 2), - TIMINGS_FILE_INITIALIZED((short) 3), - TIMINGS_CHANGE_THREAD_OPTIONS((short) 4), - TIMINGS_FILE_END((short) 5); - - @Getter - private final short id; - } } diff --git a/event-loop/src/main/java/mc/core/timings/io/TimingsLogWriter.java b/event-loop/src/main/java/mc/core/timings/io/TimingsLogWriter.java new file mode 100644 index 0000000..0fa5fa3 --- /dev/null +++ b/event-loop/src/main/java/mc/core/timings/io/TimingsLogWriter.java @@ -0,0 +1,24 @@ +package mc.core.timings.io; + +import lombok.extern.slf4j.Slf4j; +import mc.core.timings.TimingsEventType; + +import java.io.IOException; + +@Slf4j +public class TimingsLogWriter implements TimingsWriter { + @Override + public void writeEvent(int threadId, int stackId, long time, TimingsEventType type) { + log.info("[{}] Thread #{}, Stack #{}: {}", time, threadId, stackId, type.toString()); + } + + @Override + public void writeEvent(int threadId, int stackId, long time, TimingsEventType type, String data) { + log.info("[{}] Thread #{}, Stack #{}: {} ({})", time, threadId, stackId, type.toString(), data); + } + + @Override + public void close() throws IOException { + + } +} diff --git a/event-loop/src/main/java/mc/core/timings/io/TimingsWriter.java b/event-loop/src/main/java/mc/core/timings/io/TimingsWriter.java new file mode 100644 index 0000000..640bdd5 --- /dev/null +++ b/event-loop/src/main/java/mc/core/timings/io/TimingsWriter.java @@ -0,0 +1,13 @@ +package mc.core.timings.io; + +import mc.core.timings.TimingsEventType; + +import java.io.IOException; + +public interface TimingsWriter { + void writeEvent(int threadId, int stackId, long time, TimingsEventType type); + + void writeEvent(int threadId, int stackId, long time, TimingsEventType type, String data); + + void close() throws IOException; +} diff --git a/event-loop/src/main/java/mc/core/timings/io/TimingsWriterFactory.java b/event-loop/src/main/java/mc/core/timings/io/TimingsWriterFactory.java new file mode 100644 index 0000000..db12e6a --- /dev/null +++ b/event-loop/src/main/java/mc/core/timings/io/TimingsWriterFactory.java @@ -0,0 +1,8 @@ +package mc.core.timings.io; + +import java.io.File; +import java.io.IOException; + +public interface TimingsWriterFactory { + TimingsWriter newInstance(File file) throws IOException; +} From f51dba79a687082b85f5b4620dedd5c8b782ef47 Mon Sep 17 00:00:00 2001 From: Forwolk Date: Wed, 8 Aug 2018 00:17:07 +0300 Subject: [PATCH 54/62] Region managed moved to CubicWorld --- .../generated_world/world/CubicWorld.java | 148 +++++++++++++++--- .../generated_world/world/RegionManager.java | 143 ----------------- .../SeedRandomGeneratorTest.java | 2 + 3 files changed, 124 insertions(+), 169 deletions(-) delete mode 100644 generated_world/src/main/java/mc/world/generated_world/world/RegionManager.java diff --git a/generated_world/src/main/java/mc/world/generated_world/world/CubicWorld.java b/generated_world/src/main/java/mc/world/generated_world/world/CubicWorld.java index d7f1724..85c3290 100644 --- a/generated_world/src/main/java/mc/world/generated_world/world/CubicWorld.java +++ b/generated_world/src/main/java/mc/world/generated_world/world/CubicWorld.java @@ -4,34 +4,65 @@ import com.flowpowered.nbt.Tag; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; +import mc.core.Direction; import mc.core.Location; import mc.core.WarpPosition; -import mc.core.world.block.BlockType; import mc.core.player.Look; -import mc.core.world.chunk.Chunk; import mc.core.world.IWorldType; import mc.core.world.Region; import mc.core.world.World; +import mc.core.world.WorldGenerator; +import mc.core.world.block.BlockType; +import mc.core.world.chunk.Chunk; +import mc.world.generated_world.serialization.RegionReaderWriter; import org.springframework.beans.factory.annotation.Autowired; +import java.io.File; +import java.io.IOException; +import java.text.MessageFormat; import java.util.HashMap; import java.util.Map; import java.util.UUID; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Stream; -import static mc.world.generated_world.WorldConstants.WORLD_CHUNK_SIZE; -import static mc.world.generated_world.WorldConstants.WORLD_MAX_HEIGHT; +import static mc.world.generated_world.WorldConstants.*; + +/* + * NORTH + * + * EAST WEST + * + * SOUTH + * + * + ----> X + * | + * | + * | + * V Z + */ @Slf4j public class CubicWorld implements World { + private int pointX = -1; + private int pointZ = -1; + private int sizeX = 2; + private int sizeZ = 2; + private Region[][] regions = new Region[sizeX][sizeZ]; + private final Lock regionSaveLock = new ReentrantLock(); + @Autowired + private RegionReaderWriter regionReaderWriter; + @Autowired + private WorldGenerator worldGenerator; + @Setter + private boolean autoSaveRegionAfterGenerating = true; @Getter private final UUID worldId; private final int seed; private volatile WarpPosition warpPosition; private final transient Object spawnLocationLock = new Object(); private final Map> nbtTagMap = new HashMap<>(); - @Autowired - private RegionManager regionManager; @Getter@Setter private String name; @@ -62,23 +93,8 @@ public class CubicWorld implements World { @Override public WarpPosition getSpawn() { - if (warpPosition == null) { - synchronized (spawnLocationLock) { - if (warpPosition == null) { - log.warn("Spawn location is not defined. Trying to select best location"); - warpPosition = new WarpPosition(Location.startPointLocation(), new Look(0, 0)); - for (int y = WORLD_MAX_HEIGHT; y > 0; y --) { - Chunk chunk = getChunk(0,y / WORLD_CHUNK_SIZE, 0); - if (chunk.getBlock(0, y, 0).getBlockType() != BlockType.AIR) { - warpPosition = new WarpPosition(new Location(0, y + 1, 0), new Look(0, 0)); - break; - } - } - warpPosition = new WarpPosition(Location.startPointLocation(), new Look(0,0)); - } - } - } - return warpPosition; + /* FIXME */ + return new WarpPosition(new Location(0, 100, 0), new Look(0, 0)); } @Override @@ -90,7 +106,8 @@ public class CubicWorld implements World { @Override public Chunk getChunk(int x, int y, int z) { - return null; + Region region = getRegion(x / 16, z / 16); + return region.getChunkAt(x % 16, y % 16, z % 16); } @Override @@ -100,12 +117,42 @@ public class CubicWorld implements World { @Override public Region getRegion(int x, int z) { - return null; + checkCoordsInCache(x, z); + Region region; + if (regions[x - pointX][z - pointZ] == null) { + File file = new File(new File("worlds", this.getWorldId().toString()), MessageFormat.format(REGION_FILE_NAME_TEMPLATE, x, z)); + if (!file.exists()) { + region = worldGenerator.generateRegion(x, z, this); + if (autoSaveRegionAfterGenerating) { + try { + regionReaderWriter.write(region); + } catch (IOException e) { + log.error("Error occurred while saving region data"); + } + } + } else { + try { + region = regionReaderWriter.read(x, z, this); + } catch (IOException e) { + log.error("Error occurred while loading region"); + region = null; + } + } + setRegion(region.getX(), region.getZ(), region); + } else { + region = regions[x - pointX][z - pointZ]; + } + return region; } @Override public void setRegion(int x, int z, Region region) { - + try { + regionSaveLock.lock(); + regions[x - pointX][z - pointZ] = region; + } finally { + regionSaveLock.unlock(); + } } @Override @@ -127,4 +174,53 @@ public class CubicWorld implements World { public Stream> tagStream() { return nbtTagMap.values().stream(); } + + private void checkCoordsInCache (int x, int z) { + if (x < pointX) { + addLines(Direction.EAST, pointX - x); + } else if (x > pointX + sizeX) { + addLines(Direction.WEST, x - (pointX + sizeX)); + } else if (z < pointZ) { + addLines(Direction.NORTH, pointZ - z); + } else if (z > pointZ + sizeZ) { + addLines(Direction.SOUTH, z - (pointZ + sizeZ)); + } + } + + private void addLines (Direction direction, int amount) { + int addBeforeX = 0; + int addAfterX = 0; + int addBeforeZ = 0; + int addAfterZ = 0; + switch (direction) { + case NORTH: + addBeforeZ = amount; + break; + case EAST: + addBeforeX = amount; + break; + case WEST: + addAfterX = amount; + break; + case SOUTH: + addAfterZ = amount; + break; + } + try { + regionSaveLock.lock(); + int tempSizeX = sizeX + addAfterX + addBeforeX; + int tempSizeZ = sizeZ + addAfterZ + addBeforeZ; + Region[][] temp = new Region[tempSizeX][tempSizeZ]; + for (int x = 0; x < sizeX; x ++) { + System.arraycopy(regions[x], 0, temp[x + addBeforeX], addBeforeZ, sizeZ); + } + + this.sizeX = tempSizeX; + this.sizeZ = tempSizeZ; + this.pointX = pointX - addBeforeX; + this.pointZ = pointZ - addBeforeZ; + } finally { + regionSaveLock.unlock(); + } + } } diff --git a/generated_world/src/main/java/mc/world/generated_world/world/RegionManager.java b/generated_world/src/main/java/mc/world/generated_world/world/RegionManager.java deleted file mode 100644 index 0cc0ef4..0000000 --- a/generated_world/src/main/java/mc/world/generated_world/world/RegionManager.java +++ /dev/null @@ -1,143 +0,0 @@ -package mc.world.generated_world.world; - -import lombok.Setter; -import lombok.extern.slf4j.Slf4j; -import mc.core.Direction; -import mc.core.world.Region; -import mc.core.world.World; -import mc.core.world.WorldGenerator; -import mc.world.generated_world.serialization.RegionReaderWriter; -import org.springframework.beans.factory.annotation.Autowired; - -import java.io.File; -import java.io.IOException; -import java.text.MessageFormat; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - -import static mc.world.generated_world.WorldConstants.REGION_FILE_NAME_TEMPLATE; - -/* -* NORTH -* -* EAST WEST -* -* SOUTH -* -* + ----> X -* | -* | -* | -* V Z -*/ -@Slf4j -public class RegionManager { - private final World world; - private int pointX = -1; - private int pointZ = -1; - private int sizeX = 2; - private int sizeZ = 2; - private Region[][] regions = new Region[sizeX][sizeZ]; - private final Lock regionSaveLock = new ReentrantLock(); - @Autowired - private RegionReaderWriter regionReaderWriter; - @Autowired - private WorldGenerator worldGenerator; - @Setter - private boolean autoSaveRegionAfterGenerating = true; - - - public RegionManager(World world) { - this.world = world; - } - - public void setRegion (Region region) { - int x = region.getX(); - int z = region.getZ(); - - try { - regionSaveLock.lock(); - regions[x - pointX][z - pointZ] = region; - } finally { - regionSaveLock.unlock(); - } - } - - private void checkCoordsInCache (int x, int z) { - if (x < pointX) { - addLines(Direction.EAST, pointX - x); - } else if (x > pointX + sizeX) { - addLines(Direction.WEST, x - (pointX + sizeX)); - } else if (z < pointZ) { - addLines(Direction.NORTH, pointZ - z); - } else if (z > pointZ + sizeZ) { - addLines(Direction.SOUTH, z - (pointZ + sizeZ)); - } - } - - public Region getRegion (int x, int z) { - checkCoordsInCache(x, z); - Region region; - if (regions[x - pointX][z - pointZ] == null) { - File file = new File(new File("worlds", world.getWorldId().toString()), MessageFormat.format(REGION_FILE_NAME_TEMPLATE, x, z)); - if (!file.exists()) { - region = worldGenerator.generateRegion(x, z, world); - if (autoSaveRegionAfterGenerating) { - try { - regionReaderWriter.write(region); - } catch (IOException e) { - log.error("Error occurred while saving region data"); - } - } - } else { - try { - region = regionReaderWriter.read(x, z, world); - } catch (IOException e) { - log.error("Error occurred while loading region"); - region = null; - } - } - setRegion(region); - } else { - region = regions[x - pointX][z - pointZ]; - } - return region; - } - - private void addLines (Direction direction, int amount) { - int addBeforeX = 0; - int addAfterX = 0; - int addBeforeZ = 0; - int addAfterZ = 0; - switch (direction) { - case NORTH: - addBeforeZ = amount; - break; - case EAST: - addBeforeX = amount; - break; - case WEST: - addAfterX = amount; - break; - case SOUTH: - addAfterZ = amount; - break; - } - try { - int tempSizeX = sizeX + addAfterX + addBeforeX; - int tempSizeZ = sizeZ + addAfterZ + addBeforeZ; - Region[][] temp = new Region[tempSizeX][tempSizeZ]; - for (int x = 0; x < sizeX; x ++) { - System.arraycopy(regions[x], 0, temp[x + addBeforeX], addBeforeZ, sizeZ); - } - - this.sizeX = tempSizeX; - this.sizeZ = tempSizeZ; - this.pointX = pointX - addBeforeX; - this.pointZ = pointZ - addBeforeZ; - } finally { - regionSaveLock.unlock(); - } - } - -} diff --git a/generated_world/src/test/java/mc/world/generated_world/SeedRandomGeneratorTest.java b/generated_world/src/test/java/mc/world/generated_world/SeedRandomGeneratorTest.java index a99a251..6c5fbe4 100644 --- a/generated_world/src/test/java/mc/world/generated_world/SeedRandomGeneratorTest.java +++ b/generated_world/src/test/java/mc/world/generated_world/SeedRandomGeneratorTest.java @@ -1,6 +1,7 @@ package mc.world.generated_world; import mc.world.generated_world.generator.SeedRandomGenerator; +import org.junit.Ignore; import org.junit.Test; import javax.imageio.ImageIO; @@ -9,6 +10,7 @@ import java.io.File; import static org.junit.Assert.*; +@Ignore public class SeedRandomGeneratorTest { @Test From 5db14851eee3b825a3ee820f278e0f87ba8d4d6c Mon Sep 17 00:00:00 2001 From: DmitriyMX Date: Wed, 8 Aug 2018 01:37:46 +0300 Subject: [PATCH 55/62] fix: gradle run application Example: gradle runApp -PworkDir="D:\mc-core" --- build.gradle | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 74a7f0e..f7f6e99 100644 --- a/build.gradle +++ b/build.gradle @@ -1,4 +1,4 @@ -subprojects { +allprojects { apply plugin: 'java' compileJava { @@ -10,7 +10,9 @@ subprojects { repositories { mavenCentral() } +} +subprojects { ext { slf4j_version = '1.7.21' spring_version = '4.2.5.RELEASE' @@ -44,3 +46,23 @@ subprojects { delete 'libs' } } + +task runApp(type: JavaExec) { + main = 'mc.core.Main' + + workingDir = (project.hasProperty("workDir") ? project.workDir : '.') + + subprojects.findAll().each{ prj -> + classpath += prj.sourceSets.main.runtimeClasspath + } + /* Uncomment, if your Log Implements are folder '{workDir}/log-impl' */ + //classpath += files(fileTree(dir: new File(workingDir, "log-impl"))) + + /* Uncomment, if you used VM args */ + //jvmArgs = [ + // "-DspringConfig=app.xml", + // "-Dlog4j.configurationFile=log4j2.xml" + //] + + ignoreExitValue = true +} From 0269b0b0c1fb65774f005051ddd5ac578c5eb11b Mon Sep 17 00:00:00 2001 From: Daniil Date: Wed, 8 Aug 2018 22:36:08 +0700 Subject: [PATCH 56/62] Minor code-style changes Timings dump format change Pre-tests (not functional right now) --- .../mc/core/timings/MeasurableThread.java | 5 -- .../java/mc/core/timings/TimingsManager.java | 23 +++++++-- .../mc/core/timings/io/TimingsFileWriter.java | 10 ++-- .../java/mc/core/timings/TimingsTest.java | 51 +++++++++++++++++++ 4 files changed, 76 insertions(+), 13 deletions(-) delete mode 100644 event-loop/src/main/java/mc/core/timings/MeasurableThread.java create mode 100644 event-loop/src/test/java/mc/core/timings/TimingsTest.java diff --git a/event-loop/src/main/java/mc/core/timings/MeasurableThread.java b/event-loop/src/main/java/mc/core/timings/MeasurableThread.java deleted file mode 100644 index 9cd2ac3..0000000 --- a/event-loop/src/main/java/mc/core/timings/MeasurableThread.java +++ /dev/null @@ -1,5 +0,0 @@ -package mc.core.timings; - -public interface MeasurableThread { - ThreadTimings getTimings(); -} diff --git a/event-loop/src/main/java/mc/core/timings/TimingsManager.java b/event-loop/src/main/java/mc/core/timings/TimingsManager.java index 07ac32a..3e8ea66 100644 --- a/event-loop/src/main/java/mc/core/timings/TimingsManager.java +++ b/event-loop/src/main/java/mc/core/timings/TimingsManager.java @@ -19,9 +19,9 @@ import java.util.concurrent.locks.ReentrantLock; @Slf4j public class TimingsManager { + private final Map threadTimings = new ConcurrentHashMap<>(); // These variables are essential in Timings thread synchronization private final AtomicBoolean waitForFile = new AtomicBoolean(false); - private Map threadTimings = new ConcurrentHashMap<>(); private TimingsWriter writer; private Thread timingsIoThread; private CountDownLatch ioThreadStopMutex; @@ -139,8 +139,23 @@ public class TimingsManager { } public ThreadTimings getCurrentThreadTimings() { - if (Thread.currentThread() instanceof MeasurableThread) { - return ((MeasurableThread) Thread.currentThread()).getTimings(); - } else return this.threadTimings.computeIfAbsent(Thread.currentThread(), s -> new ThreadTimings()); + + synchronized (this.threadTimings) { + if (this.threadTimings.containsKey(Thread.currentThread())) { + return this.threadTimings.get(Thread.currentThread()); + } else { + ThreadTimings timings = new ThreadTimings(); + this.threadTimings.put(Thread.currentThread(), timings); + if (queue != null) { + try { + writer.writeEvent(timings.getThreadId(), 0, System.nanoTime(), TimingsEventType.TIMINGS_CHANGE_THREAD_OPTIONS, "name: " + Thread.currentThread().getName()); + } catch (NullPointerException ignored) { + // It means that there the file recording was stopped + // we don't actually care about it + } + } + return timings; + } + } } } diff --git a/event-loop/src/main/java/mc/core/timings/io/TimingsFileWriter.java b/event-loop/src/main/java/mc/core/timings/io/TimingsFileWriter.java index f7bf92c..e2b593c 100644 --- a/event-loop/src/main/java/mc/core/timings/io/TimingsFileWriter.java +++ b/event-loop/src/main/java/mc/core/timings/io/TimingsFileWriter.java @@ -26,10 +26,11 @@ public class TimingsFileWriter implements TimingsWriter { public void writeEvent(int threadId, int stackId, long time, TimingsEventType type) { lock.lock(); try { - writer.write(threadId); - writer.write(stackId); + writer.writeInt(threadId); + writer.writeInt(stackId); writer.writeLong(time); writer.writeShort(type.getId()); + writer.writeBoolean(false); } catch (IOException e) { log.error("Unable to write timings record", e); } finally { @@ -41,10 +42,11 @@ public class TimingsFileWriter implements TimingsWriter { public void writeEvent(int threadId, int stackId, long time, TimingsEventType type, String data) { lock.lock(); try { - writer.write(threadId); - writer.write(stackId); + writer.writeInt(threadId); + writer.writeInt(stackId); writer.writeLong(time); writer.writeShort(type.getId()); + writer.writeBoolean(true); writer.writeUTF(data); } catch (IOException e) { log.error("Unable to write timings record", e); diff --git a/event-loop/src/test/java/mc/core/timings/TimingsTest.java b/event-loop/src/test/java/mc/core/timings/TimingsTest.java new file mode 100644 index 0000000..a3e6200 --- /dev/null +++ b/event-loop/src/test/java/mc/core/timings/TimingsTest.java @@ -0,0 +1,51 @@ +package mc.core.timings; + +import org.junit.Test; + +import java.io.File; +import java.io.IOException; + +public class TimingsTest { + @Test + public void basicTest() { + try (Timings timings = Timings.start()) { + System.out.println("Test code"); + } + } + + @Test + public void brokenTimingTest() { + try (Timings timings = Timings.start()) { + Timings t1 = Timings.start(); + Timings.start(); + System.out.println("Pre Close t1"); + t1.close(); + System.out.println("Finished"); + } + } + + @Test + public void fileRecording() throws IOException { + Timings.getTimingsManager().startRecording(new File("test.timings")); + + try (Timings t1 = Timings.start()) { + try { + Thread.sleep(20); + } catch (InterruptedException e) { + e.printStackTrace(); + } + try (Timings t2 = Timings.start()) { + Thread.sleep(10); + } catch (InterruptedException e) { + e.printStackTrace(); + } + Thread.sleep(5); + + } catch (InterruptedException e) { + e.printStackTrace(); + } + + + Timings.getTimingsManager().stopRecording(); + } +} From 546a13d2a1f3ce55e2fb9a0b157bd511d92041f0 Mon Sep 17 00:00:00 2001 From: Daniil Date: Thu, 9 Aug 2018 23:16:16 +0700 Subject: [PATCH 57/62] Event-loop module refactoring and commenting --- .../mc/core/events/EventPipelineTask.java | 86 ++++++++++++------- .../mc/core/events/FullAsyncEventLoop.java | 28 +++--- .../core/events/RegisteredEventHandler.java | 5 +- .../mc/core/events/SharedResourceManager.java | 2 +- .../core/events/lock/CustomReentrantLock.java | 43 ---------- .../events/runner/AllInScheduleStrategy.java | 47 ++++++---- ...rThread.java => ExecutorWorkerThread.java} | 36 +++++--- ...java => ResourceAwareExecutorService.java} | 26 ++++-- ...nnable.java => ResourceAwareRunnable.java} | 4 +- .../core/events/runner/ScheduleStrategy.java | 2 +- .../{ => runner}/lock/LockObserveList.java | 2 +- .../{ => runner}/lock/PoorMansLock.java | 2 +- .../mc/core/events/EventExecutorTest.java | 4 +- .../java/mc/core/events/EventLoopTest.java | 18 ++-- .../test/java/mc/core/events/LockTest.java | 4 +- 15 files changed, 169 insertions(+), 140 deletions(-) delete mode 100644 event-loop/src/main/java/mc/core/events/lock/CustomReentrantLock.java rename event-loop/src/main/java/mc/core/events/runner/{ExecutorThread.java => ExecutorWorkerThread.java} (53%) rename event-loop/src/main/java/mc/core/events/runner/{EventExecutorService.java => ResourceAwareExecutorService.java} (67%) rename event-loop/src/main/java/mc/core/events/runner/{ResourceRunnable.java => ResourceAwareRunnable.java} (59%) rename event-loop/src/main/java/mc/core/events/{ => runner}/lock/LockObserveList.java (97%) rename event-loop/src/main/java/mc/core/events/{ => runner}/lock/PoorMansLock.java (97%) diff --git a/event-loop/src/main/java/mc/core/events/EventPipelineTask.java b/event-loop/src/main/java/mc/core/events/EventPipelineTask.java index 53eafe4..f79634f 100644 --- a/event-loop/src/main/java/mc/core/events/EventPipelineTask.java +++ b/event-loop/src/main/java/mc/core/events/EventPipelineTask.java @@ -6,17 +6,25 @@ import lombok.Setter; import lombok.extern.slf4j.Slf4j; import mc.core.events.api.EventQueueOwner; import mc.core.events.api.LockableResource; -import mc.core.events.lock.LockObserveList; -import mc.core.events.runner.EventExecutorService; -import mc.core.events.runner.ResourceRunnable; +import mc.core.events.runner.lock.LockObserveList; +import mc.core.events.runner.ResourceAwareExecutorService; +import mc.core.events.runner.ResourceAwareRunnable; import java.lang.reflect.InvocationTargetException; import java.util.List; +/** + * Holds processing pipeline for every event + * that enters {@link FullAsyncEventLoop}. + *

+ * Ensures that EventHandlers will never be called in a wrong + * order by feeding only one task at a time to the {@link ResourceAwareExecutorService} + */ @RequiredArgsConstructor @Getter @Slf4j public class EventPipelineTask { + private final ResourceAwareExecutorService service; private final List handlers; private final FullAsyncEventLoop manager; private final Event event; @@ -25,49 +33,65 @@ public class EventPipelineTask { @Setter private PipelineState state = PipelineState.IDLE; - public void next(EventExecutorService service) { + public void next() { + if (updatePipelineState()) return; + + RegisteredEventHandler handler = handlers.get(currentIndex); + // If event has been already cancelled and current handler + // ignores cancelled events + if (event.isCanceled() && handler.isIgnoreCancelled()) { + // Just skip current event handler + currentIndex++; + next(); + } else { + feedTask(handler); + } + } + + /** + * Update current pipeline status + * + * @return true if pipeline has been completed + */ + private boolean updatePipelineState() { if (state == PipelineState.IDLE) { state = PipelineState.WORKING; } if (currentIndex >= handlers.size() && state == PipelineState.WORKING) { state = PipelineState.FINISHED; manager.update(owner); - return; + return true; } if (state == PipelineState.FINISHED) { throw new IllegalStateException("Attempted to call next step on a FINISHED pipeline"); } + return false; + } - RegisteredEventHandler handler = handlers.get(currentIndex); - if (!event.isCanceled() || !handler.isIgnoreCancelled()) { - LockObserveList locks = getLocks(handler); - - service.addTask(new ResourceRunnable() { - @Override - public void run() { - try { - handler.getMethod().invoke(handler.getObject(), event); - } catch (IllegalAccessException | InvocationTargetException e) { - log.error("Unable to dispatch event " + event.getClass().getSimpleName() + " to handler " + event.getClass().getName(), e); - } + private void feedTask(RegisteredEventHandler handler) { + LockObserveList locks = getLocks(handler); + service.addTask(new ResourceAwareRunnable() { + @Override + public void run() { + try { + handler.getMethod().invoke(handler.getObject(), event); + } catch (IllegalAccessException | InvocationTargetException e) { + log.error("Unable to dispatch event " + event.getClass().getSimpleName() + " to handler " + event.getClass().getName(), e); } + } - @Override - public void after() { - currentIndex++; - next(service); - } + @Override + public void after() { + currentIndex++; + next(); + } - @Override - public LockObserveList getLocks() { - return locks; - } - }); - } else { - currentIndex++; - next(service); - } + @Override + public LockObserveList getLocks() { + return locks; + } + }); } private LockObserveList getLocks(RegisteredEventHandler handler) { diff --git a/event-loop/src/main/java/mc/core/events/FullAsyncEventLoop.java b/event-loop/src/main/java/mc/core/events/FullAsyncEventLoop.java index 89e6281..ec8fa85 100644 --- a/event-loop/src/main/java/mc/core/events/FullAsyncEventLoop.java +++ b/event-loop/src/main/java/mc/core/events/FullAsyncEventLoop.java @@ -1,11 +1,12 @@ package mc.core.events; +import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import mc.core.events.api.EventHandler; import mc.core.events.api.EventQueueOwner; import mc.core.events.api.Plugin; -import mc.core.events.runner.EventExecutorService; +import mc.core.events.runner.ResourceAwareExecutorService; import org.springframework.beans.factory.annotation.Autowired; import java.lang.reflect.Method; @@ -13,14 +14,23 @@ import java.lang.reflect.Modifier; import java.util.*; import java.util.concurrent.ConcurrentHashMap; +/** + * Event loop core. Manages event handler registration process, + * maintains event queues. + *

+ * This event loop guarantees that events, assigned to the {@link EventQueueOwner} + * will be handler in order of scheduling + */ @Slf4j public class FullAsyncEventLoop { - Map, List> handlers = new HashMap<>(); // Item leaves this queue only when EventPipeline is fully executed private Map> eventQueue = new ConcurrentHashMap<>(); + private Map, List> registeredHandlers = new HashMap<>(); + @SuppressWarnings("SpringJavaAutowiredMembersInspection") @Autowired @Setter - private EventExecutorService eventExecutorService; + private ResourceAwareExecutorService resourceAwareExecutorService; + @Getter private SharedResourceManager resourceManager = new SharedResourceManager(); public void addEventHandler(Plugin plugin, Object object) { @@ -28,14 +38,14 @@ public class FullAsyncEventLoop { for (Map.Entry pair : candidates.entrySet()) { @SuppressWarnings("unchecked") Class eventType = (Class) pair.getKey().getParameterTypes()[0]; - List handlers = this.handlers.computeIfAbsent(eventType, e -> new ArrayList<>()); + List handlers = this.registeredHandlers.computeIfAbsent(eventType, e -> new ArrayList<>()); handlers.add(new RegisteredEventHandler(plugin, object, pair.getKey(), pair.getValue().lock(), pair.getValue().pluginSynchronize(), pair.getValue().priority().getValue(), pair.getValue().ignoreCancelled())); handlers.sort(Comparator.comparingInt(RegisteredEventHandler::getPriority)); } } public List getPipelineForEvent(Event event) { - return handlers.get(event.getClass()); + return registeredHandlers.get(event.getClass()); } private Map getEventHandlerCandidates(Object object) { @@ -75,7 +85,7 @@ public class FullAsyncEventLoop { return; Queue queue = eventQueue.computeIfAbsent(owner, s -> new ArrayDeque<>()); - queue.add(new EventPipelineTask(handlers, this, event, owner)); + queue.add(new EventPipelineTask(resourceAwareExecutorService, handlers, this, event, owner)); update(owner); } @@ -105,11 +115,7 @@ public class FullAsyncEventLoop { EventPipelineTask pipeline; if ((pipeline = queue.peek()) != null && pipeline.getState() == EventPipelineTask.PipelineState.IDLE) { - pipeline.next(eventExecutorService); + pipeline.next(); } } - - public SharedResourceManager getResourceManager() { - return resourceManager; - } } diff --git a/event-loop/src/main/java/mc/core/events/RegisteredEventHandler.java b/event-loop/src/main/java/mc/core/events/RegisteredEventHandler.java index bae22ff..f0333f7 100644 --- a/event-loop/src/main/java/mc/core/events/RegisteredEventHandler.java +++ b/event-loop/src/main/java/mc/core/events/RegisteredEventHandler.java @@ -7,6 +7,10 @@ import mc.core.events.api.Plugin; import java.lang.reflect.Method; +/** + * Holds all the information necessary to register an + * event handler in an event loop + */ @RequiredArgsConstructor @Getter public class RegisteredEventHandler { @@ -17,5 +21,4 @@ public class RegisteredEventHandler { private final boolean pluginSynchronize; private final int priority; private final boolean ignoreCancelled; - } diff --git a/event-loop/src/main/java/mc/core/events/SharedResourceManager.java b/event-loop/src/main/java/mc/core/events/SharedResourceManager.java index 23bbabd..ad9f55e 100644 --- a/event-loop/src/main/java/mc/core/events/SharedResourceManager.java +++ b/event-loop/src/main/java/mc/core/events/SharedResourceManager.java @@ -7,7 +7,7 @@ import mc.core.events.api.Plugin; import mc.core.events.api.interfaces.LocationProvidingEvent; import mc.core.events.api.interfaces.PlayerProvidingEvent; import mc.core.events.api.interfaces.WorldProvidingEvent; -import mc.core.events.lock.PoorMansLock; +import mc.core.events.runner.lock.PoorMansLock; import mc.core.player.Player; import mc.core.world.World; diff --git a/event-loop/src/main/java/mc/core/events/lock/CustomReentrantLock.java b/event-loop/src/main/java/mc/core/events/lock/CustomReentrantLock.java deleted file mode 100644 index a485f53..0000000 --- a/event-loop/src/main/java/mc/core/events/lock/CustomReentrantLock.java +++ /dev/null @@ -1,43 +0,0 @@ -package mc.core.events.lock; - -import mc.core.events.runner.ExecutorThread; - -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.ReentrantLock; - -public class CustomReentrantLock extends ReentrantLock { - private void checkThread() { - if (!(Thread.currentThread() instanceof ExecutorThread)) - throw new RuntimeException("Unable to obtain this resource outside Async Executor"); - } - - @Override - public void lock() { - checkThread(); - super.lock(); - } - - @Override - public void lockInterruptibly() throws InterruptedException { - checkThread(); - super.lockInterruptibly(); - } - - @Override - public boolean tryLock() { - checkThread(); - return super.tryLock(); - } - - @Override - public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { - checkThread(); - return super.tryLock(timeout, unit); - } - - @Override - public void unlock() { - checkThread(); - super.unlock(); - } -} diff --git a/event-loop/src/main/java/mc/core/events/runner/AllInScheduleStrategy.java b/event-loop/src/main/java/mc/core/events/runner/AllInScheduleStrategy.java index 6de1556..ce275bd 100644 --- a/event-loop/src/main/java/mc/core/events/runner/AllInScheduleStrategy.java +++ b/event-loop/src/main/java/mc/core/events/runner/AllInScheduleStrategy.java @@ -3,29 +3,28 @@ package mc.core.events.runner; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; +/** + * Simple scheduling strategy. + *

+ * We wait until the first task in a queue will be able to acquire all + * the necessary resources and then we schedule it for execution + */ public class AllInScheduleStrategy implements ScheduleStrategy { - private BlockingQueue globalQueue; - private EventExecutorService eventExecutorService; + private BlockingQueue globalQueue; + private ResourceAwareExecutorService resourceAwareExecutorService; - public AllInScheduleStrategy(EventExecutorService eventExecutorService) { - this.globalQueue = eventExecutorService.queue; - this.eventExecutorService = eventExecutorService; + public AllInScheduleStrategy(ResourceAwareExecutorService resourceAwareExecutorService) { + this.globalQueue = resourceAwareExecutorService.queue; + this.resourceAwareExecutorService = resourceAwareExecutorService; } @Override - public synchronized ResourceRunnable getTask() throws InterruptedException { - - // Wait for last task to finish locking up - // the resources - synchronized (eventExecutorService.waitForLock) { - while (eventExecutorService.waitForLock.get()) { - eventExecutorService.wait(); - } - } + public synchronized ResourceAwareRunnable getTask() throws InterruptedException { + waitForResourceLockComplete(); // Wait for new task in queue - ResourceRunnable runnable = globalQueue.take(); + ResourceAwareRunnable runnable = globalQueue.take(); while (!runnable.getLocks().isReady()) { CountDownLatch latch = new CountDownLatch(1); runnable.getLocks().setCallback(latch::countDown); @@ -38,7 +37,23 @@ public class AllInScheduleStrategy implements ScheduleStrategy { // Lock execution for the next thread // (wait until resources for previous task will be blocked) - eventExecutorService.waitForLock.set(true); + resourceAwareExecutorService.waitForLock.set(true); return runnable; } + + /** + * Waits until the last scheduled task will lock all the necessary resources. + *

+ * It is required to avoid race-condition when an execution candidate task (first task in a queue) + * skips lock-await procedure due to the last scheduled task not having locked necessary resources yet. + * + * @throws InterruptedException if current thread is interrupted + */ + private void waitForResourceLockComplete() throws InterruptedException { + synchronized (resourceAwareExecutorService.waitForLock) { + while (resourceAwareExecutorService.waitForLock.get()) { + resourceAwareExecutorService.wait(); + } + } + } } diff --git a/event-loop/src/main/java/mc/core/events/runner/ExecutorThread.java b/event-loop/src/main/java/mc/core/events/runner/ExecutorWorkerThread.java similarity index 53% rename from event-loop/src/main/java/mc/core/events/runner/ExecutorThread.java rename to event-loop/src/main/java/mc/core/events/runner/ExecutorWorkerThread.java index 3e1ecc2..e1e5f7b 100644 --- a/event-loop/src/main/java/mc/core/events/runner/ExecutorThread.java +++ b/event-loop/src/main/java/mc/core/events/runner/ExecutorWorkerThread.java @@ -1,9 +1,19 @@ package mc.core.events.runner; -public class ExecutorThread extends Thread { - private EventExecutorService service; +/** + * Worker thread for {@link ResourceAwareExecutorService}. + *

+ * - Awaits for tasks from {@link ScheduleStrategy} + * - Locks up resources for this task + * - Notifies {@link ScheduleStrategy} when resource-locking procedure is complete + * - Executes the runnable in this thread + * - Unlocks all the resources + * - Calls {@link ResourceAwareRunnable#after()} callback + */ +public class ExecutorWorkerThread extends Thread { + private ResourceAwareExecutorService service; - public ExecutorThread(String name, EventExecutorService service) { + public ExecutorWorkerThread(String name, ResourceAwareExecutorService service) { super(name); this.service = service; } @@ -11,7 +21,7 @@ public class ExecutorThread extends Thread { @Override public void run() { while (!isInterrupted() && isAlive()) { - ResourceRunnable runnable; + ResourceAwareRunnable runnable; try { runnable = service.getStrategy().getTask(); } catch (InterruptedException e) { @@ -22,14 +32,9 @@ public class ExecutorThread extends Thread { } } - void executeTask(ResourceRunnable runnable) { + void executeTask(ResourceAwareRunnable runnable) { runnable.getLocks().lockAll(); - synchronized (service.waitForLock) { - if (service.waitForLock.get()) { - service.waitForLock.set(false); - service.waitForLock.notifyAll(); - } - } + notifyLockingDone(); try { runnable.run(); } finally { @@ -38,4 +43,13 @@ public class ExecutorThread extends Thread { } runnable.after(); } + + private void notifyLockingDone() { + synchronized (service.waitForLock) { + if (service.waitForLock.get()) { + service.waitForLock.set(false); + service.waitForLock.notifyAll(); + } + } + } } diff --git a/event-loop/src/main/java/mc/core/events/runner/EventExecutorService.java b/event-loop/src/main/java/mc/core/events/runner/ResourceAwareExecutorService.java similarity index 67% rename from event-loop/src/main/java/mc/core/events/runner/EventExecutorService.java rename to event-loop/src/main/java/mc/core/events/runner/ResourceAwareExecutorService.java index 3f018ab..77637c7 100644 --- a/event-loop/src/main/java/mc/core/events/runner/EventExecutorService.java +++ b/event-loop/src/main/java/mc/core/events/runner/ResourceAwareExecutorService.java @@ -9,9 +9,19 @@ import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.atomic.AtomicBoolean; -public class EventExecutorService { + +/** + * Custom implementation of an ExecutorService. + * + * Holds a queue of {@link ResourceAwareRunnable} and executes them in a thread pool. + * + * Warning! This class doesn't guarantee, that tasks will be executed in any specific order. + * In fact, it's up to {@link ScheduleStrategy} to decide which task will be scheduled for + * execution next. + */ +public class ResourceAwareExecutorService { private static final boolean WORKER_INSTANT_EXECUTE = false; - BlockingQueue queue = new ArrayBlockingQueue<>(100); + BlockingQueue queue = new ArrayBlockingQueue<>(100); // A synchronize aid, that prevents ScheduleStrategy from returning // wrong tasks when executor is late in blocking resources final AtomicBoolean waitForLock = new AtomicBoolean(false); @@ -19,7 +29,7 @@ public class EventExecutorService { private Set executorThreads = new HashSet<>(); private int threadCount; - public EventExecutorService(int threadCount) { + public ResourceAwareExecutorService(int threadCount) { this.threadCount = threadCount; } @@ -28,7 +38,7 @@ public class EventExecutorService { throw new InvalidStateException("This executor service was already started."); for (int i = 0; i < threadCount; i++) { - Thread thread = new ExecutorThread("Event Loop #" + i, this); + Thread thread = new ExecutorWorkerThread("Event Loop #" + i, this); executorThreads.add(thread); thread.start(); } @@ -46,9 +56,9 @@ public class EventExecutorService { } } - public void addTask(ResourceRunnable task) { - if (WORKER_INSTANT_EXECUTE && Thread.currentThread() instanceof ExecutorThread) { - ((ExecutorThread) Thread.currentThread()).executeTask(task); + public void addTask(ResourceAwareRunnable task) { + if (WORKER_INSTANT_EXECUTE && Thread.currentThread() instanceof ExecutorWorkerThread) { + ((ExecutorWorkerThread) Thread.currentThread()).executeTask(task); } else queue.offer(task); } @@ -59,7 +69,7 @@ public class EventExecutorService { } private class DefaultScheduleStrategy implements ScheduleStrategy { - public ResourceRunnable getTask() throws InterruptedException { + public ResourceAwareRunnable getTask() throws InterruptedException { return queue.take(); } } diff --git a/event-loop/src/main/java/mc/core/events/runner/ResourceRunnable.java b/event-loop/src/main/java/mc/core/events/runner/ResourceAwareRunnable.java similarity index 59% rename from event-loop/src/main/java/mc/core/events/runner/ResourceRunnable.java rename to event-loop/src/main/java/mc/core/events/runner/ResourceAwareRunnable.java index 6bf3492..6f88476 100644 --- a/event-loop/src/main/java/mc/core/events/runner/ResourceRunnable.java +++ b/event-loop/src/main/java/mc/core/events/runner/ResourceAwareRunnable.java @@ -1,8 +1,8 @@ package mc.core.events.runner; -import mc.core.events.lock.LockObserveList; +import mc.core.events.runner.lock.LockObserveList; -public interface ResourceRunnable extends Runnable { +public interface ResourceAwareRunnable extends Runnable { default LockObserveList getLocks() { return LockObserveList.EMPTY_LIST; } diff --git a/event-loop/src/main/java/mc/core/events/runner/ScheduleStrategy.java b/event-loop/src/main/java/mc/core/events/runner/ScheduleStrategy.java index 9d4ff50..10cee55 100644 --- a/event-loop/src/main/java/mc/core/events/runner/ScheduleStrategy.java +++ b/event-loop/src/main/java/mc/core/events/runner/ScheduleStrategy.java @@ -1,5 +1,5 @@ package mc.core.events.runner; public interface ScheduleStrategy { - ResourceRunnable getTask() throws InterruptedException; + ResourceAwareRunnable getTask() throws InterruptedException; } diff --git a/event-loop/src/main/java/mc/core/events/lock/LockObserveList.java b/event-loop/src/main/java/mc/core/events/runner/lock/LockObserveList.java similarity index 97% rename from event-loop/src/main/java/mc/core/events/lock/LockObserveList.java rename to event-loop/src/main/java/mc/core/events/runner/lock/LockObserveList.java index 5dd2985..157d325 100644 --- a/event-loop/src/main/java/mc/core/events/lock/LockObserveList.java +++ b/event-loop/src/main/java/mc/core/events/runner/lock/LockObserveList.java @@ -1,4 +1,4 @@ -package mc.core.events.lock; +package mc.core.events.runner.lock; import java.util.ArrayList; import java.util.List; diff --git a/event-loop/src/main/java/mc/core/events/lock/PoorMansLock.java b/event-loop/src/main/java/mc/core/events/runner/lock/PoorMansLock.java similarity index 97% rename from event-loop/src/main/java/mc/core/events/lock/PoorMansLock.java rename to event-loop/src/main/java/mc/core/events/runner/lock/PoorMansLock.java index a694b17..ace2edc 100644 --- a/event-loop/src/main/java/mc/core/events/lock/PoorMansLock.java +++ b/event-loop/src/main/java/mc/core/events/runner/lock/PoorMansLock.java @@ -1,4 +1,4 @@ -package mc.core.events.lock; +package mc.core.events.runner.lock; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; diff --git a/event-loop/src/test/java/mc/core/events/EventExecutorTest.java b/event-loop/src/test/java/mc/core/events/EventExecutorTest.java index 99556de..a6c6c14 100644 --- a/event-loop/src/test/java/mc/core/events/EventExecutorTest.java +++ b/event-loop/src/test/java/mc/core/events/EventExecutorTest.java @@ -1,6 +1,6 @@ package mc.core.events; -import mc.core.events.runner.EventExecutorService; +import mc.core.events.runner.ResourceAwareExecutorService; import org.junit.Assert; import org.junit.Test; @@ -14,7 +14,7 @@ public class EventExecutorTest { public void basicTest() throws InterruptedException { AtomicBoolean testVariable = new AtomicBoolean(false); CountDownLatch latch = new CountDownLatch(1); - EventExecutorService service = new EventExecutorService(1); + ResourceAwareExecutorService service = new ResourceAwareExecutorService(1); service.start(); service.addTask(() -> { testVariable.set(true); diff --git a/event-loop/src/test/java/mc/core/events/EventLoopTest.java b/event-loop/src/test/java/mc/core/events/EventLoopTest.java index bb43297..a92e738 100644 --- a/event-loop/src/test/java/mc/core/events/EventLoopTest.java +++ b/event-loop/src/test/java/mc/core/events/EventLoopTest.java @@ -4,7 +4,7 @@ import mc.core.events.api.EventHandler; import mc.core.events.api.EventPriority; import mc.core.events.api.EventQueueOwner; import mc.core.events.api.Plugin; -import mc.core.events.runner.EventExecutorService; +import mc.core.events.runner.ResourceAwareExecutorService; import org.junit.Assert; import org.junit.Test; @@ -35,10 +35,10 @@ public class EventLoopTest { } }); - EventExecutorService service = new EventExecutorService(1); + ResourceAwareExecutorService service = new ResourceAwareExecutorService(1); service.start(); - eventLoop.setEventExecutorService(service); + eventLoop.setResourceAwareExecutorService(service); eventLoop.asyncFireEvent(queueOwner, new LoginEvent(null)); latch.await(1, TimeUnit.SECONDS); @@ -64,10 +64,10 @@ public class EventLoopTest { } }); - EventExecutorService service = new EventExecutorService(1); + ResourceAwareExecutorService service = new ResourceAwareExecutorService(1); service.start(); - eventLoop.setEventExecutorService(service); + eventLoop.setResourceAwareExecutorService(service); eventLoop.asyncFireEvent(queueOwner, new LoginEvent(null)); eventLoop.asyncFireEvent(queueOwner, new LoginEvent(null)); @@ -109,10 +109,10 @@ public class EventLoopTest { } }); - EventExecutorService service = new EventExecutorService(1); + ResourceAwareExecutorService service = new ResourceAwareExecutorService(1); service.start(); - eventLoop.setEventExecutorService(service); + eventLoop.setResourceAwareExecutorService(service); eventLoop.asyncFireEvent(queueOwner, new LoginEvent(null)); @@ -156,10 +156,10 @@ public class EventLoopTest { } }); - EventExecutorService service = new EventExecutorService(1); + ResourceAwareExecutorService service = new ResourceAwareExecutorService(1); service.start(); - eventLoop.setEventExecutorService(service); + eventLoop.setResourceAwareExecutorService(service); eventLoop.asyncFireEvent(queueOwner, new LoginEvent(null)); diff --git a/event-loop/src/test/java/mc/core/events/LockTest.java b/event-loop/src/test/java/mc/core/events/LockTest.java index 97b22e2..e1e65a2 100644 --- a/event-loop/src/test/java/mc/core/events/LockTest.java +++ b/event-loop/src/test/java/mc/core/events/LockTest.java @@ -1,7 +1,7 @@ package mc.core.events; -import mc.core.events.lock.LockObserveList; -import mc.core.events.lock.PoorMansLock; +import mc.core.events.runner.lock.LockObserveList; +import mc.core.events.runner.lock.PoorMansLock; import org.junit.Assert; import org.junit.Test; From 436ed620ad593bea5045f7ea4a0397d9851d288f Mon Sep 17 00:00:00 2001 From: Forwolk Date: Thu, 9 Aug 2018 22:33:47 +0300 Subject: [PATCH 58/62] fix: getChunk --- .../main/java/mc/world/generated_world/world/CubicWorld.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/generated_world/src/main/java/mc/world/generated_world/world/CubicWorld.java b/generated_world/src/main/java/mc/world/generated_world/world/CubicWorld.java index bbaff53..1ae64b7 100644 --- a/generated_world/src/main/java/mc/world/generated_world/world/CubicWorld.java +++ b/generated_world/src/main/java/mc/world/generated_world/world/CubicWorld.java @@ -107,7 +107,8 @@ public class CubicWorld implements World { @Override public ChunkSection getChunk(int x, int y, int z) { - return null; + Region region = getRegion(x / 16, z / 16); + return region.getChunkAt(x % 16, y % 16, z % 16); } @Override From 57dc918888db29d4c3da7bec8898dca733a4bfbc Mon Sep 17 00:00:00 2001 From: Daniil Date: Fri, 10 Aug 2018 03:27:57 +0700 Subject: [PATCH 59/62] Minor merging fixes --- .../mc/core/events/api/interfaces/PlayerProvidingEvent.java | 3 +-- .../java/mc/core/events/api/samples/BlockBreakEvent.java | 3 +-- .../mc/core/events/runner/ResourceAwareExecutorService.java | 6 ++---- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/event-loop/src/main/java/mc/core/events/api/interfaces/PlayerProvidingEvent.java b/event-loop/src/main/java/mc/core/events/api/interfaces/PlayerProvidingEvent.java index b434061..d05af39 100644 --- a/event-loop/src/main/java/mc/core/events/api/interfaces/PlayerProvidingEvent.java +++ b/event-loop/src/main/java/mc/core/events/api/interfaces/PlayerProvidingEvent.java @@ -2,7 +2,6 @@ package mc.core.events.api.interfaces; import mc.core.Location; import mc.core.player.Player; -import sun.reflect.generics.reflectiveObjects.NotImplementedException; import java.util.Collection; import java.util.Collections; @@ -17,6 +16,6 @@ public interface PlayerProvidingEvent extends LocationProvidingEvent { if (players.size() == 1) return Collections.singletonList(players.get(0).getLocation()); else - throw new NotImplementedException(); + throw new RuntimeException("This method is not implemented."); } } diff --git a/event-loop/src/main/java/mc/core/events/api/samples/BlockBreakEvent.java b/event-loop/src/main/java/mc/core/events/api/samples/BlockBreakEvent.java index 5753491..a515022 100644 --- a/event-loop/src/main/java/mc/core/events/api/samples/BlockBreakEvent.java +++ b/event-loop/src/main/java/mc/core/events/api/samples/BlockBreakEvent.java @@ -3,11 +3,11 @@ package mc.core.events.api.samples; import lombok.Getter; import lombok.RequiredArgsConstructor; import mc.core.Location; -import mc.core.block.Block; import mc.core.events.EventBase; import mc.core.events.api.interfaces.LocationProvidingEvent; import mc.core.events.api.interfaces.PlayerProvidingEvent; import mc.core.player.Player; +import mc.core.world.block.Block; import java.util.Collection; import java.util.Collections; @@ -19,7 +19,6 @@ public class BlockBreakEvent extends EventBase implements PlayerProvidingEvent, private final Player player; private final Block block; - @Override public List getAssociatedPlayers() { return Collections.singletonList(player); diff --git a/event-loop/src/main/java/mc/core/events/runner/ResourceAwareExecutorService.java b/event-loop/src/main/java/mc/core/events/runner/ResourceAwareExecutorService.java index 77637c7..0ec99ca 100644 --- a/event-loop/src/main/java/mc/core/events/runner/ResourceAwareExecutorService.java +++ b/event-loop/src/main/java/mc/core/events/runner/ResourceAwareExecutorService.java @@ -1,7 +1,5 @@ package mc.core.events.runner; -import sun.plugin.dom.exception.InvalidStateException; - import java.util.HashSet; import java.util.Iterator; import java.util.Set; @@ -35,7 +33,7 @@ public class ResourceAwareExecutorService { public void start() { if (executorThreads.size() > 0) - throw new InvalidStateException("This executor service was already started."); + throw new RuntimeException("This executor service was already started."); for (int i = 0; i < threadCount; i++) { Thread thread = new ExecutorWorkerThread("Event Loop #" + i, this); @@ -46,7 +44,7 @@ public class ResourceAwareExecutorService { public void stop() { if (executorThreads.size() == 0) - throw new InvalidStateException("This executor service was not initialized yet."); + throw new RuntimeException("This executor service was not initialized yet."); Iterator iterator = executorThreads.iterator(); while (iterator.hasNext()) { From dc0b85c23bed3e2b2c132f3199899d3159c2a1fc Mon Sep 17 00:00:00 2001 From: Daniil Date: Fri, 10 Aug 2018 03:34:11 +0700 Subject: [PATCH 60/62] Reverted location-fix --- core/src/main/java/mc/core/WarpPosition.java | 4 ++-- core/src/main/java/mc/core/embedded/FakePlayerManager.java | 4 ++-- core/src/main/java/mc/core/events/PlayerLookEvent.java | 4 ++-- core/src/main/java/mc/core/player/ILook.java | 6 ++++-- .../src/main/java/mc/core/player/InMemoryPlayerManager.java | 2 +- core/src/main/java/mc/core/player/Look.java | 6 +++--- core/src/main/java/mc/core/player/Player.java | 2 +- core/src/main/java/mc/core/player/PlayerManager.java | 2 +- core/src/main/java/mc/core/player/SimplePlayer.java | 4 ++-- .../proto_1_12_2/packets/PlayerPositionAndLookPacket.java | 3 +-- 10 files changed, 19 insertions(+), 18 deletions(-) diff --git a/core/src/main/java/mc/core/WarpPosition.java b/core/src/main/java/mc/core/WarpPosition.java index c23325e..4a0e501 100644 --- a/core/src/main/java/mc/core/WarpPosition.java +++ b/core/src/main/java/mc/core/WarpPosition.java @@ -36,7 +36,7 @@ public class WarpPosition extends Location implements Serializable, ILook { super(location.getX(), location.getY(), location.getZ()); } - public WarpPosition(Location location, ILook look) { + public WarpPosition(Location location, Look look) { super(location.getX(), location.getY(), location.getZ()); this.look = look; } @@ -46,7 +46,7 @@ public class WarpPosition extends Location implements Serializable, ILook { } @Override - public void set(ILook look) { + public void set(Look look) { this.look = look; } diff --git a/core/src/main/java/mc/core/embedded/FakePlayerManager.java b/core/src/main/java/mc/core/embedded/FakePlayerManager.java index 061497a..10a8ea5 100644 --- a/core/src/main/java/mc/core/embedded/FakePlayerManager.java +++ b/core/src/main/java/mc/core/embedded/FakePlayerManager.java @@ -8,7 +8,7 @@ import mc.core.Location; import mc.core.chat.MessageType; import mc.core.network.NetChannel; import mc.core.network.SCPacket; -import mc.core.player.ILook; +import mc.core.player.Look; import mc.core.player.Player; import mc.core.player.PlayerManager; import mc.core.text.Text; @@ -52,7 +52,7 @@ public class FakePlayerManager implements PlayerManager { private static final NetChannel FAKE_NET_CHANNEL = new FakeNetChannet(); @Override - public Player createPlayer(String name, Location defaultLocation, ILook defaultLook) { + public Player createPlayer(String name, Location defaultLocation, Look defaultLook) { return null; } diff --git a/core/src/main/java/mc/core/events/PlayerLookEvent.java b/core/src/main/java/mc/core/events/PlayerLookEvent.java index fc4734f..2d03b0b 100644 --- a/core/src/main/java/mc/core/events/PlayerLookEvent.java +++ b/core/src/main/java/mc/core/events/PlayerLookEvent.java @@ -7,7 +7,7 @@ package mc.core.events; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.Setter; -import mc.core.player.ILook; +import mc.core.player.Look; import mc.core.player.Player; @RequiredArgsConstructor @@ -15,5 +15,5 @@ import mc.core.player.Player; @Setter public class PlayerLookEvent extends EventBase { private final Player player; - private ILook newLook; + private Look newLook; } diff --git a/core/src/main/java/mc/core/player/ILook.java b/core/src/main/java/mc/core/player/ILook.java index e0981bf..fdebd51 100644 --- a/core/src/main/java/mc/core/player/ILook.java +++ b/core/src/main/java/mc/core/player/ILook.java @@ -1,7 +1,9 @@ package mc.core.player; -public interface ILook { - void set(ILook look); +import java.io.Serializable; + +public interface ILook extends Serializable { + void set(Look look); float getYaw(); diff --git a/core/src/main/java/mc/core/player/InMemoryPlayerManager.java b/core/src/main/java/mc/core/player/InMemoryPlayerManager.java index bf62814..0ba717c 100644 --- a/core/src/main/java/mc/core/player/InMemoryPlayerManager.java +++ b/core/src/main/java/mc/core/player/InMemoryPlayerManager.java @@ -31,7 +31,7 @@ public class InMemoryPlayerManager implements PlayerManager, Runnable { } @Override - public Player createPlayer(String name, Location defaultLocation, ILook defaultLook) { + public Player createPlayer(String name, Location defaultLocation, Look defaultLook) { SimplePlayer player = new SimplePlayer(); player.setId(rand.nextInt(10000)); player.setUUID(UUID.nameUUIDFromBytes(name.getBytes())); diff --git a/core/src/main/java/mc/core/player/Look.java b/core/src/main/java/mc/core/player/Look.java index a13da39..f15f6f7 100644 --- a/core/src/main/java/mc/core/player/Look.java +++ b/core/src/main/java/mc/core/player/Look.java @@ -13,8 +13,8 @@ public class Look implements ILook { private float yaw, pitch; @Override - public void set(ILook look) { - this.yaw = look.getYaw(); - this.pitch = look.getPitch(); + public void set(Look look) { + this.yaw = look.yaw; + this.pitch = look.pitch; } } diff --git a/core/src/main/java/mc/core/player/Player.java b/core/src/main/java/mc/core/player/Player.java index ababfb0..0b0e2c2 100644 --- a/core/src/main/java/mc/core/player/Player.java +++ b/core/src/main/java/mc/core/player/Player.java @@ -20,7 +20,7 @@ public interface Player { Location getLocation(); - ILook getLook(); + Look getLook(); boolean isFlying(); void setFlying(boolean value); diff --git a/core/src/main/java/mc/core/player/PlayerManager.java b/core/src/main/java/mc/core/player/PlayerManager.java index aa63e66..8dd1c7a 100644 --- a/core/src/main/java/mc/core/player/PlayerManager.java +++ b/core/src/main/java/mc/core/player/PlayerManager.java @@ -11,7 +11,7 @@ import java.util.List; import java.util.Optional; public interface PlayerManager { - Player createPlayer(String name, Location defaultLocation, ILook defaultLook); + Player createPlayer(String name, Location defaultLocation, Look defaultLook); void joinServer(Player player); void leftServer(Player player); Optional getPlayer(String name); diff --git a/core/src/main/java/mc/core/player/SimplePlayer.java b/core/src/main/java/mc/core/player/SimplePlayer.java index a918971..f841595 100644 --- a/core/src/main/java/mc/core/player/SimplePlayer.java +++ b/core/src/main/java/mc/core/player/SimplePlayer.java @@ -18,7 +18,7 @@ public class SimplePlayer implements Player { private boolean online = false; private NetChannel channel; private Location location = new Location(0, 0, 0); - private ILook look = new Look(0, 0); + private Look look = new Look(0, 0); private boolean flying = false; private PlayerSettings settings; @@ -26,7 +26,7 @@ public class SimplePlayer implements Player { this.location.set(location); } - public void setLook(ILook look) { + public void setLook(Look look) { this.look.set(look); } diff --git a/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/PlayerPositionAndLookPacket.java b/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/PlayerPositionAndLookPacket.java index 1255e0c..4133bbe 100644 --- a/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/PlayerPositionAndLookPacket.java +++ b/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/PlayerPositionAndLookPacket.java @@ -10,7 +10,6 @@ import lombok.Setter; import lombok.ToString; import mc.core.Location; import mc.core.network.*; -import mc.core.player.ILook; import mc.core.player.Look; @NoArgsConstructor @@ -19,7 +18,7 @@ import mc.core.player.Look; @ToString public class PlayerPositionAndLookPacket implements SCPacket, CSPacket { private Location location; - private ILook look; + private Look look; private int teleportId; private boolean onGround = false; From b73ed2c4f5b8304e1a814f8c88d363fff5188663 Mon Sep 17 00:00:00 2001 From: Daniil Date: Fri, 10 Aug 2018 03:34:25 +0700 Subject: [PATCH 61/62] Revert "WarpPosition structure rework" This reverts commit 993d9a8 --- core/src/main/java/mc/core/WarpPosition.java | 90 ++------------------ core/src/main/java/mc/core/player/ILook.java | 15 ---- core/src/main/java/mc/core/player/Look.java | 5 +- 3 files changed, 10 insertions(+), 100 deletions(-) delete mode 100644 core/src/main/java/mc/core/player/ILook.java diff --git a/core/src/main/java/mc/core/WarpPosition.java b/core/src/main/java/mc/core/WarpPosition.java index 4a0e501..6e1aacf 100644 --- a/core/src/main/java/mc/core/WarpPosition.java +++ b/core/src/main/java/mc/core/WarpPosition.java @@ -1,90 +1,14 @@ package mc.core; -import mc.core.player.ILook; +import lombok.AllArgsConstructor; +import lombok.Data; import mc.core.player.Look; -import mc.core.world.World; import java.io.Serializable; -import java.util.Objects; -public class WarpPosition extends Location implements Serializable, ILook { - private ILook look; - - public WarpPosition(double x, double y, double z, World world) { - super(x, y, z, world); - } - - public WarpPosition(double x, double y, double z, float yaw, float pitch, World world) { - super(x, y, z, world); - this.look = new Look(yaw, pitch); - } - - public WarpPosition(double x, double y, double z) { - super(x, y, z); - } - - public WarpPosition(double x, double y, double z, float yaw, float pitch) { - super(x, y, z); - this.look = new Look(yaw, pitch); - } - - public WarpPosition(long compactValue) { - super(compactValue); - } - - public WarpPosition(Location location) { - super(location.getX(), location.getY(), location.getZ()); - } - - public WarpPosition(Location location, Look look) { - super(location.getX(), location.getY(), location.getZ()); - this.look = look; - } - - public WarpPosition(long compactValue, World world) { - super(compactValue, world); - } - - @Override - public void set(Look look) { - this.look = look; - } - - @Override - public float getYaw() { - return this.look.getYaw(); - } - - @Override - public void setYaw(float yaw) { - this.look.setYaw(yaw); - } - - @Override - public float getPitch() { - return this.look.getPitch(); - } - - @Override - public void setPitch(float pitch) { - this.look.setPitch(pitch); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - if (!super.equals(o)) return false; - WarpPosition that = (WarpPosition) o; - return Objects.equals(look, that.look); - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), look); - } - - public boolean hasLook() { - return look != null; - } +@Data +@AllArgsConstructor +public class WarpPosition implements Serializable { + private Location location; + private Look look; } diff --git a/core/src/main/java/mc/core/player/ILook.java b/core/src/main/java/mc/core/player/ILook.java deleted file mode 100644 index fdebd51..0000000 --- a/core/src/main/java/mc/core/player/ILook.java +++ /dev/null @@ -1,15 +0,0 @@ -package mc.core.player; - -import java.io.Serializable; - -public interface ILook extends Serializable { - void set(Look look); - - float getYaw(); - - float getPitch(); - - void setYaw(float yaw); - - void setPitch(float pitch); -} diff --git a/core/src/main/java/mc/core/player/Look.java b/core/src/main/java/mc/core/player/Look.java index f15f6f7..1c0f7f4 100644 --- a/core/src/main/java/mc/core/player/Look.java +++ b/core/src/main/java/mc/core/player/Look.java @@ -7,12 +7,13 @@ package mc.core.player; import lombok.AllArgsConstructor; import lombok.Data; +import java.io.Serializable; + @Data @AllArgsConstructor -public class Look implements ILook { +public class Look implements Serializable{ private float yaw, pitch; - @Override public void set(Look look) { this.yaw = look.yaw; this.pitch = look.pitch; From 92a6af264c74209649ba5a5b04d65cc71d4f42b3 Mon Sep 17 00:00:00 2001 From: Daniil Date: Fri, 10 Aug 2018 03:36:57 +0700 Subject: [PATCH 62/62] Project compilation fix --- .../proto_1_12_2/netty/handlers/LoginHandler.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/handlers/LoginHandler.java b/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/handlers/LoginHandler.java index 6e712ee..d8a2cbc 100644 --- a/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/handlers/LoginHandler.java +++ b/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/handlers/LoginHandler.java @@ -10,7 +10,6 @@ import mc.core.network.proto_1_12_2.State; import mc.core.network.proto_1_12_2.TeleportManager; import mc.core.network.proto_1_12_2.netty.wrappers.WrapperNetChannel; import mc.core.network.proto_1_12_2.packets.*; -import mc.core.player.Look; import mc.core.player.Player; import mc.core.player.PlayerManager; import mc.core.player.PlayerMode; @@ -48,8 +47,8 @@ public class LoginHandler extends AbstractStateHandler implements LoginStateHand Player player = playerManager.getPlayer(packet.getPlayerName()) .orElseGet(() -> playerManager.createPlayer( packet.getPlayerName(), - world.getSpawn(), - new Look(0f, 0f))); + world.getSpawn().getLocation(), + world.getSpawn().getLook())); channel.writeAndFlush(new LoginSuccessPacket( player.getUUID(), @@ -68,7 +67,7 @@ public class LoginHandler extends AbstractStateHandler implements LoginStateHand // Spawn Position SpawnPositionPacket pkt2 = new SpawnPositionPacket(); - pkt2.setLocation(world.getSpawn()); + pkt2.setLocation(world.getSpawn().getLocation()); channel.write(pkt2); // Player Abilities @@ -84,7 +83,7 @@ public class LoginHandler extends AbstractStateHandler implements LoginStateHand ChunkDataPacket pkt8 = new ChunkDataPacket(); pkt8.setX(0); pkt8.setZ(0); - pkt8.getChunks().add(world.getChunk(0, 0,0)); + pkt8.getChunks().add(world.getChunk(0, 0, 0)); pkt8.setInitChunk(true); channel.writeAndFlush(pkt8); @@ -107,7 +106,7 @@ public class LoginHandler extends AbstractStateHandler implements LoginStateHand playerData.setPing(0); playerData.setHasDisplayName(true); playerData.setDisplayName(Text.builder() - .append(Text.of(TextColor.RED, TextStyle.BOLD, player.getName().substring(0,1))) + .append(Text.of(TextColor.RED, TextStyle.BOLD, player.getName().substring(0, 1))) .append(Text.of(TextColor.WHITE, player.getName().substring(1))) .build() );