Qt: multi thread trophy loading

This commit is contained in:
Megamouse 2019-07-27 16:30:34 +02:00
parent 221508fa07
commit 56a249fd5a
4 changed files with 47 additions and 87 deletions

View file

@ -449,11 +449,11 @@ void game_list_frame::Refresh(const bool fromDrive, const bool scrollAfter)
QSet<QString> serials; QSet<QString> serials;
QList<int> indices; QList<size_t> indices;
for (int i = 0; i < path_list.size(); ++i) for (size_t i = 0; i < path_list.size(); ++i)
indices.append(i); indices.append(i);
QtConcurrent::blockingMap(indices, [&](int& i) QtConcurrent::blockingMap(indices, [&](size_t& i)
{ {
const std::string dir = path_list[i]; const std::string dir = path_list[i];
@ -536,7 +536,6 @@ void game_list_frame::Refresh(const bool fromDrive, const bool scrollAfter)
{ {
LOG_FATAL(GENERAL, "Failed to update game list at %s\n%s thrown: %s", dir, typeid(e).name(), e.what()); LOG_FATAL(GENERAL, "Failed to update game list at %s\n%s thrown: %s", dir, typeid(e).name(), e.what());
return; return;
// Blame MSVC for double }}
} }
}); });

View file

@ -245,8 +245,8 @@ void save_manager_dialog::UpdateList()
} }
QList<int> indices; QList<int> indices;
for (int i = 0; i < m_save_entries.size(); ++i) for (size_t i = 0; i < m_save_entries.size(); ++i)
indices.append(i); indices.append(static_cast<int>(i));
QtConcurrent::blockingMap(indices, [this, currNotes](int& row) QtConcurrent::blockingMap(indices, [this, currNotes](int& row)
{ {

View file

@ -17,6 +17,7 @@
#include "yaml-cpp/yaml.h" #include "yaml-cpp/yaml.h"
#include <QtConcurrent> #include <QtConcurrent>
#include <QFutureWatcher>
#include <QHeaderView> #include <QHeaderView>
#include <QVBoxLayout> #include <QVBoxLayout>
#include <QCheckBox> #include <QCheckBox>
@ -332,20 +333,11 @@ trophy_manager_dialog::trophy_manager_dialog(std::shared_ptr<gui_settings> gui_s
RepaintUI(true); RepaintUI(true);
StartTrophyLoadThread(); StartTrophyLoadThreads();
} }
trophy_manager_dialog::~trophy_manager_dialog() trophy_manager_dialog::~trophy_manager_dialog()
{ {
if (m_thread_state != TrophyThreadState::CLOSED)
{
TrophyThreadState expected = TrophyThreadState::RUNNING;
m_thread_state.compare_exchange_strong(expected, TrophyThreadState::CLOSING);
while (m_thread_state != TrophyThreadState::CLOSED)
{
std::this_thread::yield();
}
}
} }
bool trophy_manager_dialog::LoadTrophyFolderToDB(const std::string& trop_name) bool trophy_manager_dialog::LoadTrophyFolderToDB(const std::string& trop_name)
@ -652,46 +644,45 @@ void trophy_manager_dialog::ShowContextMenu(const QPoint& loc)
menu->exec(globalPos); menu->exec(globalPos);
} }
void trophy_manager_dialog::StartTrophyLoadThread() void trophy_manager_dialog::StartTrophyLoadThreads()
{ {
auto progressDialog = new QProgressDialog( m_trophies_db.clear();
tr("Loading trophy data, please wait..."), tr("Cancel"), 0, 1, this,
Qt::Dialog | Qt::WindowTitleHint | Qt::CustomizeWindowHint);
progressDialog->setWindowTitle(tr("Loading trophies"));
connect(progressDialog, &QProgressDialog::canceled, [this]()
{
TrophyThreadState expected = TrophyThreadState::RUNNING;
m_thread_state.compare_exchange_strong(expected, TrophyThreadState::CLOSING);
this->close(); // It's pointless to show an empty window
});
progressDialog->show();
auto trophyThread = new trophy_manager_dialog::trophy_load_thread(this); QDir trophy_dir(qstr(vfs::get(m_trophy_dir)));
connect(trophyThread, &QThread::finished, trophyThread, &QThread::deleteLater);
connect(trophyThread, &QThread::finished, progressDialog, &QProgressDialog::deleteLater);
connect(trophyThread, &trophy_manager_dialog::trophy_load_thread::TotalCountChanged, progressDialog, &QProgressDialog::setMaximum);
connect(trophyThread, &trophy_manager_dialog::trophy_load_thread::ProcessedCountChanged, progressDialog, &QProgressDialog::setValue);
connect(trophyThread, &trophy_manager_dialog::trophy_load_thread::FinishedSuccessfully, [this]() { RepaintUI(true); });
m_thread_state = TrophyThreadState::RUNNING;
trophyThread->start();
}
void trophy_manager_dialog::trophy_load_thread::run()
{
m_manager->m_trophies_db.clear();
QDir trophy_dir(qstr(vfs::get(m_manager->m_trophy_dir)));
const auto folder_list = trophy_dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); const auto folder_list = trophy_dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
const int count = folder_list.count(); const int count = folder_list.count();
Q_EMIT TotalCountChanged(count);
for (int i = 0; m_manager->m_thread_state == TrophyThreadState::RUNNING && i < count; i++) if (count <= 0)
{ {
std::string dir_name = sstr(folder_list.value(i)); RepaintUI(true);
return;
}
QList<int> indices;
for (int i = 0; i < count; ++i)
indices.append(i);
QFutureWatcher<void> futureWatcher;
QProgressDialog progressDialog(tr("Loading trophy data, please wait..."), tr("Cancel"), 0, 1, this, Qt::Dialog | Qt::WindowTitleHint | Qt::CustomizeWindowHint);
progressDialog.setWindowTitle(tr("Loading trophies"));
connect(&futureWatcher, &QFutureWatcher<void>::progressRangeChanged, &progressDialog, &QProgressDialog::setRange);
connect(&futureWatcher, &QFutureWatcher<void>::progressValueChanged, &progressDialog, &QProgressDialog::setValue);
connect(&futureWatcher, &QFutureWatcher<void>::finished, [this]() { RepaintUI(true); });
connect(&progressDialog, &QProgressDialog::canceled, [this, &futureWatcher]()
{
futureWatcher.cancel();
this->close(); // It's pointless to show an empty window
});
futureWatcher.setFuture(QtConcurrent::map(indices, [this, folder_list, &progressDialog](const int& i)
{
const std::string dir_name = sstr(folder_list.value(i));
LOG_TRACE(GENERAL, "Loading trophy dir: %s", dir_name); LOG_TRACE(GENERAL, "Loading trophy dir: %s", dir_name);
try try
{ {
m_manager->LoadTrophyFolderToDB(dir_name); LoadTrophyFolderToDB(dir_name);
} }
catch (const std::exception& e) catch (const std::exception& e)
{ {
@ -699,15 +690,11 @@ void trophy_manager_dialog::trophy_load_thread::run()
// Also add a way of showing the number of corrupted/invalid folders in UI somewhere. // Also add a way of showing the number of corrupted/invalid folders in UI somewhere.
LOG_ERROR(GENERAL, "Exception occurred while parsing folder %s for trophies: %s", dir_name, e.what()); LOG_ERROR(GENERAL, "Exception occurred while parsing folder %s for trophies: %s", dir_name, e.what());
} }
Q_EMIT ProcessedCountChanged(i + 1); }));
}
if (m_manager->m_thread_state == TrophyThreadState::RUNNING) progressDialog.exec();
{
Q_EMIT FinishedSuccessfully();
}
m_manager->m_thread_state = TrophyThreadState::CLOSED; futureWatcher.waitForFinished();
} }
void trophy_manager_dialog::PopulateGameTable() void trophy_manager_dialog::PopulateGameTable()
@ -721,15 +708,16 @@ void trophy_manager_dialog::PopulateGameTable()
QList<QString> names; QList<QString> names;
QList<int> indices; QList<int> indices;
for (int i = 0; i < m_trophies_db.size(); ++i) for (size_t i = 0; i < m_trophies_db.size(); ++i)
{ {
const int index = static_cast<int>(i);
const QString name = qstr(m_trophies_db[i]->game_name).simplified(); const QString name = qstr(m_trophies_db[i]->game_name).simplified();
m_game_combo->addItem(name, i); m_game_combo->addItem(name, index);
names.append(name); names.append(name);
indices.append(i); indices.append(index);
} }
QtConcurrent::blockingMap(indices, [this, names](int& i) QtConcurrent::blockingMap(indices, [this, &names](int& i)
{ {
const int all_trophies = m_trophies_db[i]->trop_usr->GetTrophiesCount(); const int all_trophies = m_trophies_db[i]->trop_usr->GetTrophiesCount();
const int unlocked_trophies = m_trophies_db[i]->trop_usr->GetUnlockedTrophiesCount(); const int unlocked_trophies = m_trophies_db[i]->trop_usr->GetUnlockedTrophiesCount();

View file

@ -45,13 +45,6 @@ enum GameColumns
GameColumnsCount GameColumnsCount
}; };
enum TrophyThreadState
{
RUNNING,
CLOSING,
CLOSED
};
class trophy_manager_dialog : public QWidget class trophy_manager_dialog : public QWidget
{ {
Q_OBJECT Q_OBJECT
@ -82,8 +75,8 @@ private:
*/ */
bool LoadTrophyFolderToDB(const std::string& trop_name); bool LoadTrophyFolderToDB(const std::string& trop_name);
/** Populate the trophy database (in another thread). */ /** Populate the trophy database (multithreaded). */
void StartTrophyLoadThread(); void StartTrophyLoadThreads();
/** Fills game table with information. /** Fills game table with information.
Takes results from LoadTrophyFolderToDB and puts it into the UI. Takes results from LoadTrophyFolderToDB and puts it into the UI.
@ -110,9 +103,6 @@ private:
QTableWidget* m_trophy_table; //! UI element to display trophy stuff. QTableWidget* m_trophy_table; //! UI element to display trophy stuff.
QTableWidget* m_game_table; //! UI element to display games. QTableWidget* m_game_table; //! UI element to display games.
class trophy_load_thread; //Qt cannot parse nested classes, declaration is below
std::atomic<TrophyThreadState> m_thread_state = TrophyThreadState::CLOSED;
bool m_show_hidden_trophies = false; bool m_show_hidden_trophies = false;
bool m_show_unlocked_trophies = true; bool m_show_unlocked_trophies = true;
bool m_show_locked_trophies = true; bool m_show_locked_trophies = true;
@ -132,20 +122,3 @@ private:
QSlider* m_game_icon_slider = nullptr; QSlider* m_game_icon_slider = nullptr;
QColor m_game_icon_color; QColor m_game_icon_color;
}; };
class trophy_manager_dialog::trophy_load_thread : public QThread
{
Q_OBJECT
public:
explicit trophy_load_thread(trophy_manager_dialog *manager) : m_manager(manager) {}
void run() override;
Q_SIGNALS:
void TotalCountChanged(int count);
void ProcessedCountChanged(int processed);
void FinishedSuccessfully();
private:
trophy_manager_dialog *m_manager;
};