Skip to content

Commit 24aa02b

Browse files
committed
Changed dynamic buffer to a template class; Remove slick-stram dependency; Updated CHANGELOG and README
1 parent 381da58 commit 24aa02b

7 files changed

Lines changed: 285 additions & 121 deletions

File tree

CHANGELOG.md

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,22 @@
11
# Changelog
22

3-
## v1.0.0 - 2026-06-12
3+
## v1.0.0 - 2026-06-16
44

5-
Initial release.
5+
### Added
66

7-
- `slick::dynamic_buffer` — Boost.Asio `DynamicBuffer_v1` adapter, drop-in
8-
replacement for `boost::beast::flat_buffer`; `consume(n)` publishes the consumed
9-
bytes to consumers as one message record (zero-copy fan-out).
10-
- The API uses the `slick::stream_buffer` spelling of the core buffer, so
11-
slick-stream-buffer >= v1.0.4 is required.
12-
- GoogleTest suites for the asio adapter (including a real TCP loopback test); CI for Windows/Linux/macOS.
7+
- `slick::dynamic_buffer<BufferT>` — Boost.Asio `DynamicBuffer_v1` adapter, drop-in
8+
replacement for `boost::beast::flat_buffer`. `consume(n)` publishes the consumed
9+
bytes as one message record (zero-copy fan-out) via the backend.
10+
- `slick::buffer_backend` C++20 concept defining the required backend interface
11+
(`prepare`, `commit`, `consume`, `discard`, `data`, `size`, `capacity`).
12+
- Two construction modes: `dynamic_buffer(std::shared_ptr<BufferT>)` for shared
13+
ownership and `dynamic_buffer(BufferT&)` for non-owning reference (null deleter).
14+
- `buffer()` accessor returning `BufferT&`; `buffer_ptr()` returning
15+
`std::shared_ptr<BufferT>` for shared lifetime management.
16+
- Supported backends out of the box:
17+
- `slick::stream_buffer` — single-producer ring buffer.
18+
- `slick::stream_buffer_multiplexer::producer_buffer` — per-producer ring that
19+
fans records into the shared MPMC queue.
20+
- Only requires Boost.Asio as a library dependency; backend is caller-supplied.
21+
- GoogleTest suites covering buffer copy, max-size clamping, TCP loopback zero-copy
22+
read, Asio composed ops, and both slick backends; CI for Windows/Linux/macOS.

CMakeLists.txt

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.10)
22

33
project(slick-dynamic-buffer
44
VERSION 1.0.0
5-
DESCRIPTION "Header-only Boost.Asio DynamicBuffer adapter for slick-stream-buffer - zero-copy fan-out of received network bytes to lock-free SPMC consumers across threads and processes"
5+
DESCRIPTION "Header-only Boost.Asio DynamicBuffer adapter for slick buffer backends (stream_buffer, producer_buffer) - zero-copy fan-out of received network bytes to lock-free consumers"
66
LANGUAGES CXX)
77

88
set(CMAKE_CXX_STANDARD 20)
@@ -11,22 +11,6 @@ set(CMAKE_CXX_STANDARD 20)
1111
option(BUILD_SLICK_DYNAMIC_BUFFER_TESTS "Build tests" ${PROJECT_IS_TOP_LEVEL})
1212

1313
find_package(Boost CONFIG REQUIRED COMPONENTS asio)
14-
find_package(slick-stream-buffer CONFIG QUIET)
15-
16-
if (NOT slick-stream-buffer_FOUND)
17-
include(FetchContent)
18-
19-
# Disable slick-stream-buffer tests and examples, but allow installation
20-
set(BUILD_SLICK_STREAM_BUFFER_TESTS OFF CACHE BOOL "" FORCE)
21-
FetchContent_Declare(
22-
slick-stream-buffer
23-
GIT_REPOSITORY https://github.com/SlickQuant/slick-stream-buffer.git
24-
GIT_TAG v1.0.4 # See https://github.com/SlickQuant/slick-stream-buffer/releases for latest version
25-
)
26-
FetchContent_MakeAvailable(slick-stream-buffer)
27-
else()
28-
message(STATUS "slick-stream-buffer: ${slick-stream-buffer_VERSION}")
29-
endif()
3014

3115
add_library(slick-dynamic-buffer INTERFACE)
3216
add_library(slick::dynamic_buffer ALIAS slick-dynamic-buffer)
@@ -37,7 +21,7 @@ target_include_directories(slick-dynamic-buffer INTERFACE
3721
)
3822
set_target_properties(slick-dynamic-buffer PROPERTIES EXPORT_NAME dynamic_buffer)
3923

40-
target_link_libraries(slick-dynamic-buffer INTERFACE slick::stream_buffer Boost::asio)
24+
target_link_libraries(slick-dynamic-buffer INTERFACE Boost::asio)
4125

4226
if(BUILD_SLICK_DYNAMIC_BUFFER_TESTS)
4327
enable_testing()

README.md

Lines changed: 96 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,21 @@
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

3442
Each 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

5664
Header-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
)
7384
FetchContent_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
86101
slick::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

89104
for (;;) {
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):
112121
slick::stream_buffer stream("market_data");
113122
114123
uint64_t cursor = stream.initial_reading_index(); // or 0 to replay history
115124
for (;;) {
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
157207
skips 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
162211
contiguous when the ring wraps; pointers previously returned by `data()`/`prepare()`
@@ -179,7 +228,7 @@ cmake --build build --config Debug
179228
ctest --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

cmake/slick-dynamic-bufferConfig.cmake.in

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,6 @@
22

33
include(CMakeFindDependencyMacro)
44
find_dependency(Boost CONFIG REQUIRED COMPONENTS asio)
5-
find_dependency(slick-stream-buffer QUIET)
6-
if (NOT slick-stream-buffer_FOUND)
7-
include(FetchContent)
8-
9-
# Disable slick-stream-buffer tests, but allow installation
10-
set(BUILD_SLICK_STREAM_BUFFER_TESTS OFF CACHE BOOL "" FORCE)
11-
FetchContent_Declare(
12-
slick-stream-buffer
13-
GIT_REPOSITORY https://github.com/SlickQuant/slick-stream-buffer.git
14-
GIT_TAG v1.0.4
15-
)
16-
FetchContent_MakeAvailable(slick-stream-buffer)
17-
endif()
185

196
include("${CMAKE_CURRENT_LIST_DIR}/slick-dynamic-bufferTargets.cmake")
207

0 commit comments

Comments
 (0)