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.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<ModDependency> hardDependencies = new HashSet<>();
final HashSet<ModDependency> 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<ModDependency> getHardDependencies() {
return hardDependencies;
}
public HashSet<ModDependency> 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();
}
}

View File

@ -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<MinosoftMod> mods = new LinkedList<>();
public static final ConcurrentHashMap<UUID, MinosoftMod> 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<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 -(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<UUID, MinosoftMod> 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<UUID, MinosoftMod> 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();
}
}

View File

@ -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];
}
}