summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar James Rowe2017-04-17 20:53:40 -0600
committerGravatar James Rowe2017-05-10 09:37:44 -0600
commitfc2f7b0df6b2429d45b169b51ce8d5c71ef68a4b (patch)
tree6989ddbc667f619badd214955741a0967dd97a8f /src
parentMerge pull request #2689 from yuriks/remove-disassembler (diff)
downloadyuzu-fc2f7b0df6b2429d45b169b51ce8d5c71ef68a4b.tar.gz
yuzu-fc2f7b0df6b2429d45b169b51ce8d5c71ef68a4b.tar.xz
yuzu-fc2f7b0df6b2429d45b169b51ce8d5c71ef68a4b.zip
Frontend: Prevent FileSystemWatcher from blocking UI thread
Instead of tying the QFileSystemWatcher to the GameList and updating in the UI thread, this change moves it to the worker thread. Since it gets deleted and recreated as part of the worker thread, this prevents it from ever getting used from multiple threads (which is why it was originally done on the UI thread)
Diffstat (limited to 'src')
-rw-r--r--src/citra_qt/game_list.cpp68
-rw-r--r--src/citra_qt/game_list.h5
-rw-r--r--src/citra_qt/game_list_p.h8
3 files changed, 35 insertions, 46 deletions
diff --git a/src/citra_qt/game_list.cpp b/src/citra_qt/game_list.cpp
index 51257520b..a8e3541cd 100644
--- a/src/citra_qt/game_list.cpp
+++ b/src/citra_qt/game_list.cpp
@@ -2,6 +2,7 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <QApplication>
5#include <QFileInfo> 6#include <QFileInfo>
6#include <QHeaderView> 7#include <QHeaderView>
7#include <QKeyEvent> 8#include <QKeyEvent>
@@ -194,6 +195,9 @@ void GameList::onFilterCloseClicked() {
194} 195}
195 196
196GameList::GameList(GMainWindow* parent) : QWidget{parent} { 197GameList::GameList(GMainWindow* parent) : QWidget{parent} {
198 watcher = new QFileSystemWatcher(this);
199 connect(watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory);
200
197 this->main_window = parent; 201 this->main_window = parent;
198 layout = new QVBoxLayout; 202 layout = new QVBoxLayout;
199 tree_view = new QTreeView; 203 tree_view = new QTreeView;
@@ -218,7 +222,6 @@ GameList::GameList(GMainWindow* parent) : QWidget{parent} {
218 222
219 connect(tree_view, &QTreeView::activated, this, &GameList::ValidateEntry); 223 connect(tree_view, &QTreeView::activated, this, &GameList::ValidateEntry);
220 connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu); 224 connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu);
221 connect(&watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory);
222 225
223 // We must register all custom types with the Qt Automoc system so that we are able to use it 226 // We must register all custom types with the Qt Automoc system so that we are able to use it
224 // with signals/slots. In this case, QList falls under the umbrells of custom types. 227 // with signals/slots. In this case, QList falls under the umbrells of custom types.
@@ -269,7 +272,22 @@ void GameList::ValidateEntry(const QModelIndex& item) {
269 emit GameChosen(file_path); 272 emit GameChosen(file_path);
270} 273}
271 274
272void GameList::DonePopulating() { 275void GameList::DonePopulating(QStringList watch_list) {
276 // Clear out the old directories to watch for changes and add the new ones
277 auto watch_dirs = watcher->directories();
278 if (!watch_dirs.isEmpty()) {
279 watcher->removePaths(watch_dirs);
280 }
281 // Workaround: Add the watch paths in chunks to allow the gui to refresh
282 // This prevents the UI from stalling when a large number of watch paths are added
283 // Also artificially caps the watcher to a certain number of directories
284 constexpr int LIMIT_WATCH_DIRECTORIES = 5000;
285 constexpr int SLICE_SIZE = 25;
286 int len = std::min(watch_list.length(), LIMIT_WATCH_DIRECTORIES);
287 for (int i = 0; i < len; i += SLICE_SIZE) {
288 watcher->addPaths(watch_list.mid(i, i + SLICE_SIZE));
289 QCoreApplication::processEvents();
290 }
273 tree_view->setEnabled(true); 291 tree_view->setEnabled(true);
274 int rowCount = tree_view->model()->rowCount(); 292 int rowCount = tree_view->model()->rowCount();
275 search_field->setFilterResult(rowCount, rowCount); 293 search_field->setFilterResult(rowCount, rowCount);
@@ -309,11 +327,6 @@ void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) {
309 327
310 emit ShouldCancelWorker(); 328 emit ShouldCancelWorker();
311 329
312 auto watch_dirs = watcher.directories();
313 if (!watch_dirs.isEmpty()) {
314 watcher.removePaths(watch_dirs);
315 }
316 UpdateWatcherList(dir_path.toStdString(), deep_scan ? 256 : 0);
317 GameListWorker* worker = new GameListWorker(dir_path, deep_scan); 330 GameListWorker* worker = new GameListWorker(dir_path, deep_scan);
318 331
319 connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection); 332 connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection);
@@ -359,38 +372,6 @@ void GameList::RefreshGameDirectory() {
359 } 372 }
360} 373}
361 374
362/**
363 * Adds the game list folder to the QFileSystemWatcher to check for updates.
364 *
365 * The file watcher will fire off an update to the game list when a change is detected in the game
366 * list folder.
367 *
368 * Notice: This method is run on the UI thread because QFileSystemWatcher is not thread safe and
369 * this function is fast enough to not stall the UI thread. If performance is an issue, it should
370 * be moved to another thread and properly locked to prevent concurrency issues.
371 *
372 * @param dir folder to check for changes in
373 * @param recursion 0 if recursion is disabled. Any positive number passed to this will add each
374 * directory recursively to the watcher and will update the file list if any of the folders
375 * change. The number determines how deep the recursion should traverse.
376 */
377void GameList::UpdateWatcherList(const std::string& dir, unsigned int recursion) {
378 const auto callback = [this, recursion](unsigned* num_entries_out, const std::string& directory,
379 const std::string& virtual_name) -> bool {
380 std::string physical_name = directory + DIR_SEP + virtual_name;
381
382 if (FileUtil::IsDirectory(physical_name)) {
383 UpdateWatcherList(physical_name, recursion - 1);
384 }
385 return true;
386 };
387
388 watcher.addPath(QString::fromStdString(dir));
389 if (recursion > 0) {
390 FileUtil::ForeachDirectoryEntry(nullptr, dir, callback);
391 }
392}
393
394void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) { 375void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) {
395 const auto callback = [this, recursion](unsigned* num_entries_out, const std::string& directory, 376 const auto callback = [this, recursion](unsigned* num_entries_out, const std::string& directory,
396 const std::string& virtual_name) -> bool { 377 const std::string& virtual_name) -> bool {
@@ -399,7 +380,8 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
399 if (stop_processing) 380 if (stop_processing)
400 return false; // Breaks the callback loop. 381 return false; // Breaks the callback loop.
401 382
402 if (!FileUtil::IsDirectory(physical_name) && HasSupportedFileExtension(physical_name)) { 383 bool is_dir = FileUtil::IsDirectory(physical_name);
384 if (!is_dir && HasSupportedFileExtension(physical_name)) {
403 std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(physical_name); 385 std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(physical_name);
404 if (!loader) 386 if (!loader)
405 return true; 387 return true;
@@ -416,7 +398,8 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
416 QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))), 398 QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
417 new GameListItemSize(FileUtil::GetSize(physical_name)), 399 new GameListItemSize(FileUtil::GetSize(physical_name)),
418 }); 400 });
419 } else if (recursion > 0) { 401 } else if (is_dir && recursion > 0) {
402 watch_list.append(QString::fromStdString(physical_name));
420 AddFstEntriesToGameList(physical_name, recursion - 1); 403 AddFstEntriesToGameList(physical_name, recursion - 1);
421 } 404 }
422 405
@@ -428,8 +411,9 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
428 411
429void GameListWorker::run() { 412void GameListWorker::run() {
430 stop_processing = false; 413 stop_processing = false;
414 watch_list.append(dir_path);
431 AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0); 415 AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0);
432 emit Finished(); 416 emit Finished(watch_list);
433} 417}
434 418
435void GameListWorker::Cancel() { 419void GameListWorker::Cancel() {
diff --git a/src/citra_qt/game_list.h b/src/citra_qt/game_list.h
index d8f8bc5b6..4823a1296 100644
--- a/src/citra_qt/game_list.h
+++ b/src/citra_qt/game_list.h
@@ -85,10 +85,9 @@ private slots:
85private: 85private:
86 void AddEntry(const QList<QStandardItem*>& entry_items); 86 void AddEntry(const QList<QStandardItem*>& entry_items);
87 void ValidateEntry(const QModelIndex& item); 87 void ValidateEntry(const QModelIndex& item);
88 void DonePopulating(); 88 void DonePopulating(QStringList watch_list);
89 89
90 void PopupContextMenu(const QPoint& menu_location); 90 void PopupContextMenu(const QPoint& menu_location);
91 void UpdateWatcherList(const std::string& path, unsigned int recursion);
92 void RefreshGameDirectory(); 91 void RefreshGameDirectory();
93 bool containsAllWords(QString haystack, QString userinput); 92 bool containsAllWords(QString haystack, QString userinput);
94 93
@@ -98,5 +97,5 @@ private:
98 QTreeView* tree_view = nullptr; 97 QTreeView* tree_view = nullptr;
99 QStandardItemModel* item_model = nullptr; 98 QStandardItemModel* item_model = nullptr;
100 GameListWorker* current_worker = nullptr; 99 GameListWorker* current_worker = nullptr;
101 QFileSystemWatcher watcher; 100 QFileSystemWatcher* watcher = nullptr;
102}; 101};
diff --git a/src/citra_qt/game_list_p.h b/src/citra_qt/game_list_p.h
index 3c11b6dd1..d1118ff7f 100644
--- a/src/citra_qt/game_list_p.h
+++ b/src/citra_qt/game_list_p.h
@@ -170,9 +170,15 @@ signals:
170 * @param entry_items a list with `QStandardItem`s that make up the columns of the new entry. 170 * @param entry_items a list with `QStandardItem`s that make up the columns of the new entry.
171 */ 171 */
172 void EntryReady(QList<QStandardItem*> entry_items); 172 void EntryReady(QList<QStandardItem*> entry_items);
173 void Finished(); 173
174 /**
175 * After the worker has traversed the game directory looking for entries, this signal is emmited
176 * with a list of folders that should be watched for changes as well.
177 */
178 void Finished(QStringList watch_list);
174 179
175private: 180private:
181 QStringList watch_list;
176 QString dir_path; 182 QString dir_path;
177 bool deep_scan; 183 bool deep_scan;
178 std::atomic_bool stop_processing; 184 std::atomic_bool stop_processing;