diff --git a/cmake/load_dependencies.cmake b/cmake/load_dependencies.cmake index bd67d89a..8f0e669a 100644 --- a/cmake/load_dependencies.cmake +++ b/cmake/load_dependencies.cmake @@ -3,7 +3,8 @@ find_package(magic_enum REQUIRED CONFIG) find_package(CLI11 REQUIRED CONFIG) find_package(Eigen3 REQUIRED CONFIG) find_package(GSL REQUIRED) +find_package(indicators REQUIRED) if(ENABLE_TEST) - find_package(GTest CONFIG REQUIRED) + find_package(GTest CONFIG REQUIRED) endif() diff --git a/conanfile.py b/conanfile.py index 662b4bb5..59f9ffee 100644 --- a/conanfile.py +++ b/conanfile.py @@ -15,6 +15,7 @@ def requirements(self): self.requires("magic_enum/0.9.7") # type: ignore self.requires("cli11/2.6.0") # type: ignore self.requires("eigen/5.0.1") # type: ignore + self.requires("indicators/2.3") # type: ignore # Conditions on cmake variables set from cmake/project_options if os.environ["CMAKE_ENABLE_TEST"] == "ON": diff --git a/source/centipede/reader/binary.cpp b/source/centipede/reader/binary.cpp index 7ac5e3ee..490ab3b9 100644 --- a/source/centipede/reader/binary.cpp +++ b/source/centipede/reader/binary.cpp @@ -224,6 +224,7 @@ namespace centipede::reader { return std::unexpected{ size.error() }; } + last_entry_bytes_ = (read_size + 1U) * sizeof(uint32_t); ++n_entries_; size_ = size.value(); return size.value(); diff --git a/source/centipede/reader/binary.hpp b/source/centipede/reader/binary.hpp index 2873318c..935d1d22 100644 --- a/source/centipede/reader/binary.hpp +++ b/source/centipede/reader/binary.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -169,6 +170,15 @@ namespace centipede::reader */ [[nodiscard]] constexpr auto get_n_entries() const -> std::size_t { return n_entries_; } + // TODO: Add documentation + [[nodiscard]] auto get_file_size() const -> std::size_t + { + return static_cast(std::filesystem::file_size(config_.in_filename)); + } + + // TODO: Add documentation + [[nodiscard]] auto get_last_entry_bytes() const -> std::size_t { return last_entry_bytes_; } + /** * @brief Checks if last read operation reached end of file. * @return Returns true if end of file is reached. @@ -181,7 +191,7 @@ namespace centipede::reader * The status is updated during iteration and after manual read operations. * * @return - * - ErrorCode::invalid while iteration/reading is in progress. + * - ErrorCode::incomplete while iteration/reading is in progress. * - ErrorCode::success if iteration finished successfully. * - Any other ErrorCode if a read or parsing error occurred. */ @@ -235,7 +245,7 @@ namespace centipede::reader explicit Iterator(Binary* reader_ptr) : reader_{ reader_ptr } { - reader_->status_ = ErrorCode::invalid; + reader_->status_ = ErrorCode::incomplete; ++(*this); } @@ -276,7 +286,7 @@ namespace centipede::reader } current_ = reader_->get_current_entry(); - reader_->status_ = ErrorCode::invalid; + reader_->status_ = ErrorCode::incomplete; return *this; } /** @@ -296,7 +306,10 @@ namespace centipede::reader * * @return Returns true while iteration is not finished. */ - auto operator!=(const Sentinel&) const -> bool { return reader_->status_ == ErrorCode::invalid; } + auto operator!=(const Sentinel&) const -> bool { return reader_->status_ == ErrorCode::incomplete; } + + // TODO: add documentation + bool operator==(Sentinel) const { return reader_->status_ != ErrorCode::incomplete; } private: Binary* reader_{}; //!< Associated Binary reader instance. @@ -324,8 +337,10 @@ namespace centipede::reader std::ifstream input_file_; //!< Input file handler std::size_t size_{}; //!< Number of Entrypoints in the current entry std::size_t n_entries_{}; //!< Total number of entries read by this instance - bool end_of_file_{ false }; //!< Indicates if end of file is reached. Gets updated on read. - ErrorCode status_{ ErrorCode::invalid }; + std::size_t last_entry_bytes_{}; // TODO: Add documentation + + bool end_of_file_{ false }; //!< Indicates if end of file is reached. Gets updated on read. + ErrorCode status_{ ErrorCode::incomplete }; void reset(); auto read_entry_to_buffer(uint32_t read_size) -> EnumError<>; diff --git a/source/centipede/util/CMakeLists.txt b/source/centipede/util/CMakeLists.txt index 9034c594..8e985f7c 100644 --- a/source/centipede/util/CMakeLists.txt +++ b/source/centipede/util/CMakeLists.txt @@ -3,5 +3,7 @@ target_sources( PUBLIC FILE_SET publicHeaders TYPE HEADERS - FILES common_traits.hpp error_types.hpp return_types.hpp + FILES common_traits.hpp error_types.hpp return_types.hpp progress_indicator.hpp ) + +target_link_libraries(core PUBLIC indicators::indicators) diff --git a/source/centipede/util/common_definitions.hpp b/source/centipede/util/common_definitions.hpp index 1854c256..50c09233 100644 --- a/source/centipede/util/common_definitions.hpp +++ b/source/centipede/util/common_definitions.hpp @@ -6,4 +6,5 @@ namespace centipede::common { constexpr auto DEFAULT_BUFFER_SIZE = std::size_t{ 10000 }; //!< Default maximum buffer size for binary readers/writers. + constexpr auto DEFAULT_INDICATOR_BAR_WIDTH = std::size_t{ 50 }; } // namespace centipede::common diff --git a/source/centipede/util/error_types.hpp b/source/centipede/util/error_types.hpp index 81010444..bf82f7e7 100644 --- a/source/centipede/util/error_types.hpp +++ b/source/centipede/util/error_types.hpp @@ -13,6 +13,7 @@ namespace centipede // TODO: duplication of comments invalid, //!< Error due to no evaluation! success, //!< No error. All good! + incomplete, //!< Operation incomplete! handler_incomp_n_locals, //!< Incompatible number of local variables from the current entrypoint. writer_neg_or_zero_sigma, //!< Zero or negative sigma occurs. See @ref writer::Binary. writer_buffer_overflow, //!< Buffer size is too small for a new entry occurs. See @ref writer::Binary. @@ -33,6 +34,9 @@ namespace centipede reader_uninitialized, //!< Reader is not initialized. reader_buffer_overflow, //!< Buffer size is too small for a new entry occurs. See @ref reader::Binary. reader_invalid_filename, //!< Filename is invalid or empty + progress_zero_size, //!< Given size is zero + progress_inc_exceeds_size, //!< Increment exceeds given size + progress_inc_returns_zero //!< Increment returns zero }; } // namespace centipede @@ -97,8 +101,16 @@ struct std::formatter return std::format_to(ctx.out(), "Reader: Cannot read the file. Buffer size will be exceeded!"); case reader_invalid_filename: return std::format_to(ctx.out(), "Reader: Filename is either empty or invalid!"); + case progress_zero_size: + return std::format_to(ctx.out(), "Progress: Given size is 0!"); + case progress_inc_exceeds_size: + return std::format_to(ctx.out(), "Progress: Increment exceeds given size!"); + case progress_inc_returns_zero: + return std::format_to(ctx.out(), "Progress: Increment returns 0!"); case invalid: return std::format_to(ctx.out(), "Error due to no evaluation!"); + case incomplete: + return std::format_to(ctx.out(), "Operation incomplete!"); default: break; } diff --git a/source/centipede/util/progress_indicator.hpp b/source/centipede/util/progress_indicator.hpp new file mode 100644 index 00000000..25dd4a91 --- /dev/null +++ b/source/centipede/util/progress_indicator.hpp @@ -0,0 +1,500 @@ +#include "centipede/util/common_definitions.hpp" +#include "centipede/util/error_types.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace centipede +{ + // TODO: + // Error Handling! + // Ehm, testing? + + template + concept is_increment_function = std::invocable && std::same_as, std::size_t>; + + class ProgressIndicator + { + public: + struct Config + { + bool enable_percentage = true; + std::size_t bar_width = common::DEFAULT_INDICATOR_BAR_WIDTH; + std::string label_text; + }; + + ProgressIndicator() = default; + + explicit ProgressIndicator(Config config) + : config_(std::move(config)) + { + } + + ProgressIndicator(ProgressIndicator&&) = delete; + ProgressIndicator& operator=(ProgressIndicator&&) = delete; + ProgressIndicator(const ProgressIndicator&) = delete; + ProgressIndicator& operator=(const ProgressIndicator&) = delete; + + auto get_adaptor() & { return adaptor_; } + + template + auto get_adaptor(Args&&... args) & + { + return adaptor_(std::forward(args)...); + } + + template + auto get_adaptor(Args&&...) && = delete; + + private: + template + struct ProgressView : std::ranges::view_interface> + { + using IteratorType = std::ranges::iterator_t; + using SentinelType = std::ranges::sentinel_t; + + ProgressView(BaseView&& view, + std::size_t total_size, + IncrementFunctionT&& inc_func, + ProgressIndicator* indicator) + : base_view(std::move(view)) + , total_size_n(total_size) + , increment_function(std::move(inc_func)) + , progress_indicator(indicator) + { + assert(progress_indicator); + } + + auto begin() + { + assert(progress_indicator); + + if (total_size_n == 0UZ) + { + progress_indicator->status_ = ErrorCode::progress_zero_size; + progress_indicator->bar_.mark_as_completed(); + } + + return Iterator{ this, std::ranges::begin(base_view), std::ranges::end(base_view) }; + } + + auto end() { return Sentinel{}; } + + struct Sentinel + { + }; + + class Iterator + { + public: + Iterator(ProgressView* progress_view, IteratorType current_it, SentinelType end_it) + : progress_view_(progress_view) + , current_it_(current_it) + , end_it_(end_it) + { + assert(progress_view_); + assert(progress_view_->progress_indicator); + } + + auto operator++() -> Iterator& + { + assert(current_it_ != end_it_); + + add_progress(); + ++current_it_; + + return *this; + } + + decltype(auto) operator*() const + { + assert(current_it_ != end_it_); + return *current_it_; + } + + bool operator==(Sentinel) const { return current_it_ == end_it_; } + + bool operator!=(Sentinel sentinel) const { return !(*this == sentinel); } + + private: + void add_progress() + { + assert(progress_view_); + assert(progress_view_->progress_indicator); + + auto& indicator = *progress_view_->progress_indicator; + + if (progress_view_->total_size_n == 0UZ) + { + indicator.status_ = ErrorCode::progress_zero_size; + return; + } + + assert(count_n_ <= progress_view_->total_size_n); + + const auto increment = progress_view_->increment_function(); // NOTE: std::invoke? + + if (increment == 0UZ) + { + indicator.status_ = ErrorCode::progress_inc_returns_zero; + return; + } + + const auto remaining = progress_view_->total_size_n - count_n_; + + if (increment > remaining) + { + count_n_ = progress_view_->total_size_n; + + indicator.status_ = ErrorCode::progress_inc_exceeds_size; + } + else + { + count_n_ += increment; + } + + const auto percent = 100UZ * count_n_ / progress_view_->total_size_n; + + indicator.bar_.set_progress(percent); + + if (count_n_ == progress_view_->total_size_n) + { + indicator.status_ = ErrorCode::success; + } + } + + ProgressView* progress_view_ = nullptr; + IteratorType current_it_; + SentinelType end_it_; + std::size_t count_n_{}; + }; + + BaseView base_view; + std::size_t total_size_n{}; + IncrementFunctionT increment_function; + ProgressIndicator* progress_indicator = nullptr; + }; + + struct ProgressAdaptor : std::ranges::range_adaptor_closure + { + ProgressAdaptor(ProgressIndicator* indicator) + : progress_indicator(indicator) + { + } + + auto operator()(std::ranges::sized_range auto&& range) + { + return ProgressView{ std::views::all(std::forward(range)), + std::ranges::size(range), + []() { return 1UZ; }, + progress_indicator }; + } + + auto operator()(std::ranges::viewable_range auto&& range, + std::size_t total_size, + is_increment_function auto&& inc_func) + { + return ProgressView{ std::views::all(std::forward(range)), + total_size, + std::move(inc_func), + progress_indicator }; + } + + auto operator()(std::ranges::viewable_range auto&& range, std::size_t total_size) + { + return ProgressView{ std::views::all(std::forward(range)), + total_size, + []() { return 1UZ; }, + progress_indicator }; + } + + auto operator()(std::size_t total_size) + { + return ProgressClosure{ total_size, []() { return 1UZ; }, progress_indicator }; + } + + auto operator()(std::size_t total_size, is_increment_function auto&& inc_func) + { + return ProgressClosure{ total_size, std::move(inc_func), progress_indicator }; + } + + template + struct ProgressClosure : std::ranges::range_adaptor_closure> + { + ProgressClosure(std::size_t total_size, IncrementFunctionT&& inc_func, ProgressIndicator* indicator) + : total_size_n(total_size) + , increment_function(std::move(inc_func)) + , progress_indicator(indicator) + { + } + + auto operator()(std::ranges::viewable_range auto&& range) + { + return ProgressView{ std::views::all(std::forward(range)), + total_size_n, + std::move(increment_function), + progress_indicator }; + } + + std::size_t total_size_n{}; + IncrementFunctionT increment_function; + ProgressIndicator* progress_indicator = nullptr; + }; + + ProgressIndicator* progress_indicator = nullptr; + }; + + Config config_{}; + + indicators::ProgressBar bar_{ indicators::option::BarWidth{ config_.bar_width }, + indicators::option::Start{ " [" }, + indicators::option::Fill{ "=" }, + indicators::option::Lead{ ">" }, + indicators::option::Remainder{ "-" }, + indicators::option::End{ "]" }, + indicators::option::PrefixText{ config_.label_text }, + indicators::option::ForegroundColor{ indicators::Color::yellow }, + indicators::option::ShowPercentage{ true }, + indicators::option::ShowElapsedTime{ true }, + indicators::option::ShowRemainingTime{ true }, + indicators::option::FontStyles{ + std::vector{ indicators::FontStyle::bold } } }; + ErrorCode status_{}; + ProgressAdaptor adaptor_{ this }; + }; +} // namespace centipede + +/********************* OLD *************************/ + +// using ProgressFontStyle = indicators::FontStyle; +// using ProgressColor = indicators::Color; +// +// class ProgressAdaptor : public std::ranges::range_adaptor_closure +// { +// public: +// using IncrementFunT = std::function; +// +// ProgressAdaptor() = default; +// +// template +// explicit ProgressAdaptor(iopts&&... options) +// : bar_ptr_{ std::make_shared(std::forward(options)...) } +// { +// } +// +// template +// requires std::ranges::range +// using BaseView = std::views::all_t; +// +// struct ProgressClosure : std::ranges::range_adaptor_closure +// { +// std::size_t total_size_n; +// IncrementFunT increment_fun; +// +// std::shared_ptr bar_ptr; +// std::shared_ptr status_ptr; +// +// template +// requires std::ranges::range +// auto operator()(RangeT&& range) +// { +// using ViewT = std::views::all_t; +// +// return ProgressView{ +// std::views::all(std::forward(range)), total_size_n, increment_fun, bar_ptr, +// status_ptr +// }; +// } +// }; +// +// template +// requires std::ranges::range +// auto operator()(RangeT&& range, std::size_t total_size_n, IncrementFunT increment_fun) +// { +// return ProgressView>{ std::views::all(std::forward(range)), +// total_size_n, +// std::move(increment_fun), +// bar_ptr_, +// status_ptr_ }; +// } +// +// template +// requires std::ranges::range +// auto operator()(RangeT&& range, std::size_t total_size_n) +// { +// return ProgressView>{ std::views::all(std::forward(range)), +// total_size_n, +// []() -> std::size_t { return 1UZ; }, +// bar_ptr_, +// status_ptr_ }; +// } +// +// template +// requires std::ranges::sized_range +// auto operator()(RangeT&& range) +// { +// return ProgressView>{ std::views::all(std::forward(range)), +// std::ranges::size(range), +// []() -> std::size_t { return 1UZ; }, +// bar_ptr_, +// status_ptr_ }; +// } +// +// auto operator()(std::size_t total_size_n) +// { +// return ProgressClosure{ {}, total_size_n, []() -> std::size_t { return 1UZ; }, bar_ptr_, status_ptr_ +// }; +// } +// +// auto operator()(std::size_t total_size_n, IncrementFunT increment_fun) +// { +// return ProgressClosure{ {}, total_size_n, std::move(increment_fun), bar_ptr_, status_ptr_ }; +// } +// +// [[nodiscard]] auto get_status() const -> ErrorCode { return *status_ptr_; } +// +// template +// requires std::ranges::range +// struct ProgressView +// { +// using BaseView = std::views::all_t; +// using IteratorType = std::ranges::iterator_t; +// using SentinelType = std::ranges::sentinel_t; +// +// ProgressView(BaseView base_view, +// std::size_t total_size_n, +// IncrementFunT increment_fun, +// std::shared_ptr bar_ptr, +// std::shared_ptr status_ptr) +// : base_view_(std::move(base_view)) +// , total_size_n_(total_size_n) +// , increment_fun_(std::move(increment_fun)) +// , bar_ptr_(std::move(bar_ptr)) +// , status_ptr_(std::move(status_ptr)) +// { +// assert(bar_ptr_); +// assert(status_ptr_); +// assert(increment_fun_); +// } +// +// auto get_status() -> ErrorCode { return *status_ptr_; } +// +// auto begin() +// { +// if (total_size_n_ == 0UZ) +// { +// *status_ptr_ = ErrorCode::progress_zero_size; +// +// bar_ptr_->mark_as_completed(); +// } +// +// return Iterator{ this, std::ranges::begin(base_view_), std::ranges::end(base_view_) }; +// } +// +// auto end() { return Sentinel{}; } +// +// struct Sentinel +// { +// }; +// +// class Iterator +// { +// public: +// Iterator(ProgressView* progress_view, IteratorType current_it, SentinelType end_it) +// : progress_view_(progress_view) +// , current_it_(current_it) +// , end_it_(end_it) +// { +// assert(progress_view_); +// } +// +// auto operator++() -> Iterator& +// { +// assert(current_it_ != end_it_); +// add_progress(); +// ++current_it_; +// return *this; +// } +// +// auto operator*() +// { +// assert(current_it_ != end_it_); +// return *current_it_; +// } +// +// bool operator==(Sentinel) { return current_it_ == end_it_; } +// +// bool operator!=(Sentinel sentinel) { return !(*this == sentinel); } +// +// bool operator==(Sentinel) const { return current_it_ == end_it_; } +// +// bool operator!=(Sentinel sentinel) const { return !(*this == sentinel); } +// +// void add_progress() +// { +// assert(progress_view_); +// assert(progress_view_->bar_ptr_); +// assert(progress_view_->status_ptr_); +// assert(progress_view_->increment_fun_); +// assert(count_n_ <= progress_view_->total_size_n_); +// const auto increment = progress_view_->increment_fun_(); +// +// if (increment == 0UZ) +// { +// *progress_view_->status_ptr_ = ErrorCode::progress_inc_returns_zero; +// return; +// } +// +// const auto remaining = progress_view_->total_size_n_ - count_n_; +// +// if (increment > remaining) +// { +// count_n_ = progress_view_->total_size_n_; +// +// *progress_view_->status_ptr_ = ErrorCode::progress_inc_exceeds_size; +// } +// else +// { +// count_n_ += increment; +// } +// +// const auto percent = 100UZ * count_n_ / progress_view_->total_size_n_; +// +// progress_view_->bar_ptr_->set_progress(percent); +// +// if (count_n_ == progress_view_->total_size_n_) +// { +// *(progress_view_->status_ptr_) = ErrorCode::success; +// } +// } +// +// private: +// ProgressView* progress_view_; +// IteratorType current_it_; +// SentinelType end_it_; +// std::size_t count_n_{}; +// }; +// +// private: +// BaseView base_view_; +// std::size_t total_size_n_; +// IncrementFunT increment_fun_; +// std::shared_ptr bar_ptr_ = nullptr; +// std::shared_ptr status_ptr_ = nullptr; +// }; +// +// private: +// std::shared_ptr bar_ptr_{ std::make_shared() }; +// std::shared_ptr status_ptr_ = std::make_shared(ErrorCode::incomplete); +// }; + +// } // namespace centipede diff --git a/test/integration_tests/test_reader.cpp b/test/integration_tests/test_reader.cpp index e3ba3fde..026a4d9f 100644 --- a/test/integration_tests/test_reader.cpp +++ b/test/integration_tests/test_reader.cpp @@ -1,9 +1,20 @@ #include "centipede/reader/binary.hpp" +#include "centipede/util/error_types.hpp" +#include "centipede/util/progress_indicator.hpp" +#include #include +#include +#include +#include +#include #include +#include +#include auto main() -> int { + static_assert(std::ranges::range); + static_assert(std::ranges::input_range); auto reader = centipede::reader::Binary{ centipede::reader::Binary::Config{ .in_filename = "output.bin" } }; auto init_err = reader.init(); if (not init_err.has_value()) @@ -12,21 +23,66 @@ auto main() -> int return EXIT_FAILURE; } - for ([[maybe_unused]] const auto& entry : reader) + auto progress_indicator = centipede::ProgressIndicator{}; + + std::size_t total_read{}; + + for ([[maybe_unused]] const auto& entry : + reader | progress_indicator.get_adaptor(reader.get_file_size(), + [&reader]() { return reader.get_last_entry_bytes(); })) { + total_read += reader.get_last_entry_bytes(); } - if (not reader.is_ok()) + if (total_read != reader.get_file_size()) { - std::println(stderr, "Error: {}", reader.get_status()); + std::println(stderr, "Error: not all reads counted"); return EXIT_FAILURE; } + if (not reader.is_ok()) + { + if (reader.get_status() != centipede::ErrorCode::incomplete) + { + std::println(stderr, "Error: {}", reader.get_status()); + return EXIT_FAILURE; + } + } + if (reader.get_n_entries() == 0U) { std::println(stderr, "Error: no entries read"); return EXIT_FAILURE; } + // if (const auto progress_adaptor_status = progress_adaptor.get_status(); + // progress_adaptor_status != centipede::ErrorCode::success) + // { + // std::println(stderr, "Error: {}", progress_adaptor_status); + // return EXIT_FAILURE; + // } + // + // auto array = std::array{ 1, 2, 3, 4 }; + // + // auto progress_view = array | centipede::progress::ProgressAdaptor{}; + // + // for ([[maybe_unused]] auto elem : progress_view) + // { + // } + // + // if (const auto progress_view_status = progress_view.get_status(); + // progress_view_status != centipede::ErrorCode::success) + // { + // std::println(stderr, "Error: {}", progress_view_status); + // return EXIT_FAILURE; + // } + // + // if (const auto progress_adaptor_status = progress_adaptor.get_status(); + // progress_adaptor_status != centipede::ErrorCode::success) + // { + // std::println(stderr, "Error: {}", progress_adaptor_status); + // return EXIT_FAILURE; + // } + return EXIT_SUCCESS; }