Qt: Don't reset model for every scanned game

Make the UI a little more responsive.
This commit is contained in:
Stenzek
2025-08-07 20:05:54 +10:00
parent 2669b026d7
commit a080228ee5
4 changed files with 63 additions and 79 deletions

View File

@@ -14,27 +14,30 @@
#include "moc_gamelistrefreshthread.cpp"
AsyncRefreshProgressCallback::AsyncRefreshProgressCallback(GameListRefreshThread* parent) : m_parent(parent)
GameListRefreshThread::GameListRefreshThread(bool invalidate_cache) : QThread(), m_invalidate_cache(invalidate_cache)
{
}
float AsyncRefreshProgressCallback::timeSinceStart() const
{
return m_start_time.GetTimeSeconds();
}
GameListRefreshThread::~GameListRefreshThread() = default;
void AsyncRefreshProgressCallback::Cancel()
void GameListRefreshThread::cancel()
{
// Not atomic, but we don't need to cancel immediately.
m_cancelled = true;
}
void AsyncRefreshProgressCallback::PushState()
void GameListRefreshThread::run()
{
GameList::Refresh(m_invalidate_cache, false, this);
emit refreshComplete();
}
void GameListRefreshThread::PushState()
{
ProgressCallback::PushState();
}
void AsyncRefreshProgressCallback::PopState()
void GameListRefreshThread::PopState()
{
ProgressCallback::PopState();
@@ -46,7 +49,7 @@ void AsyncRefreshProgressCallback::PopState()
fireUpdate();
}
void AsyncRefreshProgressCallback::SetStatusText(const std::string_view text)
void GameListRefreshThread::SetStatusText(const std::string_view text)
{
const QString new_text = QtUtils::StringViewToQString(text);
if (new_text == m_status_text)
@@ -56,7 +59,7 @@ void AsyncRefreshProgressCallback::SetStatusText(const std::string_view text)
fireUpdate();
}
void AsyncRefreshProgressCallback::SetProgressRange(u32 range)
void GameListRefreshThread::SetProgressRange(u32 range)
{
ProgressCallback::SetProgressRange(range);
if (static_cast<int>(m_progress_range) == m_last_range)
@@ -66,7 +69,7 @@ void AsyncRefreshProgressCallback::SetProgressRange(u32 range)
fireUpdate();
}
void AsyncRefreshProgressCallback::SetProgressValue(u32 value)
void GameListRefreshThread::SetProgressValue(u32 value)
{
ProgressCallback::SetProgressValue(value);
if (static_cast<int>(m_progress_value) == m_last_value)
@@ -76,46 +79,24 @@ void AsyncRefreshProgressCallback::SetProgressValue(u32 value)
fireUpdate();
}
void AsyncRefreshProgressCallback::ModalError(const std::string_view message)
void GameListRefreshThread::ModalError(const std::string_view message)
{
QMessageBox::critical(nullptr, QStringLiteral("Error"), QtUtils::StringViewToQString(message));
}
bool AsyncRefreshProgressCallback::ModalConfirmation(const std::string_view message)
bool GameListRefreshThread::ModalConfirmation(const std::string_view message)
{
return QMessageBox::question(nullptr, QStringLiteral("Question"), QtUtils::StringViewToQString(message)) ==
QMessageBox::Yes;
}
void AsyncRefreshProgressCallback::ModalInformation(const std::string_view message)
void GameListRefreshThread::ModalInformation(const std::string_view message)
{
QMessageBox::information(nullptr, QStringLiteral("Information"), QtUtils::StringViewToQString(message));
}
void AsyncRefreshProgressCallback::fireUpdate()
void GameListRefreshThread::fireUpdate()
{
m_parent->refreshProgress(m_status_text, m_last_value, m_last_range, m_start_time.GetTimeSeconds());
}
GameListRefreshThread::GameListRefreshThread(bool invalidate_cache)
: QThread(), m_progress(this), m_invalidate_cache(invalidate_cache)
{
}
GameListRefreshThread::~GameListRefreshThread() = default;
float GameListRefreshThread::timeSinceStart() const
{
return m_progress.timeSinceStart();
}
void GameListRefreshThread::cancel()
{
m_progress.Cancel();
}
void GameListRefreshThread::run()
{
GameList::Refresh(m_invalidate_cache, false, &m_progress);
emit refreshComplete();
emit refreshProgress(m_status_text, m_last_value, m_last_range, static_cast<int>(GameList::GetEntryCount()),
m_start_time.GetTimeSeconds());
}

View File

@@ -8,39 +8,7 @@
#include "common/progress_callback.h"
#include "common/timer.h"
class GameListRefreshThread;
class AsyncRefreshProgressCallback : public ProgressCallback
{
public:
explicit AsyncRefreshProgressCallback(GameListRefreshThread* parent);
float timeSinceStart() const;
void Cancel();
void PushState() override;
void PopState() override;
void SetStatusText(const std::string_view text) override;
void SetProgressRange(u32 range) override;
void SetProgressValue(u32 value) override;
void ModalError(const std::string_view message) override;
bool ModalConfirmation(const std::string_view message) override;
void ModalInformation(const std::string_view message) override;
private:
void fireUpdate();
GameListRefreshThread* m_parent;
Timer m_start_time;
QString m_status_text;
int m_last_range = 1;
int m_last_value = 0;
};
class GameListRefreshThread final : public QThread
class GameListRefreshThread final : public QThread, public ProgressCallback
{
Q_OBJECT
@@ -53,13 +21,29 @@ public:
void cancel();
Q_SIGNALS:
void refreshProgress(const QString& status, int current, int total, float time);
void refreshProgress(const QString& status, int current, int total, int entry_count, float time);
void refreshComplete();
protected:
void run() final;
private:
AsyncRefreshProgressCallback m_progress;
void PushState() override;
void PopState() override;
void SetStatusText(const std::string_view text) override;
void SetProgressRange(u32 range) override;
void SetProgressValue(u32 value) override;
void ModalError(const std::string_view message) override;
bool ModalConfirmation(const std::string_view message) override;
void ModalInformation(const std::string_view message) override;
void fireUpdate();
Timer m_start_time;
QString m_status_text;
int m_last_range = 1;
int m_last_value = 0;
bool m_invalidate_cache;
};

View File

@@ -32,6 +32,7 @@
#include <QtWidgets/QScrollBar>
#include <QtWidgets/QStyledItemDelegate>
#include <algorithm>
#include <limits>
#include "moc_gamelistwidget.cpp"
@@ -1281,6 +1282,7 @@ void GameListWidget::refresh(bool invalidate_cache)
Qt::QueuedConnection);
connect(m_refresh_thread, &GameListRefreshThread::refreshComplete, this, &GameListWidget::onRefreshComplete,
Qt::QueuedConnection);
m_refresh_last_entry_count = std::numeric_limits<int>::max(); // force reset on first progress update
m_refresh_thread->start();
}
@@ -1336,12 +1338,25 @@ void GameListWidget::updateBackground(bool reload_image)
});
}
void GameListWidget::onRefreshProgress(const QString& status, int current, int total, float time)
void GameListWidget::onRefreshProgress(const QString& status, int current, int total, int entry_count, float time)
{
// Avoid spamming the UI on very short refresh (e.g. game exit).
static constexpr float SHORT_REFRESH_TIME = 0.5f;
if (!m_model->hasTakenGameList())
m_model->refresh();
{
if (entry_count > m_refresh_last_entry_count)
{
m_model->beginInsertRows(QModelIndex(), m_refresh_last_entry_count, entry_count - 1);
m_model->endInsertRows();
}
else
{
m_model->beginResetModel();
m_model->endResetModel();
}
m_refresh_last_entry_count = entry_count;
}
// switch away from the placeholder while we scan, in case we find anything
if (m_ui.stack->currentIndex() == 2)

View File

@@ -27,11 +27,14 @@ Q_DECLARE_METATYPE(const GameList::Entry*);
class GameListSortModel;
class GameListRefreshThread;
class GameListWidget;
class GameListModel final : public QAbstractTableModel
{
Q_OBJECT
friend GameListWidget;
public:
enum Column : int
{
@@ -242,7 +245,7 @@ Q_SIGNALS:
void addGameDirectoryRequested();
private Q_SLOTS:
void onRefreshProgress(const QString& status, int current, int total, float time);
void onRefreshProgress(const QString& status, int current, int total, int entry_count, float time);
void onRefreshComplete();
void onCoverScaleChanged(float scale);
@@ -280,7 +283,8 @@ private:
QWidget* m_empty_widget = nullptr;
Ui::EmptyGameListWidget m_empty_ui;
GameListRefreshThread* m_refresh_thread = nullptr;
QImage m_background_image;
GameListRefreshThread* m_refresh_thread = nullptr;
int m_refresh_last_entry_count = 0;
};