/* * ringbuffer.h * * Created on: May 31, 2025 * Author: Attila Body */ #pragma once #include #include #include namespace f1ll { class iringbuffer { public: using size_type = size_t; /// @brief Copies data to the ring buffer (without committing it) /// @param data Pointer to the data to copy /// @param len Length of the data to copy /// @retval Length of the data copied (differs when not all data can fit) virtual size_type put(uint8_t const *data, size_type len) = 0; /// @brief Commits the data already placed into the buffer and notifies the /// consumer about it's availability virtual void commit() = 0; /// @brief Waits until all the data from the ring buffer gets consumed. // void flush(); /// @brief Gets a pointer to the next chunk of committed data in the buffer /// without registering the consumption. /// The caller should also call report_consumption using the returned /// chunk length after it finished processing the data. /// @param[in] len_requested Length of the data requested from the buffer. /// The length of the actual data provided /// might be actually smaller (because either reaching the end of /// the buffer or not enough data in the buffer). /// @param[out] data Receives a pointer to the first byte of the available /// data in the buffer /// @param[out] len Receives the length of the chunk available in the buffer. /// Will not exceed len_requested. /// @retval true if the buffer has more available data, false otherwise. virtual bool get_chunk(size_type len_requested, uint8_t *&data, size_type &len) = 0; /// @brief Marks the chunk returned by ringbuffer_GetChunk as available. /// @param consumed The length of the chunk as returned by /// ringbuffer_GetChunk(..., len) virtual void consumed(size_type len) = 0; /// @brief Returns the number of uncommited bytes in the ring buffer. virtual size_type uncommited() const = 0; /// @brief Returns the number of commited bytes in the ring buffer. virtual size_type commited() const = 0; /// @brief Discards the uncommited data in the ring buffer. virtual void discard() = 0; /// @brief Returns the size of the internal buffer. /// One byte in the buffer is always used as boundary virtual size_type size() const = 0; /// @brief Returns the size of the used place in the buffer // (including non-commited data) virtual size_type used() const = 0; /// @brief Returns the free storage capacity of the buffer virtual size_type unused() const = 0; }; template class ringbuffer : public iringbuffer { public: ringbuffer() = default; size_type put(uint8_t const *data, size_type len) override; void commit() override; bool get_chunk(size_type len_requested, uint8_t *&data, size_type &len) override; void consumed(size_type len) override; size_type uncommited() const override; size_type commited() const override; void discard() override; size_type size() const override; size_type used() const override; size_type unused() const override; private: uint8_t m_buffer[SZ]; //!< Data bufer size_type m_head = 0; //!< Write position size_type m_head_shadow = 0; //!< Shadowed write position for collecting data //!< before committing it size_type m_tail = 0; //!< Read position size_type marker_diff(size_type m1, size_type m2) const; }; // template iringbuffer::size_type ringbuffer::put(uint8_t const *data, size_type len) { size_type chunk1 = 0; size_type chunk2 = 0; if (!data || !len) { return 0; } size_type max_len = unused(); len = len < max_len ? len : max_len; chunk1 = sizeof(m_buffer) - m_head_shadow; if (chunk1 >= len) { chunk1 = len; } else { chunk2 = len - chunk1; } std::memcpy(m_buffer + m_head_shadow, data, chunk1); m_head_shadow += chunk1; if (m_head_shadow == sizeof(m_buffer)) { m_head_shadow = 0; } if (chunk2) { std::memcpy(m_buffer, data + chunk1, chunk2); m_head_shadow += chunk2; if (m_head_shadow == sizeof(m_buffer)) { m_head_shadow = 0; } } return len; } template void ringbuffer::commit() { iringbuffer::size_type uncommitted = marker_diff(m_head_shadow, m_head); if (!uncommitted) { return; } m_head = m_head_shadow; }; template bool ringbuffer::get_chunk(size_type len_requested, uint8_t *&data, size_type &len) { if (!len_requested) { return false; } size_type head = m_head; size_type tail = m_tail; size_type chunk_size = head >= tail ? head - tail : sizeof(m_buffer) - tail; if (!chunk_size) { len = 0; return false; } if (chunk_size > len_requested) { chunk_size = len_requested; } data = m_buffer + tail; len = chunk_size; tail += chunk_size; if (tail == sizeof(m_buffer)) { tail = 0; } return tail != head; } template void ringbuffer::consumed(size_type len) { if (!len) { return; } m_tail += len; if (m_tail == sizeof(m_buffer)) { m_tail = 0; } } template iringbuffer::size_type ringbuffer::uncommited() const { return marker_diff(m_head_shadow, m_head); } template iringbuffer::size_type ringbuffer::commited() const { return marker_diff(m_head, m_tail); } template void ringbuffer::discard() { m_head_shadow = m_head; } template iringbuffer::size_type ringbuffer::size() const { return SZ; } template iringbuffer::size_type ringbuffer::used() const { return marker_diff(m_head_shadow, m_tail); } template iringbuffer::size_type ringbuffer::unused() const { return marker_diff(m_tail, m_head_shadow) - 1; } template iringbuffer::size_type ringbuffer::marker_diff(size_type m1, size_type m2) const { return m1 >= m2 ? m1 - m2 : sizeof(m_buffer) - m2 + m1; } } // namespace f1ll