1
0
mirror of https://github.com/RPCS3/rpcs3.git synced 2024-11-23 03:02:53 +01:00

Qt: multithreaded trophy icon refresh

This commit is contained in:
Megamouse 2021-10-30 20:44:25 +02:00
parent 4e6b37ca04
commit 20331a77ce
4 changed files with 132 additions and 58 deletions

View File

@ -204,12 +204,12 @@ bool TROPUSRLoader::Generate(const std::string& filepath, const std::string& con
return Save(filepath); return Save(filepath);
} }
u32 TROPUSRLoader::GetTrophiesCount() u32 TROPUSRLoader::GetTrophiesCount() const
{ {
return ::size32(m_table6); return ::size32(m_table6);
} }
u32 TROPUSRLoader::GetUnlockedTrophiesCount() u32 TROPUSRLoader::GetUnlockedTrophiesCount() const
{ {
u32 count = 0; u32 count = 0;
for (const auto& trophy : m_table6) for (const auto& trophy : m_table6)
@ -296,7 +296,7 @@ u32 TROPUSRLoader::GetUnlockedPlatinumID(u32 trophy_id, const std::string& confi
return pid; return pid;
} }
u32 TROPUSRLoader::GetTrophyGrade(u32 id) u32 TROPUSRLoader::GetTrophyGrade(u32 id) const
{ {
if (id >= m_table4.size()) if (id >= m_table4.size())
{ {
@ -307,7 +307,7 @@ u32 TROPUSRLoader::GetTrophyGrade(u32 id)
return m_table4[id].trophy_grade; // Let's assume the trophies are stored ordered return m_table4[id].trophy_grade; // Let's assume the trophies are stored ordered
} }
u32 TROPUSRLoader::GetTrophyUnlockState(u32 id) u32 TROPUSRLoader::GetTrophyUnlockState(u32 id) const
{ {
if (id >= m_table6.size()) if (id >= m_table6.size())
{ {
@ -318,7 +318,7 @@ u32 TROPUSRLoader::GetTrophyUnlockState(u32 id)
return m_table6[id].trophy_state; // Let's assume the trophies are stored ordered return m_table6[id].trophy_state; // Let's assume the trophies are stored ordered
} }
u64 TROPUSRLoader::GetTrophyTimestamp(u32 id) u64 TROPUSRLoader::GetTrophyTimestamp(u32 id) const
{ {
if (id >= m_table6.size()) if (id >= m_table6.size())
{ {

View File

@ -89,14 +89,14 @@ public:
virtual load_result Load(const std::string& filepath, const std::string& configpath); virtual load_result Load(const std::string& filepath, const std::string& configpath);
virtual bool Save(const std::string& filepath); virtual bool Save(const std::string& filepath);
virtual u32 GetTrophiesCount(); virtual u32 GetTrophiesCount() const;
virtual u32 GetUnlockedTrophiesCount(); virtual u32 GetUnlockedTrophiesCount() const;
virtual u32 GetUnlockedPlatinumID(u32 trophy_id, const std::string& config_path); virtual u32 GetUnlockedPlatinumID(u32 trophy_id, const std::string& config_path);
virtual u32 GetTrophyGrade(u32 id); virtual u32 GetTrophyGrade(u32 id) const;
virtual u32 GetTrophyUnlockState(u32 id); virtual u32 GetTrophyUnlockState(u32 id) const;
virtual u64 GetTrophyTimestamp(u32 id); virtual u64 GetTrophyTimestamp(u32 id) const;
virtual bool UnlockTrophy(u32 id, u64 timestamp1, u64 timestamp2); virtual bool UnlockTrophy(u32 id, u64 timestamp1, u64 timestamp2);
}; };

View File

@ -111,6 +111,7 @@ trophy_manager_dialog::trophy_manager_dialog(std::shared_ptr<gui_settings> gui_s
m_trophy_table->setHorizontalHeaderLabels(QStringList{ tr("Icon"), tr("Name"), tr("Description"), tr("Type"), tr("Status"), tr("ID"), tr("Platinum Relevant") }); m_trophy_table->setHorizontalHeaderLabels(QStringList{ tr("Icon"), tr("Name"), tr("Description"), tr("Type"), tr("Status"), tr("ID"), tr("Platinum Relevant") });
m_trophy_table->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft); m_trophy_table->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft);
m_trophy_table->horizontalHeader()->setStretchLastSection(true); m_trophy_table->horizontalHeader()->setStretchLastSection(true);
m_trophy_table->horizontalHeader()->setSectionResizeMode(TrophyColumns::Icon, QHeaderView::Fixed);
m_trophy_table->verticalHeader()->setVisible(false); m_trophy_table->verticalHeader()->setVisible(false);
m_trophy_table->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); m_trophy_table->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed);
m_trophy_table->setContextMenuPolicy(Qt::CustomContextMenu); m_trophy_table->setContextMenuPolicy(Qt::CustomContextMenu);
@ -338,6 +339,24 @@ trophy_manager_dialog::trophy_manager_dialog(std::shared_ptr<gui_settings> gui_s
m_game_combo->setCurrentText(item->text()); m_game_combo->setCurrentText(item->text());
}); });
connect(&m_trophy_repaint_watcher, &QFutureWatcher<QPixmap>::finished, this, &trophy_manager_dialog::ReadjustTrophyTable);
connect(&m_trophy_repaint_watcher, &QFutureWatcher<QPixmap>::resultReadyAt, this, [this](int index)
{
if (QTableWidgetItem* icon_item = m_trophy_table->item(index, TrophyColumns::Icon))
{
icon_item->setData(Qt::DecorationRole, m_trophy_repaint_watcher.resultAt(index));
}
});
connect(&m_game_repaint_watcher, &QFutureWatcher<QPixmap>::finished, this, &trophy_manager_dialog::ReadjustGameTable);
connect(&m_game_repaint_watcher, &QFutureWatcher<QPixmap>::resultReadyAt, this, [this](int index)
{
if (QTableWidgetItem* icon_item = m_game_table->item(index, GameColumns::GameIcon))
{
icon_item->setData(Qt::DecorationRole, m_game_repaint_watcher.resultAt(index));
}
});
RepaintUI(true); RepaintUI(true);
StartTrophyLoadThreads(); StartTrophyLoadThreads();
@ -486,12 +505,33 @@ QPixmap trophy_manager_dialog::GetResizedGameIcon(int index) const
QTableWidgetItem* item = m_game_table->item(index, GameColumns::GameIcon); QTableWidgetItem* item = m_game_table->item(index, GameColumns::GameIcon);
if (!item) if (!item)
{ {
return QPixmap(); QPixmap placeholder(m_game_icon_size);
placeholder.fill(Qt::transparent);
return placeholder;
} }
const QPixmap icon = item->data(Qt::UserRole).value<QPixmap>();
QPixmap icon;
if (!item->data(GameUserRole::GamePixmapLoaded).toBool())
{
// Load game icon
const int trophy_index = item->data(GameUserRole::GameIndex).toInt();
const std::string icon_path = m_trophies_db[trophy_index]->path + "ICON0.PNG";
if (!icon.load(qstr(icon_path)))
{
gui_log.warning("Could not load trophy game icon from path %s", icon_path);
}
item->setData(GameUserRole::GamePixmapLoaded, true);
item->setData(GameUserRole::GamePixmap, icon);
}
else
{
icon = item->data(GameUserRole::GamePixmap).value<QPixmap>();
}
const qreal dpr = devicePixelRatioF(); const qreal dpr = devicePixelRatioF();
QPixmap new_icon = QPixmap(icon.size() * dpr); QPixmap new_icon(icon.size() * dpr);
new_icon.setDevicePixelRatio(dpr); new_icon.setDevicePixelRatio(dpr);
new_icon.fill(m_game_icon_color); new_icon.fill(m_game_icon_color);
@ -511,26 +551,34 @@ void trophy_manager_dialog::ResizeGameIcons()
if (m_game_combo->count() <= 0) if (m_game_combo->count() <= 0)
return; return;
if (m_game_repaint_watcher.isRunning())
{
m_game_repaint_watcher.cancel();
m_game_repaint_watcher.waitForFinished();
}
QPixmap placeholder(m_game_icon_size);
placeholder.fill(Qt::transparent);
QList<int> indices; QList<int> indices;
for (int i = 0; i < m_game_table->rowCount(); ++i) for (int i = 0; i < m_game_table->rowCount(); ++i)
{
indices.append(i); indices.append(i);
if (QTableWidgetItem* icon_item = m_game_table->item(i, GameColumns::GameIcon))
{
icon_item->setData(Qt::DecorationRole, placeholder);
}
}
const std::function<QPixmap(const int&)> get_scaled = [this](const int& i) const std::function<QPixmap(const int&)> get_scaled = [this](const int& i)
{ {
return GetResizedGameIcon(i); return GetResizedGameIcon(i);
}; };
// NOTE: Due to a Qt bug in Qt 5.15.2, QtConcurrent::blockingMapped has a high risk of deadlocking. So let's just use QtConcurrent::mapped.
const QList<QPixmap> scaled = QtConcurrent::mapped(indices, get_scaled).results();
for (int i = 0; i < m_game_table->rowCount() && i < scaled.count(); ++i)
{
QTableWidgetItem* icon_item = m_game_table->item(i, TrophyColumns::Icon);
if (icon_item)
icon_item->setData(Qt::DecorationRole, scaled[i]);
}
ReadjustGameTable(); ReadjustGameTable();
m_game_repaint_watcher.setFuture(QtConcurrent::mapped(indices, get_scaled));
} }
void trophy_manager_dialog::ResizeTrophyIcons() void trophy_manager_dialog::ResizeTrophyIcons()
@ -538,10 +586,19 @@ void trophy_manager_dialog::ResizeTrophyIcons()
if (m_game_combo->count() <= 0) if (m_game_combo->count() <= 0)
return; return;
if (m_trophy_repaint_watcher.isRunning())
{
m_trophy_repaint_watcher.cancel();
m_trophy_repaint_watcher.waitForFinished();
}
const int db_pos = m_game_combo->currentData().toInt(); const int db_pos = m_game_combo->currentData().toInt();
const qreal dpr = devicePixelRatioF(); const qreal dpr = devicePixelRatioF();
const int new_height = m_icon_height * dpr; const int new_height = m_icon_height * dpr;
QPixmap placeholder(m_icon_height, m_icon_height);
placeholder.fill(Qt::transparent);
QList<int> trophy_ids; QList<int> trophy_ids;
for (int i = 0; i < m_trophy_table->rowCount(); ++i) for (int i = 0; i < m_trophy_table->rowCount(); ++i)
{ {
@ -549,11 +606,20 @@ void trophy_manager_dialog::ResizeTrophyIcons()
{ {
trophy_ids.append(item->text().toInt()); trophy_ids.append(item->text().toInt());
} }
if (QTableWidgetItem* icon_item = m_trophy_table->item(i, TrophyColumns::Icon))
{
icon_item->setData(Qt::DecorationRole, placeholder);
}
} }
const std::function<QPixmap(const int&)> get_scaled = [this, data = m_trophies_db.at(db_pos).get(), dpr, new_height](const int& trophy_id) ReadjustTrophyTable();
const std::function<QPixmap(const int&)> get_scaled = [this, data = m_trophies_db.at(db_pos).get(), dpr, new_height](const int& trophy_id) -> QPixmap
{ {
QPixmap icon; QPixmap icon;
if (data)
{ {
std::scoped_lock lock(m_trophies_db_mtx); std::scoped_lock lock(m_trophies_db_mtx);
if (data->trophy_images.contains(trophy_id)) if (data->trophy_images.contains(trophy_id))
@ -570,7 +636,7 @@ void trophy_manager_dialog::ResizeTrophyIcons()
} }
} }
QPixmap new_icon = QPixmap(icon.size() * dpr); QPixmap new_icon(icon.size() * dpr);
new_icon.setDevicePixelRatio(dpr); new_icon.setDevicePixelRatio(dpr);
new_icon.fill(m_game_icon_color); new_icon.fill(m_game_icon_color);
@ -585,18 +651,7 @@ void trophy_manager_dialog::ResizeTrophyIcons()
return new_icon.scaledToHeight(new_height, Qt::SmoothTransformation); return new_icon.scaledToHeight(new_height, Qt::SmoothTransformation);
}; };
m_trophy_repaint_watcher.setFuture(QtConcurrent::mapped(trophy_ids, get_scaled));
// NOTE: Due to a Qt bug in Qt 5.15.2, QtConcurrent::blockingMapped has a high risk of deadlocking. So let's just use QtConcurrent::mapped.
const QList<QPixmap> scaled = QtConcurrent::mapped(trophy_ids, get_scaled).results();
for (int i = 0; i < m_trophy_table->rowCount() && i < scaled.count(); ++i)
{
QTableWidgetItem* icon_item = m_trophy_table->item(i, TrophyColumns::Icon);
if (icon_item)
icon_item->setData(Qt::DecorationRole, scaled.at(i));
}
ReadjustTrophyTable();
} }
void trophy_manager_dialog::ApplyFilter() void trophy_manager_dialog::ApplyFilter()
@ -605,10 +660,10 @@ void trophy_manager_dialog::ApplyFilter()
return; return;
const int db_pos = m_game_combo->currentData().toInt(); const int db_pos = m_game_combo->currentData().toInt();
if (db_pos + 0u >= m_trophies_db.size() || !m_trophies_db[db_pos]) if (db_pos < 0 || static_cast<usz>(db_pos) >= m_trophies_db.size() || !m_trophies_db[db_pos])
return; return;
const auto trop_usr = m_trophies_db[db_pos]->trop_usr.get(); const TROPUSRLoader* trop_usr = m_trophies_db[db_pos]->trop_usr.get();
if (!trop_usr) if (!trop_usr)
return; return;
@ -679,6 +734,16 @@ void trophy_manager_dialog::ShowContextMenu(const QPoint& pos)
void trophy_manager_dialog::StartTrophyLoadThreads() void trophy_manager_dialog::StartTrophyLoadThreads()
{ {
if (m_game_repaint_watcher.isRunning())
{
m_game_repaint_watcher.waitForFinished();
}
if (m_trophy_repaint_watcher.isRunning())
{
m_trophy_repaint_watcher.waitForFinished();
}
m_trophies_db.clear(); m_trophies_db.clear();
const QString trophy_path = qstr(vfs::get(m_trophy_dir)); const QString trophy_path = qstr(vfs::get(m_trophy_dir));
@ -738,6 +803,12 @@ void trophy_manager_dialog::StartTrophyLoadThreads()
void trophy_manager_dialog::PopulateGameTable() void trophy_manager_dialog::PopulateGameTable()
{ {
if (m_game_repaint_watcher.isRunning())
{
m_game_repaint_watcher.cancel();
m_game_repaint_watcher.waitForFinished();
}
m_game_table->setSortingEnabled(false); // Disable sorting before using setItem calls m_game_table->setSortingEnabled(false); // Disable sorting before using setItem calls
m_game_table->clearContents(); m_game_table->clearContents();
@ -749,20 +820,8 @@ void trophy_manager_dialog::PopulateGameTable()
for (usz i = 0; i < m_trophies_db.size(); ++i) for (usz i = 0; i < m_trophies_db.size(); ++i)
indices.append(static_cast<int>(i)); indices.append(static_cast<int>(i));
const std::function<QPixmap(const int&)> get_icon = [this](const int& i) QPixmap placeholder(m_game_icon_size);
{ placeholder.fill(Qt::transparent);
// Load game icon
QPixmap icon;
const std::string icon_path = m_trophies_db[i]->path + "ICON0.PNG";
if (!icon.load(qstr(icon_path)))
{
gui_log.warning("Could not load trophy game icon from path %s", icon_path);
}
return icon;
};
// NOTE: Due to a Qt bug in Qt 5.15.2, QtConcurrent::blockingMapped has a high risk of deadlocking. So let's just use QtConcurrent::mapped.
const QList<QPixmap> icons = QtConcurrent::mapped(indices, get_icon).results();
for (int i = 0; i < indices.count(); ++i) for (int i = 0; i < indices.count(); ++i)
{ {
@ -773,8 +832,10 @@ void trophy_manager_dialog::PopulateGameTable()
const QString name = qstr(m_trophies_db[i]->game_name).simplified(); const QString name = qstr(m_trophies_db[i]->game_name).simplified();
custom_table_widget_item* icon_item = new custom_table_widget_item; custom_table_widget_item* icon_item = new custom_table_widget_item;
if (icons.count() > i) icon_item->setData(Qt::DecorationRole, placeholder);
icon_item->setData(Qt::UserRole, icons[i]); icon_item->setData(GameUserRole::GameIndex, i);
icon_item->setData(GameUserRole::GamePixmapLoaded, false);
icon_item->setData(GameUserRole::GamePixmap, QPixmap());
m_game_table->setItem(i, GameColumns::GameIcon, icon_item); m_game_table->setItem(i, GameColumns::GameIcon, icon_item);
m_game_table->setItem(i, GameColumns::GameName, new custom_table_widget_item(name)); m_game_table->setItem(i, GameColumns::GameName, new custom_table_widget_item(name));
@ -785,10 +846,10 @@ void trophy_manager_dialog::PopulateGameTable()
m_game_combo->model()->sort(0, Qt::AscendingOrder); m_game_combo->model()->sort(0, Qt::AscendingOrder);
ResizeGameIcons();
m_game_table->setSortingEnabled(true); // Enable sorting only after using setItem calls m_game_table->setSortingEnabled(true); // Enable sorting only after using setItem calls
ResizeGameIcons();
gui::utils::resize_combo_box_view(m_game_combo); gui::utils::resize_combo_box_view(m_game_combo);
} }
@ -821,6 +882,9 @@ void trophy_manager_dialog::PopulateTrophyTable()
return; return;
} }
QPixmap placeholder(m_icon_height, m_icon_height);
placeholder.fill(Qt::transparent);
int i = 0; int i = 0;
for (std::shared_ptr<rXmlNode> n = trophy_base->GetChildren(); n; n = n->GetNext()) for (std::shared_ptr<rXmlNode> n = trophy_base->GetChildren(); n; n = n->GetNext())
{ {
@ -876,6 +940,7 @@ void trophy_manager_dialog::PopulateTrophyTable()
custom_table_widget_item* icon_item = new custom_table_widget_item(); custom_table_widget_item* icon_item = new custom_table_widget_item();
icon_item->setData(Qt::UserRole, hidden, true); icon_item->setData(Qt::UserRole, hidden, true);
icon_item->setData(Qt::DecorationRole, placeholder);
custom_table_widget_item* type_item = new custom_table_widget_item(trophy_type); custom_table_widget_item* type_item = new custom_table_widget_item(trophy_type);
type_item->setData(Qt::UserRole, static_cast<uint>(details.trophyGrade), true); type_item->setData(Qt::UserRole, static_cast<uint>(details.trophyGrade), true);
@ -920,7 +985,6 @@ void trophy_manager_dialog::ReadjustTrophyTable() const
// Resize and fixate icon column // Resize and fixate icon column
m_trophy_table->resizeColumnToContents(TrophyColumns::Icon); m_trophy_table->resizeColumnToContents(TrophyColumns::Icon);
m_trophy_table->horizontalHeader()->setSectionResizeMode(TrophyColumns::Icon, QHeaderView::Fixed);
// Shorten the last section to remove horizontal scrollbar if possible // Shorten the last section to remove horizontal scrollbar if possible
m_trophy_table->resizeColumnToContents(TrophyColumns::Count - 1); m_trophy_table->resizeColumnToContents(TrophyColumns::Count - 1);

View File

@ -4,6 +4,7 @@
#include <QWidget> #include <QWidget>
#include <QComboBox> #include <QComboBox>
#include <QFutureWatcher>
#include <QLabel> #include <QLabel>
#include <QPixmap> #include <QPixmap>
#include <QTableWidget> #include <QTableWidget>
@ -50,6 +51,13 @@ enum GameColumns
GameColumnsCount GameColumnsCount
}; };
enum GameUserRole
{
GameIndex = Qt::UserRole,
GamePixmapLoaded,
GamePixmap
};
class trophy_manager_dialog : public QWidget class trophy_manager_dialog : public QWidget
{ {
Q_OBJECT Q_OBJECT
@ -103,6 +111,8 @@ private:
QSplitter* m_splitter; //! Contains the game and trophy tables QSplitter* m_splitter; //! Contains the game and trophy tables
game_list* m_trophy_table; //! UI element to display trophy stuff. game_list* 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.
QFutureWatcher<QPixmap> m_game_repaint_watcher;
QFutureWatcher<QPixmap> m_trophy_repaint_watcher;
bool m_show_hidden_trophies = false; bool m_show_hidden_trophies = false;
bool m_show_unlocked_trophies = true; bool m_show_unlocked_trophies = true;