From 7cfa96f925ca5696266403951f4b4d427eddb309 Mon Sep 17 00:00:00 2001 From: dfsek Date: Mon, 15 Feb 2021 19:56:55 -0700 Subject: [PATCH] implement Event API --- .../api/core/event/TerraEventManager.java | 53 +++++++ .../api/core/event/annotations/Listener.java | 5 + .../structures/script/StructureScript.java | 5 +- .../dfsek/terra/api/util/ReflectionUtil.java | 28 ++++ .../dfsek/terra/config/pack/ConfigPack.java | 8 +- .../terra/registry/FunctionRegistry.java | 6 + .../dfsek/terra/registry/TerraRegistry.java | 5 + common/src/test/java/event/EventTest.java | 145 ++++++++++++++++++ 8 files changed, 253 insertions(+), 2 deletions(-) create mode 100644 common/src/main/java/com/dfsek/terra/api/core/event/TerraEventManager.java create mode 100644 common/src/main/java/com/dfsek/terra/api/core/event/annotations/Listener.java create mode 100644 common/src/main/java/com/dfsek/terra/api/util/ReflectionUtil.java create mode 100644 common/src/main/java/com/dfsek/terra/registry/FunctionRegistry.java create mode 100644 common/src/test/java/event/EventTest.java diff --git a/common/src/main/java/com/dfsek/terra/api/core/event/TerraEventManager.java b/common/src/main/java/com/dfsek/terra/api/core/event/TerraEventManager.java new file mode 100644 index 000000000..9baaf9982 --- /dev/null +++ b/common/src/main/java/com/dfsek/terra/api/core/event/TerraEventManager.java @@ -0,0 +1,53 @@ +package com.dfsek.terra.api.core.event; + +import com.dfsek.terra.api.core.TerraPlugin; +import com.dfsek.terra.api.util.ReflectionUtil; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class TerraEventManager implements EventManager { + private final Map, Map>> listeners = new HashMap<>(); + private final TerraPlugin main; + + public TerraEventManager(TerraPlugin main) { + this.main = main; + } + + @Override + public void callEvent(Event event) { + if(!listeners.containsKey(event.getClass())) return; + listeners.get(event.getClass()).forEach((eventListener, methods) -> methods.forEach(method -> { + try { + method.invoke(eventListener, event); + } catch(Exception e) { + StringWriter writer = new StringWriter(); + e.printStackTrace(new PrintWriter(writer)); + main.getLogger().warning("Exception occurred during event handling:"); + main.getLogger().warning(writer.toString()); + main.getLogger().warning("Report this to the maintainers of " + eventListener.getClass().getCanonicalName()); + + } + })); + } + + @SuppressWarnings("unchecked") + @Override + public void registerListener(EventListener listener) { + Class listenerClass = listener.getClass(); + Method[] methods = ReflectionUtil.getMethods(listenerClass); + + for(Method method : methods) { + if(method.getParameterCount() != 1) continue; // Check that parameter count is only 1. + Class eventParam = method.getParameterTypes()[0]; + if(!Event.class.isAssignableFrom(eventParam)) continue; // Check that parameter is an Event. + method.setAccessible(true); + listeners.computeIfAbsent((Class) eventParam, e -> new HashMap<>()).computeIfAbsent(listener, l -> new ArrayList<>()).add(method); + } + } +} diff --git a/common/src/main/java/com/dfsek/terra/api/core/event/annotations/Listener.java b/common/src/main/java/com/dfsek/terra/api/core/event/annotations/Listener.java new file mode 100644 index 000000000..627b4c2e4 --- /dev/null +++ b/common/src/main/java/com/dfsek/terra/api/core/event/annotations/Listener.java @@ -0,0 +1,5 @@ +package com.dfsek.terra.api.core.event.annotations; + +public @interface Listener { + int priority() default 0; +} diff --git a/common/src/main/java/com/dfsek/terra/api/structures/script/StructureScript.java b/common/src/main/java/com/dfsek/terra/api/structures/script/StructureScript.java index ad1512ce3..c68c7cb07 100644 --- a/common/src/main/java/com/dfsek/terra/api/structures/script/StructureScript.java +++ b/common/src/main/java/com/dfsek/terra/api/structures/script/StructureScript.java @@ -28,6 +28,7 @@ import com.dfsek.terra.api.structures.structure.Rotation; import com.dfsek.terra.api.structures.structure.buffer.Buffer; import com.dfsek.terra.api.structures.structure.buffer.DirectBuffer; import com.dfsek.terra.api.structures.structure.buffer.StructureBuffer; +import com.dfsek.terra.registry.FunctionRegistry; import com.dfsek.terra.registry.config.LootRegistry; import com.dfsek.terra.registry.config.ScriptRegistry; import com.dfsek.terra.world.generation.math.SamplerCache; @@ -48,7 +49,7 @@ public class StructureScript { private final TerraPlugin main; String tempID; - public StructureScript(InputStream inputStream, TerraPlugin main, ScriptRegistry registry, LootRegistry lootRegistry, SamplerCache cache) throws ParseException { + public StructureScript(InputStream inputStream, TerraPlugin main, ScriptRegistry registry, LootRegistry lootRegistry, SamplerCache cache, FunctionRegistry functionRegistry) throws ParseException { Parser parser; try { parser = new Parser(IOUtils.toString(inputStream)); @@ -85,6 +86,8 @@ public class StructureScript { .registerFunction("max", new BinaryNumberFunctionBuilder((number, number2) -> FastMath.max(number.doubleValue(), number2.doubleValue()))) .registerFunction("min", new BinaryNumberFunctionBuilder((number, number2) -> FastMath.min(number.doubleValue(), number2.doubleValue()))); + functionRegistry.forEach(parser::registerFunction); // Register registry functions. + block = parser.parse(); this.id = parser.getID(); tempID = id; diff --git a/common/src/main/java/com/dfsek/terra/api/util/ReflectionUtil.java b/common/src/main/java/com/dfsek/terra/api/util/ReflectionUtil.java new file mode 100644 index 000000000..74ee5fecd --- /dev/null +++ b/common/src/main/java/com/dfsek/terra/api/util/ReflectionUtil.java @@ -0,0 +1,28 @@ +package com.dfsek.terra.api.util; + +import org.jetbrains.annotations.NotNull; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.stream.Stream; + +public class ReflectionUtil { + public static Field[] getFields(@NotNull Class type) { + Field[] result = type.getDeclaredFields(); + Class parentClass = type.getSuperclass(); + if(parentClass != null) { + result = Stream.concat(Arrays.stream(result), Arrays.stream(getFields(parentClass))).toArray(Field[]::new); + } + return result; + } + + public static Method[] getMethods(@NotNull Class type) { + Method[] result = type.getDeclaredMethods(); + Class parentClass = type.getSuperclass(); + if(parentClass != null) { + result = Stream.concat(Arrays.stream(result), Arrays.stream(getMethods(parentClass))).toArray(Method[]::new); + } + return result; + } +} diff --git a/common/src/main/java/com/dfsek/terra/config/pack/ConfigPack.java b/common/src/main/java/com/dfsek/terra/config/pack/ConfigPack.java index 91a83f3fe..257361acd 100644 --- a/common/src/main/java/com/dfsek/terra/config/pack/ConfigPack.java +++ b/common/src/main/java/com/dfsek/terra/config/pack/ConfigPack.java @@ -44,6 +44,7 @@ import com.dfsek.terra.config.templates.OreTemplate; import com.dfsek.terra.config.templates.PaletteTemplate; import com.dfsek.terra.config.templates.StructureTemplate; import com.dfsek.terra.config.templates.TreeTemplate; +import com.dfsek.terra.registry.FunctionRegistry; import com.dfsek.terra.registry.TerraRegistry; import com.dfsek.terra.registry.config.BiomeRegistry; import com.dfsek.terra.registry.config.CarverRegistry; @@ -95,6 +96,7 @@ public class ConfigPack implements LoaderRegistrar { private final CarverRegistry carverRegistry = new CarverRegistry(); private final NormalizerRegistry normalizerRegistry = new NormalizerRegistry(); + private final FunctionRegistry functionRegistry = new FunctionRegistry(); private final AbstractConfigLoader abstractConfigLoader = new AbstractConfigLoader(); private final ConfigLoader selfLoader = new ConfigLoader(); @@ -201,7 +203,7 @@ public class ConfigPack implements LoaderRegistrar { loader.open("structures/data", ".tesf").thenEntries(entries -> { for(Map.Entry entry : entries) { try { - StructureScript structureScript = new StructureScript(entry.getValue(), main, scriptRegistry, lootRegistry, samplerCache); + StructureScript structureScript = new StructureScript(entry.getValue(), main, scriptRegistry, lootRegistry, samplerCache, functionRegistry); scriptRegistry.add(structureScript.getId(), structureScript); } catch(com.dfsek.terra.api.structures.parser.exceptions.ParseException e) { throw new LoadException("Unable to load script \"" + entry.getKey() + "\"", e); @@ -300,4 +302,8 @@ public class ConfigPack implements LoaderRegistrar { public BiomeProvider.BiomeProviderBuilder getBiomeProviderBuilder() { return biomeProviderBuilder; } + + public FunctionRegistry getFunctionRegistry() { + return functionRegistry; + } } diff --git a/common/src/main/java/com/dfsek/terra/registry/FunctionRegistry.java b/common/src/main/java/com/dfsek/terra/registry/FunctionRegistry.java new file mode 100644 index 000000000..dc3689d0a --- /dev/null +++ b/common/src/main/java/com/dfsek/terra/registry/FunctionRegistry.java @@ -0,0 +1,6 @@ +package com.dfsek.terra.registry; + +import com.dfsek.terra.api.structures.parser.lang.functions.FunctionBuilder; + +public class FunctionRegistry extends TerraRegistry> { +} diff --git a/common/src/main/java/com/dfsek/terra/registry/TerraRegistry.java b/common/src/main/java/com/dfsek/terra/registry/TerraRegistry.java index ab045d051..5b25ca67d 100644 --- a/common/src/main/java/com/dfsek/terra/registry/TerraRegistry.java +++ b/common/src/main/java/com/dfsek/terra/registry/TerraRegistry.java @@ -9,6 +9,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.function.BiConsumer; import java.util.function.Consumer; public abstract class TerraRegistry implements TypeLoader { @@ -59,6 +60,10 @@ public abstract class TerraRegistry implements TypeLoader { objects.forEach((id, obj) -> consumer.accept(obj)); } + public void forEach(BiConsumer consumer) { + objects.forEach(consumer); + } + public Set entries() { return new HashSet<>(objects.values()); } diff --git a/common/src/test/java/event/EventTest.java b/common/src/test/java/event/EventTest.java new file mode 100644 index 000000000..b16498277 --- /dev/null +++ b/common/src/test/java/event/EventTest.java @@ -0,0 +1,145 @@ +package event; + +import com.dfsek.tectonic.loading.TypeRegistry; +import com.dfsek.terra.api.core.TerraPlugin; +import com.dfsek.terra.api.core.event.Event; +import com.dfsek.terra.api.core.event.EventListener; +import com.dfsek.terra.api.core.event.TerraEventManager; +import com.dfsek.terra.api.core.event.annotations.Listener; +import com.dfsek.terra.api.platform.handle.ItemHandle; +import com.dfsek.terra.api.platform.handle.WorldHandle; +import com.dfsek.terra.api.platform.world.World; +import com.dfsek.terra.config.PluginConfig; +import com.dfsek.terra.config.lang.Language; +import com.dfsek.terra.debug.DebugLogger; +import com.dfsek.terra.registry.ConfigRegistry; +import com.dfsek.terra.world.TerraWorld; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.util.logging.Logger; + +public class EventTest { + public TerraPlugin main = new TerraPlugin() { + private final Logger logger = Logger.getLogger("Terra"); + + @Override + public WorldHandle getWorldHandle() { + return null; + } + + @Override + public boolean isEnabled() { + return false; + } + + @Override + public TerraWorld getWorld(World world) { + return null; + } + + @Override + public Logger getLogger() { + return logger; + } + + @Override + public PluginConfig getTerraConfig() { + return null; + } + + @Override + public File getDataFolder() { + return null; + } + + @Override + public boolean isDebug() { + return false; + } + + @Override + public Language getLanguage() { + return null; + } + + @Override + public ConfigRegistry getRegistry() { + return null; + } + + @Override + public void reload() { + + } + + @Override + public ItemHandle getItemHandle() { + return null; + } + + @Override + public void saveDefaultConfig() { + + } + + @Override + public String platformName() { + return null; + } + + @Override + public DebugLogger getDebugLogger() { + return null; + } + + @Override + public void register(TypeRegistry registry) { + + } + }; + + @Test + public void eventTest() { + TerraEventManager eventManager = new TerraEventManager(main); + eventManager.registerListener(new TestListener()); + eventManager.registerListener(new TestListener2()); + + TestEvent event = new TestEvent(4); + eventManager.callEvent(event); + + eventManager.registerListener(new TestListenerException()); + + TestEvent event2 = new TestEvent(4); + eventManager.callEvent(event2); + } + + static class TestListener implements EventListener { + @Listener + public void doThing(TestEvent event) { + System.out.println("Event value: " + event.value); + } + } + + static class TestListener2 implements EventListener { + @Listener + public void doThing(TestEvent event) { + System.out.println("Event value 2: " + event.value); + } + } + + static class TestListenerException implements EventListener { + @Listener + public void doThing(TestEvent event) { + throw new RuntimeException("bazinga: " + event.value); + } + } + + static class TestEvent implements Event { + private final int value; + + TestEvent(int value) { + this.value = value; + } + } +}