Move Registry Events to the mod event bus.

Add infrastructure to allow parallel or synchronous dispatch of
mod events, and pass-through of events to the dispatch system.

Signed-off-by: cpw <cpw+github@weeksfamily.ca>
This commit is contained in:
cpw 2019-01-19 23:26:25 -05:00
parent ff2e35c243
commit f4d2d1a24b
No known key found for this signature in database
GPG key ID: 8EB3DF749553B1B7
9 changed files with 162 additions and 88 deletions

View file

@ -260,7 +260,7 @@ project(':forge') {
installer 'org.ow2.asm:asm-tree:6.2'
installer 'cpw.mods:modlauncher:0.6.0'
installer 'net.minecraftforge:accesstransformers:0.14.+:shadowed'
installer 'net.minecraftforge:eventbus:0.3.+:service'
installer 'net.minecraftforge:eventbus:0.6.+:service'
installer 'net.minecraftforge:forgespi:0.2.+'
installer 'net.minecraftforge:coremods:0.2.+'
installer 'com.electronwill.night-config:core:3.4.2'

View file

@ -39,7 +39,8 @@ import static net.minecraftforge.fml.Logging.LOADING;
/**
* Automatic eventbus subscriber - reads {@link net.minecraftforge.fml.common.Mod.EventBusSubscriber}
* annotations and passes the class instances to the {@link net.minecraftforge.common.MinecraftForge.EVENT_BUS}
* annotations and passes the class instances to the {@link net.minecraftforge.fml.common.Mod.EventBusSubscriber.Bus}
* defined by the annotation. Defaults to {@link MinecraftForge#EVENT_BUS}
*/
public class AutomaticEventSubscriber
{
@ -60,10 +61,11 @@ public class AutomaticEventSubscriber
final EnumSet<Dist> sides = sidesValue.stream().map(eh -> Dist.valueOf(eh.getValue())).
collect(Collectors.toCollection(() -> EnumSet.noneOf(Dist.class)));
final String modId = (String)ad.getAnnotationData().getOrDefault("modId", mod.getModId());
final Mod.EventBusSubscriber.Bus busTarget = (Mod.EventBusSubscriber.Bus)ad.getAnnotationData().getOrDefault("bus", Mod.EventBusSubscriber.Bus.FORGE);
if (Objects.equals(mod.getModId(), modId) && sides.contains(FMLEnvironment.dist)) {
try
{
MinecraftForge.EVENT_BUS.register(Class.forName(ad.getClassType().getClassName(), true, loader));
busTarget.bus().get().register(Class.forName(ad.getClassType().getClassName(), true, loader));
}
catch (ClassNotFoundException e)
{

View file

@ -19,40 +19,72 @@
package net.minecraftforge.fml;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.event.lifecycle.ModLifecycleEvent;
import net.minecraftforge.fml.javafmlmod.FMLModContainer;
import net.minecraftforge.forgespi.language.ILifecycleEvent;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
public enum LifecycleEventProvider
{
CONSTRUCT(()->new LifecycleEvent(ModLoadingStage.CONSTRUCT)),
CREATE_REGISTRIES(()->new LifecycleEvent(ModLoadingStage.CREATE_REGISTRIES), ModList.inlineDispatcher),
LOAD_REGISTRIES(()->new LifecycleEvent(ModLoadingStage.LOAD_REGISTRIES, LifecycleEvent.Progression.STAY), ModList.inlineDispatcher),
SETUP(()->new LifecycleEvent(ModLoadingStage.COMMON_SETUP)),
SIDED_SETUP(()->new LifecycleEvent(ModLoadingStage.SIDED_SETUP)),
ENQUEUE_IMC(()->new LifecycleEvent(ModLoadingStage.ENQUEUE_IMC)),
PROCESS_IMC(()->new LifecycleEvent(ModLoadingStage.PROCESS_IMC)),
COMPLETE(()->new LifecycleEvent(ModLoadingStage.COMPLETE));
public void dispatch(Consumer<List<ModLoadingException>> errorHandler) {
ModList.get().dispatchLifeCycleEvent(this.event.get(), errorHandler);
}
private final Supplier<? extends LifecycleEvent> event;
private final BiConsumer<LifecycleEvent, Consumer<List<ModLoadingException>>> eventDispatcher;
private Supplier<Event> customEventSupplier;
private LifecycleEvent.Progression progression = LifecycleEvent.Progression.NEXT;
LifecycleEventProvider(Supplier<? extends LifecycleEvent> e)
{
event = e;
this(e, ModList.parallelDispatcher);
}
LifecycleEventProvider(Supplier<? extends LifecycleEvent> e, BiConsumer<LifecycleEvent, Consumer<List<ModLoadingException>>> eventDispatcher)
{
this.event = e;
this.eventDispatcher = eventDispatcher;
}
public void setCustomEventSupplier(Supplier<Event> eventSupplier) {
this.customEventSupplier = eventSupplier;
}
public void changeProgression(LifecycleEvent.Progression progression) {
this.progression = progression;
}
public void dispatch(Consumer<List<ModLoadingException>> errorHandler) {
final LifecycleEvent lifecycleEvent = this.event.get();
lifecycleEvent.setCustomEventSupplier(this.customEventSupplier);
lifecycleEvent.changeProgression(this.progression);
this.eventDispatcher.accept(lifecycleEvent, errorHandler);
}
public static class LifecycleEvent implements ILifecycleEvent<LifecycleEvent> {
private final ModLoadingStage stage;
public LifecycleEvent(ModLoadingStage stage)
private Supplier<Event> customEventSupplier;
private Progression progression;
LifecycleEvent(final ModLoadingStage stage)
{
this(stage, Progression.NEXT);
}
LifecycleEvent(final ModLoadingStage stage, final Progression progression) {
this.stage = stage;
this.progression = progression;
}
public ModLoadingStage fromStage()
@ -62,12 +94,42 @@ public enum LifecycleEventProvider
public ModLoadingStage toStage()
{
return ModLoadingStage.values()[this.stage.ordinal()+1];
return progression.apply(this.stage);
}
public ModLifecycleEvent buildModEvent(FMLModContainer fmlModContainer)
public void setCustomEventSupplier(Supplier<Event> customEventSupplier) {
this.customEventSupplier = customEventSupplier;
}
public void changeProgression(Progression p) {
this.progression = p;
}
public Event getOrBuildEvent(FMLModContainer fmlModContainer)
{
if (customEventSupplier!=null) return customEventSupplier.get();
return stage.getModEvent(fmlModContainer);
}
@Override
public String toString() {
return "LifecycleEvent:"+stage;
}
public enum Progression {
NEXT((current)-> ModLoadingStage.values()[current.ordinal()+1]),
STAY(Function.identity());
private final Function<ModLoadingStage, ModLoadingStage> edge;
Progression(Function<ModLoadingStage, ModLoadingStage> edge) {
this.edge = edge;
}
public ModLoadingStage apply(ModLoadingStage in) {
return this.edge.apply(in);
}
}
}
}

View file

@ -35,6 +35,7 @@ import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.FutureTask;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
@ -74,6 +75,10 @@ public class ModList
return INSTANCE;
}
static BiConsumer<LifecycleEventProvider.LifecycleEvent, Consumer<List<ModLoadingException>>> inlineDispatcher = (event, errors) -> ModList.get().dispatchSynchronousEvent(event, errors);
static BiConsumer<LifecycleEventProvider.LifecycleEvent, Consumer<List<ModLoadingException>>> parallelDispatcher = (event, errors) -> ModList.get().dispatchParallelEvent(event, errors);
public static ModList get() {
return INSTANCE;
}
@ -83,13 +88,19 @@ public class ModList
return modFiles;
}
public ModFileInfo getModFileById(String modid)
{
return this.fileById.get(modid);
}
public void dispatchLifeCycleEvent(LifecycleEventProvider.LifecycleEvent lifecycleEvent, final Consumer<List<ModLoadingException>> errorHandler) {
private void dispatchSynchronousEvent(LifecycleEventProvider.LifecycleEvent lifecycleEvent, final Consumer<List<ModLoadingException>> errorHandler) {
LOGGER.info(LOADING, "Dispatching synchronous event {}", lifecycleEvent);
FMLLoader.getLanguageLoadingProvider().forEach(lp->lp.consumeLifecycleEvent(()->lifecycleEvent));
this.mods.forEach(m->m.transitionState(lifecycleEvent, errorHandler));
FMLLoader.getLanguageLoadingProvider().forEach(lp->lp.consumeLifecycleEvent(()->lifecycleEvent));
}
private void dispatchParallelEvent(LifecycleEventProvider.LifecycleEvent lifecycleEvent, final Consumer<List<ModLoadingException>> errorHandler) {
LOGGER.info(LOADING, "Dispatching parallel event {}", lifecycleEvent);
FMLLoader.getLanguageLoadingProvider().forEach(lp->lp.consumeLifecycleEvent(()->lifecycleEvent));
DeferredWorkQueue.clear();
try
@ -104,7 +115,7 @@ public class ModList
FMLLoader.getLanguageLoadingProvider().forEach(lp->lp.consumeLifecycleEvent(()->lifecycleEvent));
}
public void setLoadedMods(final List<ModContainer> modContainers)
void setLoadedMods(final List<ModContainer> modContainers)
{
this.mods = modContainers;
this.indexedMods = modContainers.stream().collect(Collectors.toMap(ModContainer::getModId, Function.identity()));

View file

@ -123,10 +123,10 @@ public class ModLoader
}
modList.setLoadedMods(modContainerStream.collect(Collectors.toList()));
dispatchAndHandleError(LifecycleEventProvider.CONSTRUCT);
GameData.fireCreateRegistryEvents();
GameData.fireCreateRegistryEvents(LifecycleEventProvider.CREATE_REGISTRIES, this::dispatchAndHandleError);
ObjectHolderRegistry.findObjectHolders();
CapabilityManager.INSTANCE.injectCapabilities(modList.getAllScanData());
GameData.fireRegistryEvents();
GameData.fireRegistryEvents(rl->true, LifecycleEventProvider.LOAD_REGISTRIES, this::dispatchAndHandleError);
dispatchAndHandleError(LifecycleEventProvider.SETUP);
DistExecutor.runWhenOn(Dist.CLIENT, ModLoader::fireClientEvents);
dispatchAndHandleError(LifecycleEventProvider.SIDED_SETUP);

View file

@ -29,13 +29,14 @@ public enum ModLoadingStage
ERROR(null),
VALIDATE(null),
CONSTRUCT(null),
CREATE_REGISTRIES(null),
LOAD_REGISTRIES(null),
COMMON_SETUP(()-> FMLCommonSetupEvent::new),
SIDED_SETUP(SidedProvider.SIDED_SETUP_EVENT::get),
ENQUEUE_IMC(()-> InterModEnqueueEvent::new),
PROCESS_IMC(()-> InterModProcessEvent::new),
COMPLETE(()-> FMLLoadCompleteEvent::new),
DONE(null);
private final Supplier<Function<ModContainer, ModLifecycleEvent>> modLifecycleEventFunction;
ModLoadingStage(Supplier<Function<ModContainer, ModLifecycleEvent>> modLifecycleEventFunction)

View file

@ -23,26 +23,19 @@ import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.function.Supplier;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.fml.event.lifecycle.FMLFingerprintViolationEvent;
import net.minecraftforge.fml.event.lifecycle.InterModProcessEvent;
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
import net.minecraftforge.fml.event.server.FMLServerAboutToStartEvent;
import net.minecraftforge.fml.event.server.FMLServerStartedEvent;
import net.minecraftforge.fml.event.server.FMLServerStartingEvent;
import net.minecraftforge.fml.event.server.FMLServerStoppedEvent;
import net.minecraftforge.fml.event.server.FMLServerStoppingEvent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.fml.event.lifecycle.ModLifecycleEvent;
import net.minecraftforge.fml.event.lifecycle.InterModEnqueueEvent;
import net.minecraftforge.fml.common.registry.GameRegistry;
import net.minecraftforge.fml.javafmlmod.FMLModLoadingContext;
/**
* This defines a Mod to FML.
* Any class found with this annotation applied will be loaded as a Mod. The instance that is loaded will
* represent the mod to other Mods in the system. It will be sent various subclasses of {@link ModLifecycleEvent}
* at pre-defined times during the loading of the game, based on where you have applied the {@link EventHandler}
* annotation.
* at pre-defined times during the loading of the game.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@ -56,63 +49,60 @@ public @interface Mod
* By default, you will have a resource domain that matches the modid. All these uses require that constraints are imposed on the format of the modid.
*/
String value();
/**
* Marks the associated method as handling an FML lifecycle event.
* The method must have a single parameter, one of the following types. This annotation
* replaces the multiple different annotations that previously were used.
*
* Current event classes. This first section is standard lifecycle events. They are dispatched
* at various phases as the game starts. Each event should have information useful to that
* phase of the lifecycle. They are fired in this order.
*
* These suggestions are mostly just suggestions on what to do in each event.
* <ul>
* <li> {@link FMLCommonSetupEvent} : Run before anything else. Read your config, create blocks,
* items, etc, and register them with the {@link GameRegistry}.</li>
* <li> {@link InterModEnqueueEvent} : Do your mod setup. Build whatever data structures you care about. Register recipes,
* send {@link FMLInterModComms} messages to other mods.</li>
* <li> {@link InterModProcessEvent} : Handle interaction with other mods, complete your setup based on this.</li>
* </ul>
* <p>These are the server lifecycle events. They are fired whenever a server is running, or about to run. Each time a server
* starts they will be fired in this sequence.
* <ul>
* <li> {@link FMLServerAboutToStartEvent} : Use if you need to handle something before the server has even been created.</li>
* <li> {@link FMLServerStartingEvent} : Do stuff you need to do to set up the server. register commands, tweak the server.</li>
* <li> {@link FMLServerStartedEvent} : Do what you need to with the running server.</li>
* <li> {@link FMLServerStoppingEvent} : Do what you need to before the server has started it's shutdown sequence.</li>
* <li> {@link FMLServerStoppedEvent} : Do whatever cleanup you need once the server has shutdown. Generally only useful
* on the integrated server.</li>
* </ul>
* The second set of events are more specialized, for receiving notification of specific
* information.
* <ul>
* <li> {@link FMLFingerprintViolationEvent} : Sent just before {@link FMLCommonSetupEvent}
* if something is wrong with your mod signature</li>
* <li> {@link IMCEvent} : Sent just after {@link InterModEnqueueEvent} if you have IMC messages waiting
* from other mods</li>
* </ul>
*
* @author cpw
*
*/
@Deprecated
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface EventHandler{}
/**
* A class which will be subscribed to {@link net.minecraftforge.common.MinecraftForge.EVENT_BUS} at mod construction time.
* Annotate a class which will be subscribed to an Event Bus at mod construction time.
* Defaults to subscribing the current modid to the {@link MinecraftForge#EVENT_BUS}
* on both sides.
*
* @see Bus
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface EventBusSubscriber {
/**
* Specify targets to load this event subscriber on. Can be used to avoid loading Client specific events
* on a dedicated server, for example.
*
* @return an array of Dist to load this event subscriber on
*/
Dist[] value() default { Dist.CLIENT, Dist.DEDICATED_SERVER };
/**
* Optional value, only nessasary if tis annotation is not on the same class that has a @Mod annotation.
* Optional value, only necessary if this annotation is not on the same class that has a @Mod annotation.
* Needed to prevent early classloading of classes not owned by your mod.
* @return
* @return a modid
*/
String modid() default "";
/**
* Specify an alternative bus to listen to
*
* @return the bus you wish to listen to
*/
Bus bus() default Bus.FORGE;
enum Bus {
/**
* The main Forge Event Bus.
* @see MinecraftForge#EVENT_BUS
*/
FORGE(()-> MinecraftForge.EVENT_BUS),
/**
* The mod specific Event bus.
* @see FMLModLoadingContext#getModEventBus()
*/
MOD(()-> FMLModLoadingContext.get().getModEventBus());
private final Supplier<IEventBus> busSupplier;
Bus(final Supplier<IEventBus> eventBusSupplier) {
this.busSupplier = eventBusSupplier;
}
public Supplier<IEventBus> bus() {
return busSupplier;
}
}
}
}

View file

@ -29,7 +29,6 @@ import net.minecraftforge.fml.ModContainer;
import net.minecraftforge.fml.ModLoadingException;
import net.minecraftforge.fml.ModLoadingStage;
import net.minecraftforge.fml.ModThreadContext;
import net.minecraftforge.fml.event.lifecycle.ModLifecycleEvent;
import net.minecraftforge.forgespi.language.IModInfo;
import net.minecraftforge.forgespi.language.ModFileScanData;
@ -54,6 +53,8 @@ public class FMLModContainer extends ModContainer
LOGGER.debug("Creating FMLModContainer instance for {} with classLoader {} & {}", className, modClassLoader, getClass().getClassLoader());
this.scanResults = modFileScanResults;
triggerMap.put(ModLoadingStage.CONSTRUCT, dummy().andThen(this::beforeEvent).andThen(this::constructMod).andThen(this::afterEvent));
triggerMap.put(ModLoadingStage.CREATE_REGISTRIES, dummy().andThen(this::beforeEvent).andThen(this::fireEvent).andThen(this::afterEvent));
triggerMap.put(ModLoadingStage.LOAD_REGISTRIES, dummy().andThen(this::beforeEvent).andThen(this::fireEvent).andThen(this::afterEvent));
triggerMap.put(ModLoadingStage.COMMON_SETUP, dummy().andThen(this::beforeEvent).andThen(this::preinitMod).andThen(this::fireEvent).andThen(this::afterEvent));
triggerMap.put(ModLoadingStage.SIDED_SETUP, dummy().andThen(this::beforeEvent).andThen(this::fireEvent).andThen(this::afterEvent));
triggerMap.put(ModLoadingStage.ENQUEUE_IMC, dummy().andThen(this::beforeEvent).andThen(this::initMod).andThen(this::fireEvent).andThen(this::afterEvent));
@ -96,8 +97,8 @@ public class FMLModContainer extends ModContainer
}
private void fireEvent(LifecycleEventProvider.LifecycleEvent lifecycleEvent) {
final ModLifecycleEvent event = lifecycleEvent.buildModEvent(this);
LOGGER.debug(LOADING, "Firing event for modid {} : {}", this.getModId(), event.getClass().getName());
final Event event = lifecycleEvent.getOrBuildEvent(this);
LOGGER.info(LOADING, "Firing event for modid {} : {}", this.getModId(), event.getClass().getName());
try
{
eventBus.post(event);

View file

@ -41,6 +41,7 @@ import net.minecraft.world.biome.Biome;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.RegistryEvent;
import net.minecraftforge.event.RegistryEvent.MissingMappings;
import net.minecraftforge.fml.LifecycleEventProvider;
import net.minecraftforge.fml.ModThreadContext;
import net.minecraftforge.fml.StartupQuery;
import net.minecraftforge.fml.common.EnhancedRuntimeException;
@ -61,6 +62,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@ -779,14 +781,17 @@ public class GameData
MinecraftForge.EVENT_BUS.post(new RegistryEvent.NewRegistry());
}
public static void fireRegistryEvents()
{
fireRegistryEvents(rl -> true);
public static void fireCreateRegistryEvents(final LifecycleEventProvider lifecycleEventProvider, final Consumer<LifecycleEventProvider> eventDispatcher) {
final RegistryEvent.NewRegistry newRegistryEvent = new RegistryEvent.NewRegistry();
lifecycleEventProvider.setCustomEventSupplier(()->newRegistryEvent);
eventDispatcher.accept(lifecycleEventProvider);
}
public static void fireRegistryEvents(Predicate<ResourceLocation> filter)
public static void fireRegistryEvents(Predicate<ResourceLocation> filter, final LifecycleEventProvider lifecycleEventProvider, final Consumer<LifecycleEventProvider> eventDispatcher)
{
List<ResourceLocation> keys = Lists.newArrayList(RegistryManager.ACTIVE.registries.keySet());
Collections.sort(keys, (o1, o2) -> o1.toString().compareToIgnoreCase(o2.toString()));
keys.sort((o1, o2) -> String.valueOf(o1).compareToIgnoreCase(String.valueOf(o2)));
//Move Blocks to first, and Items to second.
keys.remove(BLOCKS);
@ -794,16 +799,18 @@ public class GameData
keys.add(0, BLOCKS);
keys.add(1, ITEMS);
for (ResourceLocation rl : keys)
{
for (int i = 0, keysSize = keys.size(); i < keysSize; i++) {
final ResourceLocation rl = keys.get(i);
if (!filter.test(rl)) continue;
ForgeRegistry<?> reg = RegistryManager.ACTIVE.getRegistry(rl);
reg.unfreeze();
MinecraftForge.EVENT_BUS.post(reg.getRegisterEvent(rl));
final RegistryEvent.Register<?> registerEvent = reg.getRegisterEvent(rl);
lifecycleEventProvider.setCustomEventSupplier(() -> registerEvent);
if (i== keysSize-1) lifecycleEventProvider.changeProgression(LifecycleEventProvider.LifecycleEvent.Progression.NEXT);
eventDispatcher.accept(lifecycleEventProvider);
reg.freeze();
LOGGER.info("{} Applying holder lookups", rl.toString());
ObjectHolderRegistry.applyObjectHolders(key -> rl.equals(key));
ObjectHolderRegistry.applyObjectHolders(rl::equals);
LOGGER.info("{} Holder lookups applied", rl.toString());
}
}