Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -111,5 +111,5 @@ endif ()
#
#-------------------------------------------------
if (BOOST_CAPY_BUILD_EXAMPLES)
# add_subdirectory(example)
add_subdirectory(example)
endif ()
192 changes: 126 additions & 66 deletions doc/modules/ROOT/pages/examples/buffer-composition.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ Composing buffer sequences without allocation for scatter/gather I/O.
== What You Will Learn

* Creating buffers from different sources
* Composing buffer sequences with `cat()`
* Zero-allocation scatter/gather patterns
* Using `const_buffer_pair` and `mutable_buffer_pair` for scatter/gather I/O
* Zero-allocation buffer sequence patterns

== Prerequisites

Expand All @@ -21,80 +21,112 @@ Composing buffer sequences without allocation for scatter/gather I/O.
#include <iostream>
#include <string>
#include <array>
#include <vector>

using namespace boost::capy;

void demonstrate_buffers()
void demonstrate_single_buffers()
{
// Individual buffers from various sources
std::cout << "=== Single Buffer Examples ===\n\n";

// Create buffers from various sources
std::string str = "Hello, World!";
char arr[] = "Array data";
std::vector<char> vec = {'V', 'e', 'c', 't', 'o', 'r'};

// make_buffer creates buffer views (no copies)
auto str_buf = make_buffer(str); // mutable_buffer
auto arr_buf = make_buffer(arr, sizeof(arr) - 1); // mutable_buffer - Exclude null terminator
auto vec_buf = make_buffer(vec); // mutable_buffer

std::cout << "String buffer: " << str_buf.size() << " bytes\n";
std::cout << "Array buffer: " << arr_buf.size() << " bytes\n";
std::cout << "Vector buffer: " << vec_buf.size() << " bytes\n";
}

void demonstrate_buffer_pair()
{
std::cout << "\n=== Buffer Pair (Scatter/Gather) ===\n\n";

// const_buffer_pair is std::array<const_buffer, 2>
std::string header = "Content-Type: text/plain\r\n\r\n";
std::string body = "Hello, World!";
char footer[] = "\r\n--END--";

// Create buffer views (no copies)
auto header_buf = make_buffer(header);
auto body_buf = make_buffer(body);
auto footer_buf = make_buffer(footer, sizeof(footer) - 1);

// Compose into a single sequence (no allocation!)
auto message = cat(header_buf, body_buf, footer_buf);
const_buffer_pair message = {{
make_buffer(header),
make_buffer(body)
}};

// Measure
std::cout << "Total message size: " << buffer_size(message) << " bytes\n";
// Calculate total size
std::size_t total = buffer_size(message);
std::cout << "Total message size: " << total << " bytes\n";
std::cout << "Buffer count: " << buffer_length(message) << "\n";

// Iterate (for demonstration)
// Iterate through buffers
std::cout << "\nBuffer contents:\n";
for (auto it = begin(message); it != end(message); ++it)
for (auto const& buf : message) // const_buffer const&
{
const_buffer buf = *it;
std::cout << " [" << buf.size() << " bytes]: ";
std::cout.write(static_cast<char const*>(buf.data()), buf.size());
std::cout << "\n";
}
}

// HTTP-style message assembly
struct http_message
void demonstrate_buffer_array()
{
std::string status_line = "HTTP/1.1 200 OK\r\n";
std::array<std::pair<std::string, std::string>, 2> headers = {{
{"Content-Type", "application/json"},
{"Server", "Capy/1.0"}
}};
std::cout << "\n=== Multi-Buffer Array ===\n\n";

// Use std::array for more than 2 buffers
std::string status = "HTTP/1.1 200 OK\r\n";
std::string content_type = "Content-Type: application/json\r\n";
std::string server = "Server: Capy/1.0\r\n";
std::string empty_line = "\r\n";
std::string body = R"({"status":"ok"})";

// Returns a composed buffer sequence
auto buffers() const
{
// Format headers
static constexpr char crlf[] = "\r\n";
static constexpr char sep[] = ": ";
static constexpr char empty_line[] = "\r\n";

return cat(
make_buffer(status_line),
make_buffer(headers[0].first), make_buffer(sep, 2),
make_buffer(headers[0].second), make_buffer(crlf, 2),
make_buffer(headers[1].first), make_buffer(sep, 2),
make_buffer(headers[1].second), make_buffer(crlf, 2),
make_buffer(empty_line, 2),
make_buffer(body)
);
}
};
std::array<const_buffer, 5> http_response = {{
make_buffer(status),
make_buffer(content_type),
make_buffer(server),
make_buffer(empty_line),
make_buffer(body)
}};

std::size_t total = buffer_size(http_response);
std::cout << "HTTP response size: " << total << " bytes\n";
std::cout << "Buffer count: " << buffer_length(http_response) << "\n";

// In real code with streams:
// co_await write(stream, http_response);
// This performs scatter/gather I/O - single syscall for all buffers
}

int main()
void demonstrate_mutable_buffers()
{
demonstrate_buffers();
std::cout << "\n=== Mutable Buffer Example ===\n\n";

// Mutable buffers for receiving data
char buf1[64];
char buf2[64];

std::cout << "\n--- HTTP Message ---\n";
http_message msg;
auto bufs = msg.buffers();
std::cout << "Message size: " << buffer_size(bufs) << " bytes\n";
mutable_buffer_pair recv_buffers = {{
mutable_buffer(buf1, sizeof(buf1)),
mutable_buffer(buf2, sizeof(buf2))
}};

std::cout << "Prepared " << buffer_length(recv_buffers)
<< " buffers with " << buffer_size(recv_buffers)
<< " bytes total capacity\n";

// In real code: co_await write(stream, msg.buffers());
// Single system call writes all buffers (scatter/gather)
// In real code:
// auto [ec, n] = co_await stream.read_some(recv_buffers);
}

int main()
{
demonstrate_single_buffers();
demonstrate_buffer_pair();
demonstrate_buffer_array();
demonstrate_mutable_buffers();

return 0;
}
Expand All @@ -114,30 +146,45 @@ target_link_libraries(buffer_composition PRIVATE capy)

[source,cpp]
----
auto header_buf = make_buffer(header);
auto body_buf = make_buffer(body);
auto footer_buf = make_buffer(footer, sizeof(footer) - 1);
auto str_buf = make_buffer(str); // mutable_buffer
auto arr_buf = make_buffer(arr, sizeof(arr) - 1); // mutable_buffer
----

`make_buffer` creates buffer views from various sources. No data is copied—the buffers reference the original storage.

=== Zero-Allocation Composition
=== Buffer Pairs

[source,cpp]
----
auto message = cat(header_buf, body_buf, footer_buf);
const_buffer_pair message = {{
make_buffer(header),
make_buffer(body)
}};
----

`cat()` composes buffer sequences without allocation. The returned object stores references and iterates through all buffers in sequence.
`const_buffer_pair` is `std::array<const_buffer, 2>` — a fixed-size buffer sequence for scatter/gather I/O. Similarly, `mutable_buffer_pair` holds two mutable buffers.

=== Multi-Buffer Arrays

[source,cpp]
----
std::array<const_buffer, 5> http_response = {{
make_buffer(status),
make_buffer(content_type),
// ...
}};
----

For more than two buffers, use `std::array` directly. Buffer sequences support `buffer_size()` and `buffer_length()` for querying total bytes and buffer count.

=== Scatter/Gather I/O

[source,cpp]
----
co_await write(stream, msg.buffers());
co_await write(stream, http_response);
----

When you write a composed buffer sequence, the OS receives all buffers in a single system call. This is *scatter/gather I/O*:
When you write a buffer sequence, the OS receives all buffers in a single system call. This is *scatter/gather I/O*:

* No intermediate buffer allocation
* No copying data together
Expand All @@ -146,18 +193,31 @@ When you write a composed buffer sequence, the OS receives all buffers in a sing
== Output

----
Total message size: 55 bytes
Buffer count: 3
=== Single Buffer Examples ===

String buffer: 13 bytes
Array buffer: 10 bytes
Vector buffer: 6 bytes

=== Buffer Pair (Scatter/Gather) ===

Total message size: 41 bytes
Buffer count: 2

Buffer contents:
[27 bytes]: Content-Type: text/plain
[28 bytes]: Content-Type: text/plain


[13 bytes]: Hello, World!
[9 bytes]:
--END--

--- HTTP Message ---
Message size: 87 bytes
=== Multi-Buffer Array ===

HTTP response size: 84 bytes
Buffer count: 5

=== Mutable Buffer Example ===

Prepared 2 buffers with 128 bytes total capacity
----

== Exercises
Expand Down
43 changes: 24 additions & 19 deletions doc/modules/ROOT/pages/examples/custom-dynamic-buffer.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,12 @@ Implementing the DynamicBuffer concept for a custom allocation strategy.
#include <boost/capy.hpp>
#include <boost/capy/test/run_blocking.hpp>
#include <boost/capy/test/stream.hpp>
#include <boost/capy/test/fuse.hpp>
#include <boost/capy/cond.hpp>
#include <iostream>
#include <vector>
#include <cassert>
#include <cstring>

using namespace boost::capy;

Expand Down Expand Up @@ -106,8 +109,9 @@ public:
// Consumer: mark bytes as processed
void consume(std::size_t n)
{
total_consumed_ += n;
read_pos_ += n;
std::size_t actual = std::min(n, size()); // std::size_t
total_consumed_ += actual;
read_pos_ += actual;

if (read_pos_ == write_pos_)
{
Expand Down Expand Up @@ -143,18 +147,19 @@ private:
};

// Demonstrate using the custom buffer
task<> read_into_tracked_buffer(any_stream& stream, tracked_buffer& buffer)
task<> read_into_tracked_buffer(test::stream& stream, tracked_buffer& buffer)
{
// Read data in chunks
while (true)
{
auto space = buffer.prepare(256);
auto space = buffer.prepare(256); // mutable_buffer
// ec: std::error_code, n: std::size_t
auto [ec, n] = co_await stream.read_some(space);

if (ec == cond::eof)
break;

if (ec.failed())
if (ec)
throw std::system_error(ec);

buffer.commit(n);
Expand All @@ -169,19 +174,19 @@ void demo_tracked_buffer()
std::cout << "=== Tracked Buffer Demo ===\n\n";

// Setup mock stream with test data
test::stream mock;
test::fuse f;
test::stream mock(f);
mock.provide("Hello, ");
mock.provide("World! ");
mock.provide("This is a test of the custom buffer.\n");
mock.provide_eof();
// Stream returns eof when data is exhausted

any_stream stream{mock};
tracked_buffer buffer;

test::run_blocking(read_into_tracked_buffer(stream, buffer));
test::run_blocking()(read_into_tracked_buffer(mock, buffer));

std::cout << "\nFinal buffer contents: ";
auto data = buffer.data();
auto data = buffer.data(); // const_buffer
std::cout.write(static_cast<char const*>(data.data()), data.size());
std::cout << "\n\n";

Expand Down Expand Up @@ -235,16 +240,17 @@ std::size_t capacity() const; // Currently allocated
[source,cpp]
----
// 1. Producer prepares space
auto space = buffer.prepare(256);
auto space = buffer.prepare(256); // mutable_buffer

// 2. Data is written into space
// ec: std::error_code, n: std::size_t
auto [ec, n] = co_await stream.read_some(space);

// 3. Producer commits written bytes
buffer.commit(n);

// 4. Consumer reads data
auto data = buffer.data();
auto data = buffer.data(); // const_buffer
process(data);

// 5. Consumer marks bytes as processed
Expand All @@ -265,26 +271,25 @@ The `tracked_buffer` implementation:
----
=== Tracked Buffer Demo ===

Read 7 bytes, buffer size now: 7
Read 7 bytes, buffer size now: 14
Read 37 bytes, buffer size now: 51
Read 51 bytes, buffer size now: 51

Final buffer contents: Hello, World! This is a test of the custom buffer.


Buffer statistics:
Total prepared: 768 bytes
Total prepared: 512 bytes
Total committed: 51 bytes
Total consumed: 0 bytes
Current size: 51 bytes
Capacity: 256 bytes
Capacity: 1024 bytes

Consuming 7 bytes...
Buffer statistics:
Total prepared: 768 bytes
Total prepared: 512 bytes
Total committed: 51 bytes
Total consumed: 7 bytes
Current size: 44 bytes
Capacity: 249 bytes
Capacity: 1017 bytes
----

== Exercises
Expand Down
Loading
Loading