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