From 3f0c669b4cedc7377d447586123bfe91d2c9f686 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 26 Apr 2026 21:37:55 -0500 Subject: [PATCH] [light] Reduce LOC via X-macro pattern, drop unused tag/BIT args MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apply the X-macro pattern from #15132 so the field list is declared once and expanded into setters, play(), and storage. Drop the unused BIT_NAME and tag arguments — only (type, name, idx) is needed since idx doubles as both the bit position and the Empty<> tag. Net change vs upstream is +10 LOC instead of +64. --- esphome/components/light/automation.h | 124 +++++++------------------ esphome/components/light/automation.py | 48 +++++----- 2 files changed, 59 insertions(+), 113 deletions(-) diff --git a/esphome/components/light/automation.h b/esphome/components/light/automation.h index 2511612b57..2dc8c08ee0 100644 --- a/esphome/components/light/automation.h +++ b/esphome/components/light/automation.h @@ -24,113 +24,59 @@ template class ToggleAction : public Action { LightState *state_; }; -// Bitmask of fields configured on a LightControlAction. Used as a non-type -// template parameter so unset fields can be elided via [[no_unique_address]] -// and skipped at compile time via if constexpr in play(). -namespace LightControlField { -constexpr uint16_t COLOR_MODE = 1 << 0; -constexpr uint16_t STATE = 1 << 1; -constexpr uint16_t TRANSITION_LENGTH = 1 << 2; -constexpr uint16_t FLASH_LENGTH = 1 << 3; -constexpr uint16_t BRIGHTNESS = 1 << 4; -constexpr uint16_t COLOR_BRIGHTNESS = 1 << 5; -constexpr uint16_t RED = 1 << 6; -constexpr uint16_t GREEN = 1 << 7; -constexpr uint16_t BLUE = 1 << 8; -constexpr uint16_t WHITE = 1 << 9; -constexpr uint16_t COLOR_TEMPERATURE = 1 << 10; -constexpr uint16_t COLD_WHITE = 1 << 11; -constexpr uint16_t WARM_WHITE = 1 << 12; -constexpr uint16_t EFFECT = 1 << 13; -} // namespace LightControlField - +// Distinct empty types per field tag so [[no_unique_address]] always coalesces +// (the same empty type used multiple times is not guaranteed to share an address). namespace light_control_detail { -// Distinct empty types per field so [[no_unique_address]] always coalesces -// (same empty type used multiple times is not guaranteed to share an address). template struct Empty {}; } // namespace light_control_detail +// X-macro: (type, field_name, bit_index). Order and bit values must match +// the FIELDS table in automation.py. +#define LIGHT_CONTROL_FIELDS(X) \ + X(ColorMode, color_mode, 0) \ + X(bool, state, 1) \ + X(uint32_t, transition_length, 2) \ + X(uint32_t, flash_length, 3) \ + X(float, brightness, 4) \ + X(float, color_brightness, 5) \ + X(float, red, 6) \ + X(float, green, 7) \ + X(float, blue, 8) \ + X(float, white, 9) \ + X(float, color_temperature, 10) \ + X(float, cold_white, 11) \ + X(float, warm_white, 12) \ + X(uint32_t, effect, 13) + template class LightControlAction : public Action { public: explicit LightControlAction(LightState *parent) : parent_(parent) {} -#define LIGHT_CONTROL_FIELD(field_bit, type, name, tag) \ - template void set_##name(V value) requires((Fields & LightControlField::field_bit) != 0) { \ - this->name##_ = value; \ - } +#define LIGHT_FIELD_SETTER_(type, name, idx) \ + template void set_##name(V value) requires((Fields & (1 << (idx))) != 0) { this->name##_ = value; } +#define LIGHT_FIELD_APPLY_(type, name, idx) \ + if constexpr ((Fields & (1 << (idx))) != 0) \ + call.set_##name(this->name##_.value(x...)); +#define LIGHT_FIELD_DECL_(type, name, idx) \ + [[no_unique_address]] std::conditional_t<(Fields & (1 << (idx))) != 0, TemplatableFn, \ + light_control_detail::Empty<(idx)>> \ + name##_{}; - LIGHT_CONTROL_FIELD(COLOR_MODE, ColorMode, color_mode, 0) - LIGHT_CONTROL_FIELD(STATE, bool, state, 1) - LIGHT_CONTROL_FIELD(TRANSITION_LENGTH, uint32_t, transition_length, 2) - LIGHT_CONTROL_FIELD(FLASH_LENGTH, uint32_t, flash_length, 3) - LIGHT_CONTROL_FIELD(BRIGHTNESS, float, brightness, 4) - LIGHT_CONTROL_FIELD(COLOR_BRIGHTNESS, float, color_brightness, 5) - LIGHT_CONTROL_FIELD(RED, float, red, 6) - LIGHT_CONTROL_FIELD(GREEN, float, green, 7) - LIGHT_CONTROL_FIELD(BLUE, float, blue, 8) - LIGHT_CONTROL_FIELD(WHITE, float, white, 9) - LIGHT_CONTROL_FIELD(COLOR_TEMPERATURE, float, color_temperature, 10) - LIGHT_CONTROL_FIELD(COLD_WHITE, float, cold_white, 11) - LIGHT_CONTROL_FIELD(WARM_WHITE, float, warm_white, 12) - LIGHT_CONTROL_FIELD(EFFECT, uint32_t, effect, 13) -#undef LIGHT_CONTROL_FIELD + LIGHT_CONTROL_FIELDS(LIGHT_FIELD_SETTER_) void play(const Ts &...x) override { auto call = this->parent_->make_call(); - if constexpr ((Fields & LightControlField::COLOR_MODE) != 0) - call.set_color_mode(this->color_mode_.value(x...)); - if constexpr ((Fields & LightControlField::STATE) != 0) - call.set_state(this->state_.value(x...)); - if constexpr ((Fields & LightControlField::TRANSITION_LENGTH) != 0) - call.set_transition_length(this->transition_length_.value(x...)); - if constexpr ((Fields & LightControlField::FLASH_LENGTH) != 0) - call.set_flash_length(this->flash_length_.value(x...)); - if constexpr ((Fields & LightControlField::BRIGHTNESS) != 0) - call.set_brightness(this->brightness_.value(x...)); - if constexpr ((Fields & LightControlField::COLOR_BRIGHTNESS) != 0) - call.set_color_brightness(this->color_brightness_.value(x...)); - if constexpr ((Fields & LightControlField::RED) != 0) - call.set_red(this->red_.value(x...)); - if constexpr ((Fields & LightControlField::GREEN) != 0) - call.set_green(this->green_.value(x...)); - if constexpr ((Fields & LightControlField::BLUE) != 0) - call.set_blue(this->blue_.value(x...)); - if constexpr ((Fields & LightControlField::WHITE) != 0) - call.set_white(this->white_.value(x...)); - if constexpr ((Fields & LightControlField::COLOR_TEMPERATURE) != 0) - call.set_color_temperature(this->color_temperature_.value(x...)); - if constexpr ((Fields & LightControlField::COLD_WHITE) != 0) - call.set_cold_white(this->cold_white_.value(x...)); - if constexpr ((Fields & LightControlField::WARM_WHITE) != 0) - call.set_warm_white(this->warm_white_.value(x...)); - if constexpr ((Fields & LightControlField::EFFECT) != 0) - call.set_effect(this->effect_.value(x...)); + LIGHT_CONTROL_FIELDS(LIGHT_FIELD_APPLY_) call.perform(); } protected: LightState *parent_; + LIGHT_CONTROL_FIELDS(LIGHT_FIELD_DECL_) -#define LIGHT_CONTROL_STORAGE(field_bit, type, name, tag) \ - [[no_unique_address]] std::conditional_t<(Fields & LightControlField::field_bit) != 0, TemplatableFn, \ - light_control_detail::Empty> \ - name##_{}; - - LIGHT_CONTROL_STORAGE(COLOR_MODE, ColorMode, color_mode, 0) - LIGHT_CONTROL_STORAGE(STATE, bool, state, 1) - LIGHT_CONTROL_STORAGE(TRANSITION_LENGTH, uint32_t, transition_length, 2) - LIGHT_CONTROL_STORAGE(FLASH_LENGTH, uint32_t, flash_length, 3) - LIGHT_CONTROL_STORAGE(BRIGHTNESS, float, brightness, 4) - LIGHT_CONTROL_STORAGE(COLOR_BRIGHTNESS, float, color_brightness, 5) - LIGHT_CONTROL_STORAGE(RED, float, red, 6) - LIGHT_CONTROL_STORAGE(GREEN, float, green, 7) - LIGHT_CONTROL_STORAGE(BLUE, float, blue, 8) - LIGHT_CONTROL_STORAGE(WHITE, float, white, 9) - LIGHT_CONTROL_STORAGE(COLOR_TEMPERATURE, float, color_temperature, 10) - LIGHT_CONTROL_STORAGE(COLD_WHITE, float, cold_white, 11) - LIGHT_CONTROL_STORAGE(WARM_WHITE, float, warm_white, 12) - LIGHT_CONTROL_STORAGE(EFFECT, uint32_t, effect, 13) -#undef LIGHT_CONTROL_STORAGE +#undef LIGHT_FIELD_DECL_ +#undef LIGHT_FIELD_APPLY_ +#undef LIGHT_FIELD_SETTER_ }; template class DimRelativeAction : public Action { diff --git a/esphome/components/light/automation.py b/esphome/components/light/automation.py index ddd8948a64..a655cecc44 100644 --- a/esphome/components/light/automation.py +++ b/esphome/components/light/automation.py @@ -179,35 +179,35 @@ def _resolve_effect_index(config: ConfigType) -> int: async def light_control_to_code(config, action_id, template_arg, args): paren = await cg.get_variable(config[CONF_ID]) - # (config_key, setter_name, c++ type, field bit) - # Bit values must match LightControlField in automation.h + # (config_key, setter_name, c++ type) — order and bit position must match + # LIGHT_CONTROL_FIELDS in automation.h. CONF_EFFECT is the last bit. FIELDS = ( - (CONF_COLOR_MODE, "set_color_mode", ColorMode, 1 << 0), - (CONF_STATE, "set_state", cg.bool_, 1 << 1), - (CONF_TRANSITION_LENGTH, "set_transition_length", cg.uint32, 1 << 2), - (CONF_FLASH_LENGTH, "set_flash_length", cg.uint32, 1 << 3), - (CONF_BRIGHTNESS, "set_brightness", cg.float_, 1 << 4), - (CONF_COLOR_BRIGHTNESS, "set_color_brightness", cg.float_, 1 << 5), - (CONF_RED, "set_red", cg.float_, 1 << 6), - (CONF_GREEN, "set_green", cg.float_, 1 << 7), - (CONF_BLUE, "set_blue", cg.float_, 1 << 8), - (CONF_WHITE, "set_white", cg.float_, 1 << 9), - (CONF_COLOR_TEMPERATURE, "set_color_temperature", cg.float_, 1 << 10), - (CONF_COLD_WHITE, "set_cold_white", cg.float_, 1 << 11), - (CONF_WARM_WHITE, "set_warm_white", cg.float_, 1 << 12), + (CONF_COLOR_MODE, "set_color_mode", ColorMode), + (CONF_STATE, "set_state", cg.bool_), + (CONF_TRANSITION_LENGTH, "set_transition_length", cg.uint32), + (CONF_FLASH_LENGTH, "set_flash_length", cg.uint32), + (CONF_BRIGHTNESS, "set_brightness", cg.float_), + (CONF_COLOR_BRIGHTNESS, "set_color_brightness", cg.float_), + (CONF_RED, "set_red", cg.float_), + (CONF_GREEN, "set_green", cg.float_), + (CONF_BLUE, "set_blue", cg.float_), + (CONF_WHITE, "set_white", cg.float_), + (CONF_COLOR_TEMPERATURE, "set_color_temperature", cg.float_), + (CONF_COLD_WHITE, "set_cold_white", cg.float_), + (CONF_WARM_WHITE, "set_warm_white", cg.float_), ) - field_mask = 0 - for conf_key, _, _, bit in FIELDS: - if conf_key in config: - field_mask |= bit - if CONF_EFFECT in config: - field_mask |= 1 << 13 # EFFECT bit + EFFECT_BIT = len(FIELDS) - fields_arg = cg.RawExpression(f"static_cast({field_mask})") - control_template_arg = cg.TemplateArguments(fields_arg, *template_arg) + field_mask = sum(1 << i for i, (k, _, _) in enumerate(FIELDS) if k in config) + if CONF_EFFECT in config: + field_mask |= 1 << EFFECT_BIT + + control_template_arg = cg.TemplateArguments( + cg.RawExpression(f"static_cast({field_mask})"), *template_arg + ) var = cg.new_Pvariable(action_id, control_template_arg, paren) - for conf_key, setter, type_, _ in FIELDS: + for conf_key, setter, type_ in FIELDS: if conf_key in config: template_ = await cg.templatable(config[conf_key], args, type_) cg.add(getattr(var, setter)(template_))