summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile23
-rw-r--r--fastbuffer.hpp157
-rw-r--r--main.cpp94
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;
+}