diff --git a/patches/minecraft/net/minecraft/client/Minecraft.java.patch b/patches/minecraft/net/minecraft/client/Minecraft.java.patch index a471f90d44..3685d324b5 100644 --- a/patches/minecraft/net/minecraft/client/Minecraft.java.patch +++ b/patches/minecraft/net/minecraft/client/Minecraft.java.patch @@ -29,7 +29,6 @@ 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); 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 d666bb6d9d..bb06a59ac9 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().isDamageable(stack))); ++ this.isSimple = !net.minecraftforge.fml.DatagenModLoader.isDataGenRunning() && !Arrays.stream(field_199807_b).anyMatch(list -> list.func_199799_a().stream().anyMatch(stack -> stack.func_77973_b().isDamageable(stack))); + Ingredient.INSTANCES.add(this); } 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/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/event/AddReloadListenerEvent.java b/src/main/java/net/minecraftforge/event/AddReloadListenerEvent.java index 4384b29d69..8c2614d7aa 100644 --- a/src/main/java/net/minecraftforge/event/AddReloadListenerEvent.java +++ b/src/main/java/net/minecraftforge/event/AddReloadListenerEvent.java @@ -20,13 +20,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. @@ -49,7 +54,7 @@ public class AddReloadListenerEvent extends Event */ public void addListener(IFutureReloadListener listener) { - listeners.add(listener); + listeners.add(new WrappedStateAwareListener(listener)); } public List getListeners() @@ -61,4 +66,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/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/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 75febfa7ae..82f14bb68a 100644 --- a/src/main/java/net/minecraftforge/fml/DeferredWorkQueue.java +++ b/src/main/java/net/minecraftforge/fml/DeferredWorkQueue.java @@ -21,23 +21,19 @@ package net.minecraftforge.fml; import static net.minecraftforge.fml.Logging.LOADING; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.Callable; +import java.util.*; 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 net.minecraftforge.fml.javafmlmod.FMLModContainer; 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 +48,74 @@ 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))); + } + + /** + * 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; + 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/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/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 4869f7557c..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,28 +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 (shutdown) return; //TODO: Remove - 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); + }); } /** @@ -165,12 +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) {} - - //TEMPORARY INTERNAL FUNCTION UNTIL net.minecraftforge.fml.ModList.dispatchParallelEvent is fixed. - @Deprecated protected boolean shutdown = false; - @Deprecated protected void shutdown() - { - this.shutdown = true; - } + 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 2eefef2ac0..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,39 +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.andThen(e -> this.mods.forEach(ModContainer::shutdown))))); - 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); - 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()) { - 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..ccb7db510c 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; @@ -104,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(); @@ -147,40 +154,27 @@ 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) { + loadingStateValid = true; 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()); + loadingStateValid = false; throw new LoadingFailedException(loadingExceptions); } statusConsumer.ifPresent(c->c.accept("Building Mod List")); @@ -192,34 +186,86 @@ 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); - 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) { + 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()) { + 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)) + .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 +275,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,23 +302,28 @@ public class ModLoader } } - public void postEvent(Event e) { - ModList.get().forEachModContainer((id, mc) -> mc.acceptEvent(e)); + /** + * @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 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 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)); } public List getWarnings() @@ -290,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 b59a776a3a..0ac88395e5 100644 --- a/src/main/java/net/minecraftforge/fml/ModLoadingStage.java +++ b/src/main/java/net/minecraftforge/fml/ModLoadingStage.java @@ -19,34 +19,144 @@ 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(); - 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; + private DeferredWorkQueue deferredWorkQueue; + + 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); + 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 DeferredWorkQueue getDeferredWorkQueue() { + return deferredWorkQueue; + } + + 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..f0a37a96c7 --- /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(Runnable ticker) { + if (!selfDriven()) { + while (driveOne()) + ticker.run(); + } + } + } + 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 deleted file mode 100644 index 7026bb9a10..0000000000 --- a/src/main/java/net/minecraftforge/fml/SidedProvider.java +++ /dev/null @@ -1,85 +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.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; -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(); }), - 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, - ()-> 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 c30e47c4ba..aa8421d3bb 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 @@ -209,12 +206,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 27168a54b9..ba0eb6bdc0 100644 --- a/src/main/java/net/minecraftforge/fml/client/ClientModLoader.java +++ b/src/main/java/net/minecraftforge/fml/client/ClientModLoader.java @@ -22,19 +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 java.util.function.Supplier; -import net.minecraft.resources.*; +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.VersionChecker; +import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -50,16 +71,6 @@ import net.minecraftforge.api.distmarker.OnlyIn; 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.ModList; -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; @@ -75,66 +86,89 @@ 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(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(), 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(); + } } - 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) { return ()-> { + if (loadingComplete) return; try { r.run(); } catch (LoadingFailedException e) { - MinecraftForge.EVENT_BUS.shutdown(); if (error == null) error = e; } }; } - 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), new SpacedRunnable(earlyLoaderGUI::renderTick))).run(); } - private static void postSidedRunnable(Consumer> perModContainerEventProcessor) { + private static void postSidedRunnable() { + LOGGER.debug(LOADING, "Running post client event work"); RenderingRegistry.loadEntityRenderers(mc.getRenderManager()); } - private static void preSidedRunnable(Consumer> perModContainerEventProcessor) { + 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, new SpacedRunnable(earlyLoaderGUI::renderTick))).run(); loading = false; + loadingComplete = true; // reload game settings on main thread - executor.execute(()->mc.gameSettings.loadOptions()); + syncExecutor.execute(()->mc.gameSettings.loadOptions()); } public static VersionChecker.Status checkForUpdates() { - boolean anyOutdated = ModList.get().getMods().stream() .map(VersionChecker::getResult) .map(result -> result.status) @@ -164,6 +198,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/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/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/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 cfc0e14431..4acd64ad82 100644 --- a/src/main/java/net/minecraftforge/fml/javafmlmod/FMLModContainer.java +++ b/src/main/java/net/minecraftforge/fml/javafmlmod/FMLModContainer.java @@ -25,22 +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.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; import static net.minecraftforge.fml.Logging.LOADING; @@ -57,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 @@ -82,53 +68,12 @@ public class FMLModContainer extends ModContainer } } - private void completeLoading(LifecycleEventProvider.LifecycleEvent lifecycleEvent) - { - - } - - private void initMod(LifecycleEventProvider.LifecycleEvent lifecycleEvent) - { - - } - - private Consumer dummy() { return new ErroringConsumer<>(); } - 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()); - this.eventBus.shutdown(); - } - } - - private void preinitMod(LifecycleEventProvider.LifecycleEvent lifecycleEvent) - { - } - - private void constructMod(LifecycleEventProvider.LifecycleEvent event) + private void constructMod() { try { @@ -139,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()); @@ -147,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); } } @@ -169,47 +114,14 @@ public class FMLModContainer extends ModContainer } @Override - protected void acceptEvent(final Event e) - { - if (this.shutdown) return; - 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; + 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/packs/ResourcePackLoader.java b/src/main/java/net/minecraftforge/fml/packs/ResourcePackLoader.java index 591c5d4a84..22f82b6b77 100644 --- a/src/main/java/net/minecraftforge/fml/packs/ResourcePackLoader.java +++ b/src/main/java/net/minecraftforge/fml/packs/ResourcePackLoader.java @@ -55,16 +55,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 7b58a2f311..184555d079 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 84055fb468..b276fadd0a 100644 --- a/src/main/java/net/minecraftforge/fml/server/ServerModLoader.java +++ b/src/main/java/net/minecraftforge/fml/server/ServerModLoader.java @@ -19,16 +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.SidedProvider; +import net.minecraftforge.fml.ModWorkManager; +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; @@ -38,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(() -> {}); - ModLoader.get().loadMods(Runnable::run, (a)->{}, (a)->{}); - ModLoader.get().finishMods(Runnable::run); - } catch (LoadingFailedException e) { + ModLoader.get().gatherAndInitializeMods(ModWorkManager.syncExecutor(), ModWorkManager.parallelExecutor(), ()->{}); + ModLoader.get().loadMods(ModWorkManager.syncExecutor(), ModWorkManager.parallelExecutor(), e-> CompletableFuture.runAsync(()->{}, e), e->CompletableFuture.runAsync(()->{}, e), ()->{}); + ModLoader.get().finishMods(ModWorkManager.syncExecutor(), ModWorkManager.parallelExecutor(), ()->{}); + } 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()) { diff --git a/src/main/java/net/minecraftforge/registries/GameData.java b/src/main/java/net/minecraftforge/registries/GameData.java index be5547ce6e..f54db22f43 100644 --- a/src/main/java/net/minecraftforge/registries/GameData.java +++ b/src/main/java/net/minecraftforge/registries/GameData.java @@ -53,7 +53,6 @@ import net.minecraft.util.RegistryKey; 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; @@ -72,8 +71,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; @@ -94,9 +93,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; import static net.minecraftforge.registries.ForgeRegistries.Keys.*; @@ -328,6 +326,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 { @@ -794,47 +820,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.func_240901_a_()); - keys.remove(ITEMS.func_240901_a_()); - - keys.add(0, BLOCKS.func_240901_a_()); - keys.add(1, ITEMS.func_240901_a_()); - 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()); } }