ringbuffer_test/f1ll/ringbuffer.h

224 lines
No EOL
6.1 KiB
C++

/*
* ringbuffer.h
*
* Created on: May 31, 2025
* Author: Attila Body
*/
#pragma once
#include <cstddef>
#include <cstdint>
#include <cstring>
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 <iringbuffer::size_type SZ> 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 SZ> iringbuffer::size_type ringbuffer<SZ>::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 <iringbuffer::size_type SZ> void ringbuffer<SZ>::commit()
{
iringbuffer::size_type uncommitted = marker_diff(m_head_shadow, m_head);
if (!uncommitted) {
return;
}
m_head = m_head_shadow;
};
template <iringbuffer::size_type SZ> bool ringbuffer<SZ>::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 <iringbuffer::size_type SZ> void ringbuffer<SZ>::consumed(size_type len)
{
if (!len) {
return;
}
m_tail += len;
if (m_tail == sizeof(m_buffer)) {
m_tail = 0;
}
}
template <iringbuffer::size_type SZ> iringbuffer::size_type ringbuffer<SZ>::uncommited() const
{
return marker_diff(m_head_shadow, m_head);
}
template <iringbuffer::size_type SZ> iringbuffer::size_type ringbuffer<SZ>::commited() const
{
return marker_diff(m_head, m_tail);
}
template <iringbuffer::size_type SZ> void ringbuffer<SZ>::discard()
{
m_head_shadow = m_head;
}
template <iringbuffer::size_type SZ> iringbuffer::size_type ringbuffer<SZ>::size() const
{
return SZ;
}
template <iringbuffer::size_type SZ> iringbuffer::size_type ringbuffer<SZ>::used() const
{
return marker_diff(m_head_shadow, m_tail);
}
template <iringbuffer::size_type SZ> iringbuffer::size_type ringbuffer<SZ>::unused() const
{
return marker_diff(m_tail, m_head_shadow) - 1;
}
template <iringbuffer::size_type SZ> iringbuffer::size_type ringbuffer<SZ>::marker_diff(size_type m1, size_type m2) const
{
return m1 >= m2 ? m1 - m2 : sizeof(m_buffer) - m2 + m1;
}
} // namespace f1ll