From 6be1f7f2cfaf411792dea620da6038fc10a6041c Mon Sep 17 00:00:00 2001 From: JeremyStarTM Date: Tue, 11 Jun 2024 22:49:00 +0200 Subject: [PATCH] Add event precomputation --- .../staropensource/sosengine/base/Engine.java | 20 ++++ .../sosengine/base/EngineConfiguration.java | 25 ++++- .../base/classes/helpers/EventHelper.java | 97 +++++++++++++++---- .../sosengine/base/events/LogEvent.java | 3 + 4 files changed, 125 insertions(+), 20 deletions(-) diff --git a/base/src/main/java/de/staropensource/sosengine/base/Engine.java b/base/src/main/java/de/staropensource/sosengine/base/Engine.java index fef581b..2d8b944 100644 --- a/base/src/main/java/de/staropensource/sosengine/base/Engine.java +++ b/base/src/main/java/de/staropensource/sosengine/base/Engine.java @@ -19,9 +19,13 @@ package de.staropensource.sosengine.base; +import de.staropensource.sosengine.base.classes.Event; import de.staropensource.sosengine.base.classes.SubsystemMainClass; +import de.staropensource.sosengine.base.classes.helpers.EventHelper; import de.staropensource.sosengine.base.data.info.EngineInformation; +import de.staropensource.sosengine.base.events.EngineCrashEvent; import de.staropensource.sosengine.base.events.EngineShutdownEvent; +import de.staropensource.sosengine.base.events.LogEvent; import de.staropensource.sosengine.base.events.internal.InternalEngineShutdownEvent; import de.staropensource.sosengine.base.logging.CrashHandler; import de.staropensource.sosengine.base.logging.LoggerInstance; @@ -95,6 +99,9 @@ public final class Engine implements SubsystemMainClass { // Populate crash content populateCrashContent(); + // Precompute event listeners + precomputeEventListeners(); + // Initialize variables logger = new LoggerInstance(new LogIssuer(getClass(), CodePart.ENGINE)); }); @@ -171,6 +178,19 @@ public final class Engine implements SubsystemMainClass { CrashHandler.getCrashContent().put("Stacktrace", "%stacktrace%"); } + /** + * Precomputes all base engine events. + * + * @since 1-alpha0 + */ + public void precomputeEventListeners() { + EventHelper.precomputeEventListeners(InternalEngineShutdownEvent.class); + + EventHelper.precomputeEventListeners(EngineCrashEvent.class); + EventHelper.precomputeEventListeners(EngineShutdownEvent.class); + EventHelper.precomputeEventListeners(LogEvent.class); + } + /** * Shuts the engine and JVM down. * diff --git a/base/src/main/java/de/staropensource/sosengine/base/EngineConfiguration.java b/base/src/main/java/de/staropensource/sosengine/base/EngineConfiguration.java index b5ec0cf..0b33e88 100644 --- a/base/src/main/java/de/staropensource/sosengine/base/EngineConfiguration.java +++ b/base/src/main/java/de/staropensource/sosengine/base/EngineConfiguration.java @@ -186,7 +186,7 @@ public final class EngineConfiguration implements SubsystemConfiguration { private boolean loggerForceStandardOutput; /** - * Makes the {@link Logger} work asynchronous if enabled. + * If enabled, will makes the {@link Logger} work asynchronous. * * @since 1-alpha0 * @@ -197,7 +197,21 @@ public final class EngineConfiguration implements SubsystemConfiguration { * @see EngineConfiguration#optimizeLogging * @since 1-alpha0 */ - private boolean optimizeLogging; + private boolean optimizeLogging; + + /** + * If enabled, allows for {@link java.util.EventListener} precomputation. + * + * @since 1-alpha0 + * + * -- GETTER -- + * Gets the value for {@code optimizeEvents}. + * + * @return variable value + * @see EngineConfiguration#optimizeEvents + * @since 1-alpha0 + */ + private boolean optimizeEvents; /** * Constructor. @@ -248,6 +262,7 @@ public final class EngineConfiguration implements SubsystemConfiguration { case "loggerForceStandardOutput" -> loggerForceStandardOutput = parser.getBoolean(group + property); case "optimizeLogging" -> optimizeLogging = parser.getBoolean(group + property); + case "optimizeEvents" -> optimizeEvents = parser.getBoolean(group + property); } } catch (NullPointerException ignored) {} } @@ -277,7 +292,8 @@ public final class EngineConfiguration implements SubsystemConfiguration { loggerImmediateShutdown = false; loggerForceStandardOutput = false; - optimizeLogging = false; + optimizeLogging = true; + optimizeEvents = true; } /** {@inheritDoc} */ @@ -314,6 +330,9 @@ public final class EngineConfiguration implements SubsystemConfiguration { case "optimizeLogging" -> { return optimizeLogging; } + case "optimizeEvents" -> { + return optimizeEvents; + } default -> { return null; } diff --git a/base/src/main/java/de/staropensource/sosengine/base/classes/helpers/EventHelper.java b/base/src/main/java/de/staropensource/sosengine/base/classes/helpers/EventHelper.java index 4f02a88..7e8af20 100644 --- a/base/src/main/java/de/staropensource/sosengine/base/classes/helpers/EventHelper.java +++ b/base/src/main/java/de/staropensource/sosengine/base/classes/helpers/EventHelper.java @@ -45,6 +45,13 @@ import java.util.*; @Getter @SuppressWarnings({ "unused" }) public class EventHelper { + /** + * Contains all precomputed event listeners. + * + * @since 1-alpha0 + */ + private static final HashMap<@NotNull Class, LinkedList<@NotNull Method>> precomputedEventListeners = new HashMap<>(); + /** * Constructor. */ @@ -64,6 +71,42 @@ public class EventHelper { Logger.diag(new LogIssuer(clazz), "Event " + clazz.getName() + " called with arguments " + ListFormatter.formatArray(arguments)); } + /** + * Returns all annotated methods. + * + * @param clazz event class + * @param forceScanning forces the method to ignore precomputed event listeners + * @return list of annotated methods + * @since 1-alpha0 + */ + @NotNull + public static LinkedList getAnnotatedMethods(@NotNull Class clazz, boolean forceScanning) { + LinkedList methods = new LinkedList<>(); + + if (forceScanning || !precomputedEventListeners.containsKey(clazz) || EngineConfiguration.getInstance().isOptimizeEvents()) { + // Scan entire classpath through Reflections library + Reflections reflections = new Reflections( + new ConfigurationBuilder() + .setUrls(ClasspathHelper.forJavaClassPath()) + .setScanners(Scanners.MethodsAnnotated) + ); + + // Get annotated methods + Set<@NotNull Method> annotatedMethods = reflections.getMethodsAnnotatedWith(EventListener.class); + + // Sort event listeners not listening for this event out + for (Method method : annotatedMethods) + if (method.getAnnotation(EventListener.class).event() == clazz) + methods.add(method); + + // Sort 'methods' linked list + methods.sort(Comparator.comparing(method0 -> method0.getAnnotation(EventListener.class).priority())); + } else + methods = precomputedEventListeners.get(clazz); + + return methods; + } + /** * Returns all annotated methods. * @@ -73,23 +116,7 @@ public class EventHelper { */ @NotNull public static LinkedList getAnnotatedMethods(@NotNull Class clazz) { - LinkedList methods = new LinkedList<>(); - - Reflections reflections = new Reflections( - new ConfigurationBuilder() - .setUrls(ClasspathHelper.forJavaClassPath()) - .setScanners(Scanners.MethodsAnnotated) - ); - Set<@NotNull Method> annotatedMethods = reflections.getMethodsAnnotatedWith(EventListener.class); - - for (Method method : annotatedMethods) - if (method.getAnnotation(EventListener.class).event() == clazz) - methods.add(method); - - // Sort 'methods' linked list - methods.sort(Comparator.comparing(method0 -> method0.getAnnotation(EventListener.class).priority())); - - return methods; + return getAnnotatedMethods(clazz, false); } /** @@ -107,4 +134,40 @@ public class EventHelper { } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | NullPointerException | ExceptionInInitializerError ignored) {} } } + + /** + * Precomputes all event listeners listening on some event. + * Will recompute all event listeners if the specified event is already precomputed. + * + * @param clazz event class to (p)recompute, set to {@code null} to recompute all precomputed events + * @since 1-alpha0 + */ + public static void precomputeEventListeners(@Nullable Class clazz) { + if (EngineConfiguration.getInstance().isOptimizeEvents()) return; + + if (clazz == null) + for (Class event : precomputedEventListeners.keySet()) + precomputeEventListeners(event); + else { + LinkedList<@NotNull Method> annotatedMethods = getAnnotatedMethods(clazz); + + if (precomputedEventListeners.containsKey(clazz)) + precomputedEventListeners.replace(clazz, annotatedMethods); + else + precomputedEventListeners.put(clazz, annotatedMethods); + } + } + + /** + * Unloads precomputed event listeners. + * + * @param clazz event class to remove precomputed event listeners for, set to {@code null} to remove all precomputed event listeners + * @since 1-alpha0 + */ + public static void removePrecomputedEventListeners(@Nullable Class clazz) { + if (clazz == null) + precomputedEventListeners.clear(); + else + precomputedEventListeners.remove(clazz); + } } diff --git a/base/src/main/java/de/staropensource/sosengine/base/events/LogEvent.java b/base/src/main/java/de/staropensource/sosengine/base/events/LogEvent.java index 7709758..026407c 100644 --- a/base/src/main/java/de/staropensource/sosengine/base/events/LogEvent.java +++ b/base/src/main/java/de/staropensource/sosengine/base/events/LogEvent.java @@ -58,6 +58,9 @@ public final class LogEvent implements Event { * @since 1-alpha0 */ public void callEvent(@NotNull LogLevel level, @NotNull LogIssuer logIssuer, @NotNull String message) { + // Uncommenting this would be a great way to cause a StackOverflowException! + //EventHelper.logCall(getClass(), level, logIssuer, message); + for (Method method : EventHelper.getAnnotatedMethods(getClass())) { try { method.invoke(null, level, logIssuer, message);