summaryrefslogtreecommitdiff
path: root/src/buffer.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/buffer.cc')
-rw-r--r--src/buffer.cc213
1 files changed, 213 insertions, 0 deletions
diff --git a/src/buffer.cc b/src/buffer.cc
new file mode 100644
index 0000000..18913a5
--- /dev/null
+++ b/src/buffer.cc
@@ -0,0 +1,213 @@
+#include "buffer.hh"
+
+#include <algorithm>
+#include <cassert>
+#include <cstring>
+#include <memory>
+#include <utility>
+
+namespace {
+
+class FixedBuffer : public Buffer {
+ public:
+ explicit FixedBuffer(size_t size) : size_(size) {}
+
+ void const* rptr(size_t& avail, size_t need) override {
+ if (rptr_ < wptr_) {
+ avail = wptr_ - rptr_;
+ } else if (rptr_ == wptr_ && !full_) {
+ avail = 0;
+ } else {
+ avail = (data_.get() + size_) - rptr_;
+ if (avail < need && rptr_ > data_.get()) {
+ rotate();
+ return rptr(avail, need);
+ }
+ }
+ return rptr_;
+ }
+
+ void consume(size_t size) override {
+ if (size == 0)
+ return;
+ if (rptr_ < wptr_) {
+ assert(std::cmp_greater_equal(wptr_ - rptr_, size));
+ rptr_ += size;
+ if (rptr_ == wptr_)
+ reset();
+ } else {
+ assert(rptr_ != wptr_ || full_);
+ assert(std::cmp_greater_equal((data_.get() + size_) - rptr_, size));
+ rptr_ += size;
+ if (rptr_ == data_.get() + size_) {
+ rptr_ = data_.get();
+ if (rptr_ == wptr_)
+ reset();
+ }
+ }
+ }
+
+ void* wptr(size_t& avail, size_t need) override {
+ if (wptr_ == nullptr) {
+ data_ = std::make_unique_for_overwrite<char[]>(size_);
+ rptr_ = wptr_ = data_.get();
+ }
+
+ if (wptr_ < rptr_) {
+ avail = rptr_ - wptr_;
+ } else if (rptr_ == wptr_ && full_) {
+ avail = 0;
+ } else {
+ avail = (data_.get() + size_) - wptr_;
+ if (avail < need && rptr_ > data_.get()) {
+ rotate();
+ return wptr(avail, need);
+ }
+ }
+ return wptr_;
+ }
+
+ void commit(size_t size) override {
+ if (size == 0)
+ return;
+ if (wptr_ < rptr_) {
+ assert(std::cmp_greater_equal(rptr_ - wptr_, size));
+ wptr_ += size;
+ if (wptr_ == rptr_) {
+ full_ = true;
+ }
+ } else {
+ assert(rptr_ != wptr_ || !full_);
+ assert(std::cmp_greater_equal((data_.get() + size_) - wptr_, size));
+ wptr_ += size;
+ if (wptr_ == data_.get() + size_) {
+ wptr_ = data_.get();
+ if (wptr_ == rptr_)
+ full_ = true;
+ }
+ }
+ }
+
+ [[nodiscard]] bool full() const override { return rptr_ == wptr_ && full_; }
+
+ [[nodiscard]] bool empty() const override { return rptr_ == wptr_ && !full_; }
+
+ private:
+ void reset() {
+ rptr_ = wptr_ = data_.get();
+ full_ = false;
+ }
+
+ void rotate() {
+ if (rptr_ < wptr_) {
+ size_t size = wptr_ - rptr_;
+ memmove(data_.get(), rptr_, size);
+ wptr_ = data_.get() + size;
+ } else {
+ size_t to_move = (data_.get() + size_) - rptr_;
+ if (wptr_ + to_move > rptr_) {
+ auto tmp = std::make_unique_for_overwrite<char[]>(to_move);
+ memcpy(tmp.get(), rptr_, to_move);
+ memmove(data_.get() + to_move, data_.get(), wptr_ - data_.get());
+ memcpy(data_.get(), tmp.get(), to_move);
+ } else {
+ memmove(data_.get() + to_move, data_.get(), wptr_ - data_.get());
+ memcpy(data_.get(), rptr_, to_move);
+ }
+ wptr_ += to_move;
+ }
+ rptr_ = data_.get();
+ }
+
+ size_t const size_;
+ std::unique_ptr<char[]> data_;
+ char* rptr_{nullptr};
+ char* wptr_{nullptr};
+ bool full_{false};
+};
+
+class DynamicBuffer : public Buffer {
+ public:
+ DynamicBuffer(size_t start_size, size_t max_size)
+ : start_size_(start_size), max_size_(max_size) {}
+
+ void const* rptr(size_t& avail, size_t /* need */) override {
+ avail = wptr_ - rptr_;
+ return rptr_;
+ }
+
+ void consume(size_t size) override {
+ assert(std::cmp_greater_equal(wptr_ - rptr_, size));
+ rptr_ += size;
+ if (rptr_ == wptr_) {
+ reset();
+ }
+ }
+
+ void* wptr(size_t& avail, size_t need) override {
+ avail = end_ - wptr_;
+ if (avail < need) {
+ if (end_ == nullptr) {
+ size_t size = std::min(max_size_, std::max(need, start_size_));
+ data_ = std::make_unique_for_overwrite<char[]>(size);
+ end_ = data_.get() + size;
+ rptr_ = wptr_ = data_.get();
+ avail = end_ - wptr_;
+ } else if (std::cmp_greater_equal(rptr_ - data_.get(), need - avail)) {
+ memmove(data_.get(), rptr_, wptr_ - rptr_);
+ wptr_ = data_.get() + (wptr_ - rptr_);
+ rptr_ = data_.get();
+ avail = end_ - wptr_;
+ } else if (std::cmp_less(end_ - data_.get(), max_size_)) {
+ size_t current_size = end_ - data_.get();
+ size_t new_size = std::min(
+ max_size_, current_size + std::max(need - avail, current_size));
+ auto tmp = std::make_unique_for_overwrite<char[]>(new_size);
+ memcpy(tmp.get(), rptr_, wptr_ - rptr_);
+ end_ = tmp.get() + new_size;
+ wptr_ = tmp.get() + (wptr_ - rptr_);
+ rptr_ = tmp.get();
+ data_ = std::move(tmp);
+ avail = end_ - wptr_;
+ }
+ }
+ return wptr_;
+ }
+
+ void commit(size_t size) override {
+ assert(std::cmp_greater_equal(end_ - wptr_, size));
+ wptr_ += size;
+ }
+
+ [[nodiscard]] bool full() const override {
+ return rptr_ == data_.get() && wptr_ == end_ &&
+ std::cmp_equal(end_ - data_.get(), max_size_);
+ }
+
+ [[nodiscard]] bool empty() const override { return rptr_ == wptr_; }
+
+ private:
+ void reset() {
+ if (std::cmp_greater(end_ - data_.get(), start_size_)) {
+ data_ = std::make_unique_for_overwrite<char[]>(start_size_);
+ }
+ rptr_ = wptr_ = data_.get();
+ }
+
+ size_t const start_size_;
+ size_t const max_size_;
+ std::unique_ptr<char[]> data_;
+ char* end_{nullptr};
+ char* rptr_{nullptr};
+ char* wptr_{nullptr};
+};
+
+} // namespace
+
+std::unique_ptr<Buffer> Buffer::fixed(size_t size) {
+ return std::make_unique<FixedBuffer>(size);
+}
+
+std::unique_ptr<Buffer> Buffer::dynamic(size_t start_size, size_t max_size) {
+ return std::make_unique<DynamicBuffer>(start_size, max_size);
+}