BufReader

Introduction

xpp::io::BufReader<R> wraps any AsyncReader with an internal 8KB buffer. Small reads copy from the buffer with zero I/O overhead; large reads (≥ 8KB) bypass the buffer entirely. Reduces per-call Promise overhead for byte-at-a-time parsing over TCP.

Satisfies the AsyncReader concept — composable with io::read_all, io::copy, or nested in another BufReader. Takes ownership of the inner reader via move semantics. Works with .await(), co_await (C++20), or .then() chains (C++11).

Example — .await()

#include <xpp/io/buf_reader.h>

xpp::EventLoop loop;
xpp::WaitScope scope(loop);

auto conn = xpp::net::TcpStream::connect("127.0.0.1:9090").await().unwrap();
xpp::io::BufReader<xpp::net::TcpStream> buf(std::move(conn));

// Small reads: copy from buffer, zero per-call Promise overhead
char c;
buf.read(&c, 1).await();  // fills buffer from TCP
buf.read(&c, 1).await();  // copies from buffer

Example — co_await (C++20)

auto conn = xpp::net::TcpStream::connect("127.0.0.1:9090").await().unwrap();
xpp::io::BufReader<xpp::net::TcpStream> buf(std::move(conn));

char c;
co_await buf.read(&c, 1);  // fills buffer from TCP
co_await buf.read(&c, 1);  // copies from buffer

How it works

read(buf, len)
    │
    ├── buffer has data? → memcpy from m_buf, advance m_pos
    │
    ├── len ≥ 8KB? → drain buffer + inner.read(buf, len) suspension
    │
    └── buffer empty → inner.read(m_buf, 8KB) suspension → memcpy to output

Internal state:
  m_buf[8KB]    stack-allocated buffer
  m_pos         next unread byte in buffer
  m_filled      total bytes in buffer (0 after refill)

The first read() call fills the buffer from the inner reader. Subsequent small reads drain it with zero additional I/O — memcpy only. When the buffer is exhausted, it refills from the inner reader.

Large reads (≥ 8KB) skip the buffer: any pending buffered data is drained first, then the remaining bytes are read directly from the inner reader. This avoids a double-copy for bulk transfers.

API Reference

MethodReturnsDescription
BufReader(R reader)Take ownership of reader. Move-only
read(buf, len)Promise<ssize_t>Buffered read. Resolves to bytes read (0 = EOF)
inner()R&Access the inner reader (e.g., for close())

Usage Examples

Byte-at-a-time parsing — .await()

xpp::io::BufReader<xpp::net::TcpStream> buf(std::move(conn));

// Read 4-byte header
char header[4];
buf.read(header, 4).await();

// Read payload length from header
uint32_t len = ntohl(*reinterpret_cast<uint32_t *>(header));

// Read payload (may be large — bypasses buffer)
auto payload = std::make_shared<std::vector<char>>(len);
buf.read(payload->data(), len).await();

Byte-at-a-time parsing — co_await (C++20)

xpp::Promise<void> parse_frame(xpp::net::TcpStream conn) {
    xpp::io::BufReader<xpp::net::TcpStream> buf(std::move(conn));
    char header[4];
    co_await buf.read(header, 4);
    uint32_t len = ntohl(*reinterpret_cast<uint32_t *>(header));
    auto payload = std::make_shared<std::vector<char>>(len);
    co_await buf.read(payload->data(), len);
}

Compose with io::read_all — .await()

xpp::io::BufReader<xpp::net::TcpStream> buf(std::move(conn));
std::string method;
char c;
while (true) {
    buf.read(&c, 1).await();
    if (c == ' ') break;
    method += c;
}
auto body = xpp::io::read_all(buf).await();

Compose with io::read_all — co_await (C++20)

xpp::Promise<void> read_request(xpp::net::TcpStream conn) {
    xpp::io::BufReader<xpp::net::TcpStream> buf(std::move(conn));
    std::string method; char c;
    while (true) {
        co_await buf.read(&c, 1);
        if (c == ' ') break; method += c;
    }
    auto body = co_await xpp::io::read_all(buf);
}

Large bypass

Reads ≥ 8KB go directly to the inner reader. No double-buffering:

// .await()
xpp::io::BufReader<xpp::net::TcpStream> buf(std::move(conn));
char data[65536];  // 64KB — larger than buffer
buf.read(data, sizeof(data)).await();  // bypasses buffer entirely

// co_await (C++20)
xpp::Promise<void> download_chunk(xpp::net::TcpStream conn) {
    xpp::io::BufReader<xpp::net::TcpStream> buf(std::move(conn));
    char data[65536];
    co_await buf.read(data, sizeof(data));
}