1415 lines
50 KiB
C++
1415 lines
50 KiB
C++
|
// 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 <cstdint>
|
||
|
# include <limits> // std::numeric_limits
|
||
|
# include <cmath> // std::round, std::log10, std::trunc, std::ceil
|
||
|
# include <type_traits> // SFINAE
|
||
|
# include <utility> // std::pair
|
||
|
# include <functional> // std::reference_wrapper
|
||
|
# include <iterator> // marks iterator tags
|
||
|
# include <bitset> // std::bitset
|
||
|
# include <string> // std::string
|
||
|
# include <chrono> // as u know
|
||
|
# include <exception> // std::exception
|
||
|
# include <iostream> // std::cerr, the output stream object used
|
||
|
|
||
|
# include <atomic> // std::atomic<bool>
|
||
|
# include <thread> // std::thread
|
||
|
# include <mutex> // std::mutex & std::unique_lock
|
||
|
# include <condition_variable> // 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 <io.h>
|
||
|
# define __PGBAR_WIN__ 1
|
||
|
# define __PGBAR_UNIX__ 0
|
||
|
# define __PGBAR_UNKNOW_PLATFORM__ 0
|
||
|
#elif defined(__unix__) || defined(unix)
|
||
|
# include <unistd.h>
|
||
|
# 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 <concepts> // std::same_as
|
||
|
# include <format> // std::format
|
||
|
# define __PGBAR_CXX20__ 1
|
||
|
#else
|
||
|
# define __PGBAR_CXX20__ 0
|
||
|
#endif // __cplusplus >= 202002L
|
||
|
#if __PGBAR_CMP_V__ >= 201703L
|
||
|
# include <string_view> // 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<const StrT>; // 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<typename T>
|
||
|
__PGBAR_INLINE_FUNC__
|
||
|
typename std::enable_if<
|
||
|
std::is_arithmetic<typename std::decay<T>::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<typename F>
|
||
|
concept FunctorType = requires(F tk) {
|
||
|
{ tk() } -> std::same_as<void>;
|
||
|
};
|
||
|
|
||
|
template<typename R>
|
||
|
concept RenderType = requires(R rndr) {
|
||
|
requires requires { R( std::declval<void()>() ); };
|
||
|
{ rndr.active() } -> std::same_as<void>;
|
||
|
{ rndr.suspend() } -> std::same_as<void>;
|
||
|
{ rndr.render() } -> std::same_as<void>;
|
||
|
};
|
||
|
|
||
|
template<typename S>
|
||
|
concept StreamType = requires(S os) {
|
||
|
{ os << StrT {} } -> std::same_as<S&>;
|
||
|
};
|
||
|
#else
|
||
|
template<typename F, typename = void>
|
||
|
struct is_void_functor : std::false_type {};
|
||
|
template<typename F>
|
||
|
struct is_void_functor<F,
|
||
|
typename std::enable_if<
|
||
|
std::is_void<decltype(std::declval<F>()())>::value
|
||
|
>::type
|
||
|
> : std::true_type {};
|
||
|
#endif // __PGBAR_CXX20__
|
||
|
template<typename T>
|
||
|
struct copyable_trait {
|
||
|
using type = typename std::conditional<
|
||
|
(std::is_copy_assignable<T>::value &&
|
||
|
std::is_copy_constructible<T>::value &&
|
||
|
!std::is_lvalue_reference<T>::value) ||
|
||
|
std::is_rvalue_reference<T>::value,
|
||
|
T, T& // then it will be `T`
|
||
|
>::type;
|
||
|
};
|
||
|
} // namespace __detail
|
||
|
|
||
|
#if __PGBAR_CXX20__
|
||
|
template<typename S>
|
||
|
struct is_stream : std::bool_constant<__detail::StreamType<S>> {};
|
||
|
|
||
|
template<typename R>
|
||
|
struct is_renderer : std::bool_constant<__detail::RenderType<R>> {};
|
||
|
#else
|
||
|
template<typename S, typename = void>
|
||
|
struct is_stream : std::false_type {};
|
||
|
template<typename S>
|
||
|
struct is_stream<S,
|
||
|
typename std::enable_if<
|
||
|
std::is_same<decltype(std::declval<S&>() << std::declval<__detail::StrT>()), S&>::value
|
||
|
>::type
|
||
|
> : std::true_type {};
|
||
|
|
||
|
template<typename R, typename = void>
|
||
|
struct is_renderer : std::false_type {};
|
||
|
template<typename R>
|
||
|
struct is_renderer<R,
|
||
|
typename std::enable_if<
|
||
|
std::is_same<decltype(R( std::declval<void()>() )), R>::value &&
|
||
|
std::is_void<decltype(std::declval<R&>().active())>::value &&
|
||
|
std::is_void<decltype(std::declval<R&>().suspend())>::value &&
|
||
|
std::is_void<decltype(std::declval<R&>().render())>::value
|
||
|
>::type
|
||
|
> : std::true_type {};
|
||
|
#endif // __PGBAR_CXX20__
|
||
|
|
||
|
#if __PGBAR_CXX14__
|
||
|
template<typename R>
|
||
|
__PGBAR_INLINE_VAR__ constexpr bool is_renderer_v = is_renderer<R>::value;
|
||
|
|
||
|
template<typename S>
|
||
|
__PGBAR_INLINE_VAR__ constexpr bool is_stream_v = is_stream<S>::value;
|
||
|
#endif // __PGBAR_CXX14__
|
||
|
|
||
|
namespace __detail {
|
||
|
struct wrapper_base {
|
||
|
virtual ~wrapper_base() {}
|
||
|
virtual void run() = 0;
|
||
|
};
|
||
|
template<typename F>
|
||
|
class functor_wrapper final : public wrapper_base {
|
||
|
static_assert(
|
||
|
#if __PGBAR_CXX20__
|
||
|
__detail::FunctorType<F>,
|
||
|
#else
|
||
|
__detail::is_void_functor<F>::value,
|
||
|
#endif // __PGBAR_CXX20__
|
||
|
"pgbar::__detail::functor_wrapper: template type error"
|
||
|
);
|
||
|
|
||
|
F func_;
|
||
|
|
||
|
public:
|
||
|
template<typename U>
|
||
|
functor_wrapper( U&& func )
|
||
|
: func_ { std::forward<U>( func ) } {}
|
||
|
void run() override final { func_(); }
|
||
|
};
|
||
|
|
||
|
template<typename NumT>
|
||
|
class numeric_iterator {
|
||
|
static_assert(
|
||
|
std::is_arithmetic<NumT>::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<SizeT>(
|
||
|
std::ceil( static_cast<double>(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<SizeT>(_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<typename _T, typename T = typename std::decay<_T>::type>
|
||
|
__PGBAR_INLINE_FUNC__ typename std::enable_if<
|
||
|
std::is_same<T, StrT>::value ||
|
||
|
std::is_same<T, ROStrT>::value ||
|
||
|
std::is_same<T, ConstStrT>::value ||
|
||
|
std::is_same<T, LiteralStrT>::value ||
|
||
|
std::is_same<T, const CharT*>::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<SizeT, RefStrT> 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<typename S>
|
||
|
__PGBAR_INLINE_FUNC__ friend S& operator<<( S& stream, charactersbuf& buf ) { // hidden friend
|
||
|
static_assert(
|
||
|
is_stream<S>::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<typename T>
|
||
|
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<typename T>
|
||
|
generic_wrapper<T>::~generic_wrapper() {}
|
||
|
|
||
|
template<typename W>
|
||
|
struct is_initr {
|
||
|
private:
|
||
|
template<typename U>
|
||
|
static std::true_type check( const generic_wrapper<U>& );
|
||
|
static std::false_type check( ... );
|
||
|
public:
|
||
|
static constexpr bool value = decltype(check( std::declval<W>() ))::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 T = generic_wrapper<std::nullptr_t>, typename ...Args>
|
||
|
struct all_of_initr {
|
||
|
static constexpr bool value = is_initr<T>::value && all_of_initr<Args...>::value;
|
||
|
};
|
||
|
template<typename T>
|
||
|
struct all_of_initr<T> {
|
||
|
static constexpr bool value = is_initr<T>::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<style::Type> {
|
||
|
constexpr explicit option( style::Type _data ) noexcept
|
||
|
: __detail::generic_wrapper<style::Type>( _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<typename B> // end point
|
||
|
__PGBAR_INLINE_FUNC__ void pipeline_expan( B& b ) {}
|
||
|
|
||
|
#define __PGBAR_EXPAN_FUNC__(OptionName, MethodName) \
|
||
|
template<typename B, typename ...Args> \
|
||
|
inline void pipeline_expan( B& b, initr::OptionName val, Args&&... args ) { \
|
||
|
b.MethodName( std::move( val.value() ) ); \
|
||
|
pipeline_expan( b, std::forward<Args>( 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<bool> active_flag_;
|
||
|
std::atomic<bool> suspend_flag_;
|
||
|
|
||
|
std::atomic<bool> finish_signal_;
|
||
|
std::atomic<bool> 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<typename std::decay<F>::type>::value
|
||
|
>::type
|
||
|
>
|
||
|
#endif
|
||
|
explicit multithread( F&& task )
|
||
|
: multithread() {
|
||
|
auto new_res = new __detail::functor_wrapper<
|
||
|
typename __detail::copyable_trait<F>::type>( std::forward<F>( task ) );
|
||
|
task_ = new_res;
|
||
|
td_ = std::thread( [this]() -> void {
|
||
|
do {
|
||
|
{
|
||
|
std::unique_lock<std::mutex> 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<std::mutex> 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<std::mutex> lock { mtx_ };
|
||
|
stop_signal_ = true;
|
||
|
}
|
||
|
while ( suspend_flag_ == false ) {}
|
||
|
{ // ensure that the thread has been suspended
|
||
|
std::unique_lock<std::mutex> lock { mtx_ };
|
||
|
active_flag_ = false;
|
||
|
}
|
||
|
}
|
||
|
void render() noexcept {}
|
||
|
};
|
||
|
|
||
|
class singlethread final {
|
||
|
__detail::wrapper_base* task_;
|
||
|
bool active_flag_;
|
||
|
std::chrono::time_point<std::chrono::system_clock> 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<F>::value
|
||
|
>::type
|
||
|
>
|
||
|
#endif
|
||
|
explicit singlethread( F&& tsk )
|
||
|
: task_ { nullptr }, active_flag_ { false } {
|
||
|
auto new_res = new __detail::functor_wrapper<
|
||
|
typename __detail::copyable_trait<F>::type>( std::forward<F>( 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<typename StreamObj = std::ostream, typename RenderMode = multithread>
|
||
|
class pgbar {
|
||
|
static_assert(
|
||
|
is_stream<StreamObj>::value,
|
||
|
"pgbar::pgbar: The 'StreamObj' must be a type that supports 'operator<<' to insert 'pgbar::__detail::StrT'"
|
||
|
);
|
||
|
static_assert(
|
||
|
is_renderer<RenderMode>::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<sizeof( style::Type ) * 8>;
|
||
|
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<StreamObj, RenderMode>& _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<bool> 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<txt_layout Style>
|
||
|
__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<StreamObj, std::ostream>::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<txt_layout::align_right>(
|
||
|
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<txt_layout::align_right>( 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<double>( time_passed ).count();
|
||
|
// zero or negetive is invalid.
|
||
|
const double frequency = seconds_passed <= 0.0 ? (std::numeric_limits<double>::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<txt_layout::align_center>( 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<std::chrono::hours>( duration );
|
||
|
duration -= hours;
|
||
|
const auto minutes = std::chrono::duration_cast<std::chrono::minutes>( duration );
|
||
|
duration -= minutes;
|
||
|
return (
|
||
|
((hours.count() > 99 ? __detail::StrT( "99" ) : time2str( hours.count() )) + ":") +
|
||
|
(time2str( minutes.count() ) + ":") +
|
||
|
time2str( std::chrono::duration_cast<std::chrono::seconds>(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<txt_layout::align_center>(
|
||
|
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<typename F>
|
||
|
/// @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<F>,
|
||
|
#else
|
||
|
__detail::is_void_functor<F>::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<typename ...Args, typename =
|
||
|
typename std::enable_if<
|
||
|
__detail::all_of_initr<typename std::decay<Args>::type...>::value
|
||
|
>::type
|
||
|
> pgbar( StreamObj& _ostream = std::cerr, Args&&... args )
|
||
|
: pgbar( 0, _ostream ) { // = default constructor
|
||
|
__detail::pipeline_expan( *this, std::forward<Args>( 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 ...Args>
|
||
|
typename std::enable_if<
|
||
|
__detail::all_of_initr<typename std::decay<Args>::type...>::value,
|
||
|
pgbar&
|
||
|
>::type set_style( Args&&... args ) {
|
||
|
__detail::pipeline_expan( *this, std::forward<Args>( 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<double>(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<typename StreamObj, typename RenderMode>
|
||
|
__detail::ConstStrT pgbar<StreamObj, RenderMode>::division { " | " };
|
||
|
|
||
|
#define __PGBAR_NAME_PREFIX__ pgbar<StreamObj, RenderMode>::rendering_core
|
||
|
template<typename StreamObj, typename RenderMode>
|
||
|
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<typename StreamObj, typename RenderMode>
|
||
|
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<double>(bar_.total_tasks());
|
||
|
|
||
|
auto controller = bar_.option_;
|
||
|
if ( num_percent - last_bar_progress_ < 0.01 )
|
||
|
controller.reset( pgbar<StreamObj, RenderMode>::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 <typename B>
|
||
|
concept PgbarType = requires {
|
||
|
typename B::StreamType;
|
||
|
typename B::RendererType;
|
||
|
requires std::conjunction_v<
|
||
|
is_stream<typename B::StreamType>,
|
||
|
is_renderer<typename B::RendererType>,
|
||
|
std::is_same<pgbar<typename B::StreamType, typename B::RendererType>, B>
|
||
|
>;
|
||
|
};
|
||
|
}
|
||
|
|
||
|
template<typename B>
|
||
|
struct is_pgbar : std::bool_constant<__detail::PgbarType<B>> {};
|
||
|
#else
|
||
|
template<typename B, typename = void>
|
||
|
struct is_pgbar : std::false_type {};
|
||
|
template<typename B>
|
||
|
struct is_pgbar<B,
|
||
|
typename std::enable_if<
|
||
|
is_stream<typename B::StreamType>::value &&
|
||
|
is_renderer<typename B::RendererType>::value &&
|
||
|
std::is_same<pgbar<typename B::StreamType, typename B::RendererType>, B>::value
|
||
|
>::type
|
||
|
> : std::true_type {};
|
||
|
#endif // __PGBAR_CXX20__
|
||
|
|
||
|
#if __PGBAR_CXX14__
|
||
|
template<typename B>
|
||
|
__PGBAR_INLINE_VAR__ constexpr bool is_pgbar_v = is_pgbar<B>::value;
|
||
|
#endif // __PGBAR_CXX14__
|
||
|
|
||
|
template<typename R = multithread, typename S, typename ...Args>
|
||
|
#if __PGBAR_CXX20__
|
||
|
requires std::conjunction_v<
|
||
|
is_stream<S>,
|
||
|
is_renderer<R>,
|
||
|
__detail::all_of_initr<Args...>
|
||
|
> __PGBAR_NODISCARD__ inline pgbar<S, R>
|
||
|
#else
|
||
|
__PGBAR_NODISCARD__ inline typename std::enable_if <
|
||
|
is_stream<S>::value &&
|
||
|
is_renderer<R>::value &&
|
||
|
__detail::all_of_initr<Args...>::value,
|
||
|
pgbar<S, R>
|
||
|
>::type
|
||
|
#endif // __PGBAR_CXX20__
|
||
|
make_pgbar( S& stream_obj, Args&&... args )
|
||
|
{
|
||
|
pgbar<S, R> bar { stream_obj };
|
||
|
__detail::pipeline_expan( bar, std::forward<Args>( 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__
|