Add event precomputation

This commit is contained in:
JeremyStar™ 2024-06-11 22:49:00 +02:00
parent 832ed5cd5c
commit 6be1f7f2cf
Signed by: JeremyStarTM
GPG key ID: E366BAEF67E4704D
4 changed files with 125 additions and 20 deletions

View file

@ -19,9 +19,13 @@
package de.staropensource.sosengine.base; 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.SubsystemMainClass;
import de.staropensource.sosengine.base.classes.helpers.EventHelper;
import de.staropensource.sosengine.base.data.info.EngineInformation; 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.EngineShutdownEvent;
import de.staropensource.sosengine.base.events.LogEvent;
import de.staropensource.sosengine.base.events.internal.InternalEngineShutdownEvent; import de.staropensource.sosengine.base.events.internal.InternalEngineShutdownEvent;
import de.staropensource.sosengine.base.logging.CrashHandler; import de.staropensource.sosengine.base.logging.CrashHandler;
import de.staropensource.sosengine.base.logging.LoggerInstance; import de.staropensource.sosengine.base.logging.LoggerInstance;
@ -95,6 +99,9 @@ public final class Engine implements SubsystemMainClass {
// Populate crash content // Populate crash content
populateCrashContent(); populateCrashContent();
// Precompute event listeners
precomputeEventListeners();
// Initialize variables // Initialize variables
logger = new LoggerInstance(new LogIssuer(getClass(), CodePart.ENGINE)); logger = new LoggerInstance(new LogIssuer(getClass(), CodePart.ENGINE));
}); });
@ -171,6 +178,19 @@ public final class Engine implements SubsystemMainClass {
CrashHandler.getCrashContent().put("Stacktrace", "%stacktrace%"); 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. * Shuts the engine and JVM down.
* *

View file

@ -186,7 +186,7 @@ public final class EngineConfiguration implements SubsystemConfiguration {
private boolean loggerForceStandardOutput; private boolean loggerForceStandardOutput;
/** /**
* Makes the {@link Logger} work asynchronous if enabled. * If enabled, will makes the {@link Logger} work asynchronous.
* *
* @since 1-alpha0 * @since 1-alpha0
* *
@ -197,7 +197,21 @@ public final class EngineConfiguration implements SubsystemConfiguration {
* @see EngineConfiguration#optimizeLogging * @see EngineConfiguration#optimizeLogging
* @since 1-alpha0 * @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. * Constructor.
@ -248,6 +262,7 @@ public final class EngineConfiguration implements SubsystemConfiguration {
case "loggerForceStandardOutput" -> loggerForceStandardOutput = parser.getBoolean(group + property); case "loggerForceStandardOutput" -> loggerForceStandardOutput = parser.getBoolean(group + property);
case "optimizeLogging" -> optimizeLogging = parser.getBoolean(group + property); case "optimizeLogging" -> optimizeLogging = parser.getBoolean(group + property);
case "optimizeEvents" -> optimizeEvents = parser.getBoolean(group + property);
} }
} catch (NullPointerException ignored) {} } catch (NullPointerException ignored) {}
} }
@ -277,7 +292,8 @@ public final class EngineConfiguration implements SubsystemConfiguration {
loggerImmediateShutdown = false; loggerImmediateShutdown = false;
loggerForceStandardOutput = false; loggerForceStandardOutput = false;
optimizeLogging = false; optimizeLogging = true;
optimizeEvents = true;
} }
/** {@inheritDoc} */ /** {@inheritDoc} */
@ -314,6 +330,9 @@ public final class EngineConfiguration implements SubsystemConfiguration {
case "optimizeLogging" -> { case "optimizeLogging" -> {
return optimizeLogging; return optimizeLogging;
} }
case "optimizeEvents" -> {
return optimizeEvents;
}
default -> { default -> {
return null; return null;
} }

View file

@ -45,6 +45,13 @@ import java.util.*;
@Getter @Getter
@SuppressWarnings({ "unused" }) @SuppressWarnings({ "unused" })
public class EventHelper { public class EventHelper {
/**
* Contains all precomputed event listeners.
*
* @since 1-alpha0
*/
private static final HashMap<@NotNull Class<? extends Event>, LinkedList<@NotNull Method>> precomputedEventListeners = new HashMap<>();
/** /**
* Constructor. * Constructor.
*/ */
@ -64,6 +71,42 @@ public class EventHelper {
Logger.diag(new LogIssuer(clazz), "Event " + clazz.getName() + " called with arguments " + ListFormatter.formatArray(arguments)); 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<Method> getAnnotatedMethods(@NotNull Class<? extends Event> clazz, boolean forceScanning) {
LinkedList<Method> 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. * Returns all annotated methods.
* *
@ -73,23 +116,7 @@ public class EventHelper {
*/ */
@NotNull @NotNull
public static LinkedList<Method> getAnnotatedMethods(@NotNull Class<? extends Event> clazz) { public static LinkedList<Method> getAnnotatedMethods(@NotNull Class<? extends Event> clazz) {
LinkedList<Method> methods = new LinkedList<>(); return getAnnotatedMethods(clazz, false);
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;
} }
/** /**
@ -107,4 +134,40 @@ public class EventHelper {
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | NullPointerException | ExceptionInInitializerError ignored) {} } 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<? extends Event> clazz) {
if (EngineConfiguration.getInstance().isOptimizeEvents()) return;
if (clazz == null)
for (Class<? extends Event> 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<? extends Event> clazz) {
if (clazz == null)
precomputedEventListeners.clear();
else
precomputedEventListeners.remove(clazz);
}
} }

View file

@ -58,6 +58,9 @@ public final class LogEvent implements Event {
* @since 1-alpha0 * @since 1-alpha0
*/ */
public void callEvent(@NotNull LogLevel level, @NotNull LogIssuer logIssuer, @NotNull String message) { 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())) { for (Method method : EventHelper.getAnnotatedMethods(getClass())) {
try { try {
method.invoke(null, level, logIssuer, message); method.invoke(null, level, logIssuer, message);