diff options
| author | 2017-05-10 18:44:06 -0700 | |
|---|---|---|
| committer | 2017-05-10 18:44:06 -0700 | |
| commit | db22b88feab5840a1e4ac474f72cd1626006db1c (patch) | |
| tree | eee29b8879414d8bbb1c2e8f933e78d0cb2c4354 /src | |
| parent | Merge pull request #2676 from wwylele/irrst (diff) | |
| parent | Frontend: Prevent FileSystemWatcher from blocking UI thread (diff) | |
| download | yuzu-db22b88feab5840a1e4ac474f72cd1626006db1c.tar.gz yuzu-db22b88feab5840a1e4ac474f72cd1626006db1c.tar.xz yuzu-db22b88feab5840a1e4ac474f72cd1626006db1c.zip | |
Merge pull request #2669 from jroweboy/async_file_watcher
Frontend: Prevent FileSystemWatcher from blocking UI thread
Diffstat (limited to 'src')
| -rw-r--r-- | src/citra_qt/game_list.cpp | 68 | ||||
| -rw-r--r-- | src/citra_qt/game_list.h | 5 | ||||
| -rw-r--r-- | src/citra_qt/game_list_p.h | 8 |
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 | ||
| 196 | GameList::GameList(GMainWindow* parent) : QWidget{parent} { | 197 | GameList::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 | ||
| 272 | void GameList::DonePopulating() { | 275 | void 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 | */ | ||
| 377 | void 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 | |||
| 394 | void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) { | 375 | void 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 | ||
| 429 | void GameListWorker::run() { | 412 | void 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 | ||
| 435 | void GameListWorker::Cancel() { | 419 | void 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: | |||
| 85 | private: | 85 | private: |
| 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 | ||
| 175 | private: | 180 | private: |
| 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; |