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