aboutsummaryrefslogtreecommitdiff
path: root/src/openvic-simulation/types
diff options
context:
space:
mode:
author Spartan322 <Megacake1234@gmail.com>2023-12-19 03:41:57 +0100
committer Spartan322 <Megacake1234@gmail.com>2023-12-24 22:57:56 +0100
commit3770de7a03879a8ff6b8cf22b402217c19fa2b53 (patch)
tree0d77d82ab8cea8955e2b86d883d1c2fd10813717 /src/openvic-simulation/types
parent14e47d58b85f657ec1fed8abf88219f09bd3efbb (diff)
Change colour_t to be a strongly typed structure
Make RGB default of `colour_t` Distinguish RGB and ARGB colors by type and colour_traits Add `_colour` and `_argb` colour user-defined literals Add `OpenVic::utility::unreachable`
Diffstat (limited to 'src/openvic-simulation/types')
-rw-r--r--src/openvic-simulation/types/Colour.hpp441
-rw-r--r--src/openvic-simulation/types/IdentifierRegistry.cpp29
-rw-r--r--src/openvic-simulation/types/IdentifierRegistry.hpp49
3 files changed, 446 insertions, 73 deletions
diff --git a/src/openvic-simulation/types/Colour.hpp b/src/openvic-simulation/types/Colour.hpp
index 7c97b12..dc9c4c6 100644
--- a/src/openvic-simulation/types/Colour.hpp
+++ b/src/openvic-simulation/types/Colour.hpp
@@ -1,42 +1,431 @@
#pragma once
#include <algorithm>
+#include <array>
+#include <cassert>
+#include <charconv>
+#include <climits>
+#include <cmath>
+#include <compare>
#include <cstdint>
#include <iomanip>
+#include <limits>
+#include <span>
#include <sstream>
#include <string>
+#include <string_view>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+
+#include "openvic-simulation/utility/Getters.hpp"
+#include "openvic-simulation/utility/Utility.hpp"
namespace OpenVic {
+ template<typename ValueT, typename IntT>
+ struct colour_traits {
+ using value_type = ValueT;
+ using integer_type = IntT;
+ static_assert(sizeof(value_type) * 4 <= sizeof(integer_type), "value_type must be 4x smaller then colour_integer_type");
+
+ /* When colour_t is used as an identifier, NULL_COLOUR is disallowed
+ * and should be reserved as an error value.
+ * When colour_t is used in a purely graphical context, NULL_COLOUR
+ * should be allowed.
+ */
+ static constexpr integer_type null = 0;
+ static constexpr integer_type component = std::numeric_limits<ValueT>::max();
+ static constexpr integer_type component_bit_size = sizeof(ValueT) * CHAR_BIT;
+ static constexpr integer_type blue_shift = 0;
+ static constexpr integer_type green_shift = component_bit_size;
+ static constexpr integer_type red_shift = component_bit_size * 2;
+ static constexpr integer_type alpha_shift = component_bit_size * 3;
+ static constexpr bool has_alpha = true;
+
+ static constexpr integer_type make_rgb_integer(value_type red, value_type green, value_type blue) {
+ return (red << red_shift) | (green << green_shift) | (blue << blue_shift);
+ }
+ static constexpr integer_type make_argb_integer(value_type red, value_type green, value_type blue, value_type alpha) {
+ return make_rgb_integer(red, green, blue) | (alpha << alpha_shift);
+ }
+
+ static constexpr value_type red_from_integer(integer_type colour) {
+ return (colour >> red_shift) & component;
+ }
+ static constexpr value_type blue_from_integer(integer_type colour) {
+ return (colour >> blue_shift) & component;
+ }
+ static constexpr value_type green_from_integer(integer_type colour) {
+ return (colour >> green_shift) & component;
+ }
+ static constexpr value_type alpha_from_integer(integer_type colour) {
+ return (colour >> alpha_shift) & component;
+ }
+
+ static constexpr float component_to_float(value_type value) {
+ return static_cast<float>(value) / static_cast<float>(component);
+ }
+
+ static constexpr value_type component_from_float(float f, float min = 0.0f, float max = 1.0f) {
+ constexpr auto floor = [](float f) {
+ const std::int64_t i = static_cast<std::int64_t>(f);
+ return f < i ? i - 1 : i;
+ };
+
+ return floor(std::clamp(min + f * (max - min), min, max) * static_cast<float>(component));
+ }
+
+ static constexpr value_type component_from_fraction(int n, int d, float min = 0.0f, float max = 1.0f) {
+ return component_from_float(static_cast<float>(n) / static_cast<float>(d), min, max);
+ }
+
+ static constexpr float red_to_float(value_type red) {
+ return component_to_float(red);
+ }
+ static constexpr float green_to_float(value_type green) {
+ return component_to_float(green);
+ }
+ static constexpr float blue_to_float(value_type blue) {
+ return component_to_float(blue);
+ }
+ static constexpr float alpha_to_float(value_type alpha) {
+ return component_to_float(alpha);
+ }
+
+ static constexpr value_type red_from_float(float red) {
+ return component_from_float(red);
+ }
+ static constexpr value_type green_from_float(float green) {
+ return component_from_float(green);
+ }
+ static constexpr value_type blue_from_float(float blue) {
+ return component_from_float(blue);
+ }
+ static constexpr value_type alpha_from_float(float alpha) {
+ return component_from_float(alpha);
+ }
+
+ static constexpr integer_type max_rgb = make_rgb_integer(component, component, component);
+ static constexpr integer_type max_argb = make_argb_integer(component, component, component, component);
+ };
+
/* Colour represented by an unsigned integer, either 24-bit RGB or 32-bit ARGB. */
- using colour_t = uint32_t;
-
- /* When colour_t is used as an identifier, NULL_COLOUR is disallowed
- * and should be reserved as an error value.
- * When colour_t is used in a purely graphical context, NULL_COLOUR
- * should be allowed.
- */
- static constexpr colour_t NULL_COLOUR = 0, COLOUR_COMPONENT = 0xFF;
- static constexpr colour_t MAX_COLOUR_RGB = 0xFFFFFF, MAX_COLOUR_ARGB = 0xFFFFFFFF;
-
- constexpr colour_t float_to_colour_byte(float f, float min = 0.0f, float max = 1.0f) {
- return static_cast<colour_t>(std::clamp(min + f * (max - min), min, max) * 255.0f);
- }
+ template<typename ValueT, typename ColourIntT, typename ColourTraits = colour_traits<ValueT, ColourIntT>>
+ struct basic_colour_t : ReturnByValueProperty {
+ using colour_traits = ColourTraits;
+ using value_type = typename colour_traits::value_type;
+ using integer_type = typename colour_traits::integer_type;
- constexpr colour_t fraction_to_colour_byte(int n, int d, float min = 0.0f, float max = 1.0f) {
- return float_to_colour_byte(static_cast<float>(n) / static_cast<float>(d), min, max);
- }
+ static_assert(std::same_as<ValueT, value_type> && std::same_as<ColourIntT, integer_type>);
- constexpr colour_t float_to_alpha_value(float a) {
- return float_to_colour_byte(a) << 24;
- }
+ static constexpr auto max_value = colour_traits::component;
- constexpr float colour_byte_to_float(colour_t colour) {
- return std::clamp(static_cast<float>(colour) / 255.0f, 0.0f, 1.0f);
- }
+ struct empty_value {
+ constexpr empty_value() {}
+ constexpr empty_value(value_type) {}
+ constexpr operator value_type() const {
+ return max_value;
+ }
+ };
+
+ private:
+ using _alpha_t = std::conditional_t<colour_traits::has_alpha, value_type, empty_value>;
+
+ public:
+ value_type red;
+ value_type green;
+ value_type blue;
+ [[no_unique_address]] _alpha_t alpha;
+
+ static constexpr std::integral_constant<std::size_t, std::is_same_v<decltype(alpha), value_type> ? 4 : 3> size = {};
+
+ static constexpr basic_colour_t fill_as(value_type value) {
+ if constexpr (colour_traits::has_alpha) {
+ return { value, value, value, value };
+ } else {
+ return { value, value, value };
+ }
+ }
+
+ static constexpr basic_colour_t null() {
+ return fill_as(colour_traits::null);
+ }
+
+ static constexpr basic_colour_t from_integer(integer_type integer) {
+ if constexpr (colour_traits::has_alpha) {
+ return { colour_traits::red_from_integer(integer), colour_traits::green_from_integer(integer),
+ colour_traits::blue_from_integer(integer), colour_traits::alpha_from_integer(integer) };
+ } else {
+ assert(
+ colour_traits::alpha_from_integer(integer) == colour_traits::null ||
+ colour_traits::alpha_from_integer(integer) == max_value
+ );
+ return { colour_traits::red_from_integer(integer), colour_traits::green_from_integer(integer),
+ colour_traits::blue_from_integer(integer) };
+ }
+ }
+
+ static constexpr basic_colour_t
+ from_floats(float r, float g, float b, float a = colour_traits::alpha_to_float(max_value))
+ requires(colour_traits::has_alpha)
+ {
+ return { colour_traits::red_from_float(r), colour_traits::green_from_float(g), colour_traits::blue_from_float(b),
+ colour_traits::alpha_from_float(a) };
+ }
+
+ static constexpr basic_colour_t from_floats(float r, float g, float b)
+ requires(!colour_traits::has_alpha)
+ {
+ return { colour_traits::red_from_float(r), colour_traits::green_from_float(g), colour_traits::blue_from_float(b) };
+ }
+
+ constexpr integer_type as_rgb() const {
+ return colour_traits::make_rgb_integer(red, green, blue);
+ }
+
+ constexpr integer_type as_argb() const {
+ return colour_traits::make_argb_integer(red, green, blue, alpha);
+ }
+
+ constexpr basic_colour_t() : basic_colour_t { null() } {}
+
+ constexpr basic_colour_t(value_type r, value_type g, value_type b, value_type a = max_value)
+ requires(colour_traits::has_alpha)
+ : red(r), green(g), blue(b), alpha(a) {}
+
+ constexpr basic_colour_t(value_type r, value_type g, value_type b)
+ requires(!colour_traits::has_alpha)
+ : red(r), green(g), blue(b) {}
+
+ template<typename _ColourTraits>
+ requires(_ColourTraits::has_alpha && std::same_as<typename _ColourTraits::value_type, value_type> && std::same_as<typename _ColourTraits::integer_type, integer_type>)
+ explicit constexpr basic_colour_t(basic_colour_t<value_type, integer_type, _ColourTraits> const& colour)
+ requires(colour_traits::has_alpha)
+ : basic_colour_t { colour.red, colour.green, colour.blue, colour.alpha } {}
+
+ template<typename _ColourTraits>
+ requires(!_ColourTraits::has_alpha && std::same_as<typename _ColourTraits::value_type, value_type> && std::same_as<typename _ColourTraits::integer_type, integer_type>)
+ explicit constexpr basic_colour_t(
+ basic_colour_t<value_type, integer_type, _ColourTraits> const& colour, value_type a = max_value
+ )
+ requires(colour_traits::has_alpha)
+ : basic_colour_t { colour.red, colour.green, colour.blue, a } {}
+
+ template<typename _ColourTraits>
+ requires(std::same_as<typename _ColourTraits::value_type, value_type> && std::same_as<typename _ColourTraits::integer_type, integer_type>)
+ explicit constexpr basic_colour_t(basic_colour_t<value_type, integer_type, _ColourTraits> const& colour)
+ requires(!colour_traits::has_alpha)
+ : basic_colour_t { colour.red, colour.green, colour.blue } {}
+
+ constexpr explicit operator integer_type() const {
+ if constexpr (colour_traits::has_alpha) {
+ return as_argb();
+ } else {
+ return as_rgb();
+ }
+ }
- inline std::string colour_to_hex_string(colour_t colour, bool alpha = false) {
- std::ostringstream stream;
- stream << std::uppercase << std::hex << std::setfill('0') << std::setw(!alpha ? 6 : 8) << colour;
- return stream.str();
+ constexpr basic_colour_t with_red(value_type r) const {
+ if constexpr (colour_traits::has_alpha) {
+ return { r, green, blue, alpha };
+ } else {
+ return { r, green, blue };
+ }
+ }
+
+ constexpr basic_colour_t with_green(value_type g) const {
+ if constexpr (colour_traits::has_alpha) {
+ return { red, g, blue, alpha };
+ } else {
+ return { red, g, blue };
+ }
+ }
+
+ constexpr basic_colour_t with_blue(value_type b) const {
+ if constexpr (colour_traits::has_alpha) {
+ return { red, green, b, alpha };
+ } else {
+ return { red, green, b };
+ }
+ }
+
+ constexpr basic_colour_t with_alpha(value_type a) const
+ requires(colour_traits::has_alpha)
+ {
+ return { red, green, blue, a };
+ }
+
+ constexpr float redf() const {
+ return colour_traits::red_to_float(red);
+ }
+
+ constexpr float greenf() const {
+ return colour_traits::green_to_float(green);
+ }
+
+ constexpr float bluef() const {
+ return colour_traits::blue_to_float(blue);
+ }
+
+ constexpr float alphaf() const {
+ return colour_traits::alpha_to_float(alpha);
+ }
+
+ inline std::string to_hex_string(bool alpha = false) const {
+ using namespace std::string_view_literals;
+ static constexpr std::string_view digits = "0123456789ABCDEF"sv;
+ static constexpr std::size_t bits_per_digit = 4;
+ static constexpr std::size_t digit_mask = (1 << bits_per_digit) - 1;
+ static constexpr std::size_t argb_length = colour_traits::component_bit_size / bits_per_digit * 4;
+ static constexpr std::size_t rgb_length = colour_traits::component_bit_size / bits_per_digit * 3;
+
+ const std::size_t length = alpha ? argb_length : rgb_length;
+ std::array<char, argb_length> address;
+ const integer_type value = alpha ? as_argb() : as_rgb();
+
+ for (std::size_t index = 0, j = (length - 1) * bits_per_digit; index < length; ++index, j -= bits_per_digit) {
+ address[index] = digits[(value >> j) & digit_mask];
+ }
+ return { address.data(), length };
+ }
+
+ explicit operator std::string() const {
+ return to_hex_string(colour_traits::has_alpha);
+ }
+
+ friend std::ostream& operator<<(std::ostream& stream, basic_colour_t const& colour) {
+ return stream << colour.operator std::string();
+ }
+
+ constexpr bool is_null() const {
+ return *this == null();
+ }
+
+ constexpr auto operator<=>(basic_colour_t const& rhs) const = default;
+ constexpr auto operator<=>(integer_type rhs) const {
+ return operator integer_type() <=> rhs;
+ }
+
+ constexpr value_type& operator[](std::size_t index) {
+ return _array_access_helper<value_type>(*this, index);
+ }
+
+ constexpr const value_type& operator[](std::size_t index) const {
+ return _array_access_helper<const value_type>(*this, index);
+ }
+
+ private:
+ template<typename V, typename T>
+ static constexpr V& _array_access_helper(T& t, std::size_t index) {
+ switch (index) {
+ case 0: return t.red;
+ case 1: return t.green;
+ case 2: return t.blue;
+ }
+ if constexpr (size() == 4) {
+ if (index == 3) {
+ return t.alpha;
+ }
+ assert(index < 4);
+ } else {
+ assert(index < 3);
+ }
+ /* Segfault if index is out of bounds and NDEBUG is defined! */
+ OpenVic::utility::unreachable();
+ }
+
+ public:
+ template<std::size_t Index>
+ auto&& get() & {
+ return get_helper<Index>(*this);
+ }
+
+ template<std::size_t Index>
+ auto&& get() && {
+ return get_helper<Index>(*this);
+ }
+
+ template<std::size_t Index>
+ auto&& get() const& {
+ return get_helper<Index>(*this);
+ }
+
+ template<std::size_t Index>
+ auto&& get() const&& {
+ return get_helper<Index>(*this);
+ }
+
+ private:
+ template<std::size_t Index, typename T>
+ static auto&& get_helper(T&& t) {
+ static_assert(Index < size(), "Index out of bounds for OpenVic::basic_colour_t");
+ if constexpr (Index == 0) {
+ return std::forward<T>(t).red;
+ }
+ if constexpr (Index == 1) {
+ return std::forward<T>(t).green;
+ }
+ if constexpr (Index == 2) {
+ return std::forward<T>(t).blue;
+ }
+ if constexpr (Index == 3) {
+ return std::forward<T>(t).alpha;
+ }
+ }
+ };
+
+ template<typename T>
+ concept IsColour = requires(T t) {
+ { basic_colour_t { t } } -> std::same_as<T>;
+ };
+
+ template<typename ValueT, typename IntT>
+ struct rgb_colour_traits : colour_traits<ValueT, IntT> {
+ static constexpr bool has_alpha = false;
+ };
+
+ using colour_argb_t = basic_colour_t<std::uint8_t, std::uint32_t>;
+ using colour_rgb_t = basic_colour_t<std::uint8_t, std::uint32_t, rgb_colour_traits<std::uint8_t, std::uint32_t>>;
+
+ using colour_t = colour_rgb_t;
+
+ namespace colour_literals {
+ constexpr colour_t operator""_colour(unsigned long long value) {
+ return colour_t::from_integer(value);
+ }
+
+ constexpr colour_argb_t operator""_argb(unsigned long long value) {
+ return colour_argb_t::from_integer(value);
+ }
}
}
+
+namespace std {
+ template<typename ValueT, typename ColourIntT, typename ColourTraits>
+ struct tuple_size<::OpenVic::basic_colour_t<ValueT, ColourIntT, ColourTraits>>
+ : integral_constant<size_t, ::OpenVic::basic_colour_t<ValueT, ColourIntT, ColourTraits>::size()> {};
+
+ template<size_t Index, typename ValueT, typename ColourIntT, typename ColourTraits>
+ struct tuple_element<Index, ::OpenVic::basic_colour_t<ValueT, ColourIntT, ColourTraits>> {
+ using type = decltype(::OpenVic::basic_colour_t<ValueT, ColourIntT, ColourTraits>::red);
+ };
+ template<typename ValueT, typename ColourIntT, typename ColourTraits>
+ struct tuple_element<1, ::OpenVic::basic_colour_t<ValueT, ColourIntT, ColourTraits>> {
+ using type = decltype(::OpenVic::basic_colour_t<ValueT, ColourIntT, ColourTraits>::green);
+ };
+ template<typename ValueT, typename ColourIntT, typename ColourTraits>
+ struct tuple_element<2, ::OpenVic::basic_colour_t<ValueT, ColourIntT, ColourTraits>> {
+ using type = decltype(::OpenVic::basic_colour_t<ValueT, ColourIntT, ColourTraits>::blue);
+ };
+ template<typename ValueT, typename ColourIntT, typename ColourTraits>
+ struct tuple_element<3, ::OpenVic::basic_colour_t<ValueT, ColourIntT, ColourTraits>> {
+ using type = decltype(::OpenVic::basic_colour_t<ValueT, ColourIntT, ColourTraits>::alpha);
+ };
+
+ template<typename ValueT, typename ColourIntT, typename ColourTraits>
+ struct hash<OpenVic::basic_colour_t<ValueT, ColourIntT, ColourTraits>> {
+ size_t operator()(OpenVic::basic_colour_t<ValueT, ColourIntT, ColourTraits> const& s) const noexcept {
+ return hash<decltype(s.as_argb())> {}(s.as_argb());
+ }
+ };
+}
diff --git a/src/openvic-simulation/types/IdentifierRegistry.cpp b/src/openvic-simulation/types/IdentifierRegistry.cpp
deleted file mode 100644
index 3964b12..0000000
--- a/src/openvic-simulation/types/IdentifierRegistry.cpp
+++ /dev/null
@@ -1,29 +0,0 @@
-#include "IdentifierRegistry.hpp"
-
-#include <cassert>
-
-using namespace OpenVic;
-
-HasIdentifier::HasIdentifier(std::string_view new_identifier) : identifier { new_identifier } {
- assert(!identifier.empty());
-}
-
-std::ostream& OpenVic::operator<<(std::ostream& stream, HasIdentifier const& obj) {
- return stream << obj.get_identifier();
-}
-
-std::ostream& OpenVic::operator<<(std::ostream& stream, HasIdentifier const* obj) {
- return obj != nullptr ? stream << *obj : stream << "<NULL>";
-}
-
-HasColour::HasColour(colour_t new_colour, bool cannot_be_null, bool can_have_alpha) : colour(new_colour) {
- assert((!cannot_be_null || colour != NULL_COLOUR) && colour <= (!can_have_alpha ? MAX_COLOUR_RGB : MAX_COLOUR_ARGB));
-}
-
-std::string HasColour::colour_to_hex_string() const {
- return OpenVic::colour_to_hex_string(colour);
-}
-
-HasIdentifierAndColour::HasIdentifierAndColour(
- std::string_view new_identifier, colour_t new_colour, bool cannot_be_null, bool can_have_alpha
-) : HasIdentifier { new_identifier }, HasColour { new_colour, cannot_be_null, can_have_alpha } {}
diff --git a/src/openvic-simulation/types/IdentifierRegistry.hpp b/src/openvic-simulation/types/IdentifierRegistry.hpp
index 3bf52e1..efdf361 100644
--- a/src/openvic-simulation/types/IdentifierRegistry.hpp
+++ b/src/openvic-simulation/types/IdentifierRegistry.hpp
@@ -1,5 +1,6 @@
#pragma once
+#include <cassert>
#include <concepts>
#include <map>
#include <type_traits>
@@ -34,7 +35,9 @@ namespace OpenVic {
const std::string PROPERTY(identifier);
protected:
- HasIdentifier(std::string_view new_identifier);
+ HasIdentifier(std::string_view new_identifier): identifier { new_identifier } {
+ assert(!identifier.empty());
+ }
public:
HasIdentifier(HasIdentifier const&) = delete;
@@ -43,41 +46,51 @@ namespace OpenVic {
HasIdentifier& operator=(HasIdentifier&&) = delete;
};
- std::ostream& operator<<(std::ostream& stream, HasIdentifier const& obj);
- std::ostream& operator<<(std::ostream& stream, HasIdentifier const* obj);
+ inline std::ostream& operator<<(std::ostream& stream, HasIdentifier const& obj) {
+ return stream << obj.get_identifier();
+ }
+ inline std::ostream& operator<<(std::ostream& stream, HasIdentifier const* obj) {
+ return obj != nullptr ? stream << *obj : stream << "<NULL>";
+ }
/*
* Base class for objects with associated colour information.
*/
- class HasColour {
- const colour_t PROPERTY(colour);
+ template<IsColour ColourT>
+ class _HasColour {
+ const ColourT PROPERTY(colour);
protected:
- HasColour(colour_t new_colour, bool cannot_be_null, bool can_have_alpha);
+ _HasColour(ColourT new_colour, bool cannot_be_null): colour { new_colour } {
+ assert(!cannot_be_null || !colour.is_null());
+ }
public:
- HasColour(HasColour const&) = delete;
- HasColour(HasColour&&) = default;
- HasColour& operator=(HasColour const&) = delete;
- HasColour& operator=(HasColour&&) = delete;
-
- std::string colour_to_hex_string() const;
+ _HasColour(_HasColour const&) = delete;
+ _HasColour(_HasColour&&) = default;
+ _HasColour& operator=(_HasColour const&) = delete;
+ _HasColour& operator=(_HasColour&&) = delete;
};
/*
* Base class for objects with a unique string identifier and associated colour information.
*/
- class HasIdentifierAndColour : public HasIdentifier, public HasColour {
+ template<IsColour ColourT>
+ class _HasIdentifierAndColour : public HasIdentifier, public _HasColour<ColourT> {
protected:
- HasIdentifierAndColour(std::string_view new_identifier, colour_t new_colour, bool cannot_be_null, bool can_have_alpha);
+ _HasIdentifierAndColour(std::string_view new_identifier, ColourT new_colour, bool cannot_be_null)
+ : HasIdentifier { new_identifier }, _HasColour<ColourT> { new_colour, cannot_be_null } {}
public:
- HasIdentifierAndColour(HasIdentifierAndColour const&) = delete;
- HasIdentifierAndColour(HasIdentifierAndColour&&) = default;
- HasIdentifierAndColour& operator=(HasIdentifierAndColour const&) = delete;
- HasIdentifierAndColour& operator=(HasIdentifierAndColour&&) = delete;
+ _HasIdentifierAndColour(_HasIdentifierAndColour const&) = delete;
+ _HasIdentifierAndColour(_HasIdentifierAndColour&&) = default;
+ _HasIdentifierAndColour& operator=(_HasIdentifierAndColour const&) = delete;
+ _HasIdentifierAndColour& operator=(_HasIdentifierAndColour&&) = delete;
};
+ using HasIdentifierAndColour = _HasIdentifierAndColour<colour_t>;
+ using HasIdentifierAndAlphaColour = _HasIdentifierAndColour<colour_argb_t>;
+
/* Callbacks for trying to add duplicate keys via UniqueKeyRegistry::add_item */
static bool duplicate_fail_callback(std::string_view registry_name, std::string_view duplicate_identifier) {
Logger::error(