diff options
-rw-r--r-- | Makefile | 23 | ||||
-rw-r--r-- | fastbuffer.hpp | 157 | ||||
-rw-r--r-- | main.cpp | 94 |
3 files changed, 274 insertions, 0 deletions
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4222b3d --- /dev/null +++ b/Makefile @@ -0,0 +1,23 @@ +CXX = g++ +CXXFLAGS = -Wall -Wextra + +ifneq ($(ENABLE_SANITIZER),) +CXXFLAGS += -fsanitize=address -fsanitize=leak -fsanitize=undefined +else +ifneq ($(ENABLE_THREAD_SANITIZER),) +CXXFLAGS += -fsanitize=thread -fsanitize=undefined +endif +endif +ifneq ($(DEBUG),) +CXXFLAGS += -g3 +endif + +all: test + +test: main.cpp fastbuffer.hpp + $(CXX) $(CXXFLAGS) main.cpp -o $@ + +clean: + rm -f test + +.PHONY: clean diff --git a/fastbuffer.hpp b/fastbuffer.hpp new file mode 100644 index 0000000..d6ac8d5 --- /dev/null +++ b/fastbuffer.hpp @@ -0,0 +1,157 @@ +#ifndef FASTBUFFER_H +#define FASTBUFFER_H 1 + +#include <boost/asio/buffer.hpp> +#include <boost/format.hpp> +#include <boost/noncopyable.hpp> +#include <exception> +#include <string> + +class BufferException : public std::exception { +public: + explicit BufferException(const char *what) : m_what(what) {} + explicit BufferException(const std::string &what) : m_what(what) {} + virtual ~BufferException() noexcept {} + virtual const char *what() const noexcept { return m_what.c_str(); } + +private: + std::string m_what; +}; + +class BufferBase { +public: + explicit BufferBase(std::size_t size) + : m_bufferOffset{0}, m_bufferUsed{0}, m_bufferSize{size} { + m_buffer = new std::uint8_t[size]; + } + BufferBase(BufferBase &&moveable) { + m_bufferOffset = std::move(moveable.m_bufferOffset); + m_bufferUsed = std::move(moveable.m_bufferUsed); + m_bufferSize = std::move(moveable.m_bufferSize); + m_buffer = std::move(moveable.m_buffer); + moveable.m_buffer = nullptr; + moveable.m_bufferOffset = moveable.m_bufferUsed = moveable.m_bufferSize = 0; + } + ~BufferBase() { delete[] m_buffer; } + void operator+=(std::size_t commit_size) { + checkFreeSpace(commit_size); + m_bufferUsed += commit_size; + } + void operator+=(const std::initializer_list<uint8_t> &to_add) { + checkFreeSpace(to_add.size()); + std::copy(to_add.begin(), to_add.end(), &m_buffer[m_bufferUsed]); + m_bufferUsed += to_add.size(); + } + void operator-=(std::size_t consume_size) { + const auto unconsumed_space = unconsumed(); + checkConsumableSpace(consume_size); + if (consume_size == unconsumed_space) { + m_bufferUsed = m_bufferOffset = 0; + return; + } + m_bufferOffset += consume_size; + } + auto operator+() { + return boost::asio::buffer(&m_buffer[m_bufferUsed], unused()); + } + auto operator-() { + return boost::asio::buffer(&m_buffer[m_bufferOffset], m_bufferUsed); + } + auto operator[](std::size_t index) const { return m_buffer[index]; } + const auto *operator()(std::size_t index) const { return &m_buffer[index]; } + auto *operator()() { return &m_buffer[m_bufferUsed]; } + auto size() const { return m_bufferUsed; } + auto capacity() const { return m_bufferSize; } + std::size_t unused() const { return m_bufferSize - m_bufferUsed; } + std::size_t unconsumed() const { return m_bufferUsed - m_bufferOffset; } + void checkFreeSpace(std::size_t commit_size) const { + const auto free_space = unused(); + if (commit_size > free_space) + throw BufferException( + (boost::format( + "Buffer overflow: %1% bytes free, %1% bytes required") % + free_space % commit_size) + .str()); + } + void checkConsumableSpace(std::size_t consume_size) const { + const auto unconsumed_space = unconsumed(); + if (consume_size > unconsumed_space) + throw BufferException( + (boost::format( + "Buffer underflow: %1% bytes used, %1% bytes consumed") % + unconsumed_space % consume_size) + .str()); + }; + auto getHealth() const { + return (static_cast<float>(m_bufferUsed) / + static_cast<float>(m_bufferSize)); + } + auto getConsumeHealth() const { + return (static_cast<float>(unconsumed()) / + static_cast<float>(m_bufferSize)); + } + +private: + std::size_t m_bufferOffset; + std::size_t m_bufferUsed; + std::size_t m_bufferSize; + std::uint8_t *m_buffer; +}; + +using ContiguousStreamBuffer = BufferBase; + +class ContiguousPacketQueue : public boost::noncopyable { +public: + struct Element { + std::size_t size; + }; + explicit ContiguousPacketQueue(std::size_t max_packets, + std::size_t max_queue_size) + : m_buffer(max_queue_size), m_packetsOffset{0}, m_packetsUsed{0}, + m_packetsSize{max_packets} { + m_packets = new Element[max_packets]; + } + ContiguousPacketQueue(ContiguousPacketQueue &&moveable) + : m_buffer(std::move(moveable.m_buffer)) { + m_packetsOffset = std::move(moveable.m_packetsOffset); + m_packetsUsed = std::move(moveable.m_packetsUsed); + m_packetsSize = std::move(moveable.m_packetsSize); + m_packets = std::move(moveable.m_packets); + moveable.m_packets = nullptr; + moveable.m_packetsOffset = moveable.m_packetsUsed = moveable.m_packetsSize = + 0; + } + ~ContiguousPacketQueue() { delete[] m_packets; } + void operator+=(std::size_t commit_size) { + m_packets[m_packetsUsed++].size = commit_size; + m_buffer += commit_size; + } + void operator+=(const std::initializer_list<uint8_t> &to_add) { + m_packets[m_packetsUsed++].size = to_add.size(); + m_buffer += to_add; + } + void operator--() { + const auto consume_size = m_packets[m_packetsOffset].size; + m_buffer -= consume_size; + if (++m_packetsOffset == m_packetsUsed) { + m_packetsOffset = m_packetsUsed = 0; + return; + } + } + auto operator+() { return +m_buffer; } + auto operator-() { return -m_buffer; } + auto size() const { return m_packetsUsed; } + auto capacity() const { return m_packetsSize; } + std::size_t unused() const { return m_packetsSize - m_packetsUsed; } + std::size_t unconsumed() const { return m_packetsUsed - m_packetsOffset; } + const auto *GetBuffer() const { return &m_buffer; } + +private: + BufferBase m_buffer; + std::size_t m_packetsOffset; + std::size_t m_packetsUsed; + std::size_t m_packetsSize; + Element *m_packets; +}; + +#endif diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..0d2e632 --- /dev/null +++ b/main.cpp @@ -0,0 +1,94 @@ +#include "fastbuffer.hpp" + +#include <iostream> + +#define LEN(x) (sizeof(x) / sizeof(x[0])) +#define STRLEN(x) (LEN(x) - 1) +#define TO_STR(x) ("" #x) +#define TEST_ASSERT_LINE(file, line, x) \ + if ((x) == 0) \ + throw std::runtime_error((boost::format("Test failed at %1%:%2%: %3%") % \ + std::string(file) % line % \ + std::string(TO_STR(x))) \ + .str()); +#define TEST_ASSERT(x) TEST_ASSERT_LINE(__FILE__, __LINE__, (x)) + +using std::cout; + +auto selftest() { + BufferBase base(16); + + TEST_ASSERT(base.capacity() == 16); + TEST_ASSERT(base.unused() == 16); + base += 3; + base += {0xFF, 0xFF, 0xFF}; + TEST_ASSERT(base.size() == 6); + TEST_ASSERT(base.unused() == 10); + static constexpr uint8_t test_buffer[] = "\xFF\xFF\xFF"; + TEST_ASSERT(memcmp(test_buffer, base(3), STRLEN(test_buffer)) == 0); + base -= 2; + TEST_ASSERT(base.unconsumed() == 4); + TEST_ASSERT(base.size() - 3 == STRLEN(test_buffer)); + TEST_ASSERT(base.unused() == 10); + base -= 2; + TEST_ASSERT(base.unconsumed() == 2); + TEST_ASSERT(base.size() - 3 == STRLEN(test_buffer)); + base -= 2; + TEST_ASSERT(base.unconsumed() == 0); + TEST_ASSERT(base.size() == 0); + TEST_ASSERT(base.unused() == 16); + base += 4; + TEST_ASSERT(base.unconsumed() == 4); + TEST_ASSERT(base.size() == 4); + TEST_ASSERT(base.unused() == 12); + TEST_ASSERT(base.size() + base.unused() == base.capacity()); + TEST_ASSERT(base.getHealth() == 0.25f); + TEST_ASSERT(base.getConsumeHealth() == 0.25f); + base -= 3; + TEST_ASSERT(base.getHealth() == 0.25f); + TEST_ASSERT(base.getConsumeHealth() == 0.0625f); + auto moved_base = BufferBase(std::move(base)); + TEST_ASSERT(base.capacity() == base.size() && base.unused() == base.size() && + base.unconsumed() == base.size() && base.size() == 0); + + ContiguousPacketQueue queue(8, 64); + queue += 4; + queue += 8; + queue += {0xFF, 0xFF, 0xFF}; + queue += 16; + auto buf = queue.GetBuffer(); + TEST_ASSERT(buf->size() == 31); + TEST_ASSERT(buf->getHealth() == buf->getConsumeHealth() && + buf->getHealth() == 0.484375f); + TEST_ASSERT(queue.size() == 4); + TEST_ASSERT(queue.capacity() == 8); + TEST_ASSERT(queue.unused() == 4); + TEST_ASSERT(queue.unconsumed() == 4); + queue += 3; + TEST_ASSERT(queue.unused() == 3); + TEST_ASSERT(queue.unconsumed() == 5); + TEST_ASSERT(queue.size() + queue.unused() == queue.capacity()); + --queue; + TEST_ASSERT(queue.size() == 5); + TEST_ASSERT(queue.unconsumed() == 4); + TEST_ASSERT(queue.size() + queue.unused() == queue.capacity()); + --queue; + --queue; + --queue; + --queue; + TEST_ASSERT(queue.size() == 0); + TEST_ASSERT(queue.unconsumed() == 0); + TEST_ASSERT(queue.size() + queue.unused() == queue.capacity()); + queue += {0xDE, 0xAD, 0xC0, 0xDE}; + auto moved_queue = ContiguousPacketQueue(std::move(queue)); + TEST_ASSERT(queue.capacity() == queue.size() && + queue.unused() == queue.size() && + queue.unconsumed() == queue.size() && queue.size() == 0); +} + +int main(void) { + cout << "Selftest..\n"; + selftest(); + + return 0; +} |