diff --git a/Containers/CMakeLists.txt b/Containers/CMakeLists.txt index 9b0f308..577d802 100644 --- a/Containers/CMakeLists.txt +++ b/Containers/CMakeLists.txt @@ -16,6 +16,7 @@ file(GLOB TEST_SOURCES ./tests/ListTest.cpp ./tests/MapTest.cpp ./tests/TreeTest.cpp + ./tests/RingBufferByte.cpp ./tests/Tests.cpp ) @@ -25,4 +26,4 @@ add_test(NAME Tests${PROJECT_NAME} COMMAND Tests${PROJECT_NAME}) add_executable(AVLTreeSpeedTest ./tests/AVLTreeProfiling.cpp) -target_link_libraries(AVLTreeSpeedTest ${PROJECT_NAME}) \ No newline at end of file +target_link_libraries(AVLTreeSpeedTest ${PROJECT_NAME}) diff --git a/Containers/public/LinearRingBuffer.hpp b/Containers/public/LinearRingBuffer.hpp new file mode 100644 index 0000000..e69de29 diff --git a/Containers/public/RingBufferByte.hpp b/Containers/public/RingBufferByte.hpp new file mode 100644 index 0000000..0f6d92c --- /dev/null +++ b/Containers/public/RingBufferByte.hpp @@ -0,0 +1,127 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +template +class RingBufferByte { +public: + explicit RingBufferByte(size_t maxCapacity) { buffer_.resize(maxCapacity); } + + ~RingBufferByte() = default; + + void pushBack(const uint8_t* buffer, size_t size) { + const auto REQUIRED_SIZE = size + sizeof(size); + const auto DEPOSIT = static_cast(freeSpace()) - static_cast(REQUIRED_SIZE); + + if (DEPOSIT < 0) { + if (ALLOW_BUFFER_OVERRIDE) { + freeupSpace(static_cast(-DEPOSIT)); + } else { + throw std::runtime_error("ring buffer overflow"); + } + } + + push(reinterpret_cast(&size), sizeof(size)); + push(buffer, size); + } + + [[nodiscard]] auto frontSize() const -> size_t { + size_t size = 0; + + if (dataSize() < sizeof(size)) { + throw std::runtime_error("no data in the buffer"); + } + + peek(reinterpret_cast(&size), sizeof(size)); + + return size; + } + + auto popFront(uint8_t* buffer) -> size_t { + size_t size = 0; + + if (dataSize() < sizeof(size)) { + return 0; + } + + pop(reinterpret_cast(&size), sizeof(size)); + pop(buffer, size); + + return size; + } + + void clear() { + end_ = 0; + start_ = 0; + } + + [[nodiscard]] bool empty() const { return start_ == end_; } + +private: + void push(const uint8_t* buffer, size_t size) { + for (size_t simpleIdx = 0; simpleIdx < size; simpleIdx++) { + buffer_[end_++] = buffer == nullptr ? 0 : buffer[simpleIdx]; + + if (end_ == buffer_.size()) { + end_ = 0; + } + } + } + + void pop(uint8_t* buffer, size_t size) { + for (size_t sampleIdx = 0; sampleIdx < size; sampleIdx++) { + if (buffer != nullptr) { + buffer[sampleIdx] = buffer_[start_]; + } + + start_++; + + if (start_ == buffer_.size()) { + start_ = 0; + } + } + } + + void peek(uint8_t* buffer, size_t size) const { + auto iter = start_; + for (size_t sampleIdx = 0; sampleIdx < size; sampleIdx++) { + buffer[sampleIdx] = buffer_[iter++]; + + if (iter == buffer_.size()) { + iter = 0; + } + } + } + + void freeupSpace(size_t additionalRequiredSpace) { + const auto CURRENT_DEPOSIT = freeSpace(); + + while (CURRENT_DEPOSIT + additionalRequiredSpace >= freeSpace()) { + popFront(nullptr); + } + } + + [[nodiscard]] size_t freeSpace() const { + if (start_ > end_) { + return start_ - end_; + } + + const auto OUT = buffer_.size() - (end_ - start_); + return OUT; + } + + [[nodiscard]] size_t dataSize() const { return buffer_.size() - freeSpace(); } + + [[nodiscard]] bool hasSpace(unsigned int length) const { return freeSpace() > length; } + + std::vector buffer_; + + size_t end_ = 0; + size_t start_ = 0; +}; diff --git a/Containers/tests/RingBufferByte.cpp b/Containers/tests/RingBufferByte.cpp new file mode 100644 index 0000000..c9d2bb9 --- /dev/null +++ b/Containers/tests/RingBufferByte.cpp @@ -0,0 +1,69 @@ + +#include "Tests.hpp" +#include "RingBufferByte.hpp" + +SUITE(RingBufferBytes) { + TEST(Simple) { + + using Data = std::vector; + using DataStorage = std::vector; + + constexpr auto BUFFER_SIZE = 395; + constexpr auto MAX_MSG_SIZE = 31; + constexpr auto ITERATIONS = 100000; + + DataStorage pushed; + DataStorage popped; + + RingBufferByte buffer(BUFFER_SIZE); + + Data randomSamples(MAX_MSG_SIZE); + + for (auto idx = 0; idx < MAX_MSG_SIZE; idx++) { + randomSamples[idx] = (idx + 5 * 31) % 13; + } + + const auto PUSH = [&](RingBufferByte& buffer, DataStorage* storage) { + const Data& pushedSample = randomSamples; + + buffer.pushBack(pushedSample.data(), pushedSample.size()); + + if (storage) { + storage->push_back(pushedSample); + } + }; + + const auto POP = [](RingBufferByte& buffer, DataStorage* storage) { + const auto poppedSampleSize = buffer.frontSize(); + + Data poppedSample(poppedSampleSize); + + const auto SIZE = buffer.popFront(poppedSample.data()); + + CHECK(poppedSample.size() == SIZE); + + if (storage) { + storage->push_back(poppedSample); + } + }; + + for (auto iteration = 0; iteration < ITERATIONS; iteration++) { + PUSH(buffer, nullptr); + } + + while (!buffer.empty()) { + POP(buffer, nullptr); + } + + for (auto iteration = 0; iteration < ITERATIONS; iteration++) { + PUSH(buffer, &pushed); + POP(buffer, &popped); + } + + CHECK(pushed.size() == popped.size()); + + for (auto idx = 0; idx < popped.size(); idx++) { + CHECK(pushed[idx] == popped[idx]); + } + } +}