324 lines
11 KiB
C++
324 lines
11 KiB
C++
/** @file buffer.hpp **/
|
|
|
|
#pragma once
|
|
|
|
#include "span.hpp"
|
|
#include <utility>
|
|
#include <cstdint>
|
|
#include <cassert>
|
|
#include <new>
|
|
|
|
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<char> 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<char> 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 <typename CharT>
|
|
class buffer {
|
|
public:
|
|
/** @brief typealias for span of CharT **/
|
|
using span_type = span<CharT>;
|
|
/** @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<CharT> swappable **/
|
|
template <typename CharT>
|
|
inline void
|
|
swap(buffer<CharT> & lhs, buffer<CharT> & rhs) {
|
|
lhs.swap(rhs);
|
|
}
|
|
} /*namespace scm*/
|
|
} /*namespace xo*/
|
|
|
|
/* end buffer.hpp */
|