Separate plugin types #10
|
@ -0,0 +1,10 @@
|
||||||
|
package dev.frogmc.frogloader.example;
|
||||||
|
|
||||||
|
import dev.frogmc.frogloader.api.plugin.ModProvider;
|
||||||
|
|
||||||
|
public class ExampleModProvider implements ModProvider {
|
||||||
Ecorous marked this conversation as resolved
|
|||||||
|
@Override
|
||||||
|
public String id() {
|
||||||
|
return "example_mod:example";
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"
|
||||||
Ecorous marked this conversation as resolved
Ecorous
commented
Should we allow more than one modprovider per frogmod? Should we allow more than one modprovider per frogmod?
owlsys
commented
Already possible Already possible
|
|||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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";
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue
I'm asumming this serves as a code example - in which case it should be moved to the documentation
The entire
minecraft
project is a test project - this tests that the discovery works properlySome of this should be in the documentation as well, but this is fine to stay