Add a macro to create bitsets from enums Adds a RIVE_MAKE_ENUM_BITFIELD() macro and accompanying EnumBitset<> class that allow us to treat strongly typed C++ enums as bitsets, including with an implicit bool conversion for expressions like "if (flags & Flags::myFlag)". Diffs= 654d4488e Add a macro to create bitsets from enums (#5922) Co-authored-by: Chris Dalton <99840794+csmartdalton@users.noreply.github.com>
diff --git a/.rive_head b/.rive_head index e430605..5706491 100644 --- a/.rive_head +++ b/.rive_head
@@ -1 +1 @@ -7bbf083ce32d92ba240dbf08b852d2702fac18ca +654d4488e936ac8c63930253b83229ef1c82618b
diff --git a/include/rive/component_dirt.hpp b/include/rive/component_dirt.hpp index a6730f8..73f465e 100644 --- a/include/rive/component_dirt.hpp +++ b/include/rive/component_dirt.hpp
@@ -1,6 +1,7 @@ #ifndef _RIVE_DIRTY_FLAGS_HPP_ #define _RIVE_DIRTY_FLAGS_HPP_ -#include <type_traits> + +#include "rive/enum_bitset.hpp" namespace rive { @@ -62,52 +63,6 @@ /// All dirty. Every flag (apart from Collapsed) is set. Filthy = 0xFFFE }; - -inline constexpr ComponentDirt operator&(ComponentDirt lhs, ComponentDirt rhs) -{ - return static_cast<ComponentDirt>(static_cast<std::underlying_type<ComponentDirt>::type>(lhs) & - static_cast<std::underlying_type<ComponentDirt>::type>(rhs)); -} - -inline constexpr ComponentDirt operator^(ComponentDirt lhs, ComponentDirt rhs) -{ - return static_cast<ComponentDirt>(static_cast<std::underlying_type<ComponentDirt>::type>(lhs) ^ - static_cast<std::underlying_type<ComponentDirt>::type>(rhs)); -} - -inline constexpr ComponentDirt operator|(ComponentDirt lhs, ComponentDirt rhs) -{ - return static_cast<ComponentDirt>(static_cast<std::underlying_type<ComponentDirt>::type>(lhs) | - static_cast<std::underlying_type<ComponentDirt>::type>(rhs)); -} - -inline constexpr ComponentDirt operator~(ComponentDirt rhs) -{ - return static_cast<ComponentDirt>(~static_cast<std::underlying_type<ComponentDirt>::type>(rhs)); -} - -inline ComponentDirt& operator|=(ComponentDirt& lhs, ComponentDirt rhs) -{ - lhs = static_cast<ComponentDirt>(static_cast<std::underlying_type<ComponentDirt>::type>(lhs) | - static_cast<std::underlying_type<ComponentDirt>::type>(rhs)); - - return lhs; -} - -inline ComponentDirt& operator&=(ComponentDirt& lhs, ComponentDirt rhs) -{ - lhs = static_cast<ComponentDirt>(static_cast<std::underlying_type<ComponentDirt>::type>(lhs) & - static_cast<std::underlying_type<ComponentDirt>::type>(rhs)); - - return lhs; -} - -inline ComponentDirt& operator^=(ComponentDirt& lhs, ComponentDirt rhs) -{ - lhs = static_cast<ComponentDirt>(static_cast<std::underlying_type<ComponentDirt>::type>(lhs) ^ - static_cast<std::underlying_type<ComponentDirt>::type>(rhs)); - - return lhs; -} +RIVE_MAKE_ENUM_BITSET(ComponentDirt) } // namespace rive -#endif \ No newline at end of file +#endif
diff --git a/include/rive/enum_bitset.hpp b/include/rive/enum_bitset.hpp new file mode 100644 index 0000000..6461bdd --- /dev/null +++ b/include/rive/enum_bitset.hpp
@@ -0,0 +1,80 @@ +/* + * Copyright 2023 Rive + */ + +#ifndef _RIVE_ENUM_BITSET_HPP_ +#define _RIVE_ENUM_BITSET_HPP_ + +#include <type_traits> + +namespace rive +{ +// Wraps an enum containing a bitfield so that we can do implicit bool conversions. +template <typename T> class EnumBitset +{ +public: + using UnderlyingType = typename std::underlying_type<T>::type; + constexpr EnumBitset() = default; + constexpr EnumBitset(T value) : m_bits(static_cast<UnderlyingType>(value)) {} + constexpr UnderlyingType bits() const { return m_bits; } + constexpr operator T() const { return static_cast<T>(m_bits); } + constexpr operator bool() const { return m_bits != 0; } + +private: + UnderlyingType m_bits = 0; +}; +} // namespace rive + +#define RIVE_MAKE_ENUM_BITSET(ENUM) \ + constexpr inline ::rive::EnumBitset<ENUM> operator&(::rive::EnumBitset<ENUM> lhs, \ + ::rive::EnumBitset<ENUM> rhs) \ + { \ + return static_cast<ENUM>(lhs.bits() & rhs.bits()); \ + } \ + constexpr inline ::rive::EnumBitset<ENUM> operator&(ENUM lhs, ::rive::EnumBitset<ENUM> rhs) \ + { \ + return ::rive::EnumBitset<ENUM>(lhs) & rhs; \ + } \ + constexpr inline ::rive::EnumBitset<ENUM> operator&(::rive::EnumBitset<ENUM> lhs, ENUM rhs) \ + { \ + return lhs & ::rive::EnumBitset<ENUM>(rhs); \ + } \ + constexpr inline ::rive::EnumBitset<ENUM> operator&(ENUM lhs, ENUM rhs) \ + { \ + return ::rive::EnumBitset<ENUM>(lhs) & ::rive::EnumBitset<ENUM>(rhs); \ + } \ + \ + constexpr inline ::rive::EnumBitset<ENUM> operator|(::rive::EnumBitset<ENUM> lhs, \ + ::rive::EnumBitset<ENUM> rhs) \ + { \ + return static_cast<ENUM>(lhs.bits() | rhs.bits()); \ + } \ + constexpr inline ::rive::EnumBitset<ENUM> operator|(ENUM lhs, ::rive::EnumBitset<ENUM> rhs) \ + { \ + return ::rive::EnumBitset<ENUM>(lhs) | rhs; \ + } \ + constexpr inline ::rive::EnumBitset<ENUM> operator|(::rive::EnumBitset<ENUM> lhs, ENUM rhs) \ + { \ + return lhs | ::rive::EnumBitset<ENUM>(rhs); \ + } \ + constexpr inline ::rive::EnumBitset<ENUM> operator|(ENUM lhs, ENUM rhs) \ + { \ + return ::rive::EnumBitset<ENUM>(lhs) | ::rive::EnumBitset<ENUM>(rhs); \ + } \ + \ + constexpr inline ::rive::EnumBitset<ENUM> operator~(::rive::EnumBitset<ENUM> rhs) \ + { \ + return static_cast<ENUM>(~rhs.bits()); \ + } \ + constexpr inline ::rive::EnumBitset<ENUM> operator~(ENUM rhs) \ + { \ + return ~::rive::EnumBitset<ENUM>(rhs); \ + } \ + \ + inline ENUM& operator&=(ENUM& lhs, ::rive::EnumBitset<ENUM> rhs) { return lhs = lhs & rhs; } \ + inline ENUM& operator&=(ENUM& lhs, ENUM rhs) { return lhs = lhs & rhs; } \ + \ + inline ENUM& operator|=(ENUM& lhs, ::rive::EnumBitset<ENUM> rhs) { return lhs = lhs | rhs; } \ + inline ENUM& operator|=(ENUM& lhs, ENUM rhs) { return lhs = lhs | rhs; } + +#endif
diff --git a/include/rive/joystick.hpp b/include/rive/joystick.hpp index e75556e..b781890 100644 --- a/include/rive/joystick.hpp +++ b/include/rive/joystick.hpp
@@ -20,7 +20,7 @@ bool isJoystickFlagged(JoystickFlags flag) const { - return (((JoystickFlags)joystickFlags()) & flag) == flag; + return ((JoystickFlags)joystickFlags()) & flag; } bool canApplyBeforeUpdate() const { return m_handleSource == nullptr; } @@ -37,4 +37,4 @@ }; } // namespace rive -#endif \ No newline at end of file +#endif
diff --git a/include/rive/joystick_flags.hpp b/include/rive/joystick_flags.hpp index d3e73cd..f4b2534 100644 --- a/include/rive/joystick_flags.hpp +++ b/include/rive/joystick_flags.hpp
@@ -1,5 +1,8 @@ #ifndef _RIVE_JOYSTICK_FLAGS_HPP_ #define _RIVE_JOYSTICK_FLAGS_HPP_ + +#include "rive/enum_bitset.hpp" + namespace rive { enum class JoystickFlags : unsigned char @@ -13,52 +16,6 @@ /// Whether this Joystick works in world space. worldSpace = 1 << 2 }; - -inline constexpr JoystickFlags operator&(JoystickFlags lhs, JoystickFlags rhs) -{ - return static_cast<JoystickFlags>(static_cast<std::underlying_type<JoystickFlags>::type>(lhs) & - static_cast<std::underlying_type<JoystickFlags>::type>(rhs)); -} - -inline constexpr JoystickFlags operator^(JoystickFlags lhs, JoystickFlags rhs) -{ - return static_cast<JoystickFlags>(static_cast<std::underlying_type<JoystickFlags>::type>(lhs) ^ - static_cast<std::underlying_type<JoystickFlags>::type>(rhs)); -} - -inline constexpr JoystickFlags operator|(JoystickFlags lhs, JoystickFlags rhs) -{ - return static_cast<JoystickFlags>(static_cast<std::underlying_type<JoystickFlags>::type>(lhs) | - static_cast<std::underlying_type<JoystickFlags>::type>(rhs)); -} - -inline constexpr JoystickFlags operator~(JoystickFlags rhs) -{ - return static_cast<JoystickFlags>(~static_cast<std::underlying_type<JoystickFlags>::type>(rhs)); -} - -inline JoystickFlags& operator|=(JoystickFlags& lhs, JoystickFlags rhs) -{ - lhs = static_cast<JoystickFlags>(static_cast<std::underlying_type<JoystickFlags>::type>(lhs) | - static_cast<std::underlying_type<JoystickFlags>::type>(rhs)); - - return lhs; -} - -inline JoystickFlags& operator&=(JoystickFlags& lhs, JoystickFlags rhs) -{ - lhs = static_cast<JoystickFlags>(static_cast<std::underlying_type<JoystickFlags>::type>(lhs) & - static_cast<std::underlying_type<JoystickFlags>::type>(rhs)); - - return lhs; -} - -inline JoystickFlags& operator^=(JoystickFlags& lhs, JoystickFlags rhs) -{ - lhs = static_cast<JoystickFlags>(static_cast<std::underlying_type<JoystickFlags>::type>(lhs) ^ - static_cast<std::underlying_type<JoystickFlags>::type>(rhs)); - - return lhs; -} +RIVE_MAKE_ENUM_BITSET(JoystickFlags) } // namespace rive -#endif \ No newline at end of file +#endif
diff --git a/test/enum_bitset_test.cpp b/test/enum_bitset_test.cpp new file mode 100644 index 0000000..b1fbd9f --- /dev/null +++ b/test/enum_bitset_test.cpp
@@ -0,0 +1,85 @@ +/* + * Copyright 2023 Rive + */ + +#include <catch.hpp> + +#include "rive/enum_bitset.hpp" + +namespace rive +{ +enum class Flags : uint8_t +{ + zero = 0, + one = 1 << 0, + two = 1 << 1, + four = 1 << 2, + eight = 1 << 3, +}; +RIVE_MAKE_ENUM_BITSET(Flags); + +// Check rive::EnumBitset<> operators +TEST_CASE("enum-operators", "[enum_bitset]") +{ + Flags flags; + + flags = Flags::one | Flags::two; + CHECK(flags == (Flags)3); + + flags &= ~Flags::two; + CHECK(flags == Flags::one); + + flags = Flags::two | (Flags::four | Flags::eight); + CHECK(flags == (Flags)14); + + flags = (Flags::two | Flags::four) & ~(Flags::one | Flags::two); + CHECK(flags == Flags::four); + + flags = (Flags::two | Flags::four) & (Flags::one | Flags::two); + CHECK(flags == Flags::two); + CHECK(flags & Flags::two); + CHECK(!(flags & Flags::one)); + CHECK(flags & (Flags::two | Flags::eight)); + CHECK(!(flags & (Flags::four | Flags::eight))); + + // All & overloads. + CHECK(!(Flags::one & Flags::two)); + CHECK(Flags::four & Flags::four); + CHECK((Flags::four & Flags::four) == Flags::four); + CHECK((Flags::one & (Flags::one | Flags::two)) == Flags::one); + CHECK(!(~Flags::one & Flags::one)); + + // All | overloads. + CHECK(!(Flags::zero | Flags::zero)); + CHECK(Flags::zero | Flags::one); + CHECK((Flags::one | Flags::two) == (Flags)3); + CHECK((Flags::one | (Flags::two | Flags::four)) == (Flags)7); + CHECK(((Flags::one | Flags::two) | Flags::four) == (Flags)7); + CHECK(((Flags::one | Flags::two) | (Flags::four | Flags::eight)) == (Flags)15); + + // All ~ overloads. + CHECK(~Flags::two == (Flags)(255 ^ 2)); // Flags is a uint8_t + CHECK(~(Flags::two | Flags::eight) == (Flags)(255 ^ 2 ^ 8)); // Flags is a uint8_t + + // All &= overloads. + flags = Flags::eight | Flags::four | Flags::two | Flags::one; + CHECK(flags == (Flags)15); + Flags inverseEight = ~Flags::eight; + flags &= inverseEight; + CHECK(flags == (Flags)7); + flags &= ~(Flags::four | Flags::one); + CHECK(flags == Flags::two); + flags &= Flags::two; + CHECK(flags == Flags::two); + flags &= Flags::one; + CHECK(flags == Flags::zero); + + // All |= overloads. + flags = Flags::zero; + CHECK(flags == Flags::zero); + flags |= Flags::eight; + CHECK(flags == Flags::eight); + flags |= ~Flags::eight; + CHECK(flags == (Flags)255); // Flags is a uint8_t +} +} // namespace rive