mirror of
https://github.com/esphome/esphome.git
synced 2025-09-14 17:22:20 +01:00
ble pool
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "ble.h"
|
||||
#include "ble_event_pool.h"
|
||||
#include "queue_index.h"
|
||||
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/log.h"
|
||||
@@ -23,8 +25,7 @@ namespace esp32_ble {
|
||||
|
||||
static const char *const TAG = "esp32_ble";
|
||||
|
||||
static RAMAllocator<BLEEvent> EVENT_ALLOCATOR( // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
RAMAllocator<BLEEvent>::ALLOW_FAILURE | RAMAllocator<BLEEvent>::ALLOC_INTERNAL);
|
||||
// No longer need static allocator - using pre-allocated pool instead
|
||||
|
||||
void ESP32BLE::setup() {
|
||||
global_ble = this;
|
||||
@@ -301,8 +302,16 @@ void ESP32BLE::loop() {
|
||||
break;
|
||||
}
|
||||
|
||||
BLEEvent *ble_event = this->ble_events_.pop();
|
||||
while (ble_event != nullptr) {
|
||||
size_t event_idx = this->ble_events_.pop();
|
||||
while (event_idx != LockFreeIndexQueue<MAX_BLE_QUEUE_SIZE>::INVALID_INDEX) {
|
||||
BLEEvent *ble_event = this->ble_event_pool_.get(event_idx);
|
||||
if (ble_event == nullptr) {
|
||||
// This should not happen - log error and continue
|
||||
ESP_LOGE(TAG, "Invalid event index: %zu", event_idx);
|
||||
event_idx = this->ble_events_.pop();
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (ble_event->type_) {
|
||||
case BLEEvent::GATTS: {
|
||||
esp_gatts_cb_event_t event = ble_event->event_.gatts.gatts_event;
|
||||
@@ -349,10 +358,9 @@ void ESP32BLE::loop() {
|
||||
default:
|
||||
break;
|
||||
}
|
||||
// Destructor will clean up external allocations for GATTC/GATTS
|
||||
ble_event->~BLEEvent();
|
||||
EVENT_ALLOCATOR.deallocate(ble_event, 1);
|
||||
ble_event = this->ble_events_.pop();
|
||||
// Return the event to the pool
|
||||
this->ble_event_pool_.deallocate(event_idx);
|
||||
event_idx = this->ble_events_.pop();
|
||||
}
|
||||
if (this->advertising_ != nullptr) {
|
||||
this->advertising_->loop();
|
||||
@@ -363,6 +371,31 @@ void ESP32BLE::loop() {
|
||||
if (dropped > 0) {
|
||||
ESP_LOGW(TAG, "Dropped %zu BLE events due to buffer overflow", dropped);
|
||||
}
|
||||
|
||||
// Log pool usage periodically (every ~10 seconds)
|
||||
static uint32_t last_pool_log = 0;
|
||||
uint32_t now = millis();
|
||||
if (now - last_pool_log > 10000) {
|
||||
size_t created = this->ble_event_pool_.get_total_created();
|
||||
if (created > 0) {
|
||||
ESP_LOGD(TAG, "BLE event pool: %zu events created (peak usage), %zu currently allocated", created,
|
||||
this->ble_event_pool_.get_allocated_count());
|
||||
}
|
||||
last_pool_log = now;
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to load new event data based on type
|
||||
void load_ble_event(BLEEvent *event, esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p) {
|
||||
event->load_gap_event(e, p);
|
||||
}
|
||||
|
||||
void load_ble_event(BLEEvent *event, esp_gattc_cb_event_t e, esp_gatt_if_t i, esp_ble_gattc_cb_param_t *p) {
|
||||
event->load_gattc_event(e, i, p);
|
||||
}
|
||||
|
||||
void load_ble_event(BLEEvent *event, esp_gatts_cb_event_t e, esp_gatt_if_t i, esp_ble_gatts_cb_param_t *p) {
|
||||
event->load_gatts_event(e, i, p);
|
||||
}
|
||||
|
||||
template<typename... Args> void enqueue_ble_event(Args... args) {
|
||||
@@ -373,23 +406,35 @@ template<typename... Args> void enqueue_ble_event(Args... args) {
|
||||
return;
|
||||
}
|
||||
|
||||
BLEEvent *new_event = EVENT_ALLOCATOR.allocate(1);
|
||||
if (new_event == nullptr) {
|
||||
// Memory too fragmented to allocate new event. Can only drop it until memory comes back
|
||||
// Allocate an event from the pool
|
||||
size_t event_idx = global_ble->ble_event_pool_.allocate();
|
||||
if (event_idx == BLEEventPool<MAX_BLE_QUEUE_SIZE>::INVALID_INDEX) {
|
||||
// Pool is full, drop the event
|
||||
global_ble->ble_events_.increment_dropped_count();
|
||||
return;
|
||||
}
|
||||
new (new_event) BLEEvent(args...);
|
||||
|
||||
// Push the event - since we're the only producer and we checked full() above,
|
||||
// this should always succeed unless we have a bug
|
||||
if (!global_ble->ble_events_.push(new_event)) {
|
||||
// Get the event object
|
||||
BLEEvent *event = global_ble->ble_event_pool_.get(event_idx);
|
||||
if (event == nullptr) {
|
||||
// This should not happen
|
||||
ESP_LOGE(TAG, "Failed to get event from pool at index %zu", event_idx);
|
||||
global_ble->ble_event_pool_.deallocate(event_idx);
|
||||
global_ble->ble_events_.increment_dropped_count();
|
||||
return;
|
||||
}
|
||||
|
||||
// Load new event data (replaces previous event)
|
||||
load_ble_event(event, args...);
|
||||
|
||||
// Push the event index to the queue
|
||||
if (!global_ble->ble_events_.push(event_idx)) {
|
||||
// This should not happen in SPSC queue with single producer
|
||||
ESP_LOGE(TAG, "BLE queue push failed unexpectedly");
|
||||
new_event->~BLEEvent();
|
||||
EVENT_ALLOCATOR.deallocate(new_event, 1);
|
||||
// Return to pool
|
||||
global_ble->ble_event_pool_.deallocate(event_idx);
|
||||
}
|
||||
}
|
||||
} // NOLINT(clang-analyzer-unix.Malloc)
|
||||
|
||||
// Explicit template instantiations for the friend function
|
||||
template void enqueue_ble_event(esp_gap_ble_cb_event_t, esp_ble_gap_cb_param_t *);
|
||||
|
@@ -12,7 +12,9 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include "ble_event.h"
|
||||
#include "ble_event_pool.h"
|
||||
#include "queue.h"
|
||||
#include "queue_index.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
@@ -147,7 +149,8 @@ class ESP32BLE : public Component {
|
||||
std::vector<BLEStatusEventHandler *> ble_status_event_handlers_;
|
||||
BLEComponentState state_{BLE_COMPONENT_STATE_OFF};
|
||||
|
||||
LockFreeQueue<BLEEvent, MAX_BLE_QUEUE_SIZE> ble_events_;
|
||||
LockFreeIndexQueue<MAX_BLE_QUEUE_SIZE> ble_events_;
|
||||
BLEEventPool<MAX_BLE_QUEUE_SIZE> ble_event_pool_;
|
||||
BLEAdvertising *advertising_{};
|
||||
esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE};
|
||||
uint32_t advertising_cycle_time_{};
|
||||
|
@@ -63,125 +63,68 @@ class BLEEvent {
|
||||
// Constructor for GAP events - no external allocations needed
|
||||
BLEEvent(esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p) {
|
||||
this->type_ = GAP;
|
||||
this->event_.gap.gap_event = e;
|
||||
|
||||
if (p == nullptr) {
|
||||
return; // Invalid event, but we can't log in header file
|
||||
}
|
||||
|
||||
// Only copy the data we actually use for each GAP event type
|
||||
switch (e) {
|
||||
case ESP_GAP_BLE_SCAN_RESULT_EVT:
|
||||
// Copy only the fields we use from scan results
|
||||
memcpy(this->event_.gap.scan_result.bda, p->scan_rst.bda, sizeof(esp_bd_addr_t));
|
||||
this->event_.gap.scan_result.ble_addr_type = p->scan_rst.ble_addr_type;
|
||||
this->event_.gap.scan_result.rssi = p->scan_rst.rssi;
|
||||
this->event_.gap.scan_result.adv_data_len = p->scan_rst.adv_data_len;
|
||||
this->event_.gap.scan_result.scan_rsp_len = p->scan_rst.scan_rsp_len;
|
||||
this->event_.gap.scan_result.search_evt = p->scan_rst.search_evt;
|
||||
memcpy(this->event_.gap.scan_result.ble_adv, p->scan_rst.ble_adv,
|
||||
ESP_BLE_ADV_DATA_LEN_MAX + ESP_BLE_SCAN_RSP_DATA_LEN_MAX);
|
||||
break;
|
||||
|
||||
case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT:
|
||||
this->event_.gap.scan_complete.status = p->scan_param_cmpl.status;
|
||||
break;
|
||||
|
||||
case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT:
|
||||
this->event_.gap.scan_complete.status = p->scan_start_cmpl.status;
|
||||
break;
|
||||
|
||||
case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT:
|
||||
this->event_.gap.scan_complete.status = p->scan_stop_cmpl.status;
|
||||
break;
|
||||
|
||||
default:
|
||||
// We only handle 4 GAP event types, others are dropped
|
||||
break;
|
||||
}
|
||||
this->init_gap_data(e, p);
|
||||
}
|
||||
|
||||
// Constructor for GATTC events - uses heap allocation
|
||||
// Creates a copy of the param struct since the original is only valid during the callback
|
||||
BLEEvent(esp_gattc_cb_event_t e, esp_gatt_if_t i, esp_ble_gattc_cb_param_t *p) {
|
||||
this->type_ = GATTC;
|
||||
this->event_.gattc.gattc_event = e;
|
||||
this->event_.gattc.gattc_if = i;
|
||||
|
||||
if (p == nullptr) {
|
||||
this->event_.gattc.gattc_param = nullptr;
|
||||
this->event_.gattc.data = nullptr;
|
||||
return; // Invalid event, but we can't log in header file
|
||||
}
|
||||
|
||||
// Heap-allocate param and data
|
||||
// Heap allocation is used because GATTC/GATTS events are rare (<1% of events)
|
||||
// while GAP events (99%) are stored inline to minimize memory usage
|
||||
this->event_.gattc.gattc_param = new esp_ble_gattc_cb_param_t(*p);
|
||||
|
||||
// Copy data for events that need it
|
||||
switch (e) {
|
||||
case ESP_GATTC_NOTIFY_EVT:
|
||||
this->event_.gattc.data = new std::vector<uint8_t>(p->notify.value, p->notify.value + p->notify.value_len);
|
||||
this->event_.gattc.gattc_param->notify.value = this->event_.gattc.data->data();
|
||||
break;
|
||||
case ESP_GATTC_READ_CHAR_EVT:
|
||||
case ESP_GATTC_READ_DESCR_EVT:
|
||||
this->event_.gattc.data = new std::vector<uint8_t>(p->read.value, p->read.value + p->read.value_len);
|
||||
this->event_.gattc.gattc_param->read.value = this->event_.gattc.data->data();
|
||||
break;
|
||||
default:
|
||||
this->event_.gattc.data = nullptr;
|
||||
break;
|
||||
}
|
||||
this->init_gattc_data(e, i, p);
|
||||
}
|
||||
|
||||
// Constructor for GATTS events - uses heap allocation
|
||||
// Creates a copy of the param struct since the original is only valid during the callback
|
||||
BLEEvent(esp_gatts_cb_event_t e, esp_gatt_if_t i, esp_ble_gatts_cb_param_t *p) {
|
||||
this->type_ = GATTS;
|
||||
this->event_.gatts.gatts_event = e;
|
||||
this->event_.gatts.gatts_if = i;
|
||||
|
||||
if (p == nullptr) {
|
||||
this->event_.gatts.gatts_param = nullptr;
|
||||
this->event_.gatts.data = nullptr;
|
||||
return; // Invalid event, but we can't log in header file
|
||||
}
|
||||
|
||||
// Heap-allocate param and data
|
||||
// Heap allocation is used because GATTC/GATTS events are rare (<1% of events)
|
||||
// while GAP events (99%) are stored inline to minimize memory usage
|
||||
this->event_.gatts.gatts_param = new esp_ble_gatts_cb_param_t(*p);
|
||||
|
||||
// Copy data for events that need it
|
||||
switch (e) {
|
||||
case ESP_GATTS_WRITE_EVT:
|
||||
this->event_.gatts.data = new std::vector<uint8_t>(p->write.value, p->write.value + p->write.len);
|
||||
this->event_.gatts.gatts_param->write.value = this->event_.gatts.data->data();
|
||||
break;
|
||||
default:
|
||||
this->event_.gatts.data = nullptr;
|
||||
break;
|
||||
}
|
||||
this->init_gatts_data(e, i, p);
|
||||
}
|
||||
|
||||
// Destructor to clean up heap allocations
|
||||
~BLEEvent() {
|
||||
switch (this->type_) {
|
||||
case GATTC:
|
||||
~BLEEvent() { this->cleanup_heap_data(); }
|
||||
|
||||
// Default constructor for pre-allocation in pool
|
||||
BLEEvent() : type_(GAP) {}
|
||||
|
||||
// Clean up any heap-allocated data
|
||||
void cleanup_heap_data() {
|
||||
if (this->type_ == GAP) {
|
||||
return;
|
||||
}
|
||||
if (this->type_ == GATTC) {
|
||||
delete this->event_.gattc.gattc_param;
|
||||
delete this->event_.gattc.data;
|
||||
break;
|
||||
case GATTS:
|
||||
this->event_.gattc.gattc_param = nullptr;
|
||||
this->event_.gattc.data = nullptr;
|
||||
return;
|
||||
}
|
||||
if (this->type_ == GATTS) {
|
||||
delete this->event_.gatts.gatts_param;
|
||||
delete this->event_.gatts.data;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
this->event_.gatts.gatts_param = nullptr;
|
||||
this->event_.gatts.data = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Load new event data for reuse (replaces previous event data)
|
||||
void load_gap_event(esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p) {
|
||||
this->cleanup_heap_data();
|
||||
this->type_ = GAP;
|
||||
this->init_gap_data(e, p);
|
||||
}
|
||||
|
||||
void load_gattc_event(esp_gattc_cb_event_t e, esp_gatt_if_t i, esp_ble_gattc_cb_param_t *p) {
|
||||
this->cleanup_heap_data();
|
||||
this->type_ = GATTC;
|
||||
this->init_gattc_data(e, i, p);
|
||||
}
|
||||
|
||||
void load_gatts_event(esp_gatts_cb_event_t e, esp_gatt_if_t i, esp_ble_gatts_cb_param_t *p) {
|
||||
this->cleanup_heap_data();
|
||||
this->type_ = GATTS;
|
||||
this->init_gatts_data(e, i, p);
|
||||
}
|
||||
|
||||
// Disable copy to prevent double-delete
|
||||
BLEEvent(const BLEEvent &) = delete;
|
||||
BLEEvent &operator=(const BLEEvent &) = delete;
|
||||
@@ -224,6 +167,102 @@ class BLEEvent {
|
||||
esp_gap_ble_cb_event_t gap_event_type() const { return event_.gap.gap_event; }
|
||||
const BLEScanResult &scan_result() const { return event_.gap.scan_result; }
|
||||
esp_bt_status_t scan_complete_status() const { return event_.gap.scan_complete.status; }
|
||||
|
||||
private:
|
||||
// Initialize GAP event data
|
||||
void init_gap_data(esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p) {
|
||||
this->event_.gap.gap_event = e;
|
||||
|
||||
if (p == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Copy data based on event type
|
||||
switch (e) {
|
||||
case ESP_GAP_BLE_SCAN_RESULT_EVT:
|
||||
memcpy(this->event_.gap.scan_result.bda, p->scan_rst.bda, sizeof(esp_bd_addr_t));
|
||||
this->event_.gap.scan_result.ble_addr_type = p->scan_rst.ble_addr_type;
|
||||
this->event_.gap.scan_result.rssi = p->scan_rst.rssi;
|
||||
this->event_.gap.scan_result.adv_data_len = p->scan_rst.adv_data_len;
|
||||
this->event_.gap.scan_result.scan_rsp_len = p->scan_rst.scan_rsp_len;
|
||||
this->event_.gap.scan_result.search_evt = p->scan_rst.search_evt;
|
||||
memcpy(this->event_.gap.scan_result.ble_adv, p->scan_rst.ble_adv,
|
||||
ESP_BLE_ADV_DATA_LEN_MAX + ESP_BLE_SCAN_RSP_DATA_LEN_MAX);
|
||||
break;
|
||||
|
||||
case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT:
|
||||
this->event_.gap.scan_complete.status = p->scan_param_cmpl.status;
|
||||
break;
|
||||
|
||||
case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT:
|
||||
this->event_.gap.scan_complete.status = p->scan_start_cmpl.status;
|
||||
break;
|
||||
|
||||
case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT:
|
||||
this->event_.gap.scan_complete.status = p->scan_stop_cmpl.status;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize GATTC event data
|
||||
void init_gattc_data(esp_gattc_cb_event_t e, esp_gatt_if_t i, esp_ble_gattc_cb_param_t *p) {
|
||||
this->event_.gattc.gattc_event = e;
|
||||
this->event_.gattc.gattc_if = i;
|
||||
|
||||
if (p == nullptr) {
|
||||
this->event_.gattc.gattc_param = nullptr;
|
||||
this->event_.gattc.data = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
// Heap-allocate param
|
||||
this->event_.gattc.gattc_param = new esp_ble_gattc_cb_param_t(*p);
|
||||
|
||||
// Copy data for events that need it
|
||||
switch (e) {
|
||||
case ESP_GATTC_NOTIFY_EVT:
|
||||
this->event_.gattc.data = new std::vector<uint8_t>(p->notify.value, p->notify.value + p->notify.value_len);
|
||||
this->event_.gattc.gattc_param->notify.value = this->event_.gattc.data->data();
|
||||
break;
|
||||
case ESP_GATTC_READ_CHAR_EVT:
|
||||
case ESP_GATTC_READ_DESCR_EVT:
|
||||
this->event_.gattc.data = new std::vector<uint8_t>(p->read.value, p->read.value + p->read.value_len);
|
||||
this->event_.gattc.gattc_param->read.value = this->event_.gattc.data->data();
|
||||
break;
|
||||
default:
|
||||
this->event_.gattc.data = nullptr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize GATTS event data
|
||||
void init_gatts_data(esp_gatts_cb_event_t e, esp_gatt_if_t i, esp_ble_gatts_cb_param_t *p) {
|
||||
this->event_.gatts.gatts_event = e;
|
||||
this->event_.gatts.gatts_if = i;
|
||||
|
||||
if (p == nullptr) {
|
||||
this->event_.gatts.gatts_param = nullptr;
|
||||
this->event_.gatts.data = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
// Heap-allocate param
|
||||
this->event_.gatts.gatts_param = new esp_ble_gatts_cb_param_t(*p);
|
||||
|
||||
// Copy data for events that need it
|
||||
switch (e) {
|
||||
case ESP_GATTS_WRITE_EVT:
|
||||
this->event_.gatts.data = new std::vector<uint8_t>(p->write.value, p->write.value + p->write.len);
|
||||
this->event_.gatts.gatts_param->write.value = this->event_.gatts.data->data();
|
||||
break;
|
||||
default:
|
||||
this->event_.gatts.data = nullptr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// BLEEvent total size: 84 bytes (80 byte union + 1 byte type + 3 bytes padding)
|
||||
|
133
esphome/components/esp32_ble/ble_event_pool.h
Normal file
133
esphome/components/esp32_ble/ble_event_pool.h
Normal file
@@ -0,0 +1,133 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include "ble_event.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_ble {
|
||||
|
||||
// BLE Event Pool - Pre-allocated pool of BLEEvent objects to avoid heap fragmentation
|
||||
// This is a lock-free pool that allows the BLE task to allocate events without malloc
|
||||
template<size_t SIZE> class BLEEventPool {
|
||||
public:
|
||||
BLEEventPool() {
|
||||
// Initialize all slots as unallocated
|
||||
for (size_t i = 0; i < SIZE; i++) {
|
||||
this->events_[i] = nullptr;
|
||||
}
|
||||
|
||||
// Initialize the free list - all indices are initially free
|
||||
for (size_t i = 0; i < SIZE - 1; i++) {
|
||||
this->next_free_[i] = i + 1;
|
||||
}
|
||||
this->next_free_[SIZE - 1] = INVALID_INDEX;
|
||||
|
||||
this->free_head_.store(0, std::memory_order_relaxed);
|
||||
this->allocated_count_.store(0, std::memory_order_relaxed);
|
||||
this->total_created_.store(0, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
~BLEEventPool() {
|
||||
// Delete any events that were created
|
||||
for (size_t i = 0; i < SIZE; i++) {
|
||||
if (this->events_[i] != nullptr) {
|
||||
delete this->events_[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate an event slot and return its index
|
||||
// Returns INVALID_INDEX if pool is full
|
||||
size_t allocate() {
|
||||
while (true) {
|
||||
size_t head = this->free_head_.load(std::memory_order_acquire);
|
||||
|
||||
if (head == INVALID_INDEX) {
|
||||
// Pool is full
|
||||
return INVALID_INDEX;
|
||||
}
|
||||
|
||||
size_t next = this->next_free_[head];
|
||||
|
||||
// Try to update the free list head
|
||||
if (this->free_head_.compare_exchange_weak(head, next, std::memory_order_release, std::memory_order_acquire)) {
|
||||
this->allocated_count_.fetch_add(1, std::memory_order_relaxed);
|
||||
return head;
|
||||
}
|
||||
// CAS failed, retry
|
||||
}
|
||||
}
|
||||
|
||||
// Deallocate an event slot by index
|
||||
void deallocate(size_t index) {
|
||||
if (index >= SIZE) {
|
||||
return; // Invalid index
|
||||
}
|
||||
|
||||
// No destructor call - events are reused
|
||||
// The event's reset methods handle cleanup when switching types
|
||||
|
||||
while (true) {
|
||||
size_t head = this->free_head_.load(std::memory_order_acquire);
|
||||
this->next_free_[index] = head;
|
||||
|
||||
// Try to add this index back to the free list
|
||||
if (this->free_head_.compare_exchange_weak(head, index, std::memory_order_release, std::memory_order_acquire)) {
|
||||
this->allocated_count_.fetch_sub(1, std::memory_order_relaxed);
|
||||
return;
|
||||
}
|
||||
// CAS failed, retry
|
||||
}
|
||||
}
|
||||
|
||||
// Get event by index, creating it if needed
|
||||
BLEEvent *get(size_t index) {
|
||||
if (index >= SIZE) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Create event on first access (warm-up)
|
||||
if (this->events_[index] == nullptr) {
|
||||
// Use internal RAM for better performance
|
||||
RAMAllocator<BLEEvent> allocator(RAMAllocator<BLEEvent>::ALLOC_INTERNAL);
|
||||
BLEEvent *event = allocator.allocate(1);
|
||||
|
||||
if (event == nullptr) {
|
||||
// Fall back to regular allocation
|
||||
event = new BLEEvent();
|
||||
} else {
|
||||
// Placement new to construct the object
|
||||
new (event) BLEEvent();
|
||||
}
|
||||
|
||||
this->events_[index] = event;
|
||||
this->total_created_.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
return this->events_[index];
|
||||
}
|
||||
|
||||
// Get number of allocated events
|
||||
size_t get_allocated_count() const { return this->allocated_count_.load(std::memory_order_relaxed); }
|
||||
|
||||
// Get total number of events created (high water mark)
|
||||
size_t get_total_created() const { return this->total_created_.load(std::memory_order_relaxed); }
|
||||
|
||||
static constexpr size_t INVALID_INDEX = SIZE_MAX;
|
||||
|
||||
private:
|
||||
BLEEvent *events_[SIZE]; // Array of pointers, allocated on demand
|
||||
size_t next_free_[SIZE]; // Next free index for each slot
|
||||
std::atomic<size_t> free_head_; // Head of the free list
|
||||
std::atomic<size_t> allocated_count_; // Number of currently allocated events
|
||||
std::atomic<size_t> total_created_; // Total events created (high water mark)
|
||||
};
|
||||
|
||||
} // namespace esp32_ble
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
81
esphome/components/esp32_ble/queue_index.h
Normal file
81
esphome/components/esp32_ble/queue_index.h
Normal file
@@ -0,0 +1,81 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_ble {
|
||||
|
||||
// Lock-free SPSC queue that stores indices instead of pointers
|
||||
// This allows us to use a pre-allocated pool of objects
|
||||
template<size_t SIZE> class LockFreeIndexQueue {
|
||||
public:
|
||||
static constexpr size_t INVALID_INDEX = SIZE_MAX;
|
||||
|
||||
LockFreeIndexQueue() : head_(0), tail_(0), dropped_count_(0) {
|
||||
// Initialize all slots to invalid
|
||||
for (size_t i = 0; i < SIZE; i++) {
|
||||
buffer_[i] = INVALID_INDEX;
|
||||
}
|
||||
}
|
||||
|
||||
bool push(size_t index) {
|
||||
if (index == INVALID_INDEX)
|
||||
return false;
|
||||
|
||||
size_t current_tail = tail_.load(std::memory_order_relaxed);
|
||||
size_t next_tail = (current_tail + 1) % SIZE;
|
||||
|
||||
if (next_tail == head_.load(std::memory_order_acquire)) {
|
||||
// Buffer full
|
||||
dropped_count_.fetch_add(1, std::memory_order_relaxed);
|
||||
return false;
|
||||
}
|
||||
|
||||
buffer_[current_tail] = index;
|
||||
tail_.store(next_tail, std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t pop() {
|
||||
size_t current_head = head_.load(std::memory_order_relaxed);
|
||||
|
||||
if (current_head == tail_.load(std::memory_order_acquire)) {
|
||||
return INVALID_INDEX; // Empty
|
||||
}
|
||||
|
||||
size_t index = buffer_[current_head];
|
||||
head_.store((current_head + 1) % SIZE, std::memory_order_release);
|
||||
return index;
|
||||
}
|
||||
|
||||
size_t size() const {
|
||||
size_t tail = tail_.load(std::memory_order_acquire);
|
||||
size_t head = head_.load(std::memory_order_acquire);
|
||||
return (tail - head + SIZE) % SIZE;
|
||||
}
|
||||
|
||||
size_t get_and_reset_dropped_count() { return dropped_count_.exchange(0, std::memory_order_relaxed); }
|
||||
|
||||
void increment_dropped_count() { dropped_count_.fetch_add(1, std::memory_order_relaxed); }
|
||||
|
||||
bool empty() const { return head_.load(std::memory_order_acquire) == tail_.load(std::memory_order_acquire); }
|
||||
|
||||
bool full() const {
|
||||
size_t next_tail = (tail_.load(std::memory_order_relaxed) + 1) % SIZE;
|
||||
return next_tail == head_.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
protected:
|
||||
size_t buffer_[SIZE];
|
||||
std::atomic<size_t> head_;
|
||||
std::atomic<size_t> tail_;
|
||||
std::atomic<size_t> dropped_count_;
|
||||
};
|
||||
|
||||
} // namespace esp32_ble
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
Reference in New Issue
Block a user