TLA Line data 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 a
67 : fixed, implementation-defined number of buffer descriptors
68 : (currently 16), automatically refilling from the underlying
69 : sequence as buffers are consumed.
70 :
71 : @par Example
72 :
73 : Create a `buffer_param` from any buffer sequence and use
74 : `data()` to get the current window of buffers. After
75 : processing some bytes, call `consume()` to advance through
76 : the sequence.
77 :
78 : @code
79 : task<> send(ConstBufferSequence auto buffers)
80 : {
81 : buffer_param bp(buffers);
82 : while(true)
83 : {
84 : auto bufs = bp.data();
85 : if(bufs.empty())
86 : break;
87 : auto n = co_await do_something(bufs);
88 : bp.consume(n);
89 : }
90 : }
91 : @endcode
92 :
93 : @par Virtual Interface Pattern
94 :
95 : This class enables passing arbitrary buffer sequences through
96 : a virtual function boundary. The template function captures
97 : the buffer sequence by value and drives the iteration, while
98 : the virtual function receives a simple span:
99 :
100 : @code
101 : class base
102 : {
103 : public:
104 : task<> write(ConstBufferSequence auto buffers)
105 : {
106 : buffer_param bp(buffers);
107 : while(true)
108 : {
109 : auto bufs = bp.data();
110 : if(bufs.empty())
111 : break;
112 : std::size_t n = 0;
113 : co_await write_impl(bufs, n);
114 : bp.consume(n);
115 : }
116 : }
117 :
118 : protected:
119 : virtual task<> write_impl(
120 : std::span<const_buffer> buffers,
121 : std::size_t& bytes_written) = 0;
122 : };
123 : @endcode
124 :
125 : @tparam BS The buffer sequence type. Must satisfy either
126 : ConstBufferSequence or MutableBufferSequence.
127 :
128 : @see ConstBufferSequence, MutableBufferSequence
129 : */
130 : template<class BS, bool MakeConst = false>
131 : requires ConstBufferSequence<BS> || MutableBufferSequence<BS>
132 : class buffer_param
133 : {
134 : public:
135 : /// The buffer type (const_buffer or mutable_buffer)
136 : using buffer_type = std::conditional_t<
137 : MakeConst,
138 : const_buffer,
139 : capy::buffer_type<BS>>;
140 :
141 : private:
142 : decltype(begin(std::declval<BS const&>())) it_;
143 : decltype(end(std::declval<BS const&>())) end_;
144 : union {
145 : int dummy_;
146 : buffer_type arr_[detail::max_iovec_];
147 : };
148 : std::size_t size_ = 0;
149 : std::size_t pos_ = 0;
150 :
151 : void
152 HIT 568 : refill()
153 : {
154 568 : pos_ = 0;
155 568 : size_ = 0;
156 1630 : for(; it_ != end_ && size_ < detail::max_iovec_; ++it_)
157 : {
158 1062 : buffer_type buf(*it_);
159 1062 : if(buf.size() > 0)
160 1024 : ::new(&arr_[size_++]) buffer_type(buf);
161 : }
162 568 : }
163 :
164 : public:
165 : /** Construct from a buffer sequence.
166 :
167 : @param bs The buffer sequence to wrap. The caller must
168 : ensure the buffer sequence remains valid for the
169 : lifetime of this object.
170 : */
171 : explicit
172 409 : buffer_param(BS const& bs)
173 409 : : it_(begin(bs))
174 409 : , end_(end(bs))
175 409 : , dummy_(0)
176 : {
177 409 : refill();
178 409 : }
179 :
180 : /** Return the current window of buffer descriptors.
181 :
182 : Returns a span of buffer descriptors representing the
183 : currently available portion of the buffer sequence.
184 : The span contains at most a fixed, implementation-defined
185 : number of buffers (currently 16).
186 :
187 : When the current window is exhausted, this function
188 : automatically refills from the underlying sequence.
189 :
190 : @return A span of buffer descriptors. Empty span
191 : indicates no more data is available.
192 : */
193 : std::span<buffer_type>
194 533 : data()
195 : {
196 533 : if(pos_ >= size_)
197 159 : refill();
198 533 : if(size_ == 0)
199 139 : return {};
200 394 : return {arr_ + pos_, size_ - pos_};
201 : }
202 :
203 : /** Check if more buffers exist beyond the current window.
204 :
205 : Returns `true` if the underlying buffer sequence has
206 : additional buffers that have not yet been loaded into
207 : the current window. Call after @ref data to determine
208 : whether the current window is the last one.
209 :
210 : @return `true` if more buffers remain in the sequence.
211 : */
212 : bool
213 43 : more() const noexcept
214 : {
215 43 : return it_ != end_;
216 : }
217 :
218 : /** Consume bytes from the buffer sequence.
219 :
220 : Advances the current position by `n` bytes, consuming
221 : data from the front of the sequence. Partially consumed
222 : buffers are adjusted in place.
223 :
224 : @param n Number of bytes to consume.
225 : */
226 : void
227 128 : consume(std::size_t n)
228 : {
229 582 : while(n > 0 && pos_ < size_)
230 : {
231 454 : auto avail = arr_[pos_].size();
232 454 : if(n < avail)
233 : {
234 5 : arr_[pos_] += n;
235 5 : n = 0;
236 : }
237 : else
238 : {
239 449 : n -= avail;
240 449 : ++pos_;
241 : }
242 : }
243 128 : }
244 : };
245 :
246 : // CTAD deduction guide
247 : template<class BS>
248 : buffer_param(BS const&) -> buffer_param<BS>;
249 :
250 : /// Alias for buffer_param that always uses const_buffer storage.
251 : template<class BS>
252 : using const_buffer_param = buffer_param<BS, true>;
253 :
254 : } // namespace capy
255 : } // namespace boost
256 :
257 : #endif
|