From eedeeb81106dc3aa5695101403cbd18053f785f0 Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Thu, 11 Jun 2026 08:46:38 -0400 Subject: [PATCH 1/2] Add items_waiting/is_empty introspection to ring buffers Expose committed-item counts and emptiness checks on the SPSC ring buffer (ESP and host) and the audio ring buffer wrapper. --- src/audio_ring_buffer.h | 12 ++++++++++++ src/platform/spsc_ring_buffer.h | 34 ++++++++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/audio_ring_buffer.h b/src/audio_ring_buffer.h index b56cb99..e81d8ae 100644 --- a/src/audio_ring_buffer.h +++ b/src/audio_ring_buffer.h @@ -108,6 +108,18 @@ class SendspinAudioRingBuffer { /// @param entry Pointer previously returned by receive_chunk(). May be nullptr (no-op). void return_chunk(AudioRingBufferEntry* entry); + /// @brief Returns the number of audio chunks waiting to be received. + /// @return Count of chunks written but not yet received by the consumer. + size_t chunks_waiting() const { + return this->ring_buffer_.items_waiting(); + } + + /// @brief Returns true if no audio chunks are waiting to be received. + /// @return true if the consumer has received every chunk written so far. + bool is_empty() const { + return this->ring_buffer_.is_empty(); + } + /// @brief Drains all items from the ring buffer. /// @note Only safe to call when the consumer task is stopped. void reset(); diff --git a/src/platform/spsc_ring_buffer.h b/src/platform/spsc_ring_buffer.h index 96cf880..4f17e3f 100644 --- a/src/platform/spsc_ring_buffer.h +++ b/src/platform/spsc_ring_buffer.h @@ -85,6 +85,20 @@ class SpscRingBuffer { return this->handle_ != nullptr; } + /// @brief Returns the number of committed items waiting to be received + /// @return Count of items written and committed but not yet received by the consumer. + size_t items_waiting() const { + UBaseType_t items = 0; + vRingbufferGetInfo(this->handle_, nullptr, nullptr, nullptr, nullptr, &items); + return static_cast(items); + } + + /// @brief Returns true if no committed items are waiting to be received + /// @return true if the ring buffer has no items pending for the consumer. + bool is_empty() const { + return this->items_waiting() == 0; + } + /// @brief Two-phase write: acquire contiguous space /// @param size Number of bytes to acquire. /// @param timeout_ms Milliseconds to wait if space is unavailable (UINT32_MAX = wait forever). @@ -192,6 +206,7 @@ class SpscRingBuffer { this->write_offset_ = 0; this->read_offset_ = 0; this->free_bytes_ = size; + this->items_waiting_ = 0; this->created_ = true; return true; } @@ -202,6 +217,20 @@ class SpscRingBuffer { return this->created_; } + /// @brief Returns the number of committed items waiting to be received + /// @return Count of items written and committed but not yet received by the consumer. + size_t items_waiting() const { + std::lock_guard lock(this->mtx_); + return this->items_waiting_; + } + + /// @brief Returns true if no committed items are waiting to be received + /// @return true if the ring buffer has no items pending for the consumer. + bool is_empty() const { + std::lock_guard lock(this->mtx_); + return this->items_waiting_ == 0; + } + /// @brief Two-phase write: acquire contiguous space /// @param size Number of bytes to acquire. /// @param timeout_ms Milliseconds to wait if space is unavailable (UINT32_MAX = wait forever). @@ -254,6 +283,7 @@ class SpscRingBuffer { auto* header = reinterpret_cast(static_cast(ptr) - sizeof(ItemHeader)); header->flags = FLAG_WRITTEN; + ++this->items_waiting_; this->cv_read_.notify_all(); return true; } @@ -390,6 +420,7 @@ class SpscRingBuffer { } if (header->flags == FLAG_WRITTEN) { *item_size = header->size; + --this->items_waiting_; return this->storage_ + this->read_offset_ + sizeof(ItemHeader); } // ACQUIRED but not yet committed -- wait @@ -401,13 +432,14 @@ class SpscRingBuffer { // Struct fields std::condition_variable cv_read_; std::condition_variable cv_write_; - std::mutex mtx_; + mutable std::mutex mtx_; // Pointer fields uint8_t* storage_{nullptr}; // size_t fields size_t free_bytes_{0}; + size_t items_waiting_{0}; size_t read_offset_{0}; size_t storage_size_{0}; size_t write_offset_{0}; From f73fbc486c3effe55e970764fd44904d99ccdc35 Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Thu, 11 Jun 2026 08:55:18 -0400 Subject: [PATCH 2/2] Guard ESP items_waiting() against null ring buffer handle Return 0 when the ring buffer has not been created, matching the host implementation's safe default and avoiding a null-handle dereference in vRingbufferGetInfo. --- src/platform/spsc_ring_buffer.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/platform/spsc_ring_buffer.h b/src/platform/spsc_ring_buffer.h index 4f17e3f..8fd5429 100644 --- a/src/platform/spsc_ring_buffer.h +++ b/src/platform/spsc_ring_buffer.h @@ -88,6 +88,9 @@ class SpscRingBuffer { /// @brief Returns the number of committed items waiting to be received /// @return Count of items written and committed but not yet received by the consumer. size_t items_waiting() const { + if (this->handle_ == nullptr) { + return 0; + } UBaseType_t items = 0; vRingbufferGetInfo(this->handle_, nullptr, nullptr, nullptr, nullptr, &items); return static_cast(items);