From 180f22f17e2add6264615dab68b651ff06056e2a Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Tue, 23 Apr 2019 09:07:31 -0400 Subject: [PATCH 1/5] ui_settings: Add option to cache game list --- src/yuzu/configuration/config.cpp | 2 ++ src/yuzu/ui_settings.h | 1 + 2 files changed, 3 insertions(+) diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index db27da23e3..d708d67863 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -645,6 +645,7 @@ void Config::ReadUIGamelistValues() { UISettings::values.icon_size = ReadSetting(QStringLiteral("icon_size"), 64).toUInt(); UISettings::values.row_1_text_id = ReadSetting(QStringLiteral("row_1_text_id"), 3).toUInt(); UISettings::values.row_2_text_id = ReadSetting(QStringLiteral("row_2_text_id"), 2).toUInt(); + UISettings::values.cache_game_list = ReadSetting(QStringLiteral("cache_game_list"), true).toBool(); qt_config->endGroup(); } @@ -1009,6 +1010,7 @@ void Config::SaveUIGamelistValues() { WriteSetting(QStringLiteral("icon_size"), UISettings::values.icon_size, 64); WriteSetting(QStringLiteral("row_1_text_id"), UISettings::values.row_1_text_id, 3); WriteSetting(QStringLiteral("row_2_text_id"), UISettings::values.row_2_text_id, 2); + WriteSetting(QStringLiteral("cache_game_list"), UISettings::values.cache_game_list, true); qt_config->endGroup(); } diff --git a/src/yuzu/ui_settings.h b/src/yuzu/ui_settings.h index dbd318e206..a62cd69115 100644 --- a/src/yuzu/ui_settings.h +++ b/src/yuzu/ui_settings.h @@ -79,6 +79,7 @@ struct Values { uint8_t row_1_text_id; uint8_t row_2_text_id; std::atomic_bool is_game_list_reload_pending{false}; + bool cache_game_list; }; extern Values values; From f95bdb5088a4264b7174f22e95d5409b5e297c16 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Tue, 23 Apr 2019 09:08:38 -0400 Subject: [PATCH 2/5] game_list: Implement caching for game list Preserves list of add ons and the icon, which are the two costliest parts of game list population. --- src/yuzu/game_list_worker.cpp | 106 +++++++++++++++++++++++++++++++--- 1 file changed, 99 insertions(+), 7 deletions(-) diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp index 82d2826bae..bc1833289d 100644 --- a/src/yuzu/game_list_worker.cpp +++ b/src/yuzu/game_list_worker.cpp @@ -9,6 +9,7 @@ #include #include +#include #include "common/common_paths.h" #include "common/file_util.h" @@ -30,13 +31,101 @@ #include "yuzu/ui_settings.h" namespace { + +template +T GetGameListCachedObject(const std::string& filename, const std::string& ext, + const std::function& generator); + +template <> +QString GetGameListCachedObject(const std::string& filename, const std::string& ext, + const std::function& generator) { + if (!UISettings::values.cache_game_list || filename == "0000000000000000") + return generator(); + + const auto& path = FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + "game_list" + + DIR_SEP + filename + "." + ext; + + FileUtil::CreateFullPath(path); + + if (!FileUtil::Exists(path)) { + const auto str = generator(); + + std::ofstream stream(path); + if (stream) + stream << str.toStdString(); + + stream.close(); + return str; + } + + std::ifstream stream(path); + + if (stream) { + const std::string out(std::istreambuf_iterator{stream}, + std::istreambuf_iterator{}); + stream.close(); + return QString::fromStdString(out); + } + + return generator(); +} + +template <> +std::pair, std::string> GetGameListCachedObject( + const std::string& filename, const std::string& ext, + const std::function, std::string>()>& generator) { + if (!UISettings::values.cache_game_list || filename == "0000000000000000") + return generator(); + + const auto& path1 = FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + + "game_list" + DIR_SEP + filename + ".jpeg"; + const auto& path2 = FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + + "game_list" + DIR_SEP + filename + ".appname.txt"; + + FileUtil::CreateFullPath(path1); + + if (!FileUtil::Exists(path1) || !FileUtil::Exists(path2)) { + const auto [icon, nacp] = generator(); + + FileUtil::IOFile file1(path1, "wb"); + file1.Resize(icon.size()); + file1.WriteBytes(icon.data(), icon.size()); + + std::ofstream stream2(path2, std::ios::out); + if (stream2) + stream2 << nacp; + + file1.Close(); + stream2.close(); + return std::make_pair(icon, nacp); + } + + FileUtil::IOFile file1(path1, "rb"); + std::ifstream stream2(path2); + + std::vector vec(file1.GetSize()); + file1.ReadBytes(vec.data(), vec.size()); + + if (stream2 && !vec.empty()) { + const std::string out(std::istreambuf_iterator{stream2}, + std::istreambuf_iterator{}); + stream2.close(); + return std::make_pair(vec, out); + } + + return generator(); +} + void GetMetadataFromControlNCA(const FileSys::PatchManager& patch_manager, const FileSys::NCA& nca, std::vector& icon, std::string& name) { - auto [nacp, icon_file] = patch_manager.ParseControlNCA(nca); - if (icon_file != nullptr) - icon = icon_file->ReadAllBytes(); - if (nacp != nullptr) - name = nacp->GetApplicationName(); + auto res = GetGameListCachedObject, std::string>>( + fmt::format("{:016X}", patch_manager.GetTitleID()), {}, [&patch_manager, &nca] { + const auto [nacp, icon_f] = patch_manager.ParseControlNCA(nca); + return std::make_pair(icon_f->ReadAllBytes(), nacp->GetApplicationName()); + }); + + icon = std::move(res.first); + name = std::move(res.second); } bool HasSupportedFileExtension(const std::string& file_name) { @@ -114,8 +203,11 @@ QList MakeGameListEntry(const std::string& path, const std::stri }; if (UISettings::values.show_add_ons) { - list.insert( - 2, new GameListItem(FormatPatchNameVersions(patch, loader, loader.IsRomFSUpdatable()))); + const auto patch_versions = GetGameListCachedObject( + fmt::format("{:016X}", patch.GetTitleID()), "pv.txt", [&patch, &loader] { + return FormatPatchNameVersions(patch, loader, loader.IsRomFSUpdatable()); + }); + list.insert(2, new GameListItem(patch_versions)); } return list; From 944c07ac7d90b5b146af346845f457d245b32bf2 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Tue, 23 Apr 2019 09:09:02 -0400 Subject: [PATCH 3/5] yuzu: Clear partial/full game list cache when data is updated --- src/yuzu/configuration/configure_per_general.cpp | 10 ++++++++++ src/yuzu/main.cpp | 3 +++ 2 files changed, 13 insertions(+) diff --git a/src/yuzu/configuration/configure_per_general.cpp b/src/yuzu/configuration/configure_per_general.cpp index 2bdfc8e5ab..c3e68fdf5e 100644 --- a/src/yuzu/configuration/configure_per_general.cpp +++ b/src/yuzu/configuration/configure_per_general.cpp @@ -13,6 +13,8 @@ #include #include +#include "common/common_paths.h" +#include "common/file_util.h" #include "core/file_sys/control_metadata.h" #include "core/file_sys/patch_manager.h" #include "core/file_sys/xts_archive.h" @@ -79,6 +81,14 @@ void ConfigurePerGameGeneral::applyConfiguration() { disabled_addons.push_back(item.front()->text().toStdString()); } + auto current = Settings::values.disabled_addons[title_id]; + std::sort(disabled_addons.begin(), disabled_addons.end()); + std::sort(current.begin(), current.end()); + if (disabled_addons != current) { + FileUtil::Delete(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + + "game_list" + DIR_SEP + fmt::format("{:016X}.pv.txt", title_id)); + } + Settings::values.disabled_addons[title_id] = disabled_addons; } diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index cef2cc1ae8..86aed0d942 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -1396,6 +1396,9 @@ void GMainWindow::OnMenuInstallToNAND() { tr("The file was successfully installed.")); game_list->PopulateAsync(UISettings::values.game_directory_path, UISettings::values.game_directory_deepscan); + // Clear the game list cache. + FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + + DIR_SEP + "game_list"); }; const auto failed = [this]() { From 46e2ca5475854a1ae58283804d7b712594ecf461 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Sun, 26 May 2019 17:14:09 -0400 Subject: [PATCH 4/5] game_list_worker: Add better error handling to caching --- src/yuzu/configuration/config.cpp | 5 +-- src/yuzu/game_list_worker.cpp | 60 ++++++++++++++++++++----------- 2 files changed, 42 insertions(+), 23 deletions(-) diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index d708d67863..b1942bedc2 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -645,7 +645,8 @@ void Config::ReadUIGamelistValues() { UISettings::values.icon_size = ReadSetting(QStringLiteral("icon_size"), 64).toUInt(); UISettings::values.row_1_text_id = ReadSetting(QStringLiteral("row_1_text_id"), 3).toUInt(); UISettings::values.row_2_text_id = ReadSetting(QStringLiteral("row_2_text_id"), 2).toUInt(); - UISettings::values.cache_game_list = ReadSetting(QStringLiteral("cache_game_list"), true).toBool(); + UISettings::values.cache_game_list = + ReadSetting(QStringLiteral("cache_game_list"), true).toBool(); qt_config->endGroup(); } @@ -1010,7 +1011,7 @@ void Config::SaveUIGamelistValues() { WriteSetting(QStringLiteral("icon_size"), UISettings::values.icon_size, 64); WriteSetting(QStringLiteral("row_1_text_id"), UISettings::values.row_1_text_id, 3); WriteSetting(QStringLiteral("row_2_text_id"), UISettings::values.row_2_text_id, 2); - WriteSetting(QStringLiteral("cache_game_list"), UISettings::values.cache_game_list, true); + WriteSetting(QStringLiteral("cache_game_list"), UISettings::values.cache_game_list, true); qt_config->endGroup(); } diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp index bc1833289d..4d951a4e78 100644 --- a/src/yuzu/game_list_worker.cpp +++ b/src/yuzu/game_list_worker.cpp @@ -39,11 +39,12 @@ T GetGameListCachedObject(const std::string& filename, const std::string& ext, template <> QString GetGameListCachedObject(const std::string& filename, const std::string& ext, const std::function& generator) { - if (!UISettings::values.cache_game_list || filename == "0000000000000000") + if (!UISettings::values.cache_game_list || filename == "0000000000000000") { return generator(); + } - const auto& path = FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + "game_list" + - DIR_SEP + filename + "." + ext; + const auto path = FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + "game_list" + + DIR_SEP + filename + '.' + ext; FileUtil::CreateFullPath(path); @@ -51,10 +52,10 @@ QString GetGameListCachedObject(const std::string& filename, const std::string& const auto str = generator(); std::ofstream stream(path); - if (stream) + if (stream) { stream << str.toStdString(); + } - stream.close(); return str; } @@ -63,7 +64,6 @@ QString GetGameListCachedObject(const std::string& filename, const std::string& if (stream) { const std::string out(std::istreambuf_iterator{stream}, std::istreambuf_iterator{}); - stream.close(); return QString::fromStdString(out); } @@ -74,13 +74,14 @@ template <> std::pair, std::string> GetGameListCachedObject( const std::string& filename, const std::string& ext, const std::function, std::string>()>& generator) { - if (!UISettings::values.cache_game_list || filename == "0000000000000000") + if (!UISettings::values.cache_game_list || filename == "0000000000000000") { return generator(); + } - const auto& path1 = FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + - "game_list" + DIR_SEP + filename + ".jpeg"; - const auto& path2 = FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + - "game_list" + DIR_SEP + filename + ".appname.txt"; + const auto path1 = FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + "game_list" + + DIR_SEP + filename + ".jpeg"; + const auto path2 = FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + "game_list" + + DIR_SEP + filename + ".appname.txt"; FileUtil::CreateFullPath(path1); @@ -88,28 +89,48 @@ std::pair, std::string> GetGameListCachedObject( const auto [icon, nacp] = generator(); FileUtil::IOFile file1(path1, "wb"); - file1.Resize(icon.size()); - file1.WriteBytes(icon.data(), icon.size()); + if (!file1.IsOpen()) { + LOG_ERROR(Frontend, "Failed to open cache file."); + return generator(); + } + + if (!file1.Resize(icon.size())) { + LOG_ERROR(Frontend, "Failed to resize cache file to necessary size."); + return generator(); + } + + if (file1.WriteBytes(icon.data(), icon.size()) != icon.size()) { + LOG_ERROR(Frontend, "Failed to write data to cache file."); + return generator(); + } std::ofstream stream2(path2, std::ios::out); - if (stream2) + if (stream2) { stream2 << nacp; + } - file1.Close(); - stream2.close(); return std::make_pair(icon, nacp); } FileUtil::IOFile file1(path1, "rb"); std::ifstream stream2(path2); + if (!file1.IsOpen()) { + LOG_ERROR(Frontend, "Failed to open cache file for reading."); + return generator(); + } + + if (!stream2) { + LOG_ERROR(Frontend, "Failed to open cache file for reading."); + return generator(); + } + std::vector vec(file1.GetSize()); file1.ReadBytes(vec.data(), vec.size()); if (stream2 && !vec.empty()) { const std::string out(std::istreambuf_iterator{stream2}, std::istreambuf_iterator{}); - stream2.close(); return std::make_pair(vec, out); } @@ -118,14 +139,11 @@ std::pair, std::string> GetGameListCachedObject( void GetMetadataFromControlNCA(const FileSys::PatchManager& patch_manager, const FileSys::NCA& nca, std::vector& icon, std::string& name) { - auto res = GetGameListCachedObject, std::string>>( + std::tie(icon, name) = GetGameListCachedObject, std::string>>( fmt::format("{:016X}", patch_manager.GetTitleID()), {}, [&patch_manager, &nca] { const auto [nacp, icon_f] = patch_manager.ParseControlNCA(nca); return std::make_pair(icon_f->ReadAllBytes(), nacp->GetApplicationName()); }); - - icon = std::move(res.first); - name = std::move(res.second); } bool HasSupportedFileExtension(const std::string& file_name) { From 9b2d38582f1b7ad1d3c8dbc73a379cfca4fdabb2 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Thu, 30 May 2019 10:47:56 -0400 Subject: [PATCH 5/5] main: Remove extraneous comment --- src/yuzu/main.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 86aed0d942..f8a0daebd1 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -1396,7 +1396,6 @@ void GMainWindow::OnMenuInstallToNAND() { tr("The file was successfully installed.")); game_list->PopulateAsync(UISettings::values.game_directory_path, UISettings::values.game_directory_deepscan); - // Clear the game list cache. FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + "game_list"); };