9#include "arg_wrappers.hpp"
14#include "int_types.hpp"
15#include "introspect.hpp"
16#include "processor.hpp"
33#include <system_error>
56 std::move_constructible<T> &&
57 requires(T &s, std::uint64_t bytes, std::span<std::byte> buf) {
58 { s.is_error() }
noexcept -> std::same_as<bool>;
59 { s.is_eof() }
noexcept -> std::same_as<bool>;
60 { s.is_good() }
noexcept -> std::same_as<bool>;
61 { s.tell() }
noexcept -> std::same_as<std::optional<std::uint64_t>>;
62 { s.skip(bytes) }
noexcept -> std::same_as<bool>;
63 { s.read(buf) }
noexcept -> std::same_as<std::uint64_t>;
69 static auto is_error() noexcept ->
bool {
return false; }
70 static auto is_eof() noexcept ->
bool {
return true; }
71 static auto is_good() noexcept ->
bool {
return false; }
72 static auto tell() noexcept -> std::optional<std::uint64_t> {
return 0; }
73 static auto skip(std::uint64_t bytes)
noexcept ->
bool {
76 static auto read(std::span<std::byte> )
noexcept
84template <
typename IStream>
class istream_input_stream {
85 static_assert(std::is_base_of_v<std::istream, IStream>);
89 explicit istream_input_stream(IStream stream) : stream(std::move(stream)) {
90 this->stream.exceptions(std::ios::goodbit);
93 auto is_error() noexcept ->
bool {
94 auto const flags = stream.rdstate();
95 return ((flags & std::ios::failbit) || (flags & std::ios::badbit)) &&
96 not(flags & std::ios::eofbit);
99 auto is_eof() noexcept ->
bool {
return stream.eof(); }
101 auto is_good() noexcept ->
bool {
return stream.good(); }
103 auto tell() noexcept -> std::optional<std::uint64_t> {
106 std::int64_t
const pos = stream.tellg();
108 return std::uint64_t(pos);
113 auto skip(std::uint64_t bytes)
noexcept ->
bool {
115 bytes > std::uint64_t(std::numeric_limits<std::streamoff>::max()))
117 stream.seekg(std::streamoff(bytes), std::ios::cur);
118 auto const ret = stream.good();
123 auto read(std::span<std::byte>
buffer)
noexcept -> std::uint64_t {
125 stream.read(
reinterpret_cast<char *
>(
buffer.data()),
126 static_cast<std::streamsize
>(
buffer.size()));
127 return static_cast<std::uint64_t
>(stream.gcount());
132class cfile_input_stream {
137 explicit cfile_input_stream(std::FILE *stream,
bool close_on_destruction)
138 : fp(stream), should_close(close_on_destruction && fp != nullptr) {}
140 cfile_input_stream(cfile_input_stream
const &) =
delete;
141 auto operator=(cfile_input_stream
const &) =
delete;
143 cfile_input_stream(cfile_input_stream &&other) noexcept
144 : fp(std::exchange(other.fp,
nullptr)),
145 should_close(std::exchange(other.should_close,
false)) {}
147 auto operator=(cfile_input_stream &&) =
delete;
149 ~cfile_input_stream() {
151 (void)std::fclose(fp);
154 auto is_error() noexcept ->
bool {
155 return fp ==
nullptr || std::ferror(fp) != 0;
158 auto is_eof() noexcept ->
bool {
159 return fp !=
nullptr && std::feof(fp) != 0;
162 auto is_good() noexcept ->
bool {
163 return fp !=
nullptr && std::ferror(fp) == 0 && std::feof(fp) == 0;
166 auto tell() noexcept -> std::optional<std::uint64_t> {
176 return std::uint64_t(pos);
180 auto skip(std::uint64_t bytes)
noexcept ->
bool {
184 if (bytes <= std::uint64_t(std::numeric_limits<__int64>::max()))
185 return ::_fseeki64(fp, __int64(bytes), SEEK_CUR) == 0;
187 if (bytes <= std::numeric_limits<long>::max())
188 return std::fseek(fp,
long(bytes), SEEK_CUR) == 0;
193 auto read(std::span<std::byte>
buffer)
noexcept -> std::uint64_t {
200template <
typename InputStream>
201 requires input_stream<InputStream>
202inline void skip_stream_bytes(InputStream &stream, std::uint64_t bytes) {
203 if (not stream.skip(bytes)) {
206 std::uint64_t bytes_discarded = 0;
209 static constexpr std::streamsize bufsize = 32768;
210 std::vector<std::byte> buf(bufsize);
211 std::span<std::byte>
const bufspan(buf);
212 while (bytes_discarded < bytes) {
214 std::min<std::uint64_t>(bufsize, bytes - bytes_discarded);
215 bytes_discarded += stream.read(bufspan.first(read_size));
216 if (not stream.is_good())
224unbuffered_binary_ifstream_input_stream(std::string
const &filename,
225 arg::start_offset<u64> start_offset) {
226 std::ifstream stream;
231 stream.rdbuf()->pubsetbuf(
nullptr, 0);
233 stream.open(filename, std::ios::binary);
235 throw input_output_error(
"failed to open input file: " + filename);
236 auto ret = internal::istream_input_stream(std::move(stream));
237 skip_stream_bytes(ret, start_offset.value);
242inline auto binary_ifstream_input_stream(std::string
const &filename,
243 arg::start_offset<u64> start_offset) {
244 std::ifstream stream;
245 stream.open(filename, std::ios::binary);
247 throw input_output_error(
"failed to open input file: " + filename);
248 auto ret = internal::istream_input_stream(std::move(stream));
249 skip_stream_bytes(ret, start_offset.value);
253inline auto unbuffered_binary_cfile_input_stream(
254 std::string
const &filename,
255 arg::start_offset<std::uint64_t> start_offset) {
258 (void)fopen_s(&fp, filename.c_str(),
"rb");
262 std::FILE *fp = std::fopen(filename.c_str(),
"rb");
266 throw std::system_error(errno, std::generic_category());
267 throw input_output_error(
"failed to open input file: " + filename);
269 if (std::setvbuf(fp,
nullptr, _IONBF, 0) != 0)
270 throw input_output_error(
271 "failed to disable buffering for input file: " + filename);
272 auto ret = internal::cfile_input_stream(fp,
true);
273 skip_stream_bytes(ret, start_offset.value);
278inline auto binary_cfile_input_stream(std::string
const &filename,
279 arg::start_offset<u64> start_offset) {
282 (void)fopen_s(&fp, filename.c_str(),
"rb");
286 std::FILE *fp = std::fopen(filename.c_str(),
"rb");
290 throw std::system_error(errno, std::generic_category());
291 throw input_output_error(
"failed to open input file: " + filename);
293 auto ret = internal::cfile_input_stream(fp,
true);
294 skip_stream_bytes(ret, start_offset.value);
324 std::string
const &filename,
328 return internal::unbuffered_binary_cfile_input_stream(filename,
350 static_assert(std::is_base_of_v<std::istream, IStream>);
351 return internal::istream_input_stream(std::move(stream));
373 return internal::cfile_input_stream(fp,
true);
399 return internal::cfile_input_stream(fp,
false);
404template <
typename InputStream,
typename Event,
typename Downstream>
405 requires input_stream<InputStream> &&
406 processor<Downstream, bucket<Event>, warning_event>
409 std::is_trivial_v<Event>,
410 "Event type must be trivial to work with read_binary_stream");
413 std::uint64_t length;
415 std::size_t read_granularity;
416 std::shared_ptr<bucket_source<Event>> bsource;
418 Downstream downstream;
420 LIBTCSPC_NOINLINE
auto first_read_size() -> std::uint64_t {
421 auto ret = read_granularity;
422 if (stream.is_good()) {
427 std::optional<std::uint64_t> pos = stream.tell();
429 ret -= *pos % read_granularity;
436 auto read_units(std::span<std::byte> dest, std::size_t max_units,
437 std::uint64_t &total_bytes_read) -> std::uint64_t {
439 std::min<std::uint64_t>(dest.size(), length - total_bytes_read);
440 if (total_bytes_read == 0) {
442 std::min<std::uint64_t>(bytes_to_read, first_read_size());
444 if (bytes_to_read > read_granularity) {
445 bytes_to_read = read_granularity *
446 std::min<std::uint64_t>(
447 max_units, bytes_to_read / read_granularity);
449 auto const bytes_read = stream.read(dest.first(bytes_to_read));
450 total_bytes_read += bytes_read;
455 explicit read_binary_stream(
456 InputStream stream, arg::max_length<std::uint64_t> max_length,
457 std::shared_ptr<bucket_source<Event>> buffer_provider,
458 arg::granularity<std::size_t> granularity, Downstream downstream)
459 : stream(std::move(stream)), length(max_length.value),
460 read_granularity(granularity.value),
461 bsource(std::move(buffer_provider)),
462 downstream(std::move(downstream)) {
464 throw std::invalid_argument(
465 "read_binary_stream buffer_provider must not be null");
466 if (read_granularity <= 0)
467 throw std::invalid_argument(
468 "read_binary_stream granularity must be positive");
471 [[nodiscard]]
auto introspect_node() const -> processor_info {
472 return processor_info(
this,
"read_binary_stream");
475 [[nodiscard]]
auto introspect_graph() const -> processor_graph {
476 return downstream.introspect_graph().push_entry_point(
this);
480 auto const bucket_size =
481 sizeof(Event) >= read_granularity
483 : (read_granularity - 1) / sizeof(Event) + 1;
484 auto const bucket_size_bytes = bucket_size *
sizeof(Event);
486 std::uint64_t total_bytes_read = 0;
488 std::size_t remainder_nbytes = 0;
490 while (total_bytes_read < length && stream.is_good()) {
491 auto const bytes_left_in_bucket =
492 bucket_size_bytes - remainder_nbytes;
493 if (bytes_left_in_bucket >= read_granularity) {
495 bkt = bsource->bucket_of_size(bucket_size);
496 auto const bytes_read = read_units(
497 std::as_writable_bytes(std::span(bkt))
498 .subspan(remainder_nbytes),
499 std::numeric_limits<std::size_t>::max(), total_bytes_read);
500 auto const available_nbytes = remainder_nbytes + bytes_read;
501 auto const this_batch_size = available_nbytes / sizeof(Event);
502 remainder_nbytes = available_nbytes % sizeof(Event);
503 if (this_batch_size == 0)
506 if (remainder_nbytes > 0) {
507 bkt2 = bsource->bucket_of_size(bucket_size);
508 auto const remainder_span =
509 std::as_bytes(std::span(bkt))
510 .subspan(available_nbytes - remainder_nbytes);
511 std::copy(remainder_span.begin(), remainder_span.end(),
512 std::as_writable_bytes(std::span(bkt2)).begin());
514 bkt.shrink(0, this_batch_size);
515 downstream.handle(std::move(bkt));
516 bkt = std::move(bkt2);
519 bkt2 = bsource->bucket_of_size(bucket_size);
520 auto const bytes_read =
521 read_units(std::as_writable_bytes(std::span(bkt2)), 1,
523 if (bytes_read < bytes_left_in_bucket)
525 auto const top_off_span =
526 std::as_bytes(std::span(bkt2)).first(bytes_left_in_bucket);
527 auto const remainder_span = std::as_bytes(std::span(bkt2))
529 .subspan(bytes_left_in_bucket);
530 std::copy(top_off_span.begin(), top_off_span.end(),
531 std::as_writable_bytes(std::span(bkt))
532 .last(bytes_left_in_bucket)
534 std::copy(remainder_span.begin(), remainder_span.end(),
535 std::as_writable_bytes(std::span(bkt2)).begin());
536 downstream.handle(std::move(bkt));
537 bkt = std::move(bkt2);
538 remainder_nbytes = remainder_span.size();
542 if (stream.is_error())
543 throw input_output_error(
"failed to read input");
544 if (remainder_nbytes > 0) {
545 downstream.handle(warning_event{
546 "bytes fewer than record size remain at end of input"});
613template <
typename Event,
typename InputStream,
typename Downstream>
618 Downstream downstream) {
620 if constexpr (std::is_base_of_v<std::istream, InputStream>) {
622 return internal::read_binary_stream<
decltype(wrapped), Event,
624 std::move(wrapped), max_length, std::move(buffer_provider),
625 granularity, std::move(downstream));
627 return internal::read_binary_stream<InputStream, Event, Downstream>(
628 std::move(stream), max_length, std::move(buffer_provider),
629 granularity, std::move(downstream));
auto buffer(arg::threshold< std::size_t > threshold, access_tracker< buffer_accessor > &&tracker, Downstream downstream)
Create a processor that buffers events and emits them on a different thread.
Definition buffer.hpp:366
auto read_binary_stream(InputStream stream, arg::max_length< std::uint64_t > max_length, std::shared_ptr< bucket_source< Event > > buffer_provider, arg::granularity< std::size_t > granularity, Downstream downstream)
Create a source that reads batches of events from a binary stream, such as a file.
Definition read_binary_stream.hpp:614
std::uint64_t u64
Short name for uint64_t.
Definition int_types.hpp:33
libtcspc namespace.
Definition acquire.hpp:30
Function argument wrapper for granularity parameter.
Definition arg_wrappers.hpp:147
Function argument wrapper for maximum length parameter.
Definition arg_wrappers.hpp:267
Function argument wrapper for start offset parameter.
Definition arg_wrappers.hpp:377
Abstract base class for polymorphic bucket sources.
Definition bucket.hpp:504