[core] Migrate entities to use lazy callbacks (#12580)

This commit is contained in:
J. Nick Koston
2025-12-19 19:04:21 -10:00
committed by GitHub
parent be6c1e4ec0
commit 3e313014e1
20 changed files with 72 additions and 39 deletions

View File

@@ -132,13 +132,13 @@ class AlarmControlPanel : public EntityBase {
// the call control function
virtual void control(const AlarmControlPanelCall &call) = 0;
// state callback - triggers check get_state() for specific state
CallbackManager<void()> state_callback_{};
LazyCallbackManager<void()> state_callback_{};
// clear callback - fires when leaving TRIGGERED state
CallbackManager<void()> cleared_callback_{};
LazyCallbackManager<void()> cleared_callback_{};
// chime callback
CallbackManager<void()> chime_callback_{};
LazyCallbackManager<void()> chime_callback_{};
// ready callback
CallbackManager<void()> ready_callback_{};
LazyCallbackManager<void()> ready_callback_{};
};
} // namespace alarm_control_panel

View File

@@ -41,7 +41,7 @@ class Button : public EntityBase, public EntityBase_DeviceClass {
*/
virtual void press_action() = 0;
CallbackManager<void()> press_callback_{};
LazyCallbackManager<void()> press_callback_{};
};
} // namespace esphome::button

View File

@@ -326,8 +326,8 @@ class Climate : public EntityBase {
void dump_traits_(const char *tag);
CallbackManager<void(Climate &)> state_callback_{};
CallbackManager<void(ClimateCall &)> control_callback_{};
LazyCallbackManager<void(Climate &)> state_callback_{};
LazyCallbackManager<void(ClimateCall &)> control_callback_{};
ESPPreferenceObject rtc_;
#ifdef USE_CLIMATE_VISUAL_OVERRIDES
float visual_min_temperature_override_{NAN};

View File

@@ -152,7 +152,7 @@ class Cover : public EntityBase, public EntityBase_DeviceClass {
optional<CoverRestoreState> restore_state_();
CallbackManager<void()> state_callback_{};
LazyCallbackManager<void()> state_callback_{};
ESPPreferenceObject rtc_;
};

View File

@@ -22,7 +22,7 @@ class DateTimeBase : public EntityBase {
#endif
protected:
CallbackManager<void()> state_callback_;
LazyCallbackManager<void()> state_callback_;
#ifdef USE_TIME
time::RealTimeClock *rtc_;

View File

@@ -50,7 +50,7 @@ class Event : public EntityBase, public EntityBase_DeviceClass {
void add_on_event_callback(std::function<void(const std::string &event_type)> &&callback);
protected:
CallbackManager<void(const std::string &event_type)> event_callback_;
LazyCallbackManager<void(const std::string &event_type)> event_callback_;
FixedVector<const char *> types_;
private:

View File

@@ -155,7 +155,7 @@ class Fan : public EntityBase {
const char *find_preset_mode_(const char *preset_mode);
const char *find_preset_mode_(const char *preset_mode, size_t len);
CallbackManager<void()> state_callback_{};
LazyCallbackManager<void()> state_callback_{};
ESPPreferenceObject rtc_;
FanRestoreMode restore_mode_;

View File

@@ -174,7 +174,7 @@ class Lock : public EntityBase {
*/
virtual void control(const LockCall &call) = 0;
CallbackManager<void()> state_callback_{};
LazyCallbackManager<void()> state_callback_{};
Deduplicator<LockState> publish_dedup_;
ESPPreferenceObject rtc_;
};

View File

@@ -157,7 +157,7 @@ class MediaPlayer : public EntityBase {
virtual void control(const MediaPlayerCall &call) = 0;
CallbackManager<void()> state_callback_{};
LazyCallbackManager<void()> state_callback_{};
};
} // namespace media_player

View File

@@ -49,7 +49,7 @@ class Number : public EntityBase {
*/
virtual void control(float value) = 0;
CallbackManager<void(float)> state_callback_;
LazyCallbackManager<void(float)> state_callback_;
};
} // namespace esphome::number

View File

@@ -111,7 +111,7 @@ class Select : public EntityBase {
}
}
CallbackManager<void(size_t)> state_callback_;
LazyCallbackManager<void(size_t)> state_callback_;
};
} // namespace esphome::select

View File

@@ -76,9 +76,7 @@ StateClass Sensor::get_state_class() {
void Sensor::publish_state(float state) {
this->raw_state = state;
if (this->raw_callback_) {
this->raw_callback_->call(state);
}
this->raw_callback_.call(state);
ESP_LOGV(TAG, "'%s': Received new state %f", this->name_.c_str(), state);
@@ -91,10 +89,7 @@ void Sensor::publish_state(float state) {
void Sensor::add_on_state_callback(std::function<void(float)> &&callback) { this->callback_.add(std::move(callback)); }
void Sensor::add_on_raw_state_callback(std::function<void(float)> &&callback) {
if (!this->raw_callback_) {
this->raw_callback_ = make_unique<CallbackManager<void(float)>>();
}
this->raw_callback_->add(std::move(callback));
this->raw_callback_.add(std::move(callback));
}
void Sensor::add_filter(Filter *filter) {

View File

@@ -125,8 +125,8 @@ class Sensor : public EntityBase, public EntityBase_DeviceClass, public EntityBa
void internal_send_state_to_frontend(float state);
protected:
std::unique_ptr<CallbackManager<void(float)>> raw_callback_; ///< Storage for raw state callbacks (lazy allocated).
CallbackManager<void(float)> callback_; ///< Storage for filtered state callbacks.
LazyCallbackManager<void(float)> raw_callback_; ///< Storage for raw state callbacks.
LazyCallbackManager<void(float)> callback_; ///< Storage for filtered state callbacks.
Filter *filter_list_{nullptr}; ///< Store all active filters.

View File

@@ -134,8 +134,8 @@ class Switch : public EntityBase, public EntityBase_DeviceClass {
// Pointer first (4 bytes)
ESPPreferenceObject rtc_;
// CallbackManager (12 bytes on 32-bit - contains vector)
CallbackManager<void(bool)> state_callback_{};
// LazyCallbackManager (4 bytes on 32-bit - nullptr when empty)
LazyCallbackManager<void(bool)> state_callback_{};
// Small types grouped together
Deduplicator<bool> publish_dedup_; // 2 bytes (bool has_value_ + bool last_value_)

View File

@@ -44,7 +44,7 @@ class Text : public EntityBase {
*/
virtual void control(const std::string &value) = 0;
CallbackManager<void(const std::string &)> state_callback_;
LazyCallbackManager<void(const std::string &)> state_callback_;
};
} // namespace text

View File

@@ -30,9 +30,7 @@ void TextSensor::publish_state(const std::string &state) {
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
this->raw_state = state;
#pragma GCC diagnostic pop
if (this->raw_callback_) {
this->raw_callback_->call(state);
}
this->raw_callback_.call(state);
ESP_LOGV(TAG, "'%s': Received new state %s", this->name_.c_str(), state.c_str());
@@ -77,10 +75,7 @@ void TextSensor::add_on_state_callback(std::function<void(std::string)> callback
this->callback_.add(std::move(callback));
}
void TextSensor::add_on_raw_state_callback(std::function<void(std::string)> callback) {
if (!this->raw_callback_) {
this->raw_callback_ = make_unique<CallbackManager<void(std::string)>>();
}
this->raw_callback_->add(std::move(callback));
this->raw_callback_.add(std::move(callback));
}
std::string TextSensor::get_state() const { return this->state; }

View File

@@ -65,9 +65,8 @@ class TextSensor : public EntityBase, public EntityBase_DeviceClass {
void internal_send_state_to_frontend(const std::string &state);
protected:
std::unique_ptr<CallbackManager<void(std::string)>>
raw_callback_; ///< Storage for raw state callbacks (lazy allocated).
CallbackManager<void(std::string)> callback_; ///< Storage for filtered state callbacks.
LazyCallbackManager<void(std::string)> raw_callback_; ///< Storage for raw state callbacks.
LazyCallbackManager<void(std::string)> callback_; ///< Storage for filtered state callbacks.
Filter *filter_list_{nullptr}; ///< Store all active filters.
};

View File

@@ -50,7 +50,7 @@ class UpdateEntity : public EntityBase, public EntityBase_DeviceClass {
UpdateState state_{UPDATE_STATE_UNKNOWN};
UpdateInfo update_info_;
CallbackManager<void()> state_callback_{};
LazyCallbackManager<void()> state_callback_{};
std::unique_ptr<Trigger<const UpdateInfo &>> update_available_trigger_{nullptr};
};

View File

@@ -144,7 +144,7 @@ class Valve : public EntityBase, public EntityBase_DeviceClass {
optional<ValveRestoreState> restore_state_();
CallbackManager<void()> state_callback_{};
LazyCallbackManager<void()> state_callback_{};
ESPPreferenceObject rtc_;
};

View File

@@ -934,6 +934,50 @@ template<typename... Ts> class CallbackManager<void(Ts...)> {
std::vector<std::function<void(Ts...)>> callbacks_;
};
template<typename... X> class LazyCallbackManager;
/** Lazy-allocating callback manager that only allocates memory when callbacks are registered.
*
* This is a drop-in replacement for CallbackManager that saves memory when no callbacks
* are registered (common case after the Controller Registry eliminated per-entity callbacks
* from API and web_server components).
*
* Memory overhead comparison (32-bit systems):
* - CallbackManager: 12 bytes (empty std::vector)
* - LazyCallbackManager: 4 bytes (nullptr unique_ptr)
*
* @tparam Ts The arguments for the callbacks, wrapped in void().
*/
template<typename... Ts> class LazyCallbackManager<void(Ts...)> {
public:
/// Add a callback to the list. Allocates the underlying CallbackManager on first use.
void add(std::function<void(Ts...)> &&callback) {
if (!this->callbacks_) {
this->callbacks_ = make_unique<CallbackManager<void(Ts...)>>();
}
this->callbacks_->add(std::move(callback));
}
/// Call all callbacks in this manager. No-op if no callbacks registered.
void call(Ts... args) {
if (this->callbacks_) {
this->callbacks_->call(args...);
}
}
/// Return the number of registered callbacks.
size_t size() const { return this->callbacks_ ? this->callbacks_->size() : 0; }
/// Check if any callbacks are registered.
bool empty() const { return !this->callbacks_ || this->callbacks_->size() == 0; }
/// Call all callbacks in this manager.
void operator()(Ts... args) { this->call(args...); }
protected:
std::unique_ptr<CallbackManager<void(Ts...)>> callbacks_;
};
/// Helper class to deduplicate items in a series of values.
template<typename T> class Deduplicator {
public: