mirror of
https://github.com/esphome/esphome.git
synced 2025-11-18 07:45:56 +00:00
[api] Eliminate heap allocations when transmitting Event types (#11773)
This commit is contained in:
@@ -1294,11 +1294,11 @@ void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRe
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_EVENT
|
#ifdef USE_EVENT
|
||||||
void APIConnection::send_event(event::Event *event, const std::string &event_type) {
|
void APIConnection::send_event(event::Event *event, const char *event_type) {
|
||||||
this->schedule_message_(event, MessageCreator(event_type), EventResponse::MESSAGE_TYPE,
|
this->schedule_message_(event, MessageCreator(event_type), EventResponse::MESSAGE_TYPE,
|
||||||
EventResponse::ESTIMATED_SIZE);
|
EventResponse::ESTIMATED_SIZE);
|
||||||
}
|
}
|
||||||
uint16_t APIConnection::try_send_event_response(event::Event *event, const std::string &event_type, APIConnection *conn,
|
uint16_t APIConnection::try_send_event_response(event::Event *event, const char *event_type, APIConnection *conn,
|
||||||
uint32_t remaining_size, bool is_single) {
|
uint32_t remaining_size, bool is_single) {
|
||||||
EventResponse resp;
|
EventResponse resp;
|
||||||
resp.set_event_type(StringRef(event_type));
|
resp.set_event_type(StringRef(event_type));
|
||||||
@@ -1650,9 +1650,7 @@ void APIConnection::DeferredBatch::add_item(EntityBase *entity, MessageCreator c
|
|||||||
// O(n) but optimized for RAM and not performance.
|
// O(n) but optimized for RAM and not performance.
|
||||||
for (auto &item : items) {
|
for (auto &item : items) {
|
||||||
if (item.entity == entity && item.message_type == message_type) {
|
if (item.entity == entity && item.message_type == message_type) {
|
||||||
// Clean up old creator before replacing
|
// Replace with new creator
|
||||||
item.creator.cleanup(message_type);
|
|
||||||
// Move assign the new creator
|
|
||||||
item.creator = std::move(creator);
|
item.creator = std::move(creator);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1822,7 +1820,7 @@ void APIConnection::process_batch_() {
|
|||||||
|
|
||||||
// Handle remaining items more efficiently
|
// Handle remaining items more efficiently
|
||||||
if (items_processed < this->deferred_batch_.size()) {
|
if (items_processed < this->deferred_batch_.size()) {
|
||||||
// Remove processed items from the beginning with proper cleanup
|
// Remove processed items from the beginning
|
||||||
this->deferred_batch_.remove_front(items_processed);
|
this->deferred_batch_.remove_front(items_processed);
|
||||||
// Reschedule for remaining items
|
// Reschedule for remaining items
|
||||||
this->schedule_batch_();
|
this->schedule_batch_();
|
||||||
@@ -1835,10 +1833,10 @@ void APIConnection::process_batch_() {
|
|||||||
uint16_t APIConnection::MessageCreator::operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
uint16_t APIConnection::MessageCreator::operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||||
bool is_single, uint8_t message_type) const {
|
bool is_single, uint8_t message_type) const {
|
||||||
#ifdef USE_EVENT
|
#ifdef USE_EVENT
|
||||||
// Special case: EventResponse uses string pointer
|
// Special case: EventResponse uses const char * pointer
|
||||||
if (message_type == EventResponse::MESSAGE_TYPE) {
|
if (message_type == EventResponse::MESSAGE_TYPE) {
|
||||||
auto *e = static_cast<event::Event *>(entity);
|
auto *e = static_cast<event::Event *>(entity);
|
||||||
return APIConnection::try_send_event_response(e, *data_.string_ptr, conn, remaining_size, is_single);
|
return APIConnection::try_send_event_response(e, data_.const_char_ptr, conn, remaining_size, is_single);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ class APIConnection final : public APIServerConnection {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_EVENT
|
#ifdef USE_EVENT
|
||||||
void send_event(event::Event *event, const std::string &event_type);
|
void send_event(event::Event *event, const char *event_type);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_UPDATE
|
#ifdef USE_UPDATE
|
||||||
@@ -450,7 +450,7 @@ class APIConnection final : public APIServerConnection {
|
|||||||
bool is_single);
|
bool is_single);
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_EVENT
|
#ifdef USE_EVENT
|
||||||
static uint16_t try_send_event_response(event::Event *event, const std::string &event_type, APIConnection *conn,
|
static uint16_t try_send_event_response(event::Event *event, const char *event_type, APIConnection *conn,
|
||||||
uint32_t remaining_size, bool is_single);
|
uint32_t remaining_size, bool is_single);
|
||||||
static uint16_t try_send_event_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
|
static uint16_t try_send_event_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
|
||||||
#endif
|
#endif
|
||||||
@@ -508,10 +508,8 @@ class APIConnection final : public APIServerConnection {
|
|||||||
// Constructor for function pointer
|
// Constructor for function pointer
|
||||||
MessageCreator(MessageCreatorPtr ptr) { data_.function_ptr = ptr; }
|
MessageCreator(MessageCreatorPtr ptr) { data_.function_ptr = ptr; }
|
||||||
|
|
||||||
// Constructor for string state capture
|
// Constructor for const char * (Event types - no allocation needed)
|
||||||
explicit MessageCreator(const std::string &str_value) { data_.string_ptr = new std::string(str_value); }
|
explicit MessageCreator(const char *str_value) { data_.const_char_ptr = str_value; }
|
||||||
|
|
||||||
// No destructor - cleanup must be called explicitly with message_type
|
|
||||||
|
|
||||||
// Delete copy operations - MessageCreator should only be moved
|
// Delete copy operations - MessageCreator should only be moved
|
||||||
MessageCreator(const MessageCreator &other) = delete;
|
MessageCreator(const MessageCreator &other) = delete;
|
||||||
@@ -523,8 +521,6 @@ class APIConnection final : public APIServerConnection {
|
|||||||
// Move assignment
|
// Move assignment
|
||||||
MessageCreator &operator=(MessageCreator &&other) noexcept {
|
MessageCreator &operator=(MessageCreator &&other) noexcept {
|
||||||
if (this != &other) {
|
if (this != &other) {
|
||||||
// IMPORTANT: Caller must ensure cleanup() was called if this contains a string!
|
|
||||||
// In our usage, this happens in add_item() deduplication and vector::erase()
|
|
||||||
data_ = other.data_;
|
data_ = other.data_;
|
||||||
other.data_.function_ptr = nullptr;
|
other.data_.function_ptr = nullptr;
|
||||||
}
|
}
|
||||||
@@ -535,20 +531,10 @@ class APIConnection final : public APIServerConnection {
|
|||||||
uint16_t operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single,
|
uint16_t operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single,
|
||||||
uint8_t message_type) const;
|
uint8_t message_type) const;
|
||||||
|
|
||||||
// Manual cleanup method - must be called before destruction for string types
|
|
||||||
void cleanup(uint8_t message_type) {
|
|
||||||
#ifdef USE_EVENT
|
|
||||||
if (message_type == EventResponse::MESSAGE_TYPE && data_.string_ptr != nullptr) {
|
|
||||||
delete data_.string_ptr;
|
|
||||||
data_.string_ptr = nullptr;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
union Data {
|
union Data {
|
||||||
MessageCreatorPtr function_ptr;
|
MessageCreatorPtr function_ptr;
|
||||||
std::string *string_ptr;
|
const char *const_char_ptr;
|
||||||
} data_; // 4 bytes on 32-bit, 8 bytes on 64-bit - same as before
|
} data_; // 4 bytes on 32-bit, 8 bytes on 64-bit - same as before
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -568,42 +554,24 @@ class APIConnection final : public APIServerConnection {
|
|||||||
std::vector<BatchItem> items;
|
std::vector<BatchItem> items;
|
||||||
uint32_t batch_start_time{0};
|
uint32_t batch_start_time{0};
|
||||||
|
|
||||||
private:
|
|
||||||
// Helper to cleanup items from the beginning
|
|
||||||
void cleanup_items_(size_t count) {
|
|
||||||
for (size_t i = 0; i < count; i++) {
|
|
||||||
items[i].creator.cleanup(items[i].message_type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
DeferredBatch() {
|
DeferredBatch() {
|
||||||
// Pre-allocate capacity for typical batch sizes to avoid reallocation
|
// Pre-allocate capacity for typical batch sizes to avoid reallocation
|
||||||
items.reserve(8);
|
items.reserve(8);
|
||||||
}
|
}
|
||||||
|
|
||||||
~DeferredBatch() {
|
|
||||||
// Ensure cleanup of any remaining items
|
|
||||||
clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add item to the batch
|
// Add item to the batch
|
||||||
void add_item(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size);
|
void add_item(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size);
|
||||||
// Add item to the front of the batch (for high priority messages like ping)
|
// Add item to the front of the batch (for high priority messages like ping)
|
||||||
void add_item_front(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size);
|
void add_item_front(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size);
|
||||||
|
|
||||||
// Clear all items with proper cleanup
|
// Clear all items
|
||||||
void clear() {
|
void clear() {
|
||||||
cleanup_items_(items.size());
|
|
||||||
items.clear();
|
items.clear();
|
||||||
batch_start_time = 0;
|
batch_start_time = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove processed items from the front with proper cleanup
|
// Remove processed items from the front
|
||||||
void remove_front(size_t count) {
|
void remove_front(size_t count) { items.erase(items.begin(), items.begin() + count); }
|
||||||
cleanup_items_(count);
|
|
||||||
items.erase(items.begin(), items.begin() + count);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool empty() const { return items.empty(); }
|
bool empty() const { return items.empty(); }
|
||||||
size_t size() const { return items.size(); }
|
size_t size() const { return items.size(); }
|
||||||
|
|||||||
Reference in New Issue
Block a user