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"
|
||||
mixin = "example_mod.mixins.json"
|
||||
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;
|
||||
|
||||
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
kode
commented
wouldn't wouldn't `int[] size = new int[] { providers.size() }` be better as there's no need for thread safety?
Ecorous
commented
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
Ecorous
commented
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
kode
commented
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
Ecorous
commented
Resolved in 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
|
|
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