Separate plugin types #10

Merged
owlsys merged 14 commits from ecorous/plugin-types into main 2024-06-16 17:13:54 -04:00
4 changed files with 40 additions and 19 deletions
Showing only changes of commit 565e4df74e - Show all commits

View file

@ -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
Review

I'm asumming this serves as a code example - in which case it should be moved to the documentation

I'm asumming this serves as a code example - in which case it should be moved to the documentation
Review

The entire minecraft project is a test project - this tests that the discovery works properly

The entire `minecraft` project is a test project - this tests that the discovery works properly
Review

Some of this should be in the documentation as well, but this is fine to stay

Some of this should be in the documentation as well, but this is fine to stay
@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"
Ecorous marked this conversation as resolved
Review

Should we allow more than one modprovider per frogmod?

Should we allow more than one modprovider per frogmod?
Review

Already possible

Already possible

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";
} }