From 352b45bf5e89c0eae0dba047eaefda9098bf971a Mon Sep 17 00:00:00 2001 From: Vivek Kushwaha Date: Tue, 23 Jun 2026 14:58:15 +0530 Subject: [PATCH] fix(ModFolderPage): prevent stale profile saves during tab switching Signed-off-by: Vivek Kushwaha --- launcher/ui/pages/instance/ModFolderPage.cpp | 59 +++++++++++--------- launcher/ui/pages/instance/ModFolderPage.h | 19 ++++--- 2 files changed, 45 insertions(+), 33 deletions(-) diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index a7e8f659e..348ac4a4b 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -198,7 +198,10 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, ModFolderModel* model, QWidget* // Install viewport filter so clicking blank space in the list clears selection. ui->treeView->viewport()->installEventFilter(this); - connect(m_profileTabBar, &QTabBar::currentChanged, this, &ModFolderPage::onProfileTabChanged); + connect(m_profileTabBar, &QTabBar::currentChanged, this, [this](int index) { + ++m_profileSwitchGeneration; + applyProfileSwitch(index, m_profileSwitchGeneration); + }); connect(m_profileTabBar, &QTabBar::customContextMenuRequested, this, &ModFolderPage::onTabContextMenuRequested); connect(m_newTabButton, &QToolButton::clicked, this, [this] { @@ -279,30 +282,15 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, ModFolderModel* model, QWidget* savedIndex = 0; } m_profileTabBar->setCurrentIndex(savedIndex); - onProfileTabChanged(m_profileTabBar->currentIndex()); + applyProfileSwitch(m_profileTabBar->currentIndex(), m_profileSwitchGeneration); - connect(m_model, &ResourceFolderModel::updateFinished, this, [this]() { - qDebug().noquote().nospace() - << "[SAVE_TRIGGER]\n" - << "source = updateFinished\n" - << "currentProfile = " << m_currentProfile; - if (m_profileLoading) { - m_profileLoading = false; - return; - } - saveCurrentProfileState(); - }); connect(m_model, &QAbstractItemModel::dataChanged, this, [this]() { - qDebug().noquote().nospace() - << "[SAVE_TRIGGER]\n" - << "source = dataChanged\n" - << "currentProfile = " << m_currentProfile; - saveCurrentProfileState(); - }); - connect(m_model, &ResourceFolderModel::updateFinished, this, [this] { - if (!m_currentProfile.isEmpty()) { - QSet expectedMods = m_profileStates.value(m_currentProfile); - compareModelToProfileState("updateFinished() FIRED", m_model, expectedMods, ++g_logSequence); + if (!m_applyingProfile) { + qDebug().noquote().nospace() + << "[SAVE_TRIGGER]\n" + << "source = dataChanged\n" + << "currentProfile = " << m_currentProfile; + saveCurrentProfileState(); } }); } @@ -749,7 +737,7 @@ void ModFolderPage::saveCurrentProfileState() << "savedModsCount:" << enabledMods.size(); } -void ModFolderPage::onProfileTabChanged(int index) { +void ModFolderPage::applyProfileSwitch(int index, int generation) { qDebug() << "[INSTRUMENTATION]" << ++g_logSequence << "onProfileTabChanged() START" << "index:" << index << "thread:" << QThread::currentThreadId() @@ -823,7 +811,23 @@ void ModFolderPage::onProfileTabChanged(int index) { } compareModelToProfileState("BEFORE update()", m_model, enabledMods, ++g_logSequence); - bool started = m_model->update(); + + // Capture generation token. If tab changes again before this update + // completes, the token will be stale and we discard the result. + int capturedGeneration = generation; + m_applyingProfile = true; + connect(m_model, &ResourceFolderModel::updateFinished, this, + [this, capturedGeneration] { + if (capturedGeneration == m_profileSwitchGeneration) { + saveCurrentProfileState(); + } + m_applyingProfile = false; + }, + Qt::SingleShotConnection); + + m_model->update(); + + qDebug().noquote().nospace() << "[ProfileSwitch]\n" << "Profile: " << tabName << "\n" @@ -842,6 +846,7 @@ void ModFolderPage::onProfileTabChanged(int index) { setActiveProfileForModel(m_model, QStringLiteral("None")); m_profileTabBar->setProperty("currentProfileName", QString()); m_profileLoading = false; + m_applyingProfile = false; } qDebug() << "[INSTRUMENTATION]" << ++g_logSequence << "onProfileTabChanged() END" @@ -1119,6 +1124,8 @@ void ModFolderPage::onTabEnableAll(int tabIndex) { if (m_profileTabBar->currentIndex() != tabIndex) { m_profileTabBar->setCurrentIndex(tabIndex); + } else { + applyProfileSwitch(tabIndex, m_profileSwitchGeneration); } // Build the full index list and enable every mod in the active profile. QModelIndexList allIndices; @@ -1136,6 +1143,8 @@ void ModFolderPage::onTabDisableAll(int tabIndex) { if (m_profileTabBar->currentIndex() != tabIndex) { m_profileTabBar->setCurrentIndex(tabIndex); + } else { + applyProfileSwitch(tabIndex, m_profileSwitchGeneration); } // Build the full index list and disable every mod in the active profile. QModelIndexList allIndices; diff --git a/launcher/ui/pages/instance/ModFolderPage.h b/launcher/ui/pages/instance/ModFolderPage.h index d835560b2..a32bfc192 100644 --- a/launcher/ui/pages/instance/ModFolderPage.h +++ b/launcher/ui/pages/instance/ModFolderPage.h @@ -48,25 +48,25 @@ class ModFolderPage : public ExternalResourcesPage { Q_OBJECT - + inline bool handleNoModLoader(); - + public: explicit ModFolderPage(BaseInstance* inst, ModFolderModel* model, QWidget* parent = nullptr); virtual ~ModFolderPage(); - + void setFilter(const QString& filter) { m_fileSelectionFilter = filter; } - + virtual QString displayName() const override { return tr("Mods"); } virtual QIcon icon() const override { return QIcon::fromTheme("loadermods"); } virtual QString id() const override { return "mods"; } virtual QString helpPage() const override { return "Loader-mods"; } - + virtual bool shouldDisplay() const override; - + public slots: void updateFrame(const QModelIndex& current, const QModelIndex& previous) override; - + private slots: void removeItems(const QItemSelection& selection) override; @@ -76,7 +76,6 @@ class ModFolderPage : public ExternalResourcesPage { void deleteModMetadata(); void exportModMetadata(); void changeModVersion(); - void onProfileTabChanged(int index); void onAddProfileClicked(); void onRemoveProfileClicked(); void saveCurrentProfileState(); @@ -91,6 +90,8 @@ class ModFolderPage : public ExternalResourcesPage { void onTabDisableAll(int tabIndex); private: + void applyProfileSwitch(int index, int generation); + // Creates a new profile tab with the given initial mod-enable state. // Pass an empty QSet for a blank slate; pass a copied QSet for duplication. void createProfile(const QString& name, const QSet& initialState, int insertAfterIndex = -1); @@ -120,6 +121,8 @@ class ModFolderPage : public ExternalResourcesPage { QString m_settingsPrefix; bool m_destructorStarted = false; bool m_profileLoading = false; + int m_profileSwitchGeneration = 0; + bool m_applyingProfile = false; }; class CoreModFolderPage : public ModFolderPage {