diff --git a/esphome/components/display/display.h b/esphome/components/display/display.h index 14205da853..97b0a4e8d7 100644 --- a/esphome/components/display/display.h +++ b/esphome/components/display/display.h @@ -176,7 +176,117 @@ class Display; class DisplayPage; class DisplayOnPageChangeTrigger; -using display_writer_t = std::function; +/** Optimized display writer that uses function pointers for stateless lambdas. + * + * Similar to TemplatableValue but specialized for display writer callbacks. + * Saves ~8 bytes per stateless lambda on 32-bit platforms (16 bytes std::function → ~8 bytes discriminator+pointer). + * + * Supports both: + * - Stateless lambdas (from YAML) → function pointer (4 bytes) + * - Stateful lambdas/std::function (from C++ code) → std::function* (heap allocated) + * + * @tparam T The display type (e.g., Display, Nextion, GPIOLCDDisplay) + */ +template class DisplayWriter { + public: + DisplayWriter() : type_(NONE) {} + + // For stateless lambdas (convertible to function pointer): use function pointer (4 bytes) + template + DisplayWriter(F f) requires std::invocable && std::convertible_to + : type_(STATELESS_LAMBDA) { + this->stateless_f_ = f; // Implicit conversion to function pointer + } + + // For stateful lambdas and std::function (not convertible to function pointer): use std::function* (heap allocated) + // This handles backwards compatibility with external components + template + DisplayWriter(F f) requires std::invocable &&(!std::convertible_to) : type_(LAMBDA) { + this->f_ = new std::function(std::move(f)); + } + + // Copy constructor + DisplayWriter(const DisplayWriter &other) : type_(other.type_) { + if (type_ == LAMBDA) { + this->f_ = new std::function(*other.f_); + } else if (type_ == STATELESS_LAMBDA) { + this->stateless_f_ = other.stateless_f_; + } + } + + // Move constructor + DisplayWriter(DisplayWriter &&other) noexcept : type_(other.type_) { + if (type_ == LAMBDA) { + this->f_ = other.f_; + other.f_ = nullptr; + } else if (type_ == STATELESS_LAMBDA) { + this->stateless_f_ = other.stateless_f_; + } + other.type_ = NONE; + } + + // Assignment operators + DisplayWriter &operator=(const DisplayWriter &other) { + if (this != &other) { + this->~DisplayWriter(); + new (this) DisplayWriter(other); + } + return *this; + } + + DisplayWriter &operator=(DisplayWriter &&other) noexcept { + if (this != &other) { + this->~DisplayWriter(); + new (this) DisplayWriter(std::move(other)); + } + return *this; + } + + ~DisplayWriter() { + if (type_ == LAMBDA) { + delete this->f_; + } + // STATELESS_LAMBDA/NONE: no cleanup needed (function pointer or empty) + } + + bool has_value() const { return this->type_ != NONE; } + + void call(T &display) const { + switch (this->type_) { + case STATELESS_LAMBDA: + this->stateless_f_(display); // Direct function pointer call + break; + case LAMBDA: + (*this->f_)(display); // std::function call + break; + case NONE: + default: + break; + } + } + + // Operator() for convenience + void operator()(T &display) const { this->call(display); } + + // Operator* for backwards compatibility with (*writer_)(*this) pattern + DisplayWriter &operator*() { return *this; } + const DisplayWriter &operator*() const { return *this; } + + protected: + enum : uint8_t { + NONE, + LAMBDA, + STATELESS_LAMBDA, + } type_; + + union { + std::function *f_; + void (*stateless_f_)(T &); + }; +}; + +// Type alias for Display writer - uses optimized DisplayWriter instead of std::function +using display_writer_t = DisplayWriter; #define LOG_DISPLAY(prefix, type, obj) \ if ((obj) != nullptr) { \ @@ -678,7 +788,7 @@ class Display : public PollingComponent { void sort_triangle_points_by_y_(int *x1, int *y1, int *x2, int *y2, int *x3, int *y3); DisplayRotation rotation_{DISPLAY_ROTATION_0_DEGREES}; - optional writer_{}; + display_writer_t writer_{}; DisplayPage *page_{nullptr}; DisplayPage *previous_page_{nullptr}; std::vector on_page_change_triggers_; diff --git a/esphome/components/lcd_gpio/gpio_lcd_display.h b/esphome/components/lcd_gpio/gpio_lcd_display.h index aba254a90a..81e4dc51a0 100644 --- a/esphome/components/lcd_gpio/gpio_lcd_display.h +++ b/esphome/components/lcd_gpio/gpio_lcd_display.h @@ -2,13 +2,18 @@ #include "esphome/core/hal.h" #include "esphome/components/lcd_base/lcd_display.h" +#include "esphome/components/display/display.h" namespace esphome { namespace lcd_gpio { +class GPIOLCDDisplay; + +using gpio_lcd_writer_t = display::DisplayWriter; + class GPIOLCDDisplay : public lcd_base::LCDDisplay { public: - void set_writer(std::function &&writer) { this->writer_ = std::move(writer); } + void set_writer(gpio_lcd_writer_t &&writer) { this->writer_ = std::move(writer); } void setup() override; void set_data_pins(GPIOPin *d0, GPIOPin *d1, GPIOPin *d2, GPIOPin *d3) { this->data_pins_[0] = d0; @@ -43,7 +48,7 @@ class GPIOLCDDisplay : public lcd_base::LCDDisplay { GPIOPin *rw_pin_{nullptr}; GPIOPin *enable_pin_{nullptr}; GPIOPin *data_pins_[8]{nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}; - std::function writer_; + gpio_lcd_writer_t writer_; }; } // namespace lcd_gpio diff --git a/esphome/components/lcd_pcf8574/pcf8574_display.h b/esphome/components/lcd_pcf8574/pcf8574_display.h index 4db3afb9b0..672b609036 100644 --- a/esphome/components/lcd_pcf8574/pcf8574_display.h +++ b/esphome/components/lcd_pcf8574/pcf8574_display.h @@ -3,13 +3,18 @@ #include "esphome/core/component.h" #include "esphome/components/lcd_base/lcd_display.h" #include "esphome/components/i2c/i2c.h" +#include "esphome/components/display/display.h" namespace esphome { namespace lcd_pcf8574 { +class PCF8574LCDDisplay; + +using pcf8574_lcd_writer_t = display::DisplayWriter; + class PCF8574LCDDisplay : public lcd_base::LCDDisplay, public i2c::I2CDevice { public: - void set_writer(std::function &&writer) { this->writer_ = std::move(writer); } + void set_writer(pcf8574_lcd_writer_t &&writer) { this->writer_ = std::move(writer); } void setup() override; void dump_config() override; void backlight(); @@ -24,7 +29,7 @@ class PCF8574LCDDisplay : public lcd_base::LCDDisplay, public i2c::I2CDevice { // Stores the current state of the backlight. uint8_t backlight_value_; - std::function writer_; + pcf8574_lcd_writer_t writer_; }; } // namespace lcd_pcf8574 diff --git a/esphome/components/max7219/max7219.h b/esphome/components/max7219/max7219.h index 270edf3282..58d871d54c 100644 --- a/esphome/components/max7219/max7219.h +++ b/esphome/components/max7219/max7219.h @@ -4,13 +4,14 @@ #include "esphome/core/time.h" #include "esphome/components/spi/spi.h" +#include "esphome/components/display/display.h" namespace esphome { namespace max7219 { class MAX7219Component; -using max7219_writer_t = std::function; +using max7219_writer_t = display::DisplayWriter; class MAX7219Component : public PollingComponent, public spi::SPIDevice writer_{}; + max7219_writer_t writer_{}; }; } // namespace max7219 diff --git a/esphome/components/max7219digit/max7219digit.h b/esphome/components/max7219digit/max7219digit.h index ead8033803..af419b9b38 100644 --- a/esphome/components/max7219digit/max7219digit.h +++ b/esphome/components/max7219digit/max7219digit.h @@ -23,7 +23,7 @@ enum ScrollMode { class MAX7219Component; -using max7219_writer_t = std::function; +using max7219_writer_t = display::DisplayWriter; class MAX7219Component : public display::DisplayBuffer, public spi::SPIDevice writer_local_{}; + max7219_writer_t writer_local_{}; }; } // namespace max7219digit diff --git a/esphome/components/nextion/nextion.h b/esphome/components/nextion/nextion.h index c078ab9d56..61068b52fc 100644 --- a/esphome/components/nextion/nextion.h +++ b/esphome/components/nextion/nextion.h @@ -9,6 +9,7 @@ #include "esphome/components/uart/uart.h" #include "nextion_base.h" #include "nextion_component.h" +#include "esphome/components/display/display.h" #include "esphome/components/display/display_color_utils.h" #ifdef USE_NEXTION_TFT_UPLOAD @@ -31,7 +32,7 @@ namespace nextion { class Nextion; class NextionComponentBase; -using nextion_writer_t = std::function; +using nextion_writer_t = display::DisplayWriter; static const std::string COMMAND_DELIMITER{static_cast(255), static_cast(255), static_cast(255)}; @@ -1471,7 +1472,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe CallbackManager touch_callback_{}; CallbackManager buffer_overflow_callback_{}; - optional writer_; + nextion_writer_t writer_; optional brightness_; #ifdef USE_NEXTION_CONFIG_DUMP_DEVICE_INFO diff --git a/esphome/components/pvvx_mithermometer/display/pvvx_display.h b/esphome/components/pvvx_mithermometer/display/pvvx_display.h index c7fc523420..8637506bae 100644 --- a/esphome/components/pvvx_mithermometer/display/pvvx_display.h +++ b/esphome/components/pvvx_mithermometer/display/pvvx_display.h @@ -3,6 +3,7 @@ #include "esphome/core/component.h" #include "esphome/core/defines.h" #include "esphome/components/ble_client/ble_client.h" +#include "esphome/components/display/display.h" #include @@ -29,7 +30,7 @@ enum UNIT { UNIT_DEG_E, ///< show "°E" }; -using pvvx_writer_t = std::function; +using pvvx_writer_t = display::DisplayWriter; class PVVXDisplay : public ble_client::BLEClientNode, public PollingComponent { public: @@ -126,7 +127,7 @@ class PVVXDisplay : public ble_client::BLEClientNode, public PollingComponent { esp32_ble_tracker::ESPBTUUID char_uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw("00001f1f-0000-1000-8000-00805f9b34fb"); - optional writer_{}; + pvvx_writer_t writer_{}; }; } // namespace pvvx_mithermometer diff --git a/esphome/components/st7920/st7920.h b/esphome/components/st7920/st7920.h index c9fdad454d..c48fe8cc1c 100644 --- a/esphome/components/st7920/st7920.h +++ b/esphome/components/st7920/st7920.h @@ -9,7 +9,7 @@ namespace st7920 { class ST7920; -using st7920_writer_t = std::function; +using st7920_writer_t = display::DisplayWriter; class ST7920 : public display::DisplayBuffer, public spi::SPIDevice writer_local_{}; + st7920_writer_t writer_local_{}; }; } // namespace st7920 diff --git a/esphome/components/tm1621/tm1621.h b/esphome/components/tm1621/tm1621.h index b9f330e96e..fe923417a6 100644 --- a/esphome/components/tm1621/tm1621.h +++ b/esphome/components/tm1621/tm1621.h @@ -3,13 +3,14 @@ #include "esphome/core/component.h" #include "esphome/core/defines.h" #include "esphome/core/hal.h" +#include "esphome/components/display/display.h" namespace esphome { namespace tm1621 { class TM1621Display; -using tm1621_writer_t = std::function; +using tm1621_writer_t = display::DisplayWriter; class TM1621Display : public PollingComponent { public: @@ -59,7 +60,7 @@ class TM1621Display : public PollingComponent { GPIOPin *cs_pin_; GPIOPin *read_pin_; GPIOPin *write_pin_; - optional writer_{}; + tm1621_writer_t writer_{}; char row_[2][12]; uint8_t state_; uint8_t device_; diff --git a/esphome/components/tm1637/tm1637.h b/esphome/components/tm1637/tm1637.h index d44680c623..b9e96119e9 100644 --- a/esphome/components/tm1637/tm1637.h +++ b/esphome/components/tm1637/tm1637.h @@ -4,6 +4,7 @@ #include "esphome/core/defines.h" #include "esphome/core/hal.h" #include "esphome/core/time.h" +#include "esphome/components/display/display.h" #include @@ -19,7 +20,7 @@ class TM1637Display; class TM1637Key; #endif -using tm1637_writer_t = std::function; +using tm1637_writer_t = display::DisplayWriter; class TM1637Display : public PollingComponent { public: @@ -78,7 +79,7 @@ class TM1637Display : public PollingComponent { uint8_t length_; bool inverted_; bool on_{true}; - optional writer_{}; + tm1637_writer_t writer_{}; uint8_t buffer_[6] = {0}; #ifdef USE_BINARY_SENSOR std::vector tm1637_keys_{}; diff --git a/esphome/components/tm1638/tm1638.h b/esphome/components/tm1638/tm1638.h index add72cfdf3..f6b2922ecf 100644 --- a/esphome/components/tm1638/tm1638.h +++ b/esphome/components/tm1638/tm1638.h @@ -5,6 +5,7 @@ #include "esphome/core/defines.h" #include "esphome/core/hal.h" #include "esphome/core/time.h" +#include "esphome/components/display/display.h" #include @@ -18,7 +19,7 @@ class KeyListener { class TM1638Component; -using tm1638_writer_t = std::function; +using tm1638_writer_t = display::DisplayWriter; class TM1638Component : public PollingComponent { public: @@ -70,7 +71,7 @@ class TM1638Component : public PollingComponent { GPIOPin *stb_pin_; GPIOPin *dio_pin_; uint8_t *buffer_ = new uint8_t[8]; - optional writer_{}; + tm1638_writer_t writer_{}; std::vector listeners_{}; }; diff --git a/tests/components/image/test.host.yaml b/tests/components/image/test.host.yaml index 0411195e2a..aa45497088 100644 --- a/tests/components/image/test.host.yaml +++ b/tests/components/image/test.host.yaml @@ -1,5 +1,6 @@ display: - platform: sdl + id: image_display auto_clear_enabled: false dimensions: width: 480 diff --git a/tests/components/sdl/common.yaml b/tests/components/sdl/common.yaml index 52991d595c..66f93915b6 100644 --- a/tests/components/sdl/common.yaml +++ b/tests/components/sdl/common.yaml @@ -13,11 +13,14 @@ display: binary_sensor: - platform: sdl + sdl_id: sdl_display id: key_up key: SDLK_UP - platform: sdl + sdl_id: sdl_display id: key_down key: SDLK_DOWN - platform: sdl + sdl_id: sdl_display id: key_enter key: SDLK_RETURN