Fix implicit Bus.BOTH not working in @EventBusSubscriber, add additional validation in strict mode, partial backport of #10676

This commit is contained in:
Paint_Ninja 2025-10-05 15:15:25 +01:00
parent c1d703e395
commit 60110cd916
12 changed files with 113 additions and 51 deletions

View file

@ -53,6 +53,8 @@ public class AutomaticEventSubscriber {
private static final Type AUTO_SUBSCRIBER = Type.getType(EventBusSubscriber.class);
private static final Type MOD_TYPE = Type.getType(Mod.class);
private static final Type ONLY_IN_TYPE = Type.getType(OnlyIn.class);
private static final List<EnumData> DEFAULT_SIDES = List.of(new EnumData(null, "CLIENT"), new EnumData(null, "DEDICATED_SERVER"));
private static final EnumData DEFAULT_BUS = new EnumData(null, "BOTH");
public static void inject(ModContainer mod, ModFileScanData scanData, ClassLoader loader) {
if (scanData == null) return;
@ -71,9 +73,6 @@ public class AutomaticEventSubscriber {
.filter(data -> MOD_TYPE.equals(data.annotationType()))
.collect(Collectors.toMap(a -> a.clazz().getClassName(), a -> (String)a.annotationData().get("value")));
var defaultSides = List.of(new EnumData(null, "CLIENT"), new EnumData(null, "DEDICATED_SERVER"));
var defaultBus = new EnumData(null, "FORGE");
for (var data : targets) {
if (!FMLEnvironment.production && onlyIns.contains(data.clazz().getClassName())) {
throw new RuntimeException("Found @OnlyIn on @EventBusSubscriber class " + data.clazz().getClassName() + " - this is not allowed as it causes crashes. Remove the OnlyIn and set value=Dist.CLIENT in the EventBusSubscriber annotation instead");
@ -82,13 +81,13 @@ public class AutomaticEventSubscriber {
var modId = modids.getOrDefault(data.clazz().getClassName(), mod.getModId());
modId = value(data, "modid", modId);
var sidesValue = value(data, "value", defaultSides);
var sidesValue = value(data, "value", DEFAULT_SIDES);
var sides = sidesValue.stream()
.map(EnumData::value)
.map(Dist::valueOf)
.collect(Collectors.toSet());
var busName = value(data, "bus", defaultBus).value();
var busName = value(data, "bus", DEFAULT_BUS).value();
var busTarget = Bus.valueOf(busName);
if (Objects.equals(mod.getModId(), modId) && sides.contains(FMLEnvironment.dist)) {
try {
@ -173,7 +172,7 @@ public class AutomaticEventSubscriber {
Class<? extends Event> eventType = (Class<? extends Event>) parameterTypes[0];
var subscribeEventAnnotation = method.getAnnotation(SubscribeEvent.class);
registerListener(busGroup, paramCount, returnType, eventType, subscribeEventAnnotation, method);
registerListener(busGroup, paramCount, returnType, eventType, subscribeEventAnnotation, method, false);
listenersCount++;
if (firstValidListenerEventType == null)
@ -259,7 +258,7 @@ public class AutomaticEventSubscriber {
throw fail(method, "Return type boolean is only valid for cancellable events");
}
registerListener(busGroup, paramCount, returnType, eventType, subscribeEventAnnotation, method);
registerListener(busGroup, paramCount, returnType, eventType, subscribeEventAnnotation, method, true);
listenersCount++;
if (firstValidListenerEventType == null)
@ -276,11 +275,24 @@ public class AutomaticEventSubscriber {
@SuppressWarnings({"unchecked", "rawtypes"})
private static EventListener registerListener(@Nullable BusGroup busGroup,
int paramCount, Class<?> returnType, Class<? extends Event> eventType,
SubscribeEvent subscribeEventAnnotation, Method method) {
SubscribeEvent subscribeEventAnnotation, Method method, boolean strict) {
if (busGroup == null) {
busGroup = IModBusEvent.class.isAssignableFrom(eventType)
? FMLJavaModLoadingContext.get().getModBusGroup()
: BusGroup.DEFAULT;
} else if (strict) {
String solution = "To fix this, remove the bus param from your @EventBusSubscriber annotation or move this event listener to another class.";
var isDefaultBusGroup = busGroup == BusGroup.DEFAULT;
var isModBusEvent = IModBusEvent.class.isAssignableFrom(eventType);
if (isDefaultBusGroup && isModBusEvent) { // requested forge bus and has IModBusEvent
throw fail(method, "Event type " + eventType.getName()
+ " is on the mod BusGroup but you are asking to register it on the default BusGroup (BusGroup.DEFAULT/EventBusSubscriber.Bus.FORGE). "
+ solution);
} else if (!isDefaultBusGroup && !isModBusEvent) { // requested mod bus and does not have IModBusEvent
throw fail(method, "Event type " + eventType.getName()
+ " is on the default BusGroup but you are asking to register it on the mod BusGroup (context.getModBusGroup()/EventBusSubscriber.Bus.MOD). "
+ solution);
}
}
// determine the listener type from its parameters and return type

View file

@ -193,6 +193,6 @@ public class FMLModContainer extends ModContainer {
@Override
public String toString() {
return "FMLModContainer[" + this.getModInfo().getModId() + ", " + this.getClass().getName() + "]";
return "FMLModContainer[" + this.getModInfo().getModId() + ", " + this.getClass().getName() + ']';
}
}

View file

@ -7,6 +7,7 @@ package net.minecraftforge.client;
import net.minecraft.client.renderer.chunk.ChunkSectionLayer;
import net.minecraft.client.resources.model.UnbakedGeometry;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.event.ModelEvent;
import net.minecraftforge.client.event.RegisterClientReloadListenersEvent;
@ -21,10 +22,10 @@ import net.minecraftforge.fml.common.Mod;
public class ClientForgeMod {
@SubscribeEvent
public static void onRegisterGeometryLoaders(ModelEvent.RegisterGeometryLoaders event) {
event.register("empty", (json, ctx) -> UnbakedGeometry.EMPTY);
event.register("obj", ObjLoader.INSTANCE);
event.register("fluid_container", DynamicFluidContainerModel.Loader.INSTANCE);
event.register("item_layers", ItemLayerGeometry.Loader.INSTANCE);
event.register(forgeRL("empty"), (json, ctx) -> UnbakedGeometry.EMPTY);
event.register(forgeRL("obj"), ObjLoader.INSTANCE);
event.register(forgeRL("fluid_container"), DynamicFluidContainerModel.Loader.INSTANCE);
event.register(forgeRL("item_layers"), ItemLayerGeometry.Loader.INSTANCE);
}
@SubscribeEvent
@ -34,6 +35,10 @@ public class ClientForgeMod {
@SubscribeEvent
public static void onRegisterNamedRenderTypes(RegisterNamedRenderTypesEvent event) {
event.register("item_unlit", ChunkSectionLayer.TRANSLUCENT, ForgeRenderTypes.ITEM_UNSORTED_UNLIT_TRANSLUCENT.get());
event.register(forgeRL("item_unlit"), ChunkSectionLayer.TRANSLUCENT, ForgeRenderTypes.ITEM_UNSORTED_UNLIT_TRANSLUCENT.get());
}
private static ResourceLocation forgeRL(String path) {
return ResourceLocation.fromNamespaceAndPath("forge", path);
}
}

View file

@ -155,8 +155,6 @@ public abstract sealed class ModelEvent {
/**
* Allows users to register their own {@link IGeometryLoader geometry loaders} for use in block/item models.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus() mod-specific event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
@ -174,13 +172,23 @@ public abstract sealed class ModelEvent {
/**
* Registers a new geometry loader.
* @param resourceLocation The namespace should match your mod's namespace, such as your mod ID
*/
public void register(ResourceLocation resourceLocation, IGeometryLoader loader) {
Preconditions.checkArgument(!loaders.containsKey(resourceLocation), "Geometry loader already registered: " + resourceLocation);
loaders.put(resourceLocation, loader);
}
/**
* Registers a new geometry loader.
* @deprecated Use {@link #register(ResourceLocation, IGeometryLoader)} instead.
*/
@Deprecated(forRemoval = true, since = "1.21.9") // removed in 1.21.9
public void register(String name, IGeometryLoader loader) {
@SuppressWarnings("removal")
var namespace = ModLoadingContext.get().getActiveNamespace();
var key = ResourceLocation.fromNamespaceAndPath(namespace, name);
Preconditions.checkArgument(!loaders.containsKey(key), "Geometry loader already registered: " + key);
loaders.put(key, loader);
register(key, loader);
}
}
}

View file

@ -13,6 +13,7 @@ import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.client.RenderTypeGroup;
import net.minecraftforge.eventbus.api.bus.BusGroup;
import net.minecraftforge.eventbus.api.bus.EventBus;
import net.minecraftforge.eventbus.api.event.characteristic.SelfDestructing;
import net.minecraftforge.fml.LogicalSide;
import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.fml.event.IModBusEvent;
@ -25,7 +26,7 @@ import java.util.Map;
*
* <p>This event is fired only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public final class RegisterNamedRenderTypesEvent implements IModBusEvent {
public final class RegisterNamedRenderTypesEvent implements SelfDestructing, IModBusEvent {
public static EventBus<RegisterNamedRenderTypesEvent> getBus(BusGroup modBusGroup) {
return IModBusEvent.getBus(modBusGroup, RegisterNamedRenderTypesEvent.class);
}
@ -40,10 +41,40 @@ public final class RegisterNamedRenderTypesEvent implements IModBusEvent {
/**
* Registers a named {@link RenderTypeGroup}.
*
* @param name The name
* @param resourceLocation The namespace should match your mod's namespace, such as your mod ID
* @param blockRenderType What ChunkSectionLayer to render in
* @param entityRenderType A {@link RenderType} using {@link DefaultVertexFormat#NEW_ENTITY}
*/
public void register(ResourceLocation resourceLocation, ChunkSectionLayer blockRenderType, RenderType entityRenderType) {
register(resourceLocation, blockRenderType, entityRenderType, entityRenderType);
}
/**
* Registers a named {@link RenderTypeGroup}.
*
* @param key The namespace should match your mod's namespace, such as your mod ID
* @param blockRenderType What ChunkSectionLayer to render in
* @param entityRenderType A {@link RenderType} using {@link DefaultVertexFormat#NEW_ENTITY}
* @param fabulousEntityRenderType A {@link RenderType} using {@link DefaultVertexFormat#NEW_ENTITY} for use when
* "fabulous" rendering is enabled
*/
public void register(ResourceLocation key, ChunkSectionLayer blockRenderType, RenderType entityRenderType, RenderType fabulousEntityRenderType) {
Preconditions.checkArgument(!renderTypes.containsKey(key), "Render type already registered: " + key);
Preconditions.checkArgument(entityRenderType.format() == DefaultVertexFormat.NEW_ENTITY, "The entity render type must use the NEW_ENTITY vertex format.");
Preconditions.checkArgument(fabulousEntityRenderType.format() == DefaultVertexFormat.NEW_ENTITY, "The fabulous entity render type must use the NEW_ENTITY vertex format.");
renderTypes.put(key, new RenderTypeGroup(blockRenderType, entityRenderType, fabulousEntityRenderType));
}
/**
* Registers a named {@link RenderTypeGroup}.
*
* @param name The name
* @param blockRenderType What ChunkSectionLayer to render in
* @param entityRenderType A {@link RenderType} using {@link DefaultVertexFormat#NEW_ENTITY}
*
* @deprecated Use {@link #register(ResourceLocation, ChunkSectionLayer, RenderType, RenderType)} instead.
*/
@Deprecated(forRemoval = true, since = "1.21.9") // removed in 1.21.9
public void register(String name, ChunkSectionLayer blockRenderType, RenderType entityRenderType) {
register(name, blockRenderType, entityRenderType, entityRenderType);
}
@ -56,13 +87,13 @@ public final class RegisterNamedRenderTypesEvent implements IModBusEvent {
* @param entityRenderType A {@link RenderType} using {@link DefaultVertexFormat#NEW_ENTITY}
* @param fabulousEntityRenderType A {@link RenderType} using {@link DefaultVertexFormat#NEW_ENTITY} for use when
* "fabulous" rendering is enabled
*
* @deprecated Use {@link #register(ResourceLocation, ChunkSectionLayer, RenderType, RenderType)} instead.
*/
@Deprecated(forRemoval = true, since = "1.21.9") // removed in 1.21.9
public void register(String name, ChunkSectionLayer blockRenderType, RenderType entityRenderType, RenderType fabulousEntityRenderType) {
@SuppressWarnings("removal")
var key = ResourceLocation.fromNamespaceAndPath(ModLoadingContext.get().getActiveNamespace(), name);
Preconditions.checkArgument(!renderTypes.containsKey(key), "Render type already registered: " + key);
Preconditions.checkArgument(entityRenderType.format() == DefaultVertexFormat.NEW_ENTITY, "The entity render type must use the NEW_ENTITY vertex format.");
Preconditions.checkArgument(fabulousEntityRenderType.format() == DefaultVertexFormat.NEW_ENTITY, "The fabulous entity render type must use the NEW_ENTITY vertex format.");
renderTypes.put(key, new RenderTypeGroup(blockRenderType, entityRenderType, fabulousEntityRenderType));
register(key, blockRenderType, entityRenderType, fabulousEntityRenderType);
}
}

View file

@ -39,12 +39,22 @@ public final class RegisterTextureAtlasSpriteLoadersEvent implements SelfDestruc
/**
* Registers a custom {@link ITextureAtlasSpriteLoader sprite loader}.
* @param resourceLocation The namespace should match your mod's namespace, such as your mod ID
*/
public void register(ResourceLocation resourceLocation, ITextureAtlasSpriteLoader loader) {
Preconditions.checkArgument(!loaders.containsKey(resourceLocation), "Sprite loader already registered: " + resourceLocation);
Preconditions.checkArgument(!loaders.containsValue(loader), "Sprite loader already registered as " + loaders.inverse().get(loader));
loaders.put(resourceLocation, loader);
}
/**
* Registers a custom {@link ITextureAtlasSpriteLoader sprite loader}.
* @deprecated Use {@link #register(ResourceLocation, ITextureAtlasSpriteLoader)} instead.
*/
@Deprecated(forRemoval = true, since = "1.21.9") // removed in 1.21.9
public void register(String name, ITextureAtlasSpriteLoader loader) {
@SuppressWarnings("removal")
var key = ResourceLocation.fromNamespaceAndPath(ModLoadingContext.get().getActiveNamespace(), name);
Preconditions.checkArgument(!loaders.containsKey(key), "Sprite loader already registered: " + key);
Preconditions.checkArgument(!loaders.containsValue(loader), "Sprite loader already registered as " + loaders.inverse().get(loader));
loaders.put(key, loader);
register(key, loader);
}
}

View file

@ -6,24 +6,21 @@
package net.minecraftforge.client.event.sound;
import net.minecraft.client.sounds.SoundEngine;
import net.minecraftforge.common.util.HasResult;
import net.minecraftforge.eventbus.api.bus.BusGroup;
import net.minecraftforge.eventbus.api.bus.EventBus;
import net.minecraftforge.eventbus.api.event.characteristic.Cancellable;
import net.minecraftforge.fml.LogicalSide;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
/**
* Fired when the {@link SoundEngine} is constructed or (re)loaded, such as during game initialization or when the sound
* output device is changed.
*
* <p>This event is not {@linkplain Cancellable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus() mod-specific event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
@NullMarked
public final class SoundEngineLoadEvent extends SoundEvent implements IModBusEvent {
public static EventBus<SoundEngineLoadEvent> getBus(BusGroup modBusGroup) {
return IModBusEvent.getBus(modBusGroup, SoundEngineLoadEvent.class);

View file

@ -9,6 +9,7 @@ import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.fml.Logging;
import net.minecraftforge.fml.ModList;
import net.minecraftforge.fml.ModLoader;
import net.minecraftforge.forgespi.language.ModFileScanData;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.ApiStatus;
@ -91,7 +92,7 @@ public final class CapabilityManager {
var autos = modlist.getAllScanData().stream()
.flatMap(e -> e.getAnnotations().stream())
.filter(a -> AUTO_REGISTER.equals(a.annotationType()))
.map(a -> a.clazz())
.map(ModFileScanData.AnnotationData::clazz)
.distinct()
.sorted(Comparator.comparing(Type::toString))
.toList();

View file

@ -9,6 +9,7 @@ import java.util.Objects;
import net.minecraftforge.eventbus.api.bus.BusGroup;
import net.minecraftforge.eventbus.api.bus.EventBus;
import org.jspecify.annotations.NullMarked;
import org.objectweb.asm.Type;
import net.minecraftforge.fml.event.IModBusEvent;
@ -20,6 +21,7 @@ import net.minecraftforge.fml.event.IModBusEvent;
* @deprecated Use {@link AutoRegisterCapability} annotation on your class.
*/
@Deprecated(forRemoval = true, since = "1.21")
@NullMarked
public final class RegisterCapabilitiesEvent implements IModBusEvent {
public static EventBus<RegisterCapabilitiesEvent> getBus(BusGroup modBusGroup) {
return IModBusEvent.getBus(modBusGroup, RegisterCapabilitiesEvent.class);

View file

@ -18,6 +18,7 @@ import net.minecraft.world.entity.SpawnPlacements;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraftforge.eventbus.api.bus.BusGroup;
import net.minecraftforge.eventbus.api.bus.EventBus;
import net.minecraftforge.eventbus.api.listener.Priority;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.registries.ForgeRegistries;
@ -26,20 +27,19 @@ import org.jetbrains.annotations.ApiStatus;
/**
* This event allows each {@link EntityType} to have a {@link SpawnPlacements.SpawnPredicate} registered or modified.
* Spawn Predicates are checked whenever an {@link Entity} of the given {@link EntityType} spawns in the world naturally.
*
* If registering your own entity's spawn placements, you should use {@link SpawnPlacementRegisterEvent#register(EntityType, SpawnPlacements.Type, Heightmap.Types, SpawnPlacements.SpawnPredicate, Operation)}
* <br>
* If registering your own entity's spawn placements, you should use {@link SpawnPlacementRegisterEvent#register(EntityType, SpawnPlacementType, Heightmap.Types, SpawnPlacements.SpawnPredicate, Operation)}
* So that you ensure that your entity has a heightmap type and placement type registered.
*
* <br>
* If modifying vanilla or another mod's spawn placements, you can use three operations:
* REPLACE: checked first, the last mod to replace the predicate wipes out all other predicates. Listen with a low {@link EventPriority} if you need to do this.
* OR: checked second, only one of these predicates must pass along with the original predicate
* AND: checked third, these predicates must all pass along with the original predicate
*
* <p>
* This event is not {@linkplain Cancelable cancellable} and does not {@linkplain Event.HasResult have a result}.
* <ul>
* <li>REPLACE: checked first, the last mod to replace the predicate wipes out all other predicates. Listen with a low {@link Priority} if you need to do this.</li>
* <li>OR: checked second, only one of these predicates must pass along with the original predicate</li>
* <li>AND: checked third, these predicates must all pass along with the original predicate</li>
* </ul>
* <p>
*
* Fired on the Mod bus {@link IModBusEvent}.<br>
* Fired on the Mod bus {@link IModBusEvent}.
*/
public final class SpawnPlacementRegisterEvent implements IModBusEvent {
public static EventBus<SpawnPlacementRegisterEvent> getBus(BusGroup modBusGroup) {

View file

@ -12,7 +12,6 @@ import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraftforge.eventbus.api.listener.SubscribeEvent;
import net.minecraftforge.eventbus.api.bus.BusGroup;
import net.minecraftforge.fml.javafmlmod.FMLModContainer;
import net.minecraftforge.registries.tags.ITagManager;
@ -20,7 +19,6 @@ import net.minecraftforge.registries.tags.ITagManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.invoke.MethodHandles;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
@ -322,7 +320,9 @@ public class DeferredRegister<T> {
* your language provider's equivalent.
*/
public void register(BusGroup modBusGroup) {
modBusGroup.register(EventDispatcher.LOOKUP, new EventDispatcher());
var dispatcher = new EventDispatcher();
RegisterEvent.getBus(modBusGroup).addListener(dispatcher::handleEvent);
NewRegistryEvent.getBus(modBusGroup).addListener(dispatcher::createRegistry);
}
/**
@ -376,9 +376,6 @@ public class DeferredRegister<T> {
}
private final class EventDispatcher {
private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
@SubscribeEvent
public void handleEvent(RegisterEvent event) {
if (event.getRegistryKey().equals(registryKey)) {
seenRegisterEvent = true;
@ -392,7 +389,6 @@ public class DeferredRegister<T> {
}
}
@SubscribeEvent
public void createRegistry(NewRegistryEvent event) {
if (registryFactory != null)
event.create(registryFactory.get(), DeferredRegister.this::onFill);

View file

@ -103,7 +103,7 @@ public abstract class BaseTestMod {
}
private final class LookupHelper {
private static final class LookupHelper {
private static final Lookup INSTANCE;
static {
try {