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;