Add first RingBuffer draft version
This commit is contained in:
487
openVulkanoCpp/Data/Containers/RingBuffer.hpp
Normal file
487
openVulkanoCpp/Data/Containers/RingBuffer.hpp
Normal file
@@ -0,0 +1,487 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Base/Utils.hpp"
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <stdexcept>
|
||||
#include <optional>
|
||||
|
||||
namespace OpenVulkano
|
||||
{
|
||||
namespace internal_detail
|
||||
{
|
||||
/**
|
||||
* \internal
|
||||
* This class is not intended for public use. Use RingBuffer<T> instead.
|
||||
*/
|
||||
template<typename T, class IMPL>
|
||||
class NPCRingBufferBase
|
||||
{
|
||||
protected:
|
||||
size_t count = 0, head = 0;
|
||||
|
||||
~NPCRingBufferBase() { assert(count == 0); }
|
||||
|
||||
[[nodiscard]] size_t HeadId() const { return head; }
|
||||
private:
|
||||
[[nodiscard]] IMPL& Impl() { return *static_cast<IMPL*>(this); }
|
||||
[[nodiscard]] const IMPL& Impl() const { return *static_cast<const IMPL*>(this); }
|
||||
|
||||
//region Iterators
|
||||
private:
|
||||
template<bool IsConst>
|
||||
class ForwardIteratorBase
|
||||
{
|
||||
using BufferType = std::conditional_t<IsConst, const NPCRingBufferBase, NPCRingBufferBase>;
|
||||
using ValueType = std::conditional_t<IsConst, const T, T>;
|
||||
|
||||
BufferType* buffer;
|
||||
size_t index;
|
||||
int64_t remainder;
|
||||
|
||||
public:
|
||||
using iterator_category = std::bidirectional_iterator_tag;
|
||||
using value_type = T;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using pointer = ValueType*;
|
||||
using reference = ValueType&;
|
||||
|
||||
ForwardIteratorBase(BufferType* buf, size_t startIdx, int64_t remainder)
|
||||
: buffer(buf), index(startIdx), remainder(remainder) {}
|
||||
|
||||
reference operator*() const { return buffer->data()[index]; }
|
||||
pointer operator->() const { return &buffer->data()[index]; }
|
||||
|
||||
ForwardIteratorBase& operator++()
|
||||
{
|
||||
if (remainder > 1)
|
||||
{
|
||||
if (index == buffer->capacity() - 1)
|
||||
index = 0;
|
||||
else
|
||||
++index;
|
||||
}
|
||||
remainder--;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ForwardIteratorBase operator++(int)
|
||||
{
|
||||
ForwardIteratorBase tmp = *this;
|
||||
++(*this);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
ForwardIteratorBase& operator--()
|
||||
{
|
||||
if (index == 0)
|
||||
index = buffer->capacity() - 1;
|
||||
else
|
||||
--index;
|
||||
remainder++;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ForwardIteratorBase operator--(int)
|
||||
{
|
||||
ForwardIteratorBase tmp = *this;
|
||||
--(*this);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
friend bool operator==(const ForwardIteratorBase& a, const ForwardIteratorBase& b)
|
||||
{
|
||||
return a.buffer == b.buffer && a.index == b.index && a.remainder == b.remainder;
|
||||
}
|
||||
|
||||
friend bool operator!=(const ForwardIteratorBase& a, const ForwardIteratorBase& b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
};
|
||||
|
||||
template<bool IsConst>
|
||||
class ReverseIteratorBase
|
||||
{
|
||||
using BufferType = std::conditional_t<IsConst, const NPCRingBufferBase, NPCRingBufferBase>;
|
||||
using ValueType = std::conditional_t<IsConst, const T, T>;
|
||||
|
||||
BufferType* buffer;
|
||||
size_t index;
|
||||
int64_t remainder;
|
||||
|
||||
public:
|
||||
using iterator_category = std::bidirectional_iterator_tag;
|
||||
using value_type = T;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using pointer = ValueType*;
|
||||
using reference = ValueType&;
|
||||
|
||||
ReverseIteratorBase(BufferType* buf, size_t startIdx, int64_t remainder)
|
||||
: buffer(buf), index(startIdx), remainder(remainder) {}
|
||||
|
||||
reference operator*() const { return buffer->data()[index]; }
|
||||
pointer operator->() const { return &buffer->data()[index]; }
|
||||
|
||||
ReverseIteratorBase& operator++()
|
||||
{
|
||||
if (remainder > 1)
|
||||
{
|
||||
if (index == 0)
|
||||
index = buffer->capacity() - 1;
|
||||
else
|
||||
--index;
|
||||
}
|
||||
remainder--;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ReverseIteratorBase operator++(int)
|
||||
{
|
||||
ReverseIteratorBase tmp = *this;
|
||||
++(*this);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
ReverseIteratorBase& operator--()
|
||||
{
|
||||
if (index == buffer->capacity() - 1)
|
||||
index = 0;
|
||||
else
|
||||
++index;
|
||||
remainder++;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ReverseIteratorBase operator--(int)
|
||||
{
|
||||
ReverseIteratorBase tmp = *this;
|
||||
--(*this);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
friend bool operator==(const ReverseIteratorBase& a, const ReverseIteratorBase& b)
|
||||
{
|
||||
return a.buffer == b.buffer && a.index == b.index && a.remainder == b.remainder;
|
||||
}
|
||||
|
||||
friend bool operator!=(const ReverseIteratorBase& a, const ReverseIteratorBase& b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
using iterator = ForwardIteratorBase<false>;
|
||||
using const_iterator = ForwardIteratorBase<true>;
|
||||
|
||||
iterator begin() { return iterator(this, Impl().TailId(), count); }
|
||||
iterator end() { return iterator(this, HeadId(), 0); }
|
||||
|
||||
const_iterator begin() const { return cbegin(); }
|
||||
const_iterator end() const { return cend(); }
|
||||
|
||||
const_iterator cbegin() const { return const_iterator(this, Impl().TailId(), count); }
|
||||
const_iterator cend() const { return const_iterator(this, HeadId(), 0); }
|
||||
|
||||
using reverse_iterator = ReverseIteratorBase<false>;
|
||||
using const_reverse_iterator = ReverseIteratorBase<true>;
|
||||
|
||||
reverse_iterator rbegin() { return reverse_iterator(this, HeadId(), count); }
|
||||
reverse_iterator rend() { return reverse_iterator(this, Impl().TailId(), 0); }
|
||||
|
||||
const_reverse_iterator rbegin() const { return crbegin(); }
|
||||
const_reverse_iterator rend() const { return crend(); }
|
||||
|
||||
const_reverse_iterator crbegin() const { return const_reverse_iterator(this, HeadId(), count); }
|
||||
const_reverse_iterator crend() const { return const_reverse_iterator(this, Impl().TailId(), 0); }
|
||||
//endregion
|
||||
|
||||
public:
|
||||
[[nodiscard]] size_t capacity() const { return Impl().Capacity(); }
|
||||
[[nodiscard]] T* data() { return Impl().Data(); }
|
||||
[[nodiscard]] const T* data() const { return Impl().Data(); }
|
||||
|
||||
public:
|
||||
[[nodiscard]] size_t Count() const { return count; }
|
||||
[[nodiscard]] size_t Size() const { return count; }
|
||||
[[nodiscard]] bool IsEmpty() const { return count == 0; }
|
||||
[[nodiscard]] bool HasFree() const { return Count() != capacity(); }
|
||||
|
||||
T PopFront()
|
||||
{
|
||||
if (IsEmpty()) throw std::underflow_error("RingBuffer is empty");
|
||||
|
||||
T value = std::move(data()[head]);
|
||||
data()[head].~T();
|
||||
if (head == 0) head = capacity() - 1;
|
||||
else head--;
|
||||
count--;
|
||||
return value;
|
||||
}
|
||||
|
||||
T PopBack()
|
||||
{
|
||||
if (IsEmpty()) throw std::underflow_error("RingBuffer is empty");
|
||||
size_t tail = Impl().TailId();
|
||||
T value = std::move(data()[tail]);
|
||||
data()[tail].~T();
|
||||
count--;
|
||||
return value;
|
||||
}
|
||||
|
||||
void Clear()
|
||||
{
|
||||
while(count)
|
||||
{
|
||||
data()[head].~T();
|
||||
if (head == 0) head = capacity() - 1;
|
||||
else head--;
|
||||
count--;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] T& Back() { return data()[Impl().TailId()]; }
|
||||
[[nodiscard]] const T& Back() const { return data()[Impl().TailId()]; }
|
||||
|
||||
[[nodiscard]] T& Front() { return data()[head]; }
|
||||
[[nodiscard]] const T& Front() const { return data()[head]; }
|
||||
|
||||
//region Insertion
|
||||
void PushNoOverwrite(const T& value) { if (!HasFree()) throw std::overflow_error("RingBuffer is full"); Push(value); }
|
||||
void PushNoOverwrite(T&& value) { if (!HasFree()) throw std::overflow_error("RingBuffer is full"); Push(std::move(value)); }
|
||||
template<typename... Args>
|
||||
void EmplaceNoOverwrite(Args&&... args)
|
||||
{
|
||||
if (!HasFree()) throw std::overflow_error("RingBuffer is full");
|
||||
Emplace(std::forward<Args...>(args...));
|
||||
}
|
||||
|
||||
void Push(const T& value)
|
||||
{
|
||||
Impl().IncrementHead();
|
||||
if (HasFree()) count++;
|
||||
else data()[head].~T();
|
||||
new (&data()[head]) T(value);
|
||||
}
|
||||
|
||||
void Push(T&& value)
|
||||
{
|
||||
Impl().IncrementHead();
|
||||
if (HasFree()) count++;
|
||||
else data()[head].~T();
|
||||
new (&data()[head]) T(std::move(value));
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
void Emplace(Args&&... args)
|
||||
{
|
||||
Impl().IncrementHead();
|
||||
if (HasFree()) count++;
|
||||
else data()[head].~T();
|
||||
new (&data()[head]) T(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<T> PushFront(const T& value)
|
||||
{
|
||||
if (HasFree())
|
||||
{
|
||||
count++;
|
||||
Impl().IncrementHead();
|
||||
new (&data()[head]) T(value);
|
||||
return std::nullopt;
|
||||
}
|
||||
Impl().IncrementHead();
|
||||
std::optional<T> oldData(std::move(Front()));
|
||||
data()[head].~T();
|
||||
new (&data()[head]) T(value);
|
||||
return oldData;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<T> PushFront(T&& value)
|
||||
{
|
||||
if (HasFree())
|
||||
{
|
||||
count++;
|
||||
Impl().IncrementHead();
|
||||
new (&data()[head]) T(std::move(value));
|
||||
return std::nullopt;
|
||||
}
|
||||
Impl().IncrementHead();
|
||||
std::optional<T> oldData(std::move(Front()));
|
||||
data()[head].~T();
|
||||
new (&data()[head]) T(std::move(value));
|
||||
return oldData;
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
[[nodiscard]] std::optional<T> EmplaceFront(Args&&... args)
|
||||
{
|
||||
if (HasFree())
|
||||
{
|
||||
count++;
|
||||
Impl().IncrementHead();
|
||||
new (&data()[head]) T(std::forward<Args>(args)...);
|
||||
return std::nullopt;
|
||||
}
|
||||
Impl().IncrementHead();
|
||||
std::optional<T> oldData(std::move(Front()));
|
||||
data()[head].~T();
|
||||
new (&data()[head]) T(std::forward<Args>(args)...);
|
||||
return oldData;
|
||||
}
|
||||
|
||||
std::optional<T> PushBack(const T& value)
|
||||
{
|
||||
if (HasFree())
|
||||
{
|
||||
count++;
|
||||
new (&data()[Impl().TailId()]) T(value);
|
||||
return std::nullopt;
|
||||
}
|
||||
size_t tail = Impl().TailId();
|
||||
std::optional<T> oldData(std::move(data()[tail]));
|
||||
data()[tail].~T();
|
||||
new (&data()[tail]) T(value);
|
||||
return oldData;
|
||||
}
|
||||
|
||||
std::optional<T> PushBack(T&& value)
|
||||
{
|
||||
if (HasFree())
|
||||
{
|
||||
count++;
|
||||
new (&data()[Impl().TailId()]) T(std::move(value));
|
||||
return std::nullopt;
|
||||
}
|
||||
size_t tail = Impl().TailId();
|
||||
std::optional<T> oldData(std::move(data()[tail]));
|
||||
data()[tail].~T();
|
||||
new (&data()[tail]) T(std::move(value));
|
||||
return oldData;
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
std::optional<T> EmplaceBack(Args&&... args)
|
||||
{
|
||||
if (HasFree())
|
||||
{
|
||||
count++;
|
||||
new (&data()[Impl().TailId()]) T(std::forward<Args>(args)...);
|
||||
return std::nullopt;
|
||||
}
|
||||
size_t tail = Impl().TailId();
|
||||
std::optional<T> oldData(std::move(data()[tail]));
|
||||
data()[tail].~T();
|
||||
new (&data()[tail]) T(std::forward<Args>(args)...);
|
||||
return oldData;
|
||||
}
|
||||
//endregion
|
||||
|
||||
[[nodiscard]] T& at(size_t idx) { if (idx >= Size()) throw std::range_error("Out of bounds"); return (*this)[idx]; }
|
||||
[[nodiscard]] const T& at(size_t idx) const { if (idx >= Size()) throw std::range_error("Out of bounds"); return (*this)[idx]; }
|
||||
|
||||
[[nodiscard]] T& operator[](size_t idx)
|
||||
{
|
||||
return data()[Impl().Index(idx)];
|
||||
}
|
||||
|
||||
[[nodiscard]] const T& operator[](size_t idx) const
|
||||
{
|
||||
return data()[Impl().Index(idx)];
|
||||
}
|
||||
|
||||
void Fill(const T& value)
|
||||
{
|
||||
while(HasFree()) { Push(value); }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
template<typename T, size_t SIZE = std::numeric_limits<size_t>::max()>
|
||||
class RingBuffer;
|
||||
|
||||
template<typename T>
|
||||
class RingBuffer<T, std::numeric_limits<size_t>::max()> final : public internal_detail::NPCRingBufferBase<T, RingBuffer<T, std::numeric_limits<size_t>::max()>>
|
||||
{
|
||||
typedef internal_detail::NPCRingBufferBase<T, RingBuffer<T, std::numeric_limits<size_t>::max()>> Parent;
|
||||
friend Parent;
|
||||
|
||||
struct RawFreeDeleter { void operator()(void* ptr) const { ::operator delete(ptr); } };
|
||||
|
||||
std::unique_ptr<T, RawFreeDeleter> m_data;
|
||||
size_t m_capacity;
|
||||
|
||||
[[nodiscard]] size_t TailId() const
|
||||
{
|
||||
if (Parent::IsEmpty()) [[unlikely]] return Parent::head;
|
||||
return (Parent::HeadId() + 1 + Capacity() - Parent::Count()) % Capacity();
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t IncrementHead()
|
||||
{
|
||||
if (Parent::IsEmpty()) [[unlikely]] return Parent::head;
|
||||
return (Parent::head = (Parent::HeadId() + 1) % Capacity());
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t Index(size_t i) const { return (TailId() + i) % Capacity(); }
|
||||
public:
|
||||
RingBuffer(const size_t size = 10): m_data(static_cast<T*>(::operator new(sizeof(T) * size))), m_capacity(size) {}
|
||||
|
||||
~RingBuffer() { Parent::Clear(); }
|
||||
|
||||
[[nodiscard]] size_t Capacity() const { return m_capacity; }
|
||||
[[nodiscard]] T* Data() { return m_data.get(); }
|
||||
[[nodiscard]] const T* Data() const { return m_data.get(); }
|
||||
};
|
||||
|
||||
template<typename T, size_t SIZE>
|
||||
class RingBuffer final : public internal_detail::NPCRingBufferBase<T, RingBuffer<T, SIZE>>
|
||||
{
|
||||
typedef internal_detail::NPCRingBufferBase<T, RingBuffer<T, SIZE>> Parent;
|
||||
friend Parent;
|
||||
constexpr static bool POW2 = (Utils::IsPow2(SIZE) && SIZE > 0);
|
||||
constexpr static size_t MASK = SIZE - 1;
|
||||
|
||||
using StorageType = typename std::aligned_storage<sizeof(T), alignof(T)>::type;
|
||||
StorageType m_data[SIZE]; // Uninitialized raw storage
|
||||
|
||||
[[nodiscard]] size_t Index(size_t i) const
|
||||
{
|
||||
if constexpr (POW2)
|
||||
return (Parent::HeadId() - (Parent::Count() - 1) - i) & MASK;
|
||||
else
|
||||
return (TailId() + i) % Capacity();
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t TailId() const
|
||||
{
|
||||
if (Parent::IsEmpty()) [[unlikely]] return Parent::head;
|
||||
if constexpr (POW2)
|
||||
return (Parent::HeadId() - (Parent::Count() - 1)) & MASK;
|
||||
else
|
||||
return (Parent::HeadId() + 1 + Capacity() - Parent::Count()) % Capacity();
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t IncrementHead()
|
||||
{
|
||||
if (Parent::IsEmpty()) [[unlikely]] return Parent::head;
|
||||
if constexpr (POW2)
|
||||
return (Parent::head = (Parent::HeadId() + 1) & MASK);
|
||||
else
|
||||
return (Parent::head = (Parent::HeadId() + 1) % Capacity());
|
||||
}
|
||||
public:
|
||||
~RingBuffer() { Parent::Clear(); }
|
||||
|
||||
[[nodiscard]] size_t Capacity() const { return SIZE; }
|
||||
[[nodiscard]] T* Data() { return reinterpret_cast<T*>(m_data); }
|
||||
[[nodiscard]] const T* Data() const { return reinterpret_cast<const T*>(m_data); }
|
||||
};
|
||||
}
|
||||
179
tests/Data/Containers/RingBufferTest.cpp
Normal file
179
tests/Data/Containers/RingBufferTest.cpp
Normal file
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <catch2/catch_all.hpp>
|
||||
#include "Data/Containers/RingBuffer.hpp"
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
|
||||
using namespace OpenVulkano;
|
||||
|
||||
// Helper to track construction/destruction
|
||||
struct Tracked {
|
||||
static inline int ctorCount = 0;
|
||||
static inline int dtorCount = 0;
|
||||
int value;
|
||||
|
||||
Tracked(int v = 0) : value(v) { ++ctorCount; }
|
||||
Tracked(const Tracked& other) : value(other.value) { ++ctorCount; }
|
||||
Tracked(Tracked&& other) noexcept : value(other.value) { ++ctorCount; }
|
||||
~Tracked() { ++dtorCount; }
|
||||
|
||||
Tracked& operator=(const Tracked&) = default;
|
||||
Tracked& operator=(Tracked&&) = default;
|
||||
|
||||
friend bool operator==(const Tracked& lhs, const Tracked& rhs) {
|
||||
return lhs.value == rhs.value;
|
||||
}
|
||||
};
|
||||
|
||||
void reset_tracking() {
|
||||
Tracked::ctorCount = 0;
|
||||
Tracked::dtorCount = 0;
|
||||
}
|
||||
|
||||
TEST_CASE("RingBuffer Tracking Destruction", "[RingBuffer][Tracked]") {
|
||||
reset_tracking();
|
||||
|
||||
{
|
||||
RingBuffer<Tracked, 4> buf;
|
||||
buf.Push(Tracked(1));
|
||||
buf.Push(Tracked(2));
|
||||
buf.PopFront();
|
||||
buf.Clear();
|
||||
}
|
||||
|
||||
REQUIRE(Tracked::dtorCount == Tracked::ctorCount);
|
||||
}
|
||||
|
||||
TEST_CASE("RingBuffer Push and Pop Operations", "[RingBuffer]") {
|
||||
RingBuffer<int, 3> buf;
|
||||
|
||||
buf.Push(1);
|
||||
buf.Push(2);
|
||||
buf.Push(3);
|
||||
|
||||
REQUIRE(buf.Count() == 3);
|
||||
REQUIRE_FALSE(buf.HasFree());
|
||||
|
||||
SECTION("PopFront/Back correctness") {
|
||||
REQUIRE(buf.PopBack() == 1);
|
||||
REQUIRE(buf.PopFront() == 3);
|
||||
REQUIRE(buf.Count() == 1);
|
||||
}
|
||||
|
||||
SECTION("Clear empties the buffer") {
|
||||
buf.Clear();
|
||||
REQUIRE(buf.IsEmpty());
|
||||
REQUIRE(buf.Count() == 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("PushBack and PushFront Overwrite Logic", "[RingBuffer]") {
|
||||
RingBuffer<int, 2> buf;
|
||||
buf.Push(1);
|
||||
buf.Push(2);
|
||||
|
||||
auto overwrittenFront = buf.PushFront(3);
|
||||
REQUIRE(overwrittenFront.has_value());
|
||||
REQUIRE(overwrittenFront.value() == 1);
|
||||
|
||||
auto overwrittenBack = buf.PushBack(4);
|
||||
REQUIRE(overwrittenBack.has_value());
|
||||
REQUIRE(overwrittenBack.value() == 2);
|
||||
|
||||
REQUIRE(buf.Front() == 3);
|
||||
REQUIRE(buf.Back() == 4);
|
||||
}
|
||||
|
||||
TEST_CASE("at() bounds checking", "[RingBuffer]") {
|
||||
RingBuffer<int, 3> buf;
|
||||
buf.Push(100);
|
||||
buf.Push(200);
|
||||
|
||||
REQUIRE(buf.at(0) == 100);
|
||||
REQUIRE(buf.at(1) == 200);
|
||||
REQUIRE_THROWS_AS(buf.at(2), std::range_error);
|
||||
}
|
||||
|
||||
TEST_CASE("Index-based Access and Wraparound", "[RingBuffer]") {
|
||||
RingBuffer<int, 3> buf;
|
||||
|
||||
buf.Push(1);
|
||||
buf.Push(2);
|
||||
buf.Push(3);
|
||||
REQUIRE(buf[0] == 1);
|
||||
REQUIRE(buf[1] == 2);
|
||||
REQUIRE(buf[2] == 3);
|
||||
|
||||
buf.PopBack(); // Remove 1
|
||||
buf.Push(4); // Overwrites oldest (2)
|
||||
|
||||
REQUIRE(buf[0] == 2); // Wrap-around behavior depends on ring position
|
||||
}
|
||||
|
||||
TEST_CASE("Emplace and EmplaceBack/Front work correctly", "[RingBuffer]") {
|
||||
RingBuffer<std::string, 2> buf;
|
||||
|
||||
buf.Emplace("first");
|
||||
buf.EmplaceBack("second");
|
||||
|
||||
REQUIRE(buf.Count() == 2);
|
||||
REQUIRE(buf[1] == "first");
|
||||
REQUIRE(buf[0] == "second");
|
||||
|
||||
auto overwritten = buf.EmplaceFront("new");
|
||||
REQUIRE(overwritten.has_value());
|
||||
REQUIRE(overwritten.value() == "second");
|
||||
REQUIRE(buf.Front() == "new");
|
||||
}
|
||||
|
||||
TEST_CASE("Iterators forward and reverse", "[RingBuffer]") {
|
||||
RingBuffer<int, 4> buf;
|
||||
buf.Push(10);
|
||||
buf.Push(20);
|
||||
buf.Push(30);
|
||||
|
||||
std::vector<int> forward;
|
||||
for (int val : buf) forward.push_back(val);
|
||||
REQUIRE(forward == std::vector<int>{10, 20, 30});
|
||||
|
||||
std::vector<int> reverse;
|
||||
for (auto it = buf.rbegin(); it != buf.rend(); ++it)
|
||||
reverse.push_back(*it);
|
||||
REQUIRE(reverse == std::vector<int>{30, 20, 10});
|
||||
}
|
||||
|
||||
TEST_CASE("Const correctness in iterators", "[RingBuffer][Const]") {
|
||||
RingBuffer<int, 3> buf;
|
||||
buf.Push(5);
|
||||
buf.Push(6);
|
||||
|
||||
const auto& constBuf = buf;
|
||||
|
||||
std::ostringstream oss;
|
||||
for (auto it = constBuf.cbegin(); it != constBuf.cend(); ++it) {
|
||||
oss << *it << " ";
|
||||
}
|
||||
|
||||
REQUIRE(oss.str() == "5 6 ");
|
||||
}
|
||||
|
||||
TEST_CASE("Dynamic RingBuffer behaves like static", "[RingBuffer][Dynamic]") {
|
||||
RingBuffer<int> buf(5);
|
||||
|
||||
for (int i = 0; i < 5; ++i)
|
||||
buf.Push(i * 10);
|
||||
|
||||
REQUIRE(buf.Count() == 5);
|
||||
REQUIRE_FALSE(buf.HasFree());
|
||||
REQUIRE(buf.Front() == 40);
|
||||
REQUIRE(buf.Back() == 0);
|
||||
|
||||
auto val = buf.PopBack();
|
||||
REQUIRE(val == 0);
|
||||
REQUIRE(buf.Count() == 4);
|
||||
}
|
||||
Reference in New Issue
Block a user