move resolution into update actions in task.

Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
This commit is contained in:
Rachel Powers 2024-06-27 15:42:25 -07:00
parent 474effe7c7
commit a85d6cb1f2
No known key found for this signature in database
GPG Key ID: E10E321EB160949B
6 changed files with 231 additions and 82 deletions

View File

@ -38,6 +38,7 @@
#include <meta/VersionList.h> #include <meta/VersionList.h>
#include <QSaveFile> #include <QSaveFile>
#include <optional>
#include "Application.h" #include "Application.h"
#include "FileSystem.h" #include "FileSystem.h"
@ -235,7 +236,8 @@ ProblemSeverity Component::getProblemSeverity() const
{ {
auto file = getVersionFile(); auto file = getVersionFile();
if (file) { if (file) {
return file->getProblemSeverity(); auto severity = file->getProblemSeverity();
return m_componentProblemSeverity > severity ? m_componentProblemSeverity : severity;
} }
return ProblemSeverity::Error; return ProblemSeverity::Error;
} }
@ -244,11 +246,27 @@ const QList<PatchProblem> Component::getProblems() const
{ {
auto file = getVersionFile(); auto file = getVersionFile();
if (file) { 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.") } }; 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) void Component::setVersion(const QString& version)
{ {
if (version == m_version) { if (version == m_version) {
@ -415,6 +433,21 @@ void Component::waitLoadMeta()
} }
} }
void Component::setUpdateAction(UpdateAction action)
{
m_updateAction = action;
}
std::optional<UpdateAction> Component::getUpdateAction()
{
return m_updateAction;
}
void Component::clearUpdateAction()
{
m_updateAction.reset();
}
QDebug operator<<(QDebug d, const Component& comp) QDebug operator<<(QDebug d, const Component& comp)
{ {
d << "Component(" << comp.m_uid << " : " << comp.m_cachedVersion << ")"; d << "Component(" << comp.m_uid << " : " << comp.m_cachedVersion << ")";

View File

@ -4,6 +4,7 @@
#include <QJsonDocument> #include <QJsonDocument>
#include <QList> #include <QList>
#include <memory> #include <memory>
#include <optional>
#include "ProblemProvider.h" #include "ProblemProvider.h"
#include "QObjectPtr.h" #include "QObjectPtr.h"
#include "meta/JsonFormat.h" #include "meta/JsonFormat.h"
@ -16,6 +17,26 @@ class VersionList;
} // namespace Meta } // namespace Meta
class VersionFile; 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<UpdateActionChangeVerison, UpdateActionLatestRecommendedCompatable, UpdateActionRemove, UpdateActionImportantChanged>;
class Component : public QObject, public ProblemProvider { class Component : public QObject, public ProblemProvider {
Q_OBJECT Q_OBJECT
public: public:
@ -58,6 +79,8 @@ class Component : public QObject, public ProblemProvider {
const QList<PatchProblem> getProblems() const override; const QList<PatchProblem> getProblems() const override;
ProblemSeverity getProblemSeverity() const override; ProblemSeverity getProblemSeverity() const override;
void addComponentProblem(ProblemSeverity severity, const QString& description);
void resetComponentProblems();
void setVersion(const QString& version); void setVersion(const QString& version);
bool customize(); bool customize();
@ -67,6 +90,11 @@ class Component : public QObject, public ProblemProvider {
void waitLoadMeta(); void waitLoadMeta();
void setUpdateAction(UpdateAction action);
void clearUpdateAction();
std::optional<UpdateAction> getUpdateAction();
std::optional<UpdateAction> takeUpdateAction();
signals: signals:
void dataChanged(); void dataChanged();
@ -104,6 +132,11 @@ class Component : public QObject, public ProblemProvider {
std::shared_ptr<Meta::Version> m_metaVersion; std::shared_ptr<Meta::Version> m_metaVersion;
std::shared_ptr<VersionFile> m_file; std::shared_ptr<VersionFile> m_file;
bool m_loaded = false; bool m_loaded = false;
private:
QList<PatchProblem> m_componentProblems;
ProblemSeverity m_componentProblemSeverity = ProblemSeverity::None;
std::optional<UpdateAction> m_updateAction = std::nullopt;
}; };
using ComponentPtr = shared_qobject_ptr<Component>; using ComponentPtr = shared_qobject_ptr<Component>;

View File

@ -1,9 +1,11 @@
#include "ComponentUpdateTask.h" #include "ComponentUpdateTask.h"
#include <algorithm>
#include "Component.h" #include "Component.h"
#include "ComponentUpdateTask_p.h" #include "ComponentUpdateTask_p.h"
#include "PackProfile.h" #include "PackProfile.h"
#include "PackProfile_p.h" #include "PackProfile_p.h"
#include "ProblemProvider.h"
#include "Version.h" #include "Version.h"
#include "cassert" #include "cassert"
#include "meta/Index.h" #include "meta/Index.h"
@ -196,6 +198,7 @@ void ComponentUpdateTask::loadComponents()
switch (result) { switch (result) {
case LoadResult::LoadedLocal: { case LoadResult::LoadedLocal: {
// Everything got loaded. Advance to dependency resolution. // Everything got loaded. Advance to dependency resolution.
performUpdateActions();
resolveDependencies(d->mode == Mode::Launch || d->netmode == Net::Mode::Offline); resolveDependencies(d->mode == Mode::Launch || d->netmode == Net::Mode::Offline);
break; break;
} }
@ -380,6 +383,42 @@ static bool getTrivialComponentChanges(const ComponentIndex& index, const Requir
return succeeded; 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, 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: 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 // FIXME: throw all this away and use a graph
@ -488,10 +527,121 @@ void ComponentUpdateTask::resolveDependencies(bool checkOnly)
if (recursionNeeded) { if (recursionNeeded) {
loadComponents(); loadComponents();
} else { } else {
finalizeComponents();
emitSucceeded(); emitSucceeded();
} }
} }
// Variant visitation via lambda
template <class... Ts>
struct overload : Ts... {
using Ts::operator()...;
};
template <class... Ts>
overload(Ts...) -> overload<Ts...>;
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) void ComponentUpdateTask::remoteLoadSucceeded(size_t taskIndex)
{ {
if (static_cast<size_t>(d->remoteLoadStatusList.size()) < taskIndex) { if (static_cast<size_t>(d->remoteLoadStatusList.size()) < taskIndex) {
@ -551,6 +701,7 @@ void ComponentUpdateTask::checkIfAllFinished()
if (d->remoteLoadSuccessful) { if (d->remoteLoadSuccessful) {
// nothing bad happened... clear the temp load status and proceed with looking at dependencies // nothing bad happened... clear the temp load status and proceed with looking at dependencies
d->remoteLoadStatusList.clear(); d->remoteLoadStatusList.clear();
performUpdateActions();
resolveDependencies(d->mode == Mode::Launch); resolveDependencies(d->mode == Mode::Launch);
} else { } else {
// remote load failed... report error and bail // remote load failed... report error and bail

View File

@ -1,5 +1,6 @@
#pragma once #pragma once
#include "minecraft/Component.h"
#include "net/Mode.h" #include "net/Mode.h"
#include "tasks/Task.h" #include "tasks/Task.h"
@ -21,7 +22,11 @@ class ComponentUpdateTask : public Task {
private: private:
void loadComponents(); void loadComponents();
/// collects components that are dependent on or dependencies of the component
QList<ComponentPtr> collectTreeLinked(const QString& uid);
void resolveDependencies(bool checkOnly); void resolveDependencies(bool checkOnly);
void performUpdateActions();
void finalizeComponents();
void remoteLoadSucceeded(size_t index); void remoteLoadSucceeded(size_t index);
void remoteLoadFailed(size_t index, const QString& msg); void remoteLoadFailed(size_t index, const QString& msg);

View File

@ -49,6 +49,7 @@
#include <QTimer> #include <QTimer>
#include <QUuid> #include <QUuid>
#include <algorithm> #include <algorithm>
#include <utility>
#include "Application.h" #include "Application.h"
#include "Exception.h" #include "Exception.h"
@ -56,6 +57,7 @@
#include "Json.h" #include "Json.h"
#include "meta/Index.h" #include "meta/Index.h"
#include "meta/JsonFormat.h" #include "meta/JsonFormat.h"
#include "minecraft/Component.h"
#include "minecraft/MinecraftInstance.h" #include "minecraft/MinecraftInstance.h"
#include "minecraft/OneSixVersionFormat.h" #include "minecraft/OneSixVersionFormat.h"
#include "minecraft/ProfileUtils.h" #include "minecraft/ProfileUtils.h"
@ -67,6 +69,8 @@
#include "minecraft/Logging.h" #include "minecraft/Logging.h"
#include "ui/dialogs/CustomMessageBox.h"
static const QMap<QString, ModPlatform::ModLoaderType> modloaderMapping{ { "net.neoforged", ModPlatform::NeoForge }, static const QMap<QString, ModPlatform::ModLoaderType> modloaderMapping{ { "net.neoforged", ModPlatform::NeoForge },
{ "net.minecraftforge", ModPlatform::Forge }, { "net.minecraftforge", ModPlatform::Forge },
{ "net.fabricmc.fabric-loader", ModPlatform::Fabric }, { "net.fabricmc.fabric-loader", ModPlatform::Fabric },
@ -988,58 +992,16 @@ bool PackProfile::setComponentVersion(const QString& uid, const QString& version
ComponentPtr component = *iter; ComponentPtr component = *iter;
// set existing // set existing
if (component->revert()) { 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 // set new version
auto oldVersion = component->getVersion();
component->setVersion(version); component->setVersion(version);
component->setImportant(important); component->setImportant(important);
if (important) { if (important) {
component->setUpdateAction(UpdateAction{ UpdateActionImportantChanged{ oldVersion } });
resolve(Net::Mode::Online); resolve(Net::Mode::Online);
} }
return true; return true;
} }
return false; 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 QString PackProfile::getComponentVersion(const QString& uid) const
{ {
const auto iter = d->componentIndex.find(uid); const auto iter = d->componentIndex.find(uid);

View File

@ -48,7 +48,6 @@
#include "Component.h" #include "Component.h"
#include "LaunchProfile.h" #include "LaunchProfile.h"
#include "minecraft/PackProfile_p.h"
#include "modplatform/ModIndex.h" #include "modplatform/ModIndex.h"
#include "net/Mode.h" #include "net/Mode.h"
@ -120,9 +119,6 @@ class PackProfile : public QAbstractListModel {
bool setComponentVersion(const QString& uid, const QString& version, bool important = false); 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); bool installEmpty(const QString& uid, const QString& name);
QString patchFilePathForUid(const QString& uid) const; QString patchFilePathForUid(const QString& uid) const;