diff --git a/Source/Core/Common/CMakeLists.txt b/Source/Core/Common/CMakeLists.txt index 1b806129fd..b1e25af45b 100644 --- a/Source/Core/Common/CMakeLists.txt +++ b/Source/Core/Common/CMakeLists.txt @@ -42,6 +42,7 @@ add_library(common ENetUtil.cpp ENetUtil.h EnumFormatter.h + EnumMap.h Event.h FileSearch.cpp FileSearch.h diff --git a/Source/Core/Common/ChunkFile.h b/Source/Core/Common/ChunkFile.h index 85507354af..8b84a4cf1c 100644 --- a/Source/Core/Common/ChunkFile.h +++ b/Source/Core/Common/ChunkFile.h @@ -27,6 +27,7 @@ #include "Common/Assert.h" #include "Common/CommonTypes.h" +#include "Common/EnumMap.h" #include "Common/Flag.h" #include "Common/Inline.h" #include "Common/Logging/Log.h" @@ -175,6 +176,12 @@ public: DoArray(x.data(), static_cast(x.size())); } + template + void DoArray(Common::EnumMap& x) + { + DoArray(x.data(), static_cast(x.size())); + } + template , int> = 0> void DoArray(T* x, u32 count) { diff --git a/Source/Core/Common/EnumFormatter.h b/Source/Core/Common/EnumFormatter.h index f590e761d4..1ab6bbeadd 100644 --- a/Source/Core/Common/EnumFormatter.h +++ b/Source/Core/Common/EnumFormatter.h @@ -3,7 +3,8 @@ #pragma once -#include +#include "Common/EnumMap.h" + #include #include @@ -41,11 +42,15 @@ * formatter() : EnumFormatter(names) {} * }; */ -template (last_member) + 1, - std::enable_if_t, bool> = true> +template class EnumFormatter { + // The second template argument is needed to avoid compile errors from ambiguity with multiple + // enums with the same number of members in GCC prior to 8. See https://godbolt.org/z/xcKaW1seW + // and https://godbolt.org/z/hz7Yqq1P5 + using T = decltype(last_member); + static_assert(std::is_enum_v); + public: constexpr auto parse(fmt::format_parse_context& ctx) { @@ -61,19 +66,19 @@ public: { const auto value_s = static_cast>(e); // Possibly signed const auto value_u = static_cast>(value_s); // Always unsigned - const bool has_name = value_s >= 0 && value_u < size && m_names[value_u] != nullptr; + const bool has_name = m_names.InBounds(e) && m_names[e] != nullptr; if (!formatting_for_shader) { if (has_name) - return fmt::format_to(ctx.out(), "{} ({})", m_names[value_u], value_s); + return fmt::format_to(ctx.out(), "{} ({})", m_names[e], value_s); else return fmt::format_to(ctx.out(), "Invalid ({})", value_s); } else { if (has_name) - return fmt::format_to(ctx.out(), "{:#x}u /* {} */", value_u, m_names[value_u]); + return fmt::format_to(ctx.out(), "{:#x}u /* {} */", value_u, m_names[e]); else return fmt::format_to(ctx.out(), "{:#x}u /* Invalid */", value_u); } @@ -81,7 +86,7 @@ public: protected: // This is needed because std::array deduces incorrectly if nullptr is included in the list - using array_type = std::array; + using array_type = Common::EnumMap; constexpr explicit EnumFormatter(const array_type names) : m_names(std::move(names)) {} diff --git a/Source/Core/Common/EnumMap.h b/Source/Core/Common/EnumMap.h new file mode 100644 index 0000000000..478ff42ec7 --- /dev/null +++ b/Source/Core/Common/EnumMap.h @@ -0,0 +1,83 @@ +// Copyright 2021 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "Common/TypeUtils.h" + +template +struct BitField; + +namespace Common +{ +// A type that allows lookup of values associated with an enum as the key. +// Designed for enums whose numeric values start at 0 and increment continuously with few gaps. +template +class EnumMap final +{ + // The third template argument is needed to avoid compile errors from ambiguity with multiple + // enums with the same number of members in GCC prior to 8. See https://godbolt.org/z/xcKaW1seW + // and https://godbolt.org/z/hz7Yqq1P5 + using T = decltype(last_member); + static_assert(std::is_enum_v); + static constexpr size_t s_size = static_cast(last_member) + 1; + + using array_type = std::array; + using iterator = typename array_type::iterator; + using const_iterator = typename array_type::const_iterator; + +public: + constexpr EnumMap() = default; + constexpr EnumMap(const EnumMap& other) = default; + constexpr EnumMap& operator=(const EnumMap& other) = default; + constexpr EnumMap(EnumMap&& other) = default; + constexpr EnumMap& operator=(EnumMap&& other) = default; + + // Constructor that accepts exactly size Vs (enforcing that all must be specified). + template ::value>> + constexpr EnumMap(T... values) : m_array{static_cast(values)...} + { + } + + constexpr const V& operator[](T key) const { return m_array[static_cast(key)]; } + constexpr V& operator[](T key) { return m_array[static_cast(key)]; } + + // These only exist to perform the safety check; without them, BitField's implicit conversion + // would work (but since BitField is used for game-generated data, we need to be careful about + // bounds-checking) + template + constexpr const V& operator[](BitField key) const + { + static_assert(1 << bits == s_size, "Unsafe indexing into EnumMap (may go out of bounds)"); + return m_array[static_cast(key.Value())]; + } + template + constexpr V& operator[](BitField key) + { + static_assert(1 << bits == s_size, "Unsafe indexing into EnumMap (may go out of bounds)"); + return m_array[static_cast(key.value())]; + } + + constexpr bool InBounds(T key) const { return static_cast(key) < s_size; } + + constexpr size_t size() const noexcept { return s_size; } + + constexpr V* data() { return m_array.data(); } + constexpr const V* data() const { return m_array.data(); } + + constexpr iterator begin() { return m_array.begin(); } + constexpr iterator end() { return m_array.end(); } + constexpr const_iterator begin() const { return m_array.begin(); } + constexpr const_iterator end() const { return m_array.end(); } + constexpr const_iterator cbegin() const { return m_array.cbegin(); } + constexpr const_iterator cend() const { return m_array.cend(); } + + constexpr void fill(const V& v) { m_array.fill(v); } + +private: + array_type m_array{}; +}; +} // namespace Common diff --git a/Source/Core/Common/TypeUtils.h b/Source/Core/Common/TypeUtils.h index c6875948c7..47077e11fb 100644 --- a/Source/Core/Common/TypeUtils.h +++ b/Source/Core/Common/TypeUtils.h @@ -3,6 +3,7 @@ #pragma once +#include #include namespace Common @@ -66,4 +67,20 @@ static_assert(std::is_same_v, Bar>); static_assert(std::is_same_v, Foo>); static_assert(!std::is_same_v, Bar>); } // namespace detail + +// Template for checking if Types is count occurrences of T. +template +struct IsNOf : std::integral_constant...> && + sizeof...(Ts) == count> +{ +}; + +static_assert(IsNOf::value); +static_assert(!IsNOf::value); +static_assert(IsNOf::value); +static_assert(!IsNOf::value); +static_assert(!IsNOf::value); +static_assert(IsNOf::value); +static_assert(IsNOf::value); // Type conversions ARE allowed +static_assert(!IsNOf::value); } // namespace Common diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index b7e9bf3b32..fc429c0ab4 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -43,6 +43,7 @@ +