55[ ![ Header-only] ( https://img.shields.io/badge/header--only-yes-brightgreen.svg )] ( #installation )
66[ ![ Lock-free] ( https://img.shields.io/badge/concurrency-lock--free-orange.svg )] ( #how-it-works )
77
8- ` slick::dynamic_buffer ` is a header-only Boost.Asio ` DynamicBuffer_v1 ` adapter over
9- [ slick-stream-buffer] ( https://github.com/SlickQuant/slick-stream-buffer ) , the lock-free
10- single-producer multi-consumer (SPMC) byte stream buffer. It is designed as a
11- ** drop-in replacement for ` boost::beast::flat_buffer ` ** : network bytes received by
12- boost::asio / boost::beast are written directly into the SlickStreamBuffer ring, and
13- publishing a complete message to consumer threads — or other processes via shared
14- memory — requires ** zero copies** .
8+ ` slick::dynamic_buffer<BufferT> ` is a header-only, template Boost.Asio ` DynamicBuffer_v1 `
9+ adapter over any slick buffer backend. It is designed as a ** drop-in replacement for
10+ ` boost::beast::flat_buffer ` ** : network bytes received by boost::asio / boost::beast are
11+ written directly into the backend ring, and publishing a complete message to consumer
12+ threads — or other processes via shared memory — requires ** zero copies** .
13+
14+ Supported backends (any type satisfying ` slick::buffer_backend ` ):
15+
16+ | Backend | Use case |
17+ | ---------| ----------|
18+ | ` slick::stream_buffer ` | Single-producer SPMC ring; consumers call ` stream_buffer.read(cursor) ` |
19+ | ` slick::stream_buffer_multiplexer::producer_buffer ` | Fans into an MPMC shared queue so multiplexer consumers receive the record |
20+
21+ The library has ** no hard dependency** on either backend — bring your own and include the
22+ relevant header alongside ` <slick/dynamic_buffer.h> ` .
1523
1624## How it works
1725
@@ -32,31 +40,34 @@ The adapter exposes the familiar dynamic-buffer interface
3240```
3341
3442Each consumer owns an independent cursor and reads whole messages zero-copy directly
35- from the underlying SlickStreamBuffer — the consumer side is provided by
36- [ slick-stream-buffer] ( https://github.com/SlickQuant/slick-stream-buffer ) ; see its
37- documentation for the full core API.
43+ from the underlying buffer.
3844
3945## Features
4046
4147- ** Boost.Asio DynamicBuffer adapter** usable with boost::beast / boost::asio read operations
4248- ** Zero-copy fan-out** of received network data to threads and processes
43- - ** Lock-free** single-producer / multi-consumer broadcast via slick-stream-buffer
49+ - ** Template backend** : works with ` slick::stream_buffer ` (SPMC) and ` producer_buffer ` (MPMC)
50+ - ** No hard backend dependency** — the library only requires Boost.Asio
4451- ** Drop-in replacement** for ` boost::beast::flat_buffer ` (including ` clear() ` )
4552- ** Cross-platform** — Windows, Linux, macOS
4653- Modern ** C++20** , header-only
4754
4855## Requirements
4956
5057- C++20 compatible compiler
51- - [ slick-stream-buffer] ( https://github.com/SlickQuant/slick-stream-buffer ) v1.0.4+ (fetched automatically when not installed)
52- - Boost.Asio — only the buffer types are used
58+ - Boost.Asio — only the buffer types are used (` boost/asio/buffer.hpp ` )
59+ - A slick buffer backend — ` slick-stream-buffer ` and/or ` slick-stream-buffer-multiplexer `
60+ (not fetched automatically; include the backend header and link its target yourself)
5361
5462## Installation
5563
5664Header-only. Add the ` include ` directory to your include path:
5765
5866``` cpp
5967#include < slick/dynamic_buffer.h>
68+ // also include your chosen backend:
69+ #include < slick/stream_buffer.h> // for stream_buffer backend
70+ #include < slick/stream_buffer_multiplexer.h> // for producer_buffer backend
6071```
6172
6273### Using CMake FetchContent
@@ -72,81 +83,120 @@ FetchContent_Declare(
7283)
7384FetchContent_MakeAvailable(slick-dynamic-buffer)
7485
75- target_link_libraries(your_target PRIVATE slick::dynamic_buffer) # slick-stream-buffer is fetched automatically
86+ # Link your backend(s) separately:
87+ target_link_libraries(your_target PRIVATE
88+ slick::dynamic_buffer
89+ slick::stream_buffer) # or slick::stream_buffer_multiplexer
7690```
7791
7892## Usage
7993
80- ### Producer: receive with boost::asio, publish on message boundaries
94+ ### With ` slick::stream_buffer ` (SPMC)
8195
8296``` cpp
8397#include < slick/dynamic_buffer.h>
98+ #include < slick/stream_buffer.h>
8499
85100// 64 MB data ring, 64K message records; named -> shared memory, nullptr -> local
86101slick::stream_buffer stream (1ull << 26, 1u << 16, "market_data");
87- slick::dynamic_buffer buffer (stream); // cheap copyable handle
102+ slick::dynamic_buffer dyn (stream); // CTAD deduces dynamic_buffer<stream_buffer>
88103
89104for (;;) {
90- std::size_t n = socket.read_some(buffer .prepare(64 * 1024));
91- buffer .commit(n);
105+ std::size_t n = socket.read_some(dyn .prepare(64 * 1024));
106+ dyn .commit(n);
92107
93108 // parse the readable area; publish every complete package
94- while (std::size_t package_size = find_complete_package(buffer .data())) {
95- buffer .consume(package_size); // publishes one record - no copy
109+ while (std::size_t package_size = find_complete_package(dyn .data())) {
110+ dyn .consume(package_size); // publishes one record — no copy
96111 }
97112}
98113```
99114
100- The adapter satisfies asio's `DynamicBuffer_v1` requirements, so it also works with
101- composed operations such as `boost::asio::read(socket, buffer, ...)`,
102- `boost::beast::http::read(...)` and `websocket::stream::read(...)`.
103-
104- ### Consumers: independent cursors, zero-copy reads
105-
106- Consumers read from the underlying SlickStreamBuffer directly:
115+ Consumers read from the underlying buffer directly:
107116
108117```cpp
109118// same process:
110- slick::stream_buffer& stream = buffer.stream_buffer ();
111- // another process:
119+ slick::stream_buffer& stream = dyn.buffer ();
120+ // another process (shared memory) :
112121slick::stream_buffer stream("market_data");
113122
114123uint64_t cursor = stream.initial_reading_index(); // or 0 to replay history
115124for (;;) {
116125 auto [data, length] = stream.read(cursor);
117- if (data == nullptr) continue; // nothing new yet
118- handle_package(data, length); // points directly into the ring
126+ if (data == nullptr) continue;
127+ handle_package(data, length); // zero-copy pointer into the ring
128+ }
129+ ```
130+
131+ ### With ` slick::stream_buffer_multiplexer::producer_buffer ` (MPMC)
132+
133+ ``` cpp
134+ #include < slick/dynamic_buffer.h>
135+ #include < slick/stream_buffer_multiplexer.h>
136+
137+ slick::stream_buffer_multiplexer mux (1024);
138+ auto pb = mux.add_producer(0, 1ull << 26, 1u << 16); // shared_ptr<producer_buffer>
139+
140+ // shared_ptr constructor (owning) — pb stays alive as long as dyn does
141+ slick::dynamic_buffer dyn(pb); // CTAD deduces dynamic_buffer<producer_buffer>
142+
143+ for (;;) {
144+ std::size_t n = socket.read_some(dyn.prepare(64 * 1024));
145+ dyn.commit(n);
146+ while (std::size_t package_size = find_complete_package(dyn.data())) {
147+ dyn.consume(package_size); // publishes to producer ring AND fans into shared queue
148+ }
119149}
120150```
121151
122- See the [ slick-stream-buffer] ( https://github.com/SlickQuant/slick-stream-buffer )
123- documentation for the consumer API (` read ` , ` read_last ` , ` initial_reading_index ` ,
124- ` loss_count ` ) and ring sizing guidance.
152+ The adapter also works with composed operations such as `boost::asio::read(socket, dyn, ...)`,
153+ `boost::beast::http::read(...)` and `websocket::stream::read(...)`.
125154
126155## API Overview
127156
157+ ### `slick::buffer_backend` concept
158+
159+ ```cpp
160+ template<typename T>
161+ concept buffer_backend = requires(T& b, const T& cb, std::size_t n) {
162+ { b.prepare(n) } -> std::same_as<std::pair<uint8_t*, std::size_t>>;
163+ { b.commit(n) };
164+ { b.consume(n) };
165+ { b.discard() };
166+ { cb.data() } -> std::same_as<const uint8_t*>;
167+ { cb.size() } -> std::convertible_to<std::size_t>;
168+ { cb.capacity() }-> std::convertible_to<std::size_t>;
169+ };
170+ ```
171+
172+ ### ` slick::dynamic_buffer<BufferT> `
173+
128174``` cpp
129- explicit dynamic_buffer (slick::stream_buffer& buffer,
175+ // Shared-ownership constructor (natural for shared_ptr backends like producer_buffer)
176+ explicit dynamic_buffer (std::shared_ptr<BufferT > ptr,
130177 std::size_t max_size = /* unlimited * /) noexcept;
178+
179+ // Non-owning reference constructor (backward-compatible; caller manages lifetime)
180+ explicit dynamic_buffer(BufferT& buffer,
181+ std::size_t max_size = /* unlimited * /);
131182```
132183
133- Wraps a `slick::stream_buffer` (the preferred spelling of `SlickStreamBuffer`, available
134- since slick-stream-buffer v1.0.4), which must outlive the adapter and all copies of it.
135- `max_size` caps `size()` plus prepared bytes (clamped to `buffer.capacity()`); beast/asio
136- read operations use it to limit how much they read. The adapter is a cheap copyable
137- handle — asio composed operations copy `DynamicBuffer_v1` objects by value.
184+ Both constructors clamp `max_size` to `buffer.capacity()`. The adapter is a cheap
185+ copyable handle — asio composed operations copy `DynamicBuffer_v1` objects by value;
186+ copies share the underlying `shared_ptr`.
138187
139188- `mutable_buffers_type prepare(std::size_t n)` — contiguous writable region of n bytes;
140189 throws `std::length_error` if `size() + n > max_size()`
141190- `void commit(std::size_t n)` — make n prepared bytes readable
142- - `published_record consume(std::size_t n)` — publish the first n readable bytes as **one
143- message record**; returns the record exactly as consumers will see it
144- (asio/beast callers may ignore the return value)
191+ - `auto consume(std::size_t n)` — publish the first n readable bytes as **one message
192+ record**; return type is deduced from `BufferT::consume()` — a `published_record` for
193+ both slick backends (asio/beast callers may ignore the return value)
145194- `void clear()` — drop the readable bytes and any prepared region **without publishing**,
146195 matching `beast::flat_buffer::clear()`
147196- `const_buffers_type data()` / `std::size_t size()` — the readable (committed, unconsumed) region
148197- `std::size_t max_size()` / `std::size_t capacity()` — limits, as required by `DynamicBuffer_v1`
149- - `slick::stream_buffer& stream_buffer()` — access the underlying buffer (e.g. for in-process consumers)
198+ - `BufferT& buffer()` / `const BufferT& buffer() const` — access the underlying backend
199+ - `std::shared_ptr<BufferT> buffer_ptr()` — shared-ownership handle to the backend
150200
151201## Important Constraints
152202
@@ -155,8 +205,7 @@ Consumers are lock-free and independent.
155205
156206**Lossy semantics.** The producer never blocks; if it laps a slow consumer, the consumer
157207skips ahead and the loss is counted. Size the rings so this cannot happen in normal
158- operation — see [slick-stream-buffer](https://github.com/SlickQuant/slick-stream-buffer)
159- for details and loss detection.
208+ operation.
160209
161210**Pointer invalidation.** `prepare()` may relocate the readable region to keep it
162211contiguous when the ring wraps; pointers previously returned by `data()`/`prepare()`
@@ -179,7 +228,7 @@ cmake --build build --config Debug
179228ctest --test-dir build -C Debug --output-on-failure
180229```
181230
182- Boost.Asio is required to build the tests (e.g. configure with a vcpkg toolchain file) .
231+ Boost.Asio and at least one slick backend are required to build the tests .
183232
184233## License
185234
0 commit comments