diff --git a/src/common/ryml_helpers.h b/src/common/ryml_helpers.h index ddde54969..3fad9de8d 100644 --- a/src/common/ryml_helpers.h +++ b/src/common/ryml_helpers.h @@ -41,6 +41,20 @@ return true; } +[[maybe_unused]] static bool GetStringFromObject(const ryml::ConstNodeRef& object, std::string_view key, + std::string_view* dest) +{ + const ryml::ConstNodeRef member = object.find_child(to_csubstr(key)); + if (!member.valid()) + { + *dest = std::string_view(); + return false; + } + + *dest = to_stringview(member.val()); + return true; +} + template [[maybe_unused]] static bool GetUIntFromObject(const ryml::ConstNodeRef& object, std::string_view key, T* dest) { diff --git a/src/core/fullscreen_ui.cpp b/src/core/fullscreen_ui.cpp index 05de29302..ee2b1adde 100644 --- a/src/core/fullscreen_ui.cpp +++ b/src/core/fullscreen_ui.cpp @@ -6418,12 +6418,13 @@ void FullscreenUI::DrawGameList(const ImVec2& heading_size) if (selected_entry->dbentry && !selected_entry->dbentry->developer.empty()) { text_width = - ImGui::CalcTextSize(selected_entry->dbentry->developer.c_str(), - selected_entry->dbentry->developer.c_str() + selected_entry->dbentry->developer.length(), + ImGui::CalcTextSize(selected_entry->dbentry->developer.data(), + selected_entry->dbentry->developer.data() + selected_entry->dbentry->developer.length(), false, work_width) .x; ImGui::SetCursorPosX((work_width - text_width) / 2.0f); - ImGui::TextWrapped("%s", selected_entry->dbentry->developer.c_str()); + ImGui::TextWrapped("%.*s", static_cast(selected_entry->dbentry->developer.size()), + selected_entry->dbentry->developer.data()); } // code @@ -6452,7 +6453,10 @@ void FullscreenUI::DrawGameList(const ImVec2& heading_size) // genre if (selected_entry->dbentry && !selected_entry->dbentry->genre.empty()) - ImGui::Text(FSUI_CSTR("Genre: %s"), selected_entry->dbentry->genre.c_str()); + { + ImGui::Text(FSUI_CSTR("Genre: %.*s"), static_cast(selected_entry->dbentry->genre.size()), + selected_entry->dbentry->genre.data()); + } // release date ImGui::Text(FSUI_CSTR("Release Date: %s"), selected_entry->GetReleaseDateString().c_str()); diff --git a/src/core/game_database.cpp b/src/core/game_database.cpp index 2bfa4ca1b..3b072b19d 100644 --- a/src/core/game_database.cpp +++ b/src/core/game_database.cpp @@ -147,6 +147,7 @@ static constexpr const char* DISCDB_YAML_FILENAME = "discdb.yaml"; static bool s_loaded = false; static bool s_track_hashes_loaded = false; +static DynamicHeapArray s_db_data; // we take strings from the data, so store a copy static std::vector s_entries; static PreferUnorderedStringMap s_code_lookup; @@ -167,8 +168,15 @@ void GameDatabase::EnsureLoaded() s_entries = {}; s_code_lookup = {}; - LoadGameDBYaml(); - SaveToCache(); + if (LoadGameDBYaml()) + { + SaveToCache(); + } + else + { + s_entries = {}; + s_code_lookup = {}; + } } INFO_LOG("Database load of {} entries took {:.0f}ms.", s_entries.size(), timer.GetTimeMilliseconds()); @@ -848,14 +856,15 @@ static std::string GetCacheFile() bool GameDatabase::LoadFromCache() { - auto fp = FileSystem::OpenManagedCFile(GetCacheFile().c_str(), "rb"); - if (!fp) + Error error; + std::optional> db_data = FileSystem::ReadBinaryFile(GetCacheFile().c_str(), &error); + if (!db_data.has_value()) { - DEV_LOG("Cache does not exist, loading full database."); + DEV_LOG("Failed to read cache, loading full database: {}", error.GetDescription()); return false; } - BinaryFileReader reader(fp.get()); + BinarySpanReader reader(db_data->cspan()); const u64 gamedb_ts = Host::GetResourceFileTimestamp("gamedb.yaml", false).value_or(0); u32 signature, version, num_entries, num_codes; @@ -950,6 +959,7 @@ bool GameDatabase::LoadFromCache() s_code_lookup.emplace(std::move(code), index); } + s_db_data = std::move(db_data.value()); return true; } @@ -1051,7 +1061,7 @@ void GameDatabase::SetRymlCallbacks() bool GameDatabase::LoadGameDBYaml() { - const std::optional gamedb_data = Host::ReadResourceFileToString(GAMEDB_YAML_FILENAME, false); + std::optional> gamedb_data = Host::ReadResourceFile(GAMEDB_YAML_FILENAME, false); if (!gamedb_data.has_value()) { ERROR_LOG("Failed to read game database"); @@ -1060,7 +1070,8 @@ bool GameDatabase::LoadGameDBYaml() SetRymlCallbacks(); - const ryml::Tree tree = ryml::parse_in_arena(to_csubstr(GAMEDB_YAML_FILENAME), to_csubstr(gamedb_data.value())); + const ryml::Tree tree = ryml::parse_in_place( + to_csubstr(GAMEDB_YAML_FILENAME), c4::substr(reinterpret_cast(gamedb_data->data()), gamedb_data->size())); const ryml::ConstNodeRef root = tree.rootref(); s_entries.reserve(root.num_children()); @@ -1108,7 +1119,14 @@ bool GameDatabase::LoadGameDBYaml() ERROR_LOG("Failed to insert code {}", code); } - return !s_entries.empty(); + if (s_entries.empty()) + { + ERROR_LOG("Game database is empty."); + return false; + } + + s_db_data = std::move(gamedb_data.value()); + return true; } bool GameDatabase::ParseYamlEntry(Entry* entry, const ryml::ConstNodeRef& value) diff --git a/src/core/game_database.h b/src/core/game_database.h index 958ab92e1..59f0d450d 100644 --- a/src/core/game_database.h +++ b/src/core/game_database.h @@ -92,14 +92,13 @@ enum class Language : u8 struct Entry { - // TODO: Make string_view. - std::string serial; - std::string title; - std::string genre; - std::string developer; - std::string publisher; - std::string compatibility_version_tested; - std::string compatibility_comments; + std::string_view serial; + std::string_view title; + std::string_view genre; + std::string_view developer; + std::string_view publisher; + std::string_view compatibility_version_tested; + std::string_view compatibility_comments; u64 release_date; u8 min_players; u8 max_players; diff --git a/src/duckstation-qt/gamelistmodel.cpp b/src/duckstation-qt/gamelistmodel.cpp index f2a62f9c4..3514aa636 100644 --- a/src/duckstation-qt/gamelistmodel.cpp +++ b/src/duckstation-qt/gamelistmodel.cpp @@ -415,24 +415,22 @@ QVariant GameListModel::data(const QModelIndex& index, int role, const GameList: switch (index.column()) { case Column_Serial: - return QString::fromStdString(ge->serial); + return QtUtils::StringViewToQString(ge->serial); case Column_Title: - return QString::fromStdString(ge->title); + return QtUtils::StringViewToQString(ge->title); case Column_FileTitle: return QtUtils::StringViewToQString(Path::GetFileTitle(ge->path)); case Column_Developer: - return (ge->dbentry && !ge->dbentry->developer.empty()) ? QString::fromStdString(ge->dbentry->developer) : - QString(); + return ge->dbentry ? QtUtils::StringViewToQString(ge->dbentry->developer) : QString(); case Column_Publisher: - return (ge->dbentry && !ge->dbentry->publisher.empty()) ? QString::fromStdString(ge->dbentry->publisher) : - QString(); + return ge->dbentry ? QtUtils::StringViewToQString(ge->dbentry->publisher) : QString(); case Column_Genre: - return (ge->dbentry && !ge->dbentry->genre.empty()) ? QString::fromStdString(ge->dbentry->genre) : QString(); + return ge->dbentry ? QtUtils::StringViewToQString(ge->dbentry->genre) : QString(); case Column_Year: { @@ -505,15 +503,13 @@ QVariant GameListModel::data(const QModelIndex& index, int role, const GameList: return QtUtils::StringViewToQString(Path::GetFileTitle(ge->path)); case Column_Developer: - return (ge->dbentry && !ge->dbentry->developer.empty()) ? QString::fromStdString(ge->dbentry->developer) : - QString(); + return ge->dbentry ? QtUtils::StringViewToQString(ge->dbentry->developer) : QString(); case Column_Publisher: - return (ge->dbentry && !ge->dbentry->publisher.empty()) ? QString::fromStdString(ge->dbentry->publisher) : - QString(); + return ge->dbentry ? QtUtils::StringViewToQString(ge->dbentry->publisher) : QString(); case Column_Genre: - return (ge->dbentry && !ge->dbentry->genre.empty()) ? QString::fromStdString(ge->dbentry->genre) : QString(); + return ge->dbentry ? QtUtils::StringViewToQString(ge->dbentry->genre) : QString(); case Column_Year: return ge->dbentry ? QDateTime::fromSecsSinceEpoch(static_cast(ge->dbentry->release_date), Qt::UTC) diff --git a/src/duckstation-qt/gamesummarywidget.cpp b/src/duckstation-qt/gamesummarywidget.cpp index 62f3a90c1..a19cf925c 100644 --- a/src/duckstation-qt/gamesummarywidget.cpp +++ b/src/duckstation-qt/gamesummarywidget.cpp @@ -83,17 +83,17 @@ void GameSummaryWidget::populateUi(const std::string& path, const std::string& s if (entry) { - m_ui.title->setText(QString::fromStdString(entry->title)); + m_ui.title->setText(QtUtils::StringViewToQString(entry->title)); m_ui.compatibility->setCurrentIndex(static_cast(entry->compatibility)); - m_ui.genre->setText(entry->genre.empty() ? tr("Unknown") : QString::fromStdString(entry->genre)); + m_ui.genre->setText(entry->genre.empty() ? tr("Unknown") : QtUtils::StringViewToQString(entry->genre)); if (!entry->developer.empty() && !entry->publisher.empty() && entry->developer != entry->publisher) m_ui.developer->setText(tr("%1 (Published by %2)") - .arg(QString::fromStdString(entry->developer)) - .arg(QString::fromStdString(entry->publisher))); + .arg(QtUtils::StringViewToQString(entry->developer)) + .arg(QtUtils::StringViewToQString(entry->publisher))); else if (!entry->developer.empty()) - m_ui.developer->setText(QString::fromStdString(entry->developer)); + m_ui.developer->setText(QtUtils::StringViewToQString(entry->developer)); else if (!entry->publisher.empty()) - m_ui.developer->setText(tr("Published by %1").arg(QString::fromStdString(entry->publisher))); + m_ui.developer->setText(tr("Published by %1").arg(QtUtils::StringViewToQString(entry->publisher))); else m_ui.developer->setText(tr("Unknown")); diff --git a/src/duckstation-qt/settingswindow.cpp b/src/duckstation-qt/settingswindow.cpp index ecf776017..a7b94fd6f 100644 --- a/src/duckstation-qt/settingswindow.cpp +++ b/src/duckstation-qt/settingswindow.cpp @@ -46,7 +46,7 @@ SettingsWindow::SettingsWindow() : QWidget() connectUi(); } -SettingsWindow::SettingsWindow(const std::string& path, const std::string& serial, DiscRegion region, +SettingsWindow::SettingsWindow(const std::string& path, std::string serial, DiscRegion region, const GameDatabase::Entry* entry, std::unique_ptr sif) : QWidget(), m_sif(std::move(sif)), m_database_entry(entry) { @@ -656,7 +656,7 @@ void SettingsWindow::openGamePropertiesDialog(const std::string& path, const std } } - const std::string& real_serial = dentry ? dentry->serial : serial; + std::string real_serial = dentry ? std::string(dentry->serial) : std::move(serial); std::string ini_filename = System::GetGameSettingsPath(real_serial); // check for an existing dialog with this crc @@ -677,7 +677,7 @@ void SettingsWindow::openGamePropertiesDialog(const std::string& path, const std if (FileSystem::FileExists(sif->GetFileName().c_str())) sif->Load(); - SettingsWindow* dialog = new SettingsWindow(path, real_serial, region, dentry, std::move(sif)); + SettingsWindow* dialog = new SettingsWindow(path, std::string(real_serial), region, dentry, std::move(sif)); dialog->show(); } diff --git a/src/duckstation-qt/settingswindow.h b/src/duckstation-qt/settingswindow.h index 9414abaa1..2d3a0baf4 100644 --- a/src/duckstation-qt/settingswindow.h +++ b/src/duckstation-qt/settingswindow.h @@ -41,8 +41,8 @@ class SettingsWindow final : public QWidget public: SettingsWindow(); - SettingsWindow(const std::string& path, const std::string& serial, DiscRegion region, - const GameDatabase::Entry* entry, std::unique_ptr sif); + SettingsWindow(const std::string& path, std::string serial, DiscRegion region, const GameDatabase::Entry* entry, + std::unique_ptr sif); ~SettingsWindow(); static void openGamePropertiesDialog(const std::string& path, const std::string& title, const std::string& serial,