/* * 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 #include #include #include namespace OpenVulkano { constexpr size_t RING_BUFFER_DYNAMIC = std::numeric_limits::max(); namespace internal_detail { /** * \internal * This class is not intended for public use. Use RingBuffer instead. */ template 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(this); } [[nodiscard]] const IMPL& Impl() const { return *static_cast(this); } //region Iterators private: template class ForwardIteratorBase { using BufferType = std::conditional_t; using ValueType = std::conditional_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 class ReverseIteratorBase { using BufferType = std::conditional_t; using ValueType = std::conditional_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; using const_iterator = ForwardIteratorBase; 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; using const_reverse_iterator = ReverseIteratorBase; 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 void EmplaceNoOverwrite(Args&&... args) { if (!HasFree()) throw std::overflow_error("RingBuffer is full"); Emplace(std::forward(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 void Emplace(Args&&... args) { Impl().IncrementHead(); if (HasFree()) count++; else data()[head].~T(); new (&data()[head]) T(std::forward(args)...); } [[nodiscard]] std::optional PushFront(const T& value) { if (HasFree()) { count++; Impl().IncrementHead(); new (&data()[head]) T(value); return std::nullopt; } Impl().IncrementHead(); std::optional oldData(std::move(Front())); data()[head].~T(); new (&data()[head]) T(value); return oldData; } [[nodiscard]] std::optional PushFront(T&& value) { if (HasFree()) { count++; Impl().IncrementHead(); new (&data()[head]) T(std::move(value)); return std::nullopt; } Impl().IncrementHead(); std::optional oldData(std::move(Front())); data()[head].~T(); new (&data()[head]) T(std::move(value)); return oldData; } template [[nodiscard]] std::optional EmplaceFront(Args&&... args) { if (HasFree()) { count++; Impl().IncrementHead(); new (&data()[head]) T(std::forward(args)...); return std::nullopt; } Impl().IncrementHead(); std::optional oldData(std::move(Front())); data()[head].~T(); new (&data()[head]) T(std::forward(args)...); return oldData; } std::optional PushBack(const T& value) { if (HasFree()) { count++; new (&data()[Impl().TailId()]) T(value); return std::nullopt; } size_t tail = Impl().TailId(); std::optional oldData(std::move(data()[tail])); data()[tail].~T(); new (&data()[tail]) T(value); return oldData; } std::optional PushBack(T&& value) { if (HasFree()) { count++; new (&data()[Impl().TailId()]) T(std::move(value)); return std::nullopt; } size_t tail = Impl().TailId(); std::optional oldData(std::move(data()[tail])); data()[tail].~T(); new (&data()[tail]) T(std::move(value)); return oldData; } template std::optional EmplaceBack(Args&&... args) { if (HasFree()) { count++; new (&data()[Impl().TailId()]) T(std::forward(args)...); return std::nullopt; } size_t tail = Impl().TailId(); std::optional oldData(std::move(data()[tail])); data()[tail].~T(); new (&data()[tail]) T(std::forward(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 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)...); 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 class RingBuffer; template class RingBuffer final : public internal_detail::NPCRingBufferBase> { typedef internal_detail::NPCRingBufferBase> Parent; friend Parent; struct RawFreeDeleter { void operator()(void* ptr) const { ::operator delete(ptr); } }; std::unique_ptr 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(::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 class RingBuffer final : public internal_detail::NPCRingBufferBase> { typedef internal_detail::NPCRingBufferBase> 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::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(m_data); } [[nodiscard]] const T* Data() const { return reinterpret_cast(m_data); } }; }