-
Notifications
You must be signed in to change notification settings - Fork 270
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Rework mod discovery, resolution and loading #461
Conversation
src/main/java/net/fabricmc/loader/impl/discovery/Explanation.java
Outdated
Show resolved
Hide resolved
@Override | ||
public String toString() { | ||
if (min == null) { | ||
if (max == null) { | ||
return "(-∞,∞)"; | ||
} else { | ||
return String.format("(-∞,%s%c", max, maxInclusive ? ']' : ')'); | ||
} | ||
} else if (max == null) { | ||
return String.format("%c%s,∞)", minInclusive ? '[' : '(', min); | ||
} else { | ||
return String.format("%c%s,%s%c", minInclusive ? '[' : '(', min, max, maxInclusive ? ']' : ')'); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it's a good idea to use mathematical notation for a toString
implementation. I'd avoid using toString
for user-readable information, and even then, this notation in general is not user-friendly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't supposed to be interpreted by the user either, IMO it does a fair job for its main applications (devs working on version analysis similar to my installable mod inference code).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would say +inf
is better than +∞
, just in case some people's computer is not UTF-8 encoding.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Java should convert it properly, I'd rather have a more compact format for this niche thing than not.
{ | ||
"name": "com.google.jimfs:jimfs:1.2-fabric", | ||
"url": "https://maven.fabricmc.net/" | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does the new ow2 sat4j need to be shipped though?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The plan is to shade it, so not relevant for the installer.
These changes are too big. I think the better way to review is to just look at the source code without looking at the diff. |
Most of the n.f.loader.discovery classes are practically rewritten, checking the diff for them is indeed rather pointless |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, too much to do an indpeth review but looks a lot more approachable overall.
src/main/java/net/fabricmc/loader/impl/discovery/ModCandidate.java
Outdated
Show resolved
Hide resolved
|
||
/** | ||
* Represents a version of a mod. | ||
* | ||
* @see ModMetadata#getVersion() | ||
*/ | ||
public interface Version { | ||
public interface Version extends Comparable<Version> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any concern here from potential existing implementations which wouldn't implement Comparable
? I can't think of any but it is technically a backward compatibility problem
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, are users even supposed to implement this interface?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They shouldn't need to, but that hasn't stopped people doing odd things with internals in the past
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't really supported and somewhat pointless (can't put that Version
anywhere in loader). The JVM doesn't verify whether a class implements everything, so would only throw AbstractMethodError on access.
src/main/java/net/fabricmc/loader/impl/discovery/ModResolver.java
Outdated
Show resolved
Hide resolved
solverPrepTime = System.nanoTime(); | ||
|
||
IPBSolver solver = SolverFactory.newDefaultOptimizer(); | ||
solver.setTimeout(60); // 60s |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should it ever be taking close to a minute to resolve mod dependencies?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be <1s even for a large modpack, but doesn't hurt to leave some room. I went for 60s because it's the most I'd want to wait. It actually usually recovers itself when the limit is hit, just proceeding with a potentially non-optimal result.
int[] comps = ((SemanticVersionImpl) v).getVersionComponents(); | ||
|
||
if (pr == null) { // no prerelease, use large pr segment | ||
pr = "zzzzzzzz"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would this be fine if someone used a prerelease of zzzzzzzzz
(ie with one more z
)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If said mod is present, the condition a line above fails and selects another code path, otherwise the least mod version suggested to be installed will be slightly off.
src/main/java/net/fabricmc/loader/impl/discovery/ResultAnalyzer.java
Outdated
Show resolved
Hide resolved
} | ||
|
||
private static String getCandidateFriendlyVersion(ModCandidate candidate) { | ||
return candidate.getVersion().getFriendlyString(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will (the use of) this result in errors like The developer(s) of x_mod have found that version * of y_mod critically conflicts with their mod.
? If so, where the version is wildcarded could it be made clearer along the lines of The developer(s) of x_mod have found that y_mod critically conflicts with their mod.
, so that it is more apparent there is no version which could ever work?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think so, ModCandidate is always a concrete mod, not a dependency reference.
src/main/java/net/fabricmc/loader/impl/discovery/ResultAnalyzer.java
Outdated
Show resolved
Hide resolved
src/main/java/net/fabricmc/loader/api/metadata/ModMetadata.java
Outdated
Show resolved
Hide resolved
Collection<ModDependency> getDepends(); | ||
@Deprecated | ||
default Collection<ModDependency> getDepends() { | ||
return getDependencies().stream().filter(d -> d.getKind() == ModDependency.Kind.DEPENDS).collect(Collectors.toList()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are five methods with the same implementation, except for their kind filter. I would create an abstract method that uses a parameter to dictate the kind filter instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Java 8 doesn't allow private interface methods, so can't factor it out without accessing the implementation or adding even more methods. These API methods are practically unused, so no need to add convenience helpers..
Set<VersionPredicate> getVersionRequirements(); | ||
Collection<VersionPredicate> getVersionRequirements(); | ||
|
||
enum Kind { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not a find of 'kind'. Would 'type', 'relation', or 'relationship' be better here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"type" is too overloaded with other meanings in this concept, relation* is wrong since ModDependency itself is the relation. Not sure what's wrong with calling depends, recommends , ... kinds of dependencies.
Kind(String key, boolean positive, boolean soft) { | ||
this.key = key; | ||
this.positive = positive; | ||
this.soft = soft; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd document this better and give it a more meaningful name to clarify that dependency kinds that are soft only produce warnings rather than failing mod resolution.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's to indicate it being a soft dependency (opposite of a hard dependency), whether it produces a warning is unspecified (suggests doesn't, recommends/conflicts do)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is a hard dependency?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hard = must be met, soft = should be met
import net.fabricmc.loader.api.Version; | ||
import net.fabricmc.loader.impl.util.version.SemanticVersionImpl; | ||
|
||
public enum VersionComparisonOperator { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The last two names clash with the others. I'd change the first four names so that they describe the functionality rather than the serialized form.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is actually surprisingly hard, particularly because ~ and ^ have a lower bound that isn't properly covered by the current name. Suggestions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed the description for ~ and ^. I tried adjusting the other names, but it causes more confusion than sticking with the operators. The class itself represents the operators in the first place and has nothing to do with the user friendly translation that happens in ReasonAnalyzer.
src/main/java/net/fabricmc/loader/impl/discovery/ModLoadCondition.java
Outdated
Show resolved
Hide resolved
import net.fabricmc.loader.api.metadata.version.VersionPredicate; | ||
import net.fabricmc.loader.api.metadata.version.VersionPredicate.PredicateTerm; | ||
|
||
final class ResultAnalyzer { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice to have tests for this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True, but not high priority. The class itself will likely see substantial changes, it is just a quick and dirty adaption of the old error generation logic.
src/main/java/net/fabricmc/loader/impl/discovery/ResultAnalyzer.java
Outdated
Show resolved
Hide resolved
@@ -167,8 +168,12 @@ public void injectIntoClassLoader(LaunchClassLoader launchClassLoader) { | |||
} | |||
|
|||
@Override | |||
public void propose(URL url) { | |||
launchClassLoader.addURL(url); | |||
public void addToClassPath(Path path) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ClassPath
should be Classpath
to be consistent with the typical capitalization both within Fabric loader and in general.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure about that actually, java started to separate it lately (--class-path instead of --classpath)..
src/main/java/net/fabricmc/loader/impl/metadata/DependencyOverrides.java
Outdated
Show resolved
Hide resolved
Looks generally good, however my loader knowledge is close to zero. I assume if there is an issue it will be easy to address. Is some testing planned in case that happens? |
We want to do some manual testing with the Mixin changes before actually releasing 0.12, other than that there are only plans/ideas for making Loader more testable. I already did some local testing ofc. |
Yes ofc, I was referring to automated or at least semi-automated testing. |
ModCandidate candidate = new ModCandidate(i, normalizedUrl, depth, requiresRemap); | ||
boolean added; | ||
if (!metadata.loadsInEnvironment(envType)) { | ||
return null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at #375, should we add a warning here? Though if a mod is not loaded, it won't appear in loader's mod list, and should be quite clear in that case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The mod explicitly opted out of being loaded, doesn't look warning worthy to me?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, now I am thinking about using the 5-tier dependency system to handle the environment thing: a mod can declare "breaks" to strictly crash when loading on one side and "conflicts" for less-strict warning.
In fact, I think we can add an extra minecraft client builtin mod; the whole dedicated server is shipped within the client, so it will just count as part of minecraft
builtin mod. If a mod opts-in to be client-only, it can declare a hard dependency on client mod; otherwise, it can declare a hard breaks on client (though I see no point for a mod to do so)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, @Chocohead what's the main problems with this?
Now, after some thinking, I feel such a split by using jij for client-specific code is actually a good approach, somewhat more feasible than adding @Environment
annotations and having less compile-time guarantees
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One would just declare the environment in the mod json instead of messing with dependencies
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we should be encouraging/offering people to crash if their mod is on the wrong side. Right now we avoid loading it which is a much more user friendly approach to the problem. Forge in comparison expects mods to handle it themselves (unlike in the past with the old clientOnlyMod
) but also doesn't offer a way to crash on the wrong side.
src/main/java/net/fabricmc/loader/api/metadata/version/VersionComparisonOperator.java
Outdated
Show resolved
Hide resolved
src/main/java/net/fabricmc/loader/api/metadata/version/VersionComparisonOperator.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks quite good in general. Hope you can commit the change that actually ships sat4j wholesale so people can build and test it more easily
changes
recommended
mods.fabric/processedMods
instead of jimfs-Dfabric.debug.loadLate=modA,modB,..
to move mods to the end of the load order for debugging/workaround purposes-Dfabric.debug.throwDirectly
to disable exception gathering while invoking entry points etc and throw the originally encountered exception directly (helps with misleading console output while debugging)associated future changes
conflicts
declarations