From cf0246941108228decf8c149bb825d1da12e0c3a Mon Sep 17 00:00:00 2001 From: Bixilon Date: Fri, 4 Dec 2020 22:34:04 +0100 Subject: [PATCH] wip (2) Mod Dependencies (check or fail) If a dependency is now given, Minosoft will check if this dependency is loaded --- .../modding/loading/ModIdentifier.java | 30 ++++ .../minosoft/modding/loading/ModInfo.java | 45 +++--- .../minosoft/modding/loading/ModLoader.java | 135 +++++++++++++----- .../minosoft/modding/loading/Priorities.java | 7 +- 4 files changed, 152 insertions(+), 65 deletions(-) create mode 100644 src/main/java/de/bixilon/minosoft/modding/loading/ModIdentifier.java diff --git a/src/main/java/de/bixilon/minosoft/modding/loading/ModIdentifier.java b/src/main/java/de/bixilon/minosoft/modding/loading/ModIdentifier.java new file mode 100644 index 000000000..0139dc734 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/modding/loading/ModIdentifier.java @@ -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 . + * + * 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); + } +} diff --git a/src/main/java/de/bixilon/minosoft/modding/loading/ModInfo.java b/src/main/java/de/bixilon/minosoft/modding/loading/ModInfo.java index a9824e2de..f92f22ba0 100644 --- a/src/main/java/de/bixilon/minosoft/modding/loading/ModInfo.java +++ b/src/main/java/de/bixilon/minosoft/modding/loading/ModInfo.java @@ -15,15 +15,12 @@ package de.bixilon.minosoft.modding.loading; import com.google.gson.JsonArray; import com.google.gson.JsonObject; -import de.bixilon.minosoft.util.Util; import java.util.HashSet; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; public class ModInfo { - final UUID uuid; - final int versionId; final String versionName; final String name; final String[] authors; @@ -32,11 +29,11 @@ public class ModInfo { final String mainClass; final HashSet hardDependencies = new HashSet<>(); final HashSet softDependencies = new HashSet<>(); + private final ModIdentifier modIdentifier; LoadingInfo loadingInfo; public ModInfo(JsonObject json) throws ModLoadingException { - this.uuid = Util.getUUIDFromString(json.get("uuid").getAsString()); - this.versionId = json.get("versionId").getAsInt(); + this.modIdentifier = ModIdentifier.serialize(json); this.versionName = json.get("versionName").getAsString(); this.name = json.get("name").getAsString(); JsonArray authors = json.get("authors").getAsJsonArray(); @@ -83,12 +80,18 @@ public class ModInfo { return loadingInfo; } + @Deprecated public UUID getUUID() { - return uuid; + return modIdentifier.uuid(); } + @Deprecated public int getVersionId() { - return versionId; + return modIdentifier.versionId(); + } + + public ModIdentifier getModIdentifier() { + return modIdentifier; } public String getVersionName() { @@ -99,28 +102,16 @@ public class ModInfo { return name; } + public HashSet getHardDependencies() { + return hardDependencies; + } + + public HashSet getSoftDependencies() { + return softDependencies; + } + @Override public String toString() { 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(); - } } diff --git a/src/main/java/de/bixilon/minosoft/modding/loading/ModLoader.java b/src/main/java/de/bixilon/minosoft/modding/loading/ModLoader.java index 7e4b87bd6..a296a48b0 100644 --- a/src/main/java/de/bixilon/minosoft/modding/loading/ModLoader.java +++ b/src/main/java/de/bixilon/minosoft/modding/loading/ModLoader.java @@ -22,8 +22,12 @@ import de.bixilon.minosoft.util.Util; import org.xeustechnologies.jcl.JarClassLoader; import org.xeustechnologies.jcl.JclObjectFactory; +import javax.annotation.Nullable; 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.ExecutorService; import java.util.concurrent.Executors; @@ -31,9 +35,11 @@ import java.util.zip.ZipFile; public class ModLoader { public static final int CURRENT_MODDING_API_VERSION = 1; - public static final LinkedList mods = new LinkedList<>(); + public static final ConcurrentHashMap mods = new ConcurrentHashMap<>(); + public static void loadMods(CountUpAndDownLatch progress) throws Exception { + final long startTime = System.currentTimeMillis(); Log.info("Start loading mods..."); ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), Util.getThreadFactory("ModLoader")); @@ -53,7 +59,7 @@ public class ModLoader { executor.execute(() -> { MinosoftMod mod = loadMod(progress, modFile); if (mod != null) { - mods.add(mod); + mods.put(mod.getInfo().getModIdentifier().uuid(), mod); } latch.countDown(); }); @@ -67,46 +73,92 @@ public class ModLoader { progress.addCount(mods.size() * ModPhases.values().length); // count * mod phases - // sort for priority - mods.sort((a, b) -> { - if (a == null || b == null) { + + // check if all dependencies are available + modLoop: + for (Map.Entry 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 sortedModMap = new TreeMap<>((mod1UUID, mod2UUID) -> { + // ToDo: Load dependencies first + if (mod1UUID == null || mod2UUID == null) { 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()) { Log.verbose(String.format("Mod loading phase changed: %s", phase)); - CountDownLatch modLatch = new CountDownLatch(mods.size()); - mods.forEach((instance) -> executor.execute(() -> { - if (!instance.isEnabled()) { + CountDownLatch modLatch = new CountDownLatch(sortedModMap.size()); + for (Map.Entry entry : sortedModMap.entrySet()) { + executor.execute(() -> { + if (!entry.getValue().isEnabled()) { + modLatch.countDown(); + progress.countDown(); + return; + } + try { + if (!entry.getValue().start(phase)) { + throw new ModLoadingException(String.format("Could not load mod %s", entry.getValue().getInfo())); + } + } catch (Throwable e) { + e.printStackTrace(); + Log.warn(String.format("An error occurred while loading %s", entry.getValue().getInfo())); + entry.getValue().setEnabled(false); + } modLatch.countDown(); progress.countDown(); - return; - } - try { - if (!instance.start(phase)) { - throw new ModLoadingException(String.format("Could not load nod %s", instance.getInfo())); - } - } catch (Throwable e) { - e.printStackTrace(); - Log.warn(String.format("An error occurred while loading %s", instance.getInfo())); - instance.setEnabled(false); - } - modLatch.countDown(); - progress.countDown(); - })); + }); + } modLatch.await(); } - mods.forEach((instance) -> { - if (instance.isEnabled()) { - Minosoft.eventManagers.add(instance.getEventManager()); + + for (Map.Entry entry : sortedModMap.entrySet()) { + if (entry.getValue().isEnabled()) { + Minosoft.eventManagers.add(entry.getValue().getEventManager()); } 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) { @@ -144,12 +196,21 @@ public class ModLoader { return Priorities.NORMAL; } - static boolean isModLoaded(ModInfo info) { - for (MinosoftMod instance : mods) { - if (instance.getInfo().getUUID().equals(info.getUUID())) { - return true; - } + public static boolean isModLoaded(ModInfo info) { + return mods.containsKey(info.getModIdentifier().uuid()); + } + + @Nullable + public static MinosoftMod getModByUUID(UUID uuid) { + return mods.get(uuid); + } + + @Nullable + public static ModInfo getModInfoByUUID(UUID uuid) { + MinosoftMod mod = getModByUUID(uuid); + if (mod == null) { + return null; } - return false; + return mod.getInfo(); } } diff --git a/src/main/java/de/bixilon/minosoft/modding/loading/Priorities.java b/src/main/java/de/bixilon/minosoft/modding/loading/Priorities.java index e8d4306d8..a32b9af2a 100644 --- a/src/main/java/de/bixilon/minosoft/modding/loading/Priorities.java +++ b/src/main/java/de/bixilon/minosoft/modding/loading/Priorities.java @@ -18,5 +18,10 @@ public enum Priorities { LOW, NORMAL, 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]; + } }