Files
OpenVulkano/openVulkanoCpp/Data/Containers/RingBuffer.hpp
2025-05-13 19:31:46 +02:00

523 lines
14 KiB
C++

/*
* 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
{
constexpr size_t RING_BUFFER_DYNAMIC = std::numeric_limits<size_t>::max();
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 (remainder == 0) { remainder++; return *this; }
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 (remainder == 0) { remainder++; return *this; }
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;
}
T PushAndOverwrite(const T& value)
{
if (HasFree()) [[unlikely]] throw std::runtime_error("Can't overwrite data because there are still empty slots.");
Impl().IncrementHead();
T oldData(std::move(Front()));
data()[head].~T();
new (&data()[head]) T(value);
return oldData;
}
T PushAndOverwrite(T&& value)
{
if (HasFree()) [[unlikely]] throw std::runtime_error("Can't overwrite data because there are still empty slots.");
Impl().IncrementHead();
T oldData(std::move(Front()));
data()[head].~T();
new (&data()[head]) T(std::move(value));
return oldData;
}
template<typename... Args>
T PushAndOverwrite(Args&&... args)
{
if (HasFree()) [[unlikely]] throw std::runtime_error("Can't overwrite data because there are still empty slots.");
Impl().IncrementHead();
T oldData(std::move(Front()));
data()[head].~T();
new (&data()[head]) 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 = RING_BUFFER_DYNAMIC>
class RingBuffer;
template<typename T>
class RingBuffer<T, RING_BUFFER_DYNAMIC> final : public internal_detail::NPCRingBufferBase<T, RingBuffer<T, RING_BUFFER_DYNAMIC>>
{
typedef internal_detail::NPCRingBufferBase<T, RingBuffer<T, RING_BUFFER_DYNAMIC>> 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();
}
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();
}
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); }
};
}