byteme
Read/write bytes from various sources
Loading...
Searching...
No Matches
BufferedWriter.hpp
Go to the documentation of this file.
1#ifndef BYTEME_BUFFERED_WRITER_HPP
2#define BYTEME_BUFFERED_WRITER_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#include <string>
20
22
23#include "Writer.hpp"
24#include "utils.hpp"
25
26namespace byteme {
27
40template<typename Type_, class WriterPointer_ = std::unique_ptr<Writer> >
42public:
46 BufferedWriter(std::size_t buffer_size) : my_buffer(sanisizer::cast<I<decltype(my_buffer.size())> >(buffer_size)) {
47 if (buffer_size == 0) {
48 throw std::runtime_error("buffer size must be positive");
49 }
50 }
51
53 BufferedWriter(const BufferedWriter&) = delete; // not copyable.
54 BufferedWriter& operator=(BufferedWriter&&) = delete;
55 BufferedWriter& operator=(const BufferedWriter&) = delete; // not copyable.
56
57 virtual ~BufferedWriter() = default;
62protected:
66 virtual void flush_async() = 0;
67
68 virtual void flush_sync(const Type_*, std::size_t) = 0;
69
70 std::vector<Type_>& get_buffer() {
71 return my_buffer;
72 }
77private:
78 std::vector<Type_> my_buffer;
79 std::size_t my_current = 0;
80
81 // Standard guarantees this to be at least 64 bits, which is more than that of size_t.
82 // We don't use uint64_t because that might not be defined by the implementation.
83 unsigned long long my_overall = 0;
84 bool my_overall_failed = false;
85
86public:
92 unsigned long long number() const {
93 return safe_report_position(my_overall, my_current, my_overall_failed);
94 }
95
96public:
103 void write(Type_ input) {
104 my_buffer[my_current] = input;
105 ++my_current;
106
107 const auto buffer_size = my_buffer.size();
108 if (my_current == buffer_size) {
109 flush_async();
110 my_current = 0;
111 safe_update_position(my_overall, buffer_size, my_overall_failed);
112 }
113 }
114
122 void write(const Type_* input, std::size_t number) {
123 const auto buffer_size = my_buffer.size();
124
125 // Seeing if we fill up the rest of our buffer or not.
126 const std::size_t remaining = buffer_size - my_current;
127 if (number < remaining) {
128 std::copy_n(input, number, my_buffer.data() + my_current);
129 my_current += number;
130 return;
131 }
132
133 std::copy_n(input, remaining, my_buffer.data() + my_current);
134 number -= remaining;
135 input += remaining;
136
137 if (number == 0) {
138 flush_async();
139 safe_update_position(my_overall, buffer_size, my_overall_failed);
140 my_current = 0;
141 return;
142 }
143
144 flush_sync(my_buffer.data(), buffer_size);
145 safe_update_position(my_overall, buffer_size, my_overall_failed);
146
147 // Directly filling the output array, bypassing our buffer.
148 while (number >= buffer_size) {
149 flush_sync(input, buffer_size);
150 safe_update_position(my_overall, buffer_size, my_overall_failed);
151 input += buffer_size;
152 number -= buffer_size;
153 }
154
155 // Filling the cache and copying the remainder into the output array.
156 std::copy_n(input, number, my_buffer.data());
157 my_current = number;
158 }
159
166 void write(const char* string) {
167 write(reinterpret_cast<const Type_*>(string), std::char_traits<char>::length(string));
168 }
169
176 void write(const std::string& string) {
177 write(reinterpret_cast<const Type_*>(string.c_str()), string.size());
178 }
179
180public:
186 void flush() {
187 flush_sync(my_buffer.data(), my_current); // Flush whatever's left.
188 safe_update_position(my_overall, my_current, my_overall_failed);
189 my_current = 0;
190 }
191
198 virtual void finish() = 0;
199};
200
211template<typename Type_, class Pointer_ = std::unique_ptr<Writer> >
212class SerialBufferedWriter final : public BufferedWriter<Type_> {
213public:
220 SerialBufferedWriter(Pointer_ writer, std::size_t buffer_size) : BufferedWriter<Type_>(buffer_size), my_writer(std::move(writer)) {}
221
226 this->flush();
227 }
232private:
233 Pointer_ my_writer;
234
235protected:
239 void flush_async() {
240 auto& buffer = this->get_buffer();
241 my_writer->write(reinterpret_cast<unsigned char*>(buffer.data()), buffer.size());
242 }
243
244 void flush_sync(const Type_* ptr, std::size_t num) {
245 my_writer->write(reinterpret_cast<const unsigned char*>(ptr), num);
246 }
251public:
252 void finish() {
253 this->flush();
254 my_writer->finish();
255 }
256};
257
269template<typename Type_, class Pointer_ = std::unique_ptr<Writer> >
270class ParallelBufferedWriter final : public BufferedWriter<Type_> {
271public:
278 ParallelBufferedWriter(Pointer_ writer, std::size_t buffer_size) :
279 BufferedWriter<Type_>(buffer_size),
280 my_writer(std::move(writer)),
281 my_buffer_worker(sanisizer::cast<I<decltype(my_buffer_worker.size())> >(buffer_size))
282 {
283 my_thread = std::thread([&]() { thread_loop(); }); // set up thread before initializing.
284 }
285
290 this->flush();
291
292 std::unique_lock lck(my_mut);
293 my_kill = true;
294 my_ready_input = true;
295 lck.unlock(); // releasing the lock so that the notified thread doesn't immediately block.
296 my_cv.notify_one();
297 my_thread.join();
298 }
303private:
304 Pointer_ my_writer;
305 std::vector<Type_> my_buffer_worker;
306 std::size_t my_to_write = 0;
307
308private:
309 std::thread my_thread;
310 std::exception_ptr my_thread_err = nullptr;
311 std::mutex my_mut;
312 std::condition_variable my_cv;
313
314 bool my_ready_input = false, my_ready_output = false;
315 bool my_worker_active = false;
316 bool my_kill = false;
317
318 void thread_loop() {
319 while (1) {
320 std::unique_lock lck(my_mut);
321 my_cv.wait(lck, [&]() { return my_ready_input; });
322 my_ready_input = false;
323
324 if (my_kill) { // Handle an explicit kill signal from the destructor.
325 break;
326 }
327
328 try {
329 my_writer->write(reinterpret_cast<const unsigned char*>(my_buffer_worker.data()), my_to_write);
330 } catch (...) {
331 my_thread_err = std::current_exception();
332 }
333
334 my_ready_output = true;
335 lck.unlock();
336 my_cv.notify_one();
337 }
338 }
339
340protected:
344 void flush_async() {
345 auto& buffer = this->get_buffer();
346 const auto num = buffer.size();
347
348 if (my_worker_active) {
349 // If the worker is active, it's probably already gotten started so we just wait for it to finish.
350 // Then we swap the results with the main buffer and submit a new job to the worker.
351 std::unique_lock lck(my_mut);
352 my_cv.wait(lck, [&]() { return my_ready_output; });
353 my_ready_output = false;
354
355 if (my_thread_err) {
356 std::rethrow_exception(my_thread_err);
357 }
358
359 buffer.swap(my_buffer_worker);
360 my_to_write = num;
361
362 my_ready_input = true;
363 lck.unlock();
364 my_cv.notify_one();
365
366 } else {
367 // If the worker is not active, we can just submit directly.
368 std::unique_lock lck(my_mut);
369 buffer.swap(my_buffer_worker);
370 my_to_write = num;
371
372 my_ready_input = true;
373 lck.unlock();
374 my_cv.notify_one();
375 my_worker_active = true;
376 }
377 }
378
379 void flush_sync(const Type_* ptr, std::size_t num) {
380 if (my_worker_active) {
381 // If the worker is active, we wait for it to finish and then write from the supplied pointer.
382 // We do not submit a new job, based on the loop in the BufferedWriter::write() overload:
383 //
384 // - We'll probably want to call this flush() overload again.
385 // On subsequent calls, we'll want to directly write from the supplied pointer to the Writer to cut out a copy.
386 // - If we didn't call this overload again, we'd probably call the other refill() overload immediately,
387 // So if we sent a new job to the worker, we'd end up immediately blocking on it to submit the next write.
388 // So we might as well skip that communication overhead and write directly on the main thread.
389 std::unique_lock lck(my_mut);
390 my_cv.wait(lck, [&]() { return my_ready_output; });
391 my_ready_output = false;
392 if (my_thread_err) {
393 std::rethrow_exception(my_thread_err);
394 }
395
396 my_writer->write(reinterpret_cast<const unsigned char*>(ptr), num);
397 my_worker_active = false;
398
399 } else {
400 // If the worker isn't active, we can directly write from the supplied pointer, avoiding some communication overhead.
401 my_writer->write(reinterpret_cast<const unsigned char*>(ptr), num);
402 }
403 }
408public:
409 void finish() {
410 this->flush();
411 my_writer->finish();
412 }
413};
414
415}
416
417#endif
Write to an output sink.
Buffered wrapper around a Writer.
Definition BufferedWriter.hpp:41
unsigned long long number() const
Definition BufferedWriter.hpp:92
virtual void finish()=0
void flush()
Definition BufferedWriter.hpp:186
void write(const Type_ *input, std::size_t number)
Definition BufferedWriter.hpp:122
void write(const char *string)
Definition BufferedWriter.hpp:166
void write(Type_ input)
Definition BufferedWriter.hpp:103
void write(const std::string &string)
Definition BufferedWriter.hpp:176
Parallelized buffering to wrap a Writer.
Definition BufferedWriter.hpp:270
ParallelBufferedWriter(Pointer_ writer, std::size_t buffer_size)
Definition BufferedWriter.hpp:278
void finish()
Definition BufferedWriter.hpp:409
Serial buffering to wrap a Writer.
Definition BufferedWriter.hpp:212
void finish()
Definition BufferedWriter.hpp:252
SerialBufferedWriter(Pointer_ writer, std::size_t buffer_size)
Definition BufferedWriter.hpp:220
Simple byte readers and writers.
Definition BufferedReader.hpp:25
constexpr Dest_ cast(Value_ x)