Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EventBusSubscriber improvements #82

Merged
merged 3 commits into from
Oct 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions src/main/java/com/gtnewhorizon/gtnhlib/CommonProxy.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@

import com.gtnewhorizon.gtnhlib.config.ConfigurationManager;
import com.gtnewhorizon.gtnhlib.eventbus.AutoEventBus;
import com.gtnewhorizon.gtnhlib.eventbus.Phase;
import com.gtnewhorizon.gtnhlib.network.NetworkHandler;
import com.gtnewhorizon.gtnhlib.network.PacketMessageAboveHotbar;

import cpw.mods.fml.common.event.FMLConstructionEvent;
import cpw.mods.fml.common.event.FMLInitializationEvent;
import cpw.mods.fml.common.event.FMLPostInitializationEvent;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
Expand All @@ -20,13 +22,17 @@

public class CommonProxy {

public void construct(FMLConstructionEvent event) {
AutoEventBus.executePhase(Phase.CONSTRUCT);
}

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

public void init(FMLInitializationEvent event) {
AutoEventBus.registerSubscribers();
AutoEventBus.executePhase(Phase.INIT);
NetworkHandler.init();
ConfigurationManager.onInit();
}
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/com/gtnewhorizon/gtnhlib/GTNHLib.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ public class GTNHLib {
@SidedProxy(clientSide = GTNHLib.GROUPNAME + ".ClientProxy", serverSide = GTNHLib.GROUPNAME + ".CommonProxy")
public static CommonProxy proxy;

@Mod.EventHandler
public void construct(FMLConstructionEvent event) {
proxy.construct(event);
}

@Mod.EventHandler
public void preInit(FMLPreInitializationEvent event) {
proxy.preInit(event);
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/com/gtnewhorizon/gtnhlib/core/GTNHLibCore.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
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.eventbus.EventBusUtil;
import com.gtnewhorizon.gtnhlib.mixins.Mixins;
import com.gtnewhorizon.gtnhmixins.IEarlyMixinLoader;

Expand Down Expand Up @@ -93,6 +93,6 @@ public boolean registerBus(EventBus bus, LoadController controller) {

@Subscribe
public void construct(FMLConstructionEvent event) {
EventBusSubTransformer.harvestData(event.getASMHarvestedData());
EventBusUtil.harvestData(event.getASMHarvestedData());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
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;
Expand All @@ -42,21 +41,14 @@ public class EventBusSubTransformer implements IClassTransformer {
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());
}
}
private static final ObjectSet<String> classesToVisit = EventBusUtil.getClassesToVisit();

@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)) {
if (classesToVisit.isEmpty() || !classesToVisit.contains(transformedName)) {
return basicClass;
}

Expand Down
138 changes: 37 additions & 101 deletions src/main/java/com/gtnewhorizon/gtnhlib/eventbus/AutoEventBus.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

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.invoke.CallSite;
import java.lang.invoke.LambdaMetafactory;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.ArrayList;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BooleanSupplier;
import java.util.function.Predicate;

import javax.annotation.Nonnull;
Expand All @@ -17,30 +19,24 @@
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.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.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.relauncher.Side;
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.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;
import lombok.NoArgsConstructor;
Expand All @@ -50,12 +46,10 @@ public class AutoEventBus {

private static final Logger LOGGER = LogManager.getLogger("GTNHLib EventBus");
private static final DummyEvent INVALID_EVENT = new DummyEvent();
private static final Object2ObjectMap<ModContainer, ObjectSet<String>> subscribers = new Object2ObjectOpenHashMap<>();
private static final Object2ObjectMap<String, ModContainer> classPathToModLookup = new Object2ObjectOpenHashMap<>();
private static final Object2ObjectMap<String, Event> eventCache = new Object2ObjectOpenHashMap<>();
private static final Object2BooleanMap<String> optionalMods = new Object2BooleanOpenHashMap<>();

private static boolean hasRegistered;
private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
private static final MethodType CONDITION_TYPE = MethodType.methodType(boolean.class);

private enum EventBusType {

Expand Down Expand Up @@ -83,58 +77,16 @@ private boolean canRegister(Class<?> 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);
}

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<ModContainer, ObjectSet<String>> entry : subscribers.object2ObjectEntrySet()) {
Set<ASMDataTable.ASMData> sideOnly = dataTable.getAnnotationsFor(entry.getKey())
.get(SideOnly.class.getName());

for (ASMDataTable.ASMData data : sideOnly) {
if (!data.getObjectName().equals(data.getClassName())) {
continue;
}
public static void executePhase(Phase phase) {
if (phase.hasExecuted) return;
phase.hasExecuted = true;
Object2ObjectMap<ModContainer, ObjectSet<String>> subscribers = phase.getModClassesForPhase();
if (subscribers.isEmpty()) return;

Map<String, Object> 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 (Object2ObjectMap.Entry<ModContainer, ObjectSet<String>> entry : subscribers.object2ObjectEntrySet()) {
for (String className : entry.getValue()) {
try {
Class<?> clazz = Class.forName(className, false, Loader.instance().getModClassLoader());
if (!isValidSide(clazz)) {
if (DEBUG_EVENT_BUS) {
LOGGER.info(
"Skipping registration for {}, invalid side {}",
clazz.getSimpleName(),
FMLCommonHandler.instance().getSide());
}
continue;
}

String conditionToCheck = EventBusUtil.getConditionsToCheck().get(className);
if (conditionToCheck != null && !isConditionMet(clazz, conditionToCheck)) {
if (DEBUG_EVENT_BUS) {
Expand All @@ -146,22 +98,23 @@ public static void registerSubscribers() {
ObjectSet<MethodInfo> methods = EventBusUtil.getMethodsToSubscribe().get(className);
if (methods == null || methods.isEmpty()) continue;
register(entry.getKey(), clazz, methods);
} catch (IllegalAccessException | ClassNotFoundException e) {
} catch (ClassNotFoundException e) {
if (DEBUG_EVENT_BUS) LOGGER.error("Failed to load class {}", className, e);
}
}
}

ObjectList<String> 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));
if (phase == Phase.INIT) {
ObjectList<String> 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("Encountered" + i + "invalid methods. " + invalidMethods.get(i));
}
throw new IllegalArgumentException(
"Encountered" + invalidMethods.size() + "invalid methods. " + invalidMethods.get(i));
}
}

Expand All @@ -177,7 +130,7 @@ private static void register(ModContainer classOwner, Class<?> target, ObjectSet
Event event = getCachedEvent(EventBusUtil.getParameterClassName(method.desc));
if (INVALID_EVENT.equals(event)) continue;

StaticASMEventHandler listener = new StaticASMEventHandler(classOwner, method);
StaticASMEventHandler listener = new StaticASMEventHandler(method);
for (EventBusType bus : EventBusType.VALUES) {
if (!bus.canRegister(event.getClass())) {
continue;
Expand Down Expand Up @@ -213,39 +166,22 @@ private static void register(ModContainer classOwner, Class<?> target, ObjectSet
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_EVENT_BUS) LOGGER.error("Failed to invoke condition {} for class {}", condition, clazz, e);
MethodHandle handle = MethodHandles.publicLookup()
.findStatic(clazz, condition.substring(0, condition.indexOf("(")), CONDITION_TYPE);
CallSite call = LambdaMetafactory.metafactory(
LOOKUP,
"getAsBoolean",
MethodType.methodType(BooleanSupplier.class),
CONDITION_TYPE,
handle,
CONDITION_TYPE);
return ((BooleanSupplier) call.getTarget().invokeWithArguments()).getAsBoolean();
} catch (Throwable e) {
LOGGER.error("Failed to invoke condition {} for class {}", condition, clazz, e);
return false;
}
}

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 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");
}

private static boolean isFMLEvent(Class<?> event) {
return event.getName().startsWith("cpw.mods.fml");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@

/**
* 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.<br>
* listen for events. Registration will happen during the specified {@link EventBusSubscriber#phase()} or during
* {@link Phase#INIT} if not specified. <br>
* All methods annotated with {@link cpw.mods.fml.common.eventhandler.SubscribeEvent} are expected to be static.
*/
@Retention(RetentionPolicy.RUNTIME)
Expand All @@ -23,13 +24,17 @@
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. <br>
* 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.
* Which equivalent {@link cpw.mods.fml.common.LoaderState} this subscriber should be registered during.
*/
Phase phase() default Phase.INIT;

/**
* Can be applied to a boolean method in the annotated class that provides a condition for registering the
* subscriber. It is expected that the method is static, returns a boolean, and takes no parameters. <br>
* There is expected to be at most one condition for a class.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.METHOD })
@Target(ElementType.METHOD)
@interface Condition {}

}
Loading