diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index 2403ab75b..f1531abad 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -44,6 +44,7 @@ add_library(common
perf_scope.h
progress_callback.cpp
progress_callback.h
+ ryml_helpers.h
scoped_guard.h
settings_interface.h
sha1_digest.cpp
diff --git a/src/common/common.vcxproj b/src/common/common.vcxproj
index 54221634c..bbaab73d1 100644
--- a/src/common/common.vcxproj
+++ b/src/common/common.vcxproj
@@ -31,6 +31,7 @@
+
diff --git a/src/common/common.vcxproj.filters b/src/common/common.vcxproj.filters
index 262410fa2..a671d44e9 100644
--- a/src/common/common.vcxproj.filters
+++ b/src/common/common.vcxproj.filters
@@ -48,6 +48,7 @@
+
diff --git a/src/common/ryml_helpers.h b/src/common/ryml_helpers.h
new file mode 100644
index 000000000..8c0f6abaa
--- /dev/null
+++ b/src/common/ryml_helpers.h
@@ -0,0 +1,124 @@
+// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin
+// SPDX-License-Identifier: PolyForm-Strict-1.0.0
+
+#include "types.h"
+
+#include "ryml.hpp"
+
+#include
+#include
+
+// RapidYAML utility routines.
+
+[[maybe_unused]] ALWAYS_INLINE std::string_view to_stringview(const c4::csubstr& s)
+{
+ return std::string_view(s.data(), s.size());
+}
+
+[[maybe_unused]] ALWAYS_INLINE std::string_view to_stringview(const c4::substr& s)
+{
+ return std::string_view(s.data(), s.size());
+}
+
+[[maybe_unused]] ALWAYS_INLINE c4::csubstr to_csubstr(std::string_view sv)
+{
+ return c4::csubstr(sv.data(), sv.length());
+}
+
+[[maybe_unused]] static bool GetStringFromObject(const ryml::ConstNodeRef& object, std::string_view key,
+ std::string* dest)
+{
+ dest->clear();
+
+ const ryml::ConstNodeRef member = object.find_child(to_csubstr(key));
+ if (!member.valid())
+ return false;
+
+ const c4::csubstr val = member.val();
+ if (!val.empty())
+ dest->assign(val.data(), val.size());
+
+ return true;
+}
+
+template
+[[maybe_unused]] static bool GetUIntFromObject(const ryml::ConstNodeRef& object, std::string_view key, T* dest)
+{
+ *dest = 0;
+
+ const ryml::ConstNodeRef member = object.find_child(to_csubstr(key));
+ if (!member.valid())
+ return false;
+
+ const c4::csubstr val = member.val();
+ if (val.empty())
+ {
+ ERROR_LOG("Unexpected empty value in {}", key);
+ return false;
+ }
+
+ const std::optional opt_value = StringUtil::FromChars(to_stringview(val));
+ if (!opt_value.has_value())
+ {
+ ERROR_LOG("Unexpected non-uint value in {}", key);
+ return false;
+ }
+
+ *dest = opt_value.value();
+ return true;
+}
+
+template
+[[maybe_unused]] static std::optional GetOptionalTFromObject(const ryml::ConstNodeRef& object, std::string_view key)
+{
+ std::optional ret;
+
+ const ryml::ConstNodeRef member = object.find_child(to_csubstr(key));
+ if (member.valid())
+ {
+ const c4::csubstr val = member.val();
+ if (!val.empty())
+ {
+ ret = StringUtil::FromChars(to_stringview(val));
+ if (!ret.has_value())
+ {
+ if constexpr (std::is_floating_point_v)
+ ERROR_LOG("Unexpected non-float value in {}", key);
+ else if constexpr (std::is_integral_v)
+ ERROR_LOG("Unexpected non-int value in {}", key);
+ }
+ }
+ else
+ {
+ ERROR_LOG("Unexpected empty value in {}", key);
+ }
+ }
+
+ return ret;
+}
+
+template
+[[maybe_unused]] static std::optional
+ParseOptionalTFromObject(const ryml::ConstNodeRef& object, std::string_view key,
+ std::optional (*from_string_function)(const char* str))
+{
+ std::optional ret;
+
+ const ryml::ConstNodeRef member = object.find_child(to_csubstr(key));
+ if (member.valid())
+ {
+ const c4::csubstr val = member.val();
+ if (!val.empty())
+ {
+ ret = from_string_function(TinyString(to_stringview(val)));
+ if (!ret.has_value())
+ ERROR_LOG("Unknown value for {}: {}", key, to_stringview(val));
+ }
+ else
+ {
+ ERROR_LOG("Unexpected empty value in {}", key);
+ }
+ }
+
+ return ret;
+}
diff --git a/src/core/game_database.cpp b/src/core/game_database.cpp
index 8f8686d2a..45fd40ebe 100644
--- a/src/core/game_database.cpp
+++ b/src/core/game_database.cpp
@@ -33,6 +33,8 @@
Log_SetChannel(GameDatabase);
+#include "common/ryml_helpers.h"
+
namespace GameDatabase {
enum : u32
@@ -143,119 +145,6 @@ static PreferUnorderedStringMap s_code_lookup;
static TrackHashesMap s_track_hashes_map;
} // namespace GameDatabase
-// RapidYAML utility routines.
-
-ALWAYS_INLINE std::string_view to_stringview(const c4::csubstr& s)
-{
- return std::string_view(s.data(), s.size());
-}
-
-ALWAYS_INLINE std::string_view to_stringview(const c4::substr& s)
-{
- return std::string_view(s.data(), s.size());
-}
-
-ALWAYS_INLINE c4::csubstr to_csubstr(std::string_view sv)
-{
- return c4::csubstr(sv.data(), sv.length());
-}
-
-static bool GetStringFromObject(const ryml::ConstNodeRef& object, std::string_view key, std::string* dest)
-{
- dest->clear();
-
- const ryml::ConstNodeRef member = object.find_child(to_csubstr(key));
- if (!member.valid())
- return false;
-
- const c4::csubstr val = member.val();
- if (!val.empty())
- dest->assign(val.data(), val.size());
-
- return true;
-}
-
-template
-static bool GetUIntFromObject(const ryml::ConstNodeRef& object, std::string_view key, T* dest)
-{
- *dest = 0;
-
- const ryml::ConstNodeRef member = object.find_child(to_csubstr(key));
- if (!member.valid())
- return false;
-
- const c4::csubstr val = member.val();
- if (val.empty())
- {
- ERROR_LOG("Unexpected empty value in {}", key);
- return false;
- }
-
- const std::optional opt_value = StringUtil::FromChars(to_stringview(val));
- if (!opt_value.has_value())
- {
- ERROR_LOG("Unexpected non-uint value in {}", key);
- return false;
- }
-
- *dest = opt_value.value();
- return true;
-}
-
-template
-static std::optional GetOptionalTFromObject(const ryml::ConstNodeRef& object, std::string_view key)
-{
- std::optional ret;
-
- const ryml::ConstNodeRef member = object.find_child(to_csubstr(key));
- if (member.valid())
- {
- const c4::csubstr val = member.val();
- if (!val.empty())
- {
- ret = StringUtil::FromChars(to_stringview(val));
- if (!ret.has_value())
- {
- if constexpr (std::is_floating_point_v)
- ERROR_LOG("Unexpected non-float value in {}", key);
- else if constexpr (std::is_integral_v)
- ERROR_LOG("Unexpected non-int value in {}", key);
- }
- }
- else
- {
- ERROR_LOG("Unexpected empty value in {}", key);
- }
- }
-
- return ret;
-}
-
-template
-static std::optional ParseOptionalTFromObject(const ryml::ConstNodeRef& object, std::string_view key,
- std::optional (*from_string_function)(const char* str))
-{
- std::optional ret;
-
- const ryml::ConstNodeRef member = object.find_child(to_csubstr(key));
- if (member.valid())
- {
- const c4::csubstr val = member.val();
- if (!val.empty())
- {
- ret = from_string_function(TinyString(to_stringview(val)));
- if (!ret.has_value())
- ERROR_LOG("Unknown value for {}: {}", key, to_stringview(val));
- }
- else
- {
- ERROR_LOG("Unexpected empty value in {}", key);
- }
- }
-
- return ret;
-}
-
void GameDatabase::EnsureLoaded()
{
if (s_loaded)
@@ -1110,7 +999,7 @@ bool GameDatabase::LoadGameDBYaml()
const ryml::ConstNodeRef root = tree.rootref();
s_entries.reserve(root.num_children());
- for (const ryml::ConstNodeRef& current : root.children())
+ for (const ryml::ConstNodeRef& current : root.cchildren())
{
// TODO: binary sort
const u32 index = static_cast(s_entries.size());
@@ -1177,7 +1066,7 @@ bool GameDatabase::ParseYamlEntry(Entry* entry, const ryml::ConstNodeRef& value)
controllers.valid() && controllers.has_children())
{
bool first = true;
- for (const ryml::ConstNodeRef& controller : controllers.children())
+ for (const ryml::ConstNodeRef& controller : controllers.cchildren())
{
const std::string_view controller_str = to_stringview(controller.val());
if (controller_str.empty())
@@ -1230,7 +1119,7 @@ bool GameDatabase::ParseYamlEntry(Entry* entry, const ryml::ConstNodeRef& value)
if (const ryml::ConstNodeRef traits = value.find_child(to_csubstr("traits")); traits.valid() && traits.has_children())
{
- for (const ryml::ConstNodeRef& trait : traits.children())
+ for (const ryml::ConstNodeRef& trait : traits.cchildren())
{
const std::string_view trait_str = to_stringview(trait.val());
if (trait_str.empty())
@@ -1388,7 +1277,7 @@ bool GameDatabase::LoadTrackHashes()
s_track_hashes_map = {};
size_t serials = 0;
- for (const ryml::ConstNodeRef& current : root.children())
+ for (const ryml::ConstNodeRef& current : root.cchildren())
{
const std::string_view serial = to_stringview(current.key());
if (serial.empty() || !current.has_children())
@@ -1405,7 +1294,7 @@ bool GameDatabase::LoadTrackHashes()
}
u32 revision = 0;
- for (const ryml::ConstNodeRef& track_revisions : track_data.children())
+ for (const ryml::ConstNodeRef& track_revisions : track_data.cchildren())
{
const ryml::ConstNodeRef tracks = track_revisions.find_child(to_csubstr("tracks"));
if (!tracks.valid() || !tracks.has_children())