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/error_types.hpp b/source/centipede/util/error_types.hpp index 81010444..0d758bb5 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. @@ -99,6 +100,8 @@ struct std::formatter return std::format_to(ctx.out(), "Reader: Filename is either empty or invalid!"); 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..0d23f1d3 --- /dev/null +++ b/source/centipede/util/progress_indicator.hpp @@ -0,0 +1,182 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace centipede::progress +{ + // TODO: + // Overload ProgressAdaptor () to: + // 1) iterate over n elements (instead of increment_fun) + // 2) iterate by 1 (for standard ranges) + // ProgressAdaptor constructor that takes some config struct (to config the prog bar) + // Error Handling! + // Ehm, testing? + class ProgressAdaptor; + + using IncrementFunT = std::function; + + template + requires std::ranges::range + using BaseView = std::views::all_t; + + struct ProgressClosure : std::ranges::range_adaptor_closure + { + ProgressAdaptor* adaptor; + std::size_t total_size_n; + IncrementFunT increment_fun; + + template + requires std::ranges::range + auto operator()(RangeT&& range) + { + return (*adaptor)(std::forward(range), total_size_n, increment_fun); + } + }; + + class ProgressAdaptor + { + public: + template + requires std::ranges::range + auto operator()(RangeT&& range, std::size_t total_size_n, IncrementFunT increment_fun) + { + return ProgressView>{ + this, std::views::all(std::forward(range)), total_size_n, std::move(increment_fun) + }; + } + + template + requires std::ranges::range + auto operator()(RangeT&& range, std::size_t total_size_n) + { + return ProgressView>{ this, + std::views::all(std::forward(range)), + total_size_n, + std::move([]() -> std::size_t { return 1; }) }; + } + + auto operator()(std::size_t total_size_n) + { + return ProgressClosure{ {}, this, total_size_n, []() -> std::size_t { return 1; } }; + } + + auto operator()(std::size_t total_size_n, IncrementFunT increment_fun) + { + return ProgressClosure{ {}, this, total_size_n, std::move(increment_fun) }; + } + + 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(ProgressAdaptor* progress_adaptor, + BaseView base_view, + std::size_t total_size_n, + IncrementFunT increment_fun) + : base_view_(std::move(base_view)) + , total_size_n_(total_size_n) + , increment_fun_(increment_fun) + , progress_adaptor_(progress_adaptor) + { + } + + auto begin() + { + return Iterator{ + progress_adaptor_, this, std::ranges::begin(base_view_), std::ranges::end(base_view_) + }; + } + + auto end() { return Sentinel{}; } + struct Sentinel + { + }; + + class Iterator + { + public: + Iterator(ProgressAdaptor* progress_adaptor, + ProgressView* progress_view, + IteratorType current_it, + SentinelType end_it) + : progress_adaptor_(progress_adaptor) + , progress_view_(progress_view) + , current_it_(current_it) + , end_it_(end_it) + { + } + + auto operator++() + { + ++current_it_; + ++element_count_; + add_progress(); + return *this; + } + + auto operator*() { return *current_it_; } + + bool operator==(Sentinel) + { + return current_it_ == end_it_ || element_count_ >= progress_view_->total_size_n_; + } + + bool operator!=(Sentinel sentinel) { return !(*this == sentinel); } + + bool operator==(Sentinel) const + { + return current_it_ == end_it_ || element_count_ >= progress_view_->total_size_n_; + } + + bool operator!=(Sentinel sentinel) const { return !(*this == sentinel); } + + void add_progress() + { + count_n_ += progress_view_->increment_fun_(); + const auto percent = 100 * count_n_ / progress_view_->total_size_n_; + progress_adaptor_->bar_.set_progress(percent); + if (percent == 100) + { + progress_adaptor_->bar_.mark_as_completed(); + } + } + + private: + ProgressAdaptor* progress_adaptor_; + ProgressView* progress_view_; + IteratorType current_it_; + SentinelType end_it_; + std::size_t element_count_{}; + std::size_t count_n_{}; + }; + + private: + BaseView base_view_; + std::size_t total_size_n_; + IncrementFunT increment_fun_; + ProgressAdaptor* progress_adaptor_; + }; + + private: + indicators::ProgressBar bar_{ indicators::option::BarWidth{ 50 }, + indicators::option::Start{ "[" }, + indicators::option::Fill{ "=" }, + indicators::option::Lead{ ">" }, + indicators::option::Remainder{ " " }, + indicators::option::End{ "]" }, + indicators::option::ForegroundColor{ indicators::Color::green }, + indicators::option::ShowPercentage{ true }, + indicators::option::FontStyles{ + std::vector{ indicators::FontStyle::bold } } }; + }; +} // namespace centipede::progress diff --git a/test/integration_tests/test_reader.cpp b/test/integration_tests/test_reader.cpp index e3ba3fde..1f5a0f06 100644 --- a/test/integration_tests/test_reader.cpp +++ b/test/integration_tests/test_reader.cpp @@ -1,9 +1,15 @@ #include "centipede/reader/binary.hpp" +#include "centipede/util/error_types.hpp" +#include "centipede/util/progress_indicator.hpp" +#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,14 +18,19 @@ auto main() -> int return EXIT_FAILURE; } - for ([[maybe_unused]] const auto& entry : reader) + auto progress_adaptor = centipede::progress::ProgressAdaptor{}; + for ([[maybe_unused]] const auto& entry : + reader | progress_adaptor(reader.get_file_size(), [&reader]() { return reader.get_last_entry_bytes(); })) { } if (not reader.is_ok()) { - std::println(stderr, "Error: {}", reader.get_status()); - return EXIT_FAILURE; + if (reader.get_status() != centipede::ErrorCode::incomplete) + { + std::println(stderr, "Error: {}", reader.get_status()); + return EXIT_FAILURE; + } } if (reader.get_n_entries() == 0U) @@ -28,5 +39,11 @@ auto main() -> int return EXIT_FAILURE; } + auto array = std::array{ 1, 2, 3, 4 }; + + for ([[maybe_unused]] auto elem : array | progress_adaptor(array.size())) + { + } + return EXIT_SUCCESS; }