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
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 1610 : for(; it_ != end_ && size_ < detail::max_iovec_; ++it_)
156 : {
157 1054 : buffer_type buf(*it_);
158 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 521 : if(pos_ >= size_)
195 155 : refill();
196 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 574 : while(n > 0 && pos_ < size_)
228 : {
229 450 : auto avail = arr_[pos_].size();
230 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
|