Buffer Algorithms
Measuring
buffer_size returns the total number of bytes across every buffer in a sequence:
auto buf1 = capy::make_buffer("hello"sv); // 5 bytes
auto buf2 = capy::make_buffer("world"sv); // 5 bytes
auto combined = std::array{buf1, buf2};
std::size_t total = capy::buffer_size(combined); // 10
buffer_length returns the number of buffers in the sequence:
auto single = capy::make_buffer("hello"sv);
capy::buffer_length(single); // 1
auto arr = std::array{buf1, buf2, buf3};
capy::buffer_length(arr); // 3
|
These two names are easy to confuse, so read them as answering different questions:
A three-buffer sequence of 100 bytes each has a |
buffer_empty reports whether a sequence carries no data, either because it holds no buffers or because every buffer has size zero:
capy::const_buffer empty_buf;
capy::buffer_empty(empty_buf); // true
capy::const_buffer non_empty("data", 4);
capy::buffer_empty(non_empty); // false
Copying
buffer_copy copies data from one buffer sequence to another and returns the number of bytes copied:
char source_data[] = "hello world";
char dest_data[20];
capy::const_buffer src(source_data, 11);
capy::mutable_buffer dst(dest_data, 20);
std::size_t copied = capy::buffer_copy(dst, src); // 11
Its at_most parameter caps the transfer, which is useful for protocols with size limits:
// Copy at most 5 bytes
std::size_t copied = capy::buffer_copy(dst, src, 5);
Source and target need not have the same shape:
// Source: 3 buffers
std::array<capy::const_buffer, 3> src = {buf1, buf2, buf3};
// Target: 2 buffers with different sizes
std::array<capy::mutable_buffer, 2> dst = {large_buf, small_buf};
// Copies across buffer boundaries as needed
std::size_t copied = capy::buffer_copy(dst, src);
The algorithm fills target buffers in order, reading from source buffers as needed. It handles a source buffer spanning several targets, and the reverse.
Partial Transfer Loops
Real transfers move some of the bytes, not all of them. Drive the loop with a consuming_buffers cursor, described on consuming_buffers.
template<capy::ReadStream Stream, capy::MutableBufferSequence Buffers>
capy::task<std::size_t> read_full(Stream& stream, Buffers buffers)
{
capy::consuming_buffers remaining(buffers);
std::size_t const total_size = capy::buffer_size(buffers);
std::size_t total = 0;
while (total < total_size)
{
auto [ec, n] = co_await stream.read_some(remaining.data());
remaining.consume(n);
total += n;
if (ec)
co_return total;
}
co_return total;
}
template<capy::WriteStream Stream, capy::ConstBufferSequence Buffers>
capy::task<std::size_t> write_full(Stream& stream, Buffers buffers)
{
capy::consuming_buffers remaining(buffers);
std::size_t const total_size = capy::buffer_size(buffers);
std::size_t total = 0;
while (total < total_size)
{
auto [ec, n] = co_await stream.write_some(remaining.data());
remaining.consume(n);
total += n;
if (ec)
co_return total;
}
co_return total;
}
Custom Buffer Types
Any memory region can be a buffer, including a memory-mapped one:
// Memory-mapped file
void* mapped = mmap(nullptr, file_size, PROT_READ, MAP_PRIVATE, fd, 0);
capy::const_buffer file_buf(mapped, file_size);
co_await capy::write(socket, file_buf); // Zero-copy network transmission
You can also define your own type satisfying the concepts:
class chunked_buffer_sequence
{
std::vector<std::vector<char>> chunks_;
public:
auto begin() const { return chunk_iterator(chunks_.begin()); }
auto end() const { return chunk_iterator(chunks_.end()); }
};
// Satisfies ConstBufferSequence—works with all algorithms
Here chunk_iterator is a small bidirectional iterator whose operator* returns each chunk as a const_buffer.