From c57be0efca6c3c5a8b0d9870b73fe4fa738d265b Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Sat, 9 Mar 2024 16:46:41 -0500 Subject: [PATCH] Refactor Leaderboard widget to allow partial updates Similarly to the Progress widget (though without the separate object for each box, because each Leaderboard unit is just three text fields stacked vertically), AchievementLeaderboardWidget.UpdateData will now accept three options: destroy all and rebuild, update all, or update a set of rows. As part of this, AchievementManager::GetLeaderboardsInfo has been refactored to GetLeaderboardInfo to return a single leaderboard for the ID passed in. --- Source/Core/Core/AchievementManager.cpp | 8 +- Source/Core/Core/AchievementManager.h | 2 +- .../AchievementLeaderboardWidget.cpp | 169 +++++++++++------- .../AchievementLeaderboardWidget.h | 7 +- .../Achievements/AchievementsWindow.cpp | 2 +- 5 files changed, 118 insertions(+), 70 deletions(-) diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index a40df12104..f73072fa59 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -358,10 +358,12 @@ AchievementManager::GetAchievementProgress(AchievementId achievement_id, u32* va return ResponseType::SUCCESS; } -const std::unordered_map& -AchievementManager::GetLeaderboardsInfo() const +const AchievementManager::LeaderboardStatus* +AchievementManager::GetLeaderboardInfo(AchievementManager::AchievementId leaderboard_id) const { - return m_leaderboard_map; + if (m_leaderboard_map.count(leaderboard_id) < 1) + return nullptr; + return &m_leaderboard_map.at(leaderboard_id); } AchievementManager::RichPresence AchievementManager::GetRichPresence() const diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index 6cd7d39a90..785c463fbf 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -143,7 +143,7 @@ public: const UnlockStatus* GetUnlockStatus(AchievementId achievement_id) const; AchievementManager::ResponseType GetAchievementProgress(AchievementId achievement_id, u32* value, u32* target); - const std::unordered_map& GetLeaderboardsInfo() const; + const LeaderboardStatus* GetLeaderboardInfo(AchievementId leaderboard_id) const; RichPresence GetRichPresence() const; bool IsDisabled() const { return m_disabled; }; void SetDisabled(bool disabled); diff --git a/Source/Core/DolphinQt/Achievements/AchievementLeaderboardWidget.cpp b/Source/Core/DolphinQt/Achievements/AchievementLeaderboardWidget.cpp index 13ab205f9c..e9705caffa 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementLeaderboardWidget.cpp +++ b/Source/Core/DolphinQt/Achievements/AchievementLeaderboardWidget.cpp @@ -24,10 +24,7 @@ AchievementLeaderboardWidget::AchievementLeaderboardWidget(QWidget* parent) : QW m_common_box = new QGroupBox(); m_common_layout = new QGridLayout(); - { - std::lock_guard lg{AchievementManager::GetInstance().GetLock()}; - UpdateData(); - } + UpdateData(true); m_common_box->setLayout(m_common_layout); @@ -38,77 +35,121 @@ AchievementLeaderboardWidget::AchievementLeaderboardWidget(QWidget* parent) : QW setLayout(layout); } -void AchievementLeaderboardWidget::UpdateData() +void AchievementLeaderboardWidget::UpdateData(bool clean_all) { - ClearLayoutRecursively(m_common_layout); - - if (!AchievementManager::GetInstance().IsGameLoaded()) - return; - const auto& leaderboards = AchievementManager::GetInstance().GetLeaderboardsInfo(); - int row = 0; - for (const auto& board_row : leaderboards) + if (clean_all) { - const AchievementManager::LeaderboardStatus& board = board_row.second; - QLabel* a_title = new QLabel(QString::fromStdString(board.name)); - QLabel* a_description = new QLabel(QString::fromStdString(board.description)); - QVBoxLayout* a_col_left = new QVBoxLayout(); - a_col_left->addWidget(a_title); - a_col_left->addWidget(a_description); - if (row > 0) + ClearLayoutRecursively(m_common_layout); + + auto& instance = AchievementManager::GetInstance(); + if (!instance.IsGameLoaded()) + return; + + rc_api_fetch_game_data_response_t* game_data; { - QFrame* a_divider = new QFrame(); - a_divider->setFrameShape(QFrame::HLine); - m_common_layout->addWidget(a_divider, row - 1, 0); + std::lock_guard lg{AchievementManager::GetInstance().GetLock()}; + game_data = instance.GetGameData(); } - m_common_layout->addLayout(a_col_left, row, 0); - // Each leaderboard entry is displayed with four values. These are *generally* intended to be, - // in order, the first place entry, the entry one above the player, the player's entry, and - // the entry one below the player. - // Edge cases: - // * If there are fewer than four entries in the leaderboard, all entries will be shown in - // order and the remainder of the list will be padded with empty values. - // * If the player does not currently have a score in the leaderboard, or is in the top 3, - // the four slots will be the top four players in order. - // * If the player is last place, the player will be in the fourth slot, and the second and - // third slots will be the two players above them. The first slot will always be first place. - std::array to_display{1, 2, 3, 4}; - if (board.player_index > to_display.size() - 1) + for (u32 row = 0; row < game_data->num_leaderboards; row += 2) { - // If the rank one below than the player is found, offset = 1. - u32 offset = static_cast(board.entries.count(board.player_index + 1)); - // Example: player is 10th place but not last - // to_display = {1, 10-3+1+1, 10-3+1+2, 10-3+1+3} = {1, 9, 10, 11} - // Example: player is 15th place and is last - // to_display = {1, 15-3+0+1, 15-3+0+2, 15-3+0+3} = {1, 13, 14, 15} - for (size_t i = 1; i < to_display.size(); ++i) - to_display[i] = board.player_index - 3 + offset + static_cast(i); - } - for (size_t i = 0; i < to_display.size(); ++i) - { - u32 index = to_display[i]; - QLabel* a_rank = new QLabel(QStringLiteral("---")); - QLabel* a_username = new QLabel(QStringLiteral("---")); - QLabel* a_score = new QLabel(QStringLiteral("---")); - const auto it = board.entries.find(index); - if (it != board.entries.end()) - { - a_rank->setText(tr("Rank %1").arg(it->second.rank)); - a_username->setText(QString::fromStdString(it->second.username)); - a_score->setText(QString::fromUtf8(it->second.score.data())); - } - QVBoxLayout* a_col = new QVBoxLayout(); - a_col->addWidget(a_rank); - a_col->addWidget(a_username); - a_col->addWidget(a_score); + const auto* leaderboard = game_data->leaderboards + (row / 2); + m_leaderboard_order[leaderboard->id] = row; + QLabel* a_title = new QLabel(QString::fromUtf8(leaderboard->title)); + QLabel* a_description = new QLabel(QString::fromUtf8(leaderboard->description)); + QVBoxLayout* a_col_left = new QVBoxLayout(); + a_col_left->addWidget(a_title); + a_col_left->addWidget(a_description); if (row > 0) { QFrame* a_divider = new QFrame(); a_divider->setFrameShape(QFrame::HLine); - m_common_layout->addWidget(a_divider, row - 1, static_cast(i) + 1); + m_common_layout->addWidget(a_divider, row - 1, 0); + } + m_common_layout->addLayout(a_col_left, row, 0); + for (size_t ix = 0; ix < 4; ix++) + { + QVBoxLayout* a_col = new QVBoxLayout(); + for (size_t jx = 0; jx < 3; jx++) + a_col->addWidget(new QLabel(QStringLiteral("---"))); + if (row > 0) + { + QFrame* a_divider = new QFrame(); + a_divider->setFrameShape(QFrame::HLine); + m_common_layout->addWidget(a_divider, row - 1, static_cast(ix) + 1); + } + m_common_layout->addLayout(a_col, row, static_cast(ix) + 1); } - m_common_layout->addLayout(a_col, row, static_cast(i) + 1); } - row += 2; + } + for (auto row : m_leaderboard_order) + { + UpdateRow(row.second); + } +} + +void AchievementLeaderboardWidget::UpdateData( + const std::set& update_ids) +{ + for (auto row : m_leaderboard_order) + { + if (update_ids.contains(row.first)) + { + UpdateRow(row.second); + } + } +} + +void AchievementLeaderboardWidget::UpdateRow(AchievementManager::AchievementId leaderboard_id) +{ + const auto leaderboard_itr = m_leaderboard_order.find(leaderboard_id); + if (leaderboard_itr == m_leaderboard_order.end()) + return; + const int row = leaderboard_itr->second; + + const AchievementManager::LeaderboardStatus* board; + { + std::lock_guard lg{AchievementManager::GetInstance().GetLock()}; + board = AchievementManager::GetInstance().GetLeaderboardInfo(leaderboard_id); + } + if (!board) + return; + + // Each leaderboard entry is displayed with four values. These are *generally* intended to be, + // in order, the first place entry, the entry one above the player, the player's entry, and + // the entry one below the player. + // Edge cases: + // * If there are fewer than four entries in the leaderboard, all entries will be shown in + // order and the remainder of the list will be padded with empty values. + // * If the player does not currently have a score in the leaderboard, or is in the top 3, + // the four slots will be the top four players in order. + // * If the player is last place, the player will be in the fourth slot, and the second and + // third slots will be the two players above them. The first slot will always be first place. + std::array to_display{1, 2, 3, 4}; + if (board->player_index > to_display.size() - 1) + { + // If the rank one below than the player is found, offset = 1. + u32 offset = static_cast(board->entries.count(board->player_index + 1)); + // Example: player is 10th place but not last + // to_display = {1, 10-3+1+1, 10-3+1+2, 10-3+1+3} = {1, 9, 10, 11} + // Example: player is 15th place and is last + // to_display = {1, 15-3+0+1, 15-3+0+2, 15-3+0+3} = {1, 13, 14, 15} + for (size_t ix = 1; ix < to_display.size(); ++ix) + to_display[ix] = board->player_index - 3 + offset + static_cast(ix); + } + for (size_t ix = 0; ix < to_display.size(); ++ix) + { + const auto it = board->entries.find(to_display[ix]); + if (it != board->entries.end()) + { + QVBoxLayout* a_col = new QVBoxLayout(); + a_col->addWidget(new QLabel(tr("Rank %1").arg(it->second.rank))); + a_col->addWidget(new QLabel(QString::fromStdString(it->second.username))); + a_col->addWidget(new QLabel(QString::fromUtf8(it->second.score.data()))); + auto old_item = m_common_layout->itemAtPosition(row, static_cast(ix) + 1); + m_common_layout->removeItem(old_item); + ClearLayoutRecursively(static_cast(old_item)); + m_common_layout->addLayout(a_col, row, static_cast(ix) + 1); + } } } diff --git a/Source/Core/DolphinQt/Achievements/AchievementLeaderboardWidget.h b/Source/Core/DolphinQt/Achievements/AchievementLeaderboardWidget.h index 055ea6ab3f..cafd2483bd 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementLeaderboardWidget.h +++ b/Source/Core/DolphinQt/Achievements/AchievementLeaderboardWidget.h @@ -6,6 +6,8 @@ #ifdef USE_RETRO_ACHIEVEMENTS #include +#include "Core/AchievementManager.h" + class QGroupBox; class QGridLayout; @@ -14,11 +16,14 @@ class AchievementLeaderboardWidget final : public QWidget Q_OBJECT public: explicit AchievementLeaderboardWidget(QWidget* parent); - void UpdateData(); + void UpdateData(bool clean_all); + void UpdateData(const std::set& update_ids); + void UpdateRow(AchievementManager::AchievementId leaderboard_id); private: QGroupBox* m_common_box; QGridLayout* m_common_layout; + std::map m_leaderboard_order; }; #endif // USE_RETRO_ACHIEVEMENTS diff --git a/Source/Core/DolphinQt/Achievements/AchievementsWindow.cpp b/Source/Core/DolphinQt/Achievements/AchievementsWindow.cpp index a95e844e29..6f26811a49 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementsWindow.cpp +++ b/Source/Core/DolphinQt/Achievements/AchievementsWindow.cpp @@ -83,7 +83,7 @@ void AchievementsWindow::UpdateData() m_settings_widget->UpdateData(); m_progress_widget->UpdateData(true); m_tab_widget->setTabVisible(1, is_game_loaded); - m_leaderboard_widget->UpdateData(); + m_leaderboard_widget->UpdateData(true); m_tab_widget->setTabVisible(2, is_game_loaded); } update();