byteme
Read/write bytes from various sources
Loading...
Searching...
No Matches
BufferedReader.hpp
Go to the documentation of this file.
1#ifndef BYTEME_BUFFERED_READER_HPP
2#define BYTEME_BUFFERED_READER_HPP
3
9#include <thread>
10#include <condition_variable>
11#include <mutex>
12#include <vector>
13#include <algorithm>
14#include <stdexcept>
15#include <exception>
16#include <type_traits>
17#include <memory>
18#include <cstddef>
19
21
22#include "Reader.hpp"
23#include "utils.hpp"
24
25namespace byteme {
26
39template<typename Type_>
41public:
45 BufferedReader(std::size_t buffer_size) : my_buffer(sanisizer::cap<I<decltype(my_buffer.size())> >(buffer_size)) {
46 if (buffer_size == 0) {
47 throw std::runtime_error("buffer size must be positive");
48 }
49 };
50
52 BufferedReader(const BufferedReader&) = delete; // not copyable.
53 BufferedReader& operator=(BufferedReader&&) = delete;
54 BufferedReader& operator=(const BufferedReader&) = delete; // not copyable.
55
56 virtual ~BufferedReader() = default;
61protected:
65 virtual std::size_t refill() = 0; // refills the buffer, returning the number of bytes filled.
66
67 virtual std::size_t refill(Type_*) = 0; // refills in the user-supplied array.
68
69 void initialize() {
70 my_available = refill();
71 }
72
73 auto& get_buffer() {
74 return my_buffer;
75 }
76
77 auto get_buffer_size() const {
78 return my_buffer.size();
79 }
84private:
85 std::vector<Type_> my_buffer;
86 std::size_t my_available = 0;
87 std::size_t my_current = 0;
88
89 // Standard guarantees this to be at least 64 bits, which is more than that of size_t.
90 // We don't use uint64_t because that might not be defined by the implementation.
91 unsigned long long my_overall = 0;
92 bool my_overall_failed = false;
93
94public:
101 bool advance() {
102 ++my_current;
103 if (my_current < my_available) {
104 return true;
105 }
106
107 my_current = 0;
108 safe_update_position(my_overall, my_available, my_overall_failed);
109
110 const auto buffer_size = get_buffer_size();
111 if (my_available == buffer_size) {
112 my_available = refill();
113 } else {
114 my_available = 0;
115 }
116
117 return my_available > 0; // Check that we haven't reached the end of the reader.
118 }
119
125 Type_ get() const {
126 return my_buffer[my_current];
127 }
128
134 unsigned long long position() const {
135 return safe_report_position(my_overall, my_current, my_overall_failed);
136 }
137
141 bool valid() const {
142 return my_current < my_available;
143 }
144
145public:
166 std::pair<std::size_t, bool> extract(std::size_t number, Type_* output) {
167 const std::size_t original = number;
168
169 // Copying contents of the cached buffer.
170 const std::size_t remaining = my_available - my_current;
171 if (number < remaining) {
172 std::copy_n(my_buffer.data() + my_current, number, output);
173 my_current += number;
174 //std::cout << "case A" << std::endl;
175 return std::make_pair(number, true);
176 }
177
178 std::copy_n(my_buffer.data() + my_current, remaining, output);
179 output += remaining;
180 number -= remaining;
181
182 // If the available number of bytes is less than the buffer size, the reader is already exhausted.
183 const auto buffer_size = get_buffer_size();
184 if (my_available < buffer_size) {
185 my_current = my_available;
186 //std::cout << "case B" << std::endl;
187 return std::make_pair(original - number, false);
188 }
189
190 // Directly filling the output array, bypassing our buffer.
191 while (number >= buffer_size) {
192 safe_update_position(my_overall, my_available, my_overall_failed);
193 my_available = refill(output);
194
195 output += my_available;
196 number -= my_available;
197 if (my_available < buffer_size) {
198 my_current = my_available;
199 //std::cout << "case C" << std::endl;
200 return std::make_pair(original - number, false);
201 }
202 }
203
204 // Filling the cache and copying the remainder into the output array.
205 safe_update_position(my_overall, my_available, my_overall_failed);
206 my_available = refill();
207
208 const auto to_use = std::min(my_available, number);
209 std::copy_n(my_buffer.data(), to_use, output);
210 number -= to_use;
211 my_current = to_use;
212
213 // We know that number < buffer_size from the loop condition, so if exhausted == true,
214 // this means that my_available <= number < buffer_size, i.e., the source is exhausted.
215 // Thus, any future advance() would definitely return false.
216 bool exhausted = (to_use == my_available);
217
218 //std::cout << "D" << std::endl;
219 return std::make_pair(original - number, !exhausted);
220 }
221
222public:
226 // Soft-deprecated, for back-compatibility only.
227 std::size_t extract_until(std::size_t number, Type_* output) {
228 const auto original = number;
229 assert(number > 0);
230
231 // Copying contents of the cached buffer.
232 // Unlike extract(), we allow equality here because we can leave ourselves on the last byte.
233 const std::size_t remaining = my_available - my_current;
234 if (number <= remaining) {
235 std::copy_n(my_buffer.data() + my_current, number, output);
236 my_current += number - 1;
237 //std::cout << "case A" << std::endl;
238 return number;
239 }
240
241 std::copy_n(my_buffer.data() + my_current, remaining, output);
242 output += remaining;
243 number -= remaining;
244
245 // If the available number of bytes is less than the buffer size, the reader is already exhausted.
246 const auto buffer_size = get_buffer_size();
247 if (my_available < buffer_size) {
248 assert(my_available > 0); // my_available > 0 otherwise valid() would be false.
249 my_current = my_available - 1;
250 //std::cout << "case B" << std::endl;
251 return original - number;
252 }
253
254 // Directly filling the output array, bypassing our buffer.
255 // Unlike extract(), we don't allow equality here because we don't want to skip past the last byte.
256 while (number > buffer_size) {
257 my_overall += my_available;
258 my_available = refill(output);
259
260 output += my_available;
261 number -= my_available;
262
263 if (my_available < buffer_size) {
264 // We want to make the last byte available for the next get(), so we just stick it in the buffer.
265 // We know that output must have advanced past its input value as remaining > 0 (otherwise valid() would be false), so subtraction is valid.
266 assert(remaining > 0);
267 my_buffer[0] = *(output - 1);
268 my_overall += my_available;
269 my_overall -= 1;
270 my_available = 1;
271 my_current = 0;
272 //std::cout << "case C" << std::endl;
273 return original - number;
274 }
275 }
276
277 // Filling the cache and copying the remainder into the output array.
278 my_overall += my_available;
279 my_available = refill();
280
281 if (my_available) {
282 // If we didn't take the loop, we know that original > remaining to get to this point, and thus number > 0.
283 // If we did take the loop, the body ensures that number > buffer_size >= my_available, so a non-premature exit would leave number > 0.
284 assert(number > 0);
285
286 const auto to_use = std::min(my_available, number); // both values are positive so to_use > 0 and we can safely subtract 1 from it.
287 std::copy_n(my_buffer.data(), to_use, output);
288 number -= to_use;
289 my_current = to_use - 1;
290 //std::cout << "case D" << std::endl;
291 } else {
292 // Again, we want to make the last byte available, so we just stick it in the buffer, see logic above.
293 assert(remaining > 0);
294 my_buffer[0] = *(output - 1);
295 my_overall -= 1;
296 my_available = 1;
297 my_current = 0;
298 //std::cout << "case E" << std::endl;
299 }
300
301 return original - number;
302 }
306};
307
318template<typename Type_, class Pointer_ = std::unique_ptr<Reader> >
319class SerialBufferedReader final : public BufferedReader<Type_> {
320public:
327 SerialBufferedReader(Pointer_ reader, std::size_t buffer_size) :
328 BufferedReader<Type_>(buffer_size),
329 my_reader(std::move(reader))
330 {
331 this->initialize();
332 }
333
334private:
335 Pointer_ my_reader;
336
337protected:
341 std::size_t refill() {
342 auto& buf = this->get_buffer();
343 return my_reader->read(reinterpret_cast<unsigned char*>(buf.data()), buf.size());
344 }
345
346 std::size_t refill(Type_* ptr) {
347 return my_reader->read(reinterpret_cast<unsigned char*>(ptr), this->get_buffer_size());
348 }
352};
353
365template<typename Type_, class Pointer_ = std::unique_ptr<Reader> >
366class ParallelBufferedReader final : public BufferedReader<Type_> {
367public:
374 ParallelBufferedReader(Pointer_ reader, std::size_t buffer_size) :
375 BufferedReader<Type_>(buffer_size),
376 my_reader(std::move(reader)),
377 my_buffer_worker(buffer_size)
378 {
379 my_thread = std::thread([&]() { thread_loop(); }); // set up thread before initializing.
380
381 try {
382 this->initialize();
383 } catch (...) {
384 // Killing thread as destructor won't be called if the constructor didn't finish.
385 kill_thread();
386 throw;
387 }
388 }
389
394 kill_thread();
395 }
400private:
401 Pointer_ my_reader;
402 std::vector<Type_> my_buffer_main, my_buffer_worker;
403 std::size_t my_next_available = 0;
404
405private:
406 std::thread my_thread;
407 std::exception_ptr my_thread_err = nullptr;
408 std::mutex my_mut;
409 std::condition_variable my_cv;
410
411 bool my_ready_input = false, my_ready_output = false;
412 bool my_worker_active = false;
413 bool my_kill = false;
414
415 void thread_loop() {
416 const auto bufsize = this->get_buffer_size();
417 while (1) {
418 std::unique_lock lck(my_mut);
419 my_cv.wait(lck, [&]() { return my_ready_input; });
420 my_ready_input = false;
421
422 if (my_kill) { // Handle an explicit kill signal from the destructor.
423 break;
424 }
425
426 try {
427 my_next_available = my_reader->read(reinterpret_cast<unsigned char*>(my_buffer_worker.data()), bufsize);
428 } catch (...) {
429 my_thread_err = std::current_exception();
430 }
431
432 my_ready_output = true;
433 lck.unlock();
434 my_cv.notify_one();
435 }
436 }
437
438 void kill_thread() {
439 std::unique_lock lck(my_mut);
440 my_kill = true;
441 my_ready_input = true;
442 lck.unlock(); // releasing the lock so that the notified thread doesn't immediately block.
443 my_cv.notify_one();
444 my_thread.join();
445 }
446
447protected:
451 std::size_t refill() {
452 auto& buffer_main = this->get_buffer();
453
454 if (my_worker_active) {
455 // If the worker is active, it's probably already gotten started so we just wait for it to finish.
456 // Then we swap the results with the main buffer and submit a new job to the worker.
457 std::unique_lock lck(my_mut);
458 my_cv.wait(lck, [&]() { return my_ready_output; });
459 my_ready_output = false;
460
461 if (my_thread_err) {
462 std::rethrow_exception(my_thread_err);
463 }
464
465 buffer_main.swap(my_buffer_worker);
466 const auto output = my_next_available;
467
468 my_ready_input = true;
469 lck.unlock();
470 my_cv.notify_one();
471
472 return output;
473
474 } else {
475 // If the worker is not active, we just do a read directly on the main thread, and then submit a new job to the worker.
476 // Obviously, if we did the read on the worker, we'd have to wait for it anyway, so we might as well cut out the communication overhead.
477 const auto available = my_reader->read(reinterpret_cast<unsigned char*>(buffer_main.data()), buffer_main.size());
478
479 std::unique_lock lck(my_mut);
480 my_ready_input = true;
481 lck.unlock();
482 my_cv.notify_one();
483 my_worker_active = true;
484
485 return available;
486 }
487 }
488
489 std::size_t refill(Type_* ptr) {
490 if (my_worker_active) {
491 // If the worker is active, we wait for it to finish and then transfer the results to the supplied pointer.
492 // We do not submit a new job, based on the loop in BufferedReader::extract():
493 //
494 // - We'll probably want to call this refill() overload again.
495 // On subsequent calls, we'll want to directly read from the Reader into the supplied pointer to cut out a copy.
496 // - But, if we didn't call this overload again, we'd still probably call the other refill() overload immediately.
497 // So if we sent a new job to the worker, we'd end up immediately blocking on it to get the next read.
498 // So we might as well skip that communication overhead and read directly on the main thread.
499 std::unique_lock lck(my_mut);
500 my_cv.wait(lck, [&]() { return my_ready_output; });
501 my_ready_output = false;
502 if (my_thread_err) {
503 std::rethrow_exception(my_thread_err);
504 }
505
506 std::copy_n(my_buffer_worker.begin(), my_next_available, ptr);
507 my_worker_active = false;
508 return my_next_available;
509
510 } else {
511 // If the worker isn't active, we can directly read the into the supplied pointer, avoiding some communication overhead.
512 return my_reader->read(reinterpret_cast<unsigned char*>(ptr), this->get_buffer_size());
513 }
514 }
518};
519
520}
521
522#endif
Read an input source.
Buffered wrapper around a Reader.
Definition BufferedReader.hpp:40
bool valid() const
Definition BufferedReader.hpp:141
std::pair< std::size_t, bool > extract(std::size_t number, Type_ *output)
Definition BufferedReader.hpp:166
Type_ get() const
Definition BufferedReader.hpp:125
bool advance()
Definition BufferedReader.hpp:101
unsigned long long position() const
Definition BufferedReader.hpp:134
Parallelized buffering to wrap a Reader.
Definition BufferedReader.hpp:366
ParallelBufferedReader(Pointer_ reader, std::size_t buffer_size)
Definition BufferedReader.hpp:374
Serial buffering to wrap a Reader.
Definition BufferedReader.hpp:319
SerialBufferedReader(Pointer_ reader, std::size_t buffer_size)
Definition BufferedReader.hpp:327
Simple byte readers and writers.
Definition BufferedReader.hpp:25
constexpr Dest_ cap(Value_ x)