GameDatabase: Store strings as views

Saves duplicating everything in memory, and a ton of heap allocations.
This commit is contained in:
Stenzek 2024-10-13 15:46:00 +10:00
parent 86d66ddf82
commit d8fef6f22e
No known key found for this signature in database
8 changed files with 75 additions and 44 deletions

View File

@ -41,6 +41,20 @@
return true; 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<typename T> template<typename T>
[[maybe_unused]] static bool GetUIntFromObject(const ryml::ConstNodeRef& object, std::string_view key, T* dest) [[maybe_unused]] static bool GetUIntFromObject(const ryml::ConstNodeRef& object, std::string_view key, T* dest)
{ {

View File

@ -6418,12 +6418,13 @@ void FullscreenUI::DrawGameList(const ImVec2& heading_size)
if (selected_entry->dbentry && !selected_entry->dbentry->developer.empty()) if (selected_entry->dbentry && !selected_entry->dbentry->developer.empty())
{ {
text_width = text_width =
ImGui::CalcTextSize(selected_entry->dbentry->developer.c_str(), ImGui::CalcTextSize(selected_entry->dbentry->developer.data(),
selected_entry->dbentry->developer.c_str() + selected_entry->dbentry->developer.length(), selected_entry->dbentry->developer.data() + selected_entry->dbentry->developer.length(),
false, work_width) false, work_width)
.x; .x;
ImGui::SetCursorPosX((work_width - text_width) / 2.0f); ImGui::SetCursorPosX((work_width - text_width) / 2.0f);
ImGui::TextWrapped("%s", selected_entry->dbentry->developer.c_str()); ImGui::TextWrapped("%.*s", static_cast<int>(selected_entry->dbentry->developer.size()),
selected_entry->dbentry->developer.data());
} }
// code // code
@ -6452,7 +6453,10 @@ void FullscreenUI::DrawGameList(const ImVec2& heading_size)
// genre // genre
if (selected_entry->dbentry && !selected_entry->dbentry->genre.empty()) 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<int>(selected_entry->dbentry->genre.size()),
selected_entry->dbentry->genre.data());
}
// release date // release date
ImGui::Text(FSUI_CSTR("Release Date: %s"), selected_entry->GetReleaseDateString().c_str()); ImGui::Text(FSUI_CSTR("Release Date: %s"), selected_entry->GetReleaseDateString().c_str());

View File

@ -147,6 +147,7 @@ static constexpr const char* DISCDB_YAML_FILENAME = "discdb.yaml";
static bool s_loaded = false; static bool s_loaded = false;
static bool s_track_hashes_loaded = false; static bool s_track_hashes_loaded = false;
static DynamicHeapArray<u8> s_db_data; // we take strings from the data, so store a copy
static std::vector<GameDatabase::Entry> s_entries; static std::vector<GameDatabase::Entry> s_entries;
static PreferUnorderedStringMap<u32> s_code_lookup; static PreferUnorderedStringMap<u32> s_code_lookup;
@ -167,8 +168,15 @@ void GameDatabase::EnsureLoaded()
s_entries = {}; s_entries = {};
s_code_lookup = {}; s_code_lookup = {};
LoadGameDBYaml(); if (LoadGameDBYaml())
SaveToCache(); {
SaveToCache();
}
else
{
s_entries = {};
s_code_lookup = {};
}
} }
INFO_LOG("Database load of {} entries took {:.0f}ms.", s_entries.size(), timer.GetTimeMilliseconds()); 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() bool GameDatabase::LoadFromCache()
{ {
auto fp = FileSystem::OpenManagedCFile(GetCacheFile().c_str(), "rb"); Error error;
if (!fp) std::optional<DynamicHeapArray<u8>> 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; return false;
} }
BinaryFileReader reader(fp.get()); BinarySpanReader reader(db_data->cspan());
const u64 gamedb_ts = Host::GetResourceFileTimestamp("gamedb.yaml", false).value_or(0); const u64 gamedb_ts = Host::GetResourceFileTimestamp("gamedb.yaml", false).value_or(0);
u32 signature, version, num_entries, num_codes; u32 signature, version, num_entries, num_codes;
@ -950,6 +959,7 @@ bool GameDatabase::LoadFromCache()
s_code_lookup.emplace(std::move(code), index); s_code_lookup.emplace(std::move(code), index);
} }
s_db_data = std::move(db_data.value());
return true; return true;
} }
@ -1051,7 +1061,7 @@ void GameDatabase::SetRymlCallbacks()
bool GameDatabase::LoadGameDBYaml() bool GameDatabase::LoadGameDBYaml()
{ {
const std::optional<std::string> gamedb_data = Host::ReadResourceFileToString(GAMEDB_YAML_FILENAME, false); std::optional<DynamicHeapArray<u8>> gamedb_data = Host::ReadResourceFile(GAMEDB_YAML_FILENAME, false);
if (!gamedb_data.has_value()) if (!gamedb_data.has_value())
{ {
ERROR_LOG("Failed to read game database"); ERROR_LOG("Failed to read game database");
@ -1060,7 +1070,8 @@ bool GameDatabase::LoadGameDBYaml()
SetRymlCallbacks(); 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<char*>(gamedb_data->data()), gamedb_data->size()));
const ryml::ConstNodeRef root = tree.rootref(); const ryml::ConstNodeRef root = tree.rootref();
s_entries.reserve(root.num_children()); s_entries.reserve(root.num_children());
@ -1108,7 +1119,14 @@ bool GameDatabase::LoadGameDBYaml()
ERROR_LOG("Failed to insert code {}", code); 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) bool GameDatabase::ParseYamlEntry(Entry* entry, const ryml::ConstNodeRef& value)

View File

@ -92,14 +92,13 @@ enum class Language : u8
struct Entry struct Entry
{ {
// TODO: Make string_view. std::string_view serial;
std::string serial; std::string_view title;
std::string title; std::string_view genre;
std::string genre; std::string_view developer;
std::string developer; std::string_view publisher;
std::string publisher; std::string_view compatibility_version_tested;
std::string compatibility_version_tested; std::string_view compatibility_comments;
std::string compatibility_comments;
u64 release_date; u64 release_date;
u8 min_players; u8 min_players;
u8 max_players; u8 max_players;

View File

@ -415,24 +415,22 @@ QVariant GameListModel::data(const QModelIndex& index, int role, const GameList:
switch (index.column()) switch (index.column())
{ {
case Column_Serial: case Column_Serial:
return QString::fromStdString(ge->serial); return QtUtils::StringViewToQString(ge->serial);
case Column_Title: case Column_Title:
return QString::fromStdString(ge->title); return QtUtils::StringViewToQString(ge->title);
case Column_FileTitle: case Column_FileTitle:
return QtUtils::StringViewToQString(Path::GetFileTitle(ge->path)); return QtUtils::StringViewToQString(Path::GetFileTitle(ge->path));
case Column_Developer: case Column_Developer:
return (ge->dbentry && !ge->dbentry->developer.empty()) ? QString::fromStdString(ge->dbentry->developer) : return ge->dbentry ? QtUtils::StringViewToQString(ge->dbentry->developer) : QString();
QString();
case Column_Publisher: case Column_Publisher:
return (ge->dbentry && !ge->dbentry->publisher.empty()) ? QString::fromStdString(ge->dbentry->publisher) : return ge->dbentry ? QtUtils::StringViewToQString(ge->dbentry->publisher) : QString();
QString();
case Column_Genre: 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: case Column_Year:
{ {
@ -505,15 +503,13 @@ QVariant GameListModel::data(const QModelIndex& index, int role, const GameList:
return QtUtils::StringViewToQString(Path::GetFileTitle(ge->path)); return QtUtils::StringViewToQString(Path::GetFileTitle(ge->path));
case Column_Developer: case Column_Developer:
return (ge->dbentry && !ge->dbentry->developer.empty()) ? QString::fromStdString(ge->dbentry->developer) : return ge->dbentry ? QtUtils::StringViewToQString(ge->dbentry->developer) : QString();
QString();
case Column_Publisher: case Column_Publisher:
return (ge->dbentry && !ge->dbentry->publisher.empty()) ? QString::fromStdString(ge->dbentry->publisher) : return ge->dbentry ? QtUtils::StringViewToQString(ge->dbentry->publisher) : QString();
QString();
case Column_Genre: 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: case Column_Year:
return ge->dbentry ? QDateTime::fromSecsSinceEpoch(static_cast<qint64>(ge->dbentry->release_date), Qt::UTC) return ge->dbentry ? QDateTime::fromSecsSinceEpoch(static_cast<qint64>(ge->dbentry->release_date), Qt::UTC)

View File

@ -83,17 +83,17 @@ void GameSummaryWidget::populateUi(const std::string& path, const std::string& s
if (entry) if (entry)
{ {
m_ui.title->setText(QString::fromStdString(entry->title)); m_ui.title->setText(QtUtils::StringViewToQString(entry->title));
m_ui.compatibility->setCurrentIndex(static_cast<int>(entry->compatibility)); m_ui.compatibility->setCurrentIndex(static_cast<int>(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) if (!entry->developer.empty() && !entry->publisher.empty() && entry->developer != entry->publisher)
m_ui.developer->setText(tr("%1 (Published by %2)") m_ui.developer->setText(tr("%1 (Published by %2)")
.arg(QString::fromStdString(entry->developer)) .arg(QtUtils::StringViewToQString(entry->developer))
.arg(QString::fromStdString(entry->publisher))); .arg(QtUtils::StringViewToQString(entry->publisher)));
else if (!entry->developer.empty()) 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()) 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 else
m_ui.developer->setText(tr("Unknown")); m_ui.developer->setText(tr("Unknown"));

View File

@ -46,7 +46,7 @@ SettingsWindow::SettingsWindow() : QWidget()
connectUi(); 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<INISettingsInterface> sif) const GameDatabase::Entry* entry, std::unique_ptr<INISettingsInterface> sif)
: QWidget(), m_sif(std::move(sif)), m_database_entry(entry) : 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); std::string ini_filename = System::GetGameSettingsPath(real_serial);
// check for an existing dialog with this crc // 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())) if (FileSystem::FileExists(sif->GetFileName().c_str()))
sif->Load(); 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(); dialog->show();
} }

View File

@ -41,8 +41,8 @@ class SettingsWindow final : public QWidget
public: public:
SettingsWindow(); SettingsWindow();
SettingsWindow(const std::string& path, const std::string& serial, DiscRegion region, SettingsWindow(const std::string& path, std::string serial, DiscRegion region, const GameDatabase::Entry* entry,
const GameDatabase::Entry* entry, std::unique_ptr<INISettingsInterface> sif); std::unique_ptr<INISettingsInterface> sif);
~SettingsWindow(); ~SettingsWindow();
static void openGamePropertiesDialog(const std::string& path, const std::string& title, const std::string& serial, static void openGamePropertiesDialog(const std::string& path, const std::string& title, const std::string& serial,