// This code is licensed under the MIT License. // Please see the LICENSE file in the root of the repository for the full license text. // Copyright (c) 2023-2024 Konvt #pragma once #ifndef __PROGRESSBAR_HPP__ # define __PROGRESSBAR_HPP__ # include # include // std::numeric_limits # include // std::round, std::log10, std::trunc, std::ceil # include // SFINAE # include // std::pair # include // std::reference_wrapper # include // marks iterator tags # include // std::bitset # include // std::string # include // as u know # include // std::exception # include // std::cerr, the output stream object used # include // std::atomic # include // std::thread # include // std::mutex & std::unique_lock # include // std::condition_variable #if defined(__GNUC__) || defined(__clang__) # define __PGBAR_INLINE_FUNC__ __attribute__((always_inline)) inline # define __PGBAR_NODISCARD__ __attribute__((warn_unused_result)) #elif defined(_MSC_VER) # define __PGBAR_INLINE_FUNC__ __forceinline inline # define __PGBAR_NODISCARD__ _Check_return_ #else # define __PGBAR_INLINE_FUNC__ inline # define __PGBAR_NODISCARD__ #endif #if defined(_MSVC_VER) && defined(_MSVC_LANG) // for msvc # define __PGBAR_CMP_V__ _MSVC_LANG #else # define __PGBAR_CMP_V__ __cplusplus #endif #if defined(_WIN32) || defined(WIN32) # include # define __PGBAR_WIN__ 1 # define __PGBAR_UNIX__ 0 # define __PGBAR_UNKNOW_PLATFORM__ 0 #elif defined(__unix__) || defined(unix) # include # define __PGBAR_WIN__ 0 # define __PGBAR_UNIX__ 1 # define __PGBAR_UNKNOW_PLATFORM__ 0 #else # define __PGBAR_WIN__ 0 # define __PGBAR_UNIX__ 0 # define __PGBAR_UNKNOW_PLATFORM__ 1 #endif #if __PGBAR_CMP_V__ >= 202002L # include // std::same_as # include // std::format # define __PGBAR_CXX20__ 1 #else # define __PGBAR_CXX20__ 0 #endif // __cplusplus >= 202002L #if __PGBAR_CMP_V__ >= 201703L # include // std::string_view # define __PGBAR_CXX17__ 1 # define __PGBAR_INLINE_VAR__ inline # define __PGBAR_ENHANCE_CONSTEXPR__ constexpr # define __PGBAR_FALLTHROUGH__ [[fallthrough]]; # undef __PGBAR_NODISCARD__ # define __PGBAR_NODISCARD__ [[nodiscard]] #else # define __PGBAR_CXX17__ 0 # define __PGBAR_INLINE_VAR__ # define __PGBAR_ENHANCE_CONSTEXPR__ # define __PGBAR_FALLTHROUGH__ #endif // __cplusplus >= 201703L #if __PGBAR_CMP_V__ >= 201402L # define __PGBAR_CXX14__ 1 # define __PGBAR_RET_CONSTEXPR__ constexpr #else # define __PGBAR_CXX14__ 0 # define __PGBAR_RET_CONSTEXPR__ #endif // __cplusplus >= 201402L #ifndef PGBAR_NOT_COL /* Specify the color and font style for the status bar. */ # define __PGBAR_BOLD__ "\x1B[1m" # define __PGBAR_BLACK__ "\x1B[30m" # define __PGBAR_RED__ "\x1B[31m" # define __PGBAR_GREEN__ "\x1B[32m" # define __PGBAR_YELLOW__ "\x1B[33m" # define __PGBAR_BLUE__ "\x1B[34m" # define __PGBAR_MAGENTA__ "\x1B[35m" # define __PGBAR_CYAN__ "\x1B[36m" # define __PGBAR_WHITE__ "\x1B[37m" # define __PGBAR_DEFAULT_COL__ "\x1B[0m" #else # define __PGBAR_BOLD__ "" # define __PGBAR_BLACK__ "" # define __PGBAR_RED__ "" # define __PGBAR_GREEN__ "" # define __PGBAR_YELLOW__ "" # define __PGBAR_BLUE__ "" # define __PGBAR_MAGENTA__ "" # define __PGBAR_CYAN__ "" # define __PGBAR_WHITE__ "" # define __PGBAR_DEFAULT_COL__ "" #endif // PGBAR_NOT_COL namespace pgbar { class bad_pgbar : public std::exception { protected: std::string message; public: bad_pgbar( std::string _mes ) : message { std::move( _mes ) } {} virtual ~bad_pgbar() {} virtual const char* what() const noexcept { return message.c_str(); } }; namespace __detail { using SizeT = size_t; using StrT = std::string; using CharT = char; using RefStrT = std::reference_wrapper; // a reference type that can be passed into STL containers #if __PGBAR_CXX17__ using ROStrT = std::string_view; // a read only string type using ConstStrT = const ROStrT; // a constant string type that has method `size()` using LiteralStrT = ROStrT; // a string type that can be a compile-time constant #else using ROStrT = const StrT&; using ConstStrT = const StrT; using LiteralStrT = const CharT*; #endif // __PGBAR_CXX17__ // The refresh rate is capped at about 25 Hz. __PGBAR_INLINE_VAR__ constexpr std::chrono::microseconds reflash_rate = std::chrono::microseconds( 35 ); template __PGBAR_INLINE_FUNC__ typename std::enable_if< std::is_arithmetic::type>::value, StrT >::type ToString( T&& value ) { // The ToString function must has the following behaviors: // 1. can convert numeric type data to a string; // 2. when converting a floating-point number to a string, preserve more than two bits of precision. return std::to_string( value ); } #if __PGBAR_CXX20__ // these concepts are like duck types template concept FunctorType = requires(F tk) { { tk() } -> std::same_as; }; template concept RenderType = requires(R rndr) { requires requires { R( std::declval() ); }; { rndr.active() } -> std::same_as; { rndr.suspend() } -> std::same_as; { rndr.render() } -> std::same_as; }; template concept StreamType = requires(S os) { { os << StrT {} } -> std::same_as; }; #else template struct is_void_functor : std::false_type {}; template struct is_void_functor()())>::value >::type > : std::true_type {}; #endif // __PGBAR_CXX20__ template struct copyable_trait { using type = typename std::conditional< (std::is_copy_assignable::value && std::is_copy_constructible::value && !std::is_lvalue_reference::value) || std::is_rvalue_reference::value, T, T& // then it will be `T` >::type; }; } // namespace __detail #if __PGBAR_CXX20__ template struct is_stream : std::bool_constant<__detail::StreamType> {}; template struct is_renderer : std::bool_constant<__detail::RenderType> {}; #else template struct is_stream : std::false_type {}; template struct is_stream() << std::declval<__detail::StrT>()), S&>::value >::type > : std::true_type {}; template struct is_renderer : std::false_type {}; template struct is_renderer() )), R>::value && std::is_void().active())>::value && std::is_void().suspend())>::value && std::is_void().render())>::value >::type > : std::true_type {}; #endif // __PGBAR_CXX20__ #if __PGBAR_CXX14__ template __PGBAR_INLINE_VAR__ constexpr bool is_renderer_v = is_renderer::value; template __PGBAR_INLINE_VAR__ constexpr bool is_stream_v = is_stream::value; #endif // __PGBAR_CXX14__ namespace __detail { struct wrapper_base { virtual ~wrapper_base() {} virtual void run() = 0; }; template class functor_wrapper final : public wrapper_base { static_assert( #if __PGBAR_CXX20__ __detail::FunctorType, #else __detail::is_void_functor::value, #endif // __PGBAR_CXX20__ "pgbar::__detail::functor_wrapper: template type error" ); F func_; public: template functor_wrapper( U&& func ) : func_ { std::forward( func ) } {} void run() override final { func_(); } }; template class numeric_iterator { static_assert( std::is_arithmetic::value, "pgbar::__detail::range_iterator: Only available for numeric types" ); NumT start_point_, end_point_, step_; SizeT cnt_, extent_; __PGBAR_INLINE_FUNC__ void init_extent() noexcept { const NumT diff = std::max( start_point_, end_point_ ) - std::min( start_point_, end_point_ ); extent_ = static_cast( std::ceil( static_cast(diff) / (step_ < 0 ? -step_ : step_) ) ); } public: using iterator_category = std::forward_iterator_tag; using value_type = NumT; using difference_type = void; using pointer = void; using reference = value_type; numeric_iterator() : start_point_ {}, end_point_ {}, step_ {}, cnt_ {}, extent_ {} {} /// @throw pgbar::bar_pgbar If '_step' is equal to 0. explicit numeric_iterator( value_type _start, value_type _end, value_type _step = 1 ) : numeric_iterator() { if ( _step == 0 ) throw bad_pgbar { "pgbar::__detail::numeric_iterator: '_step' is zero" }; start_point_ = _start; end_point_ = _end; step_ = _step; init_extent(); } __PGBAR_NODISCARD__ numeric_iterator begin() const noexcept { return *this; } __PGBAR_NODISCARD__ numeric_iterator end() const noexcept { auto endpoint = *this; endpoint.cnt_ = extent_; return endpoint; } __PGBAR_NODISCARD__ reference operator*() const noexcept { return cnt_ * step_; } __PGBAR_NODISCARD__ bool operator==( value_type _num ) const noexcept { return (cnt_ * step_) == _num; } __PGBAR_NODISCARD__ bool operator!=( value_type _num ) const noexcept { return !(operator==( _num )); } __PGBAR_NODISCARD__ bool operator==( const numeric_iterator& lhs ) const noexcept { return extent_ == lhs.extent_ && cnt_ == lhs.cnt_; } __PGBAR_NODISCARD__ bool operator!=( const numeric_iterator& lhs ) const noexcept { return !(operator==( lhs )); } __PGBAR_INLINE_FUNC__ numeric_iterator& operator++() noexcept { ++cnt_; return *this; } __PGBAR_INLINE_FUNC__ numeric_iterator operator++( int ) noexcept { auto before = *this; ++cnt_; return before; } __PGBAR_INLINE_FUNC__ numeric_iterator& operator+=( value_type _increment ) noexcept { const auto num_inc = static_cast(_increment / step_); cnt_ = cnt_ + num_inc > extent_ ? extent_ : num_inc; return *this; } __PGBAR_INLINE_FUNC__ void reset() noexcept { cnt_ = 0; } __PGBAR_INLINE_FUNC__ void set_step( value_type _step ) noexcept { step_ = _step; init_extent(); } __PGBAR_INLINE_FUNC__ void set_upper( value_type _upper ) noexcept { end_point_ = _upper; init_extent(); } __PGBAR_NODISCARD__ value_type steps() const noexcept { return step_; } __PGBAR_NODISCARD__ value_type upper() const noexcept { return end_point_; } __PGBAR_NODISCARD__ SizeT extent() const noexcept { return extent_; } __PGBAR_NODISCARD__ bool is_end() const noexcept { return cnt_ == extent_; } }; /// @brief A dynamic character buffer is provided for string concatenation to reduce heap allocations. /// @brief The core thoughts is based on `std::string::clear` does not clear the allocated memory block. class charactersbuf final { StrT buffer_; __PGBAR_INLINE_FUNC__ void member_copy( const charactersbuf& _from ) { buffer_.reserve( _from.buffer_.capacity() ); } __PGBAR_INLINE_FUNC__ void member_move( charactersbuf& _from ) { using std::swap; swap( buffer_, _from.buffer_ ); } public: charactersbuf() {} charactersbuf( const charactersbuf& lhs ) { member_copy( lhs ); } charactersbuf( charactersbuf&& rhs ) { member_move( rhs ); } charactersbuf& operator=( const charactersbuf& lhs ) { member_copy( lhs ); return *this; } charactersbuf& operator=( charactersbuf&& rhs ) { member_move( rhs ); return *this; } /// @brief Append several characters to the buffer. __PGBAR_INLINE_FUNC__ charactersbuf& append( SizeT _num, CharT _ch ) { buffer_.append( _num, _ch ); return *this; } __PGBAR_INLINE_FUNC__ void reserve( SizeT _size ) { buffer_.reserve( _size ); } __PGBAR_INLINE_FUNC__ void clear() { buffer_.clear(); } __PGBAR_INLINE_FUNC__ void release() { clear(); buffer_.shrink_to_fit(); } __PGBAR_NODISCARD__ __PGBAR_INLINE_FUNC__ StrT& data() noexcept { return buffer_; } template::type> __PGBAR_INLINE_FUNC__ typename std::enable_if< std::is_same::value || std::is_same::value || std::is_same::value || std::is_same::value || std::is_same::value, charactersbuf& >::type operator<<( _T&& info ) { buffer_.append( info ); return *this; } __PGBAR_INLINE_FUNC__ charactersbuf& operator<<( CharT character ) { buffer_.push_back( character ); return *this; } /// @brief Copy the string info_str.second info_str.first times. __PGBAR_INLINE_FUNC__ charactersbuf& operator<<( std::pair info_str ) { const auto& copied_str = info_str.second.get(); if ( info_str.first != 0 && copied_str.size() != 0 ) { for ( __detail::SizeT _ = 0; _ < info_str.first; ++_ ) buffer_.append( copied_str ); } return *this; } template __PGBAR_INLINE_FUNC__ friend S& operator<<( S& stream, charactersbuf& buf ) { // hidden friend static_assert( is_stream::value, "pgbar::__detail::charactersbuf: 'S' must be a type that supports 'operator<<' to insert 'pgbar::__detail::StrT'" ); stream << buf.data(); buf.clear(); return stream; } }; template class generic_wrapper { // for `initr` T data; public: constexpr explicit generic_wrapper( T _data ) : data { std::move( _data ) } {} virtual ~generic_wrapper() = 0; __PGBAR_INLINE_FUNC__ T& value() noexcept { return data; } }; template generic_wrapper::~generic_wrapper() {} template struct is_initr { private: template static std::true_type check( const generic_wrapper& ); static std::false_type check( ... ); public: static constexpr bool value = decltype(check( std::declval() ))::value; }; // The default template parameter is used to handle the case where `sizeof...(Args) == 0`. // `generic_wrapper` requires a template parameter, so here we fill in an impossible-to-use type `std::nullptr_t`. template, typename ...Args> struct all_of_initr { static constexpr bool value = is_initr::value && all_of_initr::value; }; template struct all_of_initr { static constexpr bool value = is_initr::value; }; } // namespace __detail struct style { using Type = uint8_t; static constexpr Type bar = 1 << 0; static constexpr Type ratio = 1 << 1; static constexpr Type task_cnt = 1 << 2; static constexpr Type rate = 1 << 3; static constexpr Type timer = 1 << 4; static constexpr Type entire = ~0; }; struct dye { static constexpr __detail::LiteralStrT none = ""; static constexpr __detail::LiteralStrT black = __PGBAR_BLACK__; static constexpr __detail::LiteralStrT red = __PGBAR_RED__; static constexpr __detail::LiteralStrT green = __PGBAR_GREEN__; static constexpr __detail::LiteralStrT yellow = __PGBAR_YELLOW__; static constexpr __detail::LiteralStrT blue = __PGBAR_BLUE__; static constexpr __detail::LiteralStrT magenta = __PGBAR_MAGENTA__; static constexpr __detail::LiteralStrT cyan = __PGBAR_CYAN__; static constexpr __detail::LiteralStrT white = __PGBAR_WHITE__; }; namespace initr { struct option final : public __detail::generic_wrapper { constexpr explicit option( style::Type _data ) noexcept : __detail::generic_wrapper( _data ) {} }; struct todo_color final : public __detail::generic_wrapper<__detail::LiteralStrT> { constexpr explicit todo_color( __detail::LiteralStrT _data ) : __detail::generic_wrapper<__detail::LiteralStrT>( std::move( _data ) ) {} }; struct done_color final : public __detail::generic_wrapper<__detail::LiteralStrT> { constexpr explicit done_color( __detail::LiteralStrT _data ) : __detail::generic_wrapper<__detail::LiteralStrT>( std::move( _data ) ) {} }; struct status_color final : public __detail::generic_wrapper<__detail::LiteralStrT> { constexpr explicit status_color( __detail::LiteralStrT _data ) : __detail::generic_wrapper<__detail::LiteralStrT>( std::move( _data ) ) {} }; struct todo_char final : public __detail::generic_wrapper<__detail::StrT> { explicit todo_char( __detail::StrT _data ) : __detail::generic_wrapper<__detail::StrT>( std::move( _data ) ) {} }; struct done_char final : public __detail::generic_wrapper<__detail::StrT> { explicit done_char( __detail::StrT _data ) : __detail::generic_wrapper<__detail::StrT>( std::move( _data ) ) {} }; struct startpoint final : public __detail::generic_wrapper<__detail::StrT> { explicit startpoint( __detail::StrT _data ) : __detail::generic_wrapper<__detail::StrT>( std::move( _data ) ) {} }; struct endpoint final : public __detail::generic_wrapper<__detail::StrT> { explicit endpoint( __detail::StrT _data ) : __detail::generic_wrapper<__detail::StrT>( std::move( _data ) ) {} }; struct left_status final : public __detail::generic_wrapper<__detail::StrT> { explicit left_status( __detail::StrT _data ) : __detail::generic_wrapper<__detail::StrT>( std::move( _data ) ) {} }; struct right_status final : public __detail::generic_wrapper<__detail::StrT> { explicit right_status( __detail::StrT _data ) : __detail::generic_wrapper<__detail::StrT>( std::move( _data ) ) {} }; struct total_tasks final : public __detail::generic_wrapper<__detail::SizeT> { constexpr explicit total_tasks( __detail::SizeT _data ) noexcept : __detail::generic_wrapper<__detail::SizeT>( _data ) {} }; struct each_setp final : public __detail::generic_wrapper<__detail::SizeT> { constexpr explicit each_setp( __detail::SizeT _data ) noexcept : __detail::generic_wrapper<__detail::SizeT>( _data ) {} }; struct bar_length final : public __detail::generic_wrapper<__detail::SizeT> { constexpr explicit bar_length( __detail::SizeT _data ) noexcept : __detail::generic_wrapper<__detail::SizeT>( _data ) {} }; } // namespace initr namespace __detail { template // end point __PGBAR_INLINE_FUNC__ void pipeline_expan( B& b ) {} #define __PGBAR_EXPAN_FUNC__(OptionName, MethodName) \ template \ inline void pipeline_expan( B& b, initr::OptionName val, Args&&... args ) { \ b.MethodName( std::move( val.value() ) ); \ pipeline_expan( b, std::forward( args )... ); \ } __PGBAR_EXPAN_FUNC__(option, set_style) __PGBAR_EXPAN_FUNC__(todo_color, set_todo_col) __PGBAR_EXPAN_FUNC__(done_color, set_done_col) __PGBAR_EXPAN_FUNC__(status_color, set_status_col) __PGBAR_EXPAN_FUNC__(todo_char, set_todo) __PGBAR_EXPAN_FUNC__(done_char, set_done) __PGBAR_EXPAN_FUNC__(startpoint, set_startpoint) __PGBAR_EXPAN_FUNC__(endpoint, set_endpoint) __PGBAR_EXPAN_FUNC__(left_status, set_lstatus) __PGBAR_EXPAN_FUNC__(right_status, set_rstatus) __PGBAR_EXPAN_FUNC__(total_tasks, set_task) __PGBAR_EXPAN_FUNC__(each_setp, set_step) __PGBAR_EXPAN_FUNC__(bar_length, set_bar_length) #undef __PGBAR_EXPAN_FUNC__ } // namespace __detail class multithread final { __detail::wrapper_base* task_; std::atomic active_flag_; std::atomic suspend_flag_; std::atomic finish_signal_; std::atomic stop_signal_; std::condition_variable cond_var_; std::mutex mtx_; std::thread td_; multithread() : task_ { nullptr } , active_flag_ { false }, suspend_flag_ { true } , finish_signal_ { false }, stop_signal_ { true } {} public: multithread( const multithread& ) = delete; multithread& operator=( multithread&& ) = delete; #if __PGBAR_CXX20__ template<__detail::FunctorType F> #else template< typename F, typename = typename std::enable_if< __detail::is_void_functor::type>::value >::type > #endif explicit multithread( F&& task ) : multithread() { auto new_res = new __detail::functor_wrapper< typename __detail::copyable_trait::type>( std::forward( task ) ); task_ = new_res; td_ = std::thread( [this]() -> void { do { { std::unique_lock lock { mtx_ }; if ( stop_signal_ && !finish_signal_ ) { if ( active_flag_ ) // it means child thread has been printed already task_->run(); // so output the last progress bar before suspend suspend_flag_ = true; cond_var_.wait( lock ); } } if ( finish_signal_ ) break; active_flag_ = true; task_->run(); std::this_thread::sleep_for( __detail::reflash_rate ); } while ( true ); } ); } ~multithread() { { std::unique_lock lock { mtx_ }; finish_signal_ = true; stop_signal_ = false; } cond_var_.notify_all(); if ( td_.joinable() ) td_.join(); delete task_; } void active() { stop_signal_ = false; cond_var_.notify_one(); // spin lock while ( active_flag_ == false ) {} suspend_flag_ = false; } void suspend() { { // there are multiple atomic variables entering the critical region std::unique_lock lock { mtx_ }; stop_signal_ = true; } while ( suspend_flag_ == false ) {} { // ensure that the thread has been suspended std::unique_lock lock { mtx_ }; active_flag_ = false; } } void render() noexcept {} }; class singlethread final { __detail::wrapper_base* task_; bool active_flag_; std::chrono::time_point last_invoke_; public: singlethread( const singlethread& ) = delete; singlethread& operator=( singlethread&& ) = delete; #if __PGBAR_CXX20__ template<__detail::FunctorType F> #else template< typename F, typename = typename std::enable_if< __detail::is_void_functor::value >::type > #endif explicit singlethread( F&& tsk ) : task_ { nullptr }, active_flag_ { false } { auto new_res = new __detail::functor_wrapper< typename __detail::copyable_trait::type>( std::forward( tsk ) ); task_ = new_res; } ~singlethread() { delete task_; } void active() { if ( active_flag_ ) return; last_invoke_ = std::chrono::system_clock::now(); task_->run(); active_flag_ = true; } void suspend() { if ( !active_flag_ ) return; task_->run(); active_flag_ = false; } void render() { if ( !active_flag_ ) return; auto current_time = std::chrono::system_clock::now(); if ( current_time - last_invoke_ < __detail::reflash_rate ) return; last_invoke_ = std::move( current_time ); task_->run(); } }; template class pgbar { static_assert( is_stream::value, "pgbar::pgbar: The 'StreamObj' must be a type that supports 'operator<<' to insert 'pgbar::__detail::StrT'" ); static_assert( is_renderer::value, "pgbar::pgbar: The 'RenderMode' must satisfy the constraint of the type predicate 'pgbar::is_renderer'" ); enum class txt_layout { align_left, align_right, align_center }; // text layout enum bit_index : style::Type { bar = 0, per, cnt, rate, timer }; using BitVector = std::bitset; class rendering_core final { // nested class const pgbar& bar_; enum class render_state { beginning, refreshing, ending, stopped } cur_state_; double last_bar_progress_; std::chrono::system_clock::time_point first_invoked_; __PGBAR_INLINE_FUNC__ render_state transition( render_state current_state ) const noexcept; __PGBAR_INLINE_FUNC__ void init_member() { cur_state_ = render_state::stopped; last_bar_progress_ = {}; first_invoked_ = {}; } public: rendering_core( const rendering_core& ) = delete; rendering_core& operator=( const rendering_core& ) = delete; rendering_core( pgbar& _bar ) : bar_ { _bar } { init_member(); } __PGBAR_INLINE_FUNC__ void reset() noexcept { init_member(); } void operator()(); }; #define __PGBAR_DEFAULT_RATIO__ " 0.00% " #define __PGBAR_DEFAULT_TIMER__ "00:00:00 < 99:60:60" #define __PGBAR_DEFAULT_RATE__ " inf Hz " static constexpr __detail::CharT blank = ' '; static constexpr __detail::CharT backspace = '\b'; static constexpr __detail::SizeT ratio_len = sizeof( __PGBAR_DEFAULT_RATIO__ ) - sizeof( __detail::CharT ); static constexpr __detail::SizeT timer_len = sizeof( __PGBAR_DEFAULT_TIMER__ ) - sizeof( __detail::CharT ); static constexpr __detail::SizeT rate_len = sizeof( __PGBAR_DEFAULT_RATE__ ) - sizeof( __detail::CharT ); static __detail::ConstStrT division; // The default division character. mutable std::atomic update_flag_; mutable __detail::charactersbuf buffer_; StreamObj& stream_; rendering_core machine_; RenderMode rndrer_; BitVector option_; __detail::LiteralStrT todo_col_, done_col_; __detail::LiteralStrT status_col_; __detail::StrT todo_ch_, done_ch_; __detail::StrT startpoint_, endpoint_; __detail::StrT lstatus_, rstatus_; __detail::numeric_iterator<__detail::SizeT> task_cnt_; __detail::SizeT bar_length_; // The length of the progress bar. __detail::SizeT cnt_length_; // The length of the task counter. __detail::SizeT status_length_; // The length of the status bar. bool in_tty_, reset_signal_; /// @brief Format the `_str`. /// @tparam Style Format mode. /// @param _width Target length, do nothing if `_width` less than the length of `_str`. /// @param _str The string will be formatted. /// @return Formatted string. template __PGBAR_INLINE_FUNC__ static __detail::StrT formatter( __detail::SizeT _width, __detail::ROStrT _str ) { if ( _width == 0 ) return {}; if ( _str.size() >= _width ) return __detail::StrT( _str ); #if __PGBAR_CXX20__ if __PGBAR_ENHANCE_CONSTEXPR__ ( Style == txt_layout::align_right ) return std::format( "{:>{}}", _str, _width ); else if __PGBAR_ENHANCE_CONSTEXPR__ ( Style == txt_layout::align_left ) return std::format( "{:<{}}", _str, _width ); else return std::format( "{:^{}}", _str, _width ); #else __detail::SizeT str_size = _str.size(); if __PGBAR_ENHANCE_CONSTEXPR__ ( Style == txt_layout::align_right ) return __detail::StrT( _width - str_size, blank ).append( _str ); else if __PGBAR_ENHANCE_CONSTEXPR__ ( Style == txt_layout::align_left ) return __detail::StrT( _str ) + __detail::StrT( _width - str_size, blank ); else { _width -= _str.size(); __detail::SizeT r_blank = _width / 2; return __detail::StrT( _width - r_blank, blank ) + __detail::StrT( _str ) + __detail::StrT( r_blank, blank ); } #endif // __PGBAR_CXX20__ } __PGBAR_INLINE_FUNC__ static bool check_output_stream( const StreamObj* const os ) { if __PGBAR_ENHANCE_CONSTEXPR__( std::is_same::value == false ) return true; // Custom object, the program does not block output. #if __PGBAR_WIN__ if ( _isatty( _fileno( stdout ) ) ) return true; #elif __PGBAR_UNIX__ if ( isatty( fileno( stdout ) ) ) return true; #elif __PGBAR_UNKNOW_PLATFORM__ if ( true ) return true; #endif // PLATFORM else return false; } __PGBAR_NODISCARD__ __PGBAR_INLINE_FUNC__ std::pair<__detail::SizeT, __detail::SizeT> produce_bar( double num_per ) const { const __detail::SizeT done_len = std::round( bar_length_ * num_per ); return std::make_pair( done_len, bar_length_ - done_len ); } __PGBAR_NODISCARD__ __PGBAR_INLINE_FUNC__ __detail::StrT produce_ratio( double num_per ) const { if ( !is_updated() ) return { __PGBAR_DEFAULT_RATIO__ }; __detail::StrT proportion = __detail::ToString( num_per * 100.0 ); proportion.resize( proportion.find( '.' ) + 3 ); return formatter( ratio_len, std::move( proportion ) + __detail::StrT( 1, '%' ) ); } __PGBAR_NODISCARD__ __PGBAR_INLINE_FUNC__ __detail::StrT produce_progress( __detail::SizeT num_done ) const { __detail::StrT total_str = __detail::ToString( total_tasks() ); __detail::SizeT size = total_str.size(); return ( formatter( size, __detail::ToString( num_done ) ) + __detail::StrT( 1, '/' ) + std::move( total_str ) ); } __PGBAR_NODISCARD__ __detail::StrT produce_rate( std::chrono::nanoseconds time_passed, __detail::SizeT num_done ) const { if ( !is_updated() ) return { __PGBAR_DEFAULT_RATE__ }; auto rate2str = []( double val ) -> __detail::StrT { __detail::StrT str = __detail::ToString( val ); str.resize( str.find( '.' ) + 3 ); // Keep two decimal places. return str; }; const double seconds_passed = std::chrono::duration( time_passed ).count(); // zero or negetive is invalid. const double frequency = seconds_passed <= 0.0 ? (std::numeric_limits::max)() : num_done / seconds_passed; __detail::StrT rate_str; if ( frequency < 1e3 ) // < 1Hz => '999.99 Hz' rate_str = rate2str( frequency ) + " Hz"; else if ( frequency < 1e6 ) // < 1 kHz => '999.99 kHz' rate_str = rate2str( frequency / 1e3 ) + " kHz"; else if ( frequency < 1e9 ) // < 1 MHz => '999.99 MHz' rate_str = rate2str( frequency / 1e6 ) + " MHz"; else { // > 999 GHz => infinity const double temp = frequency / 1e9; if ( temp > 999.99 ) rate_str = __PGBAR_DEFAULT_RATE__; // it's impossible I think else rate_str = rate2str( temp ) + __detail::StrT( " GHz" ); } return formatter( rate_len, std::move( rate_str ) ); } __PGBAR_NODISCARD__ __detail::StrT produce_timer( std::chrono::nanoseconds time_passed, __detail::SizeT num_done ) const { if ( !is_updated() ) return { __PGBAR_DEFAULT_TIMER__ }; auto time2str = []( int64_t num_time ) -> __detail::StrT { __detail::StrT ret = __detail::ToString( num_time ); if ( ret.size() < 2 ) return "0" + ret; return ret; }; auto to_time = [&time2str]( std::chrono::nanoseconds duration ) -> __detail::StrT { const auto hours = std::chrono::duration_cast( duration ); duration -= hours; const auto minutes = std::chrono::duration_cast( duration ); duration -= minutes; return ( ((hours.count() > 99 ? __detail::StrT( "99" ) : time2str( hours.count() )) + ":") + (time2str( minutes.count() ) + ":") + time2str( std::chrono::duration_cast(duration).count() ) ); }; auto time_per_task = time_passed / num_done; if ( time_per_task.count() == 0 ) time_per_task = std::chrono::duration<__detail::SizeT, std::nano>( 1 ); std::chrono::nanoseconds estimated_time = time_per_task * (total_tasks() - num_done); return formatter( timer_len, to_time( std::move( time_passed ) ) + __detail::StrT( " < " ) + to_time( std::move( estimated_time ) ) ); } /// @brief Based on the value of `option` and bitwise operations, /// @brief determine which part of the string needs to be concatenated. __detail::charactersbuf& fitter( BitVector ctrller, double num_per, __detail::SizeT num_done, std::chrono::nanoseconds time_passed ) const { const __detail::SizeT total_length = ( (ctrller[bit_index::bar] ? (bar_length_ + startpoint_.size() + endpoint_.size() + 1) : 0) + status_length_); buffer_.reserve( total_length * 2 + 1 ); // The extra 1 is for '\n'. if ( is_updated() ) buffer_.append( total_length, backspace ); if ( ctrller[bit_index::bar] ) { auto info = produce_bar( num_per ); buffer_ << startpoint_ << done_col_ << std::make_pair( info.first, std::cref( done_ch_ ) ) << todo_col_ << std::make_pair( info.second, std::cref( todo_ch_ ) ) << __PGBAR_DEFAULT_COL__ << endpoint_ << blank; } if ( status_length_ != 0 ) buffer_ << __PGBAR_BOLD__ << status_col_ << lstatus_; if ( ctrller[bit_index::per] ) { buffer_ << produce_ratio( num_per ); if ( ctrller[bit_index::cnt] || ctrller[bit_index::rate] || ctrller[bit_index::timer] ) buffer_ << division; } if ( ctrller[bit_index::cnt] ) { buffer_ << produce_progress( num_done ); if ( ctrller[bit_index::rate] || ctrller[bit_index::timer] ) buffer_ << division; } if ( ctrller[bit_index::rate] ) { buffer_ << produce_rate( time_passed, num_done ); if ( ctrller[bit_index::timer] ) buffer_ << division; } if ( ctrller[bit_index::timer] ) buffer_ << produce_timer( std::move( time_passed ), num_done ); if ( status_length_ != 0 ) buffer_ << rstatus_ << __PGBAR_DEFAULT_COL__; return buffer_; } template /// @throw pgbar::bad_pgbar If the updating is done or the number of tasks is 0. __PGBAR_INLINE_FUNC__ void do_update( F&& updating_task ) { static_assert( #if __PGBAR_CXX20__ __detail::FunctorType, #else __detail::is_void_functor::value, #endif // __PGBAR_CXX20__ "pgbar::pgbar::do_update: template type error" ); if ( is_done() ) throw bad_pgbar { "pgbar::do_update: updating a full progress bar" }; if ( !is_updated() ) { if ( task_cnt_.upper() == 0 ) throw bad_pgbar { "pgbar::do_update: the number of tasks is zero" }; rndrer_.active(); } updating_task(); rndrer_.render(); if ( task_cnt_.is_end() ) rndrer_.suspend(); // wait for child thread to finish } __PGBAR_INLINE_FUNC__ void pod_copy( const pgbar& _from ) noexcept { bar_length_ = _from.bar_length_; cnt_length_ = _from.cnt_length_; status_length_ = _from.status_length_; } __PGBAR_INLINE_FUNC__ void npod_copy( const pgbar& _from ) { option_ = _from.option_; todo_col_ = _from.todo_col_; done_col_ = _from.done_col_; status_col_ = _from.status_col_; todo_ch_ = _from.todo_ch_; done_ch_ = _from.done_ch_; startpoint_ = _from.startpoint_; endpoint_ = _from.endpoint_; lstatus_ = _from.lstatus_; rstatus_ = _from.rstatus_; buffer_ = _from.buffer_; task_cnt_ = _from.task_cnt_; task_cnt_.reset(); } __PGBAR_INLINE_FUNC__ void npod_move( pgbar& _from ) { option_ = std::move( _from.option_ ); todo_col_ = std::move( _from.todo_col_ ); done_col_ = std::move( _from.done_col_ ); status_col_ = std::move( _from.status_col_ ); todo_ch_ = std::move( _from.todo_ch_ ); done_ch_ = std::move( _from.done_ch_ ); startpoint_ = std::move( _from.startpoint_ ); endpoint_ = std::move( _from.endpoint_ ); lstatus_ = std::move( _from.lstatus_ ); rstatus_ = std::move( _from.rstatus_ ); buffer_ = std::move( _from.buffer_ ); task_cnt_ = std::move( _from.task_cnt_ ); task_cnt_.reset(); } __PGBAR_INLINE_FUNC__ void init_length( bool update_cnt_len = true ) { if ( update_cnt_len ) cnt_length_ = static_cast<__detail::SizeT>( std::log10( total_tasks() ) + 1) * 2 + 1; status_length_ = ( (option_[bit_index::per] ? ratio_len : 0) + (option_[bit_index::cnt] ? cnt_length_ : 0) + (option_[bit_index::rate] ? rate_len : 0) + (option_[bit_index::timer] ? timer_len : 0) ); if ( status_length_ != 0 ) { status_length_ += lstatus_.size() + rstatus_.size(); const __detail::SizeT status_num = option_[bit_index::per] + option_[bit_index::cnt] + option_[bit_index::rate] + option_[bit_index::timer]; status_length_ += status_num > 1 ? (status_num - 1) * division.size() : 0; } } pgbar( StreamObj* _ostream ) : update_flag_ { false }, buffer_ {} , stream_ { *_ostream }, machine_ { *this } , rndrer_ { machine_ } { option_ = style::entire; todo_col_ = dye::none; done_col_ = dye::none; status_col_ = dye::cyan; bar_length_ = 30; cnt_length_ = 1; status_length_ = 0; in_tty_ = check_output_stream( _ostream ); reset_signal_ = false; } public: using StreamType = StreamObj; using RendererType = RenderMode; pgbar( __detail::SizeT _total_tsk, __detail::SizeT _each_step, StreamObj& _ostream = std::cerr ) : pgbar( std::addressof( _ostream ) ) { todo_ch_ = __detail::StrT( 1, blank ); done_ch_ = __detail::StrT( 1, '-' ); startpoint_ = __detail::StrT( 1, '[' ); endpoint_ = __detail::StrT( 1, ']' ); lstatus_ = __detail::StrT( "[ " ); rstatus_ = __detail::StrT( " ]" ); task_cnt_ = __detail::numeric_iterator<__detail::SizeT>( 0, _total_tsk, _each_step ); init_length(); } pgbar( __detail::SizeT _total_tsk, StreamObj& _ostream = std::cerr ) : pgbar( _total_tsk, 1, _ostream ) {} template::type...>::value >::type > pgbar( StreamObj& _ostream = std::cerr, Args&&... args ) : pgbar( 0, _ostream ) { // = default constructor __detail::pipeline_expan( *this, std::forward( args )... ); init_length(); } pgbar( const pgbar& _lhs ) : pgbar( std::addressof( _lhs.stream_ ) ) { npod_copy( _lhs ); pod_copy( _lhs ); } pgbar( pgbar&& _rhs ) : pgbar( std::addressof( _rhs.stream_ ) ) { npod_move( _rhs ); pod_copy( _rhs ); } ~pgbar() { reset(); } pgbar& operator=( const pgbar& _lhs ) { if ( this == &_lhs || is_updated() ) return *this; npod_copy( *this, _lhs ); pod_copy( *this, _lhs ); return *this; } pgbar& operator=( pgbar&& _rhs ) { if ( this == &_rhs || is_updated() ) return *this; npod_move( *this, _rhs ); pod_copy( *this, _rhs ); return *this; } __PGBAR_NODISCARD__ bool is_updated() const noexcept { return update_flag_; } __PGBAR_NODISCARD__ bool is_done() const noexcept { return is_updated() && task_cnt_.is_end(); } /// @brief Reset pgbar obj, EXCLUDING the total number of tasks. pgbar& reset() { reset_signal_ = true; rndrer_.suspend(); machine_.reset(); buffer_.clear(); task_cnt_.reset(); update_flag_ = false; reset_signal_ = false; return *this; } /// @brief Set the number of steps the counter is updated each time `update()` is called. /// @throw pgbar::bad_pgbar If the `_step` is zero. pgbar& set_step( __detail::SizeT _step ) { if ( is_updated() ) return *this; else if ( _step == 0 ) throw bad_pgbar { "pgbar::set_step: zero step" }; task_cnt_.set_step( _step ); return *this; } /// @brief Set the number of tasks to be updated. /// @throw pgbar::bad_pgbar If the `_total_tsk` is zero. pgbar& set_task( __detail::SizeT _total_tsk ) { if ( is_updated() ) return *this; else if ( _total_tsk == 0 ) throw bad_pgbar { "pgbar::set_task: the number of tasks is zero" }; task_cnt_.set_upper( _total_tsk ); init_length(); return *this; } /// @brief Set the TODO characters in the progress bar. pgbar& set_done( __detail::StrT _done_ch ) noexcept { if ( !is_updated() ) done_ch_ = std::move( _done_ch ); return *this; } /// @brief Set the DONE characters in the progress bar. pgbar& set_todo( __detail::StrT _todo_ch ) noexcept { if ( !is_updated() ) todo_ch_ = std::move( _todo_ch ); return *this; } /// @brief Set the start point of the progress bar. pgbar& set_startpoint( __detail::StrT _startpoint ) noexcept { if ( !is_updated() ) startpoint_ = std::move( _startpoint ); return *this; } /// @brief Set the endpoint of the progress bar. pgbar& set_endpoint( __detail::StrT _endpoint ) noexcept { if ( !is_updated() ) endpoint_ = std::move( _endpoint ); return *this; } /// @brief Set the left bracket of the status bar. pgbar& set_lstatus( __detail::StrT _lstatus ) noexcept { if ( !is_updated() ) lstatus_ = std::move( _lstatus ); init_length( false ); return *this; } /// @brief Set the right bracket of the status bar. pgbar& set_rstatus( __detail::StrT _rstatus ) noexcept { if ( !is_updated() ) rstatus_ = std::move( _rstatus ); init_length( false ); return *this; } /// @brief Set the character length of the whole progress bar pgbar& set_bar_length( __detail::SizeT _length ) noexcept { if ( !is_updated() ) bar_length_ = _length; return *this; } /// @brief Set the color of the todo characters in the progress bar pgbar& set_todo_col( __detail::LiteralStrT _dye ) noexcept { if ( !is_updated() ) todo_col_ = std::move( _dye ); return *this; } /// @brief Set the color of the done characters in the progress bar pgbar& set_done_col( __detail::LiteralStrT _dye ) noexcept { if ( !is_updated() ) done_col_ = std::move( _dye ); return *this; } /// @brief Set the color of the status bar pgbar& set_status_col( __detail::LiteralStrT _dye ) noexcept { if ( !is_updated() ) status_col_ = std::move( _dye ); return *this; } /// @brief Select the display style by using bit operations. pgbar& set_style( style::Type _selection ) noexcept { if ( !is_updated() ) option_ = _selection; init_length( false ); return *this; } template typename std::enable_if< __detail::all_of_initr::type...>::value, pgbar& >::type set_style( Args&&... args ) { __detail::pipeline_expan( *this, std::forward( args )... ); init_length(); return *this; } /// @brief Get the total number of tasks. __PGBAR_NODISCARD__ __detail::SizeT total_tasks() const noexcept { return task_cnt_.upper(); } /// @brief Get the number of steps the iteration advances. __PGBAR_NODISCARD__ __detail::SizeT steps() const noexcept { return task_cnt_.steps(); } /// @brief Get the number of tasks that have been updated. __PGBAR_NODISCARD__ __detail::SizeT current() const noexcept { return *task_cnt_; } /// @brief Update progress bar. void update() { do_update( [this]() -> void { ++task_cnt_; } ); } /// @brief Ignore the effect of `set_step()`, increment forward several progresses, /// @brief and any `next_step` portions that exceed the total number of tasks are ignored. /// @param next_step The number that will increment forward the progresses. void update( __detail::SizeT next_step ) { do_update( [this, &next_step]() -> void { task_cnt_ += next_step; } ); } /// @brief Set the iteration steps of the progress bar to a specified percentage. /// @brief Ignore the call if the iteration count exceeds the given percentage. /// @brief If `percentage` is bigger than 100, it will be set to 100. /// @param percentage Value range: [0, 100]. void update_to( __detail::SizeT percentage ) { if ( percentage < 100 ) { const __detail::SizeT current_percent = std::trunc( (static_cast(current()) / total_tasks()) * 100.0 ); const __detail::SizeT differ = percentage - current_percent; if ( differ > 1 ) // calculates the next step update( differ * 0.01 * total_tasks() ); } else do_update( [this]() -> void { task_cnt_ = task_cnt_.end(); } ); } }; template __detail::ConstStrT pgbar::division { " | " }; #define __PGBAR_NAME_PREFIX__ pgbar::rendering_core template typename __PGBAR_NAME_PREFIX__::render_state __PGBAR_NAME_PREFIX__::transition( render_state current_state ) const noexcept { switch ( current_state ) { case render_state::beginning: // fallthrough __PGBAR_FALLTHROUGH__ case render_state::refreshing: return bar_.reset_signal_ || bar_.is_done() ? render_state::ending : render_state::refreshing; case render_state::ending: // fallthrough __PGBAR_FALLTHROUGH__ case render_state::stopped: return !bar_.in_tty_ || bar_.reset_signal_ || bar_.is_done() ? render_state::stopped : render_state::beginning; default: break; } return render_state::stopped; } template void __PGBAR_NAME_PREFIX__::operator()() { switch ( cur_state_ = transition( cur_state_ ) ) { case render_state::beginning: { // intermediate state first_invoked_ = std::chrono::system_clock::now(); bar_.stream_ << // For visual purposes, output the full progress bar at the beginning. bar_.fitter( bar_.option_, 0.0, 0, {} ); bar_.update_flag_ = true; cur_state_ = render_state::refreshing; // unconditional jump } break; case render_state::refreshing: { const auto current = bar_.current(); const double num_percent = current / static_cast(bar_.total_tasks()); auto controller = bar_.option_; if ( num_percent - last_bar_progress_ < 0.01 ) controller.reset( pgbar::bit_index::bar ); else last_bar_progress_ = num_percent; // Then normally output the progress bar. bar_.stream_ << bar_.fitter( std::move( controller ), num_percent, current, std::chrono::system_clock::now() - first_invoked_ ); } break; case render_state::ending: { // intermediate state if ( !bar_.reset_signal_ ) bar_.fitter( bar_.option_, 1, bar_.total_tasks(), std::chrono::system_clock::now() - first_invoked_ ); bar_.stream_ << bar_.buffer_.append( 1, '\n' ); // Same, for visual purposes. bar_.buffer_.release(); // releases the buffer cur_state_ = render_state::stopped; // unconditional jump } break; case render_state::stopped: // fallthrough __PGBAR_FALLTHROUGH__ default: return; } } #undef __PGBAR_NAME_PREFIX__ #if __PGBAR_CXX20__ namespace __detail { template concept PgbarType = requires { typename B::StreamType; typename B::RendererType; requires std::conjunction_v< is_stream, is_renderer, std::is_same, B> >; }; } template struct is_pgbar : std::bool_constant<__detail::PgbarType> {}; #else template struct is_pgbar : std::false_type {}; template struct is_pgbar::value && is_renderer::value && std::is_same, B>::value >::type > : std::true_type {}; #endif // __PGBAR_CXX20__ #if __PGBAR_CXX14__ template __PGBAR_INLINE_VAR__ constexpr bool is_pgbar_v = is_pgbar::value; #endif // __PGBAR_CXX14__ template #if __PGBAR_CXX20__ requires std::conjunction_v< is_stream, is_renderer, __detail::all_of_initr > __PGBAR_NODISCARD__ inline pgbar #else __PGBAR_NODISCARD__ inline typename std::enable_if < is_stream::value && is_renderer::value && __detail::all_of_initr::value, pgbar >::type #endif // __PGBAR_CXX20__ make_pgbar( S& stream_obj, Args&&... args ) { pgbar bar { stream_obj }; __detail::pipeline_expan( bar, std::forward( args )... ); return bar; } } // namespace pgbar #undef __PGBAR_DEFAULT_RATIO__ #undef __PGBAR_DEFAULT_TIMER__ #undef __PGBAR_DEFAULT_RATE__ #undef __PGBAR_DEFAULT_COL__ #undef __PGBAR_WHITE__ #undef __PGBAR_CYAN__ #undef __PGBAR_MAGENTA__ #undef __PGBAR_BLUE__ #undef __PGBAR_YELLOW__ #undef __PGBAR_GREEN__ #undef __PGBAR_RED__ #undef __PGBAR_BLACK__ #undef __PGBAR_BOLD__ #undef __PGBAR_RET_CONSTEXPR__ #undef __PGBAR_CXX14__ #undef __PGBAR_FALLTHROUGH__ #undef __PGBAR_ENHANCE_CONSTEXPR__ #undef __PGBAR_INLINE_VAR__ #undef __PGBAR_CXX17__ #undef __PGBAR_CXX20__ #undef __PGBAR_UNKNOW_PLATFORM__ #undef __PGBAR_UNIX__ #undef __PGBAR_WIN__ #undef __PGBAR_CMP_V__ #undef __PGBAR_NODISCARD__ #undef __PGBAR_INLINE_FUNC__ #endif // __PROGRESSBAR_HPP__