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 : #ifndef BOOST_CAPY_TEST_BUFGRIND_HPP
12 : #define BOOST_CAPY_TEST_BUFGRIND_HPP
13 :
14 : #include <boost/capy/detail/config.hpp>
15 : #include <boost/capy/buffers.hpp>
16 : #include <boost/capy/buffers/buffer_slice.hpp>
17 : #include <coroutine>
18 : #include <boost/capy/ex/io_env.hpp>
19 :
20 : #include <algorithm>
21 : #include <cstddef>
22 : #include <type_traits>
23 : #include <utility>
24 :
25 : namespace boost {
26 : namespace capy {
27 : namespace test {
28 :
29 : /** Iterates split points of a buffer sequence into two adjacent halves.
30 :
31 : This class iterates through all possible ways to split a buffer
32 : sequence into two parts (b1, b2) where concatenating them yields
33 : the original sequence. It uses an async-generator-like pattern
34 : that allows `co_await` between iterations.
35 :
36 : The split type automatically preserves mutability: passing a
37 : `MutableBufferSequence` yields halves that model
38 : @ref MutableBufferSequence, while passing a `ConstBufferSequence`
39 : yields halves that model @ref ConstBufferSequence. Each half is
40 : the buffer-sequence view exposed by a @ref buffer_slice over the
41 : corresponding byte range, and can be passed directly to
42 : `read_some`, `write_some`, `buffer_size`, etc.
43 :
44 : @par Thread Safety
45 : Not thread-safe.
46 :
47 : @par Example
48 : @code
49 : // Test all split points of a buffer
50 : std::string data = "hello world";
51 : auto cb = make_buffer( data );
52 :
53 : fuse f;
54 : auto r = f.inert( [&]( fuse& ) -> task<> {
55 : bufgrind bg( cb );
56 : while( bg ) {
57 : auto [b1, b2] = co_await bg.next();
58 : // b1 contains first N bytes (as a buffer sequence)
59 : // b2 contains remaining bytes (as a buffer sequence)
60 : // concatenating b1 + b2 equals original
61 : co_await some_async_operation( b1, b2 );
62 : }
63 : } );
64 : @endcode
65 :
66 : @par Mutable Buffer Example
67 : @code
68 : // Mutable buffers preserve mutability
69 : char data[100];
70 : mutable_buffer mb( data, sizeof( data ) );
71 :
72 : bufgrind bg( mb );
73 : while( bg ) {
74 : auto [b1, b2] = co_await bg.next();
75 : // b1, b2 yield mutable_buffer when iterated
76 : }
77 : @endcode
78 :
79 : @par Step Size Example
80 : @code
81 : // Skip by 10 bytes for faster iteration
82 : bufgrind bg( cb, 10 );
83 : while( bg ) {
84 : auto [b1, b2] = co_await bg.next();
85 : // Visits positions 0, 10, 20, ..., and always size
86 : }
87 : @endcode
88 :
89 : @see buffer_slice
90 : */
91 : template<ConstBufferSequence BS>
92 : class bufgrind
93 : {
94 : BS const& bs_;
95 : std::size_t size_;
96 : std::size_t step_;
97 : std::size_t pos_ = 0;
98 :
99 : public:
100 : /// Names the buffer-sequence type `buffer_slice` yields for each half.
101 : using slice_type = std::decay_t<
102 : decltype(buffer_slice(std::declval<BS const&>()))>;
103 :
104 : /// Pairs the two `slice_type` halves that @ref next yields together.
105 : using split_type = std::pair<slice_type, slice_type>;
106 :
107 : /** Construct a buffer grinder.
108 :
109 : @param bs The buffer sequence to iterate over.
110 :
111 : @param step The number of bytes to advance on each call to
112 : @ref next. A value of 0 is treated as 1. The final split
113 : at `buffer_size( bs )` is always included regardless of
114 : step alignment.
115 : */
116 : explicit
117 HIT 39 : bufgrind(
118 : BS const& bs,
119 : std::size_t step = 1) noexcept
120 39 : : bs_(bs)
121 39 : , size_(buffer_size(bs))
122 39 : , step_(step > 0 ? step : 1)
123 : {
124 39 : }
125 :
126 : /** Check if more split points remain.
127 :
128 : @return `true` if @ref next can be called, `false` otherwise.
129 : */
130 237 : explicit operator bool() const noexcept
131 : {
132 237 : return pos_ <= size_;
133 : }
134 :
135 : /** Computes the current split synchronously, so awaiting it never suspends the caller.
136 : */
137 : struct next_awaitable
138 : {
139 : /// The grinder that produced this awaitable.
140 : bufgrind* self_;
141 :
142 : /** Report whether the awaitable is ready.
143 :
144 : @return `true` always; the split is available without suspending.
145 : */
146 198 : bool await_ready() const noexcept { return true; }
147 :
148 : /** Resume the caller inline without suspending.
149 :
150 : @param h The awaiting coroutine handle.
151 :
152 : @return @p h, so the caller resumes immediately.
153 : */
154 MIS 0 : std::coroutine_handle<> await_suspend(std::coroutine_handle<> h, io_env const*) const noexcept { return h; }
155 :
156 : /** Return the current split and advance to the next.
157 :
158 : @return The `(b1, b2)` split at the current position.
159 : */
160 : split_type
161 HIT 198 : await_resume()
162 : {
163 198 : split_type result{
164 198 : buffer_slice(self_->bs_, 0, self_->pos_),
165 198 : buffer_slice(self_->bs_, self_->pos_)
166 : };
167 198 : if(self_->pos_ < self_->size_)
168 161 : self_->pos_ = (std::min)(self_->pos_ + self_->step_, self_->size_);
169 : else
170 37 : ++self_->pos_;
171 198 : return result;
172 : }
173 : };
174 :
175 : /** Return the next split point.
176 :
177 : Returns an awaitable that yields the current (b1, b2) pair
178 : and advances to the next split point.
179 :
180 : @par Preconditions
181 : `static_cast<bool>( *this )` is `true`.
182 :
183 : @return An awaitable that await-returns `split_type`.
184 : */
185 : next_awaitable
186 198 : next() noexcept
187 : {
188 198 : return {this};
189 : }
190 : };
191 :
192 : } // test
193 : } // capy
194 : } // boost
195 :
196 : #endif
|