From c6711fc354d200bd37558367720c20ebefb5d542 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 21 Oct 2025 22:19:07 -1000 Subject: [PATCH 1/4] adjust --- esphome/components/light/color_mode.h | 6 ++++++ esphome/core/enum_bitmask.h | 3 +++ 2 files changed, 9 insertions(+) diff --git a/esphome/components/light/color_mode.h b/esphome/components/light/color_mode.h index 9c6a4d147b..03132f54bf 100644 --- a/esphome/components/light/color_mode.h +++ b/esphome/components/light/color_mode.h @@ -184,6 +184,12 @@ inline bool has_capability(const ColorModeMask &mask, ColorCapability capability } // namespace esphome // 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) /// Bit positions follow the enum declaration order diff --git a/esphome/core/enum_bitmask.h b/esphome/core/enum_bitmask.h index 4c29c7047e..fdbd0c50cc 100644 --- a/esphome/core/enum_bitmask.h +++ b/esphome/core/enum_bitmask.h @@ -26,6 +26,9 @@ namespace esphome { /// if (modes.contains(CLIMATE_MODE_HEAT)) { ... } /// for (auto mode : modes) { ... } // Iterate over set bits /// +/// For complete usage examples with template specializations, see: +/// - esphome/components/light/color_mode.h (ColorMode example) +/// /// Design notes: /// - Uses compile-time type selection for optimal size (uint8_t/uint16_t/uint32_t) /// - Iterator converts bit positions to actual enum values during traversal From 1119b4e11e21636f02e123053ad043054fae2319 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 21 Oct 2025 22:23:37 -1000 Subject: [PATCH 2/4] [core] Add std::set compatibility aliases to EnumBitmask - Add insert() as alias for add() - Add erase() as alias for remove() - Add count() as alias for contains() - Makes EnumBitmask a true drop-in replacement for std::set - Update documentation to reflect compatibility --- enum_templates.md | 200 +++++++++++++++++++++++++++ esphome/core/enum_bitmask.h | 9 ++ extract_color_mode_mask_helper_pr.md | 98 +++++++++++++ 3 files changed, 307 insertions(+) create mode 100644 enum_templates.md create mode 100644 extract_color_mode_mask_helper_pr.md diff --git a/enum_templates.md b/enum_templates.md new file mode 100644 index 0000000000..175f8d0b89 --- /dev/null +++ b/enum_templates.md @@ -0,0 +1,200 @@ +# 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 fdbd0c50cc..d5d531763e 100644 --- a/esphome/core/enum_bitmask.h +++ b/esphome/core/enum_bitmask.h @@ -62,9 +62,15 @@ template class EnumBitmask { } } + /// 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); } + /// Clear all values from the set constexpr void clear() { this->mask_ = 0; } @@ -73,6 +79,9 @@ template class EnumBitmask { return (this->mask_ & (static_cast(1) << enum_to_bit(value))) != 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 new file mode 100644 index 0000000000..6a4d98a5f8 --- /dev/null +++ b/extract_color_mode_mask_helper_pr.md @@ -0,0 +1,98 @@ +# 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 From f8f967b25c0304ef00cd26f313eb16bc5306034a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 21 Oct 2025 22:25:57 -1000 Subject: [PATCH 3/4] wi --- enum_templates.md | 200 --------------------------- esphome/core/enum_bitmask.h | 32 ++--- extract_color_mode_mask_helper_pr.md | 98 ------------- 3 files changed, 12 insertions(+), 318 deletions(-) delete mode 100644 enum_templates.md delete mode 100644 extract_color_mode_mask_helper_pr.md 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 From 9d1ceba18f9246162a8e3b8de5219877e46d3da0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 21 Oct 2025 22:28:59 -1000 Subject: [PATCH 4/4] [core] Use std::set API for EnumBitmask - Replace .contains()/.add()/.remove() with .count()/.insert()/.erase() - Makes EnumBitmask a true drop-in replacement for std::set - Update all usages in light component --- esphome/components/light/light_traits.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/light/light_traits.h b/esphome/components/light/light_traits.h index 9dec9fb577..294b0cad1d 100644 --- a/esphome/components/light/light_traits.h +++ b/esphome/components/light/light_traits.h @@ -26,7 +26,7 @@ class LightTraits { this->supported_color_modes_ = ColorModeMask(modes); } - bool supports_color_mode(ColorMode color_mode) const { return this->supported_color_modes_.contains(color_mode); } + bool supports_color_mode(ColorMode color_mode) const { return this->supported_color_modes_.count(color_mode) > 0; } bool supports_color_capability(ColorCapability color_capability) const { return has_capability(this->supported_color_modes_, color_capability); }