qt: fix game list shutdown crash
This commit is contained in:
parent
9274eaecd0
commit
79894152a8
@ -380,7 +380,6 @@ void GameList::UnloadController() {
|
||||
|
||||
GameList::~GameList() {
|
||||
UnloadController();
|
||||
emit ShouldCancelWorker();
|
||||
}
|
||||
|
||||
void GameList::SetFilterFocus() {
|
||||
@ -397,6 +396,10 @@ void GameList::ClearFilter() {
|
||||
search_field->clear();
|
||||
}
|
||||
|
||||
void GameList::WorkerEvent() {
|
||||
current_worker->ProcessEvents(this);
|
||||
}
|
||||
|
||||
void GameList::AddDirEntry(GameListDir* entry_items) {
|
||||
item_model->invisibleRootItem()->appendRow(entry_items);
|
||||
tree_view->setExpanded(
|
||||
@ -826,28 +829,21 @@ void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) {
|
||||
tree_view->setColumnHidden(COLUMN_SIZE, !UISettings::values.show_size);
|
||||
tree_view->setColumnHidden(COLUMN_PLAY_TIME, !UISettings::values.show_play_time);
|
||||
|
||||
// Before deleting rows, cancel the worker so that it is not using them
|
||||
emit ShouldCancelWorker();
|
||||
// Cancel any existing worker.
|
||||
current_worker.reset();
|
||||
|
||||
// Delete any rows that might already exist if we're repopulating
|
||||
item_model->removeRows(0, item_model->rowCount());
|
||||
search_field->clear();
|
||||
|
||||
GameListWorker* worker =
|
||||
new GameListWorker(vfs, provider, game_dirs, compatibility_list, play_time_manager, system);
|
||||
current_worker = std::make_unique<GameListWorker>(vfs, provider, game_dirs, compatibility_list,
|
||||
play_time_manager, system);
|
||||
|
||||
connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection);
|
||||
connect(worker, &GameListWorker::DirEntryReady, this, &GameList::AddDirEntry,
|
||||
// Get events from the worker as data becomes available
|
||||
connect(current_worker.get(), &GameListWorker::DataAvailable, this, &GameList::WorkerEvent,
|
||||
Qt::QueuedConnection);
|
||||
connect(worker, &GameListWorker::Finished, this, &GameList::DonePopulating,
|
||||
Qt::QueuedConnection);
|
||||
// Use DirectConnection here because worker->Cancel() is thread-safe and we want it to
|
||||
// cancel without delay.
|
||||
connect(this, &GameList::ShouldCancelWorker, worker, &GameListWorker::Cancel,
|
||||
Qt::DirectConnection);
|
||||
|
||||
QThreadPool::globalInstance()->start(worker);
|
||||
current_worker = std::move(worker);
|
||||
QThreadPool::globalInstance()->start(current_worker.get());
|
||||
}
|
||||
|
||||
void GameList::SaveInterfaceLayout() {
|
||||
|
@ -109,7 +109,6 @@ signals:
|
||||
void BootGame(const QString& game_path, u64 program_id, std::size_t program_index,
|
||||
StartGameType type, AmLaunchType launch_type);
|
||||
void GameChosen(const QString& game_path, const u64 title_id = 0);
|
||||
void ShouldCancelWorker();
|
||||
void OpenFolderRequested(u64 program_id, GameListOpenTarget target,
|
||||
const std::string& game_path);
|
||||
void OpenTransferableShaderCacheRequested(u64 program_id);
|
||||
@ -138,11 +137,16 @@ private slots:
|
||||
void OnUpdateThemedIcons();
|
||||
|
||||
private:
|
||||
friend class GameListWorker;
|
||||
void WorkerEvent();
|
||||
|
||||
void AddDirEntry(GameListDir* entry_items);
|
||||
void AddEntry(const QList<QStandardItem*>& entry_items, GameListDir* parent);
|
||||
void ValidateEntry(const QModelIndex& item);
|
||||
void DonePopulating(const QStringList& watch_list);
|
||||
|
||||
private:
|
||||
void ValidateEntry(const QModelIndex& item);
|
||||
|
||||
void RefreshGameDirectory();
|
||||
|
||||
void ToggleFavorite(u64 program_id);
|
||||
@ -165,7 +169,7 @@ private:
|
||||
QVBoxLayout* layout = nullptr;
|
||||
QTreeView* tree_view = nullptr;
|
||||
QStandardItemModel* item_model = nullptr;
|
||||
GameListWorker* current_worker = nullptr;
|
||||
std::unique_ptr<GameListWorker> current_worker;
|
||||
QFileSystemWatcher* watcher = nullptr;
|
||||
ControllerNavigation* controller_navigation = nullptr;
|
||||
CompatibilityList compatibility_list;
|
||||
|
@ -233,10 +233,53 @@ GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs_,
|
||||
const PlayTime::PlayTimeManager& play_time_manager_,
|
||||
Core::System& system_)
|
||||
: vfs{std::move(vfs_)}, provider{provider_}, game_dirs{game_dirs_},
|
||||
compatibility_list{compatibility_list_},
|
||||
play_time_manager{play_time_manager_}, system{system_} {}
|
||||
compatibility_list{compatibility_list_}, play_time_manager{play_time_manager_}, system{
|
||||
system_} {
|
||||
// We want the game list to manage our lifetime.
|
||||
setAutoDelete(false);
|
||||
}
|
||||
|
||||
GameListWorker::~GameListWorker() = default;
|
||||
GameListWorker::~GameListWorker() {
|
||||
this->disconnect();
|
||||
stop_requested.store(true);
|
||||
processing_completed.Wait();
|
||||
}
|
||||
|
||||
void GameListWorker::ProcessEvents(GameList* game_list) {
|
||||
while (true) {
|
||||
std::function<void(GameList*)> func;
|
||||
{
|
||||
// Lock queue to protect concurrent modification.
|
||||
std::scoped_lock lk(lock);
|
||||
|
||||
// If we can't pop a function, return.
|
||||
if (queued_events.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Pop a function.
|
||||
func = std::move(queued_events.back());
|
||||
queued_events.pop_back();
|
||||
}
|
||||
|
||||
// Run the function.
|
||||
func(game_list);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
void GameListWorker::RecordEvent(F&& func) {
|
||||
{
|
||||
// Lock queue to protect concurrent modification.
|
||||
std::scoped_lock lk(lock);
|
||||
|
||||
// Add the function into the front of the queue.
|
||||
queued_events.emplace_front(std::move(func));
|
||||
}
|
||||
|
||||
// Data now available.
|
||||
emit DataAvailable();
|
||||
}
|
||||
|
||||
void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) {
|
||||
using namespace FileSys;
|
||||
@ -284,9 +327,9 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) {
|
||||
GetMetadataFromControlNCA(patch, *control, icon, name);
|
||||
}
|
||||
|
||||
emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, file->GetSize(), icon, *loader,
|
||||
program_id, compatibility_list, play_time_manager, patch),
|
||||
parent_dir);
|
||||
auto entry = MakeGameListEntry(file->GetFullPath(), name, file->GetSize(), icon, *loader,
|
||||
program_id, compatibility_list, play_time_manager, patch);
|
||||
RecordEvent([=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); });
|
||||
}
|
||||
}
|
||||
|
||||
@ -360,11 +403,12 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
|
||||
const FileSys::PatchManager patch{id, system.GetFileSystemController(),
|
||||
system.GetContentProvider()};
|
||||
|
||||
emit EntryReady(MakeGameListEntry(physical_name, name,
|
||||
Common::FS::GetSize(physical_name), icon,
|
||||
*loader, id, compatibility_list,
|
||||
play_time_manager, patch),
|
||||
parent_dir);
|
||||
auto entry = MakeGameListEntry(
|
||||
physical_name, name, Common::FS::GetSize(physical_name), icon, *loader,
|
||||
id, compatibility_list, play_time_manager, patch);
|
||||
|
||||
RecordEvent(
|
||||
[=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); });
|
||||
}
|
||||
} else {
|
||||
std::vector<u8> icon;
|
||||
@ -376,11 +420,12 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
|
||||
const FileSys::PatchManager patch{program_id, system.GetFileSystemController(),
|
||||
system.GetContentProvider()};
|
||||
|
||||
emit EntryReady(MakeGameListEntry(physical_name, name,
|
||||
Common::FS::GetSize(physical_name), icon,
|
||||
*loader, program_id, compatibility_list,
|
||||
play_time_manager, patch),
|
||||
parent_dir);
|
||||
auto entry = MakeGameListEntry(
|
||||
physical_name, name, Common::FS::GetSize(physical_name), icon, *loader,
|
||||
program_id, compatibility_list, play_time_manager, patch);
|
||||
|
||||
RecordEvent(
|
||||
[=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); });
|
||||
}
|
||||
}
|
||||
} else if (is_dir) {
|
||||
@ -399,25 +444,34 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
|
||||
}
|
||||
|
||||
void GameListWorker::run() {
|
||||
watch_list.clear();
|
||||
provider->ClearAllEntries();
|
||||
|
||||
const auto DirEntryReady = [&](GameListDir* game_list_dir) {
|
||||
RecordEvent([=](GameList* game_list) { game_list->AddDirEntry(game_list_dir); });
|
||||
};
|
||||
|
||||
for (UISettings::GameDir& game_dir : game_dirs) {
|
||||
if (stop_requested) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (game_dir.path == QStringLiteral("SDMC")) {
|
||||
auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SdmcDir);
|
||||
emit DirEntryReady(game_list_dir);
|
||||
DirEntryReady(game_list_dir);
|
||||
AddTitlesToGameList(game_list_dir);
|
||||
} else if (game_dir.path == QStringLiteral("UserNAND")) {
|
||||
auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::UserNandDir);
|
||||
emit DirEntryReady(game_list_dir);
|
||||
DirEntryReady(game_list_dir);
|
||||
AddTitlesToGameList(game_list_dir);
|
||||
} else if (game_dir.path == QStringLiteral("SysNAND")) {
|
||||
auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SysNandDir);
|
||||
emit DirEntryReady(game_list_dir);
|
||||
DirEntryReady(game_list_dir);
|
||||
AddTitlesToGameList(game_list_dir);
|
||||
} else {
|
||||
watch_list.append(game_dir.path);
|
||||
auto* const game_list_dir = new GameListDir(game_dir);
|
||||
emit DirEntryReady(game_list_dir);
|
||||
DirEntryReady(game_list_dir);
|
||||
ScanFileSystem(ScanTarget::FillManualContentProvider, game_dir.path.toStdString(),
|
||||
game_dir.deep_scan, game_list_dir);
|
||||
ScanFileSystem(ScanTarget::PopulateGameList, game_dir.path.toStdString(),
|
||||
@ -425,12 +479,6 @@ void GameListWorker::run() {
|
||||
}
|
||||
}
|
||||
|
||||
emit Finished(watch_list);
|
||||
RecordEvent([=](GameList* game_list) { game_list->DonePopulating(watch_list); });
|
||||
processing_completed.Set();
|
||||
}
|
||||
|
||||
void GameListWorker::Cancel() {
|
||||
this->disconnect();
|
||||
stop_requested.store(true);
|
||||
processing_completed.Wait();
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
@ -20,6 +21,7 @@ namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
class GameList;
|
||||
class QStandardItem;
|
||||
|
||||
namespace FileSys {
|
||||
@ -46,24 +48,22 @@ public:
|
||||
/// Starts the processing of directory tree information.
|
||||
void run() override;
|
||||
|
||||
/// Tells the worker that it should no longer continue processing. Thread-safe.
|
||||
void Cancel();
|
||||
public:
|
||||
/**
|
||||
* Synchronously processes any events queued by the worker.
|
||||
*
|
||||
* AddDirEntry is called on the game list for every discovered directory.
|
||||
* AddEntry is called on the game list for every discovered program.
|
||||
* DonePopulating is called on the game list when processing completes.
|
||||
*/
|
||||
void ProcessEvents(GameList* game_list);
|
||||
|
||||
signals:
|
||||
/**
|
||||
* The `EntryReady` signal is emitted once an entry has been prepared and is ready
|
||||
* to be added to the game list.
|
||||
* @param entry_items a list with `QStandardItem`s that make up the columns of the new
|
||||
* entry.
|
||||
*/
|
||||
void DirEntryReady(GameListDir* entry_items);
|
||||
void EntryReady(QList<QStandardItem*> entry_items, GameListDir* parent_dir);
|
||||
void DataAvailable();
|
||||
|
||||
/**
|
||||
* After the worker has traversed the game directory looking for entries, this signal is
|
||||
* emitted with a list of folders that should be watched for changes as well.
|
||||
*/
|
||||
void Finished(QStringList watch_list);
|
||||
private:
|
||||
template <typename F>
|
||||
void RecordEvent(F&& func);
|
||||
|
||||
private:
|
||||
void AddTitlesToGameList(GameListDir* parent_dir);
|
||||
@ -84,8 +84,11 @@ private:
|
||||
|
||||
QStringList watch_list;
|
||||
|
||||
Common::Event processing_completed;
|
||||
std::mutex lock;
|
||||
std::condition_variable cv;
|
||||
std::deque<std::function<void(GameList*)>> queued_events;
|
||||
std::atomic_bool stop_requested = false;
|
||||
Common::Event processing_completed;
|
||||
|
||||
Core::System& system;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user