From 2b7a4f8d19ae9b1a873c0c66cb0a24507147089c Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sat, 25 Jan 2025 19:45:40 +1000 Subject: [PATCH] Qt: Avoid game list refresh on shutdown We only need to invalidate the entries that have had their play times changed, not the entire list. --- src/core/game_list.cpp | 62 +++++++++--------------- src/core/game_list.h | 5 +- src/duckstation-qt/gamelistmodel.cpp | 24 +++++++++ src/duckstation-qt/gamelistmodel.h | 1 + src/duckstation-qt/mainwindow.cpp | 4 -- src/duckstation-qt/qthost.cpp | 9 ++++ src/duckstation-qt/qthost.h | 1 + src/duckstation-regtest/regtest_host.cpp | 5 ++ 8 files changed, 68 insertions(+), 43 deletions(-) diff --git a/src/core/game_list.cpp b/src/core/game_list.cpp index c008f1b4e..c978c6ecf 100644 --- a/src/core/game_list.cpp +++ b/src/core/game_list.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin // SPDX-License-Identifier: CC-BY-NC-ND-4.0 #include "game_list.h" @@ -24,6 +24,7 @@ #include "common/path.h" #include "common/progress_callback.h" #include "common/string_util.h" +#include "common/thirdparty/SmallVector.h" #include "common/timer.h" #include "fmt/format.h" @@ -1223,50 +1224,35 @@ void GameList::AddPlayedTimeForSerial(const std::string& serial, std::time_t las static_cast(pt.total_played_time)); std::unique_lock lock(s_mutex); - for (GameList::Entry& entry : s_entries) - { - if (entry.serial != serial) - continue; + const GameDatabase::Entry* dbentry = GameDatabase::GetEntryForSerial(serial); + llvm::SmallVector changed_indices; - entry.last_played_time = pt.last_played_time; - entry.total_played_time = pt.total_played_time; - } - - // We don't need to update the disc sets if we're not running Big Picture, because Qt refreshes on system destory, - // which causes the disc set entries to get recreated. - if (FullscreenUI::IsInitialized()) + for (size_t i = 0; i < s_entries.size(); i++) { - const GameDatabase::Entry* dbentry = GameDatabase::GetEntryForSerial(serial); - if (dbentry && !dbentry->disc_set_serials.empty()) + Entry& entry = s_entries[i]; + if (entry.IsDisc()) { - for (GameList::Entry& entry : s_entries) - { - if (entry.type != EntryType::DiscSet || entry.path != dbentry->disc_set_name) - continue; + if (entry.serial != serial) + continue; - entry.last_played_time = 0; - entry.total_played_time = 0; + entry.last_played_time = pt.last_played_time; + entry.total_played_time = pt.total_played_time; + changed_indices.push_back(static_cast(i)); + } + else if (entry.IsDiscSet()) + { + if (!dbentry || entry.path != dbentry->disc_set_name) + continue; - // We shouldn't ever have duplicates for disc sets, so this should be fine. - const PlayedTimeMap ptm = LoadPlayedTimeMap(GetPlayedTimeFile()); - for (const std::string& dsserial : dbentry->disc_set_serials) - { - const auto it = ptm.find(dsserial); - if (it == ptm.end()) - continue; - - entry.last_played_time = - (entry.last_played_time == 0) ? - it->second.last_played_time : - ((it->second.last_played_time != 0) ? std::max(entry.last_played_time, it->second.last_played_time) : - entry.last_played_time); - entry.total_played_time += it->second.total_played_time; - } - - break; - } + // have to add here, because other discs are already included in the sum + entry.last_played_time = pt.last_played_time; + entry.total_played_time += add_time; + changed_indices.push_back(static_cast(i)); } } + + if (!changed_indices.empty()) + Host::OnGameListEntriesChanged(std::span(changed_indices.begin(), changed_indices.end())); } void GameList::ClearPlayedTimeForSerial(const std::string& serial) diff --git a/src/core/game_list.h b/src/core/game_list.h index beb697f99..68fc89d5e 100644 --- a/src/core/game_list.h +++ b/src/core/game_list.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin // SPDX-License-Identifier: CC-BY-NC-ND-4.0 #pragma once @@ -150,4 +150,7 @@ void RefreshGameListAsync(bool invalidate_cache); /// Cancels game list refresh, if there is one in progress. void CancelGameListRefresh(); + +/// Called when game list rows are updated. +void OnGameListEntriesChanged(std::span changed_indices); } // namespace Host diff --git a/src/duckstation-qt/gamelistmodel.cpp b/src/duckstation-qt/gamelistmodel.cpp index 887d2c726..dc0883927 100644 --- a/src/duckstation-qt/gamelistmodel.cpp +++ b/src/duckstation-qt/gamelistmodel.cpp @@ -194,6 +194,8 @@ GameListModel::GameListModel(float cover_scale, bool show_cover_titles, bool sho if (m_show_game_icons) GameList::ReloadMemcardTimestampCache(); + + connect(g_emu_thread, &EmuThread::gameListRowsChanged, this, &GameListModel::rowsChanged); } GameListModel::~GameListModel() @@ -271,6 +273,28 @@ void GameListModel::coverLoaded(const std::string& path, const QPixmap& pixmap) invalidateCoverForPath(path); } +void GameListModel::rowsChanged(const QList& rows) +{ + const QList roles_changed = {Qt::DisplayRole}; + + // try to collapse multiples + size_t start = 0; + size_t idx = 0; + const size_t size = rows.size(); + for (; idx < size;) + { + if ((idx + 1) < size && rows[idx + 1] == (rows[idx] + 1)) + { + idx++; + } + else + { + emit dataChanged(createIndex(rows[start], 0), createIndex(rows[idx], Column_Count - 1), roles_changed); + start = ++idx; + } + } +} + void GameListModel::invalidateCoverForPath(const std::string& path) { std::optional row; diff --git a/src/duckstation-qt/gamelistmodel.h b/src/duckstation-qt/gamelistmodel.h index 579b07ff7..41ebfd54a 100644 --- a/src/duckstation-qt/gamelistmodel.h +++ b/src/duckstation-qt/gamelistmodel.h @@ -89,6 +89,7 @@ Q_SIGNALS: private Q_SLOTS: void coverLoaded(const std::string& path, const QPixmap& pixmap); + void rowsChanged(const QList& rows); private: QVariant data(const QModelIndex& index, int role, const GameList::Entry* ge) const; diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index 4c5ad5001..10df551b5 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -621,10 +621,6 @@ void MainWindow::onSystemDestroyed() updateDisplayWidgetCursor(); else switchToGameListView(); - - // reload played time - if (m_game_list_widget->isShowingGameList()) - m_game_list_widget->refresh(false); } void MainWindow::onRunningGameChanged(const QString& filename, const QString& game_serial, const QString& game_title) diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp index df6a9769d..118001b99 100644 --- a/src/duckstation-qt/qthost.cpp +++ b/src/duckstation-qt/qthost.cpp @@ -1397,6 +1397,15 @@ void Host::CancelGameListRefresh() QMetaObject::invokeMethod(g_main_window, "cancelGameListRefresh", Qt::BlockingQueuedConnection); } +void Host::OnGameListEntriesChanged(std::span changed_indices) +{ + QList changed_rows; + changed_rows.reserve(changed_indices.size()); + for (const u32 row : changed_indices) + changed_rows.push_back(static_cast(row)); + emit g_emu_thread->gameListRowsChanged(changed_rows); +} + void EmuThread::loadState(const QString& filename) { if (!isCurrentThread()) diff --git a/src/duckstation-qt/qthost.h b/src/duckstation-qt/qthost.h index 0b52a85bf..2b753a10a 100644 --- a/src/duckstation-qt/qthost.h +++ b/src/duckstation-qt/qthost.h @@ -141,6 +141,7 @@ Q_SIGNALS: void systemPaused(); void systemResumed(); void gameListRefreshed(); + void gameListRowsChanged(const QList& rows_changed); std::optional onAcquireRenderWindowRequested(RenderAPI render_api, bool fullscreen, bool render_to_main, bool surfaceless, bool use_main_window_pos, Error* error); void onResizeRenderWindowRequested(qint32 width, qint32 height); diff --git a/src/duckstation-regtest/regtest_host.cpp b/src/duckstation-regtest/regtest_host.cpp index db21771d9..ebdf74157 100644 --- a/src/duckstation-regtest/regtest_host.cpp +++ b/src/duckstation-regtest/regtest_host.cpp @@ -597,6 +597,11 @@ void Host::CancelGameListRefresh() // noop } +void Host::OnGameListEntriesChanged(std::span changed_indices) +{ + // noop +} + BEGIN_HOTKEY_LIST(g_host_hotkeys) END_HOTKEY_LIST()