From a85d6cb1f2a12efd40b12f5a59fbb56a8d1cc4fd Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Thu, 27 Jun 2024 15:42:25 -0700
Subject: [PATCH] move resolution into update actions in task.
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/minecraft/Component.cpp | 37 ++++-
launcher/minecraft/Component.h | 33 +++++
launcher/minecraft/ComponentUpdateTask.cpp | 151 +++++++++++++++++++++
launcher/minecraft/ComponentUpdateTask.h | 5 +
launcher/minecraft/PackProfile.cpp | 83 +----------
launcher/minecraft/PackProfile.h | 4 -
6 files changed, 231 insertions(+), 82 deletions(-)
diff --git a/launcher/minecraft/Component.cpp b/launcher/minecraft/Component.cpp
index 6ea9ee60b..67f26a02e 100644
--- a/launcher/minecraft/Component.cpp
+++ b/launcher/minecraft/Component.cpp
@@ -38,6 +38,7 @@
#include
#include
+#include
#include "Application.h"
#include "FileSystem.h"
@@ -235,7 +236,8 @@ ProblemSeverity Component::getProblemSeverity() const
{
auto file = getVersionFile();
if (file) {
- return file->getProblemSeverity();
+ auto severity = file->getProblemSeverity();
+ return m_componentProblemSeverity > severity ? m_componentProblemSeverity : severity;
}
return ProblemSeverity::Error;
}
@@ -244,11 +246,27 @@ const QList Component::getProblems() const
{
auto file = getVersionFile();
if (file) {
- return file->getProblems();
+ auto problems = file->getProblems();
+ problems.append(m_componentProblems);
+ return problems;
}
return { { ProblemSeverity::Error, QObject::tr("Patch is not loaded yet.") } };
}
+void Component::addComponentProblem(ProblemSeverity severity, const QString& description)
+{
+ if (severity > m_componentProblemSeverity) {
+ m_componentProblemSeverity = severity;
+ }
+ m_componentProblems.append({ severity, description });
+}
+
+void Component::resetComponentProblems()
+{
+ m_componentProblems.clear();
+ m_componentProblemSeverity = ProblemSeverity::None;
+}
+
void Component::setVersion(const QString& version)
{
if (version == m_version) {
@@ -415,6 +433,21 @@ void Component::waitLoadMeta()
}
}
+void Component::setUpdateAction(UpdateAction action)
+{
+ m_updateAction = action;
+}
+
+std::optional Component::getUpdateAction()
+{
+ return m_updateAction;
+}
+
+void Component::clearUpdateAction()
+{
+ m_updateAction.reset();
+}
+
QDebug operator<<(QDebug d, const Component& comp)
{
d << "Component(" << comp.m_uid << " : " << comp.m_cachedVersion << ")";
diff --git a/launcher/minecraft/Component.h b/launcher/minecraft/Component.h
index 67d79e231..50c4e02db 100644
--- a/launcher/minecraft/Component.h
+++ b/launcher/minecraft/Component.h
@@ -4,6 +4,7 @@
#include
#include
#include
+#include
#include "ProblemProvider.h"
#include "QObjectPtr.h"
#include "meta/JsonFormat.h"
@@ -16,6 +17,26 @@ class VersionList;
} // namespace Meta
class VersionFile;
+struct UpdateActionChangeVerison {
+ /// version to change to
+ QString targetVersion;
+};
+struct UpdateActionLatestRecommendedCompatable {
+ /// Parent uid
+ QString parentUid;
+ QString parentName;
+ /// Parent version
+ QString version;
+ ///
+};
+struct UpdateActionRemove {};
+struct UpdateActionImportantChanged {
+ QString oldVersion;
+};
+
+using UpdateAction =
+ std::variant;
+
class Component : public QObject, public ProblemProvider {
Q_OBJECT
public:
@@ -58,6 +79,8 @@ class Component : public QObject, public ProblemProvider {
const QList getProblems() const override;
ProblemSeverity getProblemSeverity() const override;
+ void addComponentProblem(ProblemSeverity severity, const QString& description);
+ void resetComponentProblems();
void setVersion(const QString& version);
bool customize();
@@ -67,6 +90,11 @@ class Component : public QObject, public ProblemProvider {
void waitLoadMeta();
+ void setUpdateAction(UpdateAction action);
+ void clearUpdateAction();
+ std::optional getUpdateAction();
+ std::optional takeUpdateAction();
+
signals:
void dataChanged();
@@ -104,6 +132,11 @@ class Component : public QObject, public ProblemProvider {
std::shared_ptr m_metaVersion;
std::shared_ptr m_file;
bool m_loaded = false;
+
+ private:
+ QList m_componentProblems;
+ ProblemSeverity m_componentProblemSeverity = ProblemSeverity::None;
+ std::optional m_updateAction = std::nullopt;
};
using ComponentPtr = shared_qobject_ptr;
diff --git a/launcher/minecraft/ComponentUpdateTask.cpp b/launcher/minecraft/ComponentUpdateTask.cpp
index dc59d2dc9..8f19f20c8 100644
--- a/launcher/minecraft/ComponentUpdateTask.cpp
+++ b/launcher/minecraft/ComponentUpdateTask.cpp
@@ -1,9 +1,11 @@
#include "ComponentUpdateTask.h"
+#include
#include "Component.h"
#include "ComponentUpdateTask_p.h"
#include "PackProfile.h"
#include "PackProfile_p.h"
+#include "ProblemProvider.h"
#include "Version.h"
#include "cassert"
#include "meta/Index.h"
@@ -196,6 +198,7 @@ void ComponentUpdateTask::loadComponents()
switch (result) {
case LoadResult::LoadedLocal: {
// Everything got loaded. Advance to dependency resolution.
+ performUpdateActions();
resolveDependencies(d->mode == Mode::Launch || d->netmode == Net::Mode::Offline);
break;
}
@@ -380,6 +383,42 @@ static bool getTrivialComponentChanges(const ComponentIndex& index, const Requir
return succeeded;
}
+ComponentContainer ComponentUpdateTask::collectTreeLinked(const QString& uid)
+{
+ ComponentContainer linked;
+
+ auto& components = d->m_profile->d->components;
+ auto& componentIndex = d->m_profile->d->componentIndex;
+ auto& instance = d->m_profile->d->m_instance;
+ for (auto comp : components) {
+ qCDebug(instanceProfileResolveC) << instance->name() << "|"
+ << "scanning" << comp->getID() << ":" << comp->getVersion() << "for tree link";
+ auto dep = std::find_if(comp->m_cachedRequires.cbegin(), comp->m_cachedRequires.cend(),
+ [uid](const Meta::Require& req) -> bool { return req.uid == uid; });
+ if (dep != comp->m_cachedRequires.cend()) {
+ qCDebug(instanceProfileResolveC) << instance->name() << "|" << comp->getID() << ":" << comp->getVersion() << "depends on"
+ << uid;
+ linked.append(comp);
+ }
+ }
+ auto iter = componentIndex.find(uid);
+ if (iter != componentIndex.end()) {
+ ComponentPtr comp = *iter;
+ comp->updateCachedData();
+ qCDebug(instanceProfileResolveC) << instance->name() << "|" << comp->getID() << ":" << comp->getVersion() << "has"
+ << comp->m_cachedRequires.size() << "dependencies";
+ for (auto dep : comp->m_cachedRequires) {
+ qCDebug(instanceProfileC) << instance->name() << "|" << uid << "depends on" << dep.uid;
+ auto found = componentIndex.find(dep.uid);
+ if (found != componentIndex.end()) {
+ qCDebug(instanceProfileC) << instance->name() << "|" << (*found)->getID() << "is present";
+ linked.append(*found);
+ }
+ }
+ }
+ return linked;
+}
+
// FIXME, TODO: decouple dependency resolution from loading
// FIXME: This works directly with the PackProfile internals. It shouldn't! It needs richer data types than PackProfile uses.
// FIXME: throw all this away and use a graph
@@ -488,10 +527,121 @@ void ComponentUpdateTask::resolveDependencies(bool checkOnly)
if (recursionNeeded) {
loadComponents();
} else {
+ finalizeComponents();
emitSucceeded();
}
}
+// Variant visitation via lambda
+template
+struct overload : Ts... {
+ using Ts::operator()...;
+};
+template
+overload(Ts...) -> overload;
+
+void ComponentUpdateTask::performUpdateActions()
+{
+ auto& components = d->m_profile->d->components;
+
+ bool addedActions;
+ do {
+ addedActions = false;
+ for (auto component : components) {
+ if (auto action = component->getUpdateAction()) {
+ auto visitor = overload{
+ [&component](const UpdateActionChangeVerison& cv) {
+ component->setVersion(cv.targetVersion);
+ component->waitLoadMeta();
+ },
+ [&component](const UpdateActionLatestRecommendedCompatable lrc) {
+ auto versionList = APPLICATION->metadataIndex()->get(component->getID());
+ versionList->waitToLoad();
+ if (versionList) {
+ auto recommended = versionList->getRecommendedForParent(lrc.parentUid, lrc.version);
+ if (recommended) {
+ component->setVersion(recommended->version());
+ component->waitLoadMeta();
+ return;
+ }
+
+ auto latest = versionList->getLatestForParent(lrc.parentUid, lrc.version);
+ if (latest) {
+ component->setVersion(latest->version());
+ component->waitLoadMeta();
+ } else {
+ component->addComponentProblem(ProblemSeverity::Error,
+ QObject::tr("No compatible version of %1 found for %2 %3")
+ .arg(component->getName(), lrc.parentName, lrc.version));
+ }
+ } else {
+ component->addComponentProblem(ProblemSeverity::Error,
+ QObject::tr("No version list in metadata index for %1").arg(component->getID()));
+ }
+ },
+ [this, &component](const UpdateActionRemove&) { d->m_profile->remove(component->getID()); },
+ [this, &component, &addedActions](const UpdateActionImportantChanged&) {
+ auto linked = collectTreeLinked(component->getID());
+ for (auto comp : linked) {
+ if (comp->isCustom()) {
+ continue;
+ }
+ auto compUid = comp->getID();
+ auto parentReq = std::find_if(component->m_cachedRequires.begin(), component->m_cachedRequires.end(),
+ [compUid](const Meta::Require& req) { return req.uid == compUid; });
+ if (parentReq != component->m_cachedRequires.end()) {
+ auto newVersion = parentReq->equalsVersion.isEmpty() ? parentReq->suggests : parentReq->equalsVersion;
+ if (!newVersion.isEmpty()) {
+ comp->setUpdateAction(UpdateAction{ UpdateActionChangeVerison{ newVersion } });
+ } else {
+ comp->setUpdateAction(UpdateAction{ UpdateActionLatestRecommendedCompatable{
+ component->getID(),
+ component->getName(),
+ component->getVersion(),
+ } });
+ }
+ } else {
+ comp->setUpdateAction(UpdateAction{ UpdateActionLatestRecommendedCompatable{
+ component->getID(),
+ component->getName(),
+ component->getVersion(),
+ } });
+ }
+ addedActions = true;
+ }
+ }
+ };
+ std::visit(visitor, *action);
+ component->clearUpdateAction();
+ }
+ }
+ } while (addedActions);
+}
+
+void ComponentUpdateTask::finalizeComponents()
+{
+ auto& components = d->m_profile->d->components;
+ auto& componentIndex = d->m_profile->d->componentIndex;
+ for (auto component : components) {
+ for (auto req : component->m_cachedRequires) {
+ auto found = componentIndex.find(req.uid);
+ if (found == componentIndex.cend()) {
+ component->addComponentProblem(
+ ProblemSeverity::Error,
+ QObject::tr("%1 is missing requirement %2 %3")
+ .arg(component->getName(), req.uid, req.equalsVersion.isEmpty() ? req.suggests : req.equalsVersion));
+ } else {
+ auto reqComp = *found;
+ if (!reqComp->getProblems().isEmpty()) {
+ component->addComponentProblem(
+ reqComp->getProblemSeverity(),
+ QObject::tr("%1, a dependency of this component, has reported issues").arg(reqComp->getName()));
+ }
+ }
+ }
+ }
+}
+
void ComponentUpdateTask::remoteLoadSucceeded(size_t taskIndex)
{
if (static_cast(d->remoteLoadStatusList.size()) < taskIndex) {
@@ -551,6 +701,7 @@ void ComponentUpdateTask::checkIfAllFinished()
if (d->remoteLoadSuccessful) {
// nothing bad happened... clear the temp load status and proceed with looking at dependencies
d->remoteLoadStatusList.clear();
+ performUpdateActions();
resolveDependencies(d->mode == Mode::Launch);
} else {
// remote load failed... report error and bail
diff --git a/launcher/minecraft/ComponentUpdateTask.h b/launcher/minecraft/ComponentUpdateTask.h
index 2f396a049..484c0bedd 100644
--- a/launcher/minecraft/ComponentUpdateTask.h
+++ b/launcher/minecraft/ComponentUpdateTask.h
@@ -1,5 +1,6 @@
#pragma once
+#include "minecraft/Component.h"
#include "net/Mode.h"
#include "tasks/Task.h"
@@ -21,7 +22,11 @@ class ComponentUpdateTask : public Task {
private:
void loadComponents();
+ /// collects components that are dependent on or dependencies of the component
+ QList collectTreeLinked(const QString& uid);
void resolveDependencies(bool checkOnly);
+ void performUpdateActions();
+ void finalizeComponents();
void remoteLoadSucceeded(size_t index);
void remoteLoadFailed(size_t index, const QString& msg);
diff --git a/launcher/minecraft/PackProfile.cpp b/launcher/minecraft/PackProfile.cpp
index ce57288f6..debbf0fa7 100644
--- a/launcher/minecraft/PackProfile.cpp
+++ b/launcher/minecraft/PackProfile.cpp
@@ -49,6 +49,7 @@
#include
#include
#include
+#include
#include "Application.h"
#include "Exception.h"
@@ -56,6 +57,7 @@
#include "Json.h"
#include "meta/Index.h"
#include "meta/JsonFormat.h"
+#include "minecraft/Component.h"
#include "minecraft/MinecraftInstance.h"
#include "minecraft/OneSixVersionFormat.h"
#include "minecraft/ProfileUtils.h"
@@ -67,6 +69,8 @@
#include "minecraft/Logging.h"
+#include "ui/dialogs/CustomMessageBox.h"
+
static const QMap modloaderMapping{ { "net.neoforged", ModPlatform::NeoForge },
{ "net.minecraftforge", ModPlatform::Forge },
{ "net.fabricmc.fabric-loader", ModPlatform::Fabric },
@@ -988,58 +992,16 @@ bool PackProfile::setComponentVersion(const QString& uid, const QString& version
ComponentPtr component = *iter;
// set existing
if (component->revert()) {
- // remove linked components to let them re-resolve their versions
- if (important) {
- component->waitLoadMeta();
- auto linked = collectTreeLinked(uid);
- for (auto comp : linked) {
- if (comp->isCustom()) {
- continue;
- }
- if (modloaderMapping.contains(comp->getID())) {
- qCDebug(instanceProfileC)
- << d->m_instance->name() << "|" << comp->getID() << "is a mod loader, updating it to a compatible version";
-
- auto versionList = APPLICATION->metadataIndex()->get(comp->getID());
- versionList->waitToLoad();
- if (versionList) {
- auto recommended = versionList->getRecommendedForParent(uid, version);
- if (recommended) {
- qCDebug(instanceProfileC) << d->m_instance->name() << "|"
- << "setting updated loader to recommended version: " << comp->getID() << " = "
- << recommended->version();
- comp->setVersion(recommended->version());
- } else {
- auto latest = versionList->getLatestForParent(uid, version);
- if (latest) {
- qCDebug(instanceProfileC) << d->m_instance->name() << "|"
- << "setting updated loader to latest compatible version: " << comp->getID()
- << " = " << latest->version();
- comp->setVersion(latest->version());
- } else {
- qCDebug(instanceProfileC) << d->m_instance->name() << "|"
- << "no compatible version for" << comp->getID() << "removing";
- remove(comp->getID());
- }
- }
- } else {
- qCDebug(instanceProfileC) << d->m_instance->name() << "|"
- << "no version list in metadata index for" << comp->getID();
- }
- } else {
- qCDebug(instanceProfileC) << d->m_instance->name() << "|" << comp->getID() << ":" << comp->getVersion()
- << "linked to important component: " << uid << " | Removing so it re-resolves";
- remove(comp->getID());
- }
- }
- }
// set new version
+ auto oldVersion = component->getVersion();
component->setVersion(version);
component->setImportant(important);
if (important) {
+ component->setUpdateAction(UpdateAction{ UpdateActionImportantChanged{ oldVersion } });
resolve(Net::Mode::Online);
}
+
return true;
}
return false;
@@ -1053,37 +1015,6 @@ bool PackProfile::setComponentVersion(const QString& uid, const QString& version
}
}
-ComponentContainer PackProfile::collectTreeLinked(const QString& uid)
-{
- ComponentContainer linked;
- for (auto comp : d->components) {
- qCDebug(instanceProfileC) << d->m_instance->name() << "|"
- << "scanning" << comp->getID() << ":" << comp->getVersion() << "for tree link";
- auto dep = std::find_if(comp->m_cachedRequires.cbegin(), comp->m_cachedRequires.cend(),
- [uid](const Meta::Require& req) -> bool { return req.uid == uid; });
- if (dep != comp->m_cachedRequires.cend()) {
- qCDebug(instanceProfileResolveC) << comp->getID() << ":" << comp->getVersion() << "depends on" << uid;
- linked.append(comp);
- }
- }
- auto iter = d->componentIndex.find(uid);
- if (iter != d->componentIndex.end()) {
- ComponentPtr comp = *iter;
- comp->updateCachedData();
- qCDebug(instanceProfileC) << d->m_instance->name() << "|" << comp->getID() << ":" << comp->getVersion() << "has"
- << comp->m_cachedRequires.size() << "dependencies";
- for (auto dep : comp->m_cachedRequires) {
- qCDebug(instanceProfileC) << d->m_instance->name() << "|" << uid << "depends on" << dep.uid;
- auto found = d->componentIndex.find(dep.uid);
- if (found != d->componentIndex.end()) {
- qCDebug(instanceProfileC) << d->m_instance->name() << "|" << (*found)->getID() << "is present";
- linked.append(*found);
- }
- }
- }
- return linked;
-}
-
QString PackProfile::getComponentVersion(const QString& uid) const
{
const auto iter = d->componentIndex.find(uid);
diff --git a/launcher/minecraft/PackProfile.h b/launcher/minecraft/PackProfile.h
index f733b3524..e58e9ae9a 100644
--- a/launcher/minecraft/PackProfile.h
+++ b/launcher/minecraft/PackProfile.h
@@ -48,7 +48,6 @@
#include "Component.h"
#include "LaunchProfile.h"
-#include "minecraft/PackProfile_p.h"
#include "modplatform/ModIndex.h"
#include "net/Mode.h"
@@ -120,9 +119,6 @@ class PackProfile : public QAbstractListModel {
bool setComponentVersion(const QString& uid, const QString& version, bool important = false);
- /// collects components that are dependant on or dependencies of the component
- ComponentContainer collectTreeLinked(const QString& uid);
-
bool installEmpty(const QString& uid, const QString& name);
QString patchFilePathForUid(const QString& uid) const;