From 1c986bd1e9a176a08ae885a6cdcefe76c3f700fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Branimir=20Karad=C5=BEi=C4=87?= Date: Wed, 30 Sep 2026 22:27:35 -0700 Subject: [PATCH] Fixed RingBufferControl::resize moving heads that are at the write head, and leaving heads outside the buffer when shrinking past the end of buffer. (#436) --- include/bx/inline/ringbuffer.inl | 20 ++++-- include/bx/ringbuffer.h | 8 ++- tests/ringbuffer_test.cpp | 117 +++++++++++++++++++++++++++++++ 3 files changed, 139 insertions(+), 6 deletions(-) diff --git a/include/bx/inline/ringbuffer.inl b/include/bx/inline/ringbuffer.inl index d50e286..a2556b1 100644 --- a/include/bx/inline/ringbuffer.inl +++ b/include/bx/inline/ringbuffer.inl @@ -54,10 +54,16 @@ namespace bx : _size ; + // empty slots past the end of buffer are removed from the start of buffer. + const int32_t back = bx::max(_size, int32_t(m_write) - int32_t(m_size) ); + const int32_t front = _size - back; + const uint32_t write = m_write; + m_size += _size; - m_current += m_current >= m_write ? _size : 0; - m_read += m_read >= m_write ? _size : 0; + m_current = (m_current + (m_current > write ? back : front) ) % m_size; + m_read = (m_read + (m_read > write ? back : front) ) % m_size; + m_write = (m_write + front) % m_size; } inline uint32_t RingBufferControl::consume(uint32_t _size) @@ -177,10 +183,16 @@ namespace bx : _size ; + // empty slots past the end of buffer are removed from the start of buffer. + const int32_t back = bx::max(_size, int32_t(m_write) - int32_t(m_size) ); + const int32_t front = _size - back; + const uint32_t write = m_write; + m_size += _size; - m_current += m_current >= m_write ? _size : 0; - m_read += m_read >= m_write ? _size : 0; + m_current = (m_current + (m_current > write ? back : front) ) % m_size; + m_read = (m_read + (m_read > write ? back : front) ) % m_size; + m_write = (m_write + front) % m_size; } inline uint32_t SpScRingBufferControl::consume(uint32_t _size) diff --git a/include/bx/ringbuffer.h b/include/bx/ringbuffer.h index 8099876..14be2a4 100644 --- a/include/bx/ringbuffer.h +++ b/include/bx/ringbuffer.h @@ -71,7 +71,9 @@ namespace bx /// forward or backward if write head is behind them. /// /// @param[in] _size Amount to resize. Value can be positive when growing size or negative - /// when shrinking size of buffer. + /// when shrinking size of buffer. Shrinking is limited to number of empty slots. Empty slots + /// are removed from write head to the end of buffer, and remainder is removed from the start + /// of buffer. /// void resize(int32_t _size); @@ -176,7 +178,9 @@ namespace bx /// forward or backward if write head is behind them. /// /// @param[in] _size Amount to resize. Value can be positive when growing size or negative - /// when shrinking size of buffer. + /// when shrinking size of buffer. Shrinking is limited to number of empty slots. Empty slots + /// are removed from write head to the end of buffer, and remainder is removed from the start + /// of buffer. /// void resize(int32_t _size); diff --git a/tests/ringbuffer_test.cpp b/tests/ringbuffer_test.cpp index bb9462c..6a61fce 100644 --- a/tests/ringbuffer_test.cpp +++ b/tests/ringbuffer_test.cpp @@ -123,3 +123,120 @@ TEST_CASE("RingBufferControl resize", "") REQUIRE(control.m_write == 0); REQUIRE(control.m_read == 1); } + +TEST_CASE("RingBufferControl resize without reserved slots", "") +{ + { + bx::RingBufferControl control(8); + + REQUIRE(5 == control.reserve(5) ); + REQUIRE(5 == control.commit(5) ); + REQUIRE(2 == control.consume(2) ); + + control.resize(8); + REQUIRE(16 == control.getSize() ); + REQUIRE(3 == control.getNumUsed() ); + REQUIRE(0 == control.getNumReserved() ); + REQUIRE(12 == control.getNumEmpty() ); + + REQUIRE(control.m_current == 5); + REQUIRE(control.m_write == 5); + REQUIRE(control.m_read == 2); + } + + { + bx::RingBufferControl control(8); + + REQUIRE(6 == control.reserve(6) ); + REQUIRE(6 == control.commit(6) ); + REQUIRE(5 == control.consume(5) ); + REQUIRE(4 == control.reserve(4) ); + REQUIRE(4 == control.commit(4) ); + + REQUIRE(control.m_current == 2); + REQUIRE(control.m_write == 2); + REQUIRE(control.m_read == 5); + + control.resize(8); + REQUIRE(16 == control.getSize() ); + REQUIRE(5 == control.getNumUsed() ); + REQUIRE(0 == control.getNumReserved() ); + REQUIRE(10 == control.getNumEmpty() ); + + REQUIRE(control.m_current == 2); + REQUIRE(control.m_write == 2); + REQUIRE(control.m_read == 13); + + control.resize(-8); + REQUIRE(8 == control.getSize() ); + REQUIRE(5 == control.getNumUsed() ); + REQUIRE(0 == control.getNumReserved() ); + REQUIRE(2 == control.getNumEmpty() ); + + REQUIRE(control.m_current == 2); + REQUIRE(control.m_write == 2); + REQUIRE(control.m_read == 5); + } + + { + bx::RingBufferControl control(8); + + REQUIRE(3 == control.reserve(3) ); + REQUIRE(3 == control.commit(3) ); + REQUIRE(3 == control.consume(3) ); + + control.resize(8); + REQUIRE(16 == control.getSize() ); + REQUIRE(0 == control.getNumUsed() ); + REQUIRE(0 == control.getNumReserved() ); + REQUIRE(15 == control.getNumEmpty() ); + REQUIRE(control.isEmpty() ); + + REQUIRE(control.m_current == 3); + REQUIRE(control.m_write == 3); + REQUIRE(control.m_read == 3); + } +} + +TEST_CASE("RingBufferControl shrink past the end of buffer", "") +{ + { + bx::RingBufferControl control(16); + + REQUIRE(10 == control.reserve(10) ); + REQUIRE(10 == control.commit(10) ); + REQUIRE(8 == control.consume(8) ); + + REQUIRE(13 == control.getNumEmpty() ); + + control.resize(-13); + REQUIRE(3 == control.getSize() ); + REQUIRE(2 == control.getNumUsed() ); + REQUIRE(0 == control.getNumReserved() ); + REQUIRE(0 == control.getNumEmpty() ); + + REQUIRE(control.m_current == 0); + REQUIRE(control.m_write == 0); + REQUIRE(control.m_read == 1); + } + + { + bx::RingBufferControl control(32); + + REQUIRE(29 == control.reserve(29) ); + REQUIRE(29 == control.commit(29) ); + REQUIRE(18 == control.consume(18) ); + + REQUIRE(20 == control.getNumEmpty() ); + + control.resize(-4); + REQUIRE(28 == control.getSize() ); + REQUIRE(11 == control.getNumUsed() ); + REQUIRE(0 == control.getNumReserved() ); + REQUIRE(16 == control.getNumEmpty() ); + + REQUIRE(control.m_current == 0); + REQUIRE(control.m_write == 0); + REQUIRE(control.m_read == 17); + } +}