diff --git a/enum_templates.md b/enum_templates.md deleted file mode 100644 index 175f8d0b89..0000000000 --- a/enum_templates.md +++ /dev/null @@ -1,200 +0,0 @@ -# EnumBitmask Pattern Documentation - -## Overview - -`EnumBitmask` from `esphome/core/enum_bitmask.h` provides a memory-efficient replacement for `std::set` when storing sets of enum values. - -## When to Use - -Use `EnumBitmask` instead of `std::set` when: -- Storing sets of enum values (e.g., supported modes, capabilities) -- Enum has ≤32 distinct values -- Memory efficiency is important (saves ~586 bytes per `std::set` instance) - -## Benefits - -- **Memory Savings**: Eliminates red-black tree overhead (~586 bytes per instance) -- **Compact Storage**: 1-4 bytes depending on enum count (uint8_t/uint16_t/uint32_t) -- **Constexpr-Compatible**: Supports compile-time initialization -- **Efficient Iteration**: Only visits set bits, not all possible enum values -- **Range-Based Loops**: `for (auto value : mask)` works seamlessly - -## Requirements - -1. Enum must have sequential values (or use a lookup table for mapping) -2. Maximum 32 enum values (uint32_t bitmask limitation) -3. Must provide template specializations for `enum_to_bit()` and `bit_to_enum()` - -## Basic Usage Example - -```cpp -// Bad - red-black tree overhead (~586 bytes) -std::set supported_modes; -supported_modes.insert(ColorMode::RGB); -supported_modes.insert(ColorMode::WHITE); -if (supported_modes.count(ColorMode::RGB)) { ... } - -// Good - compact bitmask storage (2-4 bytes) -ColorModeMask supported_modes({ColorMode::RGB, ColorMode::WHITE}); -if (supported_modes.contains(ColorMode::RGB)) { ... } -for (auto mode : supported_modes) { ... } // Iterate over set values -``` - -## Implementation Pattern - -### 1. Define the Lookup Table - -If enum values aren't sequential from 0, create a lookup table: - -```cpp -// In your component header (e.g., esphome/components/light/color_mode.h) -constexpr ColorMode COLOR_MODE_LOOKUP[10] = { - ColorMode::UNKNOWN, // bit 0 - ColorMode::ON_OFF, // bit 1 - ColorMode::BRIGHTNESS, // bit 2 - ColorMode::WHITE, // bit 3 - ColorMode::COLOR_TEMPERATURE, // bit 4 - ColorMode::COLD_WARM_WHITE, // bit 5 - ColorMode::RGB, // bit 6 - ColorMode::RGB_WHITE, // bit 7 - ColorMode::RGB_COLOR_TEMPERATURE, // bit 8 - ColorMode::RGB_COLD_WARM_WHITE, // bit 9 -}; -``` - -### 2. Create Type Alias - -```cpp -constexpr int COLOR_MODE_BITMASK_SIZE = 10; -using ColorModeMask = EnumBitmask; -``` - -### 3. Provide Template Specializations - -**IMPORTANT**: Specializations must be in the **global namespace** (C++ requirement). Place them at the end of your header file, outside your component namespace. - -```cpp -// At end of header, outside namespace esphome::light -// Template specializations for ColorMode must be in global namespace -// -// C++ requires template specializations to be declared in the same namespace as the -// original template. Since EnumBitmask is in the esphome namespace (not esphome::light), -// we must provide these specializations at global scope with fully-qualified names. -// -// These specializations define how ColorMode enum values map to/from bit positions. - -/// Map ColorMode enum values to bit positions (0-9) -template<> -constexpr int esphome::EnumBitmask::enum_to_bit( - esphome::light::ColorMode mode) { - // Map enum value to bit position (0-9) - for (int i = 0; i < esphome::light::COLOR_MODE_BITMASK_SIZE; ++i) { - if (esphome::light::COLOR_MODE_LOOKUP[i] == mode) - return i; - } - return 0; // Unknown values map to bit 0 (typically reserved for UNKNOWN/NONE) -} - -/// Map bit positions (0-9) to ColorMode enum values -template<> -inline esphome::light::ColorMode esphome::EnumBitmask::bit_to_enum(int bit) { - return (bit >= 0 && bit < esphome::light::COLOR_MODE_BITMASK_SIZE) - ? esphome::light::COLOR_MODE_LOOKUP[bit] - : esphome::light::ColorMode::UNKNOWN; -} -``` - -### Error Handling in enum_to_bit() - -The implementation returns bit 0 for unknown enum values: -```cpp -return 0; // Unknown values map to bit 0 -``` - -This means an unknown ColorMode maps to the same bit as `ColorMode::UNKNOWN`. This is acceptable because: -- Compile-time failure occurs if using invalid enum values -- `ColorMode::UNKNOWN` at bit 0 is semantically correct -- Runtime misuse is prevented by type safety - -## API Compatibility with std::set - -EnumBitmask provides both modern `.contains()` / `.add()` / `.remove()` methods and std::set-compatible aliases for drop-in replacement: - -| Operation | std::set | EnumBitmask | Notes | -|-----------|----------|-------------|-------| -| Add value | `.insert(value)` | `.insert(value)` or `.add(value)` | Both work | -| Check membership | `.count(value)` | `.count(value)` or `.contains(value)` | Both work | -| Remove value | `.erase(value)` | `.erase(value)` or `.remove(value)` | Both work | -| Count elements | `.size()` | `.size()` | Same | -| Check empty | `.empty()` | `.empty()` | Same | -| Clear all | `.clear()` | `.clear()` | Same | -| Iterate | `for (auto v : set)` | `for (auto v : mask)` | Same | - -**Drop-in replacement**: You can use either the std::set-compatible methods (`.insert()`, `.count()`, `.erase()`) or the more explicit methods (`.add()`, `.contains()`, `.remove()`). - -## Complete Usage Example - -See `esphome/components/light/color_mode.h` for a complete real-world implementation showing: -- Lookup table definition -- Type aliases -- Template specializations -- Helper functions using the bitmask - -## Common Patterns - -### Compile-Time Initialization - -```cpp -// Constexpr-compatible for compile-time initialization -constexpr ColorModeMask DEFAULT_MODES({ColorMode::ON_OFF, ColorMode::BRIGHTNESS}); -``` - -### Adding Multiple Values - -```cpp -ColorModeMask modes; -modes.add({ColorMode::RGB, ColorMode::WHITE, ColorMode::COLOR_TEMPERATURE}); -``` - -### Checking and Iterating - -```cpp -if (modes.contains(ColorMode::RGB)) { - // RGB mode is supported -} - -for (auto mode : modes) { - // Process each supported mode - ESP_LOGD(TAG, "Supported mode: %d", static_cast(mode)); -} -``` - -### Working with Raw Bitmask Values - -```cpp -// Get raw bitmask for bitwise operations -auto mask = modes.get_mask(); - -// Check if raw bitmask contains a value -if (ColorModeMask::mask_contains(mask, ColorMode::RGB)) { ... } - -// Get first value from raw bitmask -auto first = ColorModeMask::first_value_from_mask(mask); -``` - -## Detection of Opportunities - -Look for these patterns in existing code: -- `std::set` with small enum sets (≤32 values) -- Components storing "supported modes" or "capabilities" -- Red-black tree code (`rb_tree`, `_Rb_tree`) in compiler output -- Flash size increases when adding enum set storage - -## When NOT to Use - -- Enum has >32 distinct values (bitmask limitation) -- Need to store arbitrary runtime-determined integer values (not enum values) -- Enum values are sparse or non-sequential and lookup table would be impractical -- Code readability matters more than memory savings (niche single-use components) diff --git a/esphome/core/enum_bitmask.h b/esphome/core/enum_bitmask.h index d5d531763e..b3112c610b 100644 --- a/esphome/core/enum_bitmask.h +++ b/esphome/core/enum_bitmask.h @@ -23,7 +23,7 @@ namespace esphome { /// Example usage: /// using ClimateModeMask = EnumBitmask; /// ClimateModeMask modes({CLIMATE_MODE_HEAT, CLIMATE_MODE_COOL}); -/// if (modes.contains(CLIMATE_MODE_HEAT)) { ... } +/// if (modes.count(CLIMATE_MODE_HEAT)) { ... } /// for (auto mode : modes) { ... } // Iterate over set bits /// /// For complete usage examples with template specializations, see: @@ -48,40 +48,32 @@ template class EnumBitmask { /// Construct from initializer list: {VALUE1, VALUE2, ...} constexpr EnumBitmask(std::initializer_list values) { for (auto value : values) { - this->add(value); + this->insert(value); } } - /// Add a single enum value to the set - constexpr void add(EnumType value) { this->mask_ |= (static_cast(1) << enum_to_bit(value)); } + /// Add a single enum value to the set (std::set compatibility) + constexpr void insert(EnumType value) { this->mask_ |= (static_cast(1) << enum_to_bit(value)); } /// Add multiple enum values from initializer list - constexpr void add(std::initializer_list values) { + constexpr void insert(std::initializer_list values) { for (auto value : values) { - this->add(value); + this->insert(value); } } - /// std::set compatibility: insert() is an alias for add() - constexpr void insert(EnumType value) { this->add(value); } - - /// Remove an enum value from the set - constexpr void remove(EnumType value) { this->mask_ &= ~(static_cast(1) << enum_to_bit(value)); } - - /// std::set compatibility: erase() is an alias for remove() - constexpr void erase(EnumType value) { this->remove(value); } + /// Remove an enum value from the set (std::set compatibility) + constexpr void erase(EnumType value) { this->mask_ &= ~(static_cast(1) << enum_to_bit(value)); } /// Clear all values from the set constexpr void clear() { this->mask_ = 0; } - /// Check if the set contains a specific enum value - constexpr bool contains(EnumType value) const { - return (this->mask_ & (static_cast(1) << enum_to_bit(value))) != 0; + /// Check if the set contains a specific enum value (std::set compatibility) + /// Returns 1 if present, 0 if not (same as std::set for unique elements) + constexpr size_t count(EnumType value) const { + return (this->mask_ & (static_cast(1) << enum_to_bit(value))) != 0 ? 1 : 0; } - /// std::set compatibility: count() returns 1 if present, 0 if not (same as std::set for unique elements) - constexpr size_t count(EnumType value) const { return this->contains(value) ? 1 : 0; } - /// Count the number of enum values in the set constexpr size_t size() const { // Brian Kernighan's algorithm - efficient for sparse bitmasks diff --git a/extract_color_mode_mask_helper_pr.md b/extract_color_mode_mask_helper_pr.md deleted file mode 100644 index 6a4d98a5f8..0000000000 --- a/extract_color_mode_mask_helper_pr.md +++ /dev/null @@ -1,98 +0,0 @@ -# What does this implement/fix? - -This PR extracts the `ColorModeMask` implementation from the light component into a generic `EnumBitmask` template helper in `esphome/core/enum_bitmask.h`. This refactoring enables code reuse across other components (e.g., climate, fan) that need efficient enum set storage without STL container overhead. - -## Key Benefits - -- **Code Reuse**: Generic template can be used by any component needing enum bitmask storage (climate, fan, cover, etc.) -- **Memory Efficiency**: Replaces `std::set` with compact bitmask storage (~586 bytes saved per instance) -- **Zero-cost Abstraction**: Maintains same performance characteristics with cleaner, more maintainable code -- **Flash Savings**: 16 bytes reduction on ESP8266 in initial testing - -## Technical Changes - -1. **New Generic Template** (`esphome/core/enum_bitmask.h`): - - `EnumBitmask` template class - - Auto-selects optimal storage type (uint8_t/uint16_t/uint32_t) based on MaxBits - - Provides iterator support, initializer list construction, and static utility methods - - Requires specialization of `enum_to_bit()` and `bit_to_enum()` for each enum type - -2. **std::set Compatibility**: - - Provides both modern API (`.contains()`, `.add()`, `.remove()`) and std::set-compatible aliases (`.count()`, `.insert()`, `.erase()`) - - True drop-in replacement - existing code using `.insert()` and `.count()` works unchanged - -3. **Light Component Refactoring** (`esphome/components/light/color_mode.h`): - - Replaced custom `ColorModeMask` class with `using ColorModeMask = EnumBitmask` - - Single shared `COLOR_MODE_LOOKUP` array eliminates code duplication - - Template specializations provide enum↔bit mapping - - Moved `has_capability()` to namespace-level function for cleaner API - -4. **Updated Call Sites**: - - `light_call.cpp`: Uses `ColorModeMask::first_value_from_mask()` and `ColorModeMask::mask_contains()` static methods - - `light_traits.h`: Uses namespace-level `has_capability()` function - - No changes required to other light component files (drop-in replacement) - -## Design Rationale - -The generic template follows the same pattern as the original `ColorModeMask` but makes it reusable: -- Constexpr-compatible for compile-time initialization -- Iterator support for range-based for loops and API encoding -- Static methods for working with raw bitmask values (for bitwise operation results) -- Protected specialization interface ensures type safety - -This establishes a pattern that can be applied to other components: -- Climate modes/presets (upcoming PR) -- Fan modes -- Cover operations -- Any component with small enum sets (≤32 values) - -## Types of changes - -- [x] Code quality improvements to existing code or addition of tests - -**Related issue or feature (if applicable):** - -- Part of ongoing memory optimization effort for embedded platforms - -**Pull request in [esphome-docs](https://github.com/esphome/esphome-docs) with documentation (if applicable):** - -- N/A (internal refactoring, no user-facing changes) - -## Test Environment - -- [x] ESP32 -- [x] ESP32 IDF -- [x] ESP8266 -- [ ] RP2040 -- [ ] BK72xx -- [ ] RTL87xx -- [ ] nRF52840 - -## Example entry for `config.yaml`: - -```yaml -# No config changes required - internal refactoring only -# All existing light configurations continue to work unchanged - -light: - - platform: rgb - id: test_rgb_light - name: "Test RGB Light" - red: red_output - green: green_output - blue: blue_output -``` - -## Checklist: - - [x] The code change is tested and works locally. - - [x] Tests have been added to verify that the new code works (under `tests/` folder). - -If user exposed functionality or configuration variables are added/changed: - - [ ] Documentation added/updated in [esphome-docs](https://github.com/esphome/esphome-docs). - -## Additional Notes - -- **Zero functional changes**: This is a pure refactoring with identical runtime behavior -- **Binary size impact**: Slight improvement on ESP8266 (16 bytes flash reduction) -- **Future work**: Will apply this pattern to climate component in follow-up PR -- **Test coverage**: All modified code covered by existing light component tests