From fc8faaee9b3db4be224786509e2bde977141951d Mon Sep 17 00:00:00 2001 From: Lyfts <127234178+Lyfts@users.noreply.github.com> Date: Sun, 6 Oct 2024 22:47:34 +0200 Subject: [PATCH 1/7] backport @EventBusSubscriber annotation from 1.12.2 --- .../com/gtnewhorizon/gtnhlib/ClientProxy.java | 8 +- .../com/gtnewhorizon/gtnhlib/CommonProxy.java | 3 + .../gtnhlib/eventbus/AutoEventBus.java | 319 ++++++++++++++++++ .../gtnhlib/eventbus/EventBusSubscriber.java | 35 ++ .../eventbus/StaticASMEventHandler.java | 128 +++++++ .../gtnhlib/util/AnimatedTooltipHandler.java | 30 +- 6 files changed, 506 insertions(+), 17 deletions(-) create mode 100644 src/main/java/com/gtnewhorizon/gtnhlib/eventbus/AutoEventBus.java create mode 100644 src/main/java/com/gtnewhorizon/gtnhlib/eventbus/EventBusSubscriber.java create mode 100644 src/main/java/com/gtnewhorizon/gtnhlib/eventbus/StaticASMEventHandler.java diff --git a/src/main/java/com/gtnewhorizon/gtnhlib/ClientProxy.java b/src/main/java/com/gtnewhorizon/gtnhlib/ClientProxy.java index 02a190a..000f0f0 100644 --- a/src/main/java/com/gtnewhorizon/gtnhlib/ClientProxy.java +++ b/src/main/java/com/gtnewhorizon/gtnhlib/ClientProxy.java @@ -6,17 +6,18 @@ import net.minecraft.util.ChatComponentText; import net.minecraft.util.EnumChatFormatting; import net.minecraft.util.IChatComponent; -import net.minecraftforge.common.MinecraftForge; import com.gtnewhorizon.gtnhlib.client.model.ModelLoader; +import com.gtnewhorizon.gtnhlib.eventbus.EventBusSubscriber; import com.gtnewhorizon.gtnhlib.util.AboveHotbarHUD; -import com.gtnewhorizon.gtnhlib.util.AnimatedTooltipHandler; import cpw.mods.fml.common.event.*; import cpw.mods.fml.common.eventhandler.SubscribeEvent; import cpw.mods.fml.common.gameevent.TickEvent; +import cpw.mods.fml.relauncher.Side; @SuppressWarnings("unused") +@EventBusSubscriber(side = Side.CLIENT) public class ClientProxy extends CommonProxy { private static boolean modelsBaked = false; @@ -36,7 +37,6 @@ public void init(FMLInitializationEvent event) { @Override public void postInit(FMLPostInitializationEvent event) { super.postInit(event); - MinecraftForge.EVENT_BUS.register(new AnimatedTooltipHandler()); if (shouldLoadModels()) { Minecraft.getMinecraft().refreshResources(); @@ -131,7 +131,7 @@ public void printMessageAboveHotbar(String message, int displayDuration, boolean } @SubscribeEvent - public void onTick(TickEvent.ClientTickEvent event) { + public static void onTick(TickEvent.ClientTickEvent event) { if (!modelsBaked) { ModelLoader.bakeModels(); modelsBaked = true; diff --git a/src/main/java/com/gtnewhorizon/gtnhlib/CommonProxy.java b/src/main/java/com/gtnewhorizon/gtnhlib/CommonProxy.java index c88a800..bc12a6d 100644 --- a/src/main/java/com/gtnewhorizon/gtnhlib/CommonProxy.java +++ b/src/main/java/com/gtnewhorizon/gtnhlib/CommonProxy.java @@ -5,6 +5,7 @@ import net.minecraftforge.common.util.FakePlayer; import com.gtnewhorizon.gtnhlib.config.ConfigurationManager; +import com.gtnewhorizon.gtnhlib.eventbus.AutoEventBus; import com.gtnewhorizon.gtnhlib.network.NetworkHandler; import com.gtnewhorizon.gtnhlib.network.PacketMessageAboveHotbar; @@ -20,10 +21,12 @@ public class CommonProxy { public void preInit(FMLPreInitializationEvent event) { + AutoEventBus.setDataTable(event.getAsmData()); GTNHLib.info("GTNHLib version " + Tags.VERSION + " loaded."); } public void init(FMLInitializationEvent event) { + AutoEventBus.registerSubscribers(); NetworkHandler.init(); ConfigurationManager.onInit(); } diff --git a/src/main/java/com/gtnewhorizon/gtnhlib/eventbus/AutoEventBus.java b/src/main/java/com/gtnewhorizon/gtnhlib/eventbus/AutoEventBus.java new file mode 100644 index 0000000..9904f38 --- /dev/null +++ b/src/main/java/com/gtnewhorizon/gtnhlib/eventbus/AutoEventBus.java @@ -0,0 +1,319 @@ +package com.gtnewhorizon.gtnhlib.eventbus; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.terraingen.OreGenEvent; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.NotNull; + +import com.google.common.collect.SetMultimap; +import com.gtnewhorizon.gtnhlib.GTNHLib; +import com.gtnewhorizon.gtnhlib.reflect.Fields; + +import cpw.mods.fml.common.FMLCommonHandler; +import cpw.mods.fml.common.Loader; +import cpw.mods.fml.common.Mod; +import cpw.mods.fml.common.ModContainer; +import cpw.mods.fml.common.discovery.ASMDataTable; +import cpw.mods.fml.common.eventhandler.Event; +import cpw.mods.fml.common.eventhandler.EventBus; +import cpw.mods.fml.common.eventhandler.IEventListener; +import cpw.mods.fml.common.eventhandler.SubscribeEvent; +import cpw.mods.fml.relauncher.ReflectionHelper; +import cpw.mods.fml.relauncher.Side; +import it.unimi.dsi.fastutil.objects.Object2BooleanMap; +import it.unimi.dsi.fastutil.objects.Object2BooleanOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import it.unimi.dsi.fastutil.objects.ObjectSet; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class AutoEventBus { + + private static final Boolean DEBUG = Boolean.getBoolean("gtnhlib.debug.eventbus"); + private static final Logger LOGGER = LogManager.getLogger("GTNHLib EventBus"); + private static final Object2BooleanMap validEventsForSide = new Object2BooleanOpenHashMap<>(); + private static final ObjectSet registeredClasses = new ObjectOpenHashSet<>(); + private static boolean hasRegistered; + private static ASMDataTable asmDataTable; + + private enum EventBusType { + + FORGE(MinecraftForge.EVENT_BUS, AutoEventBus::isForgeEvent), + OREGEN(MinecraftForge.ORE_GEN_BUS, AutoEventBus::isOreGenEvent), + TERRAIN_GEN(MinecraftForge.TERRAIN_GEN_BUS, AutoEventBus::isTerrainEvent), + FML(FMLCommonHandler.instance().bus(), AutoEventBus::isFMLEvent); + + private static final EventBusType[] VALUES = values(); + private final ConcurrentHashMap> listeners; + private final Map listenerOwners; + private final Predicate> canRegister; + private final int busID; + + EventBusType(EventBus instance, Predicate> canRegister) { + this.canRegister = canRegister; + this.listeners = ReflectionHelper.getPrivateValue(EventBus.class, instance, "listeners"); + this.listenerOwners = ReflectionHelper.getPrivateValue(EventBus.class, instance, "listenerOwners"); + this.busID = ReflectionHelper.getPrivateValue(EventBus.class, instance, "busID"); + } + + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + private boolean canRegister(Class clazz) { + return canRegister.test(clazz); + } + + private boolean isRegistered(Class clazz) { + return listeners.containsKey(clazz); + } + } + + public static void registerSubscribers() { + if (hasRegistered) return; + hasRegistered = true; + Fields.ofClass(EventBus.class).getField(Fields.LookupType.DECLARED, "listeners", ConcurrentHashMap.class); + Object2ObjectMap classToModLookup = getModContainerPackageMap(); + for (ModContainer mod : Loader.instance().getModList()) { + SetMultimap annotations = asmDataTable.getAnnotationsFor(mod); + if (annotations == null) continue; + Set subscribers = annotations.get(EventBusSubscriber.class.getName()); + if (subscribers == null || subscribers.isEmpty()) continue; + + Set conditionAnnotations = annotations + .get(EventBusSubscriber.Condition.class.getName()); + + if (annotations.get(Mod.class.getName()).size() > 1) { + subscribers = subscribers.stream().filter( + data -> Objects.equals(getOwningModContainer(classToModLookup, data.getClassName()), mod)) + .collect(Collectors.toSet()); + conditionAnnotations = conditionAnnotations.stream().filter( + data -> Objects.equals(getOwningModContainer(classToModLookup, data.getClassName()), mod)) + .collect(Collectors.toSet()); + } + + Object2ObjectMap conditions = null; + if (!conditionAnnotations.isEmpty()) { + conditions = new Object2ObjectOpenHashMap<>(); + for (ASMDataTable.ASMData data : conditionAnnotations) { + conditions.put(data.getClassName(), data.getObjectName()); + } + } + + for (ASMDataTable.ASMData data : subscribers) { + try { + Class clazz = Class.forName(data.getClassName(), false, Loader.instance().getModClassLoader()); + + if (registeredClasses.contains(clazz.getName())) { + if (DEBUG) { + LOGGER.info("Skipping registration for {}, already registered", clazz.getSimpleName()); + } + continue; + } + + if (!isValidSide(clazz)) { + if (DEBUG) { + LOGGER.info( + "Skipping registration for {}, invalid side {}", + clazz.getSimpleName(), + FMLCommonHandler.instance().getSide()); + } + continue; + } + + if (conditions != null) { + if (!isConditionMet(clazz, conditions.get(data.getClassName()))) { + if (DEBUG) { + LOGGER.info("Skipping registration for {}, condition not met", clazz.getSimpleName()); + } + continue; + } + } + + register(clazz, mod); + } catch (ClassNotFoundException | IllegalAccessException e) { + if (DEBUG) LOGGER.error("Failed to load class for annotation", e); + } + } + } + } + + private static void register(Class target, ModContainer classOwner) { + Set> registeredEvents = new HashSet<>(); + for (Method method : target.getMethods()) { + if (!Modifier.isStatic(method.getModifiers()) || !method.isAnnotationPresent(SubscribeEvent.class)) { + continue; + } + Class[] parameterTypes = method.getParameterTypes(); + if (parameterTypes.length != 1 || !Event.class.isAssignableFrom(parameterTypes[0])) { + throw new IllegalArgumentException( + "Method " + method + + " has @SubscribeEvent annotation, but requires invalid parameters. " + + "Event handler methods must take a single parameter of type Event."); + } + + Class eventType = parameterTypes[0]; + if (!isEventSafeToRegister(eventType)) continue; + + if (registerEvent(eventType, target, method, classOwner)) { + if (DEBUG) { + LOGGER.info( + "Registered event handler for {} in class {} with owner {}", + eventType.getName(), + target.getName(), + classOwner.getModId()); + } + registeredEvents.add(eventType); + } + } + + registerOwner(target, classOwner, registeredEvents); + } + + private static boolean registerEvent(Class eventClass, Class target, Method method, ModContainer owner) { + try { + Constructor ctr = eventClass.getConstructor(); + ctr.setAccessible(true); + Event event = (Event) ctr.newInstance(); + StaticASMEventHandler listener = new StaticASMEventHandler(target, method, owner); + + boolean registered = false; + for (EventBusType bus : EventBusType.VALUES) { + if (bus.isRegistered(event.getClass()) || !bus.canRegister(event.getClass())) { + continue; + } + event.getListenerList().register(bus.busID, listener.getPriority(), listener); + bus.listeners.computeIfAbsent(target, k -> new ArrayList<>()).add(listener); + registered = true; + } + + return registered; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + private static void registerOwner(Class target, ModContainer owner, Set> registeredEvents) { + for (EventBusType bus : EventBusType.VALUES) { + for (Class event : registeredEvents) { + if (bus.canRegister(event)) { + registeredClasses.add(target.getName()); + bus.listenerOwners.put(target, owner); + break; + } + } + } + } + + private static boolean isValidSide(Class subscribedClass) throws IllegalAccessException { + Side currentSide = FMLCommonHandler.instance().getSide(); + if (currentSide.isClient()) return true; + + EventBusSubscriber subscriber = subscribedClass.getAnnotation(EventBusSubscriber.class); + Side[] sides = subscriber.side(); + if (sides.length == 1) { + return currentSide == sides[0]; + } + + return !subscribedClass.getName().contains("client"); + } + + private static boolean isEventSafeToRegister(Class eventClass) { + if (FMLCommonHandler.instance().getSide().isClient()) { + return true; + } + + if (validEventsForSide.containsKey(eventClass.getName())) { + return validEventsForSide.getBoolean(eventClass.getName()); + } + + try { + // noinspection ResultOfMethodCallIgnored + eventClass.getDeclaredFields(); + validEventsForSide.put(eventClass.getName(), true); + return true; + } catch (NoClassDefFoundError e) { + validEventsForSide.put(eventClass.getName(), false); + return false; + } + } + + private static Object2ObjectMap getModContainerPackageMap() { + Object2ObjectMap classToModContainer = new Object2ObjectOpenHashMap<>(); + for (ModContainer container : Loader.instance().getActiveModList()) { + Object modObject = container.getMod(); + if (modObject == null) continue; + Package modPackage = modObject.getClass().getPackage(); + if (modPackage == null) continue; + classToModContainer.put(modPackage.getName(), container); + } + return classToModContainer; + } + + private static @Nonnull ModContainer getOwningModContainer(Object2ObjectMap lookupMap, + String className) { + return lookupMap.object2ObjectEntrySet().stream().filter(e -> className.startsWith(e.getKey())) + .map(Map.Entry::getValue).findFirst().orElse(Loader.instance().getMinecraftModContainer()); + } + + private static boolean isConditionMet(@NotNull Class clazz, @Nullable String condition) { + if (condition == null) return true; + try { + if (condition.contains("()Z")) { + Method method = clazz.getDeclaredMethod(condition.substring(0, condition.indexOf("("))); + method.setAccessible(true); + return (boolean) method.invoke(null); + } + + Field field = clazz.getDeclaredField(condition); + field.setAccessible(true); + return field.getBoolean(null); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | NoSuchFieldException e) { + if (DEBUG) LOGGER.error("Failed to invoke condition {} for class {}", condition, clazz, e); + return false; + } + } + + public static void setDataTable(ASMDataTable dataTable) { + if (!Loader.instance().activeModContainer().getModId().equals(GTNHLib.MODID)) { + return; + } + asmDataTable = dataTable; + } + + private static boolean isFMLEvent(Class event) { + return event.getName().startsWith("cpw.mods.fml"); + } + + private static boolean isTerrainEvent(Class event) { + return event.getName().startsWith("net.minecraftforge.event.terraingen") && !isOreGenEvent(event); + } + + private static boolean isOreGenEvent(Class event) { + return OreGenEvent.class.isAssignableFrom(event); + } + + private static boolean isForgeEvent(Class event) { + return !isFMLEvent(event) && !isTerrainEvent(event) && !isOreGenEvent(event); + } +} diff --git a/src/main/java/com/gtnewhorizon/gtnhlib/eventbus/EventBusSubscriber.java b/src/main/java/com/gtnewhorizon/gtnhlib/eventbus/EventBusSubscriber.java new file mode 100644 index 0000000..6383ee1 --- /dev/null +++ b/src/main/java/com/gtnewhorizon/gtnhlib/eventbus/EventBusSubscriber.java @@ -0,0 +1,35 @@ +package com.gtnewhorizon.gtnhlib.eventbus; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import cpw.mods.fml.relauncher.Side; + +/** + * Annotation to mark a class as an EventBus subscriber. Classes annotated with this will automatically be registered to + * listen for events. Registration will happen during the init phase.
+ * All methods annotated with {@link cpw.mods.fml.common.eventhandler.SubscribeEvent} are expected to be static. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface EventBusSubscriber { + + /** + * The {@link cpw.mods.fml.relauncher.Side} that this subscriber should be registered on. Will default to both sides + * if not specified. + */ + Side[] side() default { Side.CLIENT, Side.SERVER }; + + /** + * Can be applied to a boolean field/method in the annotated class that provides a condition for registering the + * subscriber. It is expected that the field/method is static, returns a boolean, and takes no parameters.
+ * There is expected to be at most one condition for a class. Config values can be used as the return value since + * registration happens during init. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.FIELD, ElementType.METHOD }) + @interface Condition {} + +} diff --git a/src/main/java/com/gtnewhorizon/gtnhlib/eventbus/StaticASMEventHandler.java b/src/main/java/com/gtnewhorizon/gtnhlib/eventbus/StaticASMEventHandler.java new file mode 100644 index 0000000..f8d8b3a --- /dev/null +++ b/src/main/java/com/gtnewhorizon/gtnhlib/eventbus/StaticASMEventHandler.java @@ -0,0 +1,128 @@ +package com.gtnewhorizon.gtnhlib.eventbus; + +import static org.objectweb.asm.Opcodes.*; + +import java.lang.reflect.Method; +import java.util.HashMap; + +import org.apache.logging.log4j.ThreadContext; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Type; + +import com.google.common.collect.Maps; + +import cpw.mods.fml.common.ModContainer; +import cpw.mods.fml.common.eventhandler.Event; +import cpw.mods.fml.common.eventhandler.EventPriority; +import cpw.mods.fml.common.eventhandler.IEventListener; +import cpw.mods.fml.common.eventhandler.SubscribeEvent; + +public class StaticASMEventHandler implements IEventListener { + + private static int IDs = 0; + private static final String HANDLER_DESC = Type.getInternalName(IEventListener.class); + private static final String HANDLER_FUNC_DESC = Type + .getMethodDescriptor(IEventListener.class.getDeclaredMethods()[0]); + private static final ASMClassLoader LOADER = new ASMClassLoader(); + private static final HashMap> cache = Maps.newHashMap(); + private static final boolean GETCONTEXT = Boolean.parseBoolean(System.getProperty("fml.LogContext", "false")); + + private final IEventListener handler; + private final SubscribeEvent subInfo; + private final ModContainer owner; + private final String readable; + + StaticASMEventHandler(Object target, Method method, ModContainer owner) throws Exception { + this.owner = owner; + handler = (IEventListener) createWrapper(method).getDeclaredConstructor().newInstance(); + subInfo = method.getAnnotation(SubscribeEvent.class); + readable = "ASM: " + target + " " + method.getName() + Type.getMethodDescriptor(method); + } + + @Override + public void invoke(Event event) { + if (owner != null && GETCONTEXT) { + ThreadContext.put("mod", owner.getName()); + } else if (GETCONTEXT) { + ThreadContext.put("mod", ""); + } + if (handler != null) { + if (!event.isCancelable() || !event.isCanceled() || subInfo.receiveCanceled()) { + handler.invoke(event); + } + } + if (GETCONTEXT) ThreadContext.remove("mod"); + } + + public EventPriority getPriority() { + return subInfo.priority(); + } + + public Class createWrapper(Method callback) { + if (cache.containsKey(callback)) { + return cache.get(callback); + } + + ClassWriter cw = new ClassWriter(0); + MethodVisitor mv; + + String name = getUniqueName(callback); + String desc = name.replace('.', '/'); + String instType = Type.getInternalName(callback.getDeclaringClass()); + String eventType = Type.getInternalName(callback.getParameterTypes()[0]); + + cw.visit(V1_6, ACC_PUBLIC | ACC_SUPER, desc, null, "java/lang/Object", new String[] { HANDLER_DESC }); + + cw.visitSource(".dynamic", null); + { + mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false); + mv.visitInsn(RETURN); + mv.visitMaxs(2, 2); + mv.visitEnd(); + } + { + mv = cw.visitMethod(ACC_PUBLIC, "invoke", HANDLER_FUNC_DESC, null, null); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + mv.visitVarInsn(ALOAD, 1); + mv.visitTypeInsn(CHECKCAST, eventType); + mv.visitMethodInsn(INVOKESTATIC, instType, callback.getName(), Type.getMethodDescriptor(callback), false); + mv.visitInsn(RETURN); + mv.visitMaxs(2, 2); + mv.visitEnd(); + } + cw.visitEnd(); + Class ret = LOADER.define(name, cw.toByteArray()); + cache.put(callback, ret); + return ret; + } + + private String getUniqueName(Method callback) { + return String.format( + "%s_%d_%s_%s_%s", + getClass().getName(), + IDs++, + callback.getDeclaringClass().getSimpleName(), + callback.getName(), + callback.getParameterTypes()[0].getSimpleName()); + } + + private static class ASMClassLoader extends ClassLoader { + + private ASMClassLoader() { + super(ASMClassLoader.class.getClassLoader()); + } + + public Class define(String name, byte[] data) { + return defineClass(name, data, 0, data.length); + } + } + + public String toString() { + return readable; + } +} diff --git a/src/main/java/com/gtnewhorizon/gtnhlib/util/AnimatedTooltipHandler.java b/src/main/java/com/gtnewhorizon/gtnhlib/util/AnimatedTooltipHandler.java index 2d4d323..df0a4da 100644 --- a/src/main/java/com/gtnewhorizon/gtnhlib/util/AnimatedTooltipHandler.java +++ b/src/main/java/com/gtnewhorizon/gtnhlib/util/AnimatedTooltipHandler.java @@ -12,11 +12,14 @@ import net.minecraftforge.event.entity.player.ItemTooltipEvent; import net.minecraftforge.oredict.OreDictionary; +import com.gtnewhorizon.gtnhlib.eventbus.EventBusSubscriber; import com.gtnewhorizon.gtnhlib.util.map.ItemStackMap; import cpw.mods.fml.common.eventhandler.SubscribeEvent; import cpw.mods.fml.common.registry.GameRegistry; +import cpw.mods.fml.relauncher.Side; +@EventBusSubscriber(side = Side.CLIENT) public class AnimatedTooltipHandler { private static final Map> tooltipMap = new ItemStackMap<>(false); @@ -29,7 +32,7 @@ public class AnimatedTooltipHandler { /** * Helper method to concatenate multiple texts - * + * * @author glowredman */ @SafeVarargs @@ -45,7 +48,7 @@ public static Supplier chain(Supplier... parts) { /** * Helper method to create a static text - * + * * @author glowredman */ public static Supplier text(String text) { @@ -54,7 +57,7 @@ public static Supplier text(String text) { /** * Helper method to create a formatted and static text - * + * * @author glowredman */ public static Supplier text(String format, Object... args) { @@ -63,7 +66,7 @@ public static Supplier text(String format, Object... args) { /** * Helper method to create a translated and static text - * + * * @author glowredman */ public static Supplier translatedText(String translationKey) { @@ -72,7 +75,7 @@ public static Supplier translatedText(String translationKey) { /** * Helper method to create a translated, formatted and static text - * + * * @author glowredman */ public static Supplier translatedText(String translationKey, Object... args) { @@ -84,7 +87,7 @@ public static Supplier translatedText(String translationKey, Object... a *

* Taken and adapted from Avaritia - * + * * @param text The text to be animated * @param posstep How many steps {@code formattingArray} is shifted each {@code delay} * @param delay How many milliseconds are between each shift of {@code formattingArray} @@ -117,7 +120,7 @@ public static Supplier animatedText(String text, int posstep, int delay, *

* Taken and adapted from Avaritia - * + * * @param format The text to be formatted and animated * @param args The formatting arguments * @param posstep How many steps {@code formattingArray} is shifted each {@code delay} @@ -137,7 +140,7 @@ public static Supplier animatedText(String format, Object[] args, int po *

* Taken and adapted from Avaritia - * + * * @param translationKey The key used to look up the translation * @param posstep How many steps {@code formattingArray} is shifted each {@code delay} * @param delay How many milliseconds are between each shift of {@code formattingArray} @@ -156,7 +159,7 @@ public static Supplier translatedAnimatedText(String translationKey, int *

* Taken and adapted from Avaritia - * + * * @param translationKey The key used to look up the translation * @param args The formatting arguments * @param posstep How many steps {@code formattingArray} is shifted each {@code delay} @@ -179,7 +182,7 @@ public static Supplier translatedAnimatedText(String translationKey, Obj * Add {@code tooltip} to all items with {@code oredictName}.
* Note: The items must be registered to the {@link OreDictionary} when this method is called.
* Note: Items with equal registry name and meta but different NBT are considered equal. - * + * * @author glowredman */ public static void addOredictTooltip(String oredictName, Supplier tooltip) { @@ -193,7 +196,7 @@ public static void addOredictTooltip(String oredictName, Supplier toolti * Note: The item must be registered to the {@link GameRegistry} when this method is called.
* Note: Items with equal registry name and meta but different NBT are considered equal.
* Note: Using {@link OreDictionary#WILDCARD_VALUE} as {@code meta} is allowed. - * + * * @author glowredman */ public static void addItemTooltip(String modID, String registryName, int meta, Supplier tooltip) { @@ -206,7 +209,7 @@ public static void addItemTooltip(String modID, String registryName, int meta, S * Add {@code tooltip} to {@code item}.
* Note: Items with equal registry name and meta but different NBT are considered equal.
* Note: Using {@link OreDictionary#WILDCARD_VALUE} as meta is allowed. - * + * * @author glowredman */ public static void addItemTooltip(ItemStack item, Supplier tooltip) { @@ -215,7 +218,8 @@ public static void addItemTooltip(ItemStack item, Supplier tooltip) { } @SubscribeEvent - public void renderTooltip(ItemTooltipEvent event) { + @SuppressWarnings("unused") + public static void renderTooltip(ItemTooltipEvent event) { Supplier tooltip = tooltipMap.get(event.itemStack); if (tooltip == null) return; From 70921a7449580d36195e5264d6f3283e03d959cd Mon Sep 17 00:00:00 2001 From: Lyfts <127234178+Lyfts@users.noreply.github.com> Date: Mon, 7 Oct 2024 18:58:46 +0200 Subject: [PATCH 2/7] Cache events, safe method lookup, replace eventbus reflection with accessor --- .../gtnhlib/eventbus/AutoEventBus.java | 254 +++++++++++------- .../gtnewhorizon/gtnhlib/mixins/Mixins.java | 4 +- .../mixins/early/fml/EventBusAccessor.java | 25 ++ 3 files changed, 192 insertions(+), 91 deletions(-) create mode 100644 src/main/java/com/gtnewhorizon/gtnhlib/mixins/early/fml/EventBusAccessor.java diff --git a/src/main/java/com/gtnewhorizon/gtnhlib/eventbus/AutoEventBus.java b/src/main/java/com/gtnewhorizon/gtnhlib/eventbus/AutoEventBus.java index 9904f38..2b5ab75 100644 --- a/src/main/java/com/gtnewhorizon/gtnhlib/eventbus/AutoEventBus.java +++ b/src/main/java/com/gtnewhorizon/gtnhlib/eventbus/AutoEventBus.java @@ -1,11 +1,11 @@ package com.gtnewhorizon.gtnhlib.eventbus; -import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Objects; @@ -20,13 +20,15 @@ import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.event.terraingen.OreGenEvent; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.reflect.ConstructorUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.NotNull; import com.google.common.collect.SetMultimap; import com.gtnewhorizon.gtnhlib.GTNHLib; -import com.gtnewhorizon.gtnhlib.reflect.Fields; +import com.gtnewhorizon.gtnhlib.mixins.early.fml.EventBusAccessor; import cpw.mods.fml.common.FMLCommonHandler; import cpw.mods.fml.common.Loader; @@ -37,14 +39,15 @@ import cpw.mods.fml.common.eventhandler.EventBus; import cpw.mods.fml.common.eventhandler.IEventListener; import cpw.mods.fml.common.eventhandler.SubscribeEvent; -import cpw.mods.fml.relauncher.ReflectionHelper; import cpw.mods.fml.relauncher.Side; -import it.unimi.dsi.fastutil.objects.Object2BooleanMap; -import it.unimi.dsi.fastutil.objects.Object2BooleanOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectMaps; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import it.unimi.dsi.fastutil.objects.ObjectSet; +import it.unimi.dsi.fastutil.objects.ObjectSets; import lombok.AccessLevel; import lombok.NoArgsConstructor; @@ -53,11 +56,19 @@ public class AutoEventBus { private static final Boolean DEBUG = Boolean.getBoolean("gtnhlib.debug.eventbus"); private static final Logger LOGGER = LogManager.getLogger("GTNHLib EventBus"); - private static final Object2BooleanMap validEventsForSide = new Object2BooleanOpenHashMap<>(); + private static final DummyEvent INVALID_EVENT = new DummyEvent(); private static final ObjectSet registeredClasses = new ObjectOpenHashSet<>(); + private static final Object2ObjectMap classPathToModLookup; + private static final Object2ObjectMap, Event> eventCache = new Object2ObjectOpenHashMap<>(); + private static final Object2ObjectMap> eventClassCache = new Object2ObjectOpenHashMap<>(); + private static final Object2IntMap> eventsRegistered = new Object2IntOpenHashMap<>(); private static boolean hasRegistered; private static ASMDataTable asmDataTable; + static { + classPathToModLookup = getModContainerPackageMap(); + } + private enum EventBusType { FORGE(MinecraftForge.EVENT_BUS, AutoEventBus::isForgeEvent), @@ -73,12 +84,12 @@ private enum EventBusType { EventBusType(EventBus instance, Predicate> canRegister) { this.canRegister = canRegister; - this.listeners = ReflectionHelper.getPrivateValue(EventBus.class, instance, "listeners"); - this.listenerOwners = ReflectionHelper.getPrivateValue(EventBus.class, instance, "listenerOwners"); - this.busID = ReflectionHelper.getPrivateValue(EventBus.class, instance, "busID"); + EventBusAccessor accessor = (EventBusAccessor) instance; + this.listeners = accessor.getListeners(); + this.listenerOwners = accessor.getListenerOwners(); + this.busID = accessor.getBusID(); } - @SuppressWarnings("BooleanMethodIsAlwaysInverted") private boolean canRegister(Class clazz) { return canRegister.test(clazz); } @@ -91,26 +102,15 @@ private boolean isRegistered(Class clazz) { public static void registerSubscribers() { if (hasRegistered) return; hasRegistered = true; - Fields.ofClass(EventBus.class).getField(Fields.LookupType.DECLARED, "listeners", ConcurrentHashMap.class); - Object2ObjectMap classToModLookup = getModContainerPackageMap(); for (ModContainer mod : Loader.instance().getModList()) { SetMultimap annotations = asmDataTable.getAnnotationsFor(mod); if (annotations == null) continue; - Set subscribers = annotations.get(EventBusSubscriber.class.getName()); - if (subscribers == null || subscribers.isEmpty()) continue; - - Set conditionAnnotations = annotations - .get(EventBusSubscriber.Condition.class.getName()); - - if (annotations.get(Mod.class.getName()).size() > 1) { - subscribers = subscribers.stream().filter( - data -> Objects.equals(getOwningModContainer(classToModLookup, data.getClassName()), mod)) - .collect(Collectors.toSet()); - conditionAnnotations = conditionAnnotations.stream().filter( - data -> Objects.equals(getOwningModContainer(classToModLookup, data.getClassName()), mod)) - .collect(Collectors.toSet()); - } - + Set subscribers = getOwningModAnnotation(annotations, mod, EventBusSubscriber.class); + if (subscribers.isEmpty()) continue; + Set conditionAnnotations = getOwningModAnnotation( + annotations, + mod, + EventBusSubscriber.Condition.class); Object2ObjectMap conditions = null; if (!conditionAnnotations.isEmpty()) { conditions = new Object2ObjectOpenHashMap<>(); @@ -119,6 +119,7 @@ public static void registerSubscribers() { } } + Object2ObjectMap> methods = getMethodsForMod(annotations, mod); for (ASMDataTable.ASMData data : subscribers) { try { Class clazz = Class.forName(data.getClassName(), false, Loader.instance().getModClassLoader()); @@ -148,31 +149,23 @@ public static void registerSubscribers() { continue; } } - - register(clazz, mod); + ObjectSet methodsForClass = getMethodsToSubscribe(clazz, methods.get(data.getClassName())); + register(clazz, mod, methodsForClass); } catch (ClassNotFoundException | IllegalAccessException e) { if (DEBUG) LOGGER.error("Failed to load class for annotation", e); } } } + if (DEBUG) { + printFailedEvents(); + printRegisteredEvents(); + } } - private static void register(Class target, ModContainer classOwner) { + private static void register(Class target, ModContainer classOwner, ObjectSet methods) { Set> registeredEvents = new HashSet<>(); - for (Method method : target.getMethods()) { - if (!Modifier.isStatic(method.getModifiers()) || !method.isAnnotationPresent(SubscribeEvent.class)) { - continue; - } - Class[] parameterTypes = method.getParameterTypes(); - if (parameterTypes.length != 1 || !Event.class.isAssignableFrom(parameterTypes[0])) { - throw new IllegalArgumentException( - "Method " + method - + " has @SubscribeEvent annotation, but requires invalid parameters. " - + "Event handler methods must take a single parameter of type Event."); - } - - Class eventType = parameterTypes[0]; - if (!isEventSafeToRegister(eventType)) continue; + for (Method method : methods) { + Class eventType = method.getParameterTypes()[0]; if (registerEvent(eventType, target, method, classOwner)) { if (DEBUG) { @@ -191,19 +184,22 @@ private static void register(Class target, ModContainer classOwner) { private static boolean registerEvent(Class eventClass, Class target, Method method, ModContainer owner) { try { - Constructor ctr = eventClass.getConstructor(); - ctr.setAccessible(true); - Event event = (Event) ctr.newInstance(); - StaticASMEventHandler listener = new StaticASMEventHandler(target, method, owner); + Event event = getCachedEvent(eventClass); + if (INVALID_EVENT.equals(event)) return false; + StaticASMEventHandler listener = new StaticASMEventHandler(target, method, owner); boolean registered = false; for (EventBusType bus : EventBusType.VALUES) { - if (bus.isRegistered(event.getClass()) || !bus.canRegister(event.getClass())) { + if (bus.isRegistered(eventClass) || !bus.canRegister(eventClass)) { continue; } event.getListenerList().register(bus.busID, listener.getPriority(), listener); bus.listeners.computeIfAbsent(target, k -> new ArrayList<>()).add(listener); registered = true; + + if(DEBUG) { + eventsRegistered.merge(eventClass, 1, Integer::sum); + } } return registered; @@ -225,37 +221,92 @@ private static void registerOwner(Class target, ModContainer owner, Set subscribedClass) throws IllegalAccessException { - Side currentSide = FMLCommonHandler.instance().getSide(); - if (currentSide.isClient()) return true; + private static @Nonnull Event getCachedEvent(Class eventClass) { + return eventCache.computeIfAbsent(eventClass, e -> { + try { + return (Event) ConstructorUtils.invokeConstructor(eventClass); + } catch (NoClassDefFoundError | ExceptionInInitializerError | Exception ex) { + return INVALID_EVENT; + } + }); + } - EventBusSubscriber subscriber = subscribedClass.getAnnotation(EventBusSubscriber.class); - Side[] sides = subscriber.side(); - if (sides.length == 1) { - return currentSide == sides[0]; - } + private static boolean isConditionMet(@NotNull Class clazz, @Nullable String condition) { + if (condition == null) return true; + try { + if (condition.contains("()Z")) { + Method method = clazz.getDeclaredMethod(condition.substring(0, condition.indexOf("("))); + method.setAccessible(true); + return (boolean) method.invoke(null); + } - return !subscribedClass.getName().contains("client"); + Field field = clazz.getDeclaredField(condition); + field.setAccessible(true); + return field.getBoolean(null); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | NoSuchFieldException e) { + if (DEBUG) LOGGER.error("Failed to invoke condition {} for class {}", condition, clazz, e); + return false; + } } - private static boolean isEventSafeToRegister(Class eventClass) { - if (FMLCommonHandler.instance().getSide().isClient()) { - return true; + private static ObjectSet getMethodsToSubscribe(Class clazz, ObjectSet methodDescs) { + if (methodDescs.isEmpty()) return ObjectSets.emptySet(); + ObjectSet methodsToSub = new ObjectOpenHashSet<>(); + for (String methodDesc : methodDescs) { + Class eventClass = getCachedEventClass(methodDesc); + if (INVALID_EVENT.getClass().equals(eventClass)) continue; + String methodName = methodDesc.substring(0, methodDesc.indexOf("(")); + try { + Method method = clazz.getMethod(methodName, eventClass); + if (!Modifier.isStatic(method.getModifiers()) || !method.isAnnotationPresent(SubscribeEvent.class)) { + continue; + } + Class[] parameterTypes = method.getParameterTypes(); + if (parameterTypes.length != 1 || !Event.class.isAssignableFrom(parameterTypes[0])) { + throw new IllegalArgumentException( + "Method " + method + + " has @SubscribeEvent annotation, but requires invalid parameters. " + + "Event handler methods must take a single parameter of type Event."); + } + methodsToSub.add(method); + } catch (NoSuchMethodException ignored) { + if (DEBUG) { + LOGGER.error("Failed to register method {} for class {}", methodName, clazz); + } + } + return methodsToSub; } + return ObjectSets.emptySet(); + } - if (validEventsForSide.containsKey(eventClass.getName())) { - return validEventsForSide.getBoolean(eventClass.getName()); + private static Object2ObjectMap> getMethodsForMod( + SetMultimap annotationTable, ModContainer mod) { + Set methodAnnotations = getOwningModAnnotation( + annotationTable, + mod, + SubscribeEvent.class); + if (methodAnnotations.isEmpty()) return Object2ObjectMaps.emptyMap(); + + Object2ObjectMap> methods = new Object2ObjectOpenHashMap<>(); + for (ASMDataTable.ASMData data : methodAnnotations) { + methods.computeIfAbsent(data.getClassName(), k -> new ObjectOpenHashSet<>()).add(data.getObjectName()); } + return methods; + } - try { - // noinspection ResultOfMethodCallIgnored - eventClass.getDeclaredFields(); - validEventsForSide.put(eventClass.getName(), true); - return true; - } catch (NoClassDefFoundError e) { - validEventsForSide.put(eventClass.getName(), false); - return false; - } + private static @Nonnull Class getCachedEventClass(String methodDesc) { + String className = methodDesc.substring(methodDesc.indexOf("(L") + 2, methodDesc.indexOf(";)")) + .replaceAll("/", "."); + return eventClassCache.computeIfAbsent(className, a -> { + try { + return Class.forName(className); + } catch (NoClassDefFoundError | ClassNotFoundException e) { + if (DEBUG) { + LOGGER.error("Failed to register method {} for class {}", methodDesc, className, e); + } + return INVALID_EVENT.getClass(); + } + }); } private static Object2ObjectMap getModContainerPackageMap() { @@ -270,28 +321,34 @@ private static Object2ObjectMap getModContainerPackageMap( return classToModContainer; } - private static @Nonnull ModContainer getOwningModContainer(Object2ObjectMap lookupMap, - String className) { - return lookupMap.object2ObjectEntrySet().stream().filter(e -> className.startsWith(e.getKey())) - .map(Map.Entry::getValue).findFirst().orElse(Loader.instance().getMinecraftModContainer()); + private static @Nonnull ModContainer getOwningModContainer(String className) { + return classPathToModLookup.object2ObjectEntrySet().stream().filter(e -> className.startsWith(e.getKey())) + .map(Map.Entry::getValue).findFirst().orElse(Loader.instance().getMinecraftModContainer()); } - private static boolean isConditionMet(@NotNull Class clazz, @Nullable String condition) { - if (condition == null) return true; - try { - if (condition.contains("()Z")) { - Method method = clazz.getDeclaredMethod(condition.substring(0, condition.indexOf("("))); - method.setAccessible(true); - return (boolean) method.invoke(null); - } + private static @Nonnull Set getOwningModAnnotation( + SetMultimap dataTable, ModContainer mod, Class annotationClass) { + Set annotationData = dataTable.get(annotationClass.getName()); + if (annotationData == null || annotationData.isEmpty()) return Collections.emptySet(); + if (dataTable.get(Mod.class.getName()).size() > 1) { + annotationData = annotationData.stream() + .filter(data -> Objects.equals(getOwningModContainer(data.getClassName()), mod)) + .collect(Collectors.toSet()); + } + return annotationData; + } - Field field = clazz.getDeclaredField(condition); - field.setAccessible(true); - return field.getBoolean(null); - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | NoSuchFieldException e) { - if (DEBUG) LOGGER.error("Failed to invoke condition {} for class {}", condition, clazz, e); - return false; + private static boolean isValidSide(Class subscribedClass) throws IllegalAccessException { + Side currentSide = FMLCommonHandler.instance().getSide(); + if (currentSide.isClient()) return true; + + EventBusSubscriber subscriber = subscribedClass.getAnnotation(EventBusSubscriber.class); + Side[] sides = subscriber.side(); + if (sides.length == 1) { + return currentSide == sides[0]; } + + return !StringUtils.containsIgnoreCase(subscribedClass.getName(), "client"); } public static void setDataTable(ASMDataTable dataTable) { @@ -301,6 +358,21 @@ public static void setDataTable(ASMDataTable dataTable) { asmDataTable = dataTable; } + private static void printFailedEvents() { + Side side = FMLCommonHandler.instance().getSide(); + for (Map.Entry, Event> entry : eventCache.object2ObjectEntrySet()) { + if (entry.getValue() == null) { + LOGGER.error("Failed to register event {} for side {}", entry.getKey(), side); + } + } + } + + private static void printRegisteredEvents() { + for (Object2IntMap.Entry> entry : eventsRegistered.object2IntEntrySet()) { + LOGGER.info("Event {} was registered {} times", entry.getKey().getSimpleName(), entry.getIntValue()); + } + } + private static boolean isFMLEvent(Class event) { return event.getName().startsWith("cpw.mods.fml"); } @@ -316,4 +388,6 @@ private static boolean isOreGenEvent(Class event) { private static boolean isForgeEvent(Class event) { return !isFMLEvent(event) && !isTerrainEvent(event) && !isOreGenEvent(event); } + + private static class DummyEvent extends Event {} } diff --git a/src/main/java/com/gtnewhorizon/gtnhlib/mixins/Mixins.java b/src/main/java/com/gtnewhorizon/gtnhlib/mixins/Mixins.java index 29e5c17..9d9949a 100644 --- a/src/main/java/com/gtnewhorizon/gtnhlib/mixins/Mixins.java +++ b/src/main/java/com/gtnewhorizon/gtnhlib/mixins/Mixins.java @@ -17,7 +17,9 @@ public enum Mixins { TESSELLATOR(new Builder("Sodium").addTargetedMod(TargetedMod.VANILLA).setSide(Side.CLIENT).setPhase(Phase.EARLY) .setApplyIf(() -> true).addMixinClasses("MixinTessellator")), WAVEFRONT_VBO(new Builder("WavefrontObject").addTargetedMod(TargetedMod.VANILLA).setSide(Side.CLIENT) - .setPhase(Phase.EARLY).setApplyIf(() -> true).addMixinClasses("MixinWavefrontObject")),; + .setPhase(Phase.EARLY).setApplyIf(() -> true).addMixinClasses("MixinWavefrontObject")), + EVENT_BUS_ACCESSOR(new Builder("EventBusAccessor").addTargetedMod(TargetedMod.VANILLA).setSide(Side.BOTH) + .setPhase(Phase.EARLY).addMixinClasses("fml.EventBusAccessor")),; private final List mixinClasses; private final Supplier applyIf; diff --git a/src/main/java/com/gtnewhorizon/gtnhlib/mixins/early/fml/EventBusAccessor.java b/src/main/java/com/gtnewhorizon/gtnhlib/mixins/early/fml/EventBusAccessor.java new file mode 100644 index 0000000..8246f3e --- /dev/null +++ b/src/main/java/com/gtnewhorizon/gtnhlib/mixins/early/fml/EventBusAccessor.java @@ -0,0 +1,25 @@ +package com.gtnewhorizon.gtnhlib.mixins.early.fml; + +import java.util.ArrayList; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import cpw.mods.fml.common.ModContainer; +import cpw.mods.fml.common.eventhandler.EventBus; +import cpw.mods.fml.common.eventhandler.IEventListener; + +@Mixin(value = EventBus.class, remap = false) +public interface EventBusAccessor { + + @Accessor + Map getListenerOwners(); + + @Accessor + ConcurrentHashMap> getListeners(); + + @Accessor + int getBusID(); +} From c0e479704bb06b3c5fe1a20850ffa3db3080a722 Mon Sep 17 00:00:00 2001 From: Lyfts <127234178+Lyfts@users.noreply.github.com> Date: Mon, 7 Oct 2024 19:20:01 +0200 Subject: [PATCH 3/7] ily spotless --- .../gtnewhorizon/gtnhlib/eventbus/AutoEventBus.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/gtnewhorizon/gtnhlib/eventbus/AutoEventBus.java b/src/main/java/com/gtnewhorizon/gtnhlib/eventbus/AutoEventBus.java index 2b5ab75..894952f 100644 --- a/src/main/java/com/gtnewhorizon/gtnhlib/eventbus/AutoEventBus.java +++ b/src/main/java/com/gtnewhorizon/gtnhlib/eventbus/AutoEventBus.java @@ -197,7 +197,7 @@ private static boolean registerEvent(Class eventClass, Class target, Metho bus.listeners.computeIfAbsent(target, k -> new ArrayList<>()).add(listener); registered = true; - if(DEBUG) { + if (DEBUG) { eventsRegistered.merge(eventClass, 1, Integer::sum); } } @@ -323,17 +323,17 @@ private static Object2ObjectMap getModContainerPackageMap( private static @Nonnull ModContainer getOwningModContainer(String className) { return classPathToModLookup.object2ObjectEntrySet().stream().filter(e -> className.startsWith(e.getKey())) - .map(Map.Entry::getValue).findFirst().orElse(Loader.instance().getMinecraftModContainer()); + .map(Map.Entry::getValue).findFirst().orElse(Loader.instance().getMinecraftModContainer()); } private static @Nonnull Set getOwningModAnnotation( - SetMultimap dataTable, ModContainer mod, Class annotationClass) { + SetMultimap dataTable, ModContainer mod, Class annotationClass) { Set annotationData = dataTable.get(annotationClass.getName()); if (annotationData == null || annotationData.isEmpty()) return Collections.emptySet(); if (dataTable.get(Mod.class.getName()).size() > 1) { annotationData = annotationData.stream() - .filter(data -> Objects.equals(getOwningModContainer(data.getClassName()), mod)) - .collect(Collectors.toSet()); + .filter(data -> Objects.equals(getOwningModContainer(data.getClassName()), mod)) + .collect(Collectors.toSet()); } return annotationData; } @@ -389,5 +389,6 @@ private static boolean isForgeEvent(Class event) { return !isFMLEvent(event) && !isTerrainEvent(event) && !isOreGenEvent(event); } - private static class DummyEvent extends Event {} + private static class DummyEvent extends Event { + } } From 32f24a77fa04a3a98d15d452d5e3d9304808c241 Mon Sep 17 00:00:00 2001 From: Lyfts <127234178+Lyfts@users.noreply.github.com> Date: Wed, 9 Oct 2024 18:45:32 +0200 Subject: [PATCH 4/7] actual completely safe event registration this time with less reflection --- .../com/gtnewhorizon/gtnhlib/CommonProxy.java | 2 +- .../gtnhlib/core/GTNHLibCore.java | 39 ++- .../transformer/EventBusSubTransformer.java | 148 +++++++++ .../gtnhlib/eventbus/AutoEventBus.java | 314 +++++------------- .../gtnhlib/eventbus/EventBusUtil.java | 31 ++ .../gtnhlib/eventbus/MethodInfo.java | 19 ++ .../eventbus/StaticASMEventHandler.java | 55 ++- .../gtnewhorizon/gtnhlib/mixins/Mixins.java | 2 +- .../mixins/early/fml/EnumHolderAccessor.java | 13 + 9 files changed, 361 insertions(+), 262 deletions(-) create mode 100644 src/main/java/com/gtnewhorizon/gtnhlib/core/transformer/EventBusSubTransformer.java create mode 100644 src/main/java/com/gtnewhorizon/gtnhlib/eventbus/EventBusUtil.java create mode 100644 src/main/java/com/gtnewhorizon/gtnhlib/eventbus/MethodInfo.java create mode 100644 src/main/java/com/gtnewhorizon/gtnhlib/mixins/early/fml/EnumHolderAccessor.java diff --git a/src/main/java/com/gtnewhorizon/gtnhlib/CommonProxy.java b/src/main/java/com/gtnewhorizon/gtnhlib/CommonProxy.java index bc12a6d..a288fed 100644 --- a/src/main/java/com/gtnewhorizon/gtnhlib/CommonProxy.java +++ b/src/main/java/com/gtnewhorizon/gtnhlib/CommonProxy.java @@ -21,7 +21,7 @@ public class CommonProxy { public void preInit(FMLPreInitializationEvent event) { - AutoEventBus.setDataTable(event.getAsmData()); + AutoEventBus.init(event.getAsmData()); GTNHLib.info("GTNHLib version " + Tags.VERSION + " loaded."); } diff --git a/src/main/java/com/gtnewhorizon/gtnhlib/core/GTNHLibCore.java b/src/main/java/com/gtnewhorizon/gtnhlib/core/GTNHLibCore.java index fd5039c..1a0dc42 100644 --- a/src/main/java/com/gtnewhorizon/gtnhlib/core/GTNHLibCore.java +++ b/src/main/java/com/gtnewhorizon/gtnhlib/core/GTNHLibCore.java @@ -9,9 +9,17 @@ import org.spongepowered.asm.launch.GlobalProperties; import org.spongepowered.asm.service.mojang.MixinServiceLaunchWrapper; +import com.google.common.eventbus.EventBus; +import com.google.common.eventbus.Subscribe; +import com.gtnewhorizon.gtnhlib.Tags; +import com.gtnewhorizon.gtnhlib.core.transformer.EventBusSubTransformer; import com.gtnewhorizon.gtnhlib.mixins.Mixins; import com.gtnewhorizon.gtnhmixins.IEarlyMixinLoader; +import cpw.mods.fml.common.DummyModContainer; +import cpw.mods.fml.common.LoadController; +import cpw.mods.fml.common.ModMetadata; +import cpw.mods.fml.common.event.FMLConstructionEvent; import cpw.mods.fml.relauncher.FMLLaunchHandler; import cpw.mods.fml.relauncher.IFMLLoadingPlugin; @@ -20,26 +28,38 @@ "com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager", "com.gtnewhorizon.gtnhlib.client.renderer.CapturingTessellator" }) @IFMLLoadingPlugin.SortingIndex(-1000) -public class GTNHLibCore implements IFMLLoadingPlugin, IEarlyMixinLoader { +public class GTNHLibCore extends DummyModContainer implements IFMLLoadingPlugin, IEarlyMixinLoader { + + public static final String[] DEFAULT_TRANSFORMERS = new String[] { + "com.gtnewhorizon.gtnhlib.core.transformer.EventBusSubTransformer" }; + + public GTNHLibCore() { + super(new ModMetadata()); + ModMetadata md = getMetadata(); + md.autogenerated = true; + md.modId = md.name = "GTNHLib Core"; + md.parent = "gtnhlib"; + md.version = Tags.VERSION; + } @Override public String[] getASMTransformerClass() { if (!FMLLaunchHandler.side().isClient() || Launch.blackboard.getOrDefault("gtnhlib.rfbPluginLoaded", Boolean.FALSE) == Boolean.TRUE) { // Don't need any transformers if we're not on the client, or the RFB Plugin was loaded - return new String[0]; + return DEFAULT_TRANSFORMERS; } // Directly add this to the MixinServiceLaunchWrapper tweaker's list of Tweak Classes List mixinTweakClasses = GlobalProperties.get(MixinServiceLaunchWrapper.BLACKBOARD_KEY_TWEAKCLASSES); if (mixinTweakClasses != null) { mixinTweakClasses.add(MixinCompatHackTweaker.class.getName()); } - return new String[0]; + return DEFAULT_TRANSFORMERS; } @Override public String getModContainerClass() { - return null; + return "com.gtnewhorizon.gtnhlib.core.GTNHLibCore"; } @Override @@ -64,4 +84,15 @@ public String getMixinConfig() { public List getMixins(Set loadedCoreMods) { return Mixins.getEarlyMixins(loadedCoreMods); } + + @Override + public boolean registerBus(EventBus bus, LoadController controller) { + bus.register(this); + return true; + } + + @Subscribe + public void construct(FMLConstructionEvent event) { + EventBusSubTransformer.harvestData(event.getASMHarvestedData()); + } } diff --git a/src/main/java/com/gtnewhorizon/gtnhlib/core/transformer/EventBusSubTransformer.java b/src/main/java/com/gtnewhorizon/gtnhlib/core/transformer/EventBusSubTransformer.java new file mode 100644 index 0000000..13815e9 --- /dev/null +++ b/src/main/java/com/gtnewhorizon/gtnhlib/core/transformer/EventBusSubTransformer.java @@ -0,0 +1,148 @@ +package com.gtnewhorizon.gtnhlib.core.transformer; + +import static com.gtnewhorizon.gtnhlib.eventbus.EventBusUtil.DEBUG_EVENT_BUS; + +import java.util.Arrays; +import java.util.List; + +import net.minecraft.launchwrapper.IClassTransformer; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AnnotationNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodNode; + +import com.gtnewhorizon.gtnhlib.eventbus.EventBusSubscriber; +import com.gtnewhorizon.gtnhlib.eventbus.EventBusUtil; +import com.gtnewhorizon.gtnhlib.eventbus.MethodInfo; + +import cpw.mods.fml.common.Optional; +import cpw.mods.fml.common.discovery.ASMDataTable; +import cpw.mods.fml.common.eventhandler.EventPriority; +import cpw.mods.fml.common.eventhandler.SubscribeEvent; +import cpw.mods.fml.relauncher.FMLLaunchHandler; +import cpw.mods.fml.relauncher.SideOnly; +import it.unimi.dsi.fastutil.objects.Object2ObjectMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectMaps; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import it.unimi.dsi.fastutil.objects.ObjectSet; + +public class EventBusSubTransformer implements IClassTransformer { + + private static final Logger LOGGER = LogManager.getLogger("GTNHLib|EventBusSubTransformer"); + private static final String OPTIONAL_DESC = Type.getDescriptor(Optional.Method.class); + private static final String SIDEONLY_DESC = Type.getDescriptor(SideOnly.class); + private static final String SUBSCRIBE_DESC = Type.getDescriptor(SubscribeEvent.class); + private static final String CONDITION_DESC = Type.getDescriptor(EventBusSubscriber.Condition.class); + private static final List ANNOTATIONS = Arrays + .asList(OPTIONAL_DESC, SIDEONLY_DESC, SUBSCRIBE_DESC, CONDITION_DESC); + private static final String CURRENT_SIDE = FMLLaunchHandler.side().name(); + private static ObjectSet classesToVisit; + + public static void harvestData(ASMDataTable table) { + classesToVisit = EventBusUtil.getClassesToVisit(); + for (ASMDataTable.ASMData data : table.getAll(EventBusSubscriber.class.getName())) { + classesToVisit.add(data.getClassName()); + } + } + + @Override + public byte[] transform(String name, String transformedName, byte[] basicClass) { + if (basicClass == null) return null; + + // It's either too early or this class isn't an @EventBusSubscriber + if (classesToVisit == null || !classesToVisit.contains(transformedName)) { + return basicClass; + } + + final ClassReader cr = new ClassReader(basicClass); + final ClassNode cn = new ClassNode(); + cr.accept(cn, ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG | ClassReader.SKIP_CODE); + + // Processing all of this from the ASMDataTable is way too slow + for (MethodNode mn : cn.methods) { + Object2ObjectMap usableAnnotations = getUsableAnnotations(mn.visibleAnnotations); + if (usableAnnotations.isEmpty()) continue; + + if (!matchesSide(usableAnnotations.get(SIDEONLY_DESC))) { + if (DEBUG_EVENT_BUS) { + LOGGER.info("Skipping method {} due to side mismatch", transformedName); + } + continue; + } + + if (usableAnnotations.containsKey(CONDITION_DESC)) { + if (mn.desc.equals("()Z")) { + EventBusUtil.getConditionsToCheck().put(transformedName, mn.name + mn.desc); + } + continue; + } + + AnnotationNode subscribe = usableAnnotations.get(SUBSCRIBE_DESC); + if (subscribe == null) continue; + Object[] subscribeInfo = getSubscribeInfo(subscribe); + MethodInfo methodInfo = new MethodInfo( + transformedName, + mn.name, + mn.desc, + (Boolean) subscribeInfo[0], + (EventPriority) subscribeInfo[1]); + AnnotationNode optional = usableAnnotations.get(OPTIONAL_DESC); + if (optional != null) { + List values = optional.values; + methodInfo.setOptionalMod((String) values.get(1)); + } + + EventBusUtil.getMethodsToSubscribe().computeIfAbsent(transformedName, k -> new ObjectOpenHashSet<>()) + .add(methodInfo); + } + + return basicClass; + } + + private static Object2ObjectMap getUsableAnnotations(List annotations) { + if (annotations == null) return Object2ObjectMaps.emptyMap(); + Object2ObjectMap usable = new Object2ObjectOpenHashMap<>(); + for (AnnotationNode ann : annotations) { + if (ANNOTATIONS.contains(ann.desc)) { + usable.put(ann.desc, ann); + } + } + return usable; + } + + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + private boolean matchesSide(AnnotationNode side) { + if (side == null) return true; + for (int x = 0; x < side.values.size() - 1; x += 2) { + Object key = side.values.get(x); + Object value = side.values.get(x + 1); + if (!(key instanceof String) || !key.equals("value")) continue; + if (!(value instanceof String[]array)) continue; + if (!array[1].equals(CURRENT_SIDE)) { + return false; + } + } + return true; + } + + private static Object[] getSubscribeInfo(AnnotationNode annotation) { + Object[] info = { false, EventPriority.NORMAL }; + if (annotation.values == null) return info; + for (int i = 0; i < annotation.values.size() - 1; i += 2) { + Object key = annotation.values.get(i); + Object value = annotation.values.get(i + 1); + if (!(key instanceof String)) continue; + if (key.equals("receiveCanceled")) { + info[0] = value; + } else if (key.equals("priority") && value instanceof String[]array) { + info[1] = EventPriority.valueOf(array[1]); + } + } + return info; + } +} diff --git a/src/main/java/com/gtnewhorizon/gtnhlib/eventbus/AutoEventBus.java b/src/main/java/com/gtnewhorizon/gtnhlib/eventbus/AutoEventBus.java index 894952f..faffee8 100644 --- a/src/main/java/com/gtnewhorizon/gtnhlib/eventbus/AutoEventBus.java +++ b/src/main/java/com/gtnewhorizon/gtnhlib/eventbus/AutoEventBus.java @@ -1,18 +1,15 @@ package com.gtnewhorizon.gtnhlib.eventbus; +import static com.gtnewhorizon.gtnhlib.eventbus.EventBusUtil.DEBUG_EVENT_BUS; + import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.lang.reflect.Modifier; import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; import java.util.Map; -import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Predicate; -import java.util.stream.Collectors; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -26,48 +23,38 @@ import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.NotNull; -import com.google.common.collect.SetMultimap; -import com.gtnewhorizon.gtnhlib.GTNHLib; +import com.gtnewhorizon.gtnhlib.mixins.early.fml.EnumHolderAccessor; import com.gtnewhorizon.gtnhlib.mixins.early.fml.EventBusAccessor; import cpw.mods.fml.common.FMLCommonHandler; import cpw.mods.fml.common.Loader; -import cpw.mods.fml.common.Mod; import cpw.mods.fml.common.ModContainer; import cpw.mods.fml.common.discovery.ASMDataTable; import cpw.mods.fml.common.eventhandler.Event; import cpw.mods.fml.common.eventhandler.EventBus; import cpw.mods.fml.common.eventhandler.IEventListener; -import cpw.mods.fml.common.eventhandler.SubscribeEvent; import cpw.mods.fml.relauncher.Side; -import it.unimi.dsi.fastutil.objects.Object2IntMap; -import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import cpw.mods.fml.relauncher.SideOnly; +import it.unimi.dsi.fastutil.objects.Object2BooleanMap; +import it.unimi.dsi.fastutil.objects.Object2BooleanOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectMap; -import it.unimi.dsi.fastutil.objects.Object2ObjectMaps; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import it.unimi.dsi.fastutil.objects.ObjectSet; -import it.unimi.dsi.fastutil.objects.ObjectSets; import lombok.AccessLevel; import lombok.NoArgsConstructor; @NoArgsConstructor(access = AccessLevel.PRIVATE) public class AutoEventBus { - private static final Boolean DEBUG = Boolean.getBoolean("gtnhlib.debug.eventbus"); private static final Logger LOGGER = LogManager.getLogger("GTNHLib EventBus"); private static final DummyEvent INVALID_EVENT = new DummyEvent(); - private static final ObjectSet registeredClasses = new ObjectOpenHashSet<>(); - private static final Object2ObjectMap classPathToModLookup; - private static final Object2ObjectMap, Event> eventCache = new Object2ObjectOpenHashMap<>(); - private static final Object2ObjectMap> eventClassCache = new Object2ObjectOpenHashMap<>(); - private static final Object2IntMap> eventsRegistered = new Object2IntOpenHashMap<>(); - private static boolean hasRegistered; - private static ASMDataTable asmDataTable; + private static final Object2ObjectMap> subscribers = new Object2ObjectOpenHashMap<>(); + private static final Object2ObjectMap classPathToModLookup = new Object2ObjectOpenHashMap<>(); + private static final Object2ObjectMap eventCache = new Object2ObjectOpenHashMap<>(); + private static final Object2BooleanMap optionalMods = new Object2BooleanOpenHashMap<>(); - static { - classPathToModLookup = getModContainerPackageMap(); - } + private static boolean hasRegistered; private enum EventBusType { @@ -93,46 +80,52 @@ private enum EventBusType { private boolean canRegister(Class clazz) { return canRegister.test(clazz); } + } + + public static void init(ASMDataTable dataTable) { + for (ModContainer container : Loader.instance().getActiveModList()) { + Object modObject = container.getMod(); + if (modObject == null) continue; + Package modPackage = modObject.getClass().getPackage(); + if (modPackage == null) continue; + classPathToModLookup.put(modPackage.getName(), container); + } - private boolean isRegistered(Class clazz) { - return listeners.containsKey(clazz); + for (String className : EventBusUtil.getClassesToVisit()) { + ModContainer mod = getOwningModContainer(className); + subscribers.computeIfAbsent(mod, k -> new ObjectOpenHashSet<>()).add(className); + } + + // Due to the way we are registering events, we need to filter invalid sides out manually. + // It's much faster to do it here than to load an invalid class and throw a couple exceptions. + Side currentSide = FMLCommonHandler.instance().getSide(); + for (Object2ObjectMap.Entry> entry : subscribers.object2ObjectEntrySet()) { + Set sideOnly = dataTable.getAnnotationsFor(entry.getKey()) + .get(SideOnly.class.getName()); + + for (ASMDataTable.ASMData data : sideOnly) { + if (!data.getObjectName().equals(data.getClassName())) { + continue; + } + + Map sideInfo = data.getAnnotationInfo(); + Side side = Side.valueOf(((EnumHolderAccessor) sideInfo.get("value")).getValue()); + if (side != currentSide) { + entry.getValue().remove(data.getClassName()); + } + } } } public static void registerSubscribers() { if (hasRegistered) return; hasRegistered = true; - for (ModContainer mod : Loader.instance().getModList()) { - SetMultimap annotations = asmDataTable.getAnnotationsFor(mod); - if (annotations == null) continue; - Set subscribers = getOwningModAnnotation(annotations, mod, EventBusSubscriber.class); - if (subscribers.isEmpty()) continue; - Set conditionAnnotations = getOwningModAnnotation( - annotations, - mod, - EventBusSubscriber.Condition.class); - Object2ObjectMap conditions = null; - if (!conditionAnnotations.isEmpty()) { - conditions = new Object2ObjectOpenHashMap<>(); - for (ASMDataTable.ASMData data : conditionAnnotations) { - conditions.put(data.getClassName(), data.getObjectName()); - } - } - - Object2ObjectMap> methods = getMethodsForMod(annotations, mod); - for (ASMDataTable.ASMData data : subscribers) { + for (Object2ObjectMap.Entry> entry : subscribers.object2ObjectEntrySet()) { + for (String className : entry.getValue()) { try { - Class clazz = Class.forName(data.getClassName(), false, Loader.instance().getModClassLoader()); - - if (registeredClasses.contains(clazz.getName())) { - if (DEBUG) { - LOGGER.info("Skipping registration for {}, already registered", clazz.getSimpleName()); - } - continue; - } - + Class clazz = Class.forName(className, false, Loader.instance().getModClassLoader()); if (!isValidSide(clazz)) { - if (DEBUG) { + if (DEBUG_EVENT_BUS) { LOGGER.info( "Skipping registration for {}, invalid side {}", clazz.getSimpleName(), @@ -141,91 +134,64 @@ public static void registerSubscribers() { continue; } - if (conditions != null) { - if (!isConditionMet(clazz, conditions.get(data.getClassName()))) { - if (DEBUG) { - LOGGER.info("Skipping registration for {}, condition not met", clazz.getSimpleName()); - } - continue; + String conditionToCheck = EventBusUtil.getConditionsToCheck().get(className); + if (conditionToCheck != null && !isConditionMet(clazz, conditionToCheck)) { + if (DEBUG_EVENT_BUS) { + LOGGER.info("Skipping registration for {}, condition not met", clazz.getSimpleName()); } + continue; } - ObjectSet methodsForClass = getMethodsToSubscribe(clazz, methods.get(data.getClassName())); - register(clazz, mod, methodsForClass); - } catch (ClassNotFoundException | IllegalAccessException e) { - if (DEBUG) LOGGER.error("Failed to load class for annotation", e); - } - } - } - if (DEBUG) { - printFailedEvents(); - printRegisteredEvents(); - } - } - private static void register(Class target, ModContainer classOwner, ObjectSet methods) { - Set> registeredEvents = new HashSet<>(); - for (Method method : methods) { - Class eventType = method.getParameterTypes()[0]; - - if (registerEvent(eventType, target, method, classOwner)) { - if (DEBUG) { - LOGGER.info( - "Registered event handler for {} in class {} with owner {}", - eventType.getName(), - target.getName(), - classOwner.getModId()); + ObjectSet methods = EventBusUtil.getMethodsToSubscribe().get(className); + if (methods == null || methods.isEmpty()) continue; + register(entry.getKey(), clazz, methods); + } catch (IllegalAccessException | ClassNotFoundException e) { + if (DEBUG_EVENT_BUS) LOGGER.error("Failed to load class {}", className, e); } - registeredEvents.add(eventType); } } - - registerOwner(target, classOwner, registeredEvents); } - private static boolean registerEvent(Class eventClass, Class target, Method method, ModContainer owner) { - try { - Event event = getCachedEvent(eventClass); - if (INVALID_EVENT.equals(event)) return false; - - StaticASMEventHandler listener = new StaticASMEventHandler(target, method, owner); - boolean registered = false; - for (EventBusType bus : EventBusType.VALUES) { - if (bus.isRegistered(eventClass) || !bus.canRegister(eventClass)) { - continue; + private static void register(ModContainer classOwner, Class target, ObjectSet methods) { + for (MethodInfo method : methods) { + try { + if (method.getOptionalMod() != null) { + if (!optionalMods.computeIfAbsent(method.getOptionalMod(), Loader::isModLoaded)) { + continue; + } } - event.getListenerList().register(bus.busID, listener.getPriority(), listener); - bus.listeners.computeIfAbsent(target, k -> new ArrayList<>()).add(listener); - registered = true; - if (DEBUG) { - eventsRegistered.merge(eventClass, 1, Integer::sum); - } - } + Event event = getCachedEvent(EventBusUtil.getParameterClassName(method.desc)); + if (INVALID_EVENT.equals(event)) continue; - return registered; - } catch (Exception e) { - e.printStackTrace(); - return false; - } - } + StaticASMEventHandler listener = new StaticASMEventHandler(classOwner, method); + for (EventBusType bus : EventBusType.VALUES) { + if (!bus.canRegister(event.getClass())) { + continue; + } + event.getListenerList().register(bus.busID, listener.getPriority(), listener); + bus.listenerOwners.putIfAbsent(target, classOwner); + bus.listeners.computeIfAbsent(target, k -> new ArrayList<>()).add(listener); - private static void registerOwner(Class target, ModContainer owner, Set> registeredEvents) { - for (EventBusType bus : EventBusType.VALUES) { - for (Class event : registeredEvents) { - if (bus.canRegister(event)) { - registeredClasses.add(target.getName()); - bus.listenerOwners.put(target, owner); - break; + if (DEBUG_EVENT_BUS) { + LOGGER.info("Registered event handler for {} on {}", event.getClass().getSimpleName(), bus); + } } + } catch (Exception e) { + if (DEBUG_EVENT_BUS) LOGGER.error("Failed to register event handler for {}", method.desc, e); } } } - private static @Nonnull Event getCachedEvent(Class eventClass) { + private static @Nonnull Event getCachedEvent(String eventClass) { return eventCache.computeIfAbsent(eventClass, e -> { try { - return (Event) ConstructorUtils.invokeConstructor(eventClass); + Class clazz = Class.forName(eventClass, false, Loader.instance().getModClassLoader()); + return (Event) ConstructorUtils.invokeConstructor(clazz); } catch (NoClassDefFoundError | ExceptionInInitializerError | Exception ex) { + // Event was likely for a mod that is not loaded or an invalid side. + // The subscribed method will never be invoked, so we can safely ignore it. + if (DEBUG_EVENT_BUS) LOGGER.error("Failed to create event instance for {}", eventClass, ex); return INVALID_EVENT; } }); @@ -244,100 +210,16 @@ private static boolean isConditionMet(@NotNull Class clazz, @Nullable String field.setAccessible(true); return field.getBoolean(null); } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | NoSuchFieldException e) { - if (DEBUG) LOGGER.error("Failed to invoke condition {} for class {}", condition, clazz, e); + if (DEBUG_EVENT_BUS) LOGGER.error("Failed to invoke condition {} for class {}", condition, clazz, e); return false; } } - private static ObjectSet getMethodsToSubscribe(Class clazz, ObjectSet methodDescs) { - if (methodDescs.isEmpty()) return ObjectSets.emptySet(); - ObjectSet methodsToSub = new ObjectOpenHashSet<>(); - for (String methodDesc : methodDescs) { - Class eventClass = getCachedEventClass(methodDesc); - if (INVALID_EVENT.getClass().equals(eventClass)) continue; - String methodName = methodDesc.substring(0, methodDesc.indexOf("(")); - try { - Method method = clazz.getMethod(methodName, eventClass); - if (!Modifier.isStatic(method.getModifiers()) || !method.isAnnotationPresent(SubscribeEvent.class)) { - continue; - } - Class[] parameterTypes = method.getParameterTypes(); - if (parameterTypes.length != 1 || !Event.class.isAssignableFrom(parameterTypes[0])) { - throw new IllegalArgumentException( - "Method " + method - + " has @SubscribeEvent annotation, but requires invalid parameters. " - + "Event handler methods must take a single parameter of type Event."); - } - methodsToSub.add(method); - } catch (NoSuchMethodException ignored) { - if (DEBUG) { - LOGGER.error("Failed to register method {} for class {}", methodName, clazz); - } - } - return methodsToSub; - } - return ObjectSets.emptySet(); - } - - private static Object2ObjectMap> getMethodsForMod( - SetMultimap annotationTable, ModContainer mod) { - Set methodAnnotations = getOwningModAnnotation( - annotationTable, - mod, - SubscribeEvent.class); - if (methodAnnotations.isEmpty()) return Object2ObjectMaps.emptyMap(); - - Object2ObjectMap> methods = new Object2ObjectOpenHashMap<>(); - for (ASMDataTable.ASMData data : methodAnnotations) { - methods.computeIfAbsent(data.getClassName(), k -> new ObjectOpenHashSet<>()).add(data.getObjectName()); - } - return methods; - } - - private static @Nonnull Class getCachedEventClass(String methodDesc) { - String className = methodDesc.substring(methodDesc.indexOf("(L") + 2, methodDesc.indexOf(";)")) - .replaceAll("/", "."); - return eventClassCache.computeIfAbsent(className, a -> { - try { - return Class.forName(className); - } catch (NoClassDefFoundError | ClassNotFoundException e) { - if (DEBUG) { - LOGGER.error("Failed to register method {} for class {}", methodDesc, className, e); - } - return INVALID_EVENT.getClass(); - } - }); - } - - private static Object2ObjectMap getModContainerPackageMap() { - Object2ObjectMap classToModContainer = new Object2ObjectOpenHashMap<>(); - for (ModContainer container : Loader.instance().getActiveModList()) { - Object modObject = container.getMod(); - if (modObject == null) continue; - Package modPackage = modObject.getClass().getPackage(); - if (modPackage == null) continue; - classToModContainer.put(modPackage.getName(), container); - } - return classToModContainer; - } - private static @Nonnull ModContainer getOwningModContainer(String className) { return classPathToModLookup.object2ObjectEntrySet().stream().filter(e -> className.startsWith(e.getKey())) .map(Map.Entry::getValue).findFirst().orElse(Loader.instance().getMinecraftModContainer()); } - private static @Nonnull Set getOwningModAnnotation( - SetMultimap dataTable, ModContainer mod, Class annotationClass) { - Set annotationData = dataTable.get(annotationClass.getName()); - if (annotationData == null || annotationData.isEmpty()) return Collections.emptySet(); - if (dataTable.get(Mod.class.getName()).size() > 1) { - annotationData = annotationData.stream() - .filter(data -> Objects.equals(getOwningModContainer(data.getClassName()), mod)) - .collect(Collectors.toSet()); - } - return annotationData; - } - private static boolean isValidSide(Class subscribedClass) throws IllegalAccessException { Side currentSide = FMLCommonHandler.instance().getSide(); if (currentSide.isClient()) return true; @@ -351,28 +233,6 @@ private static boolean isValidSide(Class subscribedClass) throws IllegalAcces return !StringUtils.containsIgnoreCase(subscribedClass.getName(), "client"); } - public static void setDataTable(ASMDataTable dataTable) { - if (!Loader.instance().activeModContainer().getModId().equals(GTNHLib.MODID)) { - return; - } - asmDataTable = dataTable; - } - - private static void printFailedEvents() { - Side side = FMLCommonHandler.instance().getSide(); - for (Map.Entry, Event> entry : eventCache.object2ObjectEntrySet()) { - if (entry.getValue() == null) { - LOGGER.error("Failed to register event {} for side {}", entry.getKey(), side); - } - } - } - - private static void printRegisteredEvents() { - for (Object2IntMap.Entry> entry : eventsRegistered.object2IntEntrySet()) { - LOGGER.info("Event {} was registered {} times", entry.getKey().getSimpleName(), entry.getIntValue()); - } - } - private static boolean isFMLEvent(Class event) { return event.getName().startsWith("cpw.mods.fml"); } diff --git a/src/main/java/com/gtnewhorizon/gtnhlib/eventbus/EventBusUtil.java b/src/main/java/com/gtnewhorizon/gtnhlib/eventbus/EventBusUtil.java new file mode 100644 index 0000000..0848085 --- /dev/null +++ b/src/main/java/com/gtnewhorizon/gtnhlib/eventbus/EventBusUtil.java @@ -0,0 +1,31 @@ +package com.gtnewhorizon.gtnhlib.eventbus; + +import it.unimi.dsi.fastutil.objects.Object2ObjectMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import it.unimi.dsi.fastutil.objects.ObjectSet; +import lombok.Getter; + +public final class EventBusUtil { + + public static final Boolean DEBUG_EVENT_BUS = Boolean.getBoolean("gtnhlib.debug.eventbus"); + + @Getter + private static final ObjectSet classesToVisit = new ObjectOpenHashSet<>(); + @Getter + private static final Object2ObjectMap> methodsToSubscribe = new Object2ObjectOpenHashMap<>(); + @Getter + private static final Object2ObjectMap conditionsToCheck = new Object2ObjectOpenHashMap<>(); + + static String getParameterClassInternal(String desc) { + return desc.substring(desc.indexOf("(") + 2, desc.indexOf(";")); + } + + static String getParameterClassName(String desc) { + return getParameterClassInternal(desc).replace("/", "."); + } + + static String getSimpleClassName(String desc) { + return desc.substring(desc.lastIndexOf(".") + 1); + } +} diff --git a/src/main/java/com/gtnewhorizon/gtnhlib/eventbus/MethodInfo.java b/src/main/java/com/gtnewhorizon/gtnhlib/eventbus/MethodInfo.java new file mode 100644 index 0000000..dc3babe --- /dev/null +++ b/src/main/java/com/gtnewhorizon/gtnhlib/eventbus/MethodInfo.java @@ -0,0 +1,19 @@ +package com.gtnewhorizon.gtnhlib.eventbus; + +import cpw.mods.fml.common.eventhandler.EventPriority; +import lombok.Data; + +@Data +public final class MethodInfo { + + public final String declaringClass; + public final String name; + public final String desc; + public final boolean receiveCanceled; + public final EventPriority priority; + public String optionalMod; + + public String getKey() { + return declaringClass + " " + name + desc; + } +} diff --git a/src/main/java/com/gtnewhorizon/gtnhlib/eventbus/StaticASMEventHandler.java b/src/main/java/com/gtnewhorizon/gtnhlib/eventbus/StaticASMEventHandler.java index f8d8b3a..1c05609 100644 --- a/src/main/java/com/gtnewhorizon/gtnhlib/eventbus/StaticASMEventHandler.java +++ b/src/main/java/com/gtnewhorizon/gtnhlib/eventbus/StaticASMEventHandler.java @@ -2,21 +2,18 @@ import static org.objectweb.asm.Opcodes.*; -import java.lang.reflect.Method; -import java.util.HashMap; - import org.apache.logging.log4j.ThreadContext; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Type; -import com.google.common.collect.Maps; - import cpw.mods.fml.common.ModContainer; import cpw.mods.fml.common.eventhandler.Event; import cpw.mods.fml.common.eventhandler.EventPriority; import cpw.mods.fml.common.eventhandler.IEventListener; -import cpw.mods.fml.common.eventhandler.SubscribeEvent; +import it.unimi.dsi.fastutil.objects.Object2ObjectMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import lombok.Getter; public class StaticASMEventHandler implements IEventListener { @@ -25,19 +22,22 @@ public class StaticASMEventHandler implements IEventListener { private static final String HANDLER_FUNC_DESC = Type .getMethodDescriptor(IEventListener.class.getDeclaredMethods()[0]); private static final ASMClassLoader LOADER = new ASMClassLoader(); - private static final HashMap> cache = Maps.newHashMap(); + private static final Object2ObjectMap> cache = new Object2ObjectOpenHashMap<>(); private static final boolean GETCONTEXT = Boolean.parseBoolean(System.getProperty("fml.LogContext", "false")); private final IEventListener handler; - private final SubscribeEvent subInfo; private final ModContainer owner; private final String readable; + private final boolean receiveCanceled; + @Getter + private final EventPriority priority; - StaticASMEventHandler(Object target, Method method, ModContainer owner) throws Exception { + StaticASMEventHandler(ModContainer owner, MethodInfo method) throws Exception { this.owner = owner; handler = (IEventListener) createWrapper(method).getDeclaredConstructor().newInstance(); - subInfo = method.getAnnotation(SubscribeEvent.class); - readable = "ASM: " + target + " " + method.getName() + Type.getMethodDescriptor(method); + readable = "ASM: " + method.getDeclaringClass() + " " + method.getName() + method.getDesc(); + receiveCanceled = method.receiveCanceled; + priority = method.getPriority(); } @Override @@ -48,29 +48,24 @@ public void invoke(Event event) { ThreadContext.put("mod", ""); } if (handler != null) { - if (!event.isCancelable() || !event.isCanceled() || subInfo.receiveCanceled()) { + if (!event.isCancelable() || !event.isCanceled() || receiveCanceled) { handler.invoke(event); } } if (GETCONTEXT) ThreadContext.remove("mod"); } - public EventPriority getPriority() { - return subInfo.priority(); - } - - public Class createWrapper(Method callback) { - if (cache.containsKey(callback)) { - return cache.get(callback); - } + public Class createWrapper(MethodInfo method) { + Class cached = cache.get(method.getKey()); + if (cached != null) return cached; ClassWriter cw = new ClassWriter(0); MethodVisitor mv; - String name = getUniqueName(callback); + String name = getUniqueName(method); String desc = name.replace('.', '/'); - String instType = Type.getInternalName(callback.getDeclaringClass()); - String eventType = Type.getInternalName(callback.getParameterTypes()[0]); + String instType = method.getDeclaringClass().replace('.', '/'); + String eventType = EventBusUtil.getParameterClassInternal(method.getDesc()); cw.visit(V1_6, ACC_PUBLIC | ACC_SUPER, desc, null, "java/lang/Object", new String[] { HANDLER_DESC }); @@ -90,25 +85,27 @@ public Class createWrapper(Method callback) { mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ALOAD, 1); mv.visitTypeInsn(CHECKCAST, eventType); - mv.visitMethodInsn(INVOKESTATIC, instType, callback.getName(), Type.getMethodDescriptor(callback), false); + mv.visitMethodInsn(INVOKESTATIC, instType, method.name, method.desc, false); mv.visitInsn(RETURN); mv.visitMaxs(2, 2); mv.visitEnd(); } cw.visitEnd(); Class ret = LOADER.define(name, cw.toByteArray()); - cache.put(callback, ret); + cache.put(method.getKey(), ret); return ret; } - private String getUniqueName(Method callback) { + private String getUniqueName(MethodInfo method) { + String param = EventBusUtil.getParameterClassName(method.getDesc()); + String declaring = method.getDeclaringClass(); return String.format( "%s_%d_%s_%s_%s", getClass().getName(), IDs++, - callback.getDeclaringClass().getSimpleName(), - callback.getName(), - callback.getParameterTypes()[0].getSimpleName()); + EventBusUtil.getSimpleClassName(declaring), + method.getName(), + EventBusUtil.getSimpleClassName(param)); } private static class ASMClassLoader extends ClassLoader { diff --git a/src/main/java/com/gtnewhorizon/gtnhlib/mixins/Mixins.java b/src/main/java/com/gtnewhorizon/gtnhlib/mixins/Mixins.java index 9d9949a..8f9aa11 100644 --- a/src/main/java/com/gtnewhorizon/gtnhlib/mixins/Mixins.java +++ b/src/main/java/com/gtnewhorizon/gtnhlib/mixins/Mixins.java @@ -19,7 +19,7 @@ public enum Mixins { WAVEFRONT_VBO(new Builder("WavefrontObject").addTargetedMod(TargetedMod.VANILLA).setSide(Side.CLIENT) .setPhase(Phase.EARLY).setApplyIf(() -> true).addMixinClasses("MixinWavefrontObject")), EVENT_BUS_ACCESSOR(new Builder("EventBusAccessor").addTargetedMod(TargetedMod.VANILLA).setSide(Side.BOTH) - .setPhase(Phase.EARLY).addMixinClasses("fml.EventBusAccessor")),; + .setPhase(Phase.EARLY).addMixinClasses("fml.EventBusAccessor", "fml.EnumHolderAccessor")),; private final List mixinClasses; private final Supplier applyIf; diff --git a/src/main/java/com/gtnewhorizon/gtnhlib/mixins/early/fml/EnumHolderAccessor.java b/src/main/java/com/gtnewhorizon/gtnhlib/mixins/early/fml/EnumHolderAccessor.java new file mode 100644 index 0000000..9b6dd9b --- /dev/null +++ b/src/main/java/com/gtnewhorizon/gtnhlib/mixins/early/fml/EnumHolderAccessor.java @@ -0,0 +1,13 @@ +package com.gtnewhorizon.gtnhlib.mixins.early.fml; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import cpw.mods.fml.common.discovery.asm.ModAnnotation; + +@Mixin(value = ModAnnotation.EnumHolder.class, remap = false) +public interface EnumHolderAccessor { + + @Accessor + String getValue(); +} From 592ad47d5ffe860f6a7804bf0679c780ca43ef77 Mon Sep 17 00:00:00 2001 From: Lyfts <127234178+Lyfts@users.noreply.github.com> Date: Wed, 9 Oct 2024 22:36:43 +0200 Subject: [PATCH 5/7] throw exception when encountering an unexpected non-static method --- .../core/transformer/EventBusSubTransformer.java | 6 ++++++ .../gtnewhorizon/gtnhlib/eventbus/AutoEventBus.java | 12 ++++++++++++ .../gtnewhorizon/gtnhlib/eventbus/EventBusUtil.java | 9 +++++++++ 3 files changed, 27 insertions(+) diff --git a/src/main/java/com/gtnewhorizon/gtnhlib/core/transformer/EventBusSubTransformer.java b/src/main/java/com/gtnewhorizon/gtnhlib/core/transformer/EventBusSubTransformer.java index 13815e9..d710dd9 100644 --- a/src/main/java/com/gtnewhorizon/gtnhlib/core/transformer/EventBusSubTransformer.java +++ b/src/main/java/com/gtnewhorizon/gtnhlib/core/transformer/EventBusSubTransformer.java @@ -10,6 +10,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.objectweb.asm.ClassReader; +import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.AnnotationNode; import org.objectweb.asm.tree.ClassNode; @@ -75,6 +76,11 @@ public byte[] transform(String name, String transformedName, byte[] basicClass) continue; } + if ((mn.access & Opcodes.ACC_STATIC) == 0) { + EventBusUtil.addInvalidMethod(transformedName, mn.name + mn.desc); + continue; + } + if (usableAnnotations.containsKey(CONDITION_DESC)) { if (mn.desc.equals("()Z")) { EventBusUtil.getConditionsToCheck().put(transformedName, mn.name + mn.desc); diff --git a/src/main/java/com/gtnewhorizon/gtnhlib/eventbus/AutoEventBus.java b/src/main/java/com/gtnewhorizon/gtnhlib/eventbus/AutoEventBus.java index faffee8..2ad3e8b 100644 --- a/src/main/java/com/gtnewhorizon/gtnhlib/eventbus/AutoEventBus.java +++ b/src/main/java/com/gtnewhorizon/gtnhlib/eventbus/AutoEventBus.java @@ -39,6 +39,7 @@ import it.unimi.dsi.fastutil.objects.Object2BooleanOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectList; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import it.unimi.dsi.fastutil.objects.ObjectSet; import lombok.AccessLevel; @@ -150,6 +151,17 @@ public static void registerSubscribers() { } } } + + ObjectList invalidMethods = EventBusUtil.getInvalidMethods(); + if (invalidMethods.size() == 1) { + throw new IllegalArgumentException(invalidMethods.get(0)); + } else if (invalidMethods.size() > 1) { + int i; + for (i = 0; i < invalidMethods.size() - 1; i++) { + LOGGER.error(invalidMethods.get(i)); + } + throw new IllegalArgumentException(invalidMethods.get(i)); + } } private static void register(ModContainer classOwner, Class target, ObjectSet methods) { diff --git a/src/main/java/com/gtnewhorizon/gtnhlib/eventbus/EventBusUtil.java b/src/main/java/com/gtnewhorizon/gtnhlib/eventbus/EventBusUtil.java index 0848085..efdbdc6 100644 --- a/src/main/java/com/gtnewhorizon/gtnhlib/eventbus/EventBusUtil.java +++ b/src/main/java/com/gtnewhorizon/gtnhlib/eventbus/EventBusUtil.java @@ -2,8 +2,11 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.ObjectList; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import it.unimi.dsi.fastutil.objects.ObjectSet; +import lombok.AccessLevel; import lombok.Getter; public final class EventBusUtil { @@ -16,6 +19,8 @@ public final class EventBusUtil { private static final Object2ObjectMap> methodsToSubscribe = new Object2ObjectOpenHashMap<>(); @Getter private static final Object2ObjectMap conditionsToCheck = new Object2ObjectOpenHashMap<>(); + @Getter(AccessLevel.PACKAGE) + private static final ObjectList invalidMethods = new ObjectArrayList<>(); static String getParameterClassInternal(String desc) { return desc.substring(desc.indexOf("(") + 2, desc.indexOf(";")); @@ -28,4 +33,8 @@ static String getParameterClassName(String desc) { static String getSimpleClassName(String desc) { return desc.substring(desc.lastIndexOf(".") + 1); } + + public static void addInvalidMethod(String className, String method) { + invalidMethods.add("Encountered unexpected non-static method: " + className + " " + method); + } } From 76dc5802354dd6a7c31d22b87c57fdb18e997398 Mon Sep 17 00:00:00 2001 From: Lyfts <127234178+Lyfts@users.noreply.github.com> Date: Wed, 9 Oct 2024 22:48:24 +0200 Subject: [PATCH 6/7] oops that was too wide a net --- .../gtnhlib/core/transformer/EventBusSubTransformer.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/gtnewhorizon/gtnhlib/core/transformer/EventBusSubTransformer.java b/src/main/java/com/gtnewhorizon/gtnhlib/core/transformer/EventBusSubTransformer.java index d710dd9..b4d19be 100644 --- a/src/main/java/com/gtnewhorizon/gtnhlib/core/transformer/EventBusSubTransformer.java +++ b/src/main/java/com/gtnewhorizon/gtnhlib/core/transformer/EventBusSubTransformer.java @@ -76,19 +76,22 @@ public byte[] transform(String name, String transformedName, byte[] basicClass) continue; } + AnnotationNode subscribe = usableAnnotations.get(SUBSCRIBE_DESC); + boolean condition = usableAnnotations.containsKey(CONDITION_DESC); if ((mn.access & Opcodes.ACC_STATIC) == 0) { - EventBusUtil.addInvalidMethod(transformedName, mn.name + mn.desc); + if (!condition && subscribe != null) { + EventBusUtil.addInvalidMethod(transformedName, mn.name + mn.desc); + } continue; } - if (usableAnnotations.containsKey(CONDITION_DESC)) { + if (condition) { if (mn.desc.equals("()Z")) { EventBusUtil.getConditionsToCheck().put(transformedName, mn.name + mn.desc); } continue; } - AnnotationNode subscribe = usableAnnotations.get(SUBSCRIBE_DESC); if (subscribe == null) continue; Object[] subscribeInfo = getSubscribeInfo(subscribe); MethodInfo methodInfo = new MethodInfo( From eb1bd97edb9715179a6bf6be1c46f56feb791b29 Mon Sep 17 00:00:00 2001 From: Lyfts <127234178+Lyfts@users.noreply.github.com> Date: Thu, 10 Oct 2024 09:19:08 +0200 Subject: [PATCH 7/7] more logging for the transformer --- .../transformer/EventBusSubTransformer.java | 29 +++++++++++++++++-- .../gtnhlib/eventbus/AutoEventBus.java | 3 +- .../gtnhlib/eventbus/EventBusUtil.java | 7 +---- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/gtnewhorizon/gtnhlib/core/transformer/EventBusSubTransformer.java b/src/main/java/com/gtnewhorizon/gtnhlib/core/transformer/EventBusSubTransformer.java index b4d19be..86e7b6f 100644 --- a/src/main/java/com/gtnewhorizon/gtnhlib/core/transformer/EventBusSubTransformer.java +++ b/src/main/java/com/gtnewhorizon/gtnhlib/core/transformer/EventBusSubTransformer.java @@ -80,7 +80,8 @@ public byte[] transform(String name, String transformedName, byte[] basicClass) boolean condition = usableAnnotations.containsKey(CONDITION_DESC); if ((mn.access & Opcodes.ACC_STATIC) == 0) { if (!condition && subscribe != null) { - EventBusUtil.addInvalidMethod(transformedName, mn.name + mn.desc); + EventBusUtil.getInvalidMethods().add( + "Encountered unexpected non-static method: " + transformedName + " " + mn.name + mn.desc); } continue; } @@ -88,11 +89,26 @@ public byte[] transform(String name, String transformedName, byte[] basicClass) if (condition) { if (mn.desc.equals("()Z")) { EventBusUtil.getConditionsToCheck().put(transformedName, mn.name + mn.desc); + } else { + EventBusUtil.getInvalidMethods().add( + "Invalid condition method: " + transformedName + + " " + + mn.name + + mn.desc + + ". Condition method must have no parameters and return a boolean."); } continue; } - if (subscribe == null) continue; + if (subscribe == null) { + if (DEBUG_EVENT_BUS) { + LOGGER.info( + "Skipping method {} with annotations {}. No @SubscribeEvent found.", + transformedName, + usableAnnotations.keySet()); + } + continue; + } Object[] subscribeInfo = getSubscribeInfo(subscribe); MethodInfo methodInfo = new MethodInfo( transformedName, @@ -104,10 +120,19 @@ public byte[] transform(String name, String transformedName, byte[] basicClass) if (optional != null) { List values = optional.values; methodInfo.setOptionalMod((String) values.get(1)); + if (DEBUG_EVENT_BUS) { + LOGGER.info( + "Found optional mod {} for method {}", + methodInfo.getOptionalMod(), + methodInfo.getKey()); + } } EventBusUtil.getMethodsToSubscribe().computeIfAbsent(transformedName, k -> new ObjectOpenHashSet<>()) .add(methodInfo); + if (DEBUG_EVENT_BUS) { + LOGGER.info("Found subscribed method {}", methodInfo.getKey()); + } } return basicClass; diff --git a/src/main/java/com/gtnewhorizon/gtnhlib/eventbus/AutoEventBus.java b/src/main/java/com/gtnewhorizon/gtnhlib/eventbus/AutoEventBus.java index 2ad3e8b..1b1e429 100644 --- a/src/main/java/com/gtnewhorizon/gtnhlib/eventbus/AutoEventBus.java +++ b/src/main/java/com/gtnewhorizon/gtnhlib/eventbus/AutoEventBus.java @@ -160,7 +160,8 @@ public static void registerSubscribers() { for (i = 0; i < invalidMethods.size() - 1; i++) { LOGGER.error(invalidMethods.get(i)); } - throw new IllegalArgumentException(invalidMethods.get(i)); + throw new IllegalArgumentException( + "Encountered" + invalidMethods.size() + "invalid methods. " + invalidMethods.get(i)); } } diff --git a/src/main/java/com/gtnewhorizon/gtnhlib/eventbus/EventBusUtil.java b/src/main/java/com/gtnewhorizon/gtnhlib/eventbus/EventBusUtil.java index efdbdc6..9a952d8 100644 --- a/src/main/java/com/gtnewhorizon/gtnhlib/eventbus/EventBusUtil.java +++ b/src/main/java/com/gtnewhorizon/gtnhlib/eventbus/EventBusUtil.java @@ -6,7 +6,6 @@ import it.unimi.dsi.fastutil.objects.ObjectList; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import it.unimi.dsi.fastutil.objects.ObjectSet; -import lombok.AccessLevel; import lombok.Getter; public final class EventBusUtil { @@ -19,7 +18,7 @@ public final class EventBusUtil { private static final Object2ObjectMap> methodsToSubscribe = new Object2ObjectOpenHashMap<>(); @Getter private static final Object2ObjectMap conditionsToCheck = new Object2ObjectOpenHashMap<>(); - @Getter(AccessLevel.PACKAGE) + @Getter private static final ObjectList invalidMethods = new ObjectArrayList<>(); static String getParameterClassInternal(String desc) { @@ -33,8 +32,4 @@ static String getParameterClassName(String desc) { static String getSimpleClassName(String desc) { return desc.substring(desc.lastIndexOf(".") + 1); } - - public static void addInvalidMethod(String className, String method) { - invalidMethods.add("Encountered unexpected non-static method: " + className + " " + method); - } }