mirror of
https://github.com/esphome/esphome.git
synced 2025-07-28 14:16:40 +00:00
Optimize API connection memory with tagged pointers (#9203)
This commit is contained in:
parent
c74e5e0f04
commit
79e3d2b2d7
@ -1432,7 +1432,7 @@ void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRe
|
||||
|
||||
#ifdef USE_EVENT
|
||||
void APIConnection::send_event(event::Event *event, const std::string &event_type) {
|
||||
this->schedule_message_(event, MessageCreator(event_type, EventResponse::MESSAGE_TYPE), EventResponse::MESSAGE_TYPE);
|
||||
this->schedule_message_(event, MessageCreator(event_type), EventResponse::MESSAGE_TYPE);
|
||||
}
|
||||
void APIConnection::send_event_info(event::Event *event) {
|
||||
this->schedule_message_(event, &APIConnection::try_send_event_info, ListEntitiesEventResponse::MESSAGE_TYPE);
|
||||
@ -1787,7 +1787,8 @@ void APIConnection::process_batch_() {
|
||||
const auto &item = this->deferred_batch_.items[0];
|
||||
|
||||
// Let the creator calculate size and encode if it fits
|
||||
uint16_t payload_size = item.creator(item.entity, this, std::numeric_limits<uint16_t>::max(), true);
|
||||
uint16_t payload_size =
|
||||
item.creator(item.entity, this, std::numeric_limits<uint16_t>::max(), true, item.message_type);
|
||||
|
||||
if (payload_size > 0 &&
|
||||
this->send_buffer(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, item.message_type)) {
|
||||
@ -1837,7 +1838,7 @@ void APIConnection::process_batch_() {
|
||||
for (const auto &item : this->deferred_batch_.items) {
|
||||
// Try to encode message
|
||||
// The creator will calculate overhead to determine if the message fits
|
||||
uint16_t payload_size = item.creator(item.entity, this, remaining_size, false);
|
||||
uint16_t payload_size = item.creator(item.entity, this, remaining_size, false, item.message_type);
|
||||
|
||||
if (payload_size == 0) {
|
||||
// Message won't fit, stop processing
|
||||
@ -1900,21 +1901,23 @@ void APIConnection::process_batch_() {
|
||||
}
|
||||
|
||||
uint16_t APIConnection::MessageCreator::operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
bool is_single) const {
|
||||
switch (message_type_) {
|
||||
case 0: // Function pointer
|
||||
return data_.ptr(entity, conn, remaining_size, is_single);
|
||||
|
||||
bool is_single, uint16_t message_type) const {
|
||||
if (has_tagged_string_ptr_()) {
|
||||
// Handle string-based messages
|
||||
switch (message_type) {
|
||||
#ifdef USE_EVENT
|
||||
case EventResponse::MESSAGE_TYPE: {
|
||||
auto *e = static_cast<event::Event *>(entity);
|
||||
return APIConnection::try_send_event_response(e, *data_.string_ptr, conn, remaining_size, is_single);
|
||||
}
|
||||
case EventResponse::MESSAGE_TYPE: {
|
||||
auto *e = static_cast<event::Event *>(entity);
|
||||
return APIConnection::try_send_event_response(e, *get_string_ptr_(), conn, remaining_size, is_single);
|
||||
}
|
||||
#endif
|
||||
|
||||
default:
|
||||
// Should not happen, return 0 to indicate no message
|
||||
return 0;
|
||||
default:
|
||||
// Should not happen, return 0 to indicate no message
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
// Function pointer case
|
||||
return data_.ptr(entity, conn, remaining_size, is_single);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -483,55 +483,57 @@ class APIConnection : public APIServerConnection {
|
||||
// Function pointer type for message encoding
|
||||
using MessageCreatorPtr = uint16_t (*)(EntityBase *, APIConnection *, uint32_t remaining_size, bool is_single);
|
||||
|
||||
// Optimized MessageCreator class using union dispatch
|
||||
// Optimized MessageCreator class using tagged pointer
|
||||
class MessageCreator {
|
||||
// Ensure pointer alignment allows LSB tagging
|
||||
static_assert(alignof(std::string *) > 1, "String pointer alignment must be > 1 for LSB tagging");
|
||||
|
||||
public:
|
||||
// Constructor for function pointer (message_type = 0)
|
||||
MessageCreator(MessageCreatorPtr ptr) : message_type_(0) { data_.ptr = ptr; }
|
||||
// Constructor for function pointer
|
||||
MessageCreator(MessageCreatorPtr ptr) {
|
||||
// Function pointers are always aligned, so LSB is 0
|
||||
data_.ptr = ptr;
|
||||
}
|
||||
|
||||
// Constructor for string state capture
|
||||
MessageCreator(const std::string &value, uint16_t msg_type) : message_type_(msg_type) {
|
||||
data_.string_ptr = new std::string(value);
|
||||
explicit MessageCreator(const std::string &str_value) {
|
||||
// Allocate string and tag the pointer
|
||||
auto *str = new std::string(str_value);
|
||||
// Set LSB to 1 to indicate string pointer
|
||||
data_.tagged = reinterpret_cast<uintptr_t>(str) | 1;
|
||||
}
|
||||
|
||||
// Destructor
|
||||
~MessageCreator() {
|
||||
// Clean up string data for string-based message types
|
||||
if (uses_string_data_()) {
|
||||
delete data_.string_ptr;
|
||||
if (has_tagged_string_ptr_()) {
|
||||
delete get_string_ptr_();
|
||||
}
|
||||
}
|
||||
|
||||
// Copy constructor
|
||||
MessageCreator(const MessageCreator &other) : message_type_(other.message_type_) {
|
||||
if (message_type_ == 0) {
|
||||
data_.ptr = other.data_.ptr;
|
||||
} else if (uses_string_data_()) {
|
||||
data_.string_ptr = new std::string(*other.data_.string_ptr);
|
||||
MessageCreator(const MessageCreator &other) {
|
||||
if (other.has_tagged_string_ptr_()) {
|
||||
auto *str = new std::string(*other.get_string_ptr_());
|
||||
data_.tagged = reinterpret_cast<uintptr_t>(str) | 1;
|
||||
} else {
|
||||
data_ = other.data_; // For POD types
|
||||
data_ = other.data_;
|
||||
}
|
||||
}
|
||||
|
||||
// Move constructor
|
||||
MessageCreator(MessageCreator &&other) noexcept : data_(other.data_), message_type_(other.message_type_) {
|
||||
other.message_type_ = 0; // Reset other to function pointer type
|
||||
other.data_.ptr = nullptr;
|
||||
}
|
||||
MessageCreator(MessageCreator &&other) noexcept : data_(other.data_) { other.data_.ptr = nullptr; }
|
||||
|
||||
// Assignment operators (needed for batch deduplication)
|
||||
MessageCreator &operator=(const MessageCreator &other) {
|
||||
if (this != &other) {
|
||||
// Clean up current string data if needed
|
||||
if (uses_string_data_()) {
|
||||
delete data_.string_ptr;
|
||||
if (has_tagged_string_ptr_()) {
|
||||
delete get_string_ptr_();
|
||||
}
|
||||
// Copy new data
|
||||
message_type_ = other.message_type_;
|
||||
if (other.message_type_ == 0) {
|
||||
data_.ptr = other.data_.ptr;
|
||||
} else if (other.uses_string_data_()) {
|
||||
data_.string_ptr = new std::string(*other.data_.string_ptr);
|
||||
if (other.has_tagged_string_ptr_()) {
|
||||
auto *str = new std::string(*other.get_string_ptr_());
|
||||
data_.tagged = reinterpret_cast<uintptr_t>(str) | 1;
|
||||
} else {
|
||||
data_ = other.data_;
|
||||
}
|
||||
@ -542,30 +544,35 @@ class APIConnection : public APIServerConnection {
|
||||
MessageCreator &operator=(MessageCreator &&other) noexcept {
|
||||
if (this != &other) {
|
||||
// Clean up current string data if needed
|
||||
if (uses_string_data_()) {
|
||||
delete data_.string_ptr;
|
||||
if (has_tagged_string_ptr_()) {
|
||||
delete get_string_ptr_();
|
||||
}
|
||||
// Move data
|
||||
message_type_ = other.message_type_;
|
||||
data_ = other.data_;
|
||||
// Reset other to safe state
|
||||
other.message_type_ = 0;
|
||||
other.data_.ptr = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Call operator
|
||||
uint16_t operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single) const;
|
||||
// Call operator - now accepts message_type as parameter
|
||||
uint16_t operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single,
|
||||
uint16_t message_type) const;
|
||||
|
||||
private:
|
||||
// Helper to check if this message type uses heap-allocated strings
|
||||
bool uses_string_data_() const { return message_type_ == EventResponse::MESSAGE_TYPE; }
|
||||
union CreatorData {
|
||||
MessageCreatorPtr ptr; // 8 bytes
|
||||
std::string *string_ptr; // 8 bytes
|
||||
} data_; // 8 bytes
|
||||
uint16_t message_type_; // 2 bytes (0 = function ptr, >0 = state capture)
|
||||
// Check if this contains a string pointer
|
||||
bool has_tagged_string_ptr_() const { return (data_.tagged & 1) != 0; }
|
||||
|
||||
// Get the actual string pointer (clears the tag bit)
|
||||
std::string *get_string_ptr_() const {
|
||||
// NOLINTNEXTLINE(performance-no-int-to-ptr)
|
||||
return reinterpret_cast<std::string *>(data_.tagged & ~uintptr_t(1));
|
||||
}
|
||||
|
||||
union {
|
||||
MessageCreatorPtr ptr;
|
||||
uintptr_t tagged;
|
||||
} data_; // 4 bytes on 32-bit
|
||||
};
|
||||
|
||||
// Generic batching mechanism for both state updates and entity info
|
||||
|
Loading…
x
Reference in New Issue
Block a user