From a19cf069af1d4daa8513320ab85eacdcf6f917f3 Mon Sep 17 00:00:00 2001 From: Sebastian Hartte Date: Mon, 22 Jan 2024 00:33:31 +0100 Subject: [PATCH] Add Java-based core mods Mods must ship a separate Jar-file (for example via jar-in-jar) and mark it as FMLModType: LIBRARY to make it load above the GAME layer. They then must provide an implementation of ICoreMod via the Java ServiceLoader to contribute their transformers. --- .../fml/loading/CoreModScriptLoader.java | 87 +++++++++++++++++++ .../net/neoforged/fml/loading/FMLLoader.java | 9 -- .../fml/loading/FMLServiceProvider.java | 46 +++++++++- .../neoforged/fml/loading/LoadingModList.java | 8 -- .../fml/loading/moddiscovery/CoreModFile.java | 41 +-------- .../fml/loading/moddiscovery/ModFile.java | 1 - .../loading/moddiscovery/ModValidator.java | 1 - .../neoforgespi/coremod/ICoreMod.java | 15 ++++ 8 files changed, 148 insertions(+), 60 deletions(-) create mode 100644 loader/src/main/java/net/neoforged/fml/loading/CoreModScriptLoader.java create mode 100644 loader/src/main/java/net/neoforged/neoforgespi/coremod/ICoreMod.java diff --git a/loader/src/main/java/net/neoforged/fml/loading/CoreModScriptLoader.java b/loader/src/main/java/net/neoforged/fml/loading/CoreModScriptLoader.java new file mode 100644 index 000000000..040f75cd4 --- /dev/null +++ b/loader/src/main/java/net/neoforged/fml/loading/CoreModScriptLoader.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.fml.loading; + +import cpw.mods.modlauncher.api.ITransformer; +import java.io.IOException; +import java.io.Reader; +import java.nio.file.Files; +import java.util.List; +import java.util.Objects; +import net.neoforged.coremod.CoreModScriptingEngine; +import net.neoforged.coremod.ICoreModScriptSource; +import net.neoforged.fml.loading.moddiscovery.CoreModFile; +import net.neoforged.fml.loading.moddiscovery.ModFileInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Uses the coremod scripting engine from https://github.com/neoforged/CoreMods + * to load JS-based coremods. Avoids loading any of the classes unless a mod + * contains a JS-based coremod. + */ +class CoreModScriptLoader { + private static final Logger LOGGER = LoggerFactory.getLogger(CoreModScriptLoader.class); + + private CoreModScriptLoader() {} + + /** + * Enumerate script-based coremods. + */ + public static List> loadCoreModScripts(List modFileInfos) { + CoreModScriptingEngine engine; + try { + engine = new CoreModScriptingEngine(); + } catch (NoClassDefFoundError e) { + // Fail for all mods that require a coremod scripting engine to be present + throw new IllegalStateException("Could not find the coremod script-engine, but the following mods require it: " + modFileInfos, e); + } + + LOGGER.debug(LogMarkers.CORE, "Loading coremod scripts"); + for (var modFile : modFileInfos) { + for (var coreMod : modFile.getFile().getCoreMods()) { + engine.loadCoreMod(new ScriptSourceAdapter(coreMod)); + } + } + + return engine.initializeCoreMods(); + } + + private record ScriptSourceAdapter(CoreModFile coreMod) implements ICoreModScriptSource { + @Override + public Reader readCoreMod() throws IOException { + return Files.newBufferedReader(coreMod.path()); + } + + @Override + public String getDebugSource() { + return coreMod.path().toString(); + } + + @Override + public Reader getAdditionalFile(final String fileName) throws IOException { + return Files.newBufferedReader(coreMod.file().findResource(fileName)); + } + + @Override + public String getOwnerId() { + return this.coreMod.file().getModInfos().get(0).getModId(); + } + + @Override + public String toString() { + return "{Name: " + coreMod.name() + ", Owner: " + getOwnerId() + " @ " + getDebugSource() + "}"; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (obj == null || obj.getClass() != this.getClass()) return false; + var that = (ScriptSourceAdapter) obj; + return Objects.equals(this.coreMod, that.coreMod); + } + } +} diff --git a/loader/src/main/java/net/neoforged/fml/loading/FMLLoader.java b/loader/src/main/java/net/neoforged/fml/loading/FMLLoader.java index bff58e9b5..7cbf9d784 100644 --- a/loader/src/main/java/net/neoforged/fml/loading/FMLLoader.java +++ b/loader/src/main/java/net/neoforged/fml/loading/FMLLoader.java @@ -22,7 +22,6 @@ import net.neoforged.accesstransformer.api.AccessTransformerEngine; import net.neoforged.accesstransformer.ml.AccessTransformerService; import net.neoforged.api.distmarker.Dist; -import net.neoforged.coremod.CoreModScriptingEngine; import net.neoforged.fml.common.asm.RuntimeDistCleaner; import net.neoforged.fml.loading.mixin.DeferredMixinConfigRegistration; import net.neoforged.fml.loading.moddiscovery.ModDiscoverer; @@ -38,7 +37,6 @@ public class FMLLoader { private static final Logger LOGGER = LogUtils.getLogger(); private static AccessTransformerEngine accessTransformer; - private static CoreModScriptingEngine coreModEngine; private static LanguageProviderLoader languageProviderLoader; private static Dist dist; private static LoadingModList loadingModList; @@ -90,9 +88,6 @@ static void onInitialLoad(IEnvironment environment) throws IncompatibleEnvironme }); LOGGER.debug(LogMarkers.CORE, "Found Runtime Dist Cleaner"); - coreModEngine = new CoreModScriptingEngine(); - LOGGER.debug(LogMarkers.CORE, "FML found CoreMods version : {}", coreModEngine.getClass().getPackage().getImplementationVersion()); - try { Class.forName("com.electronwill.nightconfig.core.Config", false, environment.getClass().getClassLoader()); Class.forName("com.electronwill.nightconfig.toml.TomlFormat", false, environment.getClass().getClassLoader()); @@ -147,10 +142,6 @@ public static List completeScan(ILaunchContext return List.of(modValidator.getModResources()); } - static CoreModScriptingEngine getCoreModEngine() { - return coreModEngine; - } - public static LanguageProviderLoader getLanguageLoadingProvider() { return languageProviderLoader; } diff --git a/loader/src/main/java/net/neoforged/fml/loading/FMLServiceProvider.java b/loader/src/main/java/net/neoforged/fml/loading/FMLServiceProvider.java index e9fd84b7b..aff27e146 100644 --- a/loader/src/main/java/net/neoforged/fml/loading/FMLServiceProvider.java +++ b/loader/src/main/java/net/neoforged/fml/loading/FMLServiceProvider.java @@ -8,14 +8,18 @@ import static net.neoforged.fml.loading.LogMarkers.CORE; import com.mojang.logging.LogUtils; +import cpw.mods.modlauncher.Launcher; import cpw.mods.modlauncher.api.IEnvironment; import cpw.mods.modlauncher.api.IModuleLayerManager; import cpw.mods.modlauncher.api.ITransformationService; import cpw.mods.modlauncher.api.ITransformer; import cpw.mods.modlauncher.api.IncompatibleEnvironmentException; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Locale; +import java.util.ServiceLoader; import java.util.Set; import java.util.function.BiFunction; import java.util.function.Supplier; @@ -23,6 +27,7 @@ import joptsimple.OptionSpecBuilder; import net.neoforged.neoforgespi.Environment; import net.neoforged.neoforgespi.ILaunchContext; +import net.neoforged.neoforgespi.coremod.ICoreMod; import org.slf4j.Logger; public class FMLServiceProvider implements ITransformationService { @@ -116,6 +121,45 @@ public void argumentValues(OptionResult option) { @Override public List> transformers() { LOGGER.debug(CORE, "Loading coremod transformers"); - return FMLLoader.getCoreModEngine().initializeCoreMods(); + + var result = new ArrayList<>(loadCoreModScripts()); + + // Find all Java core mods + var pluginLayer = Launcher.INSTANCE.findLayerManager() + .flatMap(m -> m.getLayer(IModuleLayerManager.Layer.PLUGIN)) + .orElseThrow(); + for (var coreMod : ServiceLoader.load(pluginLayer, ICoreMod.class)) { + for (var transformer : coreMod.getTransformers()) { + if (transformer == null) { + throw new IllegalStateException("Core mod " + coreMod + " is trying to add null transformer"); + } + LOGGER.debug(CORE, "Adding {} transformer from core-mod {}", transformer.targets(), coreMod); + result.add(transformer); + } + } + + return result; + } + + private List> loadCoreModScripts() { + var filesWithCoreModScripts = LoadingModList.get().getModFiles() + .stream() + .filter(mf -> !mf.getFile().getCoreMods().isEmpty()) + .toList(); + + if (filesWithCoreModScripts.isEmpty()) { + // Don't even bother starting the scripting engine if no mod contains scripting core mods + LOGGER.debug(LogMarkers.CORE, "Not loading coremod script-engine since no mod requested it"); + return Collections.emptyList(); + } + + LOGGER.info(LogMarkers.CORE, "Loading coremod script-engine for {}", filesWithCoreModScripts); + try { + return CoreModScriptLoader.loadCoreModScripts(filesWithCoreModScripts); + } catch (NoClassDefFoundError e) { + var message = "Could not find the coremod script-engine, but the following mods require it: " + filesWithCoreModScripts; + ImmediateWindowHandler.crash(message); + throw new IllegalStateException(message, e); + } } } diff --git a/loader/src/main/java/net/neoforged/fml/loading/LoadingModList.java b/loader/src/main/java/net/neoforged/fml/loading/LoadingModList.java index 8f7df4df9..6ab3c419a 100644 --- a/loader/src/main/java/net/neoforged/fml/loading/LoadingModList.java +++ b/loader/src/main/java/net/neoforged/fml/loading/LoadingModList.java @@ -64,14 +64,6 @@ public static LoadingModList get() { return INSTANCE; } - public void addCoreMods() { - modFiles.stream() - .map(ModFileInfo::getFile) - .map(ModFile::getCoreMods) - .flatMap(List::stream) - .forEach(FMLLoader.getCoreModEngine()::loadCoreMod); - } - public void addMixinConfigs() { modFiles.stream() .map(ModFileInfo::getFile) diff --git a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/CoreModFile.java b/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/CoreModFile.java index 5f6ffb47c..6737d0506 100644 --- a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/CoreModFile.java +++ b/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/CoreModFile.java @@ -5,45 +5,6 @@ package net.neoforged.fml.loading.moddiscovery; -import java.io.IOException; -import java.io.Reader; -import java.nio.file.Files; import java.nio.file.Path; -import net.neoforged.coremod.ICoreModScriptSource; -public class CoreModFile implements ICoreModScriptSource { - private final Path internalPath; - private final ModFile file; - private final String name; - - CoreModFile(final String name, final Path path, final ModFile file) { - this.name = name; - this.internalPath = path; - this.file = file; - } - - @Override - public Reader readCoreMod() throws IOException { - return Files.newBufferedReader(this.internalPath); - } - - @Override - public String getDebugSource() { - return this.internalPath.toString(); - } - - @Override - public Reader getAdditionalFile(final String fileName) throws IOException { - return Files.newBufferedReader(file.findResource(fileName)); - } - - @Override - public String getOwnerId() { - return this.file.getModInfos().get(0).getModId(); - } - - @Override - public String toString() { - return "{Name: " + name + ", Owner: " + getOwnerId() + " @ " + getDebugSource() + "}"; - } -} +public record CoreModFile(String name, Path path, ModFile file) {} diff --git a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/ModFile.java b/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/ModFile.java index 7e4cc7dcb..388d0834b 100644 --- a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/ModFile.java +++ b/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/ModFile.java @@ -115,7 +115,6 @@ public boolean identifyMods() { if (this.modFileInfo == null) return this.getType() != Type.MOD; LOGGER.debug(LogMarkers.LOADING, "Loading mod file {} with languages {}", this.getFilePath(), this.modFileInfo.requiredLanguageLoaders()); this.coreMods = ModFileParser.getCoreMods(this); - this.coreMods.forEach(mi -> LOGGER.debug(LogMarkers.LOADING, "Found coremod {}", mi.getDebugSource())); this.mixinConfigs = ModFileParser.getMixinConfigs(this.modFileInfo); this.mixinConfigs.forEach(mc -> LOGGER.debug(LogMarkers.LOADING, "Found mixin config {}", mc)); this.accessTransformers = ModFileParser.getAccessTransformers(this.modFileInfo) diff --git a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/ModValidator.java b/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/ModValidator.java index 0f15f94be..f7553adf6 100644 --- a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/ModValidator.java +++ b/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/ModValidator.java @@ -94,7 +94,6 @@ public BackgroundScanHandler stage2Validation() { validateLanguages(); loadingModList = ModSorter.sort(candidateMods, issues); - loadingModList.addCoreMods(); loadingModList.addAccessTransformers(); loadingModList.addMixinConfigs(); var backgroundScanHandler = new BackgroundScanHandler(); diff --git a/loader/src/main/java/net/neoforged/neoforgespi/coremod/ICoreMod.java b/loader/src/main/java/net/neoforged/neoforgespi/coremod/ICoreMod.java new file mode 100644 index 000000000..1e56ca214 --- /dev/null +++ b/loader/src/main/java/net/neoforged/neoforgespi/coremod/ICoreMod.java @@ -0,0 +1,15 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforgespi.coremod; + +import cpw.mods.modlauncher.api.ITransformer; + +/** + * Provide using the Java {@link java.util.ServiceLoader} mechanism. + */ +public interface ICoreMod { + Iterable> getTransformers(); +}