Require extensions to be namespaced #14

Merged
owlsys merged 2 commits from TheKodeToad/extension-namespace into main 2024-09-02 12:08:59 -04:00
18 changed files with 247 additions and 155 deletions
Showing only changes of commit 7e2c29b359 - Show all commits

View file

@ -10,7 +10,7 @@ credits = [
{ name = "You", roles = ["author", "other_role"] } { name = "You", roles = ["author", "other_role"] }
] ]
[frog.extensions] [frog.extensions.frogloader]
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"

View file

@ -10,6 +10,6 @@ credits = [
{ name = "You", roles = ["author", "other_role"] } { name = "You", roles = ["author", "other_role"] }
] ]
[frog.extensions] [frog.extensions.frogloader]
prelaunch="dev.frogmc.example.ExampleTestMod" prelaunch="dev.frogmc.example.ExampleTestMod"

View file

@ -0,0 +1,17 @@
package dev.frogmc.frogloader.api.exception;
public class ModExtensionResolutionException extends Exception {
public ModExtensionResolutionException(String message) {
super(message);
}
public ModExtensionResolutionException(Throwable cause) {
super(cause);
}
public ModExtensionResolutionException(String message, Throwable cause) {
super(message, cause);
}
}

View file

@ -1,7 +1,7 @@
package dev.frogmc.frogloader.api.extensions; package dev.frogmc.frogloader.api.extensions;
import dev.frogmc.frogloader.api.mod.ModExtensions; import dev.frogmc.frogloader.api.mod.ModExtensions;
import dev.frogmc.frogloader.impl.mod.BuiltinExtensions; import dev.frogmc.frogloader.impl.Constants;
/** /**
* The Pre-Launch Extension. * The Pre-Launch Extension.
@ -13,7 +13,7 @@ public interface PreLaunchExtension {
/** /**
* This extension's id. This is the key to use in your frog.mod.toml. * This extension's id. This is the key to use in your frog.mod.toml.
*/ */
String ID = BuiltinExtensions.PRE_LAUNCH; String ID = Constants.EXTENSION_PRE_LAUNCH;
/** /**
* The initializer. This method will be invoked when this extension is run. * The initializer. This method will be invoked when this extension is run.

View file

@ -1,150 +1,74 @@
package dev.frogmc.frogloader.api.mod; package dev.frogmc.frogloader.api.mod;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandleProxies;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Map;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.slf4j.Logger; import dev.frogmc.frogloader.api.exception.ModExtensionResolutionException;
import org.slf4j.LoggerFactory;
/** /**
* This class stores a mod's extensions. * This class stores a mod's extensions.
* <p>An extension is a simple key-value mapping of a string name to any value.</p> * <p>
* <p>This class further provides utility methods to easily work with extension values of various types, * An extension is a simple key-value mapping of a string name to any value.
* especially for extensions providing some form of class or method reference.</p> * </p>
* <p>
* This class further provides utility methods to easily work with extension
* values of various types, especially for extensions providing some form of
* class or method reference.
* </p>
* *
* @see ModProperties * @see ModProperties
*/ */
public final class ModExtensions { public interface ModExtensions {
private static final Logger LOGGER = LoggerFactory.getLogger(ModExtensions.class);
private final Map<String, Object> extensions;
private ModExtensions(Map<String, Object> entries) {
extensions = entries;
}
/**
* Retrieve a new instance of this class.
* <p><strong>Internal use only.</strong></p>
*
* @param entries the entries read from the mod's properties file
* @return an instance of this class
*/
public static ModExtensions of(Map<String, Object> entries) {
return new ModExtensions(entries);
}
/** /**
* Get the value of the provided extension name. * Get the value of the provided extension name.
* *
* @param modId The mod ID this extension is stored under
* @param key The extension name to query * @param key The extension name to query
* @param <T> The type of the value of this extension * @param <T> The type of the value of this extension
*
* @return The value of the extension, or null * @return The value of the extension, or null
*/ */
@SuppressWarnings("unchecked") <T> T get(String modId, String key);
public <T> T get(String key) {
return (T) extensions.get(key);
}
/** /**
* Get the value of the provided extension name. * Get the value of the provided extension name.
* *
* @param key The extension name to query * @param modId The mod ID this extension is stored under
* @param defaultValue a default value * @param key The extension name to query
* @param <T> The type of the value of this extension * @param defaultValue A default value
* @param <T> The type of the value of this extension
*
* @return The value of the extension, or the default value if it isn't present * @return The value of the extension, or the default value if it isn't present
*/ */
@SuppressWarnings("unchecked") <T> T getOrDefault(String modId, String key, T defaultValue);
public <T> T getOrDefault(String key, T defaultValue) {
return (T) extensions.getOrDefault(key, defaultValue);
}
/** /**
* Run the given action on this extension if it is present. * Run the given action on this extension if it is present.
* *
* @param key the extension name to query * @param modId The mod ID this extension is stored under
* @param action the action to run on the value of the extension if it is present * @param key The extension name to query
* @param <T> The type of the value of this extension * @param action The action to run on the value of the extension if it is present
* @param <T> The type of the value of this extension
*/ */
@SuppressWarnings({"rawtypes", "unchecked"}) <T> void runIfPresent(String modId, String key, Consumer<T> action);
public <T> void runIfPresent(String key, Consumer<T> action) {
Object value = get(key);
if (value == null) {
return;
}
if (value instanceof Collection c) {
((Collection<T>) c).forEach(action);
} else {
action.accept((T) value);
}
}
/** /**
* Run the given action on this extension if it is present. * Run the given action on this extension if it is present.
* <p>This method simplifies handling references to classes or methods.</p> * <p>
* It will first query the string value of the extension and, if it is found, create a new instance of the referenced class * This method simplifies handling references to classes or methods.
* or invoke the referenced method to retrieve an instance of the provided class. Then the provided action is run on this * </p>
* object. * It will first query the string value of the extension and, if it is found,
* create a new instance of the referenced class or invoke the referenced method
* to retrieve an instance of the provided class. Then the provided action is
* run on this object.
* *
* @param key The name of the extension * @param modId The mod ID this extension is stored under
* @param type The class type of the extension (The class the extension class is extending/implementing) * @param key The name of the extension
* @param type The class type of the extension (The class the extension class is extending/implementing)
* @param action The action to run on the newly retrieved instance of the provided class * @param action The action to run on the newly retrieved instance of the provided class
* @param <T> The type of the class * @param <T> The type of the class
*
* @throws ModExtensionResolutionException If the reference could not be resolved
*/ */
@SuppressWarnings({"unchecked"}) <T> void runIfPresent(String modId, String key, Class<T> type, Consumer<T> action) throws ModExtensionResolutionException;
public <T> void runIfPresent(String key, Class<T> type, Consumer<T> action) {
runIfPresent(key, (Consumer<String>) s -> {
try {
T object;
if (s.contains("::")) {
String[] parts = s.split("::");
Class<?> extension = Class.forName(parts[0]);
object = (T) handleReference(extension, type, parts[1]);
} else {
object = (T) MethodHandles.lookup().findConstructor(Class.forName(s), MethodType.methodType(void.class)).invoke();
}
if (object != null) {
action.accept(object);
}
} catch (Throwable e) {
LOGGER.warn("Failed to instantiate Extension: ", e);
}
});
}
private Object handleReference(Class<?> clazz, Class<?> type, String ref) throws Throwable {
try {
Field found = clazz.getDeclaredField(ref);
found.setAccessible(true);
return found.get(null);
} catch (Exception ignored) {
}
Method found = null;
for (Method method : clazz.getDeclaredMethods()) {
if (method.getName().equals(ref)) {
if (found != null) {
throw new IllegalArgumentException("Ambiguous method reference: "+ref+" in "+clazz.getName());
}
found = method;
}
}
if (found != null) {
MethodHandle method = MethodHandles.lookup().unreflect(found);
if (!Modifier.isStatic(found.getModifiers())) {
method = method.bindTo(MethodHandles.lookup().findConstructor(clazz, MethodType.methodType(void.class)).invoke());
}
return MethodHandleProxies.asInterfaceInstance(type, method);
}
throw new IllegalArgumentException("Could not find either a static field or a method named '" + ref + "' in class " + clazz.getName() + "!");
}
} }

View file

@ -65,8 +65,9 @@ public interface ModProvider {
/** /**
* This method will be invoked just before the game is launched. It may be used for a pre-launch entrypoint for mods. * This method will be invoked just before the game is launched. It may be used for a pre-launch entrypoint for mods.
* @param mods The mods loaded by this provider * @param mods The mods loaded by this provider
* @throws Exception If an exception occurs during pre-launch. It will be handled by the loader.
*/ */
default void preLaunch(Collection<ModProperties> mods) { default void preLaunch(Collection<ModProperties> mods) throws Exception {
} }
/** /**

View file

@ -0,0 +1,14 @@
package dev.frogmc.frogloader.impl;
import lombok.experimental.UtilityClass;
@UtilityClass
public class Constants {
public final String MOD_ID = "frogloader";
public final String EXTENSION_MIXIN_CONFIG = "mixin";
public final String EXTENSION_INCLUDED_JARS = "included_jars";
public final String EXTENSION_PRE_LAUNCH = "prelaunch";
public final String EXTENSION_ACCESSWIDENER = "accesswidener";
public final String EXTENSION_LOADING_TYPE = "loading_type";
public final String EXTENSION_MOD_PROVIDER = "modprovider";
}

View file

@ -78,7 +78,8 @@ public class FrogLoaderImpl implements FrogLoader {
modIds = collectModIds(); modIds = collectModIds();
LOGGER.info(ModUtil.getModList(getMods())); LOGGER.info(ModUtil.getModList(getMods()));
LOGGER.info("Launching..."); LOGGER.info("Launching...");
modProviders.forEach(plugin -> plugin.preLaunch(mods.get(plugin.id()).values())); for (ModProvider plugin : modProviders)
plugin.preLaunch(mods.get(plugin.id()).values());
gamePlugin.run(); gamePlugin.run();
} catch (Throwable t) { } catch (Throwable t) {
LoaderGui.execReport(CrashReportGenerator.writeReport(t, getMods()), false); LoaderGui.execReport(CrashReportGenerator.writeReport(t, getMods()), false);

View file

@ -11,7 +11,6 @@ import dev.frogmc.frogloader.api.plugin.GamePlugin;
import dev.frogmc.frogloader.api.plugin.ModProvider; import dev.frogmc.frogloader.api.plugin.ModProvider;
import dev.frogmc.frogloader.impl.gui.LoaderGui; import dev.frogmc.frogloader.impl.gui.LoaderGui;
import dev.frogmc.frogloader.impl.mixin.AWProcessor; import dev.frogmc.frogloader.impl.mixin.AWProcessor;
import dev.frogmc.frogloader.impl.mod.BuiltinExtensions;
import dev.frogmc.frogloader.impl.mod.ModDependencyResolver; import dev.frogmc.frogloader.impl.mod.ModDependencyResolver;
import dev.frogmc.frogloader.impl.util.CrashReportGenerator; import dev.frogmc.frogloader.impl.util.CrashReportGenerator;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -47,10 +46,12 @@ public class PluginLoader {
properties.put(plugin.id(), loadedMods); properties.put(plugin.id(), loadedMods);
LOGGER.debug("Loaded {} mod(s) from provider: {}", loadedMods.size(), plugin.id()); LOGGER.debug("Loaded {} mod(s) from provider: {}", loadedMods.size(), plugin.id());
loadedMods.forEach(p -> p.extensions().runIfPresent(BuiltinExtensions.MOD_PROVIDER, ModProvider.class, provider -> { for (ModProperties p : loadedMods) {
providers.add(provider); p.extensions().runIfPresent(Constants.MOD_ID, Constants.EXTENSION_MOD_PROVIDER, ModProvider.class, provider -> {
size[0]++; providers.add(provider);
})); size[0]++;
owlsys marked this conversation as resolved
Review

this single-element array could be replaced with a normal int since it doesn't need to be used anymore (since the lambda was replaced with a loop)

this single-element array could be replaced with a normal int since it doesn't need to be used anymore (since the lambda was replaced with a loop)
Review

I think it is still needed as it is a callback to runIfPresent

I think it is still needed as it is a callback to runIfPresent
Review

I tried to look for other usages already but it's fairly possible I missed something in the diff view.

I tried to look for other usages already but it's fairly possible I missed something in the diff view.
});
}
modProviders.add(plugin); modProviders.add(plugin);
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Error during plugin initialisation: ", e); LOGGER.error("Error during plugin initialisation: ", e);
@ -78,7 +79,7 @@ public class PluginLoader {
private static void initializeModMixins(Collection<ModProperties> loadedMods) { private static void initializeModMixins(Collection<ModProperties> loadedMods) {
Map<String, ModProperties> configs = new HashMap<>(); Map<String, ModProperties> configs = new HashMap<>();
loadedMods.forEach(props -> { loadedMods.forEach(props -> {
Object o = props.extensions().get(BuiltinExtensions.MIXIN_CONFIG); Object o = props.extensions().get(Constants.MOD_ID, Constants.EXTENSION_MIXIN_CONFIG);
if (o instanceof String name) { if (o instanceof String name) {
configs.put(name, props); configs.put(name, props);
Mixins.addConfiguration(name); Mixins.addConfiguration(name);

View file

@ -14,12 +14,11 @@ import java.util.regex.Pattern;
import java.util.stream.Stream; import java.util.stream.Stream;
import dev.frogmc.frogloader.api.mod.ModProperties; import dev.frogmc.frogloader.api.mod.ModProperties;
import dev.frogmc.frogloader.impl.Constants;
import dev.frogmc.frogloader.impl.launch.transformer.AccessWidener; import dev.frogmc.frogloader.impl.launch.transformer.AccessWidener;
import dev.frogmc.frogloader.impl.mod.BuiltinExtensions;
public class AWProcessor { public class AWProcessor {
private static final String AW_EXTENSION_NAME = BuiltinExtensions.ACCESSWIDENER;
private static final Predicate<String> HEADER = Pattern.compile("accessWidener\\s+v[12]\\s+.*").asMatchPredicate(); private static final Predicate<String> HEADER = Pattern.compile("accessWidener\\s+v[12]\\s+.*").asMatchPredicate();
private static final String SEPARATOR = "[\\t ]+"; private static final String SEPARATOR = "[\\t ]+";
@ -30,7 +29,7 @@ public class AWProcessor {
Map<String, Map<String, AccessWidener.Entry>> mutations = new ConcurrentHashMap<>(); Map<String, Map<String, AccessWidener.Entry>> mutations = new ConcurrentHashMap<>();
Set<String> classNames = new ConcurrentSkipListSet<>(); Set<String> classNames = new ConcurrentSkipListSet<>();
mods.stream().map(ModProperties::extensions).map(e -> e.get(AW_EXTENSION_NAME)).flatMap(o -> { mods.stream().map(ModProperties::extensions).map(e -> e.get(Constants.MOD_ID, Constants.EXTENSION_ACCESSWIDENER)).flatMap(o -> {
if (o instanceof Collection<?> c) { if (o instanceof Collection<?> c) {
return c.stream().map(Object::toString); return c.stream().map(Object::toString);
} }

View file

@ -1,13 +0,0 @@
package dev.frogmc.frogloader.impl.mod;
import lombok.experimental.UtilityClass;
@UtilityClass
public class BuiltinExtensions {
public final String MIXIN_CONFIG = "mixin";
public final String INCLUDED_JARS = "included_jars";
public final String PRE_LAUNCH = "prelaunch";
public final String ACCESSWIDENER = "accesswidener";
public final String LOADING_TYPE = "loading_type";
public final String MOD_PROVIDER = "modprovider";
}

View file

@ -25,7 +25,7 @@ public class JavaModProperties {
"", "",
Map.of(System.getProperty("java.vm.vendor"), Collections.singleton("Vendor")), Map.of(System.getProperty("java.vm.vendor"), Collections.singleton("Vendor")),
new ModDependencies(Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), Collections.emptySet()), new ModDependencies(Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), Collections.emptySet()),
ModExtensions.of(Collections.emptyMap()), Collections.emptySet()); new ModExtensionsImpl(Collections.emptyMap()), Collections.emptySet());
} }
return INSTANCE; return INSTANCE;
} }

View file

@ -11,6 +11,7 @@ import com.google.common.collect.MultimapBuilder;
import dev.frogmc.frogloader.api.mod.ModDependencies; import dev.frogmc.frogloader.api.mod.ModDependencies;
import dev.frogmc.frogloader.api.mod.ModProperties; import dev.frogmc.frogloader.api.mod.ModProperties;
import dev.frogmc.frogloader.api.mod.SemVer; import dev.frogmc.frogloader.api.mod.SemVer;
import dev.frogmc.frogloader.impl.Constants;
import dev.frogmc.frogloader.impl.SemVerParseException; import dev.frogmc.frogloader.impl.SemVerParseException;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
@ -121,7 +122,7 @@ public class ModDependencyResolver {
DependencyEntry value = entry.getValue(); DependencyEntry value = entry.getValue();
if (!(presentMods.containsKey(s) && value.range.versionMatches(presentOrProvided.get(s)))) { // The dependency isn't fulfilled by any present mods if (!(presentMods.containsKey(s) && value.range.versionMatches(presentOrProvided.get(s)))) { // The dependency isn't fulfilled by any present mods
if (!(provides.containsKey(s) && provides.get(s).stream().map(ProvidedMod::version).allMatch(value.range::versionMatches))) { // The dependency also isn't fulfilled by any provided mods if (!(provides.containsKey(s) && provides.get(s).stream().map(ProvidedMod::version).allMatch(value.range::versionMatches))) { // The dependency also isn't fulfilled by any provided mods
if (value.origin.extensions().getOrDefault(BuiltinExtensions.LOADING_TYPE, "required").equals("required")) { if (value.origin.extensions().getOrDefault(Constants.MOD_ID, Constants.EXTENSION_LOADING_TYPE, "required").equals("required")) {
unfulfilled.add(new UnfulfilledDependencyException.Entry(value.origin, s, value.range, presentOrProvided.get(s), value.link(), value.name())); unfulfilled.add(new UnfulfilledDependencyException.Entry(value.origin, s, value.range, presentOrProvided.get(s), value.link(), value.name()));
} else { } else {
LOGGER.debug("Skipping optional mod: {} ({})", value.origin().id(), value.origin().name()); LOGGER.debug("Skipping optional mod: {} ({})", value.origin().id(), value.origin().name());

View file

@ -0,0 +1,136 @@
package dev.frogmc.frogloader.impl.mod;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandleProxies;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.invoke.WrongMethodTypeException;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import dev.frogmc.frogloader.api.exception.ModExtensionResolutionException;
import dev.frogmc.frogloader.api.mod.ModExtensions;
public class ModExtensionsImpl implements ModExtensions {
private final Map<Key, Object> map;
public ModExtensionsImpl(Map<Key, Object> map) {
this.map = map;
}
@Override
@SuppressWarnings("unchecked")
public <T> T get(String modId, String key) {
return (T) map.get(new Key(modId, key));
}
@Override
@SuppressWarnings("unchecked")
public <T> T getOrDefault(String modId, String key, T defaultValue) {
return (T) map.getOrDefault(new Key(modId, key), defaultValue);
}
@Override
@SuppressWarnings("unchecked")
public <T> void runIfPresent(String modId, String key, Consumer<T> action) {
Object value = get(modId, key);
if (value == null)
return;
if (value instanceof Collection)
((Collection<T>) value).forEach(action);
else
action.accept((T) value);
}
@Override
public <T> void runIfPresent(String modId, String key, Class<T> type, Consumer<T> action)
throws ModExtensionResolutionException {
String s = get(modId, key);
if (s == null)
return;
T object;
if (s.contains("::")) {
String[] parts = s.split("::", 2);
try {
Class<?> extension = Class.forName(parts[0]);
object = (T) handleReference(extension, type, parts[1]);
} catch (ModExtensionResolutionException e) {
throw e;
} catch (Throwable e) {
throw new ModExtensionResolutionException(e);
}
} else {
try {
Class<?> extension = Class.forName(s);
if (!type.isAssignableFrom(extension))
throw new ModExtensionResolutionException(extension + " does not inherit from " + type);
object = (T) MethodHandles.lookup().findConstructor(extension, MethodType.methodType(void.class))
.invoke();
} catch (ModExtensionResolutionException e) {
throw e;
} catch (Throwable e) {
throw new ModExtensionResolutionException(e);
}
}
if (object != null) {
action.accept(object);
}
}
private Object handleReference(Class<?> owner, Class<?> type, String name)
throws ModExtensionResolutionException, IllegalAccessException {
try {
Field found = owner.getDeclaredField(name);
if (Modifier.isStatic(found.getModifiers())) {
found.setAccessible(true);
return found.get(null);
}
} catch (NoSuchFieldException ignored) {
}
Method found = null;
for (Method method : owner.getDeclaredMethods()) {
if (!Modifier.isStatic(method.getModifiers()))
continue;
if (!method.getName().equals(name))
continue;
if (found != null)
throw new ModExtensionResolutionException("Ambiguous method reference: " + name + " in " + owner.getName());
found = method;
}
if (found != null) {
found.setAccessible(true);
MethodHandle method = MethodHandles.lookup().unreflect(found);
return MethodHandleProxies.asInterfaceInstance(type, method);
}
throw new ModExtensionResolutionException("Could not find a static field or method named '" + name + "' in class " + owner.getName() + "!");
}
public record Key(String modId, String key) {
}
}

View file

@ -153,16 +153,23 @@ public class ModPropertiesReader {
var suggests = parseDependencies.apply("suggests"); var suggests = parseDependencies.apply("suggests");
var provides = parseDependencies.apply("provides"); var provides = parseDependencies.apply("provides");
UnmodifiableConfig extensionsConfig = config.get("frog.extensions");
Map<ModExtensionsImpl.Key, Object> extensions = new HashMap<>();
if (extensionsConfig != null) {
for (var modEntry : extensionsConfig.entrySet()) {
UnmodifiableConfig modExtensionsConig = modEntry.getValue();
for (var entry : modExtensionsConig.entrySet())
extensions.put(new ModExtensionsImpl.Key(modEntry.getKey(), entry.getKey()), entry.getValue());
}
}
if (!badProperties.isEmpty()) if (!badProperties.isEmpty())
throw new InvalidModPropertiesException(id, sources, badProperties); throw new InvalidModPropertiesException(id, sources, badProperties);
UnmodifiableConfig extensionsConfig = config.get("frog.extensions");
Map<String, Object> extensions = new HashMap<>();
if (extensionsConfig != null)
extensionsConfig.entrySet().forEach(entry -> extensions.put(entry.getKey(), entry.getValue()));
return new ModPropertiesImpl(id, name, icon, semVer, license, Collections.unmodifiableMap(credits), return new ModPropertiesImpl(id, name, icon, semVer, license, Collections.unmodifiableMap(credits),
new ModDependencies(depends, breaks, suggests, provides), ModExtensions.of(extensions), sources); new ModDependencies(depends, breaks, suggests, provides), new ModExtensionsImpl(extensions), sources);
}); });
private static final Map<String, Parser> versions = Arrays.stream(values()).collect(Collectors.toMap(v -> v.version, v -> v.parser)); private static final Map<String, Parser> versions = Arrays.stream(values()).collect(Collectors.toMap(v -> v.version, v -> v.parser));

View file

@ -6,6 +6,7 @@ import java.util.stream.Collectors;
import com.electronwill.nightconfig.core.UnmodifiableConfig; import com.electronwill.nightconfig.core.UnmodifiableConfig;
import dev.frogmc.frogloader.api.mod.ModProperties; import dev.frogmc.frogloader.api.mod.ModProperties;
import dev.frogmc.frogloader.impl.Constants;
public class ModUtil { public class ModUtil {
@ -66,7 +67,7 @@ public class ModUtil {
private static Map<ModProperties, Collection<String>> getParentMods(Collection<ModProperties> mods) { private static Map<ModProperties, Collection<String>> getParentMods(Collection<ModProperties> mods) {
Map<ModProperties, Collection<String>> children = new HashMap<>(); Map<ModProperties, Collection<String>> children = new HashMap<>();
for (ModProperties mod : mods) { for (ModProperties mod : mods) {
List<UnmodifiableConfig> entries = mod.extensions().get(BuiltinExtensions.INCLUDED_JARS); List<UnmodifiableConfig> entries = mod.extensions().get(Constants.MOD_ID, Constants.EXTENSION_INCLUDED_JARS);
if (entries != null) { if (entries != null) {
for (var jar : entries) { for (var jar : entries) {
String id = jar.get("id"); String id = jar.get("id");

View file

@ -21,6 +21,7 @@ import dev.frogmc.frogloader.api.mod.ModExtensions;
import dev.frogmc.frogloader.api.mod.ModProperties; import dev.frogmc.frogloader.api.mod.ModProperties;
import dev.frogmc.frogloader.api.plugin.GamePlugin; import dev.frogmc.frogloader.api.plugin.GamePlugin;
import dev.frogmc.frogloader.impl.FrogLoaderImpl; import dev.frogmc.frogloader.impl.FrogLoaderImpl;
import dev.frogmc.frogloader.impl.mod.ModExtensionsImpl;
import dev.frogmc.frogloader.impl.mod.ModPropertiesImpl; import dev.frogmc.frogloader.impl.mod.ModPropertiesImpl;
import dev.frogmc.frogloader.impl.util.SystemProperties; import dev.frogmc.frogloader.impl.util.SystemProperties;
import dev.frogmc.thyroxine.HttpHelper; import dev.frogmc.thyroxine.HttpHelper;
@ -204,6 +205,6 @@ public class MinecraftGamePlugin implements GamePlugin {
MinecraftSemVerImpl.get(version), "MC-EULA", MinecraftSemVerImpl.get(version), "MC-EULA",
Map.of("Mojang AB", Collections.singleton("Author")), Map.of("Mojang AB", Collections.singleton("Author")),
new ModDependencies(Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), Collections.emptySet()), new ModDependencies(Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), Collections.emptySet()),
ModExtensions.of(Collections.emptyMap()), Collections.emptySet()); new ModExtensionsImpl(Collections.emptyMap()), Collections.emptySet());
} }
} }

View file

@ -10,10 +10,11 @@ import java.util.function.Consumer;
import com.electronwill.nightconfig.core.UnmodifiableConfig; import com.electronwill.nightconfig.core.UnmodifiableConfig;
import dev.frogmc.frogloader.api.FrogLoader; import dev.frogmc.frogloader.api.FrogLoader;
import dev.frogmc.frogloader.api.exception.ModExtensionResolutionException;
import dev.frogmc.frogloader.api.extensions.PreLaunchExtension; import dev.frogmc.frogloader.api.extensions.PreLaunchExtension;
import dev.frogmc.frogloader.api.mod.ModProperties; import dev.frogmc.frogloader.api.mod.ModProperties;
import dev.frogmc.frogloader.api.plugin.ModProvider; import dev.frogmc.frogloader.api.plugin.ModProvider;
import dev.frogmc.frogloader.impl.mod.BuiltinExtensions; import dev.frogmc.frogloader.impl.Constants;
import dev.frogmc.frogloader.impl.mod.ModPropertiesReader; import dev.frogmc.frogloader.impl.mod.ModPropertiesReader;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -115,7 +116,7 @@ public class FrogModProvider implements ModProvider {
if (opt.isPresent()) { if (opt.isPresent()) {
ModProperties p = opt.get(); ModProperties p = opt.get();
modPaths.put(mod, p); modPaths.put(mod, p);
List<UnmodifiableConfig> entries = p.extensions().getOrDefault(BuiltinExtensions.INCLUDED_JARS, Collections.emptyList()); List<UnmodifiableConfig> entries = p.extensions().getOrDefault(Constants.MOD_ID, Constants.EXTENSION_INCLUDED_JARS, Collections.emptyList());
if (entries.isEmpty()) { if (entries.isEmpty()) {
return; return;
} }
@ -135,7 +136,8 @@ public class FrogModProvider implements ModProvider {
} }
@Override @Override
public void preLaunch(Collection<ModProperties> mods) { public void preLaunch(Collection<ModProperties> mods) throws ModExtensionResolutionException {
owlsys marked this conversation as resolved
Review

the exception should probably be handled inside the loop to just skip the faulty mod instead of breaking and propagating the error upwards

the exception should probably be handled inside the loop to just skip the faulty mod instead of breaking and propagating the error upwards
Review

If a mod wants to handle an error without crashing, shouldn't it have a try catch block in its prelaunch handler?

If a mod wants to handle an error without crashing, shouldn't it have a try catch block in its prelaunch handler?
Review

it may still be okay like this considering mod devs should definitely know when their stuff is malformed or throwing errors

it may still be okay like this considering mod devs should definitely know when their stuff is malformed or throwing errors
mods.forEach(mod -> mod.extensions().runIfPresent(PreLaunchExtension.ID, PreLaunchExtension.class, PreLaunchExtension::onPreLaunch)); for (ModProperties mod : mods)
mod.extensions().runIfPresent(Constants.MOD_ID, Constants.EXTENSION_PRE_LAUNCH, PreLaunchExtension.class, PreLaunchExtension::onPreLaunch);
} }
} }