Merge branch 'text_sensor_avoid_copies_const_ref' into integration

This commit is contained in:
J. Nick Koston
2025-12-19 20:07:35 -10:00
5 changed files with 37 additions and 76 deletions
+4 -12
View File
@@ -76,9 +76,7 @@ StateClass Sensor::get_state_class() {
void Sensor::publish_state(float state) {
this->raw_state = state;
// Call raw callbacks (before filters)
this->callbacks_.call_first(this->raw_count_, state);
this->raw_callback_.call(state);
ESP_LOGV(TAG, "'%s': Received new state %f", this->name_.c_str(), state);
@@ -89,12 +87,9 @@ void Sensor::publish_state(float state) {
}
}
void Sensor::add_on_state_callback(std::function<void(float)> &&callback) {
this->callbacks_.add_second(std::move(callback));
}
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) {
this->callbacks_.add_first(std::move(callback), &this->raw_count_);
this->raw_callback_.add(std::move(callback));
}
void Sensor::add_filter(Filter *filter) {
@@ -134,10 +129,7 @@ void Sensor::internal_send_state_to_frontend(float state) {
this->state = state;
ESP_LOGD(TAG, "'%s': Sending state %.5f %s with %d decimals of accuracy", this->get_name().c_str(), state,
this->get_unit_of_measurement_ref().c_str(), this->get_accuracy_decimals());
// Call filtered callbacks (after filters)
this->callbacks_.call_second(this->raw_count_, state);
this->callback_.call(state);
#if defined(USE_SENSOR) && defined(USE_CONTROLLER_REGISTRY)
ControllerRegistry::notify_sensor_update(this);
#endif
+2 -3
View File
@@ -125,7 +125,8 @@ class Sensor : public EntityBase, public EntityBase_DeviceClass, public EntityBa
void internal_send_state_to_frontend(float state);
protected:
PartitionedCallbackManager<void(float)> 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.
@@ -140,8 +141,6 @@ class Sensor : public EntityBase, public EntityBase_DeviceClass, public EntityBa
uint8_t force_update : 1;
uint8_t reserved : 5; // Reserved for future use
} sensor_flags_{};
uint8_t raw_count_{0}; ///< Number of raw callbacks (partition point in callbacks_ vector)
};
} // namespace sensor
+4 -10
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
// Call raw callbacks (before filters)
this->callbacks_.call_first(this->raw_count_, state);
this->raw_callback_.call(state);
ESP_LOGV(TAG, "'%s': Received new state %s", this->name_.c_str(), state.c_str());
@@ -74,11 +72,10 @@ void TextSensor::clear_filters() {
}
void TextSensor::add_on_state_callback(std::function<void(const std::string &)> callback) {
this->callbacks_.add_second(std::move(callback));
this->callback_.add(std::move(callback));
}
void TextSensor::add_on_raw_state_callback(std::function<void(const std::string &)> callback) {
this->callbacks_.add_first(std::move(callback), &this->raw_count_);
this->raw_callback_.add(std::move(callback));
}
std::string TextSensor::get_state() const { return this->state; }
@@ -93,10 +90,7 @@ void TextSensor::internal_send_state_to_frontend(const std::string &state) {
this->state = state;
this->set_has_state(true);
ESP_LOGD(TAG, "'%s': Sending state '%s'", this->name_.c_str(), state.c_str());
// Call filtered callbacks (after filters)
this->callbacks_.call_second(this->raw_count_, state);
this->callback_.call(state);
#if defined(USE_TEXT_SENSOR) && defined(USE_CONTROLLER_REGISTRY)
ControllerRegistry::notify_text_sensor_update(this);
#endif
+2 -3
View File
@@ -65,11 +65,10 @@ class TextSensor : public EntityBase, public EntityBase_DeviceClass {
void internal_send_state_to_frontend(const std::string &state);
protected:
PartitionedCallbackManager<void(const std::string &)> callbacks_;
LazyCallbackManager<void(const std::string &)> raw_callback_; ///< Storage for raw state callbacks.
LazyCallbackManager<void(const std::string &)> callback_; ///< Storage for filtered state callbacks.
Filter *filter_list_{nullptr}; ///< Store all active filters.
uint8_t raw_count_{0}; ///< Number of raw callbacks (partition point in callbacks_ vector)
};
} // namespace text_sensor
+25 -48
View File
@@ -964,71 +964,48 @@ template<typename... Ts> class CallbackManager<void(Ts...)> {
std::vector<std::function<void(Ts...)>> callbacks_;
};
template<typename... X> class PartitionedCallbackManager;
template<typename... X> class LazyCallbackManager;
/** Helper class for callbacks partitioned into two sections.
/** Lazy-allocating callback manager that only allocates memory when callbacks are registered.
*
* Uses a single vector partitioned into two sections: [first_0, ..., first_m-1, second_0, ..., second_n-1]
* The partition point is tracked externally by the caller (typically stored in the entity class for optimal alignment).
* 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 efficient: Only stores a single pointer (4 bytes on 32-bit platforms, 8 bytes on 64-bit platforms).
* The partition count lives in the entity class where it can be packed with other small fields to avoid padding waste.
*
* Design rationale: The asymmetric API (add_first takes first_count*, while call_first/call_second take it by value)
* is intentional - add_first must increment the count, while call methods only read it. This avoids storing first_count
* internally, saving memory per instance.
* 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 PartitionedCallbackManager<void(Ts...)> {
template<typename... Ts> class LazyCallbackManager<void(Ts...)> {
public:
/// Add a callback to the first partition.
void add_first(std::function<void(Ts...)> &&callback, uint8_t *first_count) {
/// 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<std::vector<std::function<void(Ts...)>>>();
this->callbacks_ = make_unique<CallbackManager<void(Ts...)>>();
}
// Add to first partition: append then rotate into position
this->callbacks_->push_back(std::move(callback));
// Avoid potential underflow: rewrite comparison to not subtract from size()
if (*first_count + 1 < this->callbacks_->size()) {
// Use std::rotate to maintain registration order in second partition
std::rotate(this->callbacks_->begin() + *first_count, this->callbacks_->end() - 1, this->callbacks_->end());
}
(*first_count)++;
this->callbacks_->add(std::move(callback));
}
/// Add a callback to the second partition.
void add_second(std::function<void(Ts...)> &&callback) {
if (!this->callbacks_) {
this->callbacks_ = make_unique<std::vector<std::function<void(Ts...)>>>();
}
// Add to second partition: just append (already at end after first partition)
this->callbacks_->push_back(std::move(callback));
}
/// Call all callbacks in the first partition.
void call_first(uint8_t first_count, Ts... args) {
/// Call all callbacks in this manager. No-op if no callbacks registered.
void call(Ts... args) {
if (this->callbacks_) {
for (size_t i = 0; i < first_count; i++) {
(*this->callbacks_)[i](args...);
}
this->callbacks_->call(args...);
}
}
/// Call all callbacks in the second partition.
void call_second(uint8_t first_count, Ts... args) {
if (this->callbacks_) {
for (size_t i = first_count; i < this->callbacks_->size(); i++) {
(*this->callbacks_)[i](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:
/// Partitioned callback storage: [first_0, ..., first_m-1, second_0, ..., second_n-1]
std::unique_ptr<std::vector<std::function<void(Ts...)>>> callbacks_;
std::unique_ptr<CallbackManager<void(Ts...)>> callbacks_;
};
template<typename... X> class LazyCallbackManager;