359 lines
10 KiB
C++
359 lines
10 KiB
C++
/*
|
||
Copyright (c) 2026 - SD-Innovation S.A.S. - FRANCE
|
||
*/
|
||
|
||
/*
|
||
ver: 2.x.x - build: 2026-04-28
|
||
*/
|
||
|
||
/*
|
||
The zlib License
|
||
|
||
Copyright (c) 2026 SD-Innovation S.A.S.
|
||
|
||
This software is provided ‘as-is’, without any express or implied
|
||
warranty. In no event will the authors be held liable for any damages
|
||
arising from the use of this software.
|
||
|
||
Permission is granted to anyone to use this software for any purpose,
|
||
including commercial applications, and to alter it and redistribute it
|
||
freely, subject to the following restrictions:
|
||
|
||
1. The origin of this software must not be misrepresented; you must not
|
||
claim that you wrote the original software. If you use this software
|
||
in a product, an acknowledgment in the product documentation would be
|
||
appreciated but is not required.
|
||
|
||
2. Altered source versions must be plainly marked as such, and must not be
|
||
misrepresented as being the original software.
|
||
|
||
3. This notice may not be removed or altered from any source
|
||
distribution.
|
||
*/
|
||
|
||
#pragma once
|
||
|
||
#include <array>
|
||
#include <cstddef>
|
||
#include <optional>
|
||
|
||
namespace sdi_toolBox::common::utils
|
||
{
|
||
//--------------------------------------------------------------
|
||
/**
|
||
* @brief Fixed-size ring (circular) buffer with compile-time capacity
|
||
* and overwrite-on-full behavior.
|
||
*
|
||
* This template implements a simple circular buffer with a compile-time
|
||
* fixed capacity. When the buffer is full and a new element is pushed,
|
||
* the oldest element is overwritten.
|
||
*
|
||
* @tparam T Element type stored in the buffer.
|
||
* @tparam CAPACITY Compile-time buffer capacity. Must be > 0.
|
||
*
|
||
* @note The class provides both optional-returning and out-parameter
|
||
* overloads for pop/front/back to suit different runtime constraints.
|
||
* @warning Instantiating with CAPACITY == 0 is forbidden (static_assert).
|
||
*
|
||
* @par Example:
|
||
* @code{.cpp}
|
||
* RingBuffer<int, 8> rb;
|
||
* rb.push(1);
|
||
* int v;
|
||
* if (rb.pop(v))
|
||
* {
|
||
* ...
|
||
* }
|
||
* @endcode
|
||
*/
|
||
template<class T, std::size_t CAPACITY>
|
||
class RingBuffer
|
||
{
|
||
static_assert(CAPACITY > 0, "CAPACITY must be > 0");
|
||
|
||
public:
|
||
///@name Write operations
|
||
///@{
|
||
|
||
/**
|
||
* @brief Append a value to the buffer.
|
||
*
|
||
* If the buffer is full, the oldest element is overwritten.
|
||
* @param value Value to append (copied).
|
||
*
|
||
* @return true if the value was added without overwriting, false if an overwrite occurred.
|
||
*/
|
||
bool push(const T &value);
|
||
|
||
///@}
|
||
///@name Read operations
|
||
///@{
|
||
|
||
/**
|
||
* @brief Remove and return the oldest element from the buffer.
|
||
*
|
||
* @return std::optional<T> The oldest element if present, std::nullopt if empty.
|
||
*/
|
||
|
||
std::optional<T> pop();
|
||
/**
|
||
* @brief Remove the oldest element and store it in the provided reference.
|
||
*
|
||
* @param value Output reference that receives the removed element.
|
||
*
|
||
* @return true if an element was removed, false if the buffer was empty.
|
||
*/
|
||
bool pop(T &value);
|
||
|
||
/**
|
||
* @brief Return (without removing) the oldest element.
|
||
*
|
||
* @return std::optional<T> The oldest element if present, std::nullopt if empty.
|
||
*/
|
||
[[nodiscard]] std::optional<T> front() const;
|
||
|
||
/**
|
||
* @brief Copy the oldest element into the provided reference without removing it.
|
||
*
|
||
* @param value Output reference that receives the element.
|
||
*
|
||
* @return true if the element was copied, false if the buffer is empty.
|
||
*/
|
||
[[nodiscard]] bool front(T &value) const;
|
||
|
||
/**
|
||
* @brief Return (without removing) the newest element.
|
||
*
|
||
* @return std::optional<T> The newest element if present, std::nullopt if empty.
|
||
*/
|
||
[[nodiscard]] std::optional<T> back() const;
|
||
|
||
/**
|
||
* @brief Copy the newest element into the provided reference without removing it.
|
||
*
|
||
* @param value Output reference that receives the element.
|
||
*
|
||
* @return true if the element was copied, false if the buffer is empty.
|
||
*/
|
||
[[nodiscard]] bool back(T &value) const;
|
||
|
||
///@}
|
||
///@name State queries
|
||
///@{
|
||
|
||
/**
|
||
* @brief Check whether the buffer is empty.
|
||
*
|
||
* @return true if empty, false otherwise.
|
||
*/
|
||
[[nodiscard]] bool empty() const;
|
||
|
||
/**
|
||
* @brief Check whether the buffer is full.
|
||
*
|
||
* @return true if full, false otherwise.
|
||
*/
|
||
[[nodiscard]] bool full() const;
|
||
|
||
/**
|
||
* @brief Number of elements currently stored in the buffer.
|
||
*
|
||
* @return Current size (0 .. CAPACITY).
|
||
*/
|
||
[[nodiscard]] std::size_t size() const;
|
||
|
||
/**
|
||
* @brief Compile-time capacity of the buffer.
|
||
*
|
||
* @return The maximum number of elements the buffer can hold.
|
||
*/
|
||
[[nodiscard]] constexpr std::size_t capacity() const;
|
||
|
||
///@}
|
||
///@name Modifiers
|
||
///@{
|
||
|
||
/**
|
||
* @brief Clear the buffer and reset internal indices.
|
||
*
|
||
* After calling clear(), empty() returns true and size() returns 0.
|
||
*/
|
||
void clear();
|
||
|
||
///@}
|
||
|
||
private:
|
||
///@name Internal helpers
|
||
///@{
|
||
|
||
/**
|
||
* @brief Compute the next index in a circular manner.
|
||
*
|
||
* Implemented to avoid expensive modulus operations on some targets.
|
||
* @param index Current index.
|
||
*
|
||
* @return Next index in range [0, CAPACITY-1].
|
||
*/
|
||
[[nodiscard]] std::size_t next(std::size_t index) const;
|
||
|
||
///@}
|
||
///@name Data members
|
||
///@{
|
||
|
||
std::array<T, CAPACITY> m_data{}; ///< Storage for the buffer elements
|
||
std::size_t m_head{ 0 }; ///< Index of the next element to write
|
||
std::size_t m_tail{ 0 }; ///< Index of the next element to read
|
||
bool m_full{ false }; ///< Indicates whether the buffer is full
|
||
|
||
///@}
|
||
};
|
||
//--------------------------------------------------------------
|
||
|
||
//--------------------------------------------------------------
|
||
/* Append a value to the buffer. If the buffer is full, the
|
||
* oldest value will be overwritten. Return true if the value
|
||
* was added without overwriting, false if an overwrite occurred. */
|
||
template<class T, std::size_t CAPACITY>
|
||
bool RingBuffer<T, CAPACITY>::push(const T &value)
|
||
{
|
||
m_data[m_head] = value;
|
||
m_head = next(m_head);
|
||
|
||
if (m_full)
|
||
{
|
||
m_tail = m_head; // overwrite the oldest value
|
||
return false; // indicate that an overwrite occurred
|
||
}
|
||
else
|
||
{
|
||
m_full = (m_head == m_tail);
|
||
return true; // indicate that the value was added without overwriting
|
||
}
|
||
}
|
||
//--------------------------------------------------------------
|
||
/* Remove and return the oldest value from the buffer. If the
|
||
* buffer is empty, return std::nullopt */
|
||
template<class T, std::size_t CAPACITY>
|
||
std::optional<T> RingBuffer<T, CAPACITY>::pop()
|
||
{
|
||
if (empty())
|
||
return std::nullopt;
|
||
const std::optional<T> result = m_data[m_tail];
|
||
m_tail = next(m_tail);
|
||
m_full = false;
|
||
return result;
|
||
}
|
||
//--------------------------------------------------------------
|
||
/* Remove the oldest value from the buffer and store it in
|
||
* 'value'. Return true if successful, false if the buffer is empty */
|
||
template<class T, std::size_t CAPACITY>
|
||
bool RingBuffer<T, CAPACITY>::pop(T &value)
|
||
{
|
||
if (empty())
|
||
return false;
|
||
|
||
value = m_data[m_tail];
|
||
m_tail = next(m_tail);
|
||
m_full = false;
|
||
return true;
|
||
}
|
||
//--------------------------------------------------------------
|
||
/* Return the oldest value without removing it. If the buffer is
|
||
* empty, return std::nullopt */
|
||
template<class T, std::size_t CAPACITY>
|
||
std::optional<T> RingBuffer<T, CAPACITY>::front() const
|
||
{
|
||
if (empty())
|
||
return std::nullopt;
|
||
return m_data[m_tail];
|
||
}
|
||
//--------------------------------------------------------------
|
||
/* Return the oldest value without removing it and store it in
|
||
* 'value'. Return true if successful, false if the buffer is empty */
|
||
template<class T, std::size_t CAPACITY>
|
||
bool RingBuffer<T, CAPACITY>::front(T &value) const
|
||
{
|
||
if (empty())
|
||
return false;
|
||
value = m_data[m_tail];
|
||
return true;
|
||
}
|
||
//--------------------------------------------------------------
|
||
/* Return the newest value without removing it. If the buffer is
|
||
* empty, return std::nullopt */
|
||
template<class T, std::size_t CAPACITY>
|
||
std::optional<T> RingBuffer<T, CAPACITY>::back() const
|
||
{
|
||
if (empty())
|
||
return std::nullopt;
|
||
return m_data[(m_head == 0) ? CAPACITY - 1 : m_head - 1];
|
||
}
|
||
//--------------------------------------------------------------
|
||
/* Return the newest value without removing it and store it in
|
||
* 'value'. Return true if successful, false if the buffer is empty */
|
||
template<class T, std::size_t CAPACITY>
|
||
bool RingBuffer<T, CAPACITY>::back(T &value) const
|
||
{
|
||
if (empty())
|
||
return false;
|
||
std::size_t backIndex = (m_head == 0) ? CAPACITY - 1 : m_head - 1;
|
||
value = m_data[backIndex];
|
||
return true;
|
||
}
|
||
//--------------------------------------------------------------
|
||
/* Check if the buffer is empty */
|
||
template<class T, std::size_t CAPACITY>
|
||
bool RingBuffer<T, CAPACITY>::empty() const
|
||
{
|
||
return !m_full && (m_head == m_tail);
|
||
}
|
||
//--------------------------------------------------------------
|
||
/* Check if the buffer is full */
|
||
template<class T, std::size_t CAPACITY>
|
||
bool RingBuffer<T, CAPACITY>::full() const
|
||
{
|
||
return m_full;
|
||
}
|
||
//--------------------------------------------------------------
|
||
/* Get the number of elements currently in the buffer */
|
||
template<class T, std::size_t CAPACITY>
|
||
std::size_t RingBuffer<T, CAPACITY>::size() const
|
||
{
|
||
if (m_full)
|
||
return CAPACITY;
|
||
if (m_head >= m_tail)
|
||
return m_head - m_tail;
|
||
return CAPACITY - (m_tail - m_head);
|
||
}
|
||
//--------------------------------------------------------------
|
||
/* Get the maximum capacity of the buffer */
|
||
template<class T, std::size_t CAPACITY>
|
||
[[nodiscard]] constexpr std::size_t RingBuffer<T, CAPACITY>::capacity() const
|
||
{
|
||
return CAPACITY;
|
||
}
|
||
//--------------------------------------------------------------
|
||
/* Clear the buffer, resetting it to an empty state */
|
||
template<class T, std::size_t CAPACITY>
|
||
void RingBuffer<T, CAPACITY>::clear()
|
||
{
|
||
m_head = 0;
|
||
m_tail = 0;
|
||
m_full = false;
|
||
}
|
||
//--------------------------------------------------------------
|
||
/* Helper function to calculate the next index in a circular manner */
|
||
template<class T, std::size_t CAPACITY>
|
||
std::size_t RingBuffer<T, CAPACITY>::next(const std::size_t index) const
|
||
{
|
||
// Calculate the next index in a circular manner without using
|
||
// modulus operator for better performance
|
||
const auto nextIndex = index + 1;
|
||
if (nextIndex == CAPACITY)
|
||
return 0;
|
||
|
||
return nextIndex;
|
||
}
|
||
//--------------------------------------------------------------
|
||
} // namespace sdi_toolBox::common::utils
|