Skip to content

Commit

Permalink
Backport @EventBusSubscriber annotation from 1.12.2 (#80)
Browse files Browse the repository at this point in the history
* backport @EventBusSubscriber annotation from 1.12.2

* Cache events, safe method lookup, replace eventbus reflection with accessor

* ily spotless

* actual completely safe event registration this time with less reflection

* throw exception when encountering an unexpected non-static method

* oops that was too wide a net

* more logging for the transformer
  • Loading branch information
Lyfts authored Oct 10, 2024
1 parent 3bd8cb4 commit 2da1d6f
Show file tree
Hide file tree
Showing 13 changed files with 763 additions and 22 deletions.
8 changes: 4 additions & 4 deletions src/main/java/com/gtnewhorizon/gtnhlib/ClientProxy.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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();
Expand Down Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/com/gtnewhorizon/gtnhlib/CommonProxy.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -20,10 +21,12 @@
public class CommonProxy {

public void preInit(FMLPreInitializationEvent event) {
AutoEventBus.init(event.getAsmData());
GTNHLib.info("GTNHLib version " + Tags.VERSION + " loaded.");
}

public void init(FMLInitializationEvent event) {
AutoEventBus.registerSubscribers();
NetworkHandler.init();
ConfigurationManager.onInit();
}
Expand Down
39 changes: 35 additions & 4 deletions src/main/java/com/gtnewhorizon/gtnhlib/core/GTNHLibCore.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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<String> 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
Expand All @@ -64,4 +84,15 @@ public String getMixinConfig() {
public List<String> getMixins(Set<String> 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());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
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.Opcodes;
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<String> ANNOTATIONS = Arrays
.asList(OPTIONAL_DESC, SIDEONLY_DESC, SUBSCRIBE_DESC, CONDITION_DESC);
private static final String CURRENT_SIDE = FMLLaunchHandler.side().name();
private static ObjectSet<String> 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<String, AnnotationNode> 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;
}

AnnotationNode subscribe = usableAnnotations.get(SUBSCRIBE_DESC);
boolean condition = usableAnnotations.containsKey(CONDITION_DESC);
if ((mn.access & Opcodes.ACC_STATIC) == 0) {
if (!condition && subscribe != null) {
EventBusUtil.getInvalidMethods().add(
"Encountered unexpected non-static method: " + transformedName + " " + mn.name + mn.desc);
}
continue;
}

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) {
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,
mn.name,
mn.desc,
(Boolean) subscribeInfo[0],
(EventPriority) subscribeInfo[1]);
AnnotationNode optional = usableAnnotations.get(OPTIONAL_DESC);
if (optional != null) {
List<Object> 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;
}

private static Object2ObjectMap<String, AnnotationNode> getUsableAnnotations(List<AnnotationNode> annotations) {
if (annotations == null) return Object2ObjectMaps.emptyMap();
Object2ObjectMap<String, AnnotationNode> 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;
}
}
Loading

0 comments on commit 2da1d6f

Please sign in to comment.