Add game list refresh button (#126)

nice race condition

Signed-off-by: swurl <swurl@swurl.xyz>

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/126
Co-authored-by: swurl <swurl@swurl.xyz>
Co-committed-by: swurl <swurl@swurl.xyz>
This commit is contained in:
swurl 2025-05-29 08:20:11 +00:00 committed by crueter
parent b78089e978
commit 7f16f12202
10 changed files with 167 additions and 29 deletions

View file

@ -38,6 +38,18 @@ QPushButton#RendererStatusBarButton:!checked {
color: #0066ff; color: #0066ff;
} }
QPushButton#RefreshButton {
color: #656565;
border: 1px solid transparent;
background-color: transparent;
padding: 0px 3px 0px 3px;
text-align: center;
}
QPushButton#RefreshButton:hover {
border: 1px solid #76797C;
}
QPushButton#GPUStatusBarButton { QPushButton#GPUStatusBarButton {
color: #656565; color: #656565;
border: 1px solid transparent; border: 1px solid transparent;

View file

@ -42,6 +42,18 @@ QPushButton#RendererStatusBarButton:!checked {
color: #00ccdd; color: #00ccdd;
} }
QPushButton#RefreshButton {
color: #656565;
border: 1px solid transparent;
background-color: transparent;
padding: 0px 3px 0px 3px;
text-align: center;
}
QPushButton#RefreshButton:hover {
border: 1px solid #76797C;
}
QPushButton#GPUStatusBarButton { QPushButton#GPUStatusBarButton {
color: #656565; color: #656565;
border: 1px solid transparent; border: 1px solid transparent;

View file

@ -1283,6 +1283,19 @@ QPushButton#RendererStatusBarButton:!checked {
color: #00ccdd; color: #00ccdd;
} }
QPushButton#RefreshButton {
min-width: 0px;
color: #656565;
border: 1px solid transparent;
background-color: transparent;
padding: 0px 3px 0px 3px;
text-align: center;
}
QPushButton#RefreshButton:hover {
border: 1px solid #76797C;
}
QPushButton#GPUStatusBarButton { QPushButton#GPUStatusBarButton {
min-width: 0px; min-width: 0px;
color: #656565; color: #656565;

View file

@ -2186,6 +2186,19 @@ QPushButton#RendererStatusBarButton:!checked {
color: #00ccdd; color: #00ccdd;
} }
QPushButton#RefreshButton {
min-width: 0px;
color: #656565;
border: 1px solid transparent;
background-color: transparent;
padding: 0px 3px 0px 3px;
text-align: center;
}
QPushButton#RefreshButton:hover {
border: 1px solid #76797C;
}
QPushButton#GPUStatusBarButton { QPushButton#GPUStatusBarButton {
min-width: 0px; min-width: 0px;
color: #656565; color: #656565;

View file

@ -854,7 +854,8 @@ QStandardItemModel* GameList::GetModel() const {
return item_model; return item_model;
} }
void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) { void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs, const bool cached)
{
tree_view->setEnabled(false); tree_view->setEnabled(false);
// Update the columns in case UISettings has changed // Update the columns in case UISettings has changed
@ -871,8 +872,13 @@ void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) {
item_model->removeRows(0, item_model->rowCount()); item_model->removeRows(0, item_model->rowCount());
search_field->clear(); search_field->clear();
current_worker = std::make_unique<GameListWorker>(vfs, provider, game_dirs, compatibility_list, current_worker = std::make_unique<GameListWorker>(vfs,
play_time_manager, system); provider,
game_dirs,
compatibility_list,
play_time_manager,
system,
cached);
// Get events from the worker as data becomes available // Get events from the worker as data becomes available
connect(current_worker.get(), &GameListWorker::DataAvailable, this, &GameList::WorkerEvent, connect(current_worker.get(), &GameListWorker::DataAvailable, this, &GameList::WorkerEvent,
@ -901,7 +907,16 @@ const QStringList GameList::supported_file_extensions = {
QStringLiteral("nso"), QStringLiteral("nro"), QStringLiteral("nca"), QStringLiteral("nso"), QStringLiteral("nro"), QStringLiteral("nca"),
QStringLiteral("xci"), QStringLiteral("nsp"), QStringLiteral("kip")}; QStringLiteral("xci"), QStringLiteral("nsp"), QStringLiteral("kip")};
void GameList::RefreshGameDirectory() { void GameList::ForceRefreshGameDirectory()
{
if (!UISettings::values.game_dirs.empty() && current_worker != nullptr) {
LOG_INFO(Frontend, "Force-reloading game list per user request.");
PopulateAsync(UISettings::values.game_dirs, false);
}
}
void GameList::RefreshGameDirectory()
{
if (!UISettings::values.game_dirs.empty() && current_worker != nullptr) { if (!UISettings::values.game_dirs.empty() && current_worker != nullptr) {
LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list."); LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list.");
PopulateAsync(UISettings::values.game_dirs); PopulateAsync(UISettings::values.game_dirs);

View file

@ -94,7 +94,7 @@ public:
bool IsEmpty() const; bool IsEmpty() const;
void LoadCompatibilityList(); void LoadCompatibilityList();
void PopulateAsync(QVector<UISettings::GameDir>& game_dirs); void PopulateAsync(QVector<UISettings::GameDir>& game_dirs, const bool cached = true);
void SaveInterfaceLayout(); void SaveInterfaceLayout();
void LoadInterfaceLayout(); void LoadInterfaceLayout();
@ -106,6 +106,10 @@ public:
static const QStringList supported_file_extensions; static const QStringList supported_file_extensions;
public slots:
void ForceRefreshGameDirectory();
void RefreshGameDirectory();
signals: signals:
void BootGame(const QString& game_path, StartGameType type); void BootGame(const QString& game_path, StartGameType type);
void GameChosen(const QString& game_path, const u64 title_id = 0); void GameChosen(const QString& game_path, const u64 title_id = 0);
@ -147,8 +151,6 @@ private:
private: private:
void ValidateEntry(const QModelIndex& item); void ValidateEntry(const QModelIndex& item);
void RefreshGameDirectory();
void ToggleFavorite(u64 program_id); void ToggleFavorite(u64 program_id);
void AddFavorite(u64 program_id); void AddFavorite(u64 program_id);
void RemoveFavorite(u64 program_id); void RemoveFavorite(u64 program_id);

View file

@ -191,12 +191,17 @@ QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager,
return out; return out;
} }
QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::string& name, QList<QStandardItem*> MakeGameListEntry(const std::string& path,
const std::size_t size, const std::vector<u8>& icon, const std::string& name,
Loader::AppLoader& loader, u64 program_id, const std::size_t size,
const std::vector<u8>& icon,
Loader::AppLoader& loader,
u64 program_id,
const CompatibilityList& compatibility_list, const CompatibilityList& compatibility_list,
const PlayTime::PlayTimeManager& play_time_manager, const PlayTime::PlayTimeManager& play_time_manager,
const FileSys::PatchManager& patch) { const FileSys::PatchManager& patch,
const bool cached)
{
const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
// The game list uses this as compatibility number for untested games // The game list uses this as compatibility number for untested games
@ -217,10 +222,17 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri
new GameListItemPlayTime(play_time_manager.GetPlayTime(program_id)), new GameListItemPlayTime(play_time_manager.GetPlayTime(program_id)),
}; };
const auto patch_versions = GetGameListCachedObject( QString patch_versions;
if (cached) {
patch_versions = GetGameListCachedObject(
fmt::format("{:016X}", patch.GetTitleID()), "pv.txt", [&patch, &loader] { fmt::format("{:016X}", patch.GetTitleID()), "pv.txt", [&patch, &loader] {
return FormatPatchNameVersions(patch, loader, loader.IsRomFSUpdatable()); return FormatPatchNameVersions(patch, loader, loader.IsRomFSUpdatable());
}); });
} else {
patch_versions = FormatPatchNameVersions(patch, loader, loader.IsRomFSUpdatable());
}
list.insert(2, new GameListItem(patch_versions)); list.insert(2, new GameListItem(patch_versions));
return list; return list;
@ -232,10 +244,16 @@ GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs_,
QVector<UISettings::GameDir>& game_dirs_, QVector<UISettings::GameDir>& game_dirs_,
const CompatibilityList& compatibility_list_, const CompatibilityList& compatibility_list_,
const PlayTime::PlayTimeManager& play_time_manager_, const PlayTime::PlayTimeManager& play_time_manager_,
Core::System& system_) Core::System& system_,
: vfs{std::move(vfs_)}, provider{provider_}, game_dirs{game_dirs_}, const bool cached_)
compatibility_list{compatibility_list_}, play_time_manager{play_time_manager_}, system{ : vfs{std::move(vfs_)}
system_} { , provider{provider_}
, game_dirs{game_dirs_}
, compatibility_list{compatibility_list_}
, play_time_manager{play_time_manager_}
, system{system_}
, cached{cached_}
{
// We want the game list to manage our lifetime. // We want the game list to manage our lifetime.
setAutoDelete(false); setAutoDelete(false);
} }
@ -323,13 +341,22 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) {
const PatchManager patch{program_id, system.GetFileSystemController(), const PatchManager patch{program_id, system.GetFileSystemController(),
system.GetContentProvider()}; system.GetContentProvider()};
LOG_INFO(Frontend, "PatchManager initiated for id {:X}", program_id);
const auto control = cache.GetEntry(game.title_id, ContentRecordType::Control); const auto control = cache.GetEntry(game.title_id, ContentRecordType::Control);
if (control != nullptr) { if (control != nullptr) {
GetMetadataFromControlNCA(patch, *control, icon, name); GetMetadataFromControlNCA(patch, *control, icon, name);
} }
auto entry = MakeGameListEntry(file->GetFullPath(), name, file->GetSize(), icon, *loader, auto entry = MakeGameListEntry(file->GetFullPath(),
program_id, compatibility_list, play_time_manager, patch); name,
file->GetSize(),
icon,
*loader,
program_id,
compatibility_list,
play_time_manager,
patch,
cached);
RecordEvent([=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); }); RecordEvent([=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); });
} }
} }
@ -404,9 +431,16 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
const FileSys::PatchManager patch{id, system.GetFileSystemController(), const FileSys::PatchManager patch{id, system.GetFileSystemController(),
system.GetContentProvider()}; system.GetContentProvider()};
auto entry = MakeGameListEntry( auto entry = MakeGameListEntry(physical_name,
physical_name, name, Common::FS::GetSize(physical_name), icon, *loader, name,
id, compatibility_list, play_time_manager, patch); Common::FS::GetSize(physical_name),
icon,
*loader,
id,
compatibility_list,
play_time_manager,
patch,
cached);
RecordEvent( RecordEvent(
[=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); }); [=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); });
@ -421,9 +455,16 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
const FileSys::PatchManager patch{program_id, system.GetFileSystemController(), const FileSys::PatchManager patch{program_id, system.GetFileSystemController(),
system.GetContentProvider()}; system.GetContentProvider()};
auto entry = MakeGameListEntry( auto entry = MakeGameListEntry(physical_name,
physical_name, name, Common::FS::GetSize(physical_name), icon, *loader, name,
program_id, compatibility_list, play_time_manager, patch); Common::FS::GetSize(physical_name),
icon,
*loader,
program_id,
compatibility_list,
play_time_manager,
patch,
cached);
RecordEvent( RecordEvent(
[=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); }); [=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); });

View file

@ -14,6 +14,8 @@
#include <QString> #include <QString>
#include "common/thread.h" #include "common/thread.h"
#include "core/file_sys/registered_cache.h"
#include "uisettings.h"
#include "yuzu/compatibility_list.h" #include "yuzu/compatibility_list.h"
#include "yuzu/play_time_manager.h" #include "yuzu/play_time_manager.h"
@ -22,6 +24,7 @@ class System;
} }
class GameList; class GameList;
class GameListDir;
class QStandardItem; class QStandardItem;
namespace FileSys { namespace FileSys {
@ -42,7 +45,8 @@ public:
QVector<UISettings::GameDir>& game_dirs_, QVector<UISettings::GameDir>& game_dirs_,
const CompatibilityList& compatibility_list_, const CompatibilityList& compatibility_list_,
const PlayTime::PlayTimeManager& play_time_manager_, const PlayTime::PlayTimeManager& play_time_manager_,
Core::System& system_); Core::System& system_,
const bool cached = true);
~GameListWorker() override; ~GameListWorker() override;
/// Starts the processing of directory tree information. /// Starts the processing of directory tree information.
@ -91,4 +95,6 @@ private:
Common::Event processing_completed; Common::Event processing_completed;
Core::System& system; Core::System& system;
const bool cached;
}; };

View file

@ -449,7 +449,8 @@ GMainWindow::GMainWindow(bool has_broken_vulkan)
OnCheckFirmwareDecryption(); OnCheckFirmwareDecryption();
game_list->LoadCompatibilityList(); game_list->LoadCompatibilityList();
game_list->PopulateAsync(UISettings::values.game_dirs); // force reload on first load to ensure add-ons get updated
game_list->PopulateAsync(UISettings::values.game_dirs, false);
// make sure menubar has the arrow cursor instead of inheriting from this // make sure menubar has the arrow cursor instead of inheriting from this
ui->menubar->setCursor(QCursor()); ui->menubar->setCursor(QCursor());
@ -508,6 +509,10 @@ GMainWindow::GMainWindow(bool has_broken_vulkan)
QStringList args = QApplication::arguments(); QStringList args = QApplication::arguments();
if (args.size() < 2) { if (args.size() < 2) {
// Some moron added a race condition to the status bar
// so now we have to make this completely unnecessary call
// to prevent the UI from blowing up.
UpdateUITheme();
return; return;
} }
@ -1297,6 +1302,15 @@ void GMainWindow::InitializeWidgets() {
}); });
statusBar()->insertPermanentWidget(0, renderer_status_button); statusBar()->insertPermanentWidget(0, renderer_status_button);
// Setup Refresh Button
refresh_button = new QPushButton();
refresh_button->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));
refresh_button->setObjectName(QStringLiteral("RefreshButton"));
refresh_button->setFocusPolicy(Qt::NoFocus);
connect(refresh_button, &QPushButton::clicked, this, &GMainWindow::OnGameListRefresh);
statusBar()->insertPermanentWidget(0, refresh_button);
statusBar()->setVisible(true); statusBar()->setVisible(true);
setStyleSheet(QStringLiteral("QStatusBar::item{border: none;}")); setStyleSheet(QStringLiteral("QStatusBar::item{border: none;}"));
} }
@ -2108,6 +2122,7 @@ void GMainWindow::BootGame(const QString& filename, Service::AM::FrontendAppletP
} }
status_bar_update_timer.start(500); status_bar_update_timer.start(500);
renderer_status_button->setDisabled(true); renderer_status_button->setDisabled(true);
refresh_button->setDisabled(true);
if (UISettings::values.hide_mouse || Settings::values.mouse_panning) { if (UISettings::values.hide_mouse || Settings::values.mouse_panning) {
render_window->installEventFilter(render_window); render_window->installEventFilter(render_window);
@ -2280,6 +2295,7 @@ void GMainWindow::OnEmulationStopped() {
game_fps_label->setVisible(false); game_fps_label->setVisible(false);
emu_frametime_label->setVisible(false); emu_frametime_label->setVisible(false);
renderer_status_button->setEnabled(!UISettings::values.has_broken_vulkan); renderer_status_button->setEnabled(!UISettings::values.has_broken_vulkan);
refresh_button->setEnabled(true);
if (!firmware_label->text().isEmpty()) { if (!firmware_label->text().isEmpty()) {
firmware_label->setVisible(true); firmware_label->setVisible(true);
@ -4388,6 +4404,12 @@ void GMainWindow::OnToggleStatusBar() {
statusBar()->setVisible(ui->action_Show_Status_Bar->isChecked()); statusBar()->setVisible(ui->action_Show_Status_Bar->isChecked());
} }
void GMainWindow::OnGameListRefresh()
{
// force reload add-ons etc
game_list->ForceRefreshGameDirectory();
}
void GMainWindow::OnAlbum() { void GMainWindow::OnAlbum() {
constexpr u64 AlbumId = static_cast<u64>(Service::AM::AppletProgramId::PhotoViewer); constexpr u64 AlbumId = static_cast<u64>(Service::AM::AppletProgramId::PhotoViewer);
auto bis_system = system->GetFileSystemController().GetSystemNANDContents(); auto bis_system = system->GetFileSystemController().GetSystemNANDContents();

View file

@ -391,6 +391,7 @@ private slots:
void OnAbout(); void OnAbout();
void OnToggleFilterBar(); void OnToggleFilterBar();
void OnToggleStatusBar(); void OnToggleStatusBar();
void OnGameListRefresh();
void OnDisplayTitleBars(bool); void OnDisplayTitleBars(bool);
void InitializeHotkeys(); void InitializeHotkeys();
void ToggleFullscreen(); void ToggleFullscreen();
@ -521,6 +522,7 @@ private:
QLabel* firmware_label = nullptr; QLabel* firmware_label = nullptr;
QPushButton* gpu_accuracy_button = nullptr; QPushButton* gpu_accuracy_button = nullptr;
QPushButton* renderer_status_button = nullptr; QPushButton* renderer_status_button = nullptr;
QPushButton* refresh_button = nullptr;
QPushButton* dock_status_button = nullptr; QPushButton* dock_status_button = nullptr;
QPushButton* filter_status_button = nullptr; QPushButton* filter_status_button = nullptr;
QPushButton* aa_status_button = nullptr; QPushButton* aa_status_button = nullptr;