Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Trophy Manager loading times #10414

Merged
merged 3 commits into from Jun 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
111 changes: 59 additions & 52 deletions rpcs3/rpcs3qt/trophy_manager_dialog.cpp
Expand Up @@ -210,7 +210,7 @@ trophy_manager_dialog::trophy_manager_dialog(std::shared_ptr<gui_settings> gui_s
setLayout(all_layout);

// Make connects
connect(m_icon_slider, &QSlider::valueChanged, this, [=, this](int val)
connect(m_icon_slider, &QSlider::valueChanged, this, [this, trophy_slider_label](int val)
{
m_icon_height = val;
if (trophy_slider_label)
Expand All @@ -225,20 +225,20 @@ trophy_manager_dialog::trophy_manager_dialog(std::shared_ptr<gui_settings> gui_s
}
});

connect(m_icon_slider, &QSlider::sliderReleased, this, [&]()
connect(m_icon_slider, &QSlider::sliderReleased, this, [this]()
{
m_gui_settings->SetValue(gui::tr_icon_height, m_icon_slider->value());
});

connect(m_icon_slider, &QSlider::actionTriggered, [&](int action)
connect(m_icon_slider, &QSlider::actionTriggered, this, [this](int action)
{
if (action != QAbstractSlider::SliderNoAction && action != QAbstractSlider::SliderMove)
{ // we only want to save on mouseclicks or slider release (the other connect handles this)
m_save_icon_height = true; // actionTriggered happens before the value was changed
}
});

connect(m_game_icon_slider, &QSlider::valueChanged, this, [=, this](int val)
connect(m_game_icon_slider, &QSlider::valueChanged, this, [this, game_slider_label](int val)
{
m_game_icon_size_index = val;
m_game_icon_size = gui_settings::SizeFromSlider(val);
Expand All @@ -254,62 +254,62 @@ trophy_manager_dialog::trophy_manager_dialog(std::shared_ptr<gui_settings> gui_s
}
});

connect(m_game_icon_slider, &QSlider::sliderReleased, this, [&]()
connect(m_game_icon_slider, &QSlider::sliderReleased, this, [this]()
{
m_gui_settings->SetValue(gui::tr_game_iconSize, m_game_icon_slider->value());
});

connect(m_game_icon_slider, &QSlider::actionTriggered, [&](int action)
connect(m_game_icon_slider, &QSlider::actionTriggered, this, [this](int action)
{
if (action != QAbstractSlider::SliderNoAction && action != QAbstractSlider::SliderMove)
{ // we only want to save on mouseclicks or slider release (the other connect handles this)
m_save_game_icon_size = true; // actionTriggered happens before the value was changed
}
});

connect(check_lock_trophy, &QCheckBox::clicked, [this](bool checked)
connect(check_lock_trophy, &QCheckBox::clicked, this, [this](bool checked)
{
m_show_locked_trophies = checked;
ApplyFilter();
m_gui_settings->SetValue(gui::tr_show_locked, checked);
});

connect(check_unlock_trophy, &QCheckBox::clicked, [this](bool checked)
connect(check_unlock_trophy, &QCheckBox::clicked, this, [this](bool checked)
{
m_show_unlocked_trophies = checked;
ApplyFilter();
m_gui_settings->SetValue(gui::tr_show_unlocked, checked);
});

connect(check_hidden_trophy, &QCheckBox::clicked, [this](bool checked)
connect(check_hidden_trophy, &QCheckBox::clicked, this, [this](bool checked)
{
m_show_hidden_trophies = checked;
ApplyFilter();
m_gui_settings->SetValue(gui::tr_show_hidden, checked);
});

connect(check_bronze_trophy, &QCheckBox::clicked, [this](bool checked)
connect(check_bronze_trophy, &QCheckBox::clicked, this, [this](bool checked)
{
m_show_bronze_trophies = checked;
ApplyFilter();
m_gui_settings->SetValue(gui::tr_show_bronze, checked);
});

connect(check_silver_trophy, &QCheckBox::clicked, [this](bool checked)
connect(check_silver_trophy, &QCheckBox::clicked, this, [this](bool checked)
{
m_show_silver_trophies = checked;
ApplyFilter();
m_gui_settings->SetValue(gui::tr_show_silver, checked);
});

connect(check_gold_trophy, &QCheckBox::clicked, [this](bool checked)
connect(check_gold_trophy, &QCheckBox::clicked, this, [this](bool checked)
{
m_show_gold_trophies = checked;
ApplyFilter();
m_gui_settings->SetValue(gui::tr_show_gold, checked);
});

connect(check_platinum_trophy, &QCheckBox::clicked, [this](bool checked)
connect(check_platinum_trophy, &QCheckBox::clicked, this, [this](bool checked)
{
m_show_platinum_trophies = checked;
ApplyFilter();
Expand All @@ -318,13 +318,13 @@ trophy_manager_dialog::trophy_manager_dialog(std::shared_ptr<gui_settings> gui_s

connect(m_trophy_table, &QTableWidget::customContextMenuRequested, this, &trophy_manager_dialog::ShowContextMenu);

connect(m_game_combo, &QComboBox::currentTextChanged, [this]
connect(m_game_combo, &QComboBox::currentTextChanged, this, [this]
{
PopulateTrophyTable();
ApplyFilter();
});

connect(m_game_table, &QTableWidget::itemSelectionChanged, [this]
connect(m_game_table, &QTableWidget::itemSelectionChanged, this, [this]
{
if (m_game_table->selectedItems().isEmpty())
{
Expand Down Expand Up @@ -356,11 +356,11 @@ bool trophy_manager_dialog::LoadTrophyFolderToDB(const std::string& trop_name)

game_trophy_data->path = vfs::get(trophyPath + "/");
game_trophy_data->trop_usr.reset(new TROPUSRLoader());
const std::string trophyUsrPath = trophyPath + "/TROPUSR.DAT";
const std::string trophyConfPath = trophyPath + "/TROPCONF.SFM";
const bool success = game_trophy_data->trop_usr->Load(trophyUsrPath, trophyConfPath).success;
const std::string tropusr_path = trophyPath + "/TROPUSR.DAT";
const std::string tropconf_path = trophyPath + "/TROPCONF.SFM";
const bool success = game_trophy_data->trop_usr->Load(tropusr_path, tropconf_path).success;

fs::file config(vfs::get(trophyConfPath));
fs::file config(vfs::get(tropconf_path));

if (!success || !config)
{
Expand All @@ -372,30 +372,14 @@ bool trophy_manager_dialog::LoadTrophyFolderToDB(const std::string& trop_name)

if (trophy_count == 0)
{
gui_log.error("Warning game %s in trophy folder %s usr file reports zero trophies. Cannot load in trophy manager.", game_trophy_data->game_name, game_trophy_data->path);
gui_log.error("Warning game %s in trophy folder %s usr file reports zero trophies. Cannot load in trophy manager.", game_trophy_data->game_name, game_trophy_data->path);
return false;
}

for (u32 trophy_id = 0; trophy_id < trophy_count; ++trophy_id)
{
// Figure out how many zeros are needed for padding. (either 0, 1, or 2)
QString padding = "";
if (trophy_id < 10)
{
padding = "00";
}
else if (trophy_id < 100)
{
padding = "0";
}

QPixmap trophy_icon;
const QString path = qstr(game_trophy_data->path) + "TROP" + padding + QString::number(trophy_id) + ".PNG";
if (!trophy_icon.load(path))
{
gui_log.error("Failed to load trophy icon for trophy %d %s", trophy_id, game_trophy_data->path);
}
game_trophy_data->trophy_images.emplace_back(std::move(trophy_icon));
// A trophy icon has 3 digits from 000 to 999, for example TROP001.PNG
game_trophy_data->trophy_image_paths[trophy_id] = qstr(fmt::format("%sTROP%03d.PNG", game_trophy_data->path, trophy_id));
}

// Get game name
Expand All @@ -414,7 +398,10 @@ bool trophy_manager_dialog::LoadTrophyFolderToDB(const std::string& trop_name)
}
}

m_trophies_db.push_back(std::move(game_trophy_data));
{
std::scoped_lock lock(m_trophies_db_mtx);
m_trophies_db.push_back(std::move(game_trophy_data));
}

config.release();
return true;
Expand Down Expand Up @@ -547,20 +534,33 @@ void trophy_manager_dialog::ResizeTrophyIcons()
const qreal dpr = devicePixelRatioF();
const int new_height = m_icon_height * dpr;

QList<int> indices;
QList<int> trophy_ids;
for (int i = 0; i < m_trophy_table->rowCount(); ++i)
indices.append(i);
{
if (QTableWidgetItem* item = m_trophy_table->item(i, TrophyColumns::Id))
{
trophy_ids.append(item->text().toInt());
}
}

const std::function<QPixmap(const int&)> get_scaled = [this, db_pos, dpr, new_height](const int& i)
const std::function<QPixmap(const int&)> get_scaled = [this, data = m_trophies_db.at(db_pos).get(), dpr, new_height](const int& trophy_id)
{
QTableWidgetItem* item = m_trophy_table->item(i, TrophyColumns::Id);
QTableWidgetItem* icon_item = m_trophy_table->item(i, TrophyColumns::Icon);
if (!item || !icon_item)
QPixmap icon;
{
return QPixmap();
std::scoped_lock lock(m_trophies_db_mtx);
if (data->trophy_images.contains(trophy_id))
{
icon = data->trophy_images[trophy_id];
}
else if (const QString& path = data->trophy_image_paths[trophy_id]; !icon.load(path))
{
gui_log.error("Failed to load trophy icon for trophy %d (icon='%s')", trophy_id, sstr(path));
}
else
{
data->trophy_images[trophy_id] = icon;
}
}
const int trophy_id = item->text().toInt();
const QPixmap icon = m_trophies_db[db_pos]->trophy_images[trophy_id];

QPixmap new_icon = QPixmap(icon.size() * dpr);
new_icon.setDevicePixelRatio(dpr);
Expand All @@ -577,13 +577,18 @@ void trophy_manager_dialog::ResizeTrophyIcons()
return new_icon.scaledToHeight(new_height, Qt::SmoothTransformation);
};

QList<QPixmap> scaled = QtConcurrent::blockingMapped<QList<QPixmap>>(indices, get_scaled);

for (int i = 0; i < m_trophy_table->rowCount() && i < scaled.count(); ++i)
// NOTE: Due to a Qt bug, QtConcurrent::blockingMapped has a high risk of deadlocking
// during the QPixmap operations in get_scaled. So let's just use QtConcurrent::mapped.
QFutureWatcher<QPixmap> watcher;
watcher.setFuture(QtConcurrent::mapped(trophy_ids, get_scaled));
watcher.waitForFinished();

for (int i = 0; i < m_trophy_table->rowCount() && i < watcher.future().resultCount(); ++i)
{
QTableWidgetItem* icon_item = m_trophy_table->item(i, TrophyColumns::Icon);
if (icon_item)
icon_item->setData(Qt::DecorationRole, scaled[i]);
icon_item->setData(Qt::DecorationRole, watcher.future().resultAt(i));
}

ReadjustTrophyTable();
Expand Down Expand Up @@ -657,7 +662,7 @@ void trophy_manager_dialog::ShowContextMenu(const QPoint& pos)

const int db_ind = m_game_combo->currentData().toInt();

connect(show_trophy_dir, &QAction::triggered, [=, this]()
connect(show_trophy_dir, &QAction::triggered, this, [this, db_ind]()
{
const QString path = qstr(m_trophies_db[db_ind]->path);
QDesktopServices::openUrl(QUrl::fromLocalFile(path));
Expand Down Expand Up @@ -763,6 +768,8 @@ void trophy_manager_dialog::PopulateGameTable()
m_game_combo->addItem(name, i);
}

m_game_combo->model()->sort(0, Qt::AscendingOrder);

ResizeGameIcons();

m_game_table->setSortingEnabled(true); // Enable sorting only after using setItem calls
Expand Down
6 changes: 5 additions & 1 deletion rpcs3/rpcs3qt/trophy_manager_dialog.h
Expand Up @@ -11,6 +11,8 @@
#include <QSplitter>

#include <memory>
#include <mutex>
#include <unordered_map>

class game_list;
class gui_settings;
Expand All @@ -20,7 +22,8 @@ struct GameTrophiesData
{
std::unique_ptr<TROPUSRLoader> trop_usr;
rXmlDocument trop_config; // I'd like to use unique but the protocol inside of the function passes around shared pointers..
std::vector<QPixmap> trophy_images;
std::unordered_map<int, QPixmap> trophy_images; // Cache trophy images to avoid loading from disk as much as possible.
std::unordered_map<int, QString> trophy_image_paths;
std::string game_name;
std::string path;
};
Expand Down Expand Up @@ -94,6 +97,7 @@ private Q_SLOTS:
std::shared_ptr<gui_settings> m_gui_settings;

std::vector<std::unique_ptr<GameTrophiesData>> m_trophies_db; //! Holds all the trophy information.
std::mutex m_trophies_db_mtx;
QComboBox* m_game_combo; //! Lets you choose a game
QLabel* m_game_progress; //! Shows you the current game's progress
QSplitter* m_splitter; //! Contains the game and trophy tables
Expand Down