#pragma once #if defined(USE_ESP32) || defined(USE_ZEPHYR) #include #include #include "esphome/core/helpers.h" #include "esphome/core/lock_free_queue.h" namespace esphome { // Event Pool - On-demand pool of objects to avoid heap fragmentation // Events are allocated on first use and reused thereafter, growing to peak usage // @tparam T The type of objects managed by the pool (must have a release() method) // @tparam SIZE The maximum number of objects in the pool (1-255, limited by uint8_t) // // SIZING: When paired with a LockFreeQueue, the pool SIZE should be // Q_SIZE - 1 (the queue's actual capacity, since the ring buffer reserves one slot). // This ensures allocate() returns nullptr before push() can fail, which: // - Prevents the allocate-succeeds-but-push-fails mismatch that permanently // leaks a pool slot (the element is never returned to the pool) // - Avoids needing release() on the producer path after a failed push(), // preserving the SPSC contract on the internal free list template class EventPool { public: EventPool() : total_created_(0) {} ~EventPool() { // Clean up any remaining events in the free list // IMPORTANT: This destructor assumes no concurrent access. The EventPool must not // be destroyed while any thread might still call allocate() or release(). // In practice, this is typically ensured by destroying the pool only during // component shutdown when all producer/consumer threads have been stopped. T *event; RAMAllocator allocator(RAMAllocator::ALLOC_INTERNAL); while ((event = this->free_list_.pop()) != nullptr) { // Call destructor event->~T(); // Deallocate using RAMAllocator allocator.deallocate(event, 1); } } // Allocate an event from the pool // Returns nullptr if pool is full T *allocate() { // Try to get from free list first T *event = this->free_list_.pop(); if (event != nullptr) return event; // Need to create a new event if (this->total_created_ >= SIZE) { // Pool is at capacity return nullptr; } // Use internal RAM for better performance RAMAllocator allocator(RAMAllocator::ALLOC_INTERNAL); event = allocator.allocate(1); if (event == nullptr) { // Memory allocation failed return nullptr; } // Placement new to construct the object new (event) T(); this->total_created_++; return event; } // Return an event to the pool for reuse void release(T *event) { if (event != nullptr) { // Clean up the event's allocated memory event->release(); this->free_list_.push(event); } } private: LockFreeQueue free_list_; // Free events ready for reuse uint8_t total_created_; // Total events created (high water mark, max 255) }; } // namespace esphome #endif // defined(USE_ESP32)