pw_ring_buffer: Add TryPushBack()
This adds a PushBack variant that will only push if there is space in
the buffer, and will not evict items from the front.
Change-Id: I393d7f5703c1cf5853d2192cb390836839d565e4
diff --git a/pw_ring_buffer/BUILD.gn b/pw_ring_buffer/BUILD.gn
index f51a094..0c3bedb 100644
--- a/pw_ring_buffer/BUILD.gn
+++ b/pw_ring_buffer/BUILD.gn
@@ -36,7 +36,10 @@
}
pw_test("prefixed_entry_ring_buffer_test") {
- deps = [ ":pw_ring_buffer" ]
+ deps = [
+ ":pw_ring_buffer",
+ "$dir_pw_assert:pw_assert",
+ ]
sources = [ "prefixed_entry_ring_buffer_test.cc" ]
}
diff --git a/pw_ring_buffer/prefixed_entry_ring_buffer.cc b/pw_ring_buffer/prefixed_entry_ring_buffer.cc
index dd475ff..fac8e6e 100644
--- a/pw_ring_buffer/prefixed_entry_ring_buffer.cc
+++ b/pw_ring_buffer/prefixed_entry_ring_buffer.cc
@@ -43,8 +43,9 @@
return Status::OK;
}
-Status PrefixedEntryRingBuffer::PushBack(span<const byte> data,
- byte user_preamble_data) {
+Status PrefixedEntryRingBuffer::InternalPushBack(span<const byte> data,
+ byte user_preamble_data,
+ bool drop_elements_if_needed) {
if (buffer_ == nullptr) {
return Status::FAILED_PRECONDITION;
}
@@ -61,9 +62,15 @@
return Status::OUT_OF_RANGE;
}
- // Drop old entries until we have space for the new entry.
- while (RawAvailableBytes() < total_write_bytes) {
- PopFront();
+ if (drop_elements_if_needed) {
+ // PushBack() case: evict items as needed.
+ // Drop old entries until we have space for the new entry.
+ while (RawAvailableBytes() < total_write_bytes) {
+ PopFront();
+ }
+ } else if (RawAvailableBytes() < total_write_bytes) {
+ // TryPushBack() case: don't evict items.
+ return Status::RESOURCE_EXHAUSTED;
}
// Write the new entry into the ring buffer.
diff --git a/pw_ring_buffer/prefixed_entry_ring_buffer_test.cc b/pw_ring_buffer/prefixed_entry_ring_buffer_test.cc
index db5c14c..aad8fac 100644
--- a/pw_ring_buffer/prefixed_entry_ring_buffer_test.cc
+++ b/pw_ring_buffer/prefixed_entry_ring_buffer_test.cc
@@ -17,6 +17,7 @@
#include <cstddef>
#include <cstdint>
+#include "pw_assert/assert.h"
#include "pw_containers/vector.h"
#include "pw_unit_test/framework.h"
@@ -362,6 +363,70 @@
TEST(PrefixedEntryRingBuffer, Dering) { DeringTest(true); }
TEST(PrefixedEntryRingBuffer, DeringNoPreload) { DeringTest(false); }
+template <typename T>
+Status PushBack(PrefixedEntryRingBuffer& ring, T element) {
+ union {
+ std::array<byte, sizeof(element)> buffer;
+ T item;
+ } aliased;
+ aliased.item = element;
+ return ring.PushBack(span(aliased.buffer));
+}
+
+template <typename T>
+Status TryPushBack(PrefixedEntryRingBuffer& ring, T element) {
+ union {
+ std::array<byte, sizeof(element)> buffer;
+ T item;
+ } aliased;
+ aliased.item = element;
+ return ring.TryPushBack(span(aliased.buffer));
+}
+
+template <typename T>
+T PeekFront(PrefixedEntryRingBuffer& ring) {
+ union {
+ std::array<byte, sizeof(T)> buffer;
+ T item;
+ } aliased;
+ size_t bytes_read = 0;
+ PW_CHECK_INT_EQ(ring.PeekFront(span(aliased.buffer), &bytes_read),
+ Status::OK);
+ PW_CHECK_INT_EQ(bytes_read, sizeof(T));
+ return aliased.item;
+}
+
+TEST(PrefixedEntryRingBuffer, TryPushBack) {
+ PrefixedEntryRingBuffer ring;
+ byte test_buffer[kTestBufferSize];
+ EXPECT_EQ(ring.SetBuffer(test_buffer), Status::OK);
+
+ // Fill up the ring buffer with a constant.
+ int total_items = 0;
+ while (true) {
+ Status status = TryPushBack<int>(ring, 5);
+ if (status.ok()) {
+ total_items++;
+ } else {
+ EXPECT_EQ(status, Status::RESOURCE_EXHAUSTED);
+ break;
+ }
+ }
+ EXPECT_EQ(PeekFront<int>(ring), 5);
+
+ // Should be unable to push more items.
+ for (int i = 0; i < total_items; ++i) {
+ EXPECT_EQ(TryPushBack<int>(ring, 100), Status::RESOURCE_EXHAUSTED);
+ EXPECT_EQ(PeekFront<int>(ring), 5);
+ }
+
+ // Fill up the ring buffer with a constant.
+ for (int i = 0; i < total_items; ++i) {
+ EXPECT_EQ(PushBack<int>(ring, 100), Status::OK);
+ }
+ EXPECT_EQ(PeekFront<int>(ring), 100);
+}
+
} // namespace
} // namespace ring_buffer
} // namespace pw
diff --git a/pw_ring_buffer/public/pw_ring_buffer/prefixed_entry_ring_buffer.h b/pw_ring_buffer/public/pw_ring_buffer/prefixed_entry_ring_buffer.h
index 902a310..f87c44f 100644
--- a/pw_ring_buffer/public/pw_ring_buffer/prefixed_entry_ring_buffer.h
+++ b/pw_ring_buffer/public/pw_ring_buffer/prefixed_entry_ring_buffer.h
@@ -23,9 +23,9 @@
// A circular ring buffer for arbitrary length data entries. Each PushBack()
// produces a buffer entry. Each entry consists of a preamble followed by an
-// arbitrary length data chunk/blob. The preamble is comprised of an optional
-// user preamble byte and an always present varint. The varint encodes the
-// number of bytes in the data chunk/blob.
+// arbitrary length data chunk. The preamble is comprised of an optional user
+// preamble byte and an always present varint. The varint encodes the number of
+// bytes in the data chunk.
//
// The ring buffer holds the most recent entries stored in the buffer. Once
// filled to capacity, incoming entries bump out the oldest entries to make
@@ -52,8 +52,8 @@
// Removes all data from the ring buffer.
void Clear();
- // Write a chunk/blob of data to the ring buffer. If available space is less
- // than size of data chunk to be written then silently pop and discard oldest
+ // Write a chunk of data to the ring buffer. If available space is less than
+ // size of data chunk to be written then silently pop and discard oldest
// stored data chunks until space is available.
//
// Preamble argument is a caller-provided value prepended to the front of the
@@ -66,9 +66,29 @@
// FAILED_PRECONDITION - Buffer not initialized.
// OUT_OF_RANGE - Size of data is greater than buffer size.
Status PushBack(span<const std::byte> data,
- std::byte user_preamble_data = std::byte(0));
+ std::byte user_preamble_data = std::byte(0)) {
+ return InternalPushBack(data, user_preamble_data, true);
+ }
- // Read the oldest stored data chunk/blob of data from the ring buffer to
+ // Write a chunk of data to the ring buffer if there is space available.
+ //
+ // Preamble argument is a caller-provided value prepended to the front of the
+ // entry. It is only used if user_preamble was set at class construction
+ // time.
+ //
+ // Return values:
+ // OK - Data successfully written to the ring buffer.
+ // INVALID_ARGUMENT - Size of data to write is zero bytes
+ // FAILED_PRECONDITION - Buffer not initialized.
+ // OUT_OF_RANGE - Size of data is greater than buffer size.
+ // RESOURCE_EXHAUSTED - The ring buffer doesn't have space for the data
+ // without popping off existing elements.
+ Status TryPushBack(span<const std::byte> data,
+ std::byte user_preamble_data = std::byte(0)) {
+ return InternalPushBack(data, user_preamble_data, false);
+ }
+
+ // Read the oldest stored data chunk of data from the ring buffer to
// the provided destination span. The number of bytes read is written to
// bytes_read
//
@@ -89,8 +109,7 @@
Status PeekFrontWithPreamble(ReadOutput output);
- // Pop and discard the oldest stored data chunk/blob of data from the ring
- // buffer.
+ // Pop and discard the oldest stored data chunk of data from the ring buffer.
//
// Return values:
// OK - Data successfully read from the ring buffer.
@@ -114,15 +133,15 @@
size_t EntryCount() { return entry_count_; }
// Get the size in bytes of all the current entries in the ring buffer,
- // including preamble and data chunk/blob.
+ // including preamble and data chunk.
size_t TotalUsedBytes() { return buffer_bytes_ - RawAvailableBytes(); }
- // Get the size in bytes of the next data chunk/blob, not including
- // preamble, to be read.
+ // Get the size in bytes of the next chunk, not including preamble, to be
+ // read.
size_t FrontEntryDataSizeBytes();
- // Get the size in bytes of the next entry, including preamble and data
- // chunk/blob, to be read.
+ // Get the size in bytes of the next chunk, including preamble and data
+ // chunk, to be read.
size_t FrontEntryTotalSizeBytes();
private:
@@ -136,8 +155,14 @@
template <typename T>
Status InternalRead(T read_output, bool get_preamble);
- // Get info struct with the size of the preamble and data chunk/blob for the
- // next entry to be read.
+ // Push back implementation, which optionally discards front elements to fit
+ // the incoming element.
+ Status InternalPushBack(span<const std::byte> data,
+ std::byte user_preamble_data,
+ bool pop_front_if_needed);
+
+ // Get info struct with the size of the preamble and data chunk for the next
+ // entry to be read.
EntryInfo FrontEntryInfo();
// Get the raw number of available bytes free in the ring buffer. This is