diff --git a/shared/src/main/java/me/xginko/aef/utils/reflection/ReflectionUtil.java b/shared/src/main/java/me/xginko/aef/utils/reflection/ReflectionUtil.java index 5252e1d4..7c433662 100644 --- a/shared/src/main/java/me/xginko/aef/utils/reflection/ReflectionUtil.java +++ b/shared/src/main/java/me/xginko/aef/utils/reflection/ReflectionUtil.java @@ -118,6 +118,31 @@ public static boolean hasClass(final @NonNull String className) { return findClass(className) != null; } + /** + * Gets a handle for a class method, regardless of its return type. + * + *

{@link #findMethod} matches on the exact {@link MethodType}, return type included, so it + * cannot look up a method whose return class is not available at compile time. Use this instead + * when only the name and parameters are known.

+ * + * @param holderClass a class + * @param methodName a method name + * @param parameterClasses an array of method parameter classes + * @return a method handle or {@code null} if not found + */ + public static @Nullable MethodHandle findMethodAnyReturn(final @Nullable Class holderClass, final String methodName, final Class... parameterClasses) { + if (holderClass == null) return null; + for (final Class parameterClass : parameterClasses) { + if (parameterClass == null) return null; + } + + try { + return LOOKUP.unreflect(holderClass.getMethod(methodName, parameterClasses)); + } catch (final NoSuchMethodException | IllegalAccessException e) { + return null; + } + } + /** * Gets a handle for a class method. * diff --git a/shared/src/main/java/me/xginko/aef/utils/tickdata/FoliaTickReporter.java b/shared/src/main/java/me/xginko/aef/utils/tickdata/FoliaTickReporter.java index b3a1837d..aadd735e 100644 --- a/shared/src/main/java/me/xginko/aef/utils/tickdata/FoliaTickReporter.java +++ b/shared/src/main/java/me/xginko/aef/utils/tickdata/FoliaTickReporter.java @@ -24,10 +24,11 @@ public FoliaTickReporter(Server server, Duration cacheTime) { this.tps_cache = Caffeine.newBuilder().expireAfterWrite(cacheTime).build(); this.mspt_cache = Caffeine.newBuilder().expireAfterWrite(cacheTime).build(); - this.getTickReport5s = ReflectionUtil.findMethod( + // Returns TickData.TickReportData, which is not on the compile classpath, so the return + // type must not be part of the lookup. + this.getTickReport5s = ReflectionUtil.findMethodAnyReturn( TickRegionScheduler.RegionScheduleHandle.class, "getTickReport5s", - Object.class, long.class ); } @@ -49,10 +50,7 @@ public void disable() { @Override public double getGlobalTPS() { - if (getTickReport5s != null) { - return tps_cache.get(RegionizedServer.getGlobalTickData(), this::extractTpsValue); - } - return server.getTPS()[0]; + return tps_cache.get(RegionizedServer.getGlobalTickData(), this::extractTpsValue); } @Override @@ -60,18 +58,12 @@ public double getTPS() { final ThreadedRegionizer.ThreadedRegion region = TickRegionScheduler.getCurrentRegion(); if (region == null) return getGlobalTPS(); - if (getTickReport5s != null) { - return tps_cache.get(region.getData().getRegionSchedulingHandle(), this::extractTpsValue); - } - return server.getTPS()[0]; + return tps_cache.get(region.getData().getRegionSchedulingHandle(), this::extractTpsValue); } @Override public double getGlobalMSPT() { - if (getTickReport5s != null) { - return mspt_cache.get(RegionizedServer.getGlobalTickData(), this::extractMsptValue); - } - return server.getAverageTickTime(); + return mspt_cache.get(RegionizedServer.getGlobalTickData(), this::extractMsptValue); } @Override @@ -79,13 +71,14 @@ public double getMSPT() { final ThreadedRegionizer.ThreadedRegion region = TickRegionScheduler.getCurrentRegion(); if (region == null) return getGlobalMSPT(); - if (getTickReport5s != null) { - return mspt_cache.get(region.getData().getRegionSchedulingHandle(), this::extractMsptValue); - } - return server.getAverageTickTime(); + return mspt_cache.get(region.getData().getRegionSchedulingHandle(), this::extractMsptValue); } + // The Server fallbacks below are resolved inside the cache loaders on purpose. Server#getTPS and + // Server#getAverageTickTime rebuild the 1m/5m/15m tick reports on every call, which sorts the + // whole tick history, so they must never be reached on an uncached path. private double extractTpsValue(TickRegionScheduler.RegionScheduleHandle handle) { + if (getTickReport5s == null) return server.getTPS()[0]; try { Object report = getTickReport5s.invoke(handle, System.nanoTime()); return extractAverage(report); @@ -95,6 +88,7 @@ private double extractTpsValue(TickRegionScheduler.RegionScheduleHandle handle) } private double extractMsptValue(TickRegionScheduler.RegionScheduleHandle handle) { + if (getTickReport5s == null) return server.getAverageTickTime(); try { Object report = getTickReport5s.invoke(handle, System.nanoTime()); return extractAverage(report) / 1000000.0;