diff options
| author | 2023-10-29 11:25:22 -0400 | |
|---|---|---|
| committer | 2023-10-29 11:25:22 -0400 | |
| commit | 911d2216be8c46b7c6106e26872110f3343d28fb (patch) | |
| tree | 2bee115541d8ec0042762554e5a63ddc72483c38 /src | |
| parent | Merge pull request #11862 from liamwhite/pascal-robust (diff) | |
| parent | qt: fix game list shutdown crash (diff) | |
| download | yuzu-911d2216be8c46b7c6106e26872110f3343d28fb.tar.gz yuzu-911d2216be8c46b7c6106e26872110f3343d28fb.tar.xz yuzu-911d2216be8c46b7c6106e26872110f3343d28fb.zip | |
Merge pull request #11866 from liamwhite/more-qt-nonsense
qt: fix game list shutdown crash
Diffstat (limited to 'src')
| -rw-r--r-- | src/yuzu/game_list.cpp | 26 | ||||
| -rw-r--r-- | src/yuzu/game_list.h | 10 | ||||
| -rw-r--r-- | src/yuzu/game_list_worker.cpp | 102 | ||||
| -rw-r--r-- | src/yuzu/game_list_worker.h | 35 |
4 files changed, 112 insertions, 61 deletions
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index 2bb1a0239..7e7d8e252 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp | |||
| @@ -380,7 +380,6 @@ void GameList::UnloadController() { | |||
| 380 | 380 | ||
| 381 | GameList::~GameList() { | 381 | GameList::~GameList() { |
| 382 | UnloadController(); | 382 | UnloadController(); |
| 383 | emit ShouldCancelWorker(); | ||
| 384 | } | 383 | } |
| 385 | 384 | ||
| 386 | void GameList::SetFilterFocus() { | 385 | void GameList::SetFilterFocus() { |
| @@ -397,6 +396,10 @@ void GameList::ClearFilter() { | |||
| 397 | search_field->clear(); | 396 | search_field->clear(); |
| 398 | } | 397 | } |
| 399 | 398 | ||
| 399 | void GameList::WorkerEvent() { | ||
| 400 | current_worker->ProcessEvents(this); | ||
| 401 | } | ||
| 402 | |||
| 400 | void GameList::AddDirEntry(GameListDir* entry_items) { | 403 | void GameList::AddDirEntry(GameListDir* entry_items) { |
| 401 | item_model->invisibleRootItem()->appendRow(entry_items); | 404 | item_model->invisibleRootItem()->appendRow(entry_items); |
| 402 | tree_view->setExpanded( | 405 | tree_view->setExpanded( |
| @@ -826,28 +829,21 @@ void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) { | |||
| 826 | tree_view->setColumnHidden(COLUMN_SIZE, !UISettings::values.show_size); | 829 | tree_view->setColumnHidden(COLUMN_SIZE, !UISettings::values.show_size); |
| 827 | tree_view->setColumnHidden(COLUMN_PLAY_TIME, !UISettings::values.show_play_time); | 830 | tree_view->setColumnHidden(COLUMN_PLAY_TIME, !UISettings::values.show_play_time); |
| 828 | 831 | ||
| 829 | // Before deleting rows, cancel the worker so that it is not using them | 832 | // Cancel any existing worker. |
| 830 | emit ShouldCancelWorker(); | 833 | current_worker.reset(); |
| 831 | 834 | ||
| 832 | // Delete any rows that might already exist if we're repopulating | 835 | // Delete any rows that might already exist if we're repopulating |
| 833 | item_model->removeRows(0, item_model->rowCount()); | 836 | item_model->removeRows(0, item_model->rowCount()); |
| 834 | search_field->clear(); | 837 | search_field->clear(); |
| 835 | 838 | ||
| 836 | GameListWorker* worker = | 839 | current_worker = std::make_unique<GameListWorker>(vfs, provider, game_dirs, compatibility_list, |
| 837 | new GameListWorker(vfs, provider, game_dirs, compatibility_list, play_time_manager, system); | 840 | play_time_manager, system); |
| 838 | 841 | ||
| 839 | connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection); | 842 | // Get events from the worker as data becomes available |
| 840 | connect(worker, &GameListWorker::DirEntryReady, this, &GameList::AddDirEntry, | 843 | connect(current_worker.get(), &GameListWorker::DataAvailable, this, &GameList::WorkerEvent, |
| 841 | Qt::QueuedConnection); | ||
| 842 | connect(worker, &GameListWorker::Finished, this, &GameList::DonePopulating, | ||
| 843 | Qt::QueuedConnection); | 844 | Qt::QueuedConnection); |
| 844 | // Use DirectConnection here because worker->Cancel() is thread-safe and we want it to | ||
| 845 | // cancel without delay. | ||
| 846 | connect(this, &GameList::ShouldCancelWorker, worker, &GameListWorker::Cancel, | ||
| 847 | Qt::DirectConnection); | ||
| 848 | 845 | ||
| 849 | QThreadPool::globalInstance()->start(worker); | 846 | QThreadPool::globalInstance()->start(current_worker.get()); |
| 850 | current_worker = std::move(worker); | ||
| 851 | } | 847 | } |
| 852 | 848 | ||
| 853 | void GameList::SaveInterfaceLayout() { | 849 | void GameList::SaveInterfaceLayout() { |
diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h index 712570cea..563a3a35b 100644 --- a/src/yuzu/game_list.h +++ b/src/yuzu/game_list.h | |||
| @@ -109,7 +109,6 @@ signals: | |||
| 109 | void BootGame(const QString& game_path, u64 program_id, std::size_t program_index, | 109 | void BootGame(const QString& game_path, u64 program_id, std::size_t program_index, |
| 110 | StartGameType type, AmLaunchType launch_type); | 110 | StartGameType type, AmLaunchType launch_type); |
| 111 | void GameChosen(const QString& game_path, const u64 title_id = 0); | 111 | void GameChosen(const QString& game_path, const u64 title_id = 0); |
| 112 | void ShouldCancelWorker(); | ||
| 113 | void OpenFolderRequested(u64 program_id, GameListOpenTarget target, | 112 | void OpenFolderRequested(u64 program_id, GameListOpenTarget target, |
| 114 | const std::string& game_path); | 113 | const std::string& game_path); |
| 115 | void OpenTransferableShaderCacheRequested(u64 program_id); | 114 | void OpenTransferableShaderCacheRequested(u64 program_id); |
| @@ -138,11 +137,16 @@ private slots: | |||
| 138 | void OnUpdateThemedIcons(); | 137 | void OnUpdateThemedIcons(); |
| 139 | 138 | ||
| 140 | private: | 139 | private: |
| 140 | friend class GameListWorker; | ||
| 141 | void WorkerEvent(); | ||
| 142 | |||
| 141 | void AddDirEntry(GameListDir* entry_items); | 143 | void AddDirEntry(GameListDir* entry_items); |
| 142 | void AddEntry(const QList<QStandardItem*>& entry_items, GameListDir* parent); | 144 | void AddEntry(const QList<QStandardItem*>& entry_items, GameListDir* parent); |
| 143 | void ValidateEntry(const QModelIndex& item); | ||
| 144 | void DonePopulating(const QStringList& watch_list); | 145 | void DonePopulating(const QStringList& watch_list); |
| 145 | 146 | ||
| 147 | private: | ||
| 148 | void ValidateEntry(const QModelIndex& item); | ||
| 149 | |||
| 146 | void RefreshGameDirectory(); | 150 | void RefreshGameDirectory(); |
| 147 | 151 | ||
| 148 | void ToggleFavorite(u64 program_id); | 152 | void ToggleFavorite(u64 program_id); |
| @@ -165,7 +169,7 @@ private: | |||
| 165 | QVBoxLayout* layout = nullptr; | 169 | QVBoxLayout* layout = nullptr; |
| 166 | QTreeView* tree_view = nullptr; | 170 | QTreeView* tree_view = nullptr; |
| 167 | QStandardItemModel* item_model = nullptr; | 171 | QStandardItemModel* item_model = nullptr; |
| 168 | GameListWorker* current_worker = nullptr; | 172 | std::unique_ptr<GameListWorker> current_worker; |
| 169 | QFileSystemWatcher* watcher = nullptr; | 173 | QFileSystemWatcher* watcher = nullptr; |
| 170 | ControllerNavigation* controller_navigation = nullptr; | 174 | ControllerNavigation* controller_navigation = nullptr; |
| 171 | CompatibilityList compatibility_list; | 175 | CompatibilityList compatibility_list; |
diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp index 077ced12b..69be21027 100644 --- a/src/yuzu/game_list_worker.cpp +++ b/src/yuzu/game_list_worker.cpp | |||
| @@ -233,10 +233,53 @@ GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs_, | |||
| 233 | const PlayTime::PlayTimeManager& play_time_manager_, | 233 | const PlayTime::PlayTimeManager& play_time_manager_, |
| 234 | Core::System& system_) | 234 | Core::System& system_) |
| 235 | : vfs{std::move(vfs_)}, provider{provider_}, game_dirs{game_dirs_}, | 235 | : vfs{std::move(vfs_)}, provider{provider_}, game_dirs{game_dirs_}, |
| 236 | compatibility_list{compatibility_list_}, | 236 | compatibility_list{compatibility_list_}, play_time_manager{play_time_manager_}, system{ |
| 237 | play_time_manager{play_time_manager_}, system{system_} {} | 237 | system_} { |
| 238 | // We want the game list to manage our lifetime. | ||
| 239 | setAutoDelete(false); | ||
| 240 | } | ||
| 241 | |||
| 242 | GameListWorker::~GameListWorker() { | ||
| 243 | this->disconnect(); | ||
| 244 | stop_requested.store(true); | ||
| 245 | processing_completed.Wait(); | ||
| 246 | } | ||
| 247 | |||
| 248 | void GameListWorker::ProcessEvents(GameList* game_list) { | ||
| 249 | while (true) { | ||
| 250 | std::function<void(GameList*)> func; | ||
| 251 | { | ||
| 252 | // Lock queue to protect concurrent modification. | ||
| 253 | std::scoped_lock lk(lock); | ||
| 254 | |||
| 255 | // If we can't pop a function, return. | ||
| 256 | if (queued_events.empty()) { | ||
| 257 | return; | ||
| 258 | } | ||
| 259 | |||
| 260 | // Pop a function. | ||
| 261 | func = std::move(queued_events.back()); | ||
| 262 | queued_events.pop_back(); | ||
| 263 | } | ||
| 264 | |||
| 265 | // Run the function. | ||
| 266 | func(game_list); | ||
| 267 | } | ||
| 268 | } | ||
| 269 | |||
| 270 | template <typename F> | ||
| 271 | void GameListWorker::RecordEvent(F&& func) { | ||
| 272 | { | ||
| 273 | // Lock queue to protect concurrent modification. | ||
| 274 | std::scoped_lock lk(lock); | ||
| 238 | 275 | ||
| 239 | GameListWorker::~GameListWorker() = default; | 276 | // Add the function into the front of the queue. |
| 277 | queued_events.emplace_front(std::move(func)); | ||
| 278 | } | ||
| 279 | |||
| 280 | // Data now available. | ||
| 281 | emit DataAvailable(); | ||
| 282 | } | ||
| 240 | 283 | ||
| 241 | void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) { | 284 | void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) { |
| 242 | using namespace FileSys; | 285 | using namespace FileSys; |
| @@ -284,9 +327,9 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) { | |||
| 284 | GetMetadataFromControlNCA(patch, *control, icon, name); | 327 | GetMetadataFromControlNCA(patch, *control, icon, name); |
| 285 | } | 328 | } |
| 286 | 329 | ||
| 287 | emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, file->GetSize(), icon, *loader, | 330 | auto entry = MakeGameListEntry(file->GetFullPath(), name, file->GetSize(), icon, *loader, |
| 288 | program_id, compatibility_list, play_time_manager, patch), | 331 | program_id, compatibility_list, play_time_manager, patch); |
| 289 | parent_dir); | 332 | RecordEvent([=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); }); |
| 290 | } | 333 | } |
| 291 | } | 334 | } |
| 292 | 335 | ||
| @@ -360,11 +403,12 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa | |||
| 360 | const FileSys::PatchManager patch{id, system.GetFileSystemController(), | 403 | const FileSys::PatchManager patch{id, system.GetFileSystemController(), |
| 361 | system.GetContentProvider()}; | 404 | system.GetContentProvider()}; |
| 362 | 405 | ||
| 363 | emit EntryReady(MakeGameListEntry(physical_name, name, | 406 | auto entry = MakeGameListEntry( |
| 364 | Common::FS::GetSize(physical_name), icon, | 407 | physical_name, name, Common::FS::GetSize(physical_name), icon, *loader, |
| 365 | *loader, id, compatibility_list, | 408 | id, compatibility_list, play_time_manager, patch); |
| 366 | play_time_manager, patch), | 409 | |
| 367 | parent_dir); | 410 | RecordEvent( |
| 411 | [=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); }); | ||
| 368 | } | 412 | } |
| 369 | } else { | 413 | } else { |
| 370 | std::vector<u8> icon; | 414 | std::vector<u8> icon; |
| @@ -376,11 +420,12 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa | |||
| 376 | const FileSys::PatchManager patch{program_id, system.GetFileSystemController(), | 420 | const FileSys::PatchManager patch{program_id, system.GetFileSystemController(), |
| 377 | system.GetContentProvider()}; | 421 | system.GetContentProvider()}; |
| 378 | 422 | ||
| 379 | emit EntryReady(MakeGameListEntry(physical_name, name, | 423 | auto entry = MakeGameListEntry( |
| 380 | Common::FS::GetSize(physical_name), icon, | 424 | physical_name, name, Common::FS::GetSize(physical_name), icon, *loader, |
| 381 | *loader, program_id, compatibility_list, | 425 | program_id, compatibility_list, play_time_manager, patch); |
| 382 | play_time_manager, patch), | 426 | |
| 383 | parent_dir); | 427 | RecordEvent( |
| 428 | [=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); }); | ||
| 384 | } | 429 | } |
| 385 | } | 430 | } |
| 386 | } else if (is_dir) { | 431 | } else if (is_dir) { |
| @@ -399,25 +444,34 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa | |||
| 399 | } | 444 | } |
| 400 | 445 | ||
| 401 | void GameListWorker::run() { | 446 | void GameListWorker::run() { |
| 447 | watch_list.clear(); | ||
| 402 | provider->ClearAllEntries(); | 448 | provider->ClearAllEntries(); |
| 403 | 449 | ||
| 450 | const auto DirEntryReady = [&](GameListDir* game_list_dir) { | ||
| 451 | RecordEvent([=](GameList* game_list) { game_list->AddDirEntry(game_list_dir); }); | ||
| 452 | }; | ||
| 453 | |||
| 404 | for (UISettings::GameDir& game_dir : game_dirs) { | 454 | for (UISettings::GameDir& game_dir : game_dirs) { |
| 455 | if (stop_requested) { | ||
| 456 | break; | ||
| 457 | } | ||
| 458 | |||
| 405 | if (game_dir.path == QStringLiteral("SDMC")) { | 459 | if (game_dir.path == QStringLiteral("SDMC")) { |
| 406 | auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SdmcDir); | 460 | auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SdmcDir); |
| 407 | emit DirEntryReady(game_list_dir); | 461 | DirEntryReady(game_list_dir); |
| 408 | AddTitlesToGameList(game_list_dir); | 462 | AddTitlesToGameList(game_list_dir); |
| 409 | } else if (game_dir.path == QStringLiteral("UserNAND")) { | 463 | } else if (game_dir.path == QStringLiteral("UserNAND")) { |
| 410 | auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::UserNandDir); | 464 | auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::UserNandDir); |
| 411 | emit DirEntryReady(game_list_dir); | 465 | DirEntryReady(game_list_dir); |
| 412 | AddTitlesToGameList(game_list_dir); | 466 | AddTitlesToGameList(game_list_dir); |
| 413 | } else if (game_dir.path == QStringLiteral("SysNAND")) { | 467 | } else if (game_dir.path == QStringLiteral("SysNAND")) { |
| 414 | auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SysNandDir); | 468 | auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SysNandDir); |
| 415 | emit DirEntryReady(game_list_dir); | 469 | DirEntryReady(game_list_dir); |
| 416 | AddTitlesToGameList(game_list_dir); | 470 | AddTitlesToGameList(game_list_dir); |
| 417 | } else { | 471 | } else { |
| 418 | watch_list.append(game_dir.path); | 472 | watch_list.append(game_dir.path); |
| 419 | auto* const game_list_dir = new GameListDir(game_dir); | 473 | auto* const game_list_dir = new GameListDir(game_dir); |
| 420 | emit DirEntryReady(game_list_dir); | 474 | DirEntryReady(game_list_dir); |
| 421 | ScanFileSystem(ScanTarget::FillManualContentProvider, game_dir.path.toStdString(), | 475 | ScanFileSystem(ScanTarget::FillManualContentProvider, game_dir.path.toStdString(), |
| 422 | game_dir.deep_scan, game_list_dir); | 476 | game_dir.deep_scan, game_list_dir); |
| 423 | ScanFileSystem(ScanTarget::PopulateGameList, game_dir.path.toStdString(), | 477 | ScanFileSystem(ScanTarget::PopulateGameList, game_dir.path.toStdString(), |
| @@ -425,12 +479,6 @@ void GameListWorker::run() { | |||
| 425 | } | 479 | } |
| 426 | } | 480 | } |
| 427 | 481 | ||
| 428 | emit Finished(watch_list); | 482 | RecordEvent([=](GameList* game_list) { game_list->DonePopulating(watch_list); }); |
| 429 | processing_completed.Set(); | 483 | processing_completed.Set(); |
| 430 | } | 484 | } |
| 431 | |||
| 432 | void GameListWorker::Cancel() { | ||
| 433 | this->disconnect(); | ||
| 434 | stop_requested.store(true); | ||
| 435 | processing_completed.Wait(); | ||
| 436 | } | ||
diff --git a/src/yuzu/game_list_worker.h b/src/yuzu/game_list_worker.h index 54dc05e30..d5990fcde 100644 --- a/src/yuzu/game_list_worker.h +++ b/src/yuzu/game_list_worker.h | |||
| @@ -4,6 +4,7 @@ | |||
| 4 | #pragma once | 4 | #pragma once |
| 5 | 5 | ||
| 6 | #include <atomic> | 6 | #include <atomic> |
| 7 | #include <deque> | ||
| 7 | #include <memory> | 8 | #include <memory> |
| 8 | #include <string> | 9 | #include <string> |
| 9 | 10 | ||
| @@ -20,6 +21,7 @@ namespace Core { | |||
| 20 | class System; | 21 | class System; |
| 21 | } | 22 | } |
| 22 | 23 | ||
| 24 | class GameList; | ||
| 23 | class QStandardItem; | 25 | class QStandardItem; |
| 24 | 26 | ||
| 25 | namespace FileSys { | 27 | namespace FileSys { |
| @@ -46,24 +48,22 @@ public: | |||
| 46 | /// Starts the processing of directory tree information. | 48 | /// Starts the processing of directory tree information. |
| 47 | void run() override; | 49 | void run() override; |
| 48 | 50 | ||
| 49 | /// Tells the worker that it should no longer continue processing. Thread-safe. | 51 | public: |
| 50 | void Cancel(); | ||
| 51 | |||
| 52 | signals: | ||
| 53 | /** | 52 | /** |
| 54 | * The `EntryReady` signal is emitted once an entry has been prepared and is ready | 53 | * Synchronously processes any events queued by the worker. |
| 55 | * to be added to the game list. | 54 | * |
| 56 | * @param entry_items a list with `QStandardItem`s that make up the columns of the new | 55 | * AddDirEntry is called on the game list for every discovered directory. |
| 57 | * entry. | 56 | * AddEntry is called on the game list for every discovered program. |
| 57 | * DonePopulating is called on the game list when processing completes. | ||
| 58 | */ | 58 | */ |
| 59 | void DirEntryReady(GameListDir* entry_items); | 59 | void ProcessEvents(GameList* game_list); |
| 60 | void EntryReady(QList<QStandardItem*> entry_items, GameListDir* parent_dir); | ||
| 61 | 60 | ||
| 62 | /** | 61 | signals: |
| 63 | * After the worker has traversed the game directory looking for entries, this signal is | 62 | void DataAvailable(); |
| 64 | * emitted with a list of folders that should be watched for changes as well. | 63 | |
| 65 | */ | 64 | private: |
| 66 | void Finished(QStringList watch_list); | 65 | template <typename F> |
| 66 | void RecordEvent(F&& func); | ||
| 67 | 67 | ||
| 68 | private: | 68 | private: |
| 69 | void AddTitlesToGameList(GameListDir* parent_dir); | 69 | void AddTitlesToGameList(GameListDir* parent_dir); |
| @@ -84,8 +84,11 @@ private: | |||
| 84 | 84 | ||
| 85 | QStringList watch_list; | 85 | QStringList watch_list; |
| 86 | 86 | ||
| 87 | Common::Event processing_completed; | 87 | std::mutex lock; |
| 88 | std::condition_variable cv; | ||
| 89 | std::deque<std::function<void(GameList*)>> queued_events; | ||
| 88 | std::atomic_bool stop_requested = false; | 90 | std::atomic_bool stop_requested = false; |
| 91 | Common::Event processing_completed; | ||
| 89 | 92 | ||
| 90 | Core::System& system; | 93 | Core::System& system; |
| 91 | }; | 94 | }; |