From 19a38f5cb97d67a17e18ef7cf1df6990386b68f7 Mon Sep 17 00:00:00 2001 From: cpw Date: Tue, 28 Jul 2020 19:17:29 -0400 Subject: [PATCH 1/4] Revert "Shut down all other mod handlers if the loading cycle errors. This prevents other mods from throwing errors and being blamed for initial cause. This is a temporary hack until cpw re-writes the mod event dispatcher." This reverts commit 7592bbe8 Signed-off-by: cpw --- src/main/java/net/minecraftforge/fml/ModContainer.java | 8 -------- src/main/java/net/minecraftforge/fml/ModList.java | 1 - .../minecraftforge/fml/javafmlmod/FMLModContainer.java | 1 - 3 files changed, 10 deletions(-) diff --git a/src/main/java/net/minecraftforge/fml/ModContainer.java b/src/main/java/net/minecraftforge/fml/ModContainer.java index 4869f7557c..daeb247b0d 100644 --- a/src/main/java/net/minecraftforge/fml/ModContainer.java +++ b/src/main/java/net/minecraftforge/fml/ModContainer.java @@ -104,7 +104,6 @@ public abstract class ModContainer */ public final void transitionState(LifecycleEventProvider.LifecycleEvent event, Consumer> errorHandler) { - if (shutdown) return; //TODO: Remove if (modLoadingStage == event.fromStage()) { try @@ -166,11 +165,4 @@ public abstract class ModContainer * @param e Event to accept */ protected void acceptEvent(Event e) {} - - //TEMPORARY INTERNAL FUNCTION UNTIL net.minecraftforge.fml.ModList.dispatchParallelEvent is fixed. - @Deprecated protected boolean shutdown = false; - @Deprecated protected void shutdown() - { - this.shutdown = true; - } } diff --git a/src/main/java/net/minecraftforge/fml/ModList.java b/src/main/java/net/minecraftforge/fml/ModList.java index 929870e3c1..c587048fd2 100644 --- a/src/main/java/net/minecraftforge/fml/ModList.java +++ b/src/main/java/net/minecraftforge/fml/ModList.java @@ -141,7 +141,6 @@ public class ModList catch (InterruptedException | ExecutionException e) { LOGGER.error(LOADING, "Encountered an exception during parallel processing - sleeping 10 seconds to wait for jobs to finish", e); - this.mods.forEach(ModContainer::shutdown); //Prevent all future events from being sent out. errorHandler.accept(Collections.singletonList(new UncaughtModLoadingException(lifecycleEvent.fromStage(), e))); modLoadingThreadPool.awaitQuiescence(10, TimeUnit.SECONDS); if (!modLoadingThreadPool.isQuiescent()) { diff --git a/src/main/java/net/minecraftforge/fml/javafmlmod/FMLModContainer.java b/src/main/java/net/minecraftforge/fml/javafmlmod/FMLModContainer.java index cfc0e14431..627196af79 100644 --- a/src/main/java/net/minecraftforge/fml/javafmlmod/FMLModContainer.java +++ b/src/main/java/net/minecraftforge/fml/javafmlmod/FMLModContainer.java @@ -171,7 +171,6 @@ public class FMLModContainer extends ModContainer @Override protected void acceptEvent(final Event e) { - if (this.shutdown) return; this.eventBus.post(e); } From ff752ab14b66de510c056384a178bd98faba7193 Mon Sep 17 00:00:00 2001 From: cpw Date: Tue, 28 Jul 2020 19:17:35 -0400 Subject: [PATCH 2/4] Revert "Properly shutdown FMLModContainer's event bus when an error in a lifecycle event is detected." This reverts commit 30bad1e2 Signed-off-by: cpw --- .../net/minecraftforge/common/ForgeMod.java | 10 +++- .../fml/javafmlmod/FMLModContainer.java | 48 ++----------------- 2 files changed, 12 insertions(+), 46 deletions(-) diff --git a/src/main/java/net/minecraftforge/common/ForgeMod.java b/src/main/java/net/minecraftforge/common/ForgeMod.java index b2555c9d4e..b3f1a44279 100644 --- a/src/main/java/net/minecraftforge/common/ForgeMod.java +++ b/src/main/java/net/minecraftforge/common/ForgeMod.java @@ -23,16 +23,22 @@ import net.minecraft.entity.ai.attributes.Attribute; import net.minecraft.entity.ai.attributes.RangedAttribute; import net.minecraft.util.SoundEvent; import net.minecraft.world.storage.IServerConfiguration; +import net.minecraft.world.storage.IWorldInfo; import net.minecraft.world.storage.SaveFormat; import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.*; import net.minecraftforge.fml.config.ModConfig; import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; import net.minecraftforge.fml.event.lifecycle.FMLModIdMappingEvent; +import net.minecraftforge.fml.event.server.FMLServerStartingEvent; import net.minecraftforge.fml.event.server.FMLServerStoppingEvent; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; import net.minecraftforge.fml.loading.progress.StartupMessageManager; import net.minecraftforge.registries.DeferredRegister; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.server.command.ConfigCommand; +import net.minecraftforge.server.command.ForgeCommand; import net.minecraftforge.versions.forge.ForgeVersion; import net.minecraftforge.versions.mcp.MCPVersion; @@ -106,7 +112,7 @@ public class ForgeMod implements WorldPersistenceHooks.WorldPersistenceHook final IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus(); modEventBus.addListener(this::preInit); modEventBus.addListener(this::gatherData); - modEventBus.addGenericListener(IRecipeSerializer.class, this::registerRecipeSerialziers); + modEventBus.register(this); ATTRIBUTES.register(modEventBus); MinecraftForge.EVENT_BUS.addListener(this::serverStopping); MinecraftForge.EVENT_BUS.addGenericListener(SoundEvent.class, this::missingSoundMapping); @@ -119,6 +125,7 @@ public class ForgeMod implements WorldPersistenceHooks.WorldPersistenceHook MinecraftForge.EVENT_BUS.addListener(VillagerTradingManager::loadTrades); MinecraftForge.EVENT_BUS.register(MinecraftForge.INTERNAL_HANDLER); + MinecraftForge.EVENT_BUS.register(this); } public void preInit(FMLCommonSetupEvent evt) @@ -208,6 +215,7 @@ public class ForgeMod implements WorldPersistenceHooks.WorldPersistenceHook } } + @SubscribeEvent //ModBus, can't use addListener due to nested genetics. public void registerRecipeSerialziers(RegistryEvent.Register> event) { CraftingHelper.register(AndCondition.Serializer.INSTANCE); diff --git a/src/main/java/net/minecraftforge/fml/javafmlmod/FMLModContainer.java b/src/main/java/net/minecraftforge/fml/javafmlmod/FMLModContainer.java index 627196af79..906f921b83 100644 --- a/src/main/java/net/minecraftforge/fml/javafmlmod/FMLModContainer.java +++ b/src/main/java/net/minecraftforge/fml/javafmlmod/FMLModContainer.java @@ -30,15 +30,13 @@ import net.minecraftforge.fml.ModContainer; import net.minecraftforge.fml.ModLoadingException; import net.minecraftforge.fml.ModLoadingStage; import net.minecraftforge.fml.event.lifecycle.IModBusEvent; +import net.minecraftforge.fml.event.lifecycle.ModLifecycleEvent; import net.minecraftforge.forgespi.language.IModInfo; import net.minecraftforge.forgespi.language.ModFileScanData; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; import java.util.Optional; import java.util.function.Consumer; @@ -92,7 +90,7 @@ public class FMLModContainer extends ModContainer } - private Consumer dummy() { return new ErroringConsumer<>(); } + private Consumer dummy() { return (s) -> {}; } private void onEventFailed(IEventBus iEventBus, Event event, IEventListener[] iEventListeners, int i, Throwable throwable) { @@ -120,7 +118,6 @@ public class FMLModContainer extends ModContainer private void afterEvent(LifecycleEventProvider.LifecycleEvent lifecycleEvent) { if (getCurrentState() == ModLoadingStage.ERROR) { LOGGER.error(LOADING,"An error occurred while dispatching event {} to {}", lifecycleEvent.fromStage(), getModId()); - this.eventBus.shutdown(); } } @@ -169,46 +166,7 @@ public class FMLModContainer extends ModContainer } @Override - protected void acceptEvent(final Event e) - { + protected void acceptEvent(final Event e) { this.eventBus.post(e); } - - private class ErroringConsumer implements Consumer - { - private List> children = new ArrayList<>(); - - @Override - public void accept(T t) - { - Throwable error = null; - for (Consumer child : children) - { - try - { - child.accept(t); - } - catch (Throwable e) - { - FMLModContainer.this.modLoadingStage = ModLoadingStage.ERROR; - error = e; - } - } - - if (error != null) - { - if (error instanceof RuntimeException) - throw (RuntimeException)error; - throw new RuntimeException(error); - } - } - - @Override - public ErroringConsumer andThen(Consumer after) - { - Objects.requireNonNull(after); - children.add(after); - return this; - } - } } From d76ee4edd74d98db274578922ebbf023c1d6d522 Mon Sep 17 00:00:00 2001 From: cpw Date: Sun, 30 Aug 2020 21:39:06 -0400 Subject: [PATCH 3/4] Redo event dispatch, removes a bunch of nonsense, and tries to integrate with the vanilla CF system where possible Signed-off-by: cpw --- .../fml/loading/moddiscovery/ModFile.java | 2 +- .../minecraftforge/event/RegistryEvent.java | 1 + .../minecraftforge/fml/DeferredWorkQueue.java | 187 ++++-------------- .../fml/LifecycleEventProvider.java | 154 --------------- .../net/minecraftforge/fml/ModContainer.java | 48 +++-- .../java/net/minecraftforge/fml/ModList.java | 94 +++++---- .../net/minecraftforge/fml/ModLoader.java | 169 +++++++++------- .../minecraftforge/fml/ModLoadingStage.java | 146 ++++++++++++-- .../minecraftforge/fml/ModWorkManager.java | 103 ++++++++++ .../net/minecraftforge/fml/SidedProvider.java | 7 - .../fml/client/ClientModLoader.java | 64 +++--- .../event/lifecycle/FMLClientSetupEvent.java | 6 +- .../event/lifecycle/FMLCommonSetupEvent.java | 2 +- .../event/lifecycle/FMLConstructModEvent.java | 9 + .../FMLDedicatedServerSetupEvent.java | 2 +- .../event/lifecycle/FMLLoadCompleteEvent.java | 2 +- .../event/lifecycle/InterModEnqueueEvent.java | 2 +- .../event/lifecycle/InterModProcessEvent.java | 2 +- .../event/lifecycle/ModLifecycleEvent.java | 4 + .../lifecycle/ParallelDispatchEvent.java | 26 +++ .../fml/javafmlmod/FMLModContainer.java | 73 ++----- .../fml/server/ServerModLoader.java | 7 +- .../minecraftforge/registries/GameData.java | 75 +++---- .../client/rendering/StencilEnableTest.java | 2 +- 24 files changed, 570 insertions(+), 617 deletions(-) delete mode 100644 src/main/java/net/minecraftforge/fml/LifecycleEventProvider.java create mode 100644 src/main/java/net/minecraftforge/fml/ModWorkManager.java create mode 100644 src/main/java/net/minecraftforge/fml/event/lifecycle/FMLConstructModEvent.java create mode 100644 src/main/java/net/minecraftforge/fml/event/lifecycle/ParallelDispatchEvent.java diff --git a/src/fmllauncher/java/net/minecraftforge/fml/loading/moddiscovery/ModFile.java b/src/fmllauncher/java/net/minecraftforge/fml/loading/moddiscovery/ModFile.java index b8de3fe0bc..3dd73a9374 100644 --- a/src/fmllauncher/java/net/minecraftforge/fml/loading/moddiscovery/ModFile.java +++ b/src/fmllauncher/java/net/minecraftforge/fml/loading/moddiscovery/ModFile.java @@ -79,7 +79,7 @@ public class ModFile implements IModFile { this.locator = locator; this.filePath = file; this.parser = parser; - manifest = locator.findManifest(file).orElse(DEFAULTMANIFEST); + manifest = Optional.ofNullable(locator).flatMap(l->l.findManifest(file)).orElse(DEFAULTMANIFEST); if (manifest != DEFAULTMANIFEST) LOGGER.debug(SCAN,"Mod file {} has a manifest", file); else LOGGER.debug(SCAN,"Mod file {} is missing a manifest", file); final Optional value = Optional.ofNullable(manifest.getMainAttributes().getValue(TYPE)); diff --git a/src/main/java/net/minecraftforge/event/RegistryEvent.java b/src/main/java/net/minecraftforge/event/RegistryEvent.java index b02303270e..264aae2029 100644 --- a/src/main/java/net/minecraftforge/event/RegistryEvent.java +++ b/src/main/java/net/minecraftforge/event/RegistryEvent.java @@ -46,6 +46,7 @@ public class RegistryEvent> extends GenericEven */ public static class NewRegistry extends net.minecraftforge.eventbus.api.Event implements IModBusEvent { + public NewRegistry(ModContainer mc) {} @Override public String toString() { return "RegistryEvent.NewRegistry"; diff --git a/src/main/java/net/minecraftforge/fml/DeferredWorkQueue.java b/src/main/java/net/minecraftforge/fml/DeferredWorkQueue.java index 75febfa7ae..8174ca8afc 100644 --- a/src/main/java/net/minecraftforge/fml/DeferredWorkQueue.java +++ b/src/main/java/net/minecraftforge/fml/DeferredWorkQueue.java @@ -22,22 +22,21 @@ package net.minecraftforge.fml; import static net.minecraftforge.fml.Logging.LOADING; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; -import java.util.concurrent.Callable; +import java.util.Map; +import java.util.Optional; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; import java.util.function.Supplier; -import java.util.stream.Collectors; +import net.minecraftforge.fml.event.lifecycle.ParallelDispatchEvent; import org.apache.commons.lang3.time.StopWatch; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import com.google.common.base.Function; import com.google.common.base.Stopwatch; import net.minecraftforge.forgespi.language.IModInfo; @@ -52,166 +51,60 @@ import net.minecraftforge.forgespi.language.IModInfo; *

* Exceptions from tasks will be handled gracefully, causing a mod loading * error. Tasks that take egregiously long times to run will be logged. - * - * This is being deprecated in favour of a new interface on loading events, to remove confusion about how it operates. #TODO */ -@Deprecated public class DeferredWorkQueue { - private static class TaskInfo - { - public final IModInfo owner; - public final Runnable task; - - TaskInfo(IModInfo owner, Runnable task) { - this.owner = owner; - this.task = task; - } - } - - /** - * {@link Runnable} except it allows throwing checked exceptions. - * - * Is to {@link Runnable} as {@link Callable} is to {@link Supplier}. - */ - @FunctionalInterface - public interface CheckedRunnable - { - void run() throws Exception; - } - private static final Logger LOGGER = LogManager.getLogger(); - private static ThreadLocal currentOwner = new ThreadLocal<>(); - private static List raisedExceptions = new ArrayList<>(); + private static Map, DeferredWorkQueue> workQueues = new HashMap<>(); - private static final ConcurrentLinkedDeque taskQueue = new ConcurrentLinkedDeque<>(); - private static final Executor deferredExecutor = r -> taskQueue.add(new TaskInfo(currentOwner.get().getModInfo(), r)); + private final ModLoadingStage modLoadingStage; + private final ConcurrentLinkedDeque tasks = new ConcurrentLinkedDeque<>(); - private static Function handleException() { - final ModContainer owner = currentOwner.get(); - return t -> { - LogManager.getLogger(DeferredWorkQueue.class).error("Encountered exception executing deferred work", t); - raisedExceptions.add(new ModLoadingException(owner.getModInfo(), owner.getCurrentState(), "fml.modloading.failedtoprocesswork", t)); - return null; - }; + public DeferredWorkQueue(final ModLoadingStage modLoadingStage, Class eventClass) { + this.modLoadingStage = modLoadingStage; + workQueues.put(eventClass, this); } - /** - * Run a task on the loading thread at the next available opportunity, i.e. - * after the current lifecycle event has completed. - *

- * If the task must throw a checked exception, use - * {@link #runLaterChecked(CheckedRunnable)}. - *

- * If the task has a result, use {@link #getLater(Supplier)} or - * {@link #getLaterChecked(Callable)}. - * - * @param workToEnqueue A {@link Runnable} to execute later, on the loading - * thread - * @return A {@link CompletableFuture} that completes at said time - */ - public static CompletableFuture runLater(Runnable workToEnqueue) { - currentOwner.set(ModLoadingContext.get().getActiveContainer()); - return CompletableFuture.runAsync(workToEnqueue, deferredExecutor).exceptionally(DeferredWorkQueue.handleException()); + public static Optional lookup(Optional> parallelClass) { + return Optional.ofNullable(workQueues.get(parallelClass.orElse(null))); } - /** - * Run a task on the loading thread at the next available opportunity, i.e. - * after the current lifecycle event has completed. This variant allows the task - * to throw a checked exception. - *

- * If the task does not throw a checked exception, use - * {@link #runLater(Runnable)}. - *

- * If the task has a result, use {@link #getLater(Supplier)} or - * {@link #getLaterChecked(Callable)}. - * - * @param workToEnqueue A {@link CheckedRunnable} to execute later, on the - * loading thread - * @return A {@link CompletableFuture} that completes at said time - */ - public static CompletableFuture runLaterChecked(CheckedRunnable workToEnqueue) { - return runLater(() -> { - try { - workToEnqueue.run(); - } catch (Throwable t) { - throw new CompletionException(t); - } - }); - } - - /** - * Run a task computing a result on the loading thread at the next available - * opportunity, i.e. after the current lifecycle event has completed. - *

- * If the task throws a checked exception, use - * {@link #getLaterChecked(Callable)}. - *

- * If the task does not have a result, use {@link #runLater(Runnable)} or - * {@link #runLaterChecked(CheckedRunnable)}. - * - * @param The result type of the task - * @param workToEnqueue A {@link Supplier} to execute later, on the loading - * thread - * @return A {@link CompletableFuture} that completes at said time - */ - public static CompletableFuture getLater(Supplier workToEnqueue) { - currentOwner.set(ModLoadingContext.get().getActiveContainer()); - return CompletableFuture.supplyAsync(workToEnqueue, deferredExecutor).exceptionally(DeferredWorkQueue.handleException()); - } - - /** - * Run a task computing a result on the loading thread at the next available - * opportunity, i.e. after the current lifecycle event has completed. This - * variant allows the task to throw a checked exception. - *

- * If the task does not throw a checked exception, use - * {@link #getLater(Callable)}. - *

- * If the task does not have a result, use {@link #runLater(Runnable)} or - * {@link #runLaterChecked(CheckedRunnable)}. - * - * @param The result type of the task - * @param workToEnqueue A {@link Supplier} to execute later, on the loading - * thread - * @return A {@link CompletableFuture} that completes at said time - */ - public static CompletableFuture getLaterChecked(Callable workToEnqueue) { - return getLater(() -> { - try { - return workToEnqueue.call(); - } catch (Throwable t) { - throw new CompletionException(t); - } - }); - } - - static void clear() { - taskQueue.clear(); - } - - static Executor workExecutor = Runnable::run; - - static void runTasks(ModLoadingStage fromStage, Consumer> errorHandler) { - raisedExceptions.clear(); - if (taskQueue.isEmpty()) return; // Don't log unnecessarily - LOGGER.info(LOADING, "Dispatching synchronous work after {}: {} jobs", fromStage, taskQueue.size()); + void runTasks() { + if (tasks.isEmpty()) return; + LOGGER.debug(LOADING, "Dispatching synchronous work after {}: {} jobs", modLoadingStage, tasks.size()); StopWatch globalTimer = StopWatch.createStarted(); - final CompletableFuture tasks = CompletableFuture.allOf(taskQueue.stream().map(ti -> makeRunnable(ti, workExecutor)).toArray(CompletableFuture[]::new)); - tasks.join(); - LOGGER.info(LOADING, "Synchronous work queue completed in {}", globalTimer); - errorHandler.accept(raisedExceptions); + tasks.forEach(t->makeRunnable(t, Runnable::run)); + LOGGER.debug(LOADING, "Synchronous work queue completed in {}", globalTimer); } - private static CompletableFuture makeRunnable(TaskInfo ti, Executor executor) { - return CompletableFuture.runAsync(() -> { + private static void makeRunnable(TaskInfo ti, Executor executor) { + executor.execute(() -> { Stopwatch timer = Stopwatch.createStarted(); ti.task.run(); timer.stop(); if (timer.elapsed(TimeUnit.SECONDS) >= 1) { LOGGER.warn(LOADING, "Mod '{}' took {} to run a deferred task.", ti.owner.getModId(), timer); } - }, executor); + }); } -} + + public CompletableFuture enqueueWork(final IModInfo modInfo, final Runnable work) { + return CompletableFuture.runAsync(work, r->tasks.add(new TaskInfo(modInfo, r))); + } + + public CompletableFuture enqueueWork(final IModInfo modInfo, final Supplier work) { + return CompletableFuture.supplyAsync(work, r->tasks.add(new TaskInfo(modInfo, r))); + } + + static class TaskInfo + { + public final IModInfo owner; + public final Runnable task; + + private TaskInfo(IModInfo owner, Runnable task) { + this.owner = owner; + this.task = task; + } + } +} \ No newline at end of file diff --git a/src/main/java/net/minecraftforge/fml/LifecycleEventProvider.java b/src/main/java/net/minecraftforge/fml/LifecycleEventProvider.java deleted file mode 100644 index 87654b8c1c..0000000000 --- a/src/main/java/net/minecraftforge/fml/LifecycleEventProvider.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Minecraft Forge - * Copyright (c) 2016-2020. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation version 2.1 - * of the License. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -package net.minecraftforge.fml; - -import net.minecraftforge.eventbus.api.Event; -import net.minecraftforge.forgespi.language.ILifecycleEvent; - -import java.util.List; -import java.util.concurrent.Executor; -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)), - GATHERDATA(()->new GatherDataLifecycleEvent(ModLoadingStage.GATHERDATA), ModList.inlineDispatcher); - - private final Supplier event; - private final EventHandler>,Executor, Runnable> eventDispatcher; - private Supplier customEventSupplier; - private LifecycleEvent.Progression progression = LifecycleEvent.Progression.NEXT; - - LifecycleEventProvider(Supplier e) - { - this(e, ModList.parallelDispatcher); - } - - LifecycleEventProvider(Supplier e, EventHandler>,Executor, Runnable> eventDispatcher) - { - this.event = e; - this.eventDispatcher = eventDispatcher; - } - - public void setCustomEventSupplier(Supplier eventSupplier) { - this.customEventSupplier = eventSupplier; - } - - public void changeProgression(LifecycleEvent.Progression progression) { - this.progression = progression; - } - - public void dispatch(Consumer> errorHandler, final Executor executor, final Runnable ticker) { - final LifecycleEvent lifecycleEvent = this.event.get(); - lifecycleEvent.setCustomEventSupplier(this.customEventSupplier); - lifecycleEvent.changeProgression(this.progression); - this.eventDispatcher.dispatchEvent(lifecycleEvent, errorHandler, executor, ticker); - } - - - public static class LifecycleEvent implements ILifecycleEvent { - private final ModLoadingStage stage; - private Supplier 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() - { - return this.stage; - } - - public ModLoadingStage toStage() - { - return progression.apply(this.stage); - } - - public void setCustomEventSupplier(Supplier customEventSupplier) { - this.customEventSupplier = customEventSupplier; - } - - public void changeProgression(Progression p) { - this.progression = p; - } - - public Event getOrBuildEvent(ModContainer modContainer) - { - if (customEventSupplier!=null) return customEventSupplier.get(); - - return stage.getModEvent(modContainer); - } - - @Override - public String toString() { - return "LifecycleEvent:"+stage; - } - - public enum Progression { - NEXT((current)-> ModLoadingStage.values()[current.ordinal()+1]), - STAY(Function.identity()); - - private final Function edge; - - Progression(Function edge) { - this.edge = edge; - } - - public ModLoadingStage apply(ModLoadingStage in) { - return this.edge.apply(in); - } - } - } - - private static class GatherDataLifecycleEvent extends LifecycleEvent { - GatherDataLifecycleEvent(final ModLoadingStage stage) { - super(stage); - } - - @Override - public ModLoadingStage fromStage() { - return ModLoadingStage.COMMON_SETUP; - } - - @Override - public ModLoadingStage toStage() { - return ModLoadingStage.DONE; - } - } - - public interface EventHandler>, V extends Executor, R extends Runnable> { - void dispatchEvent(T event, U exceptionHandler, V executor, R ticker); - } -} diff --git a/src/main/java/net/minecraftforge/fml/ModContainer.java b/src/main/java/net/minecraftforge/fml/ModContainer.java index daeb247b0d..ad685edd39 100644 --- a/src/main/java/net/minecraftforge/fml/ModContainer.java +++ b/src/main/java/net/minecraftforge/fml/ModContainer.java @@ -21,17 +21,19 @@ package net.minecraftforge.fml; import net.minecraftforge.eventbus.api.Event; import net.minecraftforge.fml.config.ModConfig; +import net.minecraftforge.fml.event.lifecycle.IModBusEvent; import net.minecraftforge.forgespi.language.IModInfo; import org.apache.commons.lang3.tuple.Pair; -import java.util.Collections; import java.util.EnumMap; import java.util.HashMap; import java.util.IdentityHashMap; -import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Supplier; @@ -55,7 +57,7 @@ public abstract class ModContainer protected final IModInfo modInfo; protected ModLoadingStage modLoadingStage; protected Supplier contextExtension; - protected final Map> triggerMap; + protected final Map activityMap = new HashMap<>(); protected final Map> extensionPoints = new IdentityHashMap<>(); protected final EnumMap configs = new EnumMap<>(ModConfig.Type.class); @SuppressWarnings("OptionalUsedAsFieldOrParameterType") @@ -67,7 +69,6 @@ public abstract class ModContainer // TODO: Currently not reading namespace from configuration.. this.namespace = this.modId; this.modInfo = info; - this.triggerMap = new HashMap<>(); this.modLoadingStage = ModLoadingStage.CONSTRUCT; // default displaytest extension checks for version string match registerExtensionPoint(ExtensionPoint.DISPLAYTEST, () -> Pair.of(()->this.modInfo.getVersion().toString(), @@ -98,27 +99,22 @@ public abstract class ModContainer return modLoadingStage; } - /** - * Transition the mod to this event if possible. - * @param event to transition to - */ - public final void transitionState(LifecycleEventProvider.LifecycleEvent event, Consumer> errorHandler) - { - if (modLoadingStage == event.fromStage()) - { - try - { - ModLoadingContext.get().setActiveContainer(this, contextExtension.get()); - triggerMap.getOrDefault(modLoadingStage, e->{}).accept(event); - modLoadingStage = event.toStage(); - ModLoadingContext.get().setActiveContainer(null, null); - } - catch (ModLoadingException e) - { - modLoadingStage = ModLoadingStage.ERROR; - errorHandler.accept(Collections.singletonList(e)); - } - } + public static CompletableFuture buildTransitionHandler( + final ModContainer target, + final ModLoadingStage.EventGenerator eventGenerator, + final ModLoadingStage.EventDispatcher eventDispatcher, + final BiFunction stateChangeHandler, + final Executor executor) { + return CompletableFuture + .runAsync(() -> { + ModLoadingContext.get().setActiveContainer(target, target.contextExtension.get()); + target.activityMap.getOrDefault(target.modLoadingStage, ()->{}).run(); + eventDispatcher.apply(target::acceptEvent).accept(eventGenerator.apply(target)); + }, executor) + .whenComplete((mc, exception) -> { + target.modLoadingStage = stateChangeHandler.apply(target.modLoadingStage, exception); + ModLoadingContext.get().setActiveContainer(null, null); + }); } /** @@ -164,5 +160,5 @@ public abstract class ModContainer * Accept an arbitrary event for processing by the mod. Probably posted to an event bus in the lower level container. * @param e Event to accept */ - protected void acceptEvent(Event e) {} + protected void acceptEvent(T e) {} } diff --git a/src/main/java/net/minecraftforge/fml/ModList.java b/src/main/java/net/minecraftforge/fml/ModList.java index c587048fd2..e4d8e7da01 100644 --- a/src/main/java/net/minecraftforge/fml/ModList.java +++ b/src/main/java/net/minecraftforge/fml/ModList.java @@ -19,30 +19,37 @@ package net.minecraftforge.fml; -import net.minecraftforge.fml.loading.FMLConfig; +import net.minecraftforge.eventbus.api.Event; +import net.minecraftforge.fml.event.lifecycle.IModBusEvent; import net.minecraftforge.forgespi.language.ModFileScanData; -import net.minecraftforge.fml.loading.FMLLoader; import net.minecraftforge.fml.loading.moddiscovery.ModFile; import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo; import net.minecraftforge.fml.loading.moddiscovery.ModInfo; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.concurrent.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.Executor; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ForkJoinWorkerThread; import java.util.function.BiConsumer; +import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; -import static net.minecraftforge.fml.Logging.LOADING; - /** * Master list of all mods - game-side version. This is classloaded in the game scope and * can dispatch game level events as a result. @@ -56,7 +63,6 @@ public class ModList private final Map fileById; private List mods; private Map indexedMods; - private ForkJoinPool modLoadingThreadPool; private List modFileScanData; private ModList(final List modFiles, final List sortedList) @@ -68,9 +74,6 @@ public class ModList this.fileById = this.modFiles.stream().map(ModFileInfo::getMods).flatMap(Collection::stream). map(ModInfo.class::cast). collect(Collectors.toMap(ModInfo::getModId, ModInfo::getOwningFile)); - final int loadingThreadCount = FMLConfig.loadingThreadCount(); - LOGGER.debug(LOADING, "Using {} threads for parallel mod-loading", loadingThreadCount); - modLoadingThreadPool = new ForkJoinPool(loadingThreadCount, ModList::newForkJoinWorkerThread, null, false); CrashReportExtender.registerCrashCallable("Mod List", this::crashReport); } @@ -93,10 +96,6 @@ public class ModList return INSTANCE; } - static LifecycleEventProvider.EventHandler>, Executor, Runnable> inlineDispatcher = (event, errors, executor, ticker) -> ModList.get().dispatchSynchronousEvent(event, errors, executor, ticker); - - static LifecycleEventProvider.EventHandler>, Executor, Runnable> parallelDispatcher = (event, errors, executor, ticker) -> ModList.get().dispatchParallelEvent(event, errors, executor, ticker); - public static ModList get() { return INSTANCE; } @@ -119,38 +118,47 @@ public class ModList return this.fileById.get(modid); } - private void dispatchSynchronousEvent(LifecycleEventProvider.LifecycleEvent lifecycleEvent, final Consumer> errorHandler, final Executor executor, final Runnable ticker) { - LOGGER.debug(LOADING, "Dispatching synchronous event {}", lifecycleEvent); - executor.execute(ticker); - FMLLoader.getLanguageLoadingProvider().forEach(lp->lp.consumeLifecycleEvent(()->lifecycleEvent)); - this.mods.stream().forEach(m->m.transitionState(lifecycleEvent, errorHandler)); - FMLLoader.getLanguageLoadingProvider().forEach(lp->lp.consumeLifecycleEvent(()->lifecycleEvent)); + Function>> futureVisitor( + final ModLoadingStage.EventGenerator eventGenerator, + final ModLoadingStage.EventDispatcher eventManager, + final BiFunction stateChange) { + return executor -> gather( + this.mods.stream() + .map(m -> ModContainer.buildTransitionHandler(m, eventGenerator, eventManager, stateChange, executor)) + .collect(Collectors.toList())) + .thenComposeAsync(ModList::completableFutureFromExceptionList, executor); } - private void dispatchParallelEvent(LifecycleEventProvider.LifecycleEvent lifecycleEvent, final Consumer> errorHandler, final Executor executor, final Runnable ticker) { - LOGGER.debug(LOADING, "Dispatching parallel event {}", lifecycleEvent); - FMLLoader.getLanguageLoadingProvider().forEach(lp->lp.consumeLifecycleEvent(()->lifecycleEvent)); - DeferredWorkQueue.clear(); - try - { - final ForkJoinTask parallelTask = modLoadingThreadPool.submit(() -> this.mods.parallelStream().forEach(m -> m.transitionState(lifecycleEvent, errorHandler))); - while (ticker != null && !parallelTask.isDone()) { - executor.execute(ticker); - } - parallelTask.get(); + static CompletionStage> completableFutureFromExceptionList(List> t) { + if (t.stream().noneMatch(e->e.getValue()!=null)) { + return CompletableFuture.completedFuture(Collections.emptyList()); + } else { + final List throwables = t.stream().filter(e -> e.getValue() != null).map(Map.Entry::getValue).collect(Collectors.toList()); + CompletableFuture> cf = new CompletableFuture<>(); + final RuntimeException accumulator = new RuntimeException(); + cf.completeExceptionally(accumulator); + throwables.forEach(exception -> { + if (exception instanceof CompletionException) { + exception = exception.getCause(); + } + if (exception.getSuppressed().length!=0) { + Arrays.stream(exception.getSuppressed()).forEach(accumulator::addSuppressed); + } else { + accumulator.addSuppressed(exception); + } + }); + return cf; } - catch (InterruptedException | ExecutionException e) - { - LOGGER.error(LOADING, "Encountered an exception during parallel processing - sleeping 10 seconds to wait for jobs to finish", e); - errorHandler.accept(Collections.singletonList(new UncaughtModLoadingException(lifecycleEvent.fromStage(), e))); - modLoadingThreadPool.awaitQuiescence(10, TimeUnit.SECONDS); - if (!modLoadingThreadPool.isQuiescent()) { - LOGGER.fatal(LOADING, "The parallel pool has failed to quiesce correctly, forcing a shutdown. There is something really wrong here"); - modLoadingThreadPool.shutdownNow(); - throw new RuntimeException("Forge played \"STOP IT NOW MODS!\" - it was \"NOT VERY EFFECTIVE\""); - } - } - DeferredWorkQueue.runTasks(lifecycleEvent.fromStage(), errorHandler); - FMLLoader.getLanguageLoadingProvider().forEach(lp->lp.consumeLifecycleEvent(()->lifecycleEvent)); + } + + static CompletableFuture>> gather(List> futures) { + List> list = new ArrayList<>(futures.size()); + CompletableFuture[] results = new CompletableFuture[futures.size()]; + futures.forEach(future -> { + int i = list.size(); + list.add(null); + results[i] = future.whenComplete((result, exception) -> list.set(i, new AbstractMap.SimpleImmutableEntry<>(result, exception))); + }); + return CompletableFuture.allOf(results).handle((r, th)->null).thenApply(res -> list); } void setLoadedMods(final List modContainers) diff --git a/src/main/java/net/minecraftforge/fml/ModLoader.java b/src/main/java/net/minecraftforge/fml/ModLoader.java index 7c1b25796d..c1c7c2b435 100644 --- a/src/main/java/net/minecraftforge/fml/ModLoader.java +++ b/src/main/java/net/minecraftforge/fml/ModLoader.java @@ -29,6 +29,7 @@ import net.minecraftforge.eventbus.api.Event; import net.minecraftforge.fml.config.ConfigTracker; import net.minecraftforge.fml.config.ModConfig; import net.minecraftforge.fml.event.lifecycle.GatherDataEvent; +import net.minecraftforge.fml.event.lifecycle.IModBusEvent; import net.minecraftforge.fml.event.lifecycle.ModLifecycleEvent; import net.minecraftforge.fml.loading.FMLLoader; import net.minecraftforge.fml.loading.FMLPaths; @@ -48,12 +49,19 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.nio.file.Path; -import java.util.*; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import java.util.concurrent.Executor; -import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import java.util.function.Function; -import java.util.function.Supplier; import java.util.stream.Collectors; import static net.minecraftforge.fml.Logging.CORE; @@ -147,37 +155,22 @@ public class ModLoader return INSTANCE == null ? INSTANCE = new ModLoader() : INSTANCE; } - public void loadMods(Executor mainThreadExecutor, Consumer>> preSidedRunnable, Consumer>> postSidedRunnable) { - DeferredWorkQueue.workExecutor = mainThreadExecutor; - statusConsumer.ifPresent(c->c.accept("Loading mod config")); - DistExecutor.runWhenOn(Dist.CLIENT, ()->()-> ConfigTracker.INSTANCE.loadConfigs(ModConfig.Type.CLIENT, FMLPaths.CONFIGDIR.get())); - ConfigTracker.INSTANCE.loadConfigs(ModConfig.Type.COMMON, FMLPaths.CONFIGDIR.get()); - statusConsumer.ifPresent(c->c.accept("Mod setup: SETUP")); - dispatchAndHandleError(LifecycleEventProvider.SETUP, mainThreadExecutor, null); - statusConsumer.ifPresent(c->c.accept("Mod setup: SIDED SETUP")); - mainThreadExecutor.execute(()->preSidedRunnable.accept(c->ModList.get().forEachModContainer((mi,mc)->mc.acceptEvent(c.get())))); - dispatchAndHandleError(LifecycleEventProvider.SIDED_SETUP, mainThreadExecutor, null); - mainThreadExecutor.execute(()->postSidedRunnable.accept(c->ModList.get().forEachModContainer((mi,mc)->mc.acceptEvent(c.get())))); - statusConsumer.ifPresent(c->c.accept("Mod setup complete")); - } - - private static class SpacedRunnable implements Executor { - private static final long FIFTYMILLIS = TimeUnit.MILLISECONDS.toNanos(50); - private long next = System.nanoTime() + FIFTYMILLIS; - @Override - public void execute(final Runnable command) { - final long time = System.nanoTime(); - if (next < time) { - command.run(); - next = time + FIFTYMILLIS; - } - } - } - public void gatherAndInitializeMods(final Runnable ticker) { + /** + * Run on the primary starting thread by ClientModLoader and ServerModLoader + * @param syncExecutor An executor to run tasks on the main thread + * @param parallelExecutor An executor to run tasks on a parallel loading thread pool + * @param periodicTask Optional periodic task to perform on the main thread while other activities run + */ + public void gatherAndInitializeMods(final ModWorkManager.DrivenExecutor syncExecutor, final Executor parallelExecutor, final Runnable periodicTask) { statusConsumer.ifPresent(c->c.accept("Waiting for scan to complete")); - FMLLoader.backgroundScanHandler.waitForScanToComplete(ticker); + FMLLoader.backgroundScanHandler.waitForScanToComplete(periodicTask); statusConsumer.ifPresent(c->c.accept("Loading mods")); - final ModList modList = ModList.of(loadingModList.getModFiles().stream().map(ModFileInfo::getFile).collect(Collectors.toList()), loadingModList.getMods()); + final ModList modList = ModList.of(loadingModList + .getModFiles() + .stream() + .map(ModFileInfo::getFile) + .collect(Collectors.toList()), + loadingModList.getMods()); if (!this.loadingExceptions.isEmpty()) { LOGGER.fatal(CORE, "Error during pre-loading phase", loadingExceptions.get(0)); modList.setLoadedMods(Collections.emptyList()); @@ -195,31 +188,74 @@ public class ModLoader throw new LoadingFailedException(loadingExceptions); } modList.setLoadedMods(modContainers); - SpacedRunnable sr = new SpacedRunnable(); statusConsumer.ifPresent(c->c.accept(String.format("Constructing %d mods", modList.size()))); - dispatchAndHandleError(LifecycleEventProvider.CONSTRUCT, sr, ticker); + dispatchAndHandleError(ModLoadingStage.CONSTRUCT, syncExecutor, parallelExecutor, periodicTask); statusConsumer.ifPresent(c->c.accept("Creating registries")); - GameData.fireCreateRegistryEvents(LifecycleEventProvider.CREATE_REGISTRIES, event -> dispatchAndHandleError(event, sr, ticker)); + dispatchAndHandleError(ModLoadingStage.CREATE_REGISTRIES, syncExecutor, parallelExecutor, periodicTask); ObjectHolderRegistry.findObjectHolders(); CapabilityManager.INSTANCE.injectCapabilities(modList.getAllScanData()); statusConsumer.ifPresent(c->c.accept("Populating registries")); - GameData.fireRegistryEvents(rl->true, LifecycleEventProvider.LOAD_REGISTRIES, event -> dispatchAndHandleError(event, sr, ticker)); + dispatchAndHandleError(ModLoadingStage.LOAD_REGISTRIES, syncExecutor, parallelExecutor, periodicTask); statusConsumer.ifPresent(c->c.accept("Early mod loading complete")); } - private void dispatchAndHandleError(LifecycleEventProvider event, Executor executor, final Runnable ticker) { - if (!loadingExceptions.isEmpty()) { - LOGGER.error(LOADING,"Skipping lifecycle event {}, {} errors found.", event, loadingExceptions.size()); - } else { - event.dispatch(this::accumulateErrors, executor, ticker); - } - if (!loadingExceptions.isEmpty()) { - LOGGER.fatal(LOADING,"Failed to complete lifecycle event {}, {} errors found", event, loadingExceptions.size()); - throw new LoadingFailedException(loadingExceptions); - } + public void loadMods(final ModWorkManager.DrivenExecutor syncExecutor, final Executor parallelExecutor, final Function> beforeSidedEvent, final Function> afterSidedEvent, final Runnable periodicTask) { + statusConsumer.ifPresent(c->c.accept("Loading mod config")); + DistExecutor.unsafeRunWhenOn(Dist.CLIENT, ()->()-> ConfigTracker.INSTANCE.loadConfigs(ModConfig.Type.CLIENT, FMLPaths.CONFIGDIR.get())); + ConfigTracker.INSTANCE.loadConfigs(ModConfig.Type.COMMON, FMLPaths.CONFIGDIR.get()); + statusConsumer.ifPresent(c->c.accept("Mod setup: SETUP")); + dispatchAndHandleError(ModLoadingStage.COMMON_SETUP, syncExecutor, parallelExecutor, periodicTask); + statusConsumer.ifPresent(c->c.accept("Mod setup: SIDED SETUP")); + dispatchAndHandleError(ModLoadingStage.SIDED_SETUP, syncExecutor, parallelExecutor, periodicTask, beforeSidedEvent, afterSidedEvent); + statusConsumer.ifPresent(c->c.accept("Mod setup complete")); } - private void accumulateErrors(List errors) { - loadingExceptions.addAll(errors); + + public void finishMods(final ModWorkManager.DrivenExecutor syncExecutor, final Executor parallelExecutor, final Runnable periodicTask) + { + statusConsumer.ifPresent(c->c.accept("Mod setup: ENQUEUE IMC")); + dispatchAndHandleError(ModLoadingStage.ENQUEUE_IMC, syncExecutor, parallelExecutor, periodicTask); + statusConsumer.ifPresent(c->c.accept("Mod setup: PROCESS IMC")); + dispatchAndHandleError(ModLoadingStage.PROCESS_IMC, syncExecutor, parallelExecutor, periodicTask); + statusConsumer.ifPresent(c->c.accept("Mod setup: Final completion")); + dispatchAndHandleError(ModLoadingStage.COMPLETE, syncExecutor, parallelExecutor, periodicTask); + statusConsumer.ifPresent(c->c.accept("Freezing data")); + GameData.freezeData(); + NetworkRegistry.lock(); + statusConsumer.ifPresent(c->c.accept(String.format("Mod loading complete - %d mods loaded", ModList.get().size()))); + } + + private void dispatchAndHandleError(ModLoadingStage state, ModWorkManager.DrivenExecutor syncExecutor, Executor parallelExecutor, final Runnable ticker) { + waitForTransition(state, syncExecutor, ticker, state.buildTransition(syncExecutor, parallelExecutor)); + } + + private void dispatchAndHandleError(ModLoadingStage state, ModWorkManager.DrivenExecutor syncExecutor, Executor parallelExecutor, final Runnable ticker, Function> preSyncTask, Function> postSyncTask) { + waitForTransition(state, syncExecutor, ticker, state.buildTransition(syncExecutor, parallelExecutor, preSyncTask, postSyncTask)); + } + + private void waitForTransition(final ModLoadingStage state, final ModWorkManager.DrivenExecutor syncExecutor, final Runnable ticker, final CompletableFuture> transition) { + while (!transition.isDone()) { + ticker.run(); + syncExecutor.drive(); + } + try { + transition.join(); + } catch (CompletionException e) { + Throwable t = e.getCause(); + final List notModLoading = Arrays.stream(t.getSuppressed()) + .filter(obj -> !(obj instanceof ModLoadingException)) + .collect(Collectors.toList()); + if (!notModLoading.isEmpty()) { + LOGGER.fatal("Encountered non-modloading exceptions!", e); + throw e; + } + + final List modLoadingExceptions = Arrays.stream(t.getSuppressed()) + .filter(ModLoadingException.class::isInstance) + .map(ModLoadingException.class::cast) + .collect(Collectors.toList()); + LOGGER.fatal(LOADING,"Failed to complete lifecycle event {}, {} errors found", state, modLoadingExceptions.size()); + throw new LoadingFailedException(modLoadingExceptions); + } } private List buildMods(final ModFile modFile, final TransformingClassLoader modClassLoader) @@ -229,7 +265,7 @@ public class ModLoader LOGGER.debug(LOADING, "ModContainer is {}", ModContainer.class.getClassLoader()); final List containers = modFile.getScanResult().getTargets().entrySet().stream(). map(e -> buildModContainerFromTOML(modFile, modClassLoader, modInfoMap, e)) - .filter(e -> e != null) + .filter(Objects::nonNull) .collect(Collectors.toList()); if (containers.size() != modInfoMap.size()) { LOGGER.fatal(LOADING,"File {} constructed {} mods: {}, but had {} mods specified: {}", @@ -256,25 +292,10 @@ public class ModLoader } } - public void postEvent(Event e) { + public void postEvent(T e) { ModList.get().forEachModContainer((id, mc) -> mc.acceptEvent(e)); } - public void finishMods(Executor mainThreadExecutor) - { - DeferredWorkQueue.workExecutor = mainThreadExecutor; - statusConsumer.ifPresent(c->c.accept("Mod setup: ENQUEUE IMC")); - dispatchAndHandleError(LifecycleEventProvider.ENQUEUE_IMC, mainThreadExecutor, null); - statusConsumer.ifPresent(c->c.accept("Mod setup: PROCESS IMC")); - dispatchAndHandleError(LifecycleEventProvider.PROCESS_IMC, mainThreadExecutor, null); - statusConsumer.ifPresent(c->c.accept("Mod setup: Final completion")); - dispatchAndHandleError(LifecycleEventProvider.COMPLETE, mainThreadExecutor, null); - statusConsumer.ifPresent(c->c.accept("Freezing data")); - GameData.freezeData(); - NetworkRegistry.lock(); - statusConsumer.ifPresent(c->c.accept(String.format("Mod loading complete - %d mods loaded", ModList.get().size()))); - } - public List getWarnings() { return ImmutableList.copyOf(this.loadingWarnings); @@ -292,15 +313,15 @@ public class ModLoader } public void runDataGenerator(final Set mods, final Path path, final Collection inputs, Collection existingPacks, final boolean serverGenerators, final boolean clientGenerators, final boolean devToolGenerators, final boolean reportsGenerator, final boolean structureValidator, final boolean flat) { - if (mods.contains("minecraft") && mods.size() == 1) return; - LOGGER.info("Initializing Data Gatherer for mods {}", mods); - runningDataGen = true; - Bootstrap.register(); - dataGeneratorConfig = new GatherDataEvent.DataGeneratorConfig(mods, path, inputs, serverGenerators, clientGenerators, devToolGenerators, reportsGenerator, structureValidator, flat); - existingFileHelper = new ExistingFileHelper(existingPacks, structureValidator); - gatherAndInitializeMods(() -> {}); - dispatchAndHandleError(LifecycleEventProvider.GATHERDATA, Runnable::run, () -> {}); - dataGeneratorConfig.runAll(); +// if (mods.contains("minecraft") && mods.size() == 1) return; +// LOGGER.info("Initializing Data Gatherer for mods {}", mods); +// runningDataGen = true; +// Bootstrap.register(); +// dataGeneratorConfig = new GatherDataEvent.DataGeneratorConfig(mods, path, inputs, serverGenerators, clientGenerators, devToolGenerators, reportsGenerator, structureValidator, flat); +// existingFileHelper = new ExistingFileHelper(existingPacks, structureValidator); +// gatherAndInitializeMods(() -> {}); +// dispatchAndHandleError(LifecycleEventProvider.GATHERDATA, Runnable::run, () -> {}); +// dataGeneratorConfig.runAll(); } public Function getDataGeneratorEvent() { diff --git a/src/main/java/net/minecraftforge/fml/ModLoadingStage.java b/src/main/java/net/minecraftforge/fml/ModLoadingStage.java index b59a776a3a..157cdb7d84 100644 --- a/src/main/java/net/minecraftforge/fml/ModLoadingStage.java +++ b/src/main/java/net/minecraftforge/fml/ModLoadingStage.java @@ -19,34 +19,140 @@ package net.minecraftforge.fml; -import net.minecraftforge.fml.event.lifecycle.*; +import cpw.mods.modlauncher.api.LamdbaExceptionUtils; +import net.minecraftforge.event.RegistryEvent; +import net.minecraftforge.eventbus.api.Event; +import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; +import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; +import net.minecraftforge.fml.event.lifecycle.FMLConstructModEvent; +import net.minecraftforge.fml.event.lifecycle.FMLDedicatedServerSetupEvent; +import net.minecraftforge.fml.event.lifecycle.FMLLoadCompleteEvent; +import net.minecraftforge.fml.event.lifecycle.IModBusEvent; +import net.minecraftforge.fml.event.lifecycle.InterModEnqueueEvent; +import net.minecraftforge.fml.event.lifecycle.InterModProcessEvent; +import net.minecraftforge.fml.event.lifecycle.ParallelDispatchEvent; +import net.minecraftforge.registries.GameData; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.function.BiFunction; +import java.util.function.BinaryOperator; +import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; +import java.util.stream.Stream; 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), - GATHERDATA(ModLoader.get()::getDataGeneratorEvent); - private final Supplier> modLifecycleEventFunction; + ERROR(), + VALIDATE(), + CONSTRUCT(FMLConstructModEvent.class), + CREATE_REGISTRIES(()->Stream.of(RegistryEvent.NewRegistry::new), EventDispatcher.identity()), + LOAD_REGISTRIES(GameData::generateRegistryEvents, GameData.buildRegistryEventDispatch()), + COMMON_SETUP(FMLCommonSetupEvent.class), + SIDED_SETUP(DistExecutor.unsafeRunForDist(()->()->FMLClientSetupEvent.class, ()->()->FMLDedicatedServerSetupEvent.class)), + ENQUEUE_IMC(InterModEnqueueEvent.class), + PROCESS_IMC(InterModProcessEvent.class), + COMPLETE(FMLLoadCompleteEvent.class), + DONE(), + GATHERDATA(); - ModLoadingStage(Supplier> modLifecycleEventFunction) - { - this.modLifecycleEventFunction = modLifecycleEventFunction; + private final Supplier>> eventFunctionStream; + private final EventDispatcher eventManager; + private final Optional> parallelEventClass; + private final ThreadSelector threadSelector; + private final BiFunction>, CompletableFuture>> finalActivityGenerator; + + ModLoadingStage(Class parallelClass) { + final EventGenerator event = EventGenerator.fromFunction(LamdbaExceptionUtils.rethrowFunction((ModContainer mc) -> parallelClass.getConstructor(ModContainer.class).newInstance(mc))); + this.eventFunctionStream = ()->Stream.of(event); + this.threadSelector = ThreadSelector.PARALLEL; + this.eventManager = EventDispatcher.identity(); + this.parallelEventClass = Optional.of(parallelClass); + final DeferredWorkQueue deferredWorkQueue = new DeferredWorkQueue(this, parallelClass); + this.finalActivityGenerator = (e, prev) -> prev.thenApplyAsync((List t) -> { + deferredWorkQueue.runTasks(); + return t; + }, e); } - public ModLifecycleEvent getModEvent(ModContainer modContainer) - { - return modLifecycleEventFunction.get().apply(modContainer); + @SuppressWarnings("unchecked") + ModLoadingStage(Supplier>> eventStream, EventDispatcher eventManager) { + this.eventFunctionStream = eventStream; + this.parallelEventClass = Optional.empty(); + this.eventManager = eventManager; + this.threadSelector = ThreadSelector.SYNC; + this.finalActivityGenerator = (e, prev) ->prev.thenApplyAsync(Function.identity(), e); + } + + ModLoadingStage() { + this(ParallelDispatchEvent.class); + } + + public CompletableFuture> buildTransition(final Executor syncExecutor, final Executor parallelExecutor) { + return buildTransition(syncExecutor, parallelExecutor, e->CompletableFuture.runAsync(()->{}, e), e->CompletableFuture.runAsync(()->{}, e)); + } + @SuppressWarnings("unchecked") + public CompletableFuture> buildTransition(final Executor syncExecutor, final Executor parallelExecutor, Function> preSyncTask, Function> postSyncTask) { + List>> cfs = new ArrayList<>(); + EventDispatcher em = (EventDispatcher) this.eventManager; + eventFunctionStream.get() + .map(f->(EventGenerator)f) + .reduce((head, tail)->{ + cfs.add(ModList.get() + .futureVisitor(head, em, ModLoadingStage::currentState) + .apply(threadSelector.apply(syncExecutor, parallelExecutor)) + ); + return tail; + }) + .ifPresent(last->cfs.add(ModList.get().futureVisitor(last, em, ModLoadingStage::nextState).apply(threadSelector.apply(syncExecutor, parallelExecutor)))); + final CompletableFuture preSyncTaskCF = preSyncTask.apply(syncExecutor); + final CompletableFuture> eventDispatchCF = ModList.gather(cfs).thenCompose(ModList::completableFutureFromExceptionList); + final CompletableFuture> postEventDispatchCF = preSyncTaskCF.thenComposeAsync(n -> eventDispatchCF, parallelExecutor).thenApply(r -> { + postSyncTask.apply(syncExecutor); + return r; + }); + return this.finalActivityGenerator.apply(syncExecutor, postEventDispatchCF); + } + + ModLoadingStage nextState(Throwable exception) { + return exception != null ? ERROR : values()[this.ordinal()+1]; + } + + ModLoadingStage currentState(Throwable exception) { + return exception != null ? ERROR : this; + } + + public Optional> getParallelEventClass() { + return parallelEventClass; + } + enum ThreadSelector implements BinaryOperator { + SYNC((sync, parallel)->sync), + PARALLEL((sync, parallel)->parallel); + + private final BinaryOperator selector; + + ThreadSelector(final BinaryOperator selector) { + this.selector = selector; + } + + @Override + public Executor apply(final Executor sync, final Executor parallel) { + return this.selector.apply(sync, parallel); + } + } + + public interface EventGenerator extends Function { + static EventGenerator fromFunction(Function fn) { + return fn::apply; + } + } + public interface EventDispatcher extends Function, Consumer> { + static EventDispatcher identity() { + return consumer -> consumer; + } } } diff --git a/src/main/java/net/minecraftforge/fml/ModWorkManager.java b/src/main/java/net/minecraftforge/fml/ModWorkManager.java new file mode 100644 index 0000000000..1a0765f781 --- /dev/null +++ b/src/main/java/net/minecraftforge/fml/ModWorkManager.java @@ -0,0 +1,103 @@ +package net.minecraftforge.fml; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.concurrent.Executor; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ForkJoinWorkerThread; + +import static net.minecraftforge.fml.Logging.LOADING; + +public class ModWorkManager { + private static final Logger LOGGER = LogManager.getLogger(); + + public interface DrivenExecutor extends Executor { + boolean selfDriven(); + boolean driveOne(); + + default void drive() { + if (!selfDriven()) { + while (driveOne()) + ; + } + } + } + private static class SyncExecutor implements DrivenExecutor { + private ConcurrentLinkedDeque tasks = new ConcurrentLinkedDeque<>(); + + @Override + public boolean driveOne() { + final Runnable task = tasks.pollFirst(); + if (task != null) { + task.run(); + } + return task != null; + } + + @Override + public boolean selfDriven() { + return false; + } + + @Override + public void execute(final Runnable command) { + tasks.addLast(command); + } + } + + private static class WrappingExecutor implements DrivenExecutor { + private final Executor wrapped; + + public WrappingExecutor(final Executor executor) { + this.wrapped = executor; + } + + @Override + public boolean selfDriven() { + return true; + } + + @Override + public boolean driveOne() { + return false; + } + + @Override + public void execute(final Runnable command) { + wrapped.execute(command); + } + } + + private static SyncExecutor syncExecutor; + + public static DrivenExecutor syncExecutor() { + if (syncExecutor == null) + syncExecutor = new SyncExecutor(); + return syncExecutor; + } + + public static DrivenExecutor wrappedExecutor(Executor executor) { + return new WrappingExecutor(executor); + } + + private static ForkJoinPool parallelThreadPool; + public static Executor parallelExecutor() { + if (parallelThreadPool == null) { + final int loadingThreadCount = 2; + LOGGER.debug(LOADING, "Using {} threads for parallel mod-loading", loadingThreadCount); + parallelThreadPool = new ForkJoinPool(loadingThreadCount, ModWorkManager::newForkJoinWorkerThread, null, false); + } + return parallelThreadPool; + } + + private static ForkJoinWorkerThread newForkJoinWorkerThread(ForkJoinPool pool) { + ForkJoinWorkerThread thread = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool); + thread.setName("modloading-worker-" + thread.getPoolIndex()); + // The default sets it to the SystemClassloader, so copy the current one. + thread.setContextClassLoader(Thread.currentThread().getContextClassLoader()); + return thread; + } + +} diff --git a/src/main/java/net/minecraftforge/fml/SidedProvider.java b/src/main/java/net/minecraftforge/fml/SidedProvider.java index 7026bb9a10..230c17978c 100644 --- a/src/main/java/net/minecraftforge/fml/SidedProvider.java +++ b/src/main/java/net/minecraftforge/fml/SidedProvider.java @@ -22,10 +22,7 @@ package net.minecraftforge.fml; import net.minecraft.client.Minecraft; import net.minecraft.server.dedicated.DedicatedServer; import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.eventbus.api.Event; import net.minecraftforge.fml.client.ClientHooks; -import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; -import net.minecraftforge.fml.event.lifecycle.FMLDedicatedServerSetupEvent; import net.minecraftforge.fml.loading.FMLEnvironment; import java.util.function.Function; @@ -38,10 +35,6 @@ public enum SidedProvider c->c.get().getDataFixer(), s->s.get().getDataFixer(), ()-> { throw new UnsupportedOperationException(); }), - SIDED_SETUP_EVENT( - (Function, Function>)c-> mc->new FMLClientSetupEvent(c, mc), - s-> mc->new FMLDedicatedServerSetupEvent(mc), - ()-> { throw new UnsupportedOperationException(); }), STRIPCHARS( (Function, Function>)c-> ClientHooks::stripSpecialChars, s-> str->str, diff --git a/src/main/java/net/minecraftforge/fml/client/ClientModLoader.java b/src/main/java/net/minecraftforge/fml/client/ClientModLoader.java index 65304c93a0..2eb0f94166 100644 --- a/src/main/java/net/minecraftforge/fml/client/ClientModLoader.java +++ b/src/main/java/net/minecraftforge/fml/client/ClientModLoader.java @@ -32,9 +32,22 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import java.util.function.BiConsumer; import java.util.function.Consumer; -import java.util.function.Supplier; -import net.minecraft.resources.*; +import net.minecraft.resources.IFutureReloadListener; +import net.minecraft.resources.IPackNameDecorator; +import net.minecraft.resources.IReloadableResourceManager; +import net.minecraft.resources.IResourceManager; +import net.minecraft.resources.ResourcePackInfo; +import net.minecraft.resources.ResourcePackList; +import net.minecraftforge.fml.BrandingControl; +import net.minecraftforge.fml.LoadingFailedException; +import net.minecraftforge.fml.LogicalSidedProvider; +import net.minecraftforge.fml.ModLoader; +import net.minecraftforge.fml.ModLoadingStage; +import net.minecraftforge.fml.ModLoadingWarning; +import net.minecraftforge.fml.ModWorkManager; +import net.minecraftforge.fml.SidedProvider; +import net.minecraftforge.fml.VersionChecker; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -48,19 +61,9 @@ import net.minecraft.resources.data.PackMetadataSection; import net.minecraft.util.text.TranslationTextComponent; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; -import net.minecraftforge.client.event.ModelRegistryEvent; import net.minecraftforge.client.model.ModelLoaderRegistry; import net.minecraftforge.common.ForgeConfig; import net.minecraftforge.common.MinecraftForge; -import net.minecraftforge.eventbus.api.Event; -import net.minecraftforge.fml.BrandingControl; -import net.minecraftforge.fml.LoadingFailedException; -import net.minecraftforge.fml.LogicalSidedProvider; -import net.minecraftforge.fml.ModLoader; -import net.minecraftforge.fml.ModLoadingStage; -import net.minecraftforge.fml.ModLoadingWarning; -import net.minecraftforge.fml.SidedProvider; -import net.minecraftforge.fml.VersionChecker; import net.minecraftforge.fml.client.gui.screen.LoadingErrorScreen; import net.minecraftforge.fml.client.registry.RenderingRegistry; import net.minecraftforge.fml.loading.moddiscovery.ModFile; @@ -90,17 +93,19 @@ public class ClientModLoader LogicalSidedProvider.setClient(()->minecraft); LanguageHook.loadForgeAndMCLangs(); earlyLoaderGUI = new EarlyLoaderGUI(minecraft.getMainWindow()); - createRunnableWithCatch(() -> ModLoader.get().gatherAndInitializeMods(earlyLoaderGUI::renderTick)).run(); - ResourcePackLoader.loadResourcePacks(defaultResourcePacks, ClientModLoader::buildPackFinder); - mcResourceManager.addReloadListener(ClientModLoader::onreload); - mcResourceManager.addReloadListener(BrandingControl.resourceManagerReloadListener()); - ModelLoaderRegistry.init(); + createRunnableWithCatch(()->ModLoader.get().gatherAndInitializeMods(ModWorkManager.syncExecutor(), ModWorkManager.parallelExecutor(), earlyLoaderGUI::renderTick)).run(); + if (error == null) { + ResourcePackLoader.loadResourcePacks(defaultResourcePacks, ClientModLoader::buildPackFinder); + mcResourceManager.addReloadListener(ClientModLoader::onResourceReload); + mcResourceManager.addReloadListener(BrandingControl.resourceManagerReloadListener()); + ModelLoaderRegistry.init(); + } } - private static CompletableFuture onreload(final IFutureReloadListener.IStage stage, final IResourceManager resourceManager, final IProfiler prepareProfiler, final IProfiler executeProfiler, final Executor asyncExecutor, final Executor syncExecutor) { - return CompletableFuture.runAsync(createRunnableWithCatch(() -> startModLoading(syncExecutor)), asyncExecutor). - thenCompose(stage::markCompleteAwaitingOthers). - thenRunAsync(() -> finishModLoading(syncExecutor), asyncExecutor); + private static CompletableFuture onResourceReload(final IFutureReloadListener.IStage stage, final IResourceManager resourceManager, final IProfiler prepareProfiler, final IProfiler executeProfiler, final Executor asyncExecutor, final Executor syncExecutor) { + return CompletableFuture.runAsync(createRunnableWithCatch(() -> startModLoading(ModWorkManager.wrappedExecutor(syncExecutor), asyncExecutor)), asyncExecutor) + .thenCompose(stage::markCompleteAwaitingOthers) + .thenRunAsync(() -> finishModLoading(ModWorkManager.wrappedExecutor(syncExecutor), asyncExecutor), asyncExecutor); } private static Runnable createRunnableWithCatch(Runnable r) { @@ -114,26 +119,27 @@ public class ClientModLoader }; } - private static void startModLoading(Executor executor) { + private static void startModLoading(ModWorkManager.DrivenExecutor syncExecutor, Executor parallelExecutor) { earlyLoaderGUI.handleElsewhere(); - createRunnableWithCatch(() -> ModLoader.get().loadMods(executor, ClientModLoader::preSidedRunnable, ClientModLoader::postSidedRunnable)).run(); + createRunnableWithCatch(() -> ModLoader.get().loadMods(syncExecutor, parallelExecutor, executor -> CompletableFuture.runAsync(ClientModLoader::preSidedRunnable, executor), executor -> CompletableFuture.runAsync(ClientModLoader::postSidedRunnable, executor), ()->{})).run(); } - private static void postSidedRunnable(Consumer> perModContainerEventProcessor) { + private static void postSidedRunnable() { + LOGGER.debug(LOADING, "Running post client event work"); RenderingRegistry.loadEntityRenderers(mc.getRenderManager()); ModelLoaderRegistry.initComplete(); } - private static void preSidedRunnable(Consumer> perModContainerEventProcessor) { - perModContainerEventProcessor.accept(ModelRegistryEvent::new); + private static void preSidedRunnable() { + LOGGER.debug(LOADING, "Running pre client event work"); } - private static void finishModLoading(Executor executor) + private static void finishModLoading(ModWorkManager.DrivenExecutor syncExecutor, Executor parallelExecutor) { - createRunnableWithCatch(() -> ModLoader.get().finishMods(executor)).run(); + createRunnableWithCatch(() -> ModLoader.get().finishMods(syncExecutor, parallelExecutor, ()->{})).run(); loading = false; // reload game settings on main thread - executor.execute(()->mc.gameSettings.loadOptions()); + syncExecutor.execute(()->mc.gameSettings.loadOptions()); } public static VersionChecker.Status checkForUpdates() diff --git a/src/main/java/net/minecraftforge/fml/event/lifecycle/FMLClientSetupEvent.java b/src/main/java/net/minecraftforge/fml/event/lifecycle/FMLClientSetupEvent.java index 17a21e695d..a9ba525a69 100644 --- a/src/main/java/net/minecraftforge/fml/event/lifecycle/FMLClientSetupEvent.java +++ b/src/main/java/net/minecraftforge/fml/event/lifecycle/FMLClientSetupEvent.java @@ -38,14 +38,14 @@ import java.util.function.Supplier; * * This is a parallel dispatch event. */ -public class FMLClientSetupEvent extends ModLifecycleEvent +public class FMLClientSetupEvent extends ParallelDispatchEvent { private final Supplier minecraftSupplier; - public FMLClientSetupEvent(Supplier mc, ModContainer container) + public FMLClientSetupEvent(ModContainer container) { super(container); - this.minecraftSupplier = mc; + minecraftSupplier = Minecraft::getInstance; } public Supplier getMinecraftSupplier() diff --git a/src/main/java/net/minecraftforge/fml/event/lifecycle/FMLCommonSetupEvent.java b/src/main/java/net/minecraftforge/fml/event/lifecycle/FMLCommonSetupEvent.java index 36d65ca2b4..82788839f3 100644 --- a/src/main/java/net/minecraftforge/fml/event/lifecycle/FMLCommonSetupEvent.java +++ b/src/main/java/net/minecraftforge/fml/event/lifecycle/FMLCommonSetupEvent.java @@ -40,7 +40,7 @@ import java.util.function.Consumer; * @see net.minecraftforge.fml.DeferredWorkQueue to enqueue work to run on the main game thread after this event has * completed dispatch */ -public class FMLCommonSetupEvent extends ModLifecycleEvent +public class FMLCommonSetupEvent extends ParallelDispatchEvent { public FMLCommonSetupEvent(final ModContainer container) { diff --git a/src/main/java/net/minecraftforge/fml/event/lifecycle/FMLConstructModEvent.java b/src/main/java/net/minecraftforge/fml/event/lifecycle/FMLConstructModEvent.java new file mode 100644 index 0000000000..4ea9bda24c --- /dev/null +++ b/src/main/java/net/minecraftforge/fml/event/lifecycle/FMLConstructModEvent.java @@ -0,0 +1,9 @@ +package net.minecraftforge.fml.event.lifecycle; + +import net.minecraftforge.fml.ModContainer; + +public class FMLConstructModEvent extends ParallelDispatchEvent { + public FMLConstructModEvent(final ModContainer container) { + super(container); + } +} diff --git a/src/main/java/net/minecraftforge/fml/event/lifecycle/FMLDedicatedServerSetupEvent.java b/src/main/java/net/minecraftforge/fml/event/lifecycle/FMLDedicatedServerSetupEvent.java index dc58121379..888a3f3b9e 100644 --- a/src/main/java/net/minecraftforge/fml/event/lifecycle/FMLDedicatedServerSetupEvent.java +++ b/src/main/java/net/minecraftforge/fml/event/lifecycle/FMLDedicatedServerSetupEvent.java @@ -42,7 +42,7 @@ import java.util.function.Supplier; * * This is a parallel dispatch event. */ -public class FMLDedicatedServerSetupEvent extends ModLifecycleEvent +public class FMLDedicatedServerSetupEvent extends ParallelDispatchEvent { public FMLDedicatedServerSetupEvent(ModContainer container) { diff --git a/src/main/java/net/minecraftforge/fml/event/lifecycle/FMLLoadCompleteEvent.java b/src/main/java/net/minecraftforge/fml/event/lifecycle/FMLLoadCompleteEvent.java index 68c81fddc7..712780e67e 100644 --- a/src/main/java/net/minecraftforge/fml/event/lifecycle/FMLLoadCompleteEvent.java +++ b/src/main/java/net/minecraftforge/fml/event/lifecycle/FMLLoadCompleteEvent.java @@ -27,7 +27,7 @@ import net.minecraftforge.fml.ModContainer; * * @author cpw */ -public class FMLLoadCompleteEvent extends ModLifecycleEvent +public class FMLLoadCompleteEvent extends ParallelDispatchEvent { public FMLLoadCompleteEvent(final ModContainer container) { diff --git a/src/main/java/net/minecraftforge/fml/event/lifecycle/InterModEnqueueEvent.java b/src/main/java/net/minecraftforge/fml/event/lifecycle/InterModEnqueueEvent.java index 880bf5d4db..89f9d04918 100644 --- a/src/main/java/net/minecraftforge/fml/event/lifecycle/InterModEnqueueEvent.java +++ b/src/main/java/net/minecraftforge/fml/event/lifecycle/InterModEnqueueEvent.java @@ -32,7 +32,7 @@ import net.minecraftforge.fml.ModContainer; * * This is a parallel dispatch event. */ -public class InterModEnqueueEvent extends ModLifecycleEvent +public class InterModEnqueueEvent extends ParallelDispatchEvent { public InterModEnqueueEvent(final ModContainer container) diff --git a/src/main/java/net/minecraftforge/fml/event/lifecycle/InterModProcessEvent.java b/src/main/java/net/minecraftforge/fml/event/lifecycle/InterModProcessEvent.java index 65225e021d..a5ce93ed27 100644 --- a/src/main/java/net/minecraftforge/fml/event/lifecycle/InterModProcessEvent.java +++ b/src/main/java/net/minecraftforge/fml/event/lifecycle/InterModProcessEvent.java @@ -36,7 +36,7 @@ import java.util.function.Predicate; * @see #getIMCStream() * @see #getIMCStream(Predicate) */ -public class InterModProcessEvent extends ModLifecycleEvent +public class InterModProcessEvent extends ParallelDispatchEvent { public InterModProcessEvent(final ModContainer container) { diff --git a/src/main/java/net/minecraftforge/fml/event/lifecycle/ModLifecycleEvent.java b/src/main/java/net/minecraftforge/fml/event/lifecycle/ModLifecycleEvent.java index f52dcada80..430f0cde2c 100644 --- a/src/main/java/net/minecraftforge/fml/event/lifecycle/ModLifecycleEvent.java +++ b/src/main/java/net/minecraftforge/fml/event/lifecycle/ModLifecycleEvent.java @@ -54,6 +54,10 @@ public class ModLifecycleEvent extends Event implements IModBusEvent return InterModComms.getMessages(this.container.getModId(), methodFilter); } + ModContainer getContainer() { + return this.container; + } + @Override public String toString() { return description(); diff --git a/src/main/java/net/minecraftforge/fml/event/lifecycle/ParallelDispatchEvent.java b/src/main/java/net/minecraftforge/fml/event/lifecycle/ParallelDispatchEvent.java new file mode 100644 index 0000000000..597ebe8eb5 --- /dev/null +++ b/src/main/java/net/minecraftforge/fml/event/lifecycle/ParallelDispatchEvent.java @@ -0,0 +1,26 @@ +package net.minecraftforge.fml.event.lifecycle; + +import net.minecraftforge.fml.DeferredWorkQueue; +import net.minecraftforge.fml.ModContainer; + +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; + +public class ParallelDispatchEvent extends ModLifecycleEvent { + public ParallelDispatchEvent(final ModContainer container) { + super(container); + } + + private Optional getQueue() { + return DeferredWorkQueue.lookup(Optional.of(getClass())); + } + + public CompletableFuture enqueueWork(Runnable work) { + return getQueue().map(q->q.enqueueWork(getContainer().getModInfo(), work)).orElseThrow(()->new RuntimeException("No work queue found!")); + } + + public CompletableFuture enqueueWork(Supplier work) { + return getQueue().map(q->q.enqueueWork(getContainer().getModInfo(), work)).orElseThrow(()->new RuntimeException("No work queue found!")); + } +} \ No newline at end of file diff --git a/src/main/java/net/minecraftforge/fml/javafmlmod/FMLModContainer.java b/src/main/java/net/minecraftforge/fml/javafmlmod/FMLModContainer.java index 906f921b83..4acd64ad82 100644 --- a/src/main/java/net/minecraftforge/fml/javafmlmod/FMLModContainer.java +++ b/src/main/java/net/minecraftforge/fml/javafmlmod/FMLModContainer.java @@ -25,20 +25,16 @@ import net.minecraftforge.eventbus.api.Event; import net.minecraftforge.eventbus.api.IEventBus; import net.minecraftforge.eventbus.api.IEventListener; import net.minecraftforge.fml.AutomaticEventSubscriber; -import net.minecraftforge.fml.LifecycleEventProvider; import net.minecraftforge.fml.ModContainer; import net.minecraftforge.fml.ModLoadingException; import net.minecraftforge.fml.ModLoadingStage; import net.minecraftforge.fml.event.lifecycle.IModBusEvent; -import net.minecraftforge.fml.event.lifecycle.ModLifecycleEvent; import net.minecraftforge.forgespi.language.IModInfo; import net.minecraftforge.forgespi.language.ModFileScanData; - import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.util.Optional; -import java.util.function.Consumer; import static net.minecraftforge.fml.Logging.LOADING; @@ -55,17 +51,9 @@ public class FMLModContainer extends ModContainer super(info); LOGGER.debug(LOADING,"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)); - triggerMap.put(ModLoadingStage.PROCESS_IMC, dummy().andThen(this::beforeEvent).andThen(this::fireEvent).andThen(this::afterEvent)); - triggerMap.put(ModLoadingStage.COMPLETE, dummy().andThen(this::beforeEvent).andThen(this::completeLoading).andThen(this::fireEvent).andThen(this::afterEvent)); - triggerMap.put(ModLoadingStage.GATHERDATA, dummy().andThen(this::beforeEvent).andThen(this::fireEvent).andThen(this::afterEvent)); + activityMap.put(ModLoadingStage.CONSTRUCT, this::constructMod); this.eventBus = BusBuilder.builder().setExceptionHandler(this::onEventFailed).setTrackPhases(false).markerType(IModBusEvent.class).build(); - this.configHandler = Optional.of(event -> this.eventBus.post(event)); + this.configHandler = Optional.of(this.eventBus::post); final FMLJavaModLoadingContext contextExtension = new FMLJavaModLoadingContext(this); this.contextExtension = () -> contextExtension; try @@ -80,52 +68,12 @@ public class FMLModContainer extends ModContainer } } - private void completeLoading(LifecycleEventProvider.LifecycleEvent lifecycleEvent) - { - - } - - private void initMod(LifecycleEventProvider.LifecycleEvent lifecycleEvent) - { - - } - - private Consumer dummy() { return (s) -> {}; } - private void onEventFailed(IEventBus iEventBus, Event event, IEventListener[] iEventListeners, int i, Throwable throwable) { LOGGER.error(new EventBusErrorMessage(event, i, iEventListeners, throwable)); } - private void beforeEvent(LifecycleEventProvider.LifecycleEvent lifecycleEvent) { - } - - private void fireEvent(LifecycleEventProvider.LifecycleEvent lifecycleEvent) { - final Event event = lifecycleEvent.getOrBuildEvent(this); - LOGGER.debug(LOADING, "Firing event for modid {} : {}", this.getModId(), event); - try - { - eventBus.post(event); - LOGGER.debug(LOADING, "Fired event for modid {} : {}", this.getModId(), event); - } - catch (Throwable e) - { - LOGGER.error(LOADING,"Caught exception during event {} dispatch for modid {}", event, this.getModId(), e); - throw new ModLoadingException(modInfo, lifecycleEvent.fromStage(), "fml.modloading.errorduringevent", e); - } - } - - private void afterEvent(LifecycleEventProvider.LifecycleEvent lifecycleEvent) { - if (getCurrentState() == ModLoadingStage.ERROR) { - LOGGER.error(LOADING,"An error occurred while dispatching event {} to {}", lifecycleEvent.fromStage(), getModId()); - } - } - - private void preinitMod(LifecycleEventProvider.LifecycleEvent lifecycleEvent) - { - } - - private void constructMod(LifecycleEventProvider.LifecycleEvent event) + private void constructMod() { try { @@ -136,7 +84,7 @@ public class FMLModContainer extends ModContainer catch (Throwable e) { LOGGER.error(LOADING,"Failed to create mod instance. ModID: {}, class {}", getModId(), modClass.getName(), e); - throw new ModLoadingException(modInfo, event.fromStage(), "fml.modloading.failedtoloadmod", e, modClass); + throw new ModLoadingException(modInfo, ModLoadingStage.CONSTRUCT, "fml.modloading.failedtoloadmod", e, modClass); } try { LOGGER.debug(LOADING, "Injecting Automatic event subscribers for {}", getModId()); @@ -144,7 +92,7 @@ public class FMLModContainer extends ModContainer LOGGER.debug(LOADING, "Completed Automatic event subscribers for {}", getModId()); } catch (Throwable e) { LOGGER.error(LOADING,"Failed to register automatic subscribers. ModID: {}, class {}", getModId(), modClass.getName(), e); - throw new ModLoadingException(modInfo, event.fromStage(), "fml.modloading.failedtoloadmod", e, modClass); + throw new ModLoadingException(modInfo, ModLoadingStage.CONSTRUCT, "fml.modloading.failedtoloadmod", e, modClass); } } @@ -166,7 +114,14 @@ public class FMLModContainer extends ModContainer } @Override - protected void acceptEvent(final Event e) { - this.eventBus.post(e); + protected void acceptEvent(final T e) { + try { + LOGGER.debug(LOADING, "Firing event for modid {} : {}", this.getModId(), e); + this.eventBus.post(e); + LOGGER.debug(LOADING, "Fired event for modid {} : {}", this.getModId(), e); + } catch (Throwable t) { + LOGGER.error(LOADING,"Caught exception during event {} dispatch for modid {}", e, this.getModId(), t); + throw new ModLoadingException(modInfo, modLoadingStage, "fml.modloading.errorduringevent", t); + } } } diff --git a/src/main/java/net/minecraftforge/fml/server/ServerModLoader.java b/src/main/java/net/minecraftforge/fml/server/ServerModLoader.java index 84055fb468..8e17b9b557 100644 --- a/src/main/java/net/minecraftforge/fml/server/ServerModLoader.java +++ b/src/main/java/net/minecraftforge/fml/server/ServerModLoader.java @@ -24,6 +24,7 @@ import net.minecraftforge.fml.LoadingFailedException; import net.minecraftforge.fml.LogicalSidedProvider; import net.minecraftforge.fml.ModLoader; import net.minecraftforge.fml.ModLoadingWarning; +import net.minecraftforge.fml.ModWorkManager; import net.minecraftforge.fml.SidedProvider; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -46,9 +47,9 @@ public class ServerModLoader }); LanguageHook.loadForgeAndMCLangs(); try { - ModLoader.get().gatherAndInitializeMods(() -> {}); - ModLoader.get().loadMods(Runnable::run, (a)->{}, (a)->{}); - ModLoader.get().finishMods(Runnable::run); + ModLoader.get().gatherAndInitializeMods(ModWorkManager.syncExecutor(), ModWorkManager.parallelExecutor(), ()->{}); + ModLoader.get().loadMods(ModWorkManager.syncExecutor(), ModWorkManager.parallelExecutor(), null, null, ()->{}); + ModLoader.get().finishMods(ModWorkManager.syncExecutor(), ModWorkManager.parallelExecutor(), ()->{}); } catch (LoadingFailedException e) { ServerModLoader.hasErrors = true; throw e; diff --git a/src/main/java/net/minecraftforge/registries/GameData.java b/src/main/java/net/minecraftforge/registries/GameData.java index 84008d4530..b7779f810b 100644 --- a/src/main/java/net/minecraftforge/registries/GameData.java +++ b/src/main/java/net/minecraftforge/registries/GameData.java @@ -51,7 +51,6 @@ import net.minecraft.util.ObjectIntIdentityMap; import net.minecraft.util.ResourceLocation; import net.minecraft.util.SoundEvent; import net.minecraft.util.registry.DefaultedRegistry; -import net.minecraft.util.registry.Registry; import net.minecraft.util.registry.SimpleRegistry; import net.minecraft.village.PointOfInterestType; import net.minecraft.world.biome.Biome; @@ -69,8 +68,8 @@ import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.loot.GlobalLootModifierSerializer; import net.minecraftforge.event.RegistryEvent; import net.minecraftforge.event.RegistryEvent.MissingMappings; -import net.minecraftforge.fml.LifecycleEventProvider; import net.minecraftforge.fml.ModLoadingContext; +import net.minecraftforge.fml.ModLoadingStage; import net.minecraftforge.fml.common.EnhancedRuntimeException; import net.minecraftforge.fml.common.thread.EffectiveSide; import net.minecraftforge.fml.event.lifecycle.FMLModIdMappingEvent; @@ -91,9 +90,8 @@ 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; +import java.util.stream.Stream; import static net.minecraftforge.registries.ForgeRegistry.REGISTRIES; @@ -367,6 +365,34 @@ public class GameData LOGGER.debug(REGISTRIES, "Reverting complete"); } + public static Stream> generateRegistryEvents() { + List keys = Lists.newArrayList(RegistryManager.ACTIVE.registries.keySet()); + keys.sort((o1, o2) -> String.valueOf(o1).compareToIgnoreCase(String.valueOf(o2))); + + //Move Blocks to first, and Items to second. + keys.remove(BLOCKS); + keys.remove(ITEMS); + + keys.add(0, BLOCKS); + keys.add(1, ITEMS); + + return keys.stream().map(rl -> mc -> RegistryManager.ACTIVE.getRegistry(rl).getRegisterEvent(rl)); + } + + public static ModLoadingStage.EventDispatcher> buildRegistryEventDispatch() { + return eventConsumer -> eventToSend -> { + final ResourceLocation rl = eventToSend.getName(); + ForgeRegistry fr = (ForgeRegistry) eventToSend.getRegistry(); + StartupMessageManager.modLoaderConsumer().ifPresent(s->s.accept("REGISTERING "+rl)); + fr.unfreeze(); + eventConsumer.accept(eventToSend); + fr.freeze(); + LOGGER.debug(REGISTRIES,"Applying holder lookups: {}", rl.toString()); + ObjectHolderRegistry.applyObjectHolders(rl::equals); + LOGGER.debug(REGISTRIES,"Holder lookups applied: {}", rl.toString()); + }; + } + //Lets us clear the map so we can rebuild it. private static class ClearableObjectIntIdentityMap extends ObjectIntIdentityMap { @@ -832,47 +858,6 @@ public class GameData newRegistry.loadIds(_new, frozen.getOverrideOwners(), Maps.newLinkedHashMap(), remaps, frozen, name); } - public static void fireCreateRegistryEvents() - { - MinecraftForge.EVENT_BUS.post(new RegistryEvent.NewRegistry()); - } - - public static void fireCreateRegistryEvents(final LifecycleEventProvider lifecycleEventProvider, final Consumer eventDispatcher) { - final RegistryEvent.NewRegistry newRegistryEvent = new RegistryEvent.NewRegistry(); - lifecycleEventProvider.setCustomEventSupplier(()->newRegistryEvent); - eventDispatcher.accept(lifecycleEventProvider); - } - - - public static void fireRegistryEvents(Predicate filter, final LifecycleEventProvider lifecycleEventProvider, final Consumer eventDispatcher) - { - List keys = Lists.newArrayList(RegistryManager.ACTIVE.registries.keySet()); - keys.sort((o1, o2) -> String.valueOf(o1).compareToIgnoreCase(String.valueOf(o2))); - - //Move Blocks to first, and Items to second. - keys.remove(BLOCKS); - keys.remove(ITEMS); - - keys.add(0, BLOCKS); - keys.add(1, ITEMS); - 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(); - StartupMessageManager.modLoaderConsumer().ifPresent(s->s.accept("REGISTERING "+rl)); - final RegistryEvent.Register registerEvent = reg.getRegisterEvent(rl); - lifecycleEventProvider.setCustomEventSupplier(() -> registerEvent); - lifecycleEventProvider.changeProgression(LifecycleEventProvider.LifecycleEvent.Progression.STAY); - if (i==keysSize-1) lifecycleEventProvider.changeProgression(LifecycleEventProvider.LifecycleEvent.Progression.NEXT); - eventDispatcher.accept(lifecycleEventProvider); - reg.freeze(); - LOGGER.debug(REGISTRIES,"Applying holder lookups: {}", rl.toString()); - ObjectHolderRegistry.applyObjectHolders(rl::equals); - LOGGER.debug(REGISTRIES,"Holder lookups applied: {}", rl.toString()); - } - } - /** * Check a name for a domain prefix, and if not present infer it from the * current active mod container. diff --git a/src/test/java/net/minecraftforge/debug/client/rendering/StencilEnableTest.java b/src/test/java/net/minecraftforge/debug/client/rendering/StencilEnableTest.java index 103cfb8f45..ca5312c282 100644 --- a/src/test/java/net/minecraftforge/debug/client/rendering/StencilEnableTest.java +++ b/src/test/java/net/minecraftforge/debug/client/rendering/StencilEnableTest.java @@ -36,6 +36,6 @@ public class StencilEnableTest { private void clientSetup(FMLClientSetupEvent event) { if (ENABLED) - DeferredWorkQueue.runLater(() -> Minecraft.getInstance().getFramebuffer().enableStencil()); + event.enqueueWork(() -> Minecraft.getInstance().getFramebuffer().enableStencil()); } } From 2e42340dfa80cb95f78ec2774d3022cf86606310 Mon Sep 17 00:00:00 2001 From: cpw Date: Mon, 31 Aug 2020 21:00:44 -0400 Subject: [PATCH 4/4] Fix up all the things. Removed SidedProvider because it served no real purpose anymore. Signed-off-by: cpw --- .../net/minecraft/client/Minecraft.java.patch | 66 ++++++++-------- .../crash/CrashReportCategory.java.patch | 11 +++ .../net/minecraft/data/Main.java.patch | 2 +- .../item/crafting/Ingredient.java.patch | 2 +- .../event/AddReloadListenerEvent.java | 22 +++++- .../minecraftforge/fml/DatagenModLoader.java | 34 ++++++++ .../minecraftforge/fml/DeferredWorkQueue.java | 21 +++-- .../net/minecraftforge/fml/ForgeI18n.java | 19 +++++ .../net/minecraftforge/fml/ModLoader.java | 54 ++++++++----- .../minecraftforge/fml/ModLoadingContext.java | 1 + .../fml/ModLoadingException.java | 9 +++ .../minecraftforge/fml/ModLoadingStage.java | 10 ++- .../minecraftforge/fml/ModWorkManager.java | 4 +- .../net/minecraftforge/fml/SidedProvider.java | 78 ------------------- .../fml/client/ClientHooks.java | 9 --- .../fml/client/ClientModLoader.java | 59 ++++++++++++-- .../fml/event/lifecycle/GatherDataEvent.java | 13 +++- .../fml/packs/ResourcePackLoader.java | 10 +-- .../fml/server/ServerLifecycleHooks.java | 28 ------- .../fml/server/ServerModLoader.java | 34 ++++++-- 20 files changed, 282 insertions(+), 204 deletions(-) create mode 100644 src/main/java/net/minecraftforge/fml/DatagenModLoader.java delete mode 100644 src/main/java/net/minecraftforge/fml/SidedProvider.java diff --git a/patches/minecraft/net/minecraft/client/Minecraft.java.patch b/patches/minecraft/net/minecraft/client/Minecraft.java.patch index 0b8cc56c06..26f8a7cf35 100644 --- a/patches/minecraft/net/minecraft/client/Minecraft.java.patch +++ b/patches/minecraft/net/minecraft/client/Minecraft.java.patch @@ -16,7 +16,7 @@ this.field_71459_aj = p_i45547_1_.field_178741_d.field_178756_a; this.field_238175_ae_ = !p_i45547_1_.field_178741_d.field_239099_d_; this.field_238176_af_ = !p_i45547_1_.field_178741_d.field_239100_e_; -@@ -416,14 +416,15 @@ +@@ -416,13 +416,13 @@ this.field_195558_d.func_216526_a(this.field_71474_y.field_74350_i); this.field_71417_B = new MouseHelper(this); @@ -29,11 +29,9 @@ this.field_110451_am = new SimpleReloadableResourceManager(ResourcePackType.CLIENT_RESOURCES); + net.minecraftforge.fml.client.ClientModLoader.begin(this, this.field_110448_aq, this.field_110451_am, this.field_195554_ax); this.field_110448_aq.func_198983_a(); -+ DatapackCodec.field_234880_a_.addModPacks(net.minecraftforge.common.ForgeHooks.getModPacks()); this.field_71474_y.func_198017_a(this.field_110448_aq); this.field_135017_as = new LanguageManager(this.field_71474_y.field_74363_ab); - this.field_110451_am.func_219534_a(this.field_135017_as); -@@ -463,6 +464,7 @@ +@@ -463,6 +463,7 @@ this.func_193986_ar(); this.field_110451_am.func_219534_a(this.field_193995_ae); this.field_71452_i = new ParticleManager(this.field_71441_e, this.field_71446_o); @@ -41,7 +39,7 @@ this.field_110451_am.func_219534_a(this.field_71452_i); this.field_213272_aL = new PaintingSpriteUploader(this.field_71446_o); this.field_110451_am.func_219534_a(this.field_213272_aL); -@@ -470,7 +472,8 @@ +@@ -470,7 +471,8 @@ this.field_110451_am.func_219534_a(this.field_213273_aM); this.field_241557_ar_ = new GPUWarning(); this.field_110451_am.func_219534_a(this.field_241557_ar_); @@ -51,7 +49,7 @@ this.field_184132_p = new DebugRenderer(this); RenderSystem.setErrorCallback(this::func_195545_a); if (this.field_71474_y.field_74353_u && !this.field_195558_d.func_198113_j()) { -@@ -482,11 +485,6 @@ +@@ -482,11 +484,6 @@ this.field_195558_d.func_224798_d(this.field_71474_y.field_225307_E); this.field_195558_d.func_227801_c_(); this.func_213226_a(); @@ -63,7 +61,7 @@ ResourceLoadProgressGui.func_212970_a(this); List list = this.field_110448_aq.func_232623_f_(); -@@ -495,7 +493,14 @@ +@@ -495,7 +492,14 @@ if (SharedConstants.field_206244_b) { this.func_213256_aB(); } @@ -79,7 +77,7 @@ }); }, false)); } -@@ -534,7 +539,7 @@ +@@ -534,7 +538,7 @@ } private void func_229988_a_(Throwable p_229988_1_) { @@ -88,7 +86,7 @@ ITextComponent itextcomponent; if (p_229988_1_ instanceof SimpleReloadableResourceManager.FailedPackException) { itextcomponent = new StringTextComponent(((SimpleReloadableResourceManager.FailedPackException)p_229988_1_).func_241203_a().func_195762_a()); -@@ -618,7 +623,7 @@ +@@ -618,7 +622,7 @@ return Stream.of(Registry.field_212630_s.func_177774_c(p_213251_0_.func_77973_b())); }); SearchTreeReloadable searchtreereloadable = new SearchTreeReloadable<>((p_213235_0_) -> { @@ -97,7 +95,7 @@ }); NonNullList nonnulllist = NonNullList.func_191196_a(); -@@ -688,13 +693,13 @@ +@@ -688,13 +692,13 @@ Bootstrap.func_179870_a(p_71377_0_.func_71502_e()); if (p_71377_0_.func_71497_f() != null) { Bootstrap.func_179870_a("#@!@# Game crashed! Crash report saved to: #@!@# " + p_71377_0_.func_71497_f()); @@ -114,7 +112,7 @@ } } -@@ -703,6 +708,7 @@ +@@ -703,6 +707,7 @@ return this.field_71474_y.field_211842_aO; } @@ -122,7 +120,7 @@ public CompletableFuture func_213237_g() { if (this.field_213276_aV != null) { return this.field_213276_aV; -@@ -791,10 +797,6 @@ +@@ -791,10 +796,6 @@ } public void func_147108_a(@Nullable Screen p_147108_1_) { @@ -133,7 +131,7 @@ if (p_147108_1_ == null && this.field_71441_e == null) { p_147108_1_ = new MainMenuScreen(); } else if (p_147108_1_ == null && this.field_71439_g.func_233643_dh_()) { -@@ -805,6 +807,14 @@ +@@ -805,6 +806,14 @@ } } @@ -148,7 +146,7 @@ if (p_147108_1_ instanceof MainMenuScreen || p_147108_1_ instanceof MultiplayerScreen) { this.field_71474_y.field_74330_P = false; this.field_71456_v.func_146158_b().func_146231_a(true); -@@ -936,11 +946,13 @@ +@@ -936,11 +945,13 @@ RenderSystem.enableCull(); this.field_71424_I.func_76319_b(); if (!this.field_71454_w) { @@ -162,7 +160,7 @@ } if (this.field_238174_aV_ != null) { -@@ -1039,7 +1051,7 @@ +@@ -1039,7 +1050,7 @@ Framebuffer framebuffer = this.func_147110_a(); framebuffer.func_216491_a(this.field_195558_d.func_198109_k(), this.field_195558_d.func_198091_l(), field_142025_a); @@ -171,7 +169,7 @@ this.field_71417_B.func_198021_g(); } -@@ -1227,11 +1239,21 @@ +@@ -1227,11 +1238,21 @@ if (p_147115_1_ && this.field_71476_x != null && this.field_71476_x.func_216346_c() == RayTraceResult.Type.BLOCK) { BlockRayTraceResult blockraytraceresult = (BlockRayTraceResult)this.field_71476_x; BlockPos blockpos = blockraytraceresult.func_216350_a(); @@ -195,7 +193,7 @@ } } -@@ -1250,6 +1272,8 @@ +@@ -1250,6 +1271,8 @@ } } else if (!this.field_71439_g.func_184838_M()) { @@ -204,7 +202,7 @@ switch(this.field_71476_x.func_216346_c()) { case ENTITY: this.field_71442_b.func_78764_a(this.field_71439_g, ((EntityRayTraceResult)this.field_71476_x).func_216348_a()); -@@ -1257,7 +1281,7 @@ +@@ -1257,7 +1280,7 @@ case BLOCK: BlockRayTraceResult blockraytraceresult = (BlockRayTraceResult)this.field_71476_x; BlockPos blockpos = blockraytraceresult.func_216350_a(); @@ -213,7 +211,7 @@ this.field_71442_b.func_180511_b(blockpos, blockraytraceresult.func_216354_b()); break; } -@@ -1267,8 +1291,10 @@ +@@ -1267,8 +1290,10 @@ } this.field_71439_g.func_184821_cY(); @@ -224,7 +222,7 @@ this.field_71439_g.func_184609_a(Hand.MAIN_HAND); } } -@@ -1283,6 +1309,11 @@ +@@ -1283,6 +1308,11 @@ } for(Hand hand : Hand.values()) { @@ -236,7 +234,7 @@ ItemStack itemstack = this.field_71439_g.func_184586_b(hand); if (this.field_71476_x != null) { switch(this.field_71476_x.func_216346_c()) { -@@ -1296,6 +1327,7 @@ +@@ -1296,6 +1326,7 @@ if (actionresulttype.func_226246_a_()) { if (actionresulttype.func_226247_b_()) { @@ -244,7 +242,7 @@ this.field_71439_g.func_184609_a(hand); } -@@ -1308,6 +1340,7 @@ +@@ -1308,6 +1339,7 @@ ActionResultType actionresulttype1 = this.field_71442_b.func_217292_a(this.field_71439_g, this.field_71441_e, hand, blockraytraceresult); if (actionresulttype1.func_226246_a_()) { if (actionresulttype1.func_226247_b_()) { @@ -252,7 +250,7 @@ this.field_71439_g.func_184609_a(hand); if (!itemstack.func_190926_b() && (itemstack.func_190916_E() != i || this.field_71442_b.func_78758_h())) { this.field_71460_t.field_78516_c.func_187460_a(hand); -@@ -1323,6 +1356,9 @@ +@@ -1323,6 +1355,9 @@ } } @@ -262,7 +260,7 @@ if (!itemstack.func_190926_b()) { ActionResultType actionresulttype2 = this.field_71442_b.func_187101_a(this.field_71439_g, this.field_71441_e, hand); if (actionresulttype2.func_226246_a_()) { -@@ -1349,6 +1385,8 @@ +@@ -1349,6 +1384,8 @@ --this.field_71467_ac; } @@ -271,7 +269,7 @@ this.field_71424_I.func_76320_a("gui"); if (!this.field_71445_n) { this.field_71456_v.func_73831_a(); -@@ -1465,6 +1503,8 @@ +@@ -1465,6 +1502,8 @@ this.field_71424_I.func_219895_b("keyboard"); this.field_195559_v.func_204870_b(); this.field_71424_I.func_76319_b(); @@ -280,7 +278,7 @@ } private void func_184117_aA() { -@@ -1696,7 +1736,12 @@ +@@ -1696,7 +1735,12 @@ networkmanager.func_150719_a(new ClientLoginNetHandler(networkmanager, this, (Screen)null, (p_229998_0_) -> { })); networkmanager.func_179290_a(new CHandshakePacket(socketaddress.toString(), 0, ProtocolType.LOGIN)); @@ -294,7 +292,7 @@ this.field_71453_ak = networkmanager; } else { this.func_241559_a_(p_238195_6_, p_238195_1_, flag, () -> { -@@ -1770,6 +1815,7 @@ +@@ -1770,6 +1814,7 @@ } public void func_71403_a(ClientWorld p_71403_1_) { @@ -302,7 +300,7 @@ WorkingScreen workingscreen = new WorkingScreen(); workingscreen.func_200210_a(new TranslationTextComponent("connect.joining")); this.func_213241_c(workingscreen); -@@ -1801,10 +1847,12 @@ +@@ -1801,10 +1846,12 @@ IntegratedServer integratedserver = this.field_71437_Z; this.field_71437_Z = null; this.field_71460_t.func_190564_k(); @@ -315,7 +313,7 @@ if (integratedserver != null) { this.field_71424_I.func_76320_a("waitForServer"); -@@ -1819,6 +1867,7 @@ +@@ -1819,6 +1866,7 @@ this.field_71456_v.func_181029_i(); this.field_71422_O = null; this.field_71455_al = false; @@ -323,7 +321,7 @@ this.field_213274_aO.func_216815_b(); } -@@ -1849,6 +1898,7 @@ +@@ -1849,6 +1897,7 @@ this.field_71452_i.func_78870_a(p_213257_1_); TileEntityRendererDispatcher.field_147556_a.func_147543_a(p_213257_1_); this.func_230150_b_(); @@ -331,7 +329,7 @@ } public boolean func_238216_r_() { -@@ -1894,112 +1944,9 @@ +@@ -1894,112 +1943,9 @@ private void func_147112_ai() { if (this.field_71476_x != null && this.field_71476_x.func_216346_c() != RayTraceResult.Type.MISS) { @@ -447,7 +445,7 @@ } } -@@ -2081,6 +2028,7 @@ +@@ -2081,6 +2027,7 @@ return field_71432_P; } @@ -455,7 +453,7 @@ public CompletableFuture func_213245_w() { return this.func_213169_a(this::func_213237_g).thenCompose((p_229993_0_) -> { return p_229993_0_; -@@ -2388,7 +2336,7 @@ +@@ -2388,7 +2335,7 @@ supplier = func_228022_c_(supplier); } @@ -464,7 +462,7 @@ } private static Supplier func_228021_b_(Supplier p_228021_0_) { -@@ -2407,6 +2355,14 @@ +@@ -2407,6 +2354,14 @@ this.field_175617_aL.func_229355_a_(p_228020_1_); } diff --git a/patches/minecraft/net/minecraft/crash/CrashReportCategory.java.patch b/patches/minecraft/net/minecraft/crash/CrashReportCategory.java.patch index 51237b55f6..1f9144e5d3 100644 --- a/patches/minecraft/net/minecraft/crash/CrashReportCategory.java.patch +++ b/patches/minecraft/net/minecraft/crash/CrashReportCategory.java.patch @@ -13,3 +13,14 @@ return this.field_85075_d.length; } } +@@ -158,6 +160,10 @@ + return this.field_85075_d; + } + ++ public void applyStackTrace(Throwable t) { ++ this.field_85075_d = t.getStackTrace(); ++ } ++ + public static void func_175750_a(CrashReportCategory p_175750_0_, BlockPos p_175750_1_, @Nullable BlockState p_175750_2_) { + if (p_175750_2_ != null) { + p_175750_0_.func_189529_a("Block", p_175750_2_::toString); diff --git a/patches/minecraft/net/minecraft/data/Main.java.patch b/patches/minecraft/net/minecraft/data/Main.java.patch index 24c6b3f1db..48b2791999 100644 --- a/patches/minecraft/net/minecraft/data/Main.java.patch +++ b/patches/minecraft/net/minecraft/data/Main.java.patch @@ -26,7 +26,7 @@ + Collection existingPacks = optionset.valuesOf(existing).stream().map(Paths::get).collect(Collectors.toList()); + java.util.Set mods = new java.util.HashSet<>(optionset.valuesOf(mod)); + boolean isFlat = mods.isEmpty() || optionset.has(flat); -+ net.minecraftforge.fml.ModLoader.get().runDataGenerator(mods, path, inputs, existingPacks, flag2, flag1, flag3, flag4, flag5, isFlat); ++ net.minecraftforge.fml.DatagenModLoader.begin(mods, path, inputs, existingPacks, flag2, flag1, flag3, flag4, flag5, isFlat); + if (mods.contains("minecraft") || mods.isEmpty()) + func_200264_a(isFlat ? path : path.resolve("minecraft"), inputs, flag1, flag2, flag3, flag4, flag5).func_200392_c(); } else { diff --git a/patches/minecraft/net/minecraft/item/crafting/Ingredient.java.patch b/patches/minecraft/net/minecraft/item/crafting/Ingredient.java.patch index 900631fbc0..6d26cb2e01 100644 --- a/patches/minecraft/net/minecraft/item/crafting/Ingredient.java.patch +++ b/patches/minecraft/net/minecraft/item/crafting/Ingredient.java.patch @@ -20,7 +20,7 @@ this.field_199807_b = p_i49381_1_.toArray((p_209360_0_) -> { return new Ingredient.IItemList[p_209360_0_]; }); -+ this.isSimple = !net.minecraftforge.fml.ModLoader.isDataGenRunning() && !Arrays.stream(field_199807_b).anyMatch(list -> list.func_199799_a().stream().anyMatch(stack -> stack.func_77973_b().func_77645_m())); ++ this.isSimple = !net.minecraftforge.fml.DatagenModLoader.isRunningDataGen() && !Arrays.stream(field_199807_b).anyMatch(list -> list.func_199799_a().stream().anyMatch(stack -> stack.func_77973_b().func_77645_m())); + Ingredient.INSTANCES.add(this); } diff --git a/src/main/java/net/minecraftforge/event/AddReloadListenerEvent.java b/src/main/java/net/minecraftforge/event/AddReloadListenerEvent.java index 9b0ab446e2..325acd13e4 100644 --- a/src/main/java/net/minecraftforge/event/AddReloadListenerEvent.java +++ b/src/main/java/net/minecraftforge/event/AddReloadListenerEvent.java @@ -1,13 +1,18 @@ package net.minecraftforge.event; import com.google.common.collect.ImmutableList; +import net.minecraft.profiler.IProfiler; import net.minecraft.resources.DataPackRegistries; import net.minecraft.resources.IFutureReloadListener; +import net.minecraft.resources.IResourceManager; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.eventbus.api.Event; +import net.minecraftforge.fml.ModLoader; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; /** * The main ResourceManager is recreated on each reload, through {@link DataPackRegistries}'s creation. @@ -30,7 +35,7 @@ public class AddReloadListenerEvent extends Event */ public void addListener(IFutureReloadListener listener) { - listeners.add(listener); + listeners.add(new WrappedStateAwareListener(listener)); } public List getListeners() @@ -42,4 +47,19 @@ public class AddReloadListenerEvent extends Event { return dataPackRegistries; } + private static class WrappedStateAwareListener implements IFutureReloadListener { + private final IFutureReloadListener wrapped; + + private WrappedStateAwareListener(final IFutureReloadListener wrapped) { + this.wrapped = wrapped; + } + + @Override + public CompletableFuture reload(final IStage stage, final IResourceManager resourceManager, final IProfiler preparationsProfiler, final IProfiler reloadProfiler, final Executor backgroundExecutor, final Executor gameExecutor) { + if (ModLoader.isLoadingStateValid()) + return wrapped.reload(stage, resourceManager, preparationsProfiler, reloadProfiler, backgroundExecutor, gameExecutor); + else + return CompletableFuture.completedFuture(null); + } + } } diff --git a/src/main/java/net/minecraftforge/fml/DatagenModLoader.java b/src/main/java/net/minecraftforge/fml/DatagenModLoader.java new file mode 100644 index 0000000000..ca09f51251 --- /dev/null +++ b/src/main/java/net/minecraftforge/fml/DatagenModLoader.java @@ -0,0 +1,34 @@ +package net.minecraftforge.fml; + +import net.minecraft.util.registry.Bootstrap; +import net.minecraftforge.client.model.generators.ExistingFileHelper; +import net.minecraftforge.fml.event.lifecycle.GatherDataEvent; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.nio.file.Path; +import java.util.Collection; +import java.util.Set; + +public class DatagenModLoader { + private static final Logger LOGGER = LogManager.getLogger(); + private static GatherDataEvent.DataGeneratorConfig dataGeneratorConfig; + private static ExistingFileHelper existingFileHelper; + private static boolean runningDataGen; + + public static boolean isRunningDataGen() { + return runningDataGen; + } + + public static void begin(final Set mods, final Path path, final java.util.Collection inputs, Collection existingPacks, final boolean serverGenerators, final boolean clientGenerators, final boolean devToolGenerators, final boolean reportsGenerator, final boolean structureValidator, final boolean flat) { + if (mods.contains("minecraft") && mods.size() == 1) return; + LOGGER.info("Initializing Data Gatherer for mods {}", mods); + runningDataGen = true; + Bootstrap.register(); + dataGeneratorConfig = new GatherDataEvent.DataGeneratorConfig(mods, path, inputs, serverGenerators, clientGenerators, devToolGenerators, reportsGenerator, structureValidator, flat); + existingFileHelper = new ExistingFileHelper(existingPacks, structureValidator); + ModLoader.get().gatherAndInitializeMods(ModWorkManager.syncExecutor(), ModWorkManager.parallelExecutor(), ()->{}); + ModLoader.get().runEventGenerator(mc->new GatherDataEvent(mc, dataGeneratorConfig.makeGenerator(p->dataGeneratorConfig.isFlat() ? p : p.resolve(mc.getModId()), dataGeneratorConfig.getMods().contains(mc.getModId())), dataGeneratorConfig, existingFileHelper)); + dataGeneratorConfig.runAll(); + } +} diff --git a/src/main/java/net/minecraftforge/fml/DeferredWorkQueue.java b/src/main/java/net/minecraftforge/fml/DeferredWorkQueue.java index 8174ca8afc..82f14bb68a 100644 --- a/src/main/java/net/minecraftforge/fml/DeferredWorkQueue.java +++ b/src/main/java/net/minecraftforge/fml/DeferredWorkQueue.java @@ -21,11 +21,7 @@ package net.minecraftforge.fml; import static net.minecraftforge.fml.Logging.LOADING; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; +import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.Executor; @@ -33,6 +29,7 @@ import java.util.concurrent.TimeUnit; import java.util.function.Supplier; import net.minecraftforge.fml.event.lifecycle.ParallelDispatchEvent; +import net.minecraftforge.fml.javafmlmod.FMLModContainer; import org.apache.commons.lang3.time.StopWatch; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -97,6 +94,20 @@ public class DeferredWorkQueue return CompletableFuture.supplyAsync(work, r->tasks.add(new TaskInfo(modInfo, r))); } + /** + * DEPRECATED FOR REMOVAL. Use {@link ParallelDispatchEvent#enqueueWork(Runnable)} or {@link ParallelDispatchEvent#enqueueWork(Supplier)} + * + * @param workToEnqueue Runnable to execute later + * @return a completable future + */ + @Deprecated + public static CompletableFuture runLater(Runnable workToEnqueue) { + return + Optional.ofNullable(ModLoadingContext.get().getActiveContainer().modLoadingStage.getDeferredWorkQueue()) + .map(wq->wq.enqueueWork(ModLoadingContext.get().getActiveContainer().modInfo, workToEnqueue)) + .orElseGet(()->CompletableFuture.completedFuture(null)); + } + static class TaskInfo { public final IModInfo owner; diff --git a/src/main/java/net/minecraftforge/fml/ForgeI18n.java b/src/main/java/net/minecraftforge/fml/ForgeI18n.java index d5fd28773d..e2758252d4 100644 --- a/src/main/java/net/minecraftforge/fml/ForgeI18n.java +++ b/src/main/java/net/minecraftforge/fml/ForgeI18n.java @@ -19,6 +19,7 @@ package net.minecraftforge.fml; +import com.google.common.base.CharMatcher; import net.minecraftforge.fml.loading.StringUtils; import net.minecraftforge.fml.loading.moddiscovery.ModInfo; import org.apache.commons.lang3.text.ExtendedMessageFormat; @@ -33,14 +34,22 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.function.BiConsumer; +import java.util.regex.Pattern; import static net.minecraftforge.fml.Logging.CORE; //TODO, this should be re-evaluated now that ITextComponents are passed everywhere instaed of strings. public class ForgeI18n { private static final Logger LOGGER = LogManager.getLogger(); + // From FontRenderer.renderCharAtPos + private static final String ALLOWED_CHARS = "\u00c0\u00c1\u00c2\u00c8\u00ca\u00cb\u00cd\u00d3\u00d4\u00d5\u00da\u00df\u00e3\u00f5\u011f\u0130\u0131\u0152\u0153\u015e\u015f\u0174\u0175\u017e\u0207\u0000\u0000\u0000\u0000\u0000\u0000\u0000 !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u0000\u00c7\u00fc\u00e9\u00e2\u00e4\u00e0\u00e5\u00e7\u00ea\u00eb\u00e8\u00ef\u00ee\u00ec\u00c4\u00c5\u00c9\u00e6\u00c6\u00f4\u00f6\u00f2\u00fb\u00f9\u00ff\u00d6\u00dc\u00f8\u00a3\u00d8\u00d7\u0192\u00e1\u00ed\u00f3\u00fa\u00f1\u00d1\u00aa\u00ba\u00bf\u00ae\u00ac\u00bd\u00bc\u00a1\u00ab\u00bb\u2591\u2592\u2593\u2502\u2524\u2561\u2562\u2556\u2555\u2563\u2551\u2557\u255d\u255c\u255b\u2510\u2514\u2534\u252c\u251c\u2500\u253c\u255e\u255f\u255a\u2554\u2569\u2566\u2560\u2550\u256c\u2567\u2568\u2564\u2565\u2559\u2558\u2552\u2553\u256b\u256a\u2518\u250c\u2588\u2584\u258c\u2590\u2580\u03b1\u03b2\u0393\u03c0\u03a3\u03c3\u03bc\u03c4\u03a6\u0398\u03a9\u03b4\u221e\u2205\u2208\u2229\u2261\u00b1\u2265\u2264\u2320\u2321\u00f7\u2248\u00b0\u2219\u00b7\u221a\u207f\u00b2\u25a0\u0000"; + private static final CharMatcher DISALLOWED_CHAR_MATCHER = CharMatcher.anyOf(ALLOWED_CHARS).negate(); private static Map i18n; private static Map customFactories; + // From StringUtils + private static final Pattern PATTERN_CONTROL_CODE = Pattern.compile("(?i)\\u00A7[0-9A-FK-OR]"); + + static { customFactories = new HashMap<>(); @@ -102,6 +111,16 @@ public class ForgeI18n { return extendedMessageFormat.format(args); } + public static String stripSpecialChars(String message) + { + // We can't handle many unicode points in the splash renderer + return DISALLOWED_CHAR_MATCHER.removeFrom(stripControlCodes(message)); + } + + public static String stripControlCodes(String text) { + return PATTERN_CONTROL_CODE.matcher(text).replaceAll(""); + } + public static class CustomReadOnlyFormat extends Format { private final BiConsumer formatter; diff --git a/src/main/java/net/minecraftforge/fml/ModLoader.java b/src/main/java/net/minecraftforge/fml/ModLoader.java index c1c7c2b435..ccb7db510c 100644 --- a/src/main/java/net/minecraftforge/fml/ModLoader.java +++ b/src/main/java/net/minecraftforge/fml/ModLoader.java @@ -112,8 +112,7 @@ public class ModLoader private final List loadingExceptions; private final List loadingWarnings; - private GatherDataEvent.DataGeneratorConfig dataGeneratorConfig; - private ExistingFileHelper existingFileHelper; + private boolean loadingStateValid; @SuppressWarnings("OptionalUsedAsFieldOrParameterType") private final Optional> statusConsumer = StartupMessageManager.modLoaderConsumer(); @@ -162,6 +161,7 @@ public class ModLoader * @param periodicTask Optional periodic task to perform on the main thread while other activities run */ public void gatherAndInitializeMods(final ModWorkManager.DrivenExecutor syncExecutor, final Executor parallelExecutor, final Runnable periodicTask) { + loadingStateValid = true; statusConsumer.ifPresent(c->c.accept("Waiting for scan to complete")); FMLLoader.backgroundScanHandler.waitForScanToComplete(periodicTask); statusConsumer.ifPresent(c->c.accept("Loading mods")); @@ -174,6 +174,7 @@ public class ModLoader if (!this.loadingExceptions.isEmpty()) { LOGGER.fatal(CORE, "Error during pre-loading phase", loadingExceptions.get(0)); modList.setLoadedMods(Collections.emptyList()); + loadingStateValid = false; throw new LoadingFailedException(loadingExceptions); } statusConsumer.ifPresent(c->c.accept("Building Mod List")); @@ -185,6 +186,7 @@ public class ModLoader if (!loadingExceptions.isEmpty()) { LOGGER.fatal(CORE, "Failed to initialize mod containers", loadingExceptions.get(0)); modList.setLoadedMods(Collections.emptyList()); + loadingStateValid = false; throw new LoadingFailedException(loadingExceptions); } modList.setLoadedMods(modContainers); @@ -225,21 +227,29 @@ public class ModLoader } private void dispatchAndHandleError(ModLoadingStage state, ModWorkManager.DrivenExecutor syncExecutor, Executor parallelExecutor, final Runnable ticker) { + if (!isLoadingStateValid()) { + LOGGER.error("Cowardly refusing to process mod state change request from {}", state); + return; + } waitForTransition(state, syncExecutor, ticker, state.buildTransition(syncExecutor, parallelExecutor)); } private void dispatchAndHandleError(ModLoadingStage state, ModWorkManager.DrivenExecutor syncExecutor, Executor parallelExecutor, final Runnable ticker, Function> preSyncTask, Function> postSyncTask) { + if (!isLoadingStateValid()) { + LOGGER.error("Cowardly refusing to process mod state change request from {}", state); + return; + } waitForTransition(state, syncExecutor, ticker, state.buildTransition(syncExecutor, parallelExecutor, preSyncTask, postSyncTask)); } private void waitForTransition(final ModLoadingStage state, final ModWorkManager.DrivenExecutor syncExecutor, final Runnable ticker, final CompletableFuture> transition) { while (!transition.isDone()) { - ticker.run(); - syncExecutor.drive(); + syncExecutor.drive(ticker); } try { transition.join(); } catch (CompletionException e) { + loadingStateValid = false; Throwable t = e.getCause(); final List notModLoading = Arrays.stream(t.getSuppressed()) .filter(obj -> !(obj instanceof ModLoadingException)) @@ -292,7 +302,27 @@ public class ModLoader } } + /** + * @return If the current mod loading state is valid. Use if you interact with vanilla systems directly during loading + * and don't want to cause extraneous crashes due to trying to do things that aren't possible in a "broken load" + */ + public static boolean isLoadingStateValid() { + return get().loadingStateValid; + } + + public void runEventGenerator(Function generator) { + if (!loadingStateValid) { + LOGGER.error("Cowardly refusing to send event generator to a broken mod state"); + return; + } + ModList.get().forEachModContainer((id, mc) -> mc.acceptEvent(generator.apply(mc))); + } + public void postEvent(T e) { + if (!loadingStateValid) { + LOGGER.error("Cowardly refusing to send event {} to a broken mod state", e.getClass().getName()); + return; + } ModList.get().forEachModContainer((id, mc) -> mc.acceptEvent(e)); } @@ -311,20 +341,4 @@ public class ModLoader public static boolean isDataGenRunning () { return runningDataGen; } - - public void runDataGenerator(final Set mods, final Path path, final Collection inputs, Collection existingPacks, final boolean serverGenerators, final boolean clientGenerators, final boolean devToolGenerators, final boolean reportsGenerator, final boolean structureValidator, final boolean flat) { -// if (mods.contains("minecraft") && mods.size() == 1) return; -// LOGGER.info("Initializing Data Gatherer for mods {}", mods); -// runningDataGen = true; -// Bootstrap.register(); -// dataGeneratorConfig = new GatherDataEvent.DataGeneratorConfig(mods, path, inputs, serverGenerators, clientGenerators, devToolGenerators, reportsGenerator, structureValidator, flat); -// existingFileHelper = new ExistingFileHelper(existingPacks, structureValidator); -// gatherAndInitializeMods(() -> {}); -// dispatchAndHandleError(LifecycleEventProvider.GATHERDATA, Runnable::run, () -> {}); -// dataGeneratorConfig.runAll(); - } - - public Function getDataGeneratorEvent() { - return mc -> new GatherDataEvent(mc, dataGeneratorConfig.makeGenerator(p->dataGeneratorConfig.isFlat() ? p : p.resolve(mc.getModId()), dataGeneratorConfig.getMods().contains(mc.getModId())), dataGeneratorConfig, existingFileHelper); - } } diff --git a/src/main/java/net/minecraftforge/fml/ModLoadingContext.java b/src/main/java/net/minecraftforge/fml/ModLoadingContext.java index 23dbd81a98..20bcb09683 100644 --- a/src/main/java/net/minecraftforge/fml/ModLoadingContext.java +++ b/src/main/java/net/minecraftforge/fml/ModLoadingContext.java @@ -28,6 +28,7 @@ public class ModLoadingContext { private static ThreadLocal context = ThreadLocal.withInitial(ModLoadingContext::new); private Object languageExtension; + private ModLoadingStage stage; public static ModLoadingContext get() { return context.get(); diff --git a/src/main/java/net/minecraftforge/fml/ModLoadingException.java b/src/main/java/net/minecraftforge/fml/ModLoadingException.java index 3547cef176..73fb4a9729 100644 --- a/src/main/java/net/minecraftforge/fml/ModLoadingException.java +++ b/src/main/java/net/minecraftforge/fml/ModLoadingException.java @@ -20,7 +20,9 @@ package net.minecraftforge.fml; import com.google.common.collect.Streams; +import net.minecraft.util.StringUtils; import net.minecraftforge.fml.loading.EarlyLoadingException; +import net.minecraftforge.fml.loading.moddiscovery.ModInfo; import net.minecraftforge.forgespi.language.IModInfo; import java.util.Arrays; @@ -79,4 +81,11 @@ public class ModLoadingException extends RuntimeException public String getMessage() { return formatToString(); } + + public IModInfo getModInfo() { + return modInfo; + } + public String getCleanMessage() { + return ForgeI18n.stripControlCodes(formatToString()); + } } diff --git a/src/main/java/net/minecraftforge/fml/ModLoadingStage.java b/src/main/java/net/minecraftforge/fml/ModLoadingStage.java index 157cdb7d84..0ac88395e5 100644 --- a/src/main/java/net/minecraftforge/fml/ModLoadingStage.java +++ b/src/main/java/net/minecraftforge/fml/ModLoadingStage.java @@ -57,14 +57,14 @@ public enum ModLoadingStage ENQUEUE_IMC(InterModEnqueueEvent.class), PROCESS_IMC(InterModProcessEvent.class), COMPLETE(FMLLoadCompleteEvent.class), - DONE(), - GATHERDATA(); + DONE(); private final Supplier>> eventFunctionStream; private final EventDispatcher eventManager; private final Optional> parallelEventClass; private final ThreadSelector threadSelector; private final BiFunction>, CompletableFuture>> finalActivityGenerator; + private DeferredWorkQueue deferredWorkQueue; ModLoadingStage(Class parallelClass) { final EventGenerator event = EventGenerator.fromFunction(LamdbaExceptionUtils.rethrowFunction((ModContainer mc) -> parallelClass.getConstructor(ModContainer.class).newInstance(mc))); @@ -72,7 +72,7 @@ public enum ModLoadingStage this.threadSelector = ThreadSelector.PARALLEL; this.eventManager = EventDispatcher.identity(); this.parallelEventClass = Optional.of(parallelClass); - final DeferredWorkQueue deferredWorkQueue = new DeferredWorkQueue(this, parallelClass); + deferredWorkQueue = new DeferredWorkQueue(this, parallelClass); this.finalActivityGenerator = (e, prev) -> prev.thenApplyAsync((List t) -> { deferredWorkQueue.runTasks(); return t; @@ -145,6 +145,10 @@ public enum ModLoadingStage } } + public DeferredWorkQueue getDeferredWorkQueue() { + return deferredWorkQueue; + } + public interface EventGenerator extends Function { static EventGenerator fromFunction(Function fn) { return fn::apply; diff --git a/src/main/java/net/minecraftforge/fml/ModWorkManager.java b/src/main/java/net/minecraftforge/fml/ModWorkManager.java index 1a0765f781..f0a37a96c7 100644 --- a/src/main/java/net/minecraftforge/fml/ModWorkManager.java +++ b/src/main/java/net/minecraftforge/fml/ModWorkManager.java @@ -17,10 +17,10 @@ public class ModWorkManager { boolean selfDriven(); boolean driveOne(); - default void drive() { + default void drive(Runnable ticker) { if (!selfDriven()) { while (driveOne()) - ; + ticker.run(); } } } diff --git a/src/main/java/net/minecraftforge/fml/SidedProvider.java b/src/main/java/net/minecraftforge/fml/SidedProvider.java deleted file mode 100644 index 230c17978c..0000000000 --- a/src/main/java/net/minecraftforge/fml/SidedProvider.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Minecraft Forge - * Copyright (c) 2016-2020. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation version 2.1 - * of the License. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -package net.minecraftforge.fml; - -import net.minecraft.client.Minecraft; -import net.minecraft.server.dedicated.DedicatedServer; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.fml.client.ClientHooks; -import net.minecraftforge.fml.loading.FMLEnvironment; - -import java.util.function.Function; -import java.util.function.Supplier; - -public enum SidedProvider -{ - // All of these need to be careful not to directly dereference the client and server elements in their signatures - DATAFIXER( - c->c.get().getDataFixer(), - s->s.get().getDataFixer(), - ()-> { throw new UnsupportedOperationException(); }), - STRIPCHARS( - (Function, Function>)c-> ClientHooks::stripSpecialChars, - s-> str->str, - ()-> str->str); - - private static Supplier client; - private static Supplier server; - - public static void setClient(Supplier client) - { - SidedProvider.client = client; - } - public static void setServer(Supplier server) - { - SidedProvider.server = server; - } - - private final Function, ?> clientSide; - private final Function, ?> serverSide; - private final Supplier testSide; - - SidedProvider(Function, T> clientSide, Function, T> serverSide, Supplier testSide) - { - this.clientSide = clientSide; - this.serverSide = serverSide; - this.testSide = testSide; - } - - @SuppressWarnings("unchecked") - public T get() { - if (FMLEnvironment.dist == Dist.CLIENT) { - return (T)this.clientSide.apply(client); - } - else if (FMLEnvironment.dist == Dist.DEDICATED_SERVER) { - return (T)this.serverSide.apply(server); - } - else { - return (T)this.testSide.get(); - } - } -} diff --git a/src/main/java/net/minecraftforge/fml/client/ClientHooks.java b/src/main/java/net/minecraftforge/fml/client/ClientHooks.java index 79093cc683..28cdc0e3b8 100644 --- a/src/main/java/net/minecraftforge/fml/client/ClientHooks.java +++ b/src/main/java/net/minecraftforge/fml/client/ClientHooks.java @@ -75,9 +75,6 @@ public class ClientHooks { private static final Logger LOGGER = LogManager.getLogger(); private static final Marker CLIENTHOOKS = MarkerManager.getMarker("CLIENTHOOKS"); - // From FontRenderer.renderCharAtPos - private static final String ALLOWED_CHARS = "\u00c0\u00c1\u00c2\u00c8\u00ca\u00cb\u00cd\u00d3\u00d4\u00d5\u00da\u00df\u00e3\u00f5\u011f\u0130\u0131\u0152\u0153\u015e\u015f\u0174\u0175\u017e\u0207\u0000\u0000\u0000\u0000\u0000\u0000\u0000 !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u0000\u00c7\u00fc\u00e9\u00e2\u00e4\u00e0\u00e5\u00e7\u00ea\u00eb\u00e8\u00ef\u00ee\u00ec\u00c4\u00c5\u00c9\u00e6\u00c6\u00f4\u00f6\u00f2\u00fb\u00f9\u00ff\u00d6\u00dc\u00f8\u00a3\u00d8\u00d7\u0192\u00e1\u00ed\u00f3\u00fa\u00f1\u00d1\u00aa\u00ba\u00bf\u00ae\u00ac\u00bd\u00bc\u00a1\u00ab\u00bb\u2591\u2592\u2593\u2502\u2524\u2561\u2562\u2556\u2555\u2563\u2551\u2557\u255d\u255c\u255b\u2510\u2514\u2534\u252c\u251c\u2500\u253c\u255e\u255f\u255a\u2554\u2569\u2566\u2560\u2550\u256c\u2567\u2568\u2564\u2565\u2559\u2558\u2552\u2553\u256b\u256a\u2518\u250c\u2588\u2584\u258c\u2590\u2580\u03b1\u03b2\u0393\u03c0\u03a3\u03c3\u03bc\u03c4\u03a6\u0398\u03a9\u03b4\u221e\u2205\u2208\u2229\u2261\u00b1\u2265\u2264\u2320\u2321\u00f7\u2248\u00b0\u2219\u00b7\u221a\u207f\u00b2\u25a0\u0000"; - private static final CharMatcher DISALLOWED_CHAR_MATCHER = CharMatcher.anyOf(ALLOWED_CHARS).negate(); private static final ResourceLocation iconSheet = new ResourceLocation(ForgeVersion.MOD_ID, "textures/gui/icons.png"); @Nullable @@ -197,12 +194,6 @@ public class ClientHooks } - public static String stripSpecialChars(String message) - { - // We can't handle many unicode points in the splash renderer - return DISALLOWED_CHAR_MATCHER.removeFrom(net.minecraft.util.StringUtils.stripControlCodes(message)); - } - private static SetMultimap missingTextures = HashMultimap.create(); private static Set badTextureDomains = Sets.newHashSet(); private static Table> brokenTextures = HashBasedTable.create(); diff --git a/src/main/java/net/minecraftforge/fml/client/ClientModLoader.java b/src/main/java/net/minecraftforge/fml/client/ClientModLoader.java index 5bd0762553..8597706cc5 100644 --- a/src/main/java/net/minecraftforge/fml/client/ClientModLoader.java +++ b/src/main/java/net/minecraftforge/fml/client/ClientModLoader.java @@ -22,32 +22,40 @@ package net.minecraftforge.fml.client; import static net.minecraftforge.fml.Logging.CORE; import static net.minecraftforge.fml.loading.LogMarkers.LOADING; +import java.io.File; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; +import java.util.Date; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; import java.util.function.Consumer; +import net.minecraft.crash.CrashReport; +import net.minecraft.crash.CrashReportCategory; import net.minecraft.resources.IFutureReloadListener; import net.minecraft.resources.IPackNameDecorator; import net.minecraft.resources.IReloadableResourceManager; import net.minecraft.resources.IResourceManager; import net.minecraft.resources.ResourcePackInfo; import net.minecraft.resources.ResourcePackList; +import net.minecraft.util.datafix.codec.DatapackCodec; import net.minecraftforge.fml.BrandingControl; import net.minecraftforge.fml.LoadingFailedException; import net.minecraftforge.fml.LogicalSidedProvider; +import net.minecraftforge.fml.ModList; import net.minecraftforge.fml.ModLoader; import net.minecraftforge.fml.ModLoadingStage; import net.minecraftforge.fml.ModLoadingWarning; import net.minecraftforge.fml.ModWorkManager; -import net.minecraftforge.fml.SidedProvider; import net.minecraftforge.fml.VersionChecker; +import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -80,22 +88,41 @@ public class ClientModLoader private static final Logger LOGGER = LogManager.getLogger(); private static boolean loading; private static Minecraft mc; + private static boolean loadingComplete; private static LoadingFailedException error; private static EarlyLoaderGUI earlyLoaderGUI; + private static class SpacedRunnable implements Runnable { + static final long NANO_SLEEP_TIME = TimeUnit.MILLISECONDS.toNanos(50); + private final Runnable wrapped; + private long lastRun; + + private SpacedRunnable(final Runnable wrapped) { + this.wrapped = wrapped; + this.lastRun = System.nanoTime() - NANO_SLEEP_TIME; + } + + @Override + public void run() { + if (System.nanoTime() - this.lastRun > NANO_SLEEP_TIME) { + wrapped.run(); + this.lastRun = System.nanoTime(); + } + } + } public static void begin(final Minecraft minecraft, final ResourcePackList defaultResourcePacks, final IReloadableResourceManager mcResourceManager, DownloadingPackFinder metadataSerializer) { // force log4j to shutdown logging in a shutdown hook. This is because we disable default shutdown hook so the server properly logs it's shutdown Runtime.getRuntime().addShutdownHook(new Thread(LogManager::shutdown)); loading = true; ClientModLoader.mc = minecraft; - SidedProvider.setClient(()->minecraft); LogicalSidedProvider.setClient(()->minecraft); LanguageHook.loadForgeAndMCLangs(); earlyLoaderGUI = new EarlyLoaderGUI(minecraft.getMainWindow()); - createRunnableWithCatch(()->ModLoader.get().gatherAndInitializeMods(ModWorkManager.syncExecutor(), ModWorkManager.parallelExecutor(), earlyLoaderGUI::renderTick)).run(); + createRunnableWithCatch(()->ModLoader.get().gatherAndInitializeMods(ModWorkManager.syncExecutor(), ModWorkManager.parallelExecutor(), new SpacedRunnable(earlyLoaderGUI::renderTick))).run(); if (error == null) { ResourcePackLoader.loadResourcePacks(defaultResourcePacks, ClientModLoader::buildPackFinder); + DatapackCodec.field_234880_a_.addModPacks(ResourcePackLoader.getPackNames()); mcResourceManager.addReloadListener(ClientModLoader::onResourceReload); mcResourceManager.addReloadListener(BrandingControl.resourceManagerReloadListener()); ModelLoaderRegistry.init(); @@ -110,10 +137,10 @@ public class ClientModLoader private static Runnable createRunnableWithCatch(Runnable r) { return ()-> { + if (loadingComplete) return; try { r.run(); } catch (LoadingFailedException e) { - MinecraftForge.EVENT_BUS.shutdown(); if (error == null) error = e; } }; @@ -121,7 +148,7 @@ public class ClientModLoader private static void startModLoading(ModWorkManager.DrivenExecutor syncExecutor, Executor parallelExecutor) { earlyLoaderGUI.handleElsewhere(); - createRunnableWithCatch(() -> ModLoader.get().loadMods(syncExecutor, parallelExecutor, executor -> CompletableFuture.runAsync(ClientModLoader::preSidedRunnable, executor), executor -> CompletableFuture.runAsync(ClientModLoader::postSidedRunnable, executor), ()->{})).run(); + createRunnableWithCatch(() -> ModLoader.get().loadMods(syncExecutor, parallelExecutor, executor -> CompletableFuture.runAsync(ClientModLoader::preSidedRunnable, executor), executor -> CompletableFuture.runAsync(ClientModLoader::postSidedRunnable, executor), new SpacedRunnable(earlyLoaderGUI::renderTick))).run(); } private static void postSidedRunnable() { @@ -135,15 +162,15 @@ public class ClientModLoader private static void finishModLoading(ModWorkManager.DrivenExecutor syncExecutor, Executor parallelExecutor) { - createRunnableWithCatch(() -> ModLoader.get().finishMods(syncExecutor, parallelExecutor, ()->{})).run(); + createRunnableWithCatch(() -> ModLoader.get().finishMods(syncExecutor, parallelExecutor, new SpacedRunnable(earlyLoaderGUI::renderTick))).run(); loading = false; + loadingComplete = true; // reload game settings on main thread syncExecutor.execute(()->mc.gameSettings.loadOptions()); } public static VersionChecker.Status checkForUpdates() { - boolean anyOutdated = ModList.get().getMods().stream() .map(VersionChecker::getResult) .map(result -> result.status) @@ -173,6 +200,24 @@ public class ClientModLoader if (error == null) { // We can finally start the forge eventbus up MinecraftForge.EVENT_BUS.start(); + } else { + final CrashReport crashReport = CrashReport.makeCrashReport(new Exception("Mod Loading has failed"), "Mod loading error has occurred"); + error.getErrors().forEach(mle -> { + final CrashReportCategory category = crashReport.makeCategory(mle.getModInfo().getModId()); + category.applyStackTrace(mle.getCause()); + category.addDetail("Failure message", mle.getCleanMessage()); + category.addDetail("Exception message", mle.getCause().toString()); + category.addDetail("Mod Version", mle.getModInfo().getVersion().toString()); + category.addDetail("Mod Issue URL", ((ModFileInfo)mle.getModInfo().getOwningFile()).getConfigElement("issueTrackerURL").orElse("NOT PROVIDED")); + }); + final File file1 = new File(mc.gameDir, "crash-reports"); + final File file2 = new File(file1, "crash-" + (new SimpleDateFormat("yyyy-MM-dd_HH.mm.ss")).format(new Date()) + "-client.txt"); + if (crashReport.saveToFile(file2)) { + LOGGER.fatal("Crash report saved to {}", crashReport.getFile()); + } else { + LOGGER.fatal("Failed to save crash report"); + } + System.out.print(crashReport.getCompleteReport()); } if (error != null || !warnings.isEmpty()) { mc.displayGuiScreen(new LoadingErrorScreen(error, warnings)); diff --git a/src/main/java/net/minecraftforge/fml/event/lifecycle/GatherDataEvent.java b/src/main/java/net/minecraftforge/fml/event/lifecycle/GatherDataEvent.java index 54620d2fb5..cc0051fa31 100644 --- a/src/main/java/net/minecraftforge/fml/event/lifecycle/GatherDataEvent.java +++ b/src/main/java/net/minecraftforge/fml/event/lifecycle/GatherDataEvent.java @@ -22,6 +22,7 @@ package net.minecraftforge.fml.event.lifecycle; import cpw.mods.modlauncher.api.LamdbaExceptionUtils; import net.minecraft.data.DataGenerator; import net.minecraftforge.client.model.generators.ExistingFileHelper; +import net.minecraftforge.eventbus.api.Event; import net.minecraftforge.fml.ModContainer; import java.nio.file.Path; @@ -33,19 +34,25 @@ import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; -public class GatherDataEvent extends ModLifecycleEvent +public class GatherDataEvent extends Event implements IModBusEvent { private final DataGenerator dataGenerator; private final DataGeneratorConfig config; private final ExistingFileHelper existingFileHelper; - public GatherDataEvent(final ModContainer modContainer, final DataGenerator dataGenerator, final DataGeneratorConfig dataGeneratorConfig, ExistingFileHelper existingFileHelper) + private final ModContainer modContainer; + + public GatherDataEvent(final ModContainer mc, final DataGenerator dataGenerator, final DataGeneratorConfig dataGeneratorConfig, ExistingFileHelper existingFileHelper) { - super(modContainer); + this.modContainer = mc; this.dataGenerator = dataGenerator; this.config = dataGeneratorConfig; this.existingFileHelper = existingFileHelper; } + public ModContainer getModContainer() { + return this.modContainer; + } + public DataGenerator getGenerator() { return this.dataGenerator; } public ExistingFileHelper getExistingFileHelper() { return existingFileHelper; } public boolean includeServer() { return this.config.server; } diff --git a/src/main/java/net/minecraftforge/fml/packs/ResourcePackLoader.java b/src/main/java/net/minecraftforge/fml/packs/ResourcePackLoader.java index 35c401d206..c2b80c68bf 100644 --- a/src/main/java/net/minecraftforge/fml/packs/ResourcePackLoader.java +++ b/src/main/java/net/minecraftforge/fml/packs/ResourcePackLoader.java @@ -54,16 +54,16 @@ public class ResourcePackLoader public static void loadResourcePacks(ResourcePackList resourcePacks, BiFunction, BiConsumer, IPackInfoFinder> packFinder) { resourcePackList = resourcePacks; - modResourcePacks = ModList.get().getModFiles().stream(). - filter(mf->!Objects.equals(mf.getModLoader(),"minecraft")). - map(mf -> new ModFileResourcePack(mf.getFile())). - collect(Collectors.toMap(ModFileResourcePack::getModFile, Function.identity(), (u,v) -> { throw new IllegalStateException(String.format("Duplicate key %s", u)); }, LinkedHashMap::new)); + modResourcePacks = ModList.get().getModFiles().stream() + .filter(mf->!Objects.equals(mf.getModLoader(),"minecraft")) + .map(mf -> new ModFileResourcePack(mf.getFile())) + .collect(Collectors.toMap(ModFileResourcePack::getModFile, Function.identity(), (u,v) -> { throw new IllegalStateException(String.format("Duplicate key %s", u)); }, LinkedHashMap::new)); resourcePacks.addPackFinder(new LambdaFriendlyPackFinder(packFinder.apply(modResourcePacks, ModFileResourcePack::setPackInfo))); } public static List getPackNames() { - return modResourcePacks.values().stream().map(pack -> pack.getPackInfo().getName()).collect(Collectors.toList()); + return ModList.get().applyForEachModFile(mf->"mod:"+mf.getModInfos().get(0).getModId()).filter(n->!n.equals("mod:minecraft")).collect(Collectors.toList()); } public static Comparator> getSorter() { diff --git a/src/main/java/net/minecraftforge/fml/server/ServerLifecycleHooks.java b/src/main/java/net/minecraftforge/fml/server/ServerLifecycleHooks.java index 107ab5d046..71f7398011 100644 --- a/src/main/java/net/minecraftforge/fml/server/ServerLifecycleHooks.java +++ b/src/main/java/net/minecraftforge/fml/server/ServerLifecycleHooks.java @@ -31,14 +31,12 @@ import java.util.function.BiConsumer; import java.util.function.Consumer; import net.minecraft.resources.IPackNameDecorator; -import net.minecraft.server.dedicated.DedicatedServer; import net.minecraft.world.storage.FolderName; import net.minecraftforge.fml.DistExecutor; import net.minecraftforge.fml.LogicalSidedProvider; import net.minecraftforge.fml.ModLoader; import net.minecraftforge.fml.ModLoadingStage; import net.minecraftforge.fml.ModLoadingWarning; -import net.minecraftforge.fml.SidedProvider; import net.minecraftforge.fml.network.ConnectionType; import net.minecraftforge.fml.network.FMLNetworkConstants; import net.minecraftforge.fml.network.FMLStatusPing; @@ -92,7 +90,6 @@ public class ServerLifecycleHooks currentServer = server; currentServer.getServerStatusResponse().setForgeData(new FMLStatusPing()); //gathers NetworkRegistry data // on the dedi server we need to force the stuff to setup properly - DistExecutor.unsafeRunWhenOn(Dist.DEDICATED_SERVER, ()->()->SidedProvider.setServer(()->(DedicatedServer)server)); LogicalSidedProvider.setServer(()->server); ConfigTracker.INSTANCE.loadConfigs(ModConfig.Type.SERVER, getServerConfigPath(server)); return !MinecraftForge.EVENT_BUS.post(new FMLServerAboutToStartEvent(server)); @@ -185,30 +182,6 @@ public class ServerLifecycleHooks public static void handleExit(int retVal) { -/* - CountDownLatch latch = exitLatch; - - if (latch != null) - { - try - { - LOGGER.info("Waiting for the server to terminate/save."); - if (!latch.await(10, TimeUnit.SECONDS)) - { - LOGGER.warn("The server didn't stop within 10 seconds, exiting anyway."); - } - else - { - LOGGER.info("Server terminated."); - } - } - catch (InterruptedException e) - { - LOGGER.warn("Interrupted wait, exiting."); - } - } - -*/ System.exit(retVal); } @@ -235,5 +208,4 @@ public class ServerLifecycleHooks consumer.accept(packInfo); } } - } diff --git a/src/main/java/net/minecraftforge/fml/server/ServerModLoader.java b/src/main/java/net/minecraftforge/fml/server/ServerModLoader.java index 8e17b9b557..b276fadd0a 100644 --- a/src/main/java/net/minecraftforge/fml/server/ServerModLoader.java +++ b/src/main/java/net/minecraftforge/fml/server/ServerModLoader.java @@ -19,17 +19,23 @@ package net.minecraftforge.fml.server; +import net.minecraft.crash.CrashReport; +import net.minecraft.crash.CrashReportCategory; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.fml.LoadingFailedException; import net.minecraftforge.fml.LogicalSidedProvider; import net.minecraftforge.fml.ModLoader; import net.minecraftforge.fml.ModLoadingWarning; import net.minecraftforge.fml.ModWorkManager; -import net.minecraftforge.fml.SidedProvider; +import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import java.io.File; +import java.text.SimpleDateFormat; +import java.util.Date; import java.util.List; +import java.util.concurrent.CompletableFuture; import static net.minecraftforge.fml.loading.LogMarkers.LOADING; @@ -39,20 +45,34 @@ public class ServerModLoader private static boolean hasErrors = false; public static void load() { - SidedProvider.setServer(()-> { - throw new IllegalStateException("Unable to access server yet"); - }); LogicalSidedProvider.setServer(()-> { throw new IllegalStateException("Unable to access server yet"); }); LanguageHook.loadForgeAndMCLangs(); try { ModLoader.get().gatherAndInitializeMods(ModWorkManager.syncExecutor(), ModWorkManager.parallelExecutor(), ()->{}); - ModLoader.get().loadMods(ModWorkManager.syncExecutor(), ModWorkManager.parallelExecutor(), null, null, ()->{}); + ModLoader.get().loadMods(ModWorkManager.syncExecutor(), ModWorkManager.parallelExecutor(), e-> CompletableFuture.runAsync(()->{}, e), e->CompletableFuture.runAsync(()->{}, e), ()->{}); ModLoader.get().finishMods(ModWorkManager.syncExecutor(), ModWorkManager.parallelExecutor(), ()->{}); - } catch (LoadingFailedException e) { + } catch (LoadingFailedException error) { ServerModLoader.hasErrors = true; - throw e; + final CrashReport crashReport = CrashReport.makeCrashReport(error, "Mod loading error has occurred"); + error.getErrors().forEach(mle -> { + final CrashReportCategory category = crashReport.makeCategory(mle.getModInfo().getModId()); + category.applyStackTrace(mle.getCause()); + category.addDetail("Failure message", mle.getCleanMessage()); + category.addDetail("Exception message", mle.getCause().toString()); + category.addDetail("Mod Version", mle.getModInfo().getVersion().toString()); + category.addDetail("Mod Issue URL", ((ModFileInfo)mle.getModInfo().getOwningFile()).getConfigElement("issueTrackerURL").orElse("NOT PROVIDED")); + }); + final File file1 = new File("crash-reports"); + final File file2 = new File(file1, "crash-" + (new SimpleDateFormat("yyyy-MM-dd_HH.mm.ss")).format(new Date()) + "-server.txt"); + if (crashReport.saveToFile(file2)) { + LOGGER.fatal("Crash report saved to {}", file2); + } else { + LOGGER.fatal("Failed to save crash report"); + } + System.out.print(crashReport.getCompleteReport()); + throw error; } List warnings = ModLoader.get().getWarnings(); if (!warnings.isEmpty()) {