diff --git a/loader/src/main/java/net/neoforged/fml/ModLoader.java b/loader/src/main/java/net/neoforged/fml/ModLoader.java index 1a7539252..fa3cf3f78 100644 --- a/loader/src/main/java/net/neoforged/fml/ModLoader.java +++ b/loader/src/main/java/net/neoforged/fml/ModLoader.java @@ -50,83 +50,54 @@ import org.jetbrains.annotations.Nullable; /** - * Loads mods. + * Contains the logic to load mods, i.e. turn the {@link LoadingModList} into the {@link ModList}, + * as well as initialization tasks for mods and methods to dispatch mod bus events. * - * Dispatch cycle is seen in {@code #loadMods()} and {@code #finishMods()} + *

For the mod initialization flow, see {@code CommonModLoader} in NeoForge. * - * Overall sequence for loadMods is: - *

- *
CONSTRUCT
- *
Constructs the mod instance. Mods can typically setup basic environment such as Event listeners - * and Configuration specifications here.
- *
Automated dispatches
- *
Dispatches automated elements : {@code net.neoforged.fml.common.Mod.EventBusSubscriber}, - * {@code net.neoforged.event.RegistryEvent}, {@code net.neoforged.common.capabilities.CapabilityInject} - * and others
- *
CONFIG_LOAD
- *
Dispatches ConfigLoadEvent to mods
- *
COMMON_SETUP
- *
Dispatches {@code net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent} to mods
- *
SIDED_SETUP
- *
Dispatches {@code net.neoforged.fml.event.lifecycle.FMLClientSetupEvent} or - * {@code net.neoforged.fml.event.lifecycle.FMLDedicatedServerSetupEvent} to mods
- *
- * - * Overall sequence for finishMods is: - *
- *
ENQUEUE_IMC
- *
Dispatches {@code net.neoforged.fml.event.lifecycle.InterModEnqueueEvent} to mods, - * for enqueuing {@link InterModComms} messages for other mods to receive subsequently
- *
PROCESS_IMC
- *
Dispatches {@code net.neoforged.fml.event.lifecycle.InterModProcessEvent} to mods, - * for processing {@link InterModComms} messages received from other mods prior to this event
- *
COMPLETE
- *
Dispatches {@code net.neoforged.fml.event.lifecycle.FMLLoadCompleteEvent} to mods, - * and completes the mod loading sequence.
- *
+ *

For mod bus event dispatch, see {@link #postEvent(Event)} and related methods. */ -public class ModLoader { +public final class ModLoader { + private ModLoader() {} + private static final Logger LOGGER = LogManager.getLogger(); - private static ModLoader INSTANCE; - private final LoadingModList loadingModList; - - private final List loadingExceptions; - private final List loadingWarnings; - private boolean loadingStateValid; - @SuppressWarnings("OptionalUsedAsFieldOrParameterType") - private final Optional> statusConsumer = StartupNotificationManager.modLoaderConsumer(); - private ModList modList; - - private ModLoader() { - INSTANCE = this; - this.loadingModList = FMLLoader.getLoadingModList(); - this.loadingExceptions = FMLLoader.getLoadingModList().getErrors().stream() + + private static final List loadingExceptions = new ArrayList<>(); + private static final List loadingWarnings = new ArrayList<>(); + private static boolean loadingStateValid; + private static ModList modList; + + static { + CrashReportCallables.registerCrashCallable("ModLauncher", FMLLoader::getLauncherInfo); + CrashReportCallables.registerCrashCallable("ModLauncher launch target", FMLLoader::launcherHandlerName); + CrashReportCallables.registerCrashCallable("ModLauncher services", ModLoader::computeModLauncherServiceList); + CrashReportCallables.registerCrashCallable("FML Language Providers", ModLoader::computeLanguageList); + } + + private static void collectLoadingErrors(LoadingModList loadingModList) { + loadingModList.getErrors().stream() .flatMap(ModLoadingException::fromEarlyException) - .collect(Collectors.toList()); - this.loadingWarnings = FMLLoader.getLoadingModList().getBrokenFiles().stream() + .forEach(loadingExceptions::add); + loadingModList.getBrokenFiles().stream() .map(file -> new ModLoadingWarning(null, InvalidModIdentifier.identifyJarProblem(file.getFilePath()).orElse("fml.modloading.brokenfile"), file.getFileName())) - .collect(Collectors.toList()); + .forEach(loadingWarnings::add); - FMLLoader.getLoadingModList().getWarnings().stream() + loadingModList.getWarnings().stream() .flatMap(ModLoadingWarning::fromEarlyException) - .forEach(this.loadingWarnings::add); + .forEach(loadingWarnings::add); - FMLLoader.getLoadingModList().getModFiles().stream() + loadingModList.getModFiles().stream() .filter(ModFileInfo::missingLicense) - .filter(modFileInfo -> modFileInfo.getMods().stream().noneMatch(thisModInfo -> this.loadingExceptions.stream().map(ModLoadingException::getModInfo).anyMatch(otherInfo -> otherInfo == thisModInfo))) //Ignore files where any other mod already encountered an error + .filter(modFileInfo -> modFileInfo.getMods().stream().noneMatch(thisModInfo -> loadingExceptions.stream().map(ModLoadingException::getModInfo).anyMatch(otherInfo -> otherInfo == thisModInfo))) //Ignore files where any other mod already encountered an error .map(modFileInfo -> new ModLoadingException(null, "fml.modloading.missinglicense", null, modFileInfo.getFile())) - .forEach(this.loadingExceptions::add); - CrashReportCallables.registerCrashCallable("ModLauncher", FMLLoader::getLauncherInfo); - CrashReportCallables.registerCrashCallable("ModLauncher launch target", FMLLoader::launcherHandlerName); - CrashReportCallables.registerCrashCallable("ModLauncher services", this::computeModLauncherServiceList); - CrashReportCallables.registerCrashCallable("FML Language Providers", this::computeLanguageList); + .forEach(loadingExceptions::add); } - private String computeLanguageList() { + private static String computeLanguageList() { return "\n" + FMLLoader.getLanguageLoadingProvider().applyForEach(lp -> lp.name() + "@" + lp.getClass().getPackage().getImplementationVersion()).collect(Collectors.joining("\n\t\t", "\t\t", "")); } - private String computeModLauncherServiceList() { + private static String computeModLauncherServiceList() { final List> mods = FMLLoader.modLauncherModList(); return "\n" + mods.stream().map(mod -> mod.getOrDefault("file", "nofile") + " " + mod.getOrDefault("name", "missing") + @@ -134,10 +105,6 @@ private String computeModLauncherServiceList() { " " + mod.getOrDefault("description", "")).collect(Collectors.joining("\n\t\t", "\t\t", "")); } - public static ModLoader get() { - return INSTANCE == null ? INSTANCE = new ModLoader() : INSTANCE; - } - /** * Run on the primary starting thread by ClientModLoader and ServerModLoader * @@ -145,16 +112,19 @@ public static ModLoader get() { * @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 Executor syncExecutor, final Executor parallelExecutor, final Runnable periodicTask) { + public static void gatherAndInitializeMods(final Executor syncExecutor, final Executor parallelExecutor, final Runnable periodicTask) { + LoadingModList loadingModList = FMLLoader.getLoadingModList(); + collectLoadingErrors(loadingModList); + ForgeFeature.registerFeature("javaVersion", ForgeFeature.VersionFeatureTest.forVersionString(IModInfo.DependencySide.BOTH, System.getProperty("java.version"))); ForgeFeature.registerFeature("openGLVersion", ForgeFeature.VersionFeatureTest.forVersionString(IModInfo.DependencySide.CLIENT, ImmediateWindowHandler.getGLVersion())); loadingStateValid = true; FMLLoader.backgroundScanHandler.waitForScanToComplete(periodicTask); final ModList modList = ModList.of(loadingModList.getModFiles().stream().map(ModFileInfo::getFile).toList(), loadingModList.getMods()); - if (!this.loadingExceptions.isEmpty()) { + if (!loadingExceptions.isEmpty()) { LOGGER.fatal(CORE, "Error during pre-loading phase", loadingExceptions.get(0)); - statusConsumer.ifPresent(c -> c.accept("ERROR DURING MOD LOADING")); + StartupNotificationManager.modLoaderMessage("ERROR DURING MOD LOADING"); modList.setLoadedMods(Collections.emptyList()); loadingStateValid = false; throw new LoadingFailedException(loadingExceptions); @@ -167,7 +137,7 @@ public void gatherAndInitializeMods(final Executor syncExecutor, final Executor if (!failedBounds.isEmpty()) { LOGGER.fatal(CORE, "Failed to validate feature bounds for mods: {}", failedBounds); - statusConsumer.ifPresent(c -> c.accept("ERROR DURING MOD LOADING")); + StartupNotificationManager.modLoaderMessage("ERROR DURING MOD LOADING"); modList.setLoadedMods(Collections.emptyList()); loadingStateValid = false; throw new LoadingFailedException(failedBounds.stream() @@ -177,23 +147,23 @@ public void gatherAndInitializeMods(final Executor syncExecutor, final Executor final List modContainers = loadingModList.getModFiles().stream() .map(ModFileInfo::getFile) - .map(this::buildMods) + .map(ModLoader::buildMods) .mapMulti(Iterable::forEach) .toList(); if (!loadingExceptions.isEmpty()) { LOGGER.fatal(CORE, "Failed to initialize mod containers", loadingExceptions.get(0)); - statusConsumer.ifPresent(c -> c.accept("ERROR DURING MOD LOADING")); + StartupNotificationManager.modLoaderMessage("ERROR DURING MOD LOADING"); modList.setLoadedMods(Collections.emptyList()); loadingStateValid = false; throw new LoadingFailedException(loadingExceptions); } modList.setLoadedMods(modContainers); - this.modList = modList; + ModLoader.modList = modList; constructMods(syncExecutor, parallelExecutor, periodicTask); } - private void constructMods(Executor syncExecutor, Executor parallelExecutor, Runnable periodicTask) { + private static void constructMods(Executor syncExecutor, Executor parallelExecutor, Runnable periodicTask) { var workQueue = new DeferredWorkQueue("Mod Construction"); dispatchParallelTask("Mod Construction", parallelExecutor, periodicTask, modContainer -> { modContainer.constructMod(); @@ -205,14 +175,14 @@ private void constructMods(Executor syncExecutor, Executor parallelExecutor, Run /** * Runs a single task on the {@code syncExecutor}, while ticking the loading screen. */ - public void runInitTask(String name, Executor syncExecutor, Runnable periodicTask, Runnable initTask) { + public static void runInitTask(String name, Executor syncExecutor, Runnable periodicTask, Runnable initTask) { waitForTask(name, periodicTask, CompletableFuture.runAsync(initTask, syncExecutor)); } /** * Dispatches a parallel event across all mod containers, with progress displayed on the loading screen. */ - public void dispatchParallelEvent(String name, Executor syncExecutor, Executor parallelExecutor, Runnable periodicTask, BiFunction eventConstructor) { + public static void dispatchParallelEvent(String name, Executor syncExecutor, Executor parallelExecutor, Runnable periodicTask, BiFunction eventConstructor) { var workQueue = new DeferredWorkQueue(name); dispatchParallelTask(name, parallelExecutor, periodicTask, modContainer -> { modContainer.acceptEvent(eventConstructor.apply(modContainer, workQueue)); @@ -223,8 +193,8 @@ public void dispatchParallelEvent(String name, Executor syncExecutor, Executor p /** * Waits for a task to complete, displaying the name of the task on the loading screen. */ - public void waitForTask(String name, Runnable periodicTask, CompletableFuture future) { - var progress = StartupMessageManager.addProgressBar(name, 0); + public static void waitForTask(String name, Runnable periodicTask, CompletableFuture future) { + var progress = StartupNotificationManager.addProgressBar(name, 0); try { waitForFuture(name, periodicTask, future); } finally { @@ -235,8 +205,8 @@ public void waitForTask(String name, Runnable periodicTask, CompletableFuture /** * Dispatches a task across all mod containers in parallel, with progress displayed on the loading screen. */ - public void dispatchParallelTask(String name, Executor parallelExecutor, Runnable periodicTask, Consumer task) { - var progress = StartupMessageManager.addProgressBar(name, modList.size()); + public static void dispatchParallelTask(String name, Executor parallelExecutor, Runnable periodicTask, Consumer task) { + var progress = StartupNotificationManager.addProgressBar(name, modList.size()); try { periodicTask.run(); var futureList = modList.getSortedMods().stream() @@ -258,7 +228,7 @@ public void dispatchParallelTask(String name, Executor parallelExecutor, Runnabl } } - private void waitForFuture(String name, Runnable periodicTask, CompletableFuture future) { + private static void waitForFuture(String name, Runnable periodicTask, CompletableFuture future) { while (true) { periodicTask.run(); try { @@ -272,7 +242,7 @@ private void waitForFuture(String name, Runnable periodicTask, CompletableFuture .collect(Collectors.toList()); if (!notModLoading.isEmpty()) { LOGGER.fatal("Encountered non-modloading exceptions!", e); - statusConsumer.ifPresent(c -> c.accept("ERROR DURING MOD LOADING")); + StartupNotificationManager.modLoaderMessage("ERROR DURING MOD LOADING"); throw new RuntimeException("Encountered non-modloading exception in future " + name, e); } @@ -281,13 +251,13 @@ private void waitForFuture(String name, Runnable periodicTask, CompletableFuture .map(ModLoadingException.class::cast) .collect(Collectors.toList()); LOGGER.fatal(LOADING, "Failed to wait for future {}, {} errors found", name, modLoadingExceptions.size()); - statusConsumer.ifPresent(c -> c.accept("ERROR DURING MOD LOADING")); + StartupNotificationManager.modLoaderMessage("ERROR DURING MOD LOADING"); throw new LoadingFailedException(modLoadingExceptions); } catch (Exception ignored) {} } } - private List buildMods(final IModFile modFile) { + private static List buildMods(final IModFile modFile) { final Map modInfoMap = modFile.getModFileInfo().getMods().stream().collect(Collectors.toMap(IModInfo::getModId, Function.identity())); LOGGER.trace(LOADING, "ModContainer is {}", ModContainer.class.getClassLoader()); @@ -320,7 +290,7 @@ private List buildMods(final IModFile modFile) { return containers.stream().filter(mc -> !(mc instanceof ErroredModContainer)).collect(Collectors.toList()); } - private ModContainer buildModContainerFromTOML(final IModFile modFile, final Map modInfoMap, final Map.Entry idToProviderEntry) { + private static ModContainer buildModContainerFromTOML(final IModFile modFile, final Map modInfoMap, final Map.Entry idToProviderEntry) { try { final String modId = idToProviderEntry.getKey(); final IModLanguageProvider.IModLanguageLoader languageLoader = idToProviderEntry.getValue(); @@ -341,10 +311,10 @@ private ModContainer buildModContainerFromTOML(final IModFile modFile, final Map * 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; + return loadingStateValid; } - public void runEventGenerator(Function generator) { + public static void runEventGenerator(Function generator) { if (!loadingStateValid) { LOGGER.error("Cowardly refusing to send event generator to a broken mod state"); return; @@ -363,7 +333,7 @@ public void runEventGenerator(Function void postEvent(T e) { + public static void postEvent(T e) { if (!loadingStateValid) { LOGGER.error("Cowardly refusing to send event {} to a broken mod state", e.getClass().getName()); return; @@ -373,16 +343,16 @@ public void postEvent(T e) { } } - public T postEventWithReturn(T e) { + public static T postEventWithReturn(T e) { postEvent(e); return e; } - public void postEventWrapContainerInModOrder(T event) { + public static void postEventWrapContainerInModOrder(T event) { postEventWithWrapInModOrder(event, (mc, e) -> ModLoadingContext.get().setActiveContainer(mc), (mc, e) -> ModLoadingContext.get().setActiveContainer(null)); } - public void postEventWithWrapInModOrder(T e, BiConsumer pre, BiConsumer post) { + public static void postEventWithWrapInModOrder(T e, BiConsumer pre, BiConsumer post) { if (!loadingStateValid) { LOGGER.error("Cowardly refusing to send event {} to a broken mod state", e.getClass().getName()); return; @@ -396,12 +366,12 @@ public void postEventWithWrapInModOrder(T e, Bi } } - public List getWarnings() { - return ImmutableList.copyOf(this.loadingWarnings); + public static List getWarnings() { + return ImmutableList.copyOf(loadingWarnings); } - public void addWarning(ModLoadingWarning warning) { - this.loadingWarnings.add(warning); + public static void addWarning(ModLoadingWarning warning) { + loadingWarnings.add(warning); } private static boolean runningDataGen = false; diff --git a/loader/src/main/java/net/neoforged/fml/StartupMessageManager.java b/loader/src/main/java/net/neoforged/fml/StartupMessageManager.java deleted file mode 100644 index f092a9d00..000000000 --- a/loader/src/main/java/net/neoforged/fml/StartupMessageManager.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.fml; - -import java.util.Optional; -import java.util.function.Consumer; -import net.neoforged.fml.loading.progress.ProgressMeter; -import net.neoforged.fml.loading.progress.StartupNotificationManager; - -public class StartupMessageManager { - public static void addModMessage(final String message) { - StartupNotificationManager.addModMessage(message); - } - - public static Optional> modLoaderConsumer() { - return StartupNotificationManager.modLoaderConsumer(); - } - - public static Optional> mcLoaderConsumer() { - return StartupNotificationManager.mcLoaderConsumer(); - } - - public static ProgressMeter addProgressBar(final String barName, final int count) { - return StartupNotificationManager.addProgressBar(barName, count); - } - - public static ProgressMeter prependProgressBar(final String barName, final int count) { - return StartupNotificationManager.prependProgressBar(barName, count); - } -} diff --git a/loader/src/main/java/net/neoforged/fml/loading/progress/StartupNotificationManager.java b/loader/src/main/java/net/neoforged/fml/loading/progress/StartupNotificationManager.java index a25c8aade..5425d53b6 100644 --- a/loader/src/main/java/net/neoforged/fml/loading/progress/StartupNotificationManager.java +++ b/loader/src/main/java/net/neoforged/fml/loading/progress/StartupNotificationManager.java @@ -83,6 +83,10 @@ public static void addModMessage(final String message) { addMessage(Message.MessageType.MOD, safeMessage, 20); } + public static void modLoaderMessage(String message) { + addMessage(Message.MessageType.ML, message, -1); + } + public static Optional> modLoaderConsumer() { return Optional.of(s -> addMessage(Message.MessageType.ML, s, -1)); }