get rid of duplicate buffers

This commit is contained in:
J. Nick Koston
2025-06-06 21:14:05 -05:00
parent 4b50b51faf
commit 2f230f32fa
4 changed files with 35 additions and 25 deletions

View File

@@ -33,8 +33,6 @@ static const int ESP32_CAMERA_STOP_STREAM = 5000;
APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent)
: parent_(parent), initial_state_iterator_(this), list_entities_iterator_(this) {
this->proto_write_buffer_.reserve(64);
#if defined(USE_API_PLAINTEXT) && defined(USE_API_NOISE)
auto noise_ctx = parent->get_noise_ctx();
if (noise_ctx->has_psk()) {
@@ -1669,7 +1667,8 @@ void APIConnection::process_batch_() {
// 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);
if (payload_size > 0 && this->send_buffer(ProtoWriteBuffer{&this->proto_write_buffer_}, item.message_type)) {
if (payload_size > 0 &&
this->send_buffer(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, item.message_type)) {
this->deferred_batch_.clear();
} else if (payload_size == 0) {
// Message too large
@@ -1688,7 +1687,7 @@ void APIConnection::process_batch_() {
const uint8_t footer_size = this->helper_->frame_footer_size();
// Initialize buffer and tracking variables
this->proto_write_buffer_.clear();
this->parent_->get_shared_buffer_ref().clear();
// Pre-calculate exact buffer size needed based on message types
uint32_t total_estimated_size = 0;
@@ -1700,7 +1699,7 @@ void APIConnection::process_batch_() {
uint32_t total_overhead = (header_padding + footer_size) * num_items;
// Reserve based on estimated size (much more accurate than 24-byte worst-case)
this->proto_write_buffer_.reserve(total_estimated_size + total_overhead);
this->parent_->get_shared_buffer_ref().reserve(total_estimated_size + total_overhead);
this->batch_first_message_ = true;
size_t items_processed = 0;
@@ -1732,7 +1731,7 @@ void APIConnection::process_batch_() {
remaining_size -= payload_size;
// Calculate where the next message's header padding will start
// Current buffer size + footer space (that prepare_message_buffer will add for this message)
current_offset = this->proto_write_buffer_.size() + footer_size;
current_offset = this->parent_->get_shared_buffer_ref().size() + footer_size;
items_processed++;
}
@@ -1743,11 +1742,13 @@ void APIConnection::process_batch_() {
// Add footer space for the last message (for Noise protocol MAC)
if (footer_size > 0) {
this->proto_write_buffer_.resize(this->proto_write_buffer_.size() + footer_size);
auto &shared_buf = this->parent_->get_shared_buffer_ref();
shared_buf.resize(shared_buf.size() + footer_size);
}
// Send all collected packets
APIError err = this->helper_->write_protobuf_packets(ProtoWriteBuffer{&this->proto_write_buffer_}, packet_info);
APIError err =
this->helper_->write_protobuf_packets(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, packet_info);
if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
on_fatal_error();
if (err == APIError::SOCKET_WRITE_FAILED && errno == ECONNRESET) {

View File

@@ -233,45 +233,49 @@ class APIConnection : public APIServerConnection {
// Get header padding size - used for both reserve and insert
uint8_t header_padding = this->helper_->frame_header_padding();
this->proto_write_buffer_.clear();
// Get shared buffer from parent server
std::vector<uint8_t> &shared_buf = this->parent_->get_shared_buffer_ref();
shared_buf.clear();
// Reserve space for header padding + message + footer
// - Header padding: space for protocol headers (7 bytes for Noise, 6 for Plaintext)
// - Footer: space for MAC (16 bytes for Noise, 0 for Plaintext)
this->proto_write_buffer_.reserve(reserve_size + header_padding + this->helper_->frame_footer_size());
shared_buf.reserve(reserve_size + header_padding + this->helper_->frame_footer_size());
// Insert header padding bytes so message encoding starts at the correct position
this->proto_write_buffer_.insert(this->proto_write_buffer_.begin(), header_padding, 0);
return {&this->proto_write_buffer_};
shared_buf.insert(shared_buf.begin(), header_padding, 0);
return {&shared_buf};
}
// Prepare buffer for next message in batch
ProtoWriteBuffer prepare_message_buffer(uint32_t message_size, bool is_first_message) {
size_t current_size = this->proto_write_buffer_.size();
// Get reference to shared buffer (it maintains state between batch messages)
std::vector<uint8_t> &shared_buf = this->parent_->get_shared_buffer_ref();
size_t current_size = shared_buf.size();
if (is_first_message) {
// For first message, initialize buffer with header padding
uint8_t header_padding = this->helper_->frame_header_padding();
this->proto_write_buffer_.clear();
this->proto_write_buffer_.reserve(message_size + header_padding);
this->proto_write_buffer_.resize(header_padding);
shared_buf.clear();
shared_buf.reserve(message_size + header_padding);
shared_buf.resize(header_padding);
// Fill header padding with zeros
std::fill(this->proto_write_buffer_.begin(), this->proto_write_buffer_.end(), 0);
std::fill(shared_buf.begin(), shared_buf.end(), 0);
} else {
// For subsequent messages, add footer space for previous message and header for this message
uint8_t footer_size = this->helper_->frame_footer_size();
uint8_t header_padding = this->helper_->frame_header_padding();
// Reserve additional space for everything
this->proto_write_buffer_.reserve(current_size + footer_size + header_padding + message_size);
shared_buf.reserve(current_size + footer_size + header_padding + message_size);
// Single resize to add both footer and header padding
size_t new_size = current_size + footer_size + header_padding;
this->proto_write_buffer_.resize(new_size);
shared_buf.resize(new_size);
// Fill the newly added bytes with zeros (footer + header padding)
std::fill(this->proto_write_buffer_.begin() + current_size, this->proto_write_buffer_.end(), 0);
std::fill(shared_buf.begin() + current_size, shared_buf.end(), 0);
}
return {&this->proto_write_buffer_};
return {&shared_buf};
}
bool try_to_clear_buffer(bool log_out_of_space);
@@ -431,9 +435,6 @@ class APIConnection : public APIServerConnection {
bool remove_{false};
// Buffer used to encode proto messages
// Re-use to prevent allocations
std::vector<uint8_t> proto_write_buffer_;
std::unique_ptr<APIFrameHelper> helper_;
std::string client_info_;

View File

@@ -24,7 +24,11 @@ static const char *const TAG = "api";
// APIServer
APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
APIServer::APIServer() { global_api_server = this; }
APIServer::APIServer() {
global_api_server = this;
// Pre-allocate shared write buffer
shared_write_buffer_.reserve(64);
}
void APIServer::setup() {
ESP_LOGCONFIG(TAG, "Running setup");

View File

@@ -42,6 +42,9 @@ class APIServer : public Component, public Controller {
void set_batch_delay(uint32_t batch_delay);
uint32_t get_batch_delay() const { return batch_delay_; }
// Get reference to shared buffer for API connections
std::vector<uint8_t> &get_shared_buffer_ref() { return shared_write_buffer_; }
#ifdef USE_API_NOISE
bool save_noise_psk(psk_t psk, bool make_active = true);
void set_noise_psk(psk_t psk) { noise_ctx_->set_psk(psk); }
@@ -145,6 +148,7 @@ class APIServer : public Component, public Controller {
uint32_t last_connected_{0};
std::vector<std::unique_ptr<APIConnection>> clients_;
std::string password_;
std::vector<uint8_t> shared_write_buffer_; // Shared proto write buffer for all connections
std::vector<HomeAssistantStateSubscription> state_subs_;
std::vector<UserServiceDescriptor *> user_services_;
Trigger<std::string, std::string> *client_connected_trigger_ = new Trigger<std::string, std::string>();