diff --git a/include/bx/inline/ringbuffer.inl b/include/bx/inline/ringbuffer.inl index 309d612..921ff77 100644 --- a/include/bx/inline/ringbuffer.inl +++ b/include/bx/inline/ringbuffer.inl @@ -10,7 +10,7 @@ namespace bx { inline RingBufferControl::RingBufferControl(uint32_t _size) - : m_size(_size) + : m_size(max(_size, 2) ) , m_current(0) , m_write(0) , m_read(0) @@ -26,6 +26,45 @@ namespace bx return distance(m_read, m_current); } + inline bool RingBufferControl::isEmpty() const + { + return m_read == m_write; + } + + inline uint32_t RingBufferControl::getSize() const + { + return m_size; + } + + inline uint32_t RingBufferControl::getNumEmpty() const + { + return m_size - distance(m_read, m_write) - 1; + } + + inline uint32_t RingBufferControl::getNumUsed() const + { + return distance(m_read, m_current); + } + + inline uint32_t RingBufferControl::getNumReserved() const + { + return distance(m_current, m_write); + } + + inline void RingBufferControl::resize(int32_t _size) + { + _size = 0 > _size + // can shrink only by number of empty slots. + ? bx::max(_size, -int32_t(getNumEmpty() ) ) + : _size + ; + + m_size += _size; + + m_current += m_current >= m_write ? _size : 0; + m_read += m_read >= m_write ? _size : 0; + } + inline uint32_t RingBufferControl::consume(uint32_t _size) { const uint32_t maxSize = distance(m_read, m_current); diff --git a/include/bx/ringbuffer.h b/include/bx/ringbuffer.h index fc9a3f6..20ea6f8 100644 --- a/include/bx/ringbuffer.h +++ b/include/bx/ringbuffer.h @@ -12,6 +12,13 @@ namespace bx { + /// Ring buffer control structure. Tracking "read", "write", and "current" head. + /// + /// This is not container, and data control represents is user defined. Read/write/current are + /// just indices. + /// + /// @notice One slot is always reseved. When creating ring buffer of N slots, N-1 slots can be + /// used. /// class RingBufferControl { @@ -21,34 +28,98 @@ namespace bx ); public: + /// Constructor. + /// + /// @param[in] _size Maximum number of slots. /// RingBufferControl(uint32_t _size); - /// + /// Destructor. ~RingBufferControl(); + /// Returns number of used slots. + /// + /// @returns Number of used slots. /// uint32_t available() const; + /// Returns 'true' if ring buffer is empty. + /// + /// @returns Returns 'true' if ring buffer is empty. + /// + bool isEmpty() const; + + /// Returns total size of ring buffer. + /// + /// @returns Total size of ring buffer. + /// + uint32_t getSize() const; + + /// Returns number of empty slots. + /// + /// @returns Number of empty slots. + /// + uint32_t getNumEmpty() const; + + /// Returns number of used slots. + /// + /// @returns Number of used slots. + /// + uint32_t getNumUsed() const; + + /// Returns number of reserved slots. + /// + /// @returns Number of reserved slots. + /// + uint32_t getNumReserved() const; + + /// Resize ring buffer. Resize happens at write head, read and current head will be moved + /// 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. + /// + void resize(int32_t _size); + + /// Consume slots, makes slots free to be reserved. Moves "read" head forward. + /// + /// @returns Number of reserved slots reserved. /// uint32_t consume(uint32_t _size); // consumer only + /// Reserve slots, makes slots non-free, but ready to be used yet. Moves "write" head forward. + /// + /// @param[in] _size Number of slots. + /// @param[in] _mustSucceed If argument is true it will not reseve any slots unless `_size` + /// of slots is reseved. + /// + /// @returns Number of reserved slots reserved. /// uint32_t reserve(uint32_t _size, bool _mustSucceed = false); // producer only + /// Commit slots, makes slots used, and ready to be consumed. Moves "current" head forward. + /// + /// @param[in] _size Number of commited slots. /// uint32_t commit(uint32_t _size); // producer only + /// Calculate distance between two slots. Function takes wrapping into account. + /// + /// @param[in] _from From. + /// @param[in] _to To. + /// + /// @returns Distance between slots. /// uint32_t distance(uint32_t _from, uint32_t _to) const; // both + /// Invalidate ring buffer. /// void reset(); - const uint32_t m_size; - uint32_t m_current; - uint32_t m_write; - uint32_t m_read; + uint32_t m_size; //!< Size of ring buffer. + uint32_t m_current; //!< Currently operated area start. + uint32_t m_write; //!< Write head. + uint32_t m_read; //!< Read head. }; /// diff --git a/tests/ringbuffer_test.cpp b/tests/ringbuffer_test.cpp index 7712cd4..1686872 100644 --- a/tests/ringbuffer_test.cpp +++ b/tests/ringbuffer_test.cpp @@ -8,13 +8,118 @@ TEST_CASE("RingBufferControl", "") { - bx::RingBufferControl control(16); + constexpr uint32_t kMax = 16; + + bx::RingBufferControl control(kMax); + + REQUIRE(kMax == control.getSize() ); + REQUIRE(0 == control.getNumUsed() ); + REQUIRE(0 == control.getNumReserved() ); + REQUIRE(kMax-1 == control.getNumEmpty() ); + REQUIRE(control.isEmpty() ); + + REQUIRE(1 == control.reserve(1) ); + + REQUIRE(kMax == control.getSize() ); + REQUIRE(0 == control.getNumUsed() ); + REQUIRE(1 == control.getNumReserved() ); + REQUIRE(kMax-2 == control.getNumEmpty() ); + REQUIRE(!control.isEmpty() ); + + REQUIRE(0 == control.reserve(16, true) ); + REQUIRE(kMax-2 == control.reserve(16) ); + + REQUIRE(kMax == control.getSize() ); + REQUIRE(0 == control.getNumUsed() ); + REQUIRE(kMax-1 == control.getNumReserved() ); + REQUIRE(0 == control.getNumEmpty() ); + REQUIRE(!control.isEmpty() ); - REQUIRE(1 == control.reserve(1) ); - REQUIRE(0 == control.reserve(16, true) ); - REQUIRE(14 == control.reserve(16) ); REQUIRE(15 == control.commit(15) ); - REQUIRE(15 == control.available() ); + + REQUIRE(kMax == control.getSize() ); + REQUIRE(kMax-1 == control.getNumUsed() ); + REQUIRE(0 == control.getNumReserved() ); + REQUIRE(0 == control.getNumEmpty() ); + REQUIRE(!control.isEmpty() ); + REQUIRE(15 == control.consume(15) ); - REQUIRE(0 == control.available() ); + + REQUIRE(kMax == control.getSize() ); + REQUIRE(0 == control.getNumUsed() ); + REQUIRE(0 == control.getNumReserved() ); + REQUIRE(kMax-1 == control.getNumEmpty() ); + REQUIRE(control.isEmpty() ); +} + +TEST_CASE("RingBufferControl resize", "") +{ + bx::RingBufferControl control(10); + + uint32_t reserved; + uint32_t commited; + uint32_t consumed; + + reserved = control.reserve(8); + REQUIRE(reserved == 8); + REQUIRE(control.m_current == 0); + REQUIRE(control.m_write == 8); + REQUIRE(control.m_read == 0); + + commited = control.commit(4); + REQUIRE(commited == 4); + REQUIRE(control.m_current == 4); + REQUIRE(control.m_write == 8); + REQUIRE(control.m_read == 0); + + consumed = control.consume(2); + REQUIRE(consumed == 2); + REQUIRE(control.m_current == 4); + REQUIRE(control.m_write == 8); + REQUIRE(control.m_read == 2); + + REQUIRE(10 == control.getSize() ); + + control.resize(10); + REQUIRE(20 == control.getSize() ); + + control.reserve(8); + REQUIRE(control.m_current == 4); + REQUIRE(control.m_write == 16); + REQUIRE(control.m_read == 2); + + control.commit(4); + REQUIRE(control.m_current == 8); + REQUIRE(control.m_write == 16); + REQUIRE(control.m_read == 2); + + control.consume(2); + REQUIRE(control.m_current == 8); + REQUIRE(control.m_write == 16); + REQUIRE(control.m_read == 4); + + reserved = control.reserve(4); + REQUIRE(reserved == 4); + commited = control.commit(4); + REQUIRE(commited == 4); + consumed = control.consume(6); + REQUIRE(consumed == 6); + + REQUIRE(control.m_current == 12); + REQUIRE(control.m_write == 0); + REQUIRE(control.m_read == 10); + + REQUIRE(2 == control.getNumUsed() ); + REQUIRE(8 == control.getNumReserved() ); + REQUIRE(9 == control.getNumEmpty() ); + + control.resize(-10); + REQUIRE(11 == control.getSize() ); + REQUIRE(2 == control.getNumUsed() ); + REQUIRE(8 == control.getNumReserved() ); + REQUIRE(0 == control.getNumEmpty() ); + + REQUIRE(control.m_current == 3); + REQUIRE(control.m_write == 0); + REQUIRE(control.m_read == 1); }