/** @file buffer.hpp **/ #pragma once #include "span.hpp" #include #include #include #include namespace xo { namespace scm { /** * @class buffer buffer.hpp * * @brief Container for a (possibly owned) FIFO queue of chars * * @tparam CharT. buffer element type. * * @code * .buf * * +------------------------------------------+ * | | ... | | X| ... | X| | ... | | * +------------------------------------------+ * ^ ^ ^ ^ * 0 .lo .hi .buf_z * * <-contents-><----avail-----> * @endcode * * Buffer does not support wrapped content: * content that has not been consumed always occupies contiguous memory. * * Example: * @code * // 1. * buffer buf(64*1024); * buf.empty() -> true * buf.buf_z() -> 65536 * buf.lo_pos() -> 0 * buf.hi_pos() -> 65536 * buf.contents() -> empty span * buf.avail() -> span entire buffer memory * * // write to (a prefix of) buf.avail() * ::strncpy(buf.buf(), "hello, world\n", 13); * buf.produce(span_type(buf.buf(), buf.buf() + 13)); * * buf.lo_pos() -> 0 * buf.hi_pos() -> 13 * buf.contents() -> "hello, world\n"; * * * // examine stored content (does not change buffer state) * auto span = buf.contents(); * cerr << string_view(span.lo(), span.hi()); // "hello, world\n" * * // consume (a prefix of) stored content * buf.consume(span.prefix(7); * * buf.lo_pos() -> 7 * buf.hi_pos() -> 13 * buf.contents() -> "world\n" * * // consuming all remain content resets to original state * buf.consume(buf.contents()); * * buf.empty() -> true * buf.hi_pos() -> 0 // not 13! * * // 2. * buffer buf; * buf.empty() -> true * buf.buf_z() -> 0 * buf.lo_pos() -> 0 * buf.hi_pos() -> 0 * buf.contents() -> empty span * buf.avail() -> empty span * * // allocate memory separately from ctor * buf.alloc(64*1024); * @endcode **/ template class buffer { public: /** @brief typealias for span of CharT **/ using span_type = span; /** @brief typealias for buffer size (counts CharT's, not bytes) **/ using size_type = std::uint64_t; public: /** @brief create empty buffer. Does not allocate any storage; @see alloc **/ buffer() = default; /** @brief create empty buffer, and possibly allocate storage. @param buf_z Buffer size. allocate storage (owned by this buffer) if >0. @param align_z Align to this value, e.g. 8 to align storage on an 8-byte boundary **/ buffer(size_type buf_z, size_type align_z = sizeof(char)) : is_owner_{true}, buf_{buf_z ? (new (std::align_val_t(align_z)) CharT [buf_z]) : nullptr}, buf_z_{buf_z}, lo_pos_{0}, hi_pos_{0} {} /** @brief buffer is not copyable **/ buffer(buffer const & x) = delete; /** @brief destructor. Release storage if owned **/ ~buffer() { this->reset(); } /** @name Access methods **/ ///@{ /** @brief start of buffer memory **/ CharT * buf() const { return buf_; } /** @brief buffer size (number of characters) **/ size_type buf_z() const { return buf_z_; } /** @brief current start position within buffer **/ size_type lo_pos() const { return lo_pos_; } /** @brief current end position within buffer **/ size_type hi_pos() const { return hi_pos_; } ///@} /** @brief readonly access to a single buffer element. Relative to start of buffer (ignores current consume position) **/ CharT const & operator[](size_type i) const { return buf_[i]; } /** @brief return span for current buffer contents **/ span_type contents() const { return span_type(buf_ + lo_pos_, buf_ + hi_pos_); } /** @brief returns span for writable buffer contents (unused prefix following produce position **/ span_type avail() const { return span_type(buf_ + hi_pos_, buf_ + buf_z_); } /** @brief @c true iff buffer is empty **/ bool empty() const { return lo_pos_ == hi_pos_; } /** @brief update buffer produce position, after (independently) writing contents of span to it @pre left endpoint of @p span equals buffer produce position (@c .hi_pos) @pre right endpoint of @p span within bounds of buffer memory range @post right endpoint of @p span equals buffer produce position. **/ void produce(span_type const & span) { assert(span.lo() == buf_ + hi_pos_); hi_pos_ += span.size(); } /** @brief update buffer consume position, when done with contents of span @pre left endpoint of @p span equals buffer consume position (@c .lo_pos) @pre right endpoint of @p span within bounds of buffer memory range @post Either buffer is empty, with @c .lo_pos = @c .hi_pos = @c 0. buffer is non-empty, right endpoint of @p span equals new buffer consume position. **/ void consume(span_type const & span) { if (span.size()) { assert(span.lo() == buf_ + lo_pos_); lo_pos_ += span.size(); } else { /* since .consume() that arrives at empty contents also resets .lo_pos .hi_pos, * we don't want to blow up when called with an empty span -- argument * may represent some pre-reset location in buffer */ } if (lo_pos_ == hi_pos_) { lo_pos_ = 0; hi_pos_ = 0; } } /** @brief allocate buffer with desired amount of memory @param buf_z desired buffer size @param align_z alignment; buffer memory will be aligned on this byte-boundary. **/ void alloc(size_type buf_z, size_type align_z = sizeof(char)) { /* properly reset (+ discard) any existing state */ this->reset(); is_owner_ = true; if (buf_z) buf_ = new (std::align_val_t(align_z)) CharT [buf_z]; buf_z_ = buf_z; lo_pos_ = 0; hi_pos_ = 0; } /** @brief attach buffer to (unowned) range of @p buf_z bytes starting at @p buf[0] Buffer is not responsible for managing storage. @post 1. buffer is empty @post 2. buffer read position = buffer write position = 0 **/ void setbuf(CharT * buf, size_type buf_z) { /* properly reset (+ discard) any existing state */ this->reset(); is_owner_ = false; lo_pos_ = 0; hi_pos_ = 0; buf_ = buf; buf_z_ = buf_z; } /** @brief revert buffer to empty state and possibly zero it @param zero_buffer_flag Zero buffer contents iff this is true @post 1. buffer is empty @post 2. buffer read position = buffer write position = 0 **/ void clear2empty(bool zero_buffer_flag) { if (buf_ && zero_buffer_flag) explicit_bzero(buf_, buf_z_ * sizeof(CharT)); lo_pos_ = 0; hi_pos_ = 0; } /** @brief swap representation with another buffer instance. **/ void swap (buffer & x) { std::swap(is_owner_, x.is_owner_); std::swap(buf_, x.buf_); std::swap(buf_z_, x.buf_z_); std::swap(lo_pos_, x.lo_pos_); std::swap(hi_pos_, x.hi_pos_); } /** @brief reset buffer to an empty state and recover owned storage **/ void reset() { if (is_owner_ && buf_) delete [] buf_; is_owner_ = false; buf_ = nullptr; buf_z_ = 0; lo_pos_ = 0; hi_pos_ = 0; } /** @brief move-assignment operator. @param x right-hand-side to move from. @post @p x is in a valid, empty, **/ buffer & operator= (buffer && x) { is_owner_ = x.is_owner_; buf_ = x.buf_; buf_z_ = x.buf_z_; lo_pos_ = x.lo_pos_; hi_pos_ = x.hi_pos_; x.is_owner_ = false; x.lo_pos_ = 0; x.hi_pos_ = 0; x.buf_ = nullptr; x.buf_z_ = 0; return *this; } /** @brief buffer is not assignable */ buffer & operator= (buffer & x) = delete; private: /** @brief true iff buffer is responsible for freeing storage at @c buf_ **/ bool is_owner_ = false; /** @brief buffer contents. buffer memory comprises @c buf_[0] to @c buf_[buf_z_] **/ CharT * buf_ = nullptr; /** @brief buffer size (in units of CharT) **/ size_type buf_z_ = 0; /** @brief buffer read (consume) position @invariant 0 <= lo_pos_ <= hi_pos_ < buf_z_ **/ size_type lo_pos_ = 0; /** @brief buffer write (produce) position @invariant 0 <= hi_pos_ < hi_pos_ < buf_z_ **/ size_type hi_pos_ = 0; }; /** @brief Overload for @c swap, so that @c buffer swappable **/ template inline void swap(buffer & lhs, buffer & rhs) { lhs.swap(rhs); } } /*namespace scm*/ } /*namespace xo*/ /* end buffer.hpp */