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"
mixin = "example_mod.mixins.json"
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;
import java.nio.file.Path;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.stream.Collectors;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import dev.frogmc.frogloader.api.FrogLoader;
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 {
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
Ecorous marked this conversation as resolved Outdated
Outdated
Review

wouldn't int[] size = new int[] { providers.size() } be better as there's no need for thread safety?

wouldn't `int[] size = new int[] { providers.size() }` be better as there's no need for thread safety?

I believe this works better - but I wouldn't know as I didn't write it

I believe this works better - but I wouldn't know as I didn't write it

The only difference here would be a insignificantly smaller memory footprint - this isn't an issue

The only difference here would be a insignificantly smaller memory footprint - this isn't an issue
Outdated
Review

It's not about performance, i think it's bad practice to use AtomicInteger when you don't need it

It's not about performance, i think it's bad practice to use AtomicInteger when you don't need it

Resolved in 39ba054fcb

Resolved in 39ba054fcbb139134081d9bf776e035c4d893182
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());
if (!plugin.isApplicable()) {
continue;
}
try {
LOGGER.debug("Initialising mod provider: {}", plugin.id());
Map<String, ModProperties> modsFromProvider = new HashMap<>();
Collection<ModProperties> loadedMods = plugin.loadMods(Discovery.find(gameDir.resolve(plugin.loadDirectory()), plugin::isDirectoryApplicable, plugin::isFileApplicable), FrogLoaderImpl.getInstance().getClassloader()::addURL);
Collection<ModProperties> loadedMods = plugin.loadMods(Discovery.find(gameDir.resolve(plugin.loadDirectory()),
plugin::isDirectoryApplicable,
plugin::isFileApplicable),
FrogLoaderImpl.getInstance().getClassloader()::addURL);
initializeModMixins(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());
mods.put(plugin.id(), modsFromProvider);
modIds.addAll(modsFromProvider.keySet());
modProviders.add(plugin);
LOGGER.debug("Loaded {} mod(s) from provider: {}", loadedMods.size(), plugin.id());
loadedMods.forEach(p -> p.extensions().runIfPresent(BuiltinExtensions.MOD_PROVIDER, ModProvider.class, provider -> {
providers.add(provider);
size.getAndIncrement();
}));
} catch (Throwable 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 {
Collection<ModProperties> solved = new ModDependencyResolver(properties).solve();
mods.forEach((s, map) -> map.values().retainAll(solved));
Collection<ModProperties> solved = new ModDependencyResolver(flattened).solve();
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) {
LoaderGui.execUnfulfilledDep(CrashReportGenerator.writeReport(e, properties), e, false);
LoaderGui.execUnfulfilledDep(CrashReportGenerator.writeReport(e, flattened), e, false);
} 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 ACCESSWIDENER = "accesswidener";
public final String LOADING_TYPE = "loading_type";
public final String MOD_PROVIDER = "modprovider";
}