summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/yuzu/game_list.cpp26
-rw-r--r--src/yuzu/game_list.h10
-rw-r--r--src/yuzu/game_list_worker.cpp102
-rw-r--r--src/yuzu/game_list_worker.h35
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
381GameList::~GameList() { 381GameList::~GameList() {
382 UnloadController(); 382 UnloadController();
383 emit ShouldCancelWorker();
384} 383}
385 384
386void GameList::SetFilterFocus() { 385void GameList::SetFilterFocus() {
@@ -397,6 +396,10 @@ void GameList::ClearFilter() {
397 search_field->clear(); 396 search_field->clear();
398} 397}
399 398
399void GameList::WorkerEvent() {
400 current_worker->ProcessEvents(this);
401}
402
400void GameList::AddDirEntry(GameListDir* entry_items) { 403void 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
853void GameList::SaveInterfaceLayout() { 849void 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
140private: 139private:
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
147private:
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
242GameListWorker::~GameListWorker() {
243 this->disconnect();
244 stop_requested.store(true);
245 processing_completed.Wait();
246}
247
248void 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
270template <typename F>
271void GameListWorker::RecordEvent(F&& func) {
272 {
273 // Lock queue to protect concurrent modification.
274 std::scoped_lock lk(lock);
238 275
239GameListWorker::~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
241void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) { 284void 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
401void GameListWorker::run() { 446void 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
432void 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 {
20class System; 21class System;
21} 22}
22 23
24class GameList;
23class QStandardItem; 25class QStandardItem;
24 26
25namespace FileSys { 27namespace 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. 51public:
50 void Cancel();
51
52signals:
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 /** 61signals:
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 */ 64private:
66 void Finished(QStringList watch_list); 65 template <typename F>
66 void RecordEvent(F&& func);
67 67
68private: 68private:
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};