#pragma once #include #include #include #include #include namespace esphome { /// Default bit mapping policy for contiguous enums starting at 0 /// Provides 1:1 mapping where enum value equals bit position template struct DefaultBitPolicy { // Automatic bitmask type selection based on MaxBits // ≤8 bits: uint8_t, ≤16 bits: uint16_t, otherwise: uint32_t using mask_t = typename std::conditional<(MaxBits <= 8), uint8_t, typename std::conditional<(MaxBits <= 16), uint16_t, uint32_t>::type>::type; static constexpr int MAX_BITS = MaxBits; static constexpr unsigned to_bit(ValueType value) { return static_cast(value); } static constexpr ValueType from_bit(unsigned bit) { return static_cast(bit); } }; /// Generic bitmask for storing a finite set of discrete values efficiently. /// Replaces std::set to eliminate red-black tree overhead (~586 bytes per instantiation). /// /// Template parameters: /// ValueType: The type to store (typically enum, but can be any discrete bounded type) /// BitPolicy: Policy class defining bit mapping and mask type (defaults to DefaultBitPolicy) /// /// BitPolicy requirements: /// - using mask_t = // Bitmask storage type /// - static constexpr int MAX_BITS // Maximum number of bits /// - static constexpr unsigned to_bit(ValueType) // Convert value to bit position /// - static constexpr ValueType from_bit(unsigned) // Convert bit position to value /// /// Example usage (1:1 mapping - climate enums): /// // For contiguous enums starting at 0, use DefaultBitPolicy /// using ClimateModeMask = FiniteSetMask>; /// ClimateModeMask modes({CLIMATE_MODE_HEAT, CLIMATE_MODE_COOL}); /// if (modes.count(CLIMATE_MODE_HEAT)) { ... } /// for (auto mode : modes) { ... } /// /// Example usage (custom mapping - ColorMode): /// // For custom mappings, define a custom BitPolicy /// // See esphome/components/light/color_mode.h for complete example /// /// Design notes: /// - Policy-based design allows custom bit mappings without template specialization /// - Iterator converts bit positions to actual values during traversal /// - All operations are constexpr-compatible for compile-time initialization /// - Drop-in replacement for std::set with simpler API /// template> class FiniteSetMask { public: using bitmask_t = typename BitPolicy::mask_t; constexpr FiniteSetMask() = default; /// Construct from initializer list: {VALUE1, VALUE2, ...} constexpr FiniteSetMask(std::initializer_list values) { for (auto value : values) { this->insert(value); } } /// Add a single value to the set (std::set compatibility) constexpr void insert(ValueType value) { this->mask_ |= (static_cast(1) << BitPolicy::to_bit(value)); } /// Add multiple values from initializer list constexpr void insert(std::initializer_list values) { for (auto value : values) { this->insert(value); } } /// Remove a value from the set (std::set compatibility) constexpr void erase(ValueType value) { this->mask_ &= ~(static_cast(1) << BitPolicy::to_bit(value)); } /// Clear all values from the set constexpr void clear() { this->mask_ = 0; } /// Check if the set contains a specific value (std::set compatibility) /// Returns 1 if present, 0 if not (same as std::set for unique elements) constexpr size_t count(ValueType value) const { return (this->mask_ & (static_cast(1) << BitPolicy::to_bit(value))) != 0 ? 1 : 0; } /// Count the number of values in the set constexpr size_t size() const { // Brian Kernighan's algorithm - efficient for sparse bitmasks // Typical case: 2-4 modes out of 10 possible bitmask_t n = this->mask_; size_t count = 0; while (n) { n &= n - 1; // Clear the least significant set bit count++; } return count; } /// Check if the set is empty constexpr bool empty() const { return this->mask_ == 0; } /// Iterator support for range-based for loops and API encoding /// Iterates over set bits and converts bit positions to values /// Optimization: removes bits from mask as we iterate class Iterator { public: using iterator_category = std::forward_iterator_tag; using value_type = ValueType; using difference_type = std::ptrdiff_t; using pointer = const ValueType *; using reference = ValueType; constexpr explicit Iterator(bitmask_t mask) : mask_(mask) {} constexpr ValueType operator*() const { // Return value for the first set bit return BitPolicy::from_bit(find_lowest_set_bit(mask_)); } constexpr Iterator &operator++() { // Clear the lowest set bit (Brian Kernighan's algorithm) mask_ &= mask_ - 1; return *this; } constexpr bool operator==(const Iterator &other) const { return mask_ == other.mask_; } constexpr bool operator!=(const Iterator &other) const { return !(*this == other); } private: bitmask_t mask_; }; constexpr Iterator begin() const { return Iterator(mask_); } constexpr Iterator end() const { return Iterator(0); } /// Get the raw bitmask value for optimized operations constexpr bitmask_t get_mask() const { return this->mask_; } /// Check if a specific value is present in a raw bitmask /// Useful for checking intersection results without creating temporary objects static constexpr bool mask_contains(bitmask_t mask, ValueType value) { return (mask & (static_cast(1) << BitPolicy::to_bit(value))) != 0; } /// Get the first value from a raw bitmask /// Used for optimizing intersection logic (e.g., "pick first suitable mode") static constexpr ValueType first_value_from_mask(bitmask_t mask) { return BitPolicy::from_bit(find_lowest_set_bit(mask)); } /// Find the lowest set bit in a bitmask /// Returns the bit position, or MAX_BITS if no bits are set static constexpr int find_lowest_set_bit(bitmask_t mask) { if (mask == 0) { return BitPolicy::MAX_BITS; } #if defined(__GNUC__) || defined(__clang__) int bit; if constexpr (sizeof(bitmask_t) <= sizeof(unsigned int)) { bit = __builtin_ctz(static_cast(mask)); } else if constexpr (sizeof(bitmask_t) <= sizeof(uint32_t)) { bit = __builtin_ctzl(static_cast(mask)); } else { bit = __builtin_ctzll(static_cast(mask)); } return bit < BitPolicy::MAX_BITS ? bit : BitPolicy::MAX_BITS; #else int bit = 0; while (bit < BitPolicy::MAX_BITS && !(mask & (static_cast(1) << bit))) { ++bit; } return bit; #endif } protected: bitmask_t mask_{0}; }; } // namespace esphome