diff options
| author | 2017-05-10 18:44:06 -0700 | |
|---|---|---|
| committer | 2017-05-10 18:44:06 -0700 | |
| commit | db22b88feab5840a1e4ac474f72cd1626006db1c (patch) | |
| tree | eee29b8879414d8bbb1c2e8f933e78d0cb2c4354 /src/citra_qt/game_list.cpp | |
| 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/citra_qt/game_list.cpp')
| -rw-r--r-- | src/citra_qt/game_list.cpp | 68 |
1 files changed, 26 insertions, 42 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() { |