libs/capy/include/boost/capy/buffers/buffer_param.hpp

100.0% Lines (31/31) 97.5% Functions (39/40) 95.5% Branches (21/22)
libs/capy/include/boost/capy/buffers/buffer_param.hpp
Line Branch Hits Source Code
1 //
2 // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3 //
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 //
7 // Official repository: https://github.com/cppalliance/capy
8 //
9
10 /*
11 COROUTINE BUFFER SEQUENCE LIFETIME REQUIREMENT
12 ===============================================
13 Buffer sequence parameters in coroutine APIs MUST be passed BY VALUE,
14 never by reference. When a coroutine suspends, reference parameters may
15 dangle if the caller's object goes out of scope before resumption.
16
17 CORRECT: task<> read_some(MutableBufferSequence auto buffers)
18 WRONG: task<> read_some(MutableBufferSequence auto& buffers)
19 WRONG: task<> read_some(MutableBufferSequence auto const& buffers)
20
21 The buffer_param class works with this model: it takes a const& in its
22 constructor (for the non-coroutine scope) but the caller's template
23 function accepts the buffer sequence by value, ensuring the sequence
24 lives in the coroutine frame.
25 */
26
27 #ifndef BOOST_CAPY_BUFFERS_BUFFER_PARAM_HPP
28 #define BOOST_CAPY_BUFFERS_BUFFER_PARAM_HPP
29
30 #include <boost/capy/detail/config.hpp>
31 #include <boost/capy/buffers.hpp>
32
33 #include <new>
34 #include <span>
35 #include <type_traits>
36
37 namespace boost {
38 namespace capy {
39
40 /** A buffer sequence wrapper providing windowed access.
41
42 This template class wraps any buffer sequence and provides
43 incremental access through a sliding window of buffer
44 descriptors. It handles both const and mutable buffer
45 sequences automatically.
46
47 @par Coroutine Lifetime Requirement
48
49 When used in coroutine APIs, the outer template function
50 MUST accept the buffer sequence parameter BY VALUE:
51
52 @code
53 task<> write(ConstBufferSequence auto buffers); // CORRECT
54 task<> write(ConstBufferSequence auto& buffers); // WRONG - dangling reference
55 @endcode
56
57 Pass-by-value ensures the buffer sequence is copied into
58 the coroutine frame and remains valid across suspension
59 points. References would dangle when the caller's scope
60 exits before the coroutine resumes.
61
62 @par Purpose
63
64 When iterating through large buffer sequences, it is often
65 more efficient to process buffers in batches rather than
66 one at a time. This class maintains a window of up to
67 @ref max_size buffer descriptors, automatically refilling
68 from the underlying sequence as buffers are consumed.
69
70 @par Usage
71
72 Create a `buffer_param` from any buffer sequence and use
73 `data()` to get the current window of buffers. After
74 processing some bytes, call `consume()` to advance through
75 the sequence.
76
77 @code
78 task<> send(ConstBufferSequence auto buffers)
79 {
80 buffer_param bp(buffers);
81 while(true)
82 {
83 auto bufs = bp.data();
84 if(bufs.empty())
85 break;
86 auto n = co_await do_something(bufs);
87 bp.consume(n);
88 }
89 }
90 @endcode
91
92 @par Virtual Interface Pattern
93
94 This class enables passing arbitrary buffer sequences through
95 a virtual function boundary. The template function captures
96 the buffer sequence by value and drives the iteration, while
97 the virtual function receives a simple span:
98
99 @code
100 class base
101 {
102 public:
103 task<> write(ConstBufferSequence auto buffers)
104 {
105 buffer_param bp(buffers);
106 while(true)
107 {
108 auto bufs = bp.data();
109 if(bufs.empty())
110 break;
111 std::size_t n = 0;
112 co_await write_impl(bufs, n);
113 bp.consume(n);
114 }
115 }
116
117 protected:
118 virtual task<> write_impl(
119 std::span<const_buffer> buffers,
120 std::size_t& bytes_written) = 0;
121 };
122 @endcode
123
124 @tparam BS The buffer sequence type. Must satisfy either
125 ConstBufferSequence or MutableBufferSequence.
126
127 @see ConstBufferSequence, MutableBufferSequence
128 */
129 template<class BS, bool MakeConst = false>
130 requires ConstBufferSequence<BS> || MutableBufferSequence<BS>
131 class buffer_param
132 {
133 public:
134 /// The buffer type (const_buffer or mutable_buffer)
135 using buffer_type = std::conditional_t<
136 MakeConst,
137 const_buffer,
138 capy::buffer_type<BS>>;
139
140 private:
141 decltype(begin(std::declval<BS const&>())) it_;
142 decltype(end(std::declval<BS const&>())) end_;
143 union {
144 int dummy_;
145 buffer_type arr_[detail::max_iovec_];
146 };
147 std::size_t size_ = 0;
148 std::size_t pos_ = 0;
149
150 void
151 556 refill()
152 {
153 556 pos_ = 0;
154 556 size_ = 0;
155
10/10
✓ Branch 0 taken 688 times.
✓ Branch 1 taken 495 times.
✓ Branch 2 taken 402 times.
✓ Branch 2 taken 672 times.
✓ Branch 3 taken 25 times.
✓ Branch 3 taken 16 times.
✓ Branch 4 taken 382 times.
✓ Branch 5 taken 20 times.
✓ Branch 6 taken 382 times.
✓ Branch 7 taken 45 times.
1610 for(; it_ != end_ && size_ < detail::max_iovec_; ++it_)
156 {
157 1054 buffer_type buf(*it_);
158
2/2
✓ Branch 1 taken 1016 times.
✓ Branch 2 taken 38 times.
1054 if(buf.size() > 0)
159 1016 ::new(&arr_[size_++]) buffer_type(buf);
160 }
161 556 }
162
163 public:
164 /** Construct from a buffer sequence.
165
166 @param bs The buffer sequence to wrap. The caller must
167 ensure the buffer sequence remains valid for the
168 lifetime of this object.
169 */
170 explicit
171 401 buffer_param(BS const& bs)
172 401 : it_(begin(bs))
173 401 , end_(end(bs))
174 401 , dummy_(0)
175 {
176 401 refill();
177 401 }
178
179 /** Return the current window of buffer descriptors.
180
181 Returns a span of buffer descriptors representing the
182 currently available portion of the buffer sequence.
183 The span contains at most @ref max_size buffers.
184
185 When the current window is exhausted, this function
186 automatically refills from the underlying sequence.
187
188 @return A span of buffer descriptors. Empty span
189 indicates no more data is available.
190 */
191 std::span<buffer_type>
192 521 data()
193 {
194
2/2
✓ Branch 0 taken 155 times.
✓ Branch 1 taken 366 times.
521 if(pos_ >= size_)
195 155 refill();
196
2/2
✓ Branch 0 taken 135 times.
✓ Branch 1 taken 386 times.
521 if(size_ == 0)
197 135 return {};
198 386 return {arr_ + pos_, size_ - pos_};
199 }
200
201 /** Check if more buffers exist beyond the current window.
202
203 Returns `true` if the underlying buffer sequence has
204 additional buffers that have not yet been loaded into
205 the current window. Call after @ref data to determine
206 whether the current window is the last one.
207
208 @return `true` if more buffers remain in the sequence.
209 */
210 bool
211 41 more() const noexcept
212 {
213 41 return it_ != end_;
214 }
215
216 /** Consume bytes from the buffer sequence.
217
218 Advances the current position by `n` bytes, consuming
219 data from the front of the sequence. Partially consumed
220 buffers are adjusted in place.
221
222 @param n Number of bytes to consume.
223 */
224 void
225 124 consume(std::size_t n)
226 {
227
3/4
✓ Branch 0 taken 450 times.
✓ Branch 1 taken 124 times.
✓ Branch 2 taken 450 times.
✗ Branch 3 not taken.
574 while(n > 0 && pos_ < size_)
228 {
229 450 auto avail = arr_[pos_].size();
230
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 445 times.
450 if(n < avail)
231 {
232 5 arr_[pos_] += n;
233 5 n = 0;
234 }
235 else
236 {
237 445 n -= avail;
238 445 ++pos_;
239 }
240 }
241 124 }
242 };
243
244 // CTAD deduction guide
245 template<class BS>
246 buffer_param(BS const&) -> buffer_param<BS>;
247
248 /// Alias for buffer_param that always uses const_buffer storage.
249 template<class BS>
250 using const_buffer_param = buffer_param<BS, true>;
251
252 } // namespace capy
253 } // namespace boost
254
255 #endif
256