wip (2) Mod Dependencies (check or fail)

If a dependency is now given, Minosoft will check if this dependency is loaded
This commit is contained in:
Bixilon 2020-12-04 22:34:04 +01:00
parent b68f1772a7
commit cf02469411
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
4 changed files with 152 additions and 65 deletions

View File

@ -0,0 +1,30 @@
/*
* Minosoft
* Copyright (C) 2020 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.modding.loading;
import com.google.gson.JsonObject;
import de.bixilon.minosoft.util.Util;
import java.util.UUID;
public record ModIdentifier(UUID uuid, int versionId) {
public static ModIdentifier serialize(JsonObject json) {
return new ModIdentifier(Util.getUUIDFromString(json.get("uuid").getAsString()), json.get("versionId").getAsInt());
}
@Override
public String toString() {
return String.format("uuid=%s, versionId=%d", uuid, versionId);
}
}

View File

@ -15,15 +15,12 @@ package de.bixilon.minosoft.modding.loading;
import com.google.gson.JsonArray; import com.google.gson.JsonArray;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import de.bixilon.minosoft.util.Util;
import java.util.HashSet; import java.util.HashSet;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
public class ModInfo { public class ModInfo {
final UUID uuid;
final int versionId;
final String versionName; final String versionName;
final String name; final String name;
final String[] authors; final String[] authors;
@ -32,11 +29,11 @@ public class ModInfo {
final String mainClass; final String mainClass;
final HashSet<ModDependency> hardDependencies = new HashSet<>(); final HashSet<ModDependency> hardDependencies = new HashSet<>();
final HashSet<ModDependency> softDependencies = new HashSet<>(); final HashSet<ModDependency> softDependencies = new HashSet<>();
private final ModIdentifier modIdentifier;
LoadingInfo loadingInfo; LoadingInfo loadingInfo;
public ModInfo(JsonObject json) throws ModLoadingException { public ModInfo(JsonObject json) throws ModLoadingException {
this.uuid = Util.getUUIDFromString(json.get("uuid").getAsString()); this.modIdentifier = ModIdentifier.serialize(json);
this.versionId = json.get("versionId").getAsInt();
this.versionName = json.get("versionName").getAsString(); this.versionName = json.get("versionName").getAsString();
this.name = json.get("name").getAsString(); this.name = json.get("name").getAsString();
JsonArray authors = json.get("authors").getAsJsonArray(); JsonArray authors = json.get("authors").getAsJsonArray();
@ -83,12 +80,18 @@ public class ModInfo {
return loadingInfo; return loadingInfo;
} }
@Deprecated
public UUID getUUID() { public UUID getUUID() {
return uuid; return modIdentifier.uuid();
} }
@Deprecated
public int getVersionId() { public int getVersionId() {
return versionId; return modIdentifier.versionId();
}
public ModIdentifier getModIdentifier() {
return modIdentifier;
} }
public String getVersionName() { public String getVersionName() {
@ -99,28 +102,16 @@ public class ModInfo {
return name; return name;
} }
public HashSet<ModDependency> getHardDependencies() {
return hardDependencies;
}
public HashSet<ModDependency> getSoftDependencies() {
return softDependencies;
}
@Override @Override
public String toString() { public String toString() {
return String.format("name=\"%s\", uuid=%s, versionName=\"%s\", versionId=%d", getName(), getUUID(), getVersionName(), getVersionId()); return String.format("name=\"%s\", uuid=%s, versionName=\"%s\", versionId=%d", getName(), getUUID(), getVersionName(), getVersionId());
} }
@Override
public int hashCode() {
return uuid.hashCode() * versionId;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (super.equals(obj)) {
return true;
}
if (this.hashCode() != obj.hashCode()) {
return false;
}
ModInfo their = (ModInfo) obj;
return getUUID().equals(their.getUUID()) && getVersionId() == their.getVersionId();
}
} }

View File

@ -22,8 +22,12 @@ import de.bixilon.minosoft.util.Util;
import org.xeustechnologies.jcl.JarClassLoader; import org.xeustechnologies.jcl.JarClassLoader;
import org.xeustechnologies.jcl.JclObjectFactory; import org.xeustechnologies.jcl.JclObjectFactory;
import javax.annotation.Nullable;
import java.io.File; import java.io.File;
import java.util.LinkedList; import java.util.Map;
import java.util.TreeMap;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
@ -31,9 +35,11 @@ import java.util.zip.ZipFile;
public class ModLoader { public class ModLoader {
public static final int CURRENT_MODDING_API_VERSION = 1; public static final int CURRENT_MODDING_API_VERSION = 1;
public static final LinkedList<MinosoftMod> mods = new LinkedList<>(); public static final ConcurrentHashMap<UUID, MinosoftMod> mods = new ConcurrentHashMap<>();
public static void loadMods(CountUpAndDownLatch progress) throws Exception { public static void loadMods(CountUpAndDownLatch progress) throws Exception {
final long startTime = System.currentTimeMillis();
Log.info("Start loading mods..."); Log.info("Start loading mods...");
ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), Util.getThreadFactory("ModLoader")); ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), Util.getThreadFactory("ModLoader"));
@ -53,7 +59,7 @@ public class ModLoader {
executor.execute(() -> { executor.execute(() -> {
MinosoftMod mod = loadMod(progress, modFile); MinosoftMod mod = loadMod(progress, modFile);
if (mod != null) { if (mod != null) {
mods.add(mod); mods.put(mod.getInfo().getModIdentifier().uuid(), mod);
} }
latch.countDown(); latch.countDown();
}); });
@ -67,46 +73,92 @@ public class ModLoader {
progress.addCount(mods.size() * ModPhases.values().length); // count * mod phases progress.addCount(mods.size() * ModPhases.values().length); // count * mod phases
// sort for priority
mods.sort((a, b) -> { // check if all dependencies are available
if (a == null || b == null) { modLoop:
for (Map.Entry<UUID, MinosoftMod> modEntry : mods.entrySet()) {
ModInfo currentModInfo = modEntry.getValue().getInfo();
for (ModDependency dependency : currentModInfo.getHardDependencies()) {
ModInfo info = getModInfoByUUID(dependency.getUUID());
if (info == null) {
Log.warn("Could not satisfy mod dependency for mod %s (Requires %s)", modEntry.getValue().getInfo(), dependency.getUUID());
mods.remove(modEntry.getKey());
continue modLoop;
}
if (dependency.getVersionMinimum() < info.getModIdentifier().versionId()) {
Log.warn("Could not satisfy mod dependency for mod %s (Requires %s version > %d)", modEntry.getValue().getInfo(), dependency.getUUID(), dependency.getVersionMinimum());
mods.remove(modEntry.getKey());
continue modLoop;
}
if (dependency.getVersionMaximum() > info.getModIdentifier().versionId()) {
Log.warn("Could not satisfy mod dependency for mod %s (Requires %s version < %d)", modEntry.getValue().getInfo(), dependency.getUUID(), dependency.getVersionMaximum());
mods.remove(modEntry.getKey());
continue modLoop;
}
}
for (ModDependency dependency : currentModInfo.getSoftDependencies()) {
ModInfo info = getModInfoByUUID(dependency.getUUID());
if (info == null) {
Log.warn("Could not satisfy mod soft dependency for mod %s (Requires %s)", modEntry.getValue().getInfo(), dependency.getUUID());
continue;
}
if (dependency.getVersionMinimum() < info.getModIdentifier().versionId()) {
Log.warn("Could not satisfy mod dependency for mod %s (Requires %s version > %d)", modEntry.getValue().getInfo(), dependency.getUUID(), dependency.getVersionMinimum());
continue;
}
if (dependency.getVersionMaximum() > info.getModIdentifier().versionId()) {
Log.warn("Could not satisfy mod soft dependency for mod %s (Requires %s version < %d)", modEntry.getValue().getInfo(), dependency.getUUID(), dependency.getVersionMaximum());
}
}
}
final TreeMap<UUID, MinosoftMod> sortedModMap = new TreeMap<>((mod1UUID, mod2UUID) -> {
// ToDo: Load dependencies first
if (mod1UUID == null || mod2UUID == null) {
return 0; return 0;
} }
return -(getLoadingPriorityOrDefault(b.getInfo()).ordinal() - getLoadingPriorityOrDefault(a.getInfo()).ordinal()); return -(getLoadingPriorityOrDefault(mods.get(mod2UUID).getInfo()).ordinal() - getLoadingPriorityOrDefault(mods.get(mod1UUID).getInfo()).ordinal());
}); });
// ToDo: check dependencies
sortedModMap.putAll(mods);
for (ModPhases phase : ModPhases.values()) { for (ModPhases phase : ModPhases.values()) {
Log.verbose(String.format("Mod loading phase changed: %s", phase)); Log.verbose(String.format("Mod loading phase changed: %s", phase));
CountDownLatch modLatch = new CountDownLatch(mods.size()); CountDownLatch modLatch = new CountDownLatch(sortedModMap.size());
mods.forEach((instance) -> executor.execute(() -> { for (Map.Entry<UUID, MinosoftMod> entry : sortedModMap.entrySet()) {
if (!instance.isEnabled()) { executor.execute(() -> {
if (!entry.getValue().isEnabled()) {
modLatch.countDown(); modLatch.countDown();
progress.countDown(); progress.countDown();
return; return;
} }
try { try {
if (!instance.start(phase)) { if (!entry.getValue().start(phase)) {
throw new ModLoadingException(String.format("Could not load nod %s", instance.getInfo())); throw new ModLoadingException(String.format("Could not load mod %s", entry.getValue().getInfo()));
} }
} catch (Throwable e) { } catch (Throwable e) {
e.printStackTrace(); e.printStackTrace();
Log.warn(String.format("An error occurred while loading %s", instance.getInfo())); Log.warn(String.format("An error occurred while loading %s", entry.getValue().getInfo()));
instance.setEnabled(false); entry.getValue().setEnabled(false);
} }
modLatch.countDown(); modLatch.countDown();
progress.countDown(); progress.countDown();
})); });
}
modLatch.await(); modLatch.await();
} }
mods.forEach((instance) -> {
if (instance.isEnabled()) { for (Map.Entry<UUID, MinosoftMod> entry : sortedModMap.entrySet()) {
Minosoft.eventManagers.add(instance.getEventManager()); if (entry.getValue().isEnabled()) {
Minosoft.eventManagers.add(entry.getValue().getEventManager());
} else { } else {
mods.remove(instance); mods.remove(entry.getKey());
} }
}); }
Log.info("Loading of %d mods finished!", mods.size()); Log.info("Loading of %d mods finished in %dms!", sortedModMap.size(), (System.currentTimeMillis() - startTime));
} }
public static MinosoftMod loadMod(CountUpAndDownLatch progress, File file) { public static MinosoftMod loadMod(CountUpAndDownLatch progress, File file) {
@ -144,12 +196,21 @@ public class ModLoader {
return Priorities.NORMAL; return Priorities.NORMAL;
} }
static boolean isModLoaded(ModInfo info) { public static boolean isModLoaded(ModInfo info) {
for (MinosoftMod instance : mods) { return mods.containsKey(info.getModIdentifier().uuid());
if (instance.getInfo().getUUID().equals(info.getUUID())) {
return true;
} }
@Nullable
public static MinosoftMod getModByUUID(UUID uuid) {
return mods.get(uuid);
} }
return false;
@Nullable
public static ModInfo getModInfoByUUID(UUID uuid) {
MinosoftMod mod = getModByUUID(uuid);
if (mod == null) {
return null;
}
return mod.getInfo();
} }
} }

View File

@ -18,5 +18,10 @@ public enum Priorities {
LOW, LOW,
NORMAL, NORMAL,
HIGH, HIGH,
HIGHEST HIGHEST,
ULTRA_HIGH; // this priority is even higher. Do not use in normal case!
public static Priorities getHigherPriority(Priorities priority) {
return Priorities.values()[priority.ordinal() + 1];
}
} }