From 4ff0d30e076d8b4c1a886e7e4ee0b04a26969abb Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 26 Mar 2026 13:24:58 -1000 Subject: [PATCH] Use leak-on-purpose raw pointer for Fan-owned preset modes vector Fan entities live for the entire program lifetime, so the preset modes vector never needs to be freed. Use a raw pointer (null by default, allocated on first set_supported_preset_modes() call) instead of an inline std::vector member. This saves 24 bytes of RAM per Fan instance for components that don't use preset modes (binary, bedjet, tuya, etc.). --- esphome/components/fan/fan.cpp | 10 +++++----- esphome/components/fan/fan.h | 13 +++++++++---- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/esphome/components/fan/fan.cpp b/esphome/components/fan/fan.cpp index 659abf70a9..f486c40ca4 100644 --- a/esphome/components/fan/fan.cpp +++ b/esphome/components/fan/fan.cpp @@ -148,9 +148,9 @@ const char *Fan::find_preset_mode_(const char *preset_mode) { } const char *Fan::find_preset_mode_(const char *preset_mode, size_t len) { - if (preset_mode == nullptr || len == 0) + if (preset_mode == nullptr || len == 0 || !this->supported_preset_modes_) return nullptr; - for (const char *mode : this->supported_preset_modes_) { + for (const char *mode : *this->supported_preset_modes_) { if (strncmp(mode, preset_mode, len) == 0 && mode[len] == '\0') return mode; } @@ -274,10 +274,10 @@ void Fan::save_state_() { state.direction = this->direction; state.preset_mode = FanRestoreState::NO_PRESET; - if (this->has_preset_mode()) { + if (this->has_preset_mode() && this->supported_preset_modes_) { // Find index of current preset mode (pointer comparison is safe since preset is from our vector) - for (size_t i = 0; i < this->supported_preset_modes_.size(); i++) { - if (this->supported_preset_modes_[i] == this->preset_mode_) { + for (size_t i = 0; i < this->supported_preset_modes_->size(); i++) { + if ((*this->supported_preset_modes_)[i] == this->preset_mode_) { state.preset_mode = i; break; } diff --git a/esphome/components/fan/fan.h b/esphome/components/fan/fan.h index 42999d16f7..2b76fa24da 100644 --- a/esphome/components/fan/fan.h +++ b/esphome/components/fan/fan.h @@ -132,10 +132,15 @@ class Fan : public EntityBase { /// Set the supported preset modes (stored on Fan, referenced by FanTraits via pointer). void set_supported_preset_modes(std::initializer_list preset_modes) { - this->supported_preset_modes_ = preset_modes; + if (!this->supported_preset_modes_) + this->supported_preset_modes_ = + new std::vector(); // NOLINT - intentional leak, entity lives forever + *this->supported_preset_modes_ = preset_modes; } void set_supported_preset_modes(const std::vector &preset_modes) { - this->supported_preset_modes_ = preset_modes; + if (!this->supported_preset_modes_) + this->supported_preset_modes_ = new std::vector(); // NOLINT + *this->supported_preset_modes_ = preset_modes; } /// Set the restore mode of this fan. @@ -179,8 +184,8 @@ class Fan : public EntityBase { ESPPreferenceObject rtc_; FanRestoreMode restore_mode_; - /// Preset mode storage — owned by Fan, referenced by FanTraits via pointer. - std::vector supported_preset_modes_; + /// Preset mode storage — allocated on first use, never freed (entity lives forever). + std::vector *supported_preset_modes_{nullptr}; private: const char *preset_mode_{nullptr};