add ability for mods to provide further mod providers

This commit is contained in:
moehreag 2024-06-16 12:25:19 +02:00
parent 6dd1bf04ed
commit 565e4df74e
4 changed files with 40 additions and 19 deletions

View file

@ -0,0 +1,10 @@
package dev.frogmc.frogloader.example;
import dev.frogmc.frogloader.api.plugin.ModProvider;
public class ExampleModProvider implements ModProvider {
@Override
public String id() {
return "example_mod:example";
}
}

View file

@ -14,4 +14,4 @@ credits = [
prelaunch = "dev.frogmc.frogloader.example.ExamplePreLaunchExtension" prelaunch = "dev.frogmc.frogloader.example.ExamplePreLaunchExtension"
mixin = "example_mod.mixins.json" mixin = "example_mod.mixins.json"
accesswidener = "example_mod.accesswidener" accesswidener = "example_mod.accesswidener"
modprovider = "dev.frogmc.frogloader.example.ExampleModProvider"

View file

@ -1,11 +1,8 @@
package dev.frogmc.frogloader.impl; package dev.frogmc.frogloader.impl;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Collection; import java.util.*;
import java.util.HashMap; import java.util.concurrent.atomic.AtomicInteger;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.stream.Collectors;
import dev.frogmc.frogloader.api.FrogLoader; import dev.frogmc.frogloader.api.FrogLoader;
import dev.frogmc.frogloader.api.mod.ModProperties; import dev.frogmc.frogloader.api.mod.ModProperties;
@ -26,37 +23,50 @@ public class PluginLoader {
public static void discoverModProviders(Path gameDir, Map<String, Map<String, ModProperties>> mods, Collection<String> modIds, Collection<ModProvider> modProviders) throws ModDependencyResolver.ResolverException { public static void discoverModProviders(Path gameDir, Map<String, Map<String, ModProperties>> mods, Collection<String> modIds, Collection<ModProvider> modProviders) throws ModDependencyResolver.ResolverException {
LOGGER.info("Discovering mod providers..."); LOGGER.info("Discovering mod providers...");
for (ModProvider plugin : ServiceLoader.load(ModProvider.class)) { List<ModProvider> providers = new ArrayList<>(ServiceLoader.load(ModProvider.class).stream().map(ServiceLoader.Provider::get).toList()); // we need mutability & random access
Map<String, Collection<ModProperties>> properties = new HashMap<>();
AtomicInteger size = new AtomicInteger(providers.size()); // use a separate size variable to not have to iterate over the list over and over again
for (int i = 0; i < size.get(); i++) {
ModProvider plugin = providers.get(i); // use random access to work around concurrent access (through modifications during iteration)
LOGGER.debug("Found mod provider: {}", plugin.getClass().getName()); LOGGER.debug("Found mod provider: {}", plugin.getClass().getName());
if (!plugin.isApplicable()) { if (!plugin.isApplicable()) {
continue; continue;
} }
try { try {
LOGGER.debug("Initialising mod provider: {}", plugin.id()); LOGGER.debug("Initialising mod provider: {}", plugin.id());
Map<String, ModProperties> modsFromProvider = new HashMap<>(); Collection<ModProperties> loadedMods = plugin.loadMods(Discovery.find(gameDir.resolve(plugin.loadDirectory()),
Collection<ModProperties> loadedMods = plugin.loadMods(Discovery.find(gameDir.resolve(plugin.loadDirectory()), plugin::isDirectoryApplicable, plugin::isFileApplicable), FrogLoaderImpl.getInstance().getClassloader()::addURL); plugin::isDirectoryApplicable,
plugin::isFileApplicable),
FrogLoaderImpl.getInstance().getClassloader()::addURL);
initializeModMixins(loadedMods); initializeModMixins(loadedMods);
AWProcessor.load(loadedMods); AWProcessor.load(loadedMods);
loadedMods.forEach(m -> modsFromProvider.put(m.id(), m)); properties.put(plugin.id(), loadedMods);
LOGGER.debug("Loaded {} mod(s) from provider: {}", modsFromProvider.size(), plugin.id()); LOGGER.debug("Loaded {} mod(s) from provider: {}", loadedMods.size(), plugin.id());
mods.put(plugin.id(), modsFromProvider); loadedMods.forEach(p -> p.extensions().runIfPresent(BuiltinExtensions.MOD_PROVIDER, ModProvider.class, provider -> {
modIds.addAll(modsFromProvider.keySet()); providers.add(provider);
modProviders.add(plugin); size.getAndIncrement();
}));
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Error during plugin initialisation: ", e); LOGGER.error("Error during plugin initialisation: ", e);
} }
} }
Collection<ModProperties> properties = mods.values().stream().map(Map::values).flatMap(Collection::stream).collect(Collectors.toUnmodifiableSet()); Collection<ModProperties> flattened = properties.values().stream().flatMap(Collection::stream).toList();
try { try {
Collection<ModProperties> solved = new ModDependencyResolver(properties).solve(); Collection<ModProperties> solved = new ModDependencyResolver(flattened).solve();
mods.forEach((s, map) -> map.values().retainAll(solved)); properties.forEach((s, c) -> c.retainAll(solved));
properties.forEach((s, c) -> {
Map<String, ModProperties> map = mods.computeIfAbsent(s, u -> new HashMap<>());
c.forEach(p -> map.put(p.id(), p));
});
modIds.addAll(solved.stream().map(ModProperties::id).toList());
modProviders.addAll(providers);
} catch (ModDependencyResolver.UnfulfilledDependencyException e) { } catch (ModDependencyResolver.UnfulfilledDependencyException e) {
LoaderGui.execUnfulfilledDep(CrashReportGenerator.writeReport(e, properties), e, false); LoaderGui.execUnfulfilledDep(CrashReportGenerator.writeReport(e, flattened), e, false);
} catch (ModDependencyResolver.BreakingModException e) { } catch (ModDependencyResolver.BreakingModException e) {
LoaderGui.execBreakingDep(CrashReportGenerator.writeReport(e, properties), e, false); LoaderGui.execBreakingDep(CrashReportGenerator.writeReport(e, flattened), e, false);
} }
} }

View file

@ -9,4 +9,5 @@ public class BuiltinExtensions {
public final String PRE_LAUNCH = "prelaunch"; public final String PRE_LAUNCH = "prelaunch";
public final String ACCESSWIDENER = "accesswidener"; public final String ACCESSWIDENER = "accesswidener";
public final String LOADING_TYPE = "loading_type"; public final String LOADING_TYPE = "loading_type";
public final String MOD_PROVIDER = "modprovider";
} }