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;
}
[[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>
[[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())
{
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<int>(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<int>(selected_entry->dbentry->genre.size()),
selected_entry->dbentry->genre.data());
}
// release date
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_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 PreferUnorderedStringMap<u32> 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<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;
}
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<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())
{
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<char*>(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)

View File

@ -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;

View File

@ -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<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)
{
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.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"));

View File

@ -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<INISettingsInterface> 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();
}

View File

@ -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<INISettingsInterface> sif);
SettingsWindow(const std::string& path, std::string serial, DiscRegion region, const GameDatabase::Entry* entry,
std::unique_ptr<INISettingsInterface> sif);
~SettingsWindow();
static void openGamePropertiesDialog(const std::string& path, const std::string& title, const std::string& serial,