Merge pull request #7714 from cristian64/avoid_leaking_gamelistmodel

DolphinQt: Avoid leaking the GameListModel instance to gracefully shutdown the GameTracker and prevent a crash on exit
This commit is contained in:
Léo Lam 2020-11-18 02:14:51 +01:00 committed by GitHub
commit 31d7be521c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 191 additions and 117 deletions

View File

@ -27,6 +27,7 @@ public:
{ {
Shutdown(); Shutdown();
m_shutdown.Clear(); m_shutdown.Clear();
m_cancelled.Clear();
m_function = std::move(function); m_function = std::move(function);
m_thread = std::thread(&WorkQueueThread::ThreadLoop, this); m_thread = std::thread(&WorkQueueThread::ThreadLoop, this);
} }
@ -34,6 +35,7 @@ public:
template <typename... Args> template <typename... Args>
void EmplaceItem(Args&&... args) void EmplaceItem(Args&&... args)
{ {
if (!m_cancelled.IsSet())
{ {
std::lock_guard lg(m_lock); std::lock_guard lg(m_lock);
m_items.emplace(std::forward<Args>(args)...); m_items.emplace(std::forward<Args>(args)...);
@ -41,6 +43,24 @@ public:
m_wakeup.Set(); m_wakeup.Set();
} }
void Clear()
{
{
std::lock_guard lg(m_lock);
m_items = std::queue<T>();
}
m_wakeup.Set();
}
void Cancel()
{
m_cancelled.Set();
Clear();
Shutdown();
}
bool IsCancelled() const { return m_cancelled.IsSet(); }
private: private:
void Shutdown() void Shutdown()
{ {
@ -81,6 +101,7 @@ private:
std::thread m_thread; std::thread m_thread;
Common::Event m_wakeup; Common::Event m_wakeup;
Common::Flag m_shutdown; Common::Flag m_shutdown;
Common::Flag m_cancelled;
std::mutex m_lock; std::mutex m_lock;
std::queue<T> m_items; std::queue<T> m_items;
}; };

View File

@ -33,7 +33,6 @@
#include "DolphinQt/Config/ARCodeWidget.h" #include "DolphinQt/Config/ARCodeWidget.h"
#include "DolphinQt/Config/GeckoCodeWidget.h" #include "DolphinQt/Config/GeckoCodeWidget.h"
#include "DolphinQt/GameList/GameListModel.h"
#include "DolphinQt/Settings.h" #include "DolphinQt/Settings.h"
constexpr u32 MAX_RESULTS = 50; constexpr u32 MAX_RESULTS = 50;
@ -152,7 +151,8 @@ static bool Compare(T mem_value, T value, CompareType op)
} }
} }
CheatsManager::CheatsManager(QWidget* parent) : QDialog(parent) CheatsManager::CheatsManager(const GameListModel& game_list_model, QWidget* parent)
: QDialog(parent), m_game_list_model(game_list_model)
{ {
setWindowTitle(tr("Cheats Manager")); setWindowTitle(tr("Cheats Manager"));
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
@ -175,11 +175,9 @@ void CheatsManager::OnStateChanged(Core::State state)
if (state != Core::State::Running && state != Core::State::Paused) if (state != Core::State::Running && state != Core::State::Paused)
return; return;
auto* model = Settings::Instance().GetGameListModel(); for (int i = 0; i < m_game_list_model.rowCount(QModelIndex()); i++)
for (int i = 0; i < model->rowCount(QModelIndex()); i++)
{ {
auto file = model->GetGameFile(i); auto file = m_game_list_model.GetGameFile(i);
if (file->GetGameID() == SConfig::GetInstance().GetGameID()) if (file->GetGameID() == SConfig::GetInstance().GetGameID())
{ {

View File

@ -11,6 +11,7 @@
#include <QDialog> #include <QDialog>
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "DolphinQt/GameList/GameListModel.h"
class ARCodeWidget; class ARCodeWidget;
class QComboBox; class QComboBox;
@ -39,7 +40,7 @@ class CheatsManager : public QDialog
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit CheatsManager(QWidget* parent = nullptr); explicit CheatsManager(const GameListModel& game_list_model, QWidget* parent = nullptr);
~CheatsManager(); ~CheatsManager();
private: private:
@ -61,6 +62,7 @@ private:
void OnMatchContextMenu(); void OnMatchContextMenu();
void OnWatchItemChanged(QTableWidgetItem* item); void OnWatchItemChanged(QTableWidgetItem* item);
const GameListModel& m_game_list_model;
std::vector<Result> m_results; std::vector<Result> m_results;
std::vector<Result> m_watch; std::vector<Result> m_watch;
std::shared_ptr<const UICommon::GameFile> m_game_file; std::shared_ptr<const UICommon::GameFile> m_game_file;

View File

@ -41,7 +41,6 @@
#include "DolphinQt/Config/PropertiesDialog.h" #include "DolphinQt/Config/PropertiesDialog.h"
#include "DolphinQt/ConvertDialog.h" #include "DolphinQt/ConvertDialog.h"
#include "DolphinQt/GameList/GameListModel.h"
#include "DolphinQt/GameList/GridProxyModel.h" #include "DolphinQt/GameList/GridProxyModel.h"
#include "DolphinQt/GameList/ListProxyModel.h" #include "DolphinQt/GameList/ListProxyModel.h"
#include "DolphinQt/MenuBar.h" #include "DolphinQt/MenuBar.h"
@ -54,27 +53,26 @@
#include "UICommon/GameFile.h" #include "UICommon/GameFile.h"
GameList::GameList(QWidget* parent) : QStackedWidget(parent) GameList::GameList(QWidget* parent) : QStackedWidget(parent), m_model(this)
{ {
m_model = Settings::Instance().GetGameListModel();
m_list_proxy = new ListProxyModel(this); m_list_proxy = new ListProxyModel(this);
m_list_proxy->setSortCaseSensitivity(Qt::CaseInsensitive); m_list_proxy->setSortCaseSensitivity(Qt::CaseInsensitive);
m_list_proxy->setSortRole(GameListModel::SORT_ROLE); m_list_proxy->setSortRole(GameListModel::SORT_ROLE);
m_list_proxy->setSourceModel(m_model); m_list_proxy->setSourceModel(&m_model);
m_grid_proxy = new GridProxyModel(this); m_grid_proxy = new GridProxyModel(this);
m_grid_proxy->setSourceModel(m_model); m_grid_proxy->setSourceModel(&m_model);
MakeListView(); MakeListView();
MakeGridView(); MakeGridView();
MakeEmptyView(); MakeEmptyView();
if (Settings::GetQSettings().contains(QStringLiteral("gridview/scale"))) if (Settings::GetQSettings().contains(QStringLiteral("gridview/scale")))
m_model->SetScale(Settings::GetQSettings().value(QStringLiteral("gridview/scale")).toFloat()); m_model.SetScale(Settings::GetQSettings().value(QStringLiteral("gridview/scale")).toFloat());
connect(m_list, &QTableView::doubleClicked, this, &GameList::GameSelected); connect(m_list, &QTableView::doubleClicked, this, &GameList::GameSelected);
connect(m_grid, &QListView::doubleClicked, this, &GameList::GameSelected); connect(m_grid, &QListView::doubleClicked, this, &GameList::GameSelected);
connect(m_model, &QAbstractItemModel::rowsInserted, this, &GameList::ConsiderViewChange); connect(&m_model, &QAbstractItemModel::rowsInserted, this, &GameList::ConsiderViewChange);
connect(m_model, &QAbstractItemModel::rowsRemoved, this, &GameList::ConsiderViewChange); connect(&m_model, &QAbstractItemModel::rowsRemoved, this, &GameList::ConsiderViewChange);
addWidget(m_list); addWidget(m_list);
addWidget(m_grid); addWidget(m_grid);
@ -94,7 +92,7 @@ GameList::GameList(QWidget* parent) : QStackedWidget(parent)
void GameList::PurgeCache() void GameList::PurgeCache()
{ {
m_model->PurgeCache(); m_model.PurgeCache();
} }
void GameList::MakeListView() void GameList::MakeListView()
@ -176,7 +174,7 @@ GameList::~GameList()
{ {
Settings::GetQSettings().setValue(QStringLiteral("tableheader/state"), Settings::GetQSettings().setValue(QStringLiteral("tableheader/state"),
m_list->horizontalHeader()->saveState()); m_list->horizontalHeader()->saveState());
Settings::GetQSettings().setValue(QStringLiteral("gridview/scale"), m_model->GetScale()); Settings::GetQSettings().setValue(QStringLiteral("gridview/scale"), m_model.GetScale());
} }
void GameList::UpdateColumnVisibility() void GameList::UpdateColumnVisibility()
@ -205,9 +203,13 @@ void GameList::UpdateColumnVisibility()
void GameList::MakeEmptyView() void GameList::MakeEmptyView()
{ {
const QString refreshing_msg = tr("Refreshing...");
const QString empty_msg = tr("Dolphin could not find any GameCube/Wii ISOs or WADs.\n"
"Double-click here to set a games directory...");
m_empty = new QLabel(this); m_empty = new QLabel(this);
m_empty->setText(tr("Dolphin could not find any GameCube/Wii ISOs or WADs.\n" m_empty->setText(refreshing_msg);
"Double-click here to set a games directory...")); m_empty->setEnabled(false);
m_empty->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); m_empty->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
auto event_filter = new DoubleClickEventFilter{m_empty}; auto event_filter = new DoubleClickEventFilter{m_empty};
@ -218,6 +220,21 @@ void GameList::MakeEmptyView()
if (!dir.isEmpty()) if (!dir.isEmpty())
Settings::Instance().AddPath(dir); Settings::Instance().AddPath(dir);
}); });
QSizePolicy size_policy{m_empty->sizePolicy()};
size_policy.setRetainSizeWhenHidden(true);
m_empty->setSizePolicy(size_policy);
connect(&Settings::Instance(), &Settings::GameListRefreshRequested, this,
[this, refreshing_msg = refreshing_msg] {
m_empty->setText(refreshing_msg);
m_empty->setEnabled(false);
});
connect(&Settings::Instance(), &Settings::GameListRefreshCompleted, this,
[this, empty_msg = empty_msg] {
m_empty->setText(empty_msg);
m_empty->setEnabled(true);
});
} }
void GameList::resizeEvent(QResizeEvent* event) void GameList::resizeEvent(QResizeEvent* event)
@ -368,21 +385,19 @@ void GameList::ShowContextMenu(const QPoint&)
menu->addSeparator(); menu->addSeparator();
auto* model = Settings::Instance().GetGameListModel();
auto* tags_menu = menu->addMenu(tr("Tags")); auto* tags_menu = menu->addMenu(tr("Tags"));
auto path = game->GetFilePath(); auto path = game->GetFilePath();
auto game_tags = model->GetGameTags(path); auto game_tags = m_model.GetGameTags(path);
for (const auto& tag : model->GetAllTags()) for (const auto& tag : m_model.GetAllTags())
{ {
auto* tag_action = tags_menu->addAction(tag); auto* tag_action = tags_menu->addAction(tag);
tag_action->setCheckable(true); tag_action->setCheckable(true);
tag_action->setChecked(game_tags.contains(tag)); tag_action->setChecked(game_tags.contains(tag));
connect(tag_action, &QAction::toggled, [path, tag, model](bool checked) { connect(tag_action, &QAction::toggled, [path, tag, model = &m_model](bool checked) {
if (!checked) if (!checked)
model->RemoveGameTag(path, tag); model->RemoveGameTag(path, tag);
else else
@ -635,7 +650,7 @@ void GameList::DeleteFile()
if (deletion_successful) if (deletion_successful)
{ {
m_model->RemoveGame(game->GetFilePath()); m_model.RemoveGame(game->GetFilePath());
} }
else else
{ {
@ -686,7 +701,7 @@ std::shared_ptr<const UICommon::GameFile> GameList::GetSelectedGame() const
if (sel_model->hasSelection()) if (sel_model->hasSelection())
{ {
QModelIndex model_index = proxy->mapToSource(sel_model->selectedIndexes()[0]); QModelIndex model_index = proxy->mapToSource(sel_model->selectedIndexes()[0]);
return m_model->GetGameFile(model_index.row()); return m_model.GetGameFile(model_index.row());
} }
return {}; return {};
} }
@ -714,7 +729,7 @@ QList<std::shared_ptr<const UICommon::GameFile>> GameList::GetSelectedGames() co
for (const auto& index : index_list) for (const auto& index : index_list)
{ {
QModelIndex model_index = proxy->mapToSource(index); QModelIndex model_index = proxy->mapToSource(index);
selected_list.push_back(m_model->GetGameFile(model_index.row())); selected_list.push_back(m_model.GetGameFile(model_index.row()));
} }
} }
return selected_list; return selected_list;
@ -728,18 +743,18 @@ bool GameList::HasMultipleSelected() const
std::shared_ptr<const UICommon::GameFile> GameList::FindGame(const std::string& path) const std::shared_ptr<const UICommon::GameFile> GameList::FindGame(const std::string& path) const
{ {
return m_model->FindGame(path); return m_model.FindGame(path);
} }
std::shared_ptr<const UICommon::GameFile> std::shared_ptr<const UICommon::GameFile>
GameList::FindSecondDisc(const UICommon::GameFile& game) const GameList::FindSecondDisc(const UICommon::GameFile& game) const
{ {
return m_model->FindSecondDisc(game); return m_model.FindSecondDisc(game);
} }
std::string GameList::GetNetPlayName(const UICommon::GameFile& game) const std::string GameList::GetNetPlayName(const UICommon::GameFile& game) const
{ {
return m_model->GetNetPlayName(game); return m_model.GetNetPlayName(game);
} }
void GameList::SetViewColumn(int col, bool view) void GameList::SetViewColumn(int col, bool view)
@ -756,7 +771,7 @@ void GameList::SetPreferredView(bool list)
void GameList::ConsiderViewChange() void GameList::ConsiderViewChange()
{ {
if (m_model->rowCount(QModelIndex()) > 0) if (m_model.rowCount(QModelIndex()) > 0)
{ {
if (m_prefer_list) if (m_prefer_list)
setCurrentWidget(m_list); setCurrentWidget(m_list);
@ -905,7 +920,7 @@ void GameList::NewTag()
if (tag.isEmpty()) if (tag.isEmpty())
return; return;
Settings::Instance().GetGameListModel()->NewTag(tag); m_model.NewTag(tag);
} }
void GameList::DeleteTag() void GameList::DeleteTag()
@ -915,12 +930,12 @@ void GameList::DeleteTag()
if (tag.isEmpty()) if (tag.isEmpty())
return; return;
Settings::Instance().GetGameListModel()->DeleteTag(tag); m_model.DeleteTag(tag);
} }
void GameList::SetSearchTerm(const QString& term) void GameList::SetSearchTerm(const QString& term)
{ {
m_model->SetSearchTerm(term); m_model.SetSearchTerm(term);
m_list_proxy->invalidate(); m_list_proxy->invalidate();
m_grid_proxy->invalidate(); m_grid_proxy->invalidate();
@ -930,7 +945,7 @@ void GameList::SetSearchTerm(const QString& term)
void GameList::ZoomIn() void GameList::ZoomIn()
{ {
m_model->SetScale(m_model->GetScale() + 0.1); m_model.SetScale(m_model.GetScale() + 0.1);
m_list_proxy->invalidate(); m_list_proxy->invalidate();
m_grid_proxy->invalidate(); m_grid_proxy->invalidate();
@ -940,10 +955,10 @@ void GameList::ZoomIn()
void GameList::ZoomOut() void GameList::ZoomOut()
{ {
if (m_model->GetScale() <= 0.1) if (m_model.GetScale() <= 0.1)
return; return;
m_model->SetScale(m_model->GetScale() - 0.1); m_model.SetScale(m_model.GetScale() - 0.1);
m_list_proxy->invalidate(); m_list_proxy->invalidate();
m_grid_proxy->invalidate(); m_grid_proxy->invalidate();
@ -955,7 +970,7 @@ void GameList::UpdateFont()
{ {
QFont f; QFont f;
f.setPointSizeF(m_model->GetScale() * f.pointSize()); f.setPointSizeF(m_model.GetScale() * f.pointSize());
m_grid->setFont(f); m_grid->setFont(f);
} }

View File

@ -8,7 +8,8 @@
#include <QStackedWidget> #include <QStackedWidget>
class GameListModel; #include "DolphinQt/GameList/GameListModel.h"
class QLabel; class QLabel;
class QListView; class QListView;
class QSortFilterProxyModel; class QSortFilterProxyModel;
@ -46,6 +47,8 @@ public:
void PurgeCache(); void PurgeCache();
const GameListModel& GetGameListModel() const { return m_model; }
signals: signals:
void GameSelected(); void GameSelected();
void NetPlayHost(const UICommon::GameFile& game); void NetPlayHost(const UICommon::GameFile& game);
@ -85,7 +88,7 @@ private:
void ConsiderViewChange(); void ConsiderViewChange();
void UpdateFont(); void UpdateFont();
GameListModel* m_model; GameListModel m_model;
QSortFilterProxyModel* m_list_proxy; QSortFilterProxyModel* m_list_proxy;
QSortFilterProxyModel* m_grid_proxy; QSortFilterProxyModel* m_grid_proxy;

View File

@ -4,6 +4,7 @@
#include "DolphinQt/GameList/GameTracker.h" #include "DolphinQt/GameList/GameTracker.h"
#include <QApplication>
#include <QDir> #include <QDir>
#include <QDirIterator> #include <QDirIterator>
#include <QFile> #include <QFile>
@ -15,7 +16,6 @@
#include "DiscIO/DirectoryBlob.h" #include "DiscIO/DirectoryBlob.h"
#include "DolphinQt/QtUtils/QueueOnObject.h" #include "DolphinQt/QtUtils/QueueOnObject.h"
#include "DolphinQt/QtUtils/RunOnObject.h"
#include "DolphinQt/Settings.h" #include "DolphinQt/Settings.h"
@ -35,6 +35,10 @@ GameTracker::GameTracker(QObject* parent) : QFileSystemWatcher(parent)
qRegisterMetaType<std::shared_ptr<const UICommon::GameFile>>(); qRegisterMetaType<std::shared_ptr<const UICommon::GameFile>>();
qRegisterMetaType<std::string>(); qRegisterMetaType<std::string>();
connect(qApp, &QApplication::aboutToQuit, this, [this] {
m_processing_halted = true;
m_load_thread.Cancel();
});
connect(this, &QFileSystemWatcher::directoryChanged, this, &GameTracker::UpdateDirectory); connect(this, &QFileSystemWatcher::directoryChanged, this, &GameTracker::UpdateDirectory);
connect(this, &QFileSystemWatcher::fileChanged, this, &GameTracker::UpdateFile); connect(this, &QFileSystemWatcher::fileChanged, this, &GameTracker::UpdateFile);
connect(&Settings::Instance(), &Settings::AutoRefreshToggled, this, [] { connect(&Settings::Instance(), &Settings::AutoRefreshToggled, this, [] {
@ -74,27 +78,24 @@ GameTracker::GameTracker(QObject* parent) : QFileSystemWatcher(parent)
break; break;
case CommandType::UpdateMetadata: case CommandType::UpdateMetadata:
m_cache.UpdateAdditionalMetadata( m_cache.UpdateAdditionalMetadata(
[this](const std::shared_ptr<const UICommon::GameFile>& game) { [this](const std::shared_ptr<const UICommon::GameFile>& game) { emit GameUpdated(game); },
emit GameUpdated(game); m_processing_halted);
});
QueueOnObject(this, [] { Settings::Instance().NotifyMetadataRefreshComplete(); }); QueueOnObject(this, [] { Settings::Instance().NotifyMetadataRefreshComplete(); });
break; break;
case CommandType::ResumeProcessing:
m_processing_halted = false;
break;
case CommandType::PurgeCache: case CommandType::PurgeCache:
m_cache.Clear(UICommon::GameFileCache::DeleteOnDisk::Yes); m_cache.Clear(UICommon::GameFileCache::DeleteOnDisk::Yes);
break; break;
case CommandType::BeginRefresh: case CommandType::BeginRefresh:
if (m_busy_count++ == 0) QueueOnObject(this, [] { Settings::Instance().NotifyRefreshGameListStarted(); });
{
for (auto& file : m_tracked_files.keys()) for (auto& file : m_tracked_files.keys())
emit GameRemoved(file.toStdString()); emit GameRemoved(file.toStdString());
m_tracked_files.clear(); m_tracked_files.clear();
}
break; break;
case CommandType::EndRefresh: case CommandType::EndRefresh:
if (--m_busy_count == 0)
{
QueueOnObject(this, [] { Settings::Instance().NotifyRefreshGameListComplete(); }); QueueOnObject(this, [] { Settings::Instance().NotifyRefreshGameListComplete(); });
}
break; break;
} }
}); });
@ -134,6 +135,8 @@ void GameTracker::StartInternal()
m_started = true; m_started = true;
QueueOnObject(this, [] { Settings::Instance().NotifyRefreshGameListStarted(); });
std::vector<std::string> paths; std::vector<std::string> paths;
paths.reserve(m_tracked_files.size()); paths.reserve(m_tracked_files.size());
for (const QString& path : m_tracked_files.keys()) for (const QString& path : m_tracked_files.keys())
@ -149,18 +152,20 @@ void GameTracker::StartInternal()
m_initial_games_emitted_event.Wait(); m_initial_games_emitted_event.Wait();
bool cache_updated = m_cache.Update(paths, emit_game_loaded, emit_game_removed); bool cache_updated =
cache_updated |= m_cache.UpdateAdditionalMetadata(emit_game_updated); m_cache.Update(paths, emit_game_loaded, emit_game_removed, m_processing_halted);
cache_updated |= m_cache.UpdateAdditionalMetadata(emit_game_updated, m_processing_halted);
if (cache_updated) if (cache_updated)
m_cache.Save(); m_cache.Save();
QueueOnObject(this, [] { Settings::Instance().NotifyMetadataRefreshComplete(); }); QueueOnObject(this, [] { Settings::Instance().NotifyMetadataRefreshComplete(); });
QueueOnObject(this, [] { Settings::Instance().NotifyRefreshGameListComplete(); });
} }
bool GameTracker::AddPath(const QString& dir) bool GameTracker::AddPath(const QString& dir)
{ {
if (Settings::Instance().IsAutoRefreshEnabled()) if (Settings::Instance().IsAutoRefreshEnabled())
RunOnObject(this, [this, dir] { return addPath(dir); }); QueueOnObject(this, [this, dir] { return addPath(dir); });
m_tracked_paths.push_back(dir); m_tracked_paths.push_back(dir);
@ -170,7 +175,7 @@ bool GameTracker::AddPath(const QString& dir)
bool GameTracker::RemovePath(const QString& dir) bool GameTracker::RemovePath(const QString& dir)
{ {
if (Settings::Instance().IsAutoRefreshEnabled()) if (Settings::Instance().IsAutoRefreshEnabled())
RunOnObject(this, [this, dir] { return removePath(dir); }); QueueOnObject(this, [this, dir] { return removePath(dir); });
const auto index = m_tracked_paths.indexOf(dir); const auto index = m_tracked_paths.indexOf(dir);
@ -194,6 +199,16 @@ void GameTracker::RemoveDirectory(const QString& dir)
void GameTracker::RefreshAll() void GameTracker::RefreshAll()
{ {
m_processing_halted = true;
m_load_thread.Clear();
m_load_thread.EmplaceItem(Command{CommandType::ResumeProcessing, {}});
if (m_needs_purge)
{
m_load_thread.EmplaceItem(Command{CommandType::PurgeCache, {}});
m_needs_purge = false;
}
m_load_thread.EmplaceItem(Command{CommandType::BeginRefresh}); m_load_thread.EmplaceItem(Command{CommandType::BeginRefresh});
for (const QString& dir : Settings::Instance().GetPaths()) for (const QString& dir : Settings::Instance().GetPaths())
@ -255,7 +270,7 @@ void GameTracker::RemoveDirectoryInternal(const QString& dir)
void GameTracker::UpdateDirectoryInternal(const QString& dir) void GameTracker::UpdateDirectoryInternal(const QString& dir)
{ {
auto it = GetIterator(dir); auto it = GetIterator(dir);
while (it->hasNext()) while (it->hasNext() && !m_processing_halted)
{ {
QString path = QFileInfo(it->next()).canonicalFilePath(); QString path = QFileInfo(it->next()).canonicalFilePath();
@ -275,6 +290,9 @@ void GameTracker::UpdateDirectoryInternal(const QString& dir)
for (const auto& missing : FindMissingFiles(dir)) for (const auto& missing : FindMissingFiles(dir))
{ {
if (m_processing_halted)
break;
auto& tracked_file = m_tracked_files[missing]; auto& tracked_file = m_tracked_files[missing];
tracked_file.remove(dir); tracked_file.remove(dir);
@ -345,6 +363,6 @@ void GameTracker::LoadGame(const QString& path)
void GameTracker::PurgeCache() void GameTracker::PurgeCache()
{ {
m_load_thread.EmplaceItem(Command{CommandType::PurgeCache, {}}); m_needs_purge = true;
RefreshAll(); Settings::Instance().RefreshGameList();
} }

View File

@ -4,6 +4,7 @@
#pragma once #pragma once
#include <atomic>
#include <memory> #include <memory>
#include <string> #include <string>
@ -72,6 +73,7 @@ private:
UpdateDirectory, UpdateDirectory,
UpdateFile, UpdateFile,
UpdateMetadata, UpdateMetadata,
ResumeProcessing,
PurgeCache, PurgeCache,
BeginRefresh, BeginRefresh,
EndRefresh, EndRefresh,
@ -92,8 +94,8 @@ private:
Common::Event m_initial_games_emitted_event; Common::Event m_initial_games_emitted_event;
bool m_initial_games_emitted = false; bool m_initial_games_emitted = false;
bool m_started = false; bool m_started = false;
// Count of currently running refresh jobs bool m_needs_purge = false;
u32 m_busy_count = 0; std::atomic_bool m_processing_halted = false;
}; };
Q_DECLARE_METATYPE(std::shared_ptr<const UICommon::GameFile>) Q_DECLARE_METATYPE(std::shared_ptr<const UICommon::GameFile>)

View File

@ -399,7 +399,7 @@ void MainWindow::CreateComponents()
m_watch_widget = new WatchWidget(this); m_watch_widget = new WatchWidget(this);
m_breakpoint_widget = new BreakpointWidget(this); m_breakpoint_widget = new BreakpointWidget(this);
m_code_widget = new CodeWidget(this); m_code_widget = new CodeWidget(this);
m_cheats_manager = new CheatsManager(this); m_cheats_manager = new CheatsManager(m_game_list->GetGameListModel(), this);
const auto request_watch = [this](QString name, u32 addr) { const auto request_watch = [this](QString name, u32 addr) {
m_watch_widget->AddWatch(name, addr); m_watch_widget->AddWatch(name, addr);
@ -1276,8 +1276,9 @@ void MainWindow::BootWiiSystemMenu()
void MainWindow::NetPlayInit() void MainWindow::NetPlayInit()
{ {
m_netplay_setup_dialog = new NetPlaySetupDialog(this); const auto& game_list_model = m_game_list->GetGameListModel();
m_netplay_dialog = new NetPlayDialog; m_netplay_setup_dialog = new NetPlaySetupDialog(game_list_model, this);
m_netplay_dialog = new NetPlayDialog(game_list_model);
#ifdef USE_DISCORD_PRESENCE #ifdef USE_DISCORD_PRESENCE
m_netplay_discord = new DiscordHandler(this); m_netplay_discord = new DiscordHandler(this);
#endif #endif

View File

@ -506,7 +506,13 @@ void MenuBar::AddViewMenu()
AddShowRegionsMenu(view_menu); AddShowRegionsMenu(view_menu);
view_menu->addSeparator(); view_menu->addSeparator();
QAction* const purge_action =
view_menu->addAction(tr("Purge Game List Cache"), this, &MenuBar::PurgeGameListCache); view_menu->addAction(tr("Purge Game List Cache"), this, &MenuBar::PurgeGameListCache);
purge_action->setEnabled(false);
connect(&Settings::Instance(), &Settings::GameListRefreshRequested, purge_action,
[purge_action] { purge_action->setEnabled(false); });
connect(&Settings::Instance(), &Settings::GameListRefreshStarted, purge_action,
[purge_action] { purge_action->setEnabled(true); });
view_menu->addSeparator(); view_menu->addSeparator();
view_menu->addAction(tr("Search"), this, &MenuBar::ShowSearch, QKeySequence::Find); view_menu->addAction(tr("Search"), this, &MenuBar::ShowSearch, QKeySequence::Find);
} }

View File

@ -10,11 +10,10 @@
#include <QListWidget> #include <QListWidget>
#include <QVBoxLayout> #include <QVBoxLayout>
#include "DolphinQt/GameList/GameListModel.h"
#include "DolphinQt/Settings.h"
#include "UICommon/GameFile.h" #include "UICommon/GameFile.h"
GameListDialog::GameListDialog(QWidget* parent) : QDialog(parent) GameListDialog::GameListDialog(const GameListModel& game_list_model, QWidget* parent)
: QDialog(parent), m_game_list_model(game_list_model)
{ {
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
setWindowTitle(tr("Select a game")); setWindowTitle(tr("Select a game"));
@ -47,16 +46,14 @@ void GameListDialog::ConnectWidgets()
void GameListDialog::PopulateGameList() void GameListDialog::PopulateGameList()
{ {
auto* game_list_model = Settings::Instance().GetGameListModel();
m_game_list->clear(); m_game_list->clear();
for (int i = 0; i < game_list_model->rowCount(QModelIndex()); i++) for (int i = 0; i < m_game_list_model.rowCount(QModelIndex()); i++)
{ {
std::shared_ptr<const UICommon::GameFile> game = game_list_model->GetGameFile(i); std::shared_ptr<const UICommon::GameFile> game = m_game_list_model.GetGameFile(i);
auto* item = auto* item =
new QListWidgetItem(QString::fromStdString(game_list_model->GetNetPlayName(*game))); new QListWidgetItem(QString::fromStdString(m_game_list_model.GetNetPlayName(*game)));
item->setData(Qt::UserRole, QVariant::fromValue(std::move(game))); item->setData(Qt::UserRole, QVariant::fromValue(std::move(game)));
m_game_list->addItem(item); m_game_list->addItem(item);
} }

View File

@ -6,7 +6,8 @@
#include <QDialog> #include <QDialog>
class GameListModel; #include "DolphinQt/GameList/GameListModel.h"
class QVBoxLayout; class QVBoxLayout;
class QListWidget; class QListWidget;
class QDialogButtonBox; class QDialogButtonBox;
@ -20,7 +21,7 @@ class GameListDialog : public QDialog
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit GameListDialog(QWidget* parent); explicit GameListDialog(const GameListModel& game_list_model, QWidget* parent);
int exec() override; int exec() override;
const UICommon::GameFile& GetSelectedGame() const; const UICommon::GameFile& GetSelectedGame() const;
@ -30,6 +31,7 @@ private:
void ConnectWidgets(); void ConnectWidgets();
void PopulateGameList(); void PopulateGameList();
const GameListModel& m_game_list_model;
QVBoxLayout* m_main_layout; QVBoxLayout* m_main_layout;
QListWidget* m_game_list; QListWidget* m_game_list;
QDialogButtonBox* m_button_box; QDialogButtonBox* m_button_box;

View File

@ -40,7 +40,6 @@
#include "Core/NetPlayServer.h" #include "Core/NetPlayServer.h"
#include "Core/SyncIdentifier.h" #include "Core/SyncIdentifier.h"
#include "DolphinQt/GameList/GameListModel.h"
#include "DolphinQt/NetPlay/ChunkedProgressDialog.h" #include "DolphinQt/NetPlay/ChunkedProgressDialog.h"
#include "DolphinQt/NetPlay/GameListDialog.h" #include "DolphinQt/NetPlay/GameListDialog.h"
#include "DolphinQt/NetPlay/MD5Dialog.h" #include "DolphinQt/NetPlay/MD5Dialog.h"
@ -60,8 +59,8 @@
#include "VideoCommon/RenderBase.h" #include "VideoCommon/RenderBase.h"
#include "VideoCommon/VideoConfig.h" #include "VideoCommon/VideoConfig.h"
NetPlayDialog::NetPlayDialog(QWidget* parent) NetPlayDialog::NetPlayDialog(const GameListModel& game_list_model, QWidget* parent)
: QDialog(parent), m_game_list_model(Settings::Instance().GetGameListModel()) : QDialog(parent), m_game_list_model(game_list_model)
{ {
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
@ -158,7 +157,7 @@ void NetPlayDialog::CreateMainLayout()
Settings::Instance().GetNetPlayServer()->ComputeMD5(m_current_game_identifier); Settings::Instance().GetNetPlayServer()->ComputeMD5(m_current_game_identifier);
}); });
m_md5_menu->addAction(tr("Other game..."), this, [this] { m_md5_menu->addAction(tr("Other game..."), this, [this] {
GameListDialog gld(this); GameListDialog gld(m_game_list_model, this);
if (gld.exec() != QDialog::Accepted) if (gld.exec() != QDialog::Accepted)
return; return;
@ -322,13 +321,13 @@ void NetPlayDialog::ConnectWidgets()
connect(m_quit_button, &QPushButton::clicked, this, &NetPlayDialog::reject); connect(m_quit_button, &QPushButton::clicked, this, &NetPlayDialog::reject);
connect(m_game_button, &QPushButton::clicked, [this] { connect(m_game_button, &QPushButton::clicked, [this] {
GameListDialog gld(this); GameListDialog gld(m_game_list_model, this);
if (gld.exec() == QDialog::Accepted) if (gld.exec() == QDialog::Accepted)
{ {
Settings& settings = Settings::Instance(); Settings& settings = Settings::Instance();
const UICommon::GameFile& game = gld.GetSelectedGame(); const UICommon::GameFile& game = gld.GetSelectedGame();
const std::string netplay_name = settings.GetGameListModel()->GetNetPlayName(game); const std::string netplay_name = m_game_list_model.GetNetPlayName(game);
settings.GetNetPlayServer()->ChangeGame(game.GetSyncIdentifier(), netplay_name); settings.GetNetPlayServer()->ChangeGame(game.GetSyncIdentifier(), netplay_name);
Settings::GetQSettings().setValue(QStringLiteral("netplay/hostgame"), Settings::GetQSettings().setValue(QStringLiteral("netplay/hostgame"),
@ -1048,9 +1047,9 @@ NetPlayDialog::FindGameFile(const NetPlay::SyncIdentifier& sync_identifier,
std::optional<std::shared_ptr<const UICommon::GameFile>> game_file = std::optional<std::shared_ptr<const UICommon::GameFile>> game_file =
RunOnObject(this, [this, &sync_identifier, found] { RunOnObject(this, [this, &sync_identifier, found] {
for (int i = 0; i < m_game_list_model->rowCount(QModelIndex()); i++) for (int i = 0; i < m_game_list_model.rowCount(QModelIndex()); i++)
{ {
auto game_file = m_game_list_model->GetGameFile(i); auto game_file = m_game_list_model.GetGameFile(i);
*found = std::min(*found, game_file->CompareSyncIdentifier(sync_identifier)); *found = std::min(*found, game_file->CompareSyncIdentifier(sync_identifier));
if (*found == NetPlay::SyncIdentifierComparison::SameGame) if (*found == NetPlay::SyncIdentifierComparison::SameGame)
return game_file; return game_file;

View File

@ -9,11 +9,11 @@
#include "Common/Lazy.h" #include "Common/Lazy.h"
#include "Core/NetPlayClient.h" #include "Core/NetPlayClient.h"
#include "DolphinQt/GameList/GameListModel.h"
#include "VideoCommon/OnScreenDisplay.h" #include "VideoCommon/OnScreenDisplay.h"
class ChunkedProgressDialog; class ChunkedProgressDialog;
class MD5Dialog; class MD5Dialog;
class GameListModel;
class PadMappingDialog; class PadMappingDialog;
class QCheckBox; class QCheckBox;
class QComboBox; class QComboBox;
@ -31,7 +31,7 @@ class NetPlayDialog : public QDialog, public NetPlay::NetPlayUI
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit NetPlayDialog(QWidget* parent = nullptr); explicit NetPlayDialog(const GameListModel& game_list_model, QWidget* parent = nullptr);
~NetPlayDialog(); ~NetPlayDialog();
void show(std::string nickname, bool use_traversal); void show(std::string nickname, bool use_traversal);
@ -151,7 +151,7 @@ private:
std::string m_current_game_name; std::string m_current_game_name;
Common::Lazy<std::string> m_external_ip_address; Common::Lazy<std::string> m_external_ip_address;
std::string m_nickname; std::string m_nickname;
GameListModel* m_game_list_model = nullptr; const GameListModel& m_game_list_model;
bool m_use_traversal = false; bool m_use_traversal = false;
bool m_is_copy_button_retry = false; bool m_is_copy_button_retry = false;
bool m_got_stop_request = true; bool m_got_stop_request = true;

View File

@ -21,7 +21,6 @@
#include "Core/Config/NetplaySettings.h" #include "Core/Config/NetplaySettings.h"
#include "Core/NetPlayProto.h" #include "Core/NetPlayProto.h"
#include "DolphinQt/GameList/GameListModel.h"
#include "DolphinQt/QtUtils/ModalMessageBox.h" #include "DolphinQt/QtUtils/ModalMessageBox.h"
#include "DolphinQt/QtUtils/UTF8CodePointCountValidator.h" #include "DolphinQt/QtUtils/UTF8CodePointCountValidator.h"
#include "DolphinQt/Settings.h" #include "DolphinQt/Settings.h"
@ -29,8 +28,8 @@
#include "UICommon/GameFile.h" #include "UICommon/GameFile.h"
#include "UICommon/NetPlayIndex.h" #include "UICommon/NetPlayIndex.h"
NetPlaySetupDialog::NetPlaySetupDialog(QWidget* parent) NetPlaySetupDialog::NetPlaySetupDialog(const GameListModel& game_list_model, QWidget* parent)
: QDialog(parent), m_game_list_model(Settings::Instance().GetGameListModel()) : QDialog(parent), m_game_list_model(game_list_model)
{ {
setWindowTitle(tr("NetPlay Setup")); setWindowTitle(tr("NetPlay Setup"));
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
@ -359,12 +358,12 @@ void NetPlaySetupDialog::PopulateGameList()
QSignalBlocker blocker(m_host_games); QSignalBlocker blocker(m_host_games);
m_host_games->clear(); m_host_games->clear();
for (int i = 0; i < m_game_list_model->rowCount(QModelIndex()); i++) for (int i = 0; i < m_game_list_model.rowCount(QModelIndex()); i++)
{ {
std::shared_ptr<const UICommon::GameFile> game = m_game_list_model->GetGameFile(i); std::shared_ptr<const UICommon::GameFile> game = m_game_list_model.GetGameFile(i);
auto* item = auto* item =
new QListWidgetItem(QString::fromStdString(m_game_list_model->GetNetPlayName(*game))); new QListWidgetItem(QString::fromStdString(m_game_list_model.GetNetPlayName(*game)));
item->setData(Qt::UserRole, QVariant::fromValue(std::move(game))); item->setData(Qt::UserRole, QVariant::fromValue(std::move(game)));
m_host_games->addItem(item); m_host_games->addItem(item);
} }

View File

@ -6,7 +6,8 @@
#include <QDialog> #include <QDialog>
class GameListModel; #include "DolphinQt/GameList/GameListModel.h"
class QCheckBox; class QCheckBox;
class QComboBox; class QComboBox;
class QDialogButtonBox; class QDialogButtonBox;
@ -27,7 +28,7 @@ class NetPlaySetupDialog : public QDialog
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit NetPlaySetupDialog(QWidget* parent); explicit NetPlaySetupDialog(const GameListModel& game_list_model, QWidget* parent);
void accept() override; void accept() override;
void show(); void show();
@ -79,5 +80,5 @@ private:
QCheckBox* m_host_upnp; QCheckBox* m_host_upnp;
#endif #endif
GameListModel* m_game_list_model; const GameListModel& m_game_list_model;
}; };

View File

@ -24,7 +24,6 @@
#include "Core/NetPlayClient.h" #include "Core/NetPlayClient.h"
#include "Core/NetPlayServer.h" #include "Core/NetPlayServer.h"
#include "DolphinQt/GameList/GameListModel.h"
#include "DolphinQt/QtUtils/QueueOnObject.h" #include "DolphinQt/QtUtils/QueueOnObject.h"
#include "InputCommon/ControllerInterface/ControllerInterface.h" #include "InputCommon/ControllerInterface/ControllerInterface.h"
@ -148,6 +147,11 @@ void Settings::RefreshGameList()
emit GameListRefreshRequested(); emit GameListRefreshRequested();
} }
void Settings::NotifyRefreshGameListStarted()
{
emit GameListRefreshStarted();
}
void Settings::NotifyRefreshGameListComplete() void Settings::NotifyRefreshGameListComplete()
{ {
emit GameListRefreshCompleted(); emit GameListRefreshCompleted();
@ -296,12 +300,6 @@ void Settings::SetLogConfigVisible(bool visible)
} }
} }
GameListModel* Settings::GetGameListModel() const
{
static GameListModel* model = new GameListModel;
return model;
}
std::shared_ptr<NetPlay::NetPlayClient> Settings::GetNetPlayClient() std::shared_ptr<NetPlay::NetPlayClient> Settings::GetNetPlayClient()
{ {
return m_client; return m_client;

View File

@ -26,7 +26,6 @@ class NetPlayClient;
class NetPlayServer; class NetPlayServer;
} // namespace NetPlay } // namespace NetPlay
class GameListModel;
class InputConfig; class InputConfig;
// UI settings to be stored in the config directory. // UI settings to be stored in the config directory.
@ -73,6 +72,7 @@ public:
QString GetDefaultGame() const; QString GetDefaultGame() const;
void SetDefaultGame(QString path); void SetDefaultGame(QString path);
void RefreshGameList(); void RefreshGameList();
void NotifyRefreshGameListStarted();
void NotifyRefreshGameListComplete(); void NotifyRefreshGameListComplete();
void RefreshMetadata(); void RefreshMetadata();
void NotifyMetadataRefreshComplete(); void NotifyMetadataRefreshComplete();
@ -143,8 +143,6 @@ public:
bool IsAnalyticsEnabled() const; bool IsAnalyticsEnabled() const;
void SetAnalyticsEnabled(bool enabled); void SetAnalyticsEnabled(bool enabled);
// Other
GameListModel* GetGameListModel() const;
signals: signals:
void ConfigChanged(); void ConfigChanged();
void EmulationStateChanged(Core::State new_state); void EmulationStateChanged(Core::State new_state);
@ -153,6 +151,7 @@ signals:
void PathRemoved(const QString&); void PathRemoved(const QString&);
void DefaultGameChanged(const QString&); void DefaultGameChanged(const QString&);
void GameListRefreshRequested(); void GameListRefreshRequested();
void GameListRefreshStarted();
void GameListRefreshCompleted(); void GameListRefreshCompleted();
void TitleDBReloadRequested(); void TitleDBReloadRequested();
void MetadataRefreshRequested(); void MetadataRefreshRequested();

View File

@ -23,7 +23,6 @@
#include "Core/Config/UISettings.h" #include "Core/Config/UISettings.h"
#include "Core/ConfigManager.h" #include "Core/ConfigManager.h"
#include "DolphinQt/GameList/GameListModel.h"
#include "DolphinQt/QtUtils/ModalMessageBox.h" #include "DolphinQt/QtUtils/ModalMessageBox.h"
#include "DolphinQt/Settings.h" #include "DolphinQt/Settings.h"

View File

@ -46,7 +46,9 @@ ToolBar::ToolBar(QWidget* parent) : QToolBar(parent)
connect(&Settings::Instance(), &Settings::WidgetLockChanged, this, connect(&Settings::Instance(), &Settings::WidgetLockChanged, this,
[this](bool locked) { setMovable(!locked); }); [this](bool locked) { setMovable(!locked); });
connect(&Settings::Instance(), &Settings::GameListRefreshCompleted, this, connect(&Settings::Instance(), &Settings::GameListRefreshRequested, this,
[this] { m_refresh_action->setEnabled(false); });
connect(&Settings::Instance(), &Settings::GameListRefreshStarted, this,
[this] { m_refresh_action->setEnabled(true); }); [this] { m_refresh_action->setEnabled(true); });
OnEmulationStateChanged(Core::GetState()); OnEmulationStateChanged(Core::GetState());
@ -112,10 +114,8 @@ void ToolBar::MakeActions()
m_set_pc_action = addAction(tr("Set PC"), this, &ToolBar::SetPCPressed); m_set_pc_action = addAction(tr("Set PC"), this, &ToolBar::SetPCPressed);
m_open_action = addAction(tr("Open"), this, &ToolBar::OpenPressed); m_open_action = addAction(tr("Open"), this, &ToolBar::OpenPressed);
m_refresh_action = addAction(tr("Refresh"), [this] { m_refresh_action = addAction(tr("Refresh"), [this] { emit RefreshPressed(); });
m_refresh_action->setEnabled(false); m_refresh_action->setEnabled(false);
emit RefreshPressed();
});
addSeparator(); addSeparator();

View File

@ -90,7 +90,8 @@ std::shared_ptr<const GameFile> GameFileCache::AddOrGet(const std::string& path,
bool GameFileCache::Update( bool GameFileCache::Update(
const std::vector<std::string>& all_game_paths, const std::vector<std::string>& all_game_paths,
std::function<void(const std::shared_ptr<const GameFile>&)> game_added_to_cache, std::function<void(const std::shared_ptr<const GameFile>&)> game_added_to_cache,
std::function<void(const std::string&)> game_removed_from_cache) std::function<void(const std::string&)> game_removed_from_cache,
const std::atomic_bool& processing_halted)
{ {
// Copy game paths into a set, except ones that match DiscIO::ShouldHideFromGameList. // Copy game paths into a set, except ones that match DiscIO::ShouldHideFromGameList.
// TODO: Prevent DoFileSearch from looking inside /files/ directories of DirectoryBlobs at all? // TODO: Prevent DoFileSearch from looking inside /files/ directories of DirectoryBlobs at all?
@ -113,6 +114,9 @@ bool GameFileCache::Update(
auto end = m_cached_files.end(); auto end = m_cached_files.end();
while (it != end) while (it != end)
{ {
if (processing_halted)
break;
if (game_paths.erase((*it)->GetFilePath())) if (game_paths.erase((*it)->GetFilePath()))
{ {
++it; ++it;
@ -134,6 +138,9 @@ bool GameFileCache::Update(
// aren't in m_cached_files, so we simply add all of them to m_cached_files. // aren't in m_cached_files, so we simply add all of them to m_cached_files.
for (const std::string& path : game_paths) for (const std::string& path : game_paths)
{ {
if (processing_halted)
break;
auto file = std::make_shared<GameFile>(path); auto file = std::make_shared<GameFile>(path);
if (file->IsValid()) if (file->IsValid())
{ {
@ -149,12 +156,16 @@ bool GameFileCache::Update(
} }
bool GameFileCache::UpdateAdditionalMetadata( bool GameFileCache::UpdateAdditionalMetadata(
std::function<void(const std::shared_ptr<const GameFile>&)> game_updated) std::function<void(const std::shared_ptr<const GameFile>&)> game_updated,
const std::atomic_bool& processing_halted)
{ {
bool cache_changed = false; bool cache_changed = false;
for (std::shared_ptr<GameFile>& file : m_cached_files) for (std::shared_ptr<GameFile>& file : m_cached_files)
{ {
if (processing_halted)
break;
const bool updated = UpdateAdditionalMetadata(&file); const bool updated = UpdateAdditionalMetadata(&file);
cache_changed |= updated; cache_changed |= updated;
if (game_updated && updated) if (game_updated && updated)

View File

@ -4,6 +4,7 @@
#pragma once #pragma once
#include <atomic>
#include <cstddef> #include <cstddef>
#include <functional> #include <functional>
#include <memory> #include <memory>
@ -44,9 +45,11 @@ public:
// These functions return true if the call modified the cache. // These functions return true if the call modified the cache.
bool Update(const std::vector<std::string>& all_game_paths, bool Update(const std::vector<std::string>& all_game_paths,
std::function<void(const std::shared_ptr<const GameFile>&)> game_added_to_cache = {}, std::function<void(const std::shared_ptr<const GameFile>&)> game_added_to_cache = {},
std::function<void(const std::string&)> game_removed_from_cache = {}); std::function<void(const std::string&)> game_removed_from_cache = {},
const std::atomic_bool& processing_halted = false);
bool UpdateAdditionalMetadata( bool UpdateAdditionalMetadata(
std::function<void(const std::shared_ptr<const GameFile>&)> game_updated = {}); std::function<void(const std::shared_ptr<const GameFile>&)> game_updated = {},
const std::atomic_bool& processing_halted = false);
bool Load(); bool Load();
bool Save(); bool Save();