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 <span>
34 :
35 : namespace boost {
36 : namespace capy {
37 :
38 : /** A buffer sequence wrapper providing windowed access.
39 :
40 : This template class wraps any buffer sequence and provides
41 : incremental access through a sliding window of buffer
42 : descriptors. It handles both const and mutable buffer
43 : sequences automatically.
44 :
45 : @par Coroutine Lifetime Requirement
46 :
47 : When used in coroutine APIs, the outer template function
48 : MUST accept the buffer sequence parameter BY VALUE:
49 :
50 : @code
51 : task<> write(ConstBufferSequence auto buffers); // CORRECT
52 : task<> write(ConstBufferSequence auto& buffers); // WRONG - dangling reference
53 : @endcode
54 :
55 : Pass-by-value ensures the buffer sequence is copied into
56 : the coroutine frame and remains valid across suspension
57 : points. References would dangle when the caller's scope
58 : exits before the coroutine resumes.
59 :
60 : @par Purpose
61 :
62 : When iterating through large buffer sequences, it is often
63 : more efficient to process buffers in batches rather than
64 : one at a time. This class maintains a window of up to
65 : @ref max_size buffer descriptors, automatically refilling
66 : from the underlying sequence as buffers are consumed.
67 :
68 : @par Usage
69 :
70 : Create a `buffer_param` from any buffer sequence and use
71 : `data()` to get the current window of buffers. After
72 : processing some bytes, call `consume()` to advance through
73 : the sequence.
74 :
75 : @code
76 : task<> send(ConstBufferSequence auto buffers)
77 : {
78 : buffer_param bp(buffers);
79 : while(true)
80 : {
81 : auto bufs = bp.data();
82 : if(bufs.empty())
83 : break;
84 : auto n = co_await do_something(bufs);
85 : bp.consume(n);
86 : }
87 : }
88 : @endcode
89 :
90 : @par Virtual Interface Pattern
91 :
92 : This class enables passing arbitrary buffer sequences through
93 : a virtual function boundary. The template function captures
94 : the buffer sequence by value and drives the iteration, while
95 : the virtual function receives a simple span:
96 :
97 : @code
98 : class base
99 : {
100 : public:
101 : task<> write(ConstBufferSequence auto buffers)
102 : {
103 : buffer_param bp(buffers);
104 : while(true)
105 : {
106 : auto bufs = bp.data();
107 : if(bufs.empty())
108 : break;
109 : std::size_t n = 0;
110 : co_await write_impl(bufs, n);
111 : bp.consume(n);
112 : }
113 : }
114 :
115 : protected:
116 : virtual task<> write_impl(
117 : std::span<const_buffer> buffers,
118 : std::size_t& bytes_written) = 0;
119 : };
120 : @endcode
121 :
122 : @tparam BS The buffer sequence type. Must satisfy either
123 : ConstBufferSequence or MutableBufferSequence.
124 :
125 : @see ConstBufferSequence, MutableBufferSequence
126 : */
127 : template<class BS>
128 : requires ConstBufferSequence<BS> || MutableBufferSequence<BS>
129 : class buffer_param
130 : {
131 : public:
132 : /// The buffer type (const_buffer or mutable_buffer)
133 : using buffer_type = capy::buffer_type<BS>;
134 :
135 : private:
136 : decltype(begin(std::declval<BS const&>())) it_;
137 : decltype(end(std::declval<BS const&>())) end_;
138 : buffer_type arr_[detail::max_iovec_];
139 : std::size_t size_ = 0;
140 : std::size_t pos_ = 0;
141 :
142 : void
143 435 : refill()
144 : {
145 435 : pos_ = 0;
146 435 : size_ = 0;
147 883 : for(; it_ != end_ && size_ < detail::max_iovec_; ++it_)
148 : {
149 448 : buffer_type buf(*it_);
150 448 : if(buf.size() > 0)
151 436 : arr_[size_++] = buf;
152 : }
153 435 : }
154 :
155 : public:
156 : /** Construct from a buffer sequence.
157 :
158 : @param bs The buffer sequence to wrap. The caller must
159 : ensure the buffer sequence remains valid for the
160 : lifetime of this object.
161 : */
162 : explicit
163 345 : buffer_param(BS const& bs)
164 345 : : it_(begin(bs))
165 345 : , end_(end(bs))
166 : {
167 345 : refill();
168 345 : }
169 :
170 : /** Return the current window of buffer descriptors.
171 :
172 : Returns a span of buffer descriptors representing the
173 : currently available portion of the buffer sequence.
174 : The span contains at most @ref max_size buffers.
175 :
176 : When the current window is exhausted, this function
177 : automatically refills from the underlying sequence.
178 :
179 : @return A span of buffer descriptors. Empty span
180 : indicates no more data is available.
181 : */
182 : std::span<buffer_type>
183 487 : data()
184 : {
185 487 : if(pos_ >= size_)
186 90 : refill();
187 487 : if(size_ == 0)
188 87 : return {};
189 400 : return {arr_ + pos_, size_ - pos_};
190 : }
191 :
192 : /** Consume bytes from the buffer sequence.
193 :
194 : Advances the current position by `n` bytes, consuming
195 : data from the front of the sequence. Partially consumed
196 : buffers are adjusted in place.
197 :
198 : @param n Number of bytes to consume.
199 : */
200 : void
201 146 : consume(std::size_t n)
202 : {
203 351 : while(n > 0 && pos_ < size_)
204 : {
205 205 : auto avail = arr_[pos_].size();
206 205 : if(n < avail)
207 : {
208 65 : arr_[pos_] += n;
209 65 : n = 0;
210 : }
211 : else
212 : {
213 140 : n -= avail;
214 140 : ++pos_;
215 : }
216 : }
217 146 : }
218 : };
219 :
220 : // CTAD deduction guide
221 : template<class BS>
222 : buffer_param(BS const&) -> buffer_param<BS>;
223 :
224 : } // namespace capy
225 : } // namespace boost
226 :
227 : #endif
|