diff --git a/barkeep/barkeep.h b/barkeep/barkeep.h index b204e23..094ef97 100644 --- a/barkeep/barkeep.h +++ b/barkeep/barkeep.h @@ -16,10 +16,12 @@ #define BARKEEP_H #include +#include #include #include #include #include +#include #include #include #include @@ -472,6 +474,90 @@ inline auto Status(const AnimationConfig& cfg = {}) { return std::make_shared(cfg); } +template +struct Provider { + using value_type = T; + using provider_type = T*; + using underlying_type = T; + + Provider(provider_type ptr) noexcept : ptr_{ptr} {} + Provider(const Provider&) = default; + Provider& operator=(const Provider&) = default; + + [[nodiscard]] T load() const noexcept { return *ptr_; } + + [[nodiscard]] bool ok() const noexcept { return ptr_; } + + provider_type ptr_{nullptr}; +}; + +template +struct Provider> { + using value_type = T; + using provider_type = std::atomic*; + using underlying_type = std::atomic; + + Provider(provider_type ptr) noexcept : ptr_{ptr} {} + Provider(const Provider&) = default; + Provider& operator=(const Provider&) = default; + + [[nodiscard]] T load() const noexcept { return ptr_->load(std::memory_order_relaxed); } + + [[nodiscard]] bool ok() const noexcept { return ptr_; } + + provider_type ptr_{nullptr}; +}; + +template +struct Provider> { + using value_type = T; + using provider_type = std::function; + using underlying_type = std::function; + + Provider(provider_type fun) : fun_{std::move(fun)} {} + Provider(const Provider&) = default; + Provider& operator=(const Provider&) = default; + + [[nodiscard]] T load() const { return fun_(); } + + [[nodiscard]] bool ok() const noexcept { return fun_; } + + provider_type fun_; +}; + +template +struct ReasonablyInvocable { + constexpr static auto value = false; +}; + +template +struct ReasonablyInvocable>>> { + constexpr static auto value = true; +}; + +template +constexpr bool ReasonablyInvocableV = ReasonablyInvocable::value; + +template +struct ProviderSelector { + using type_t = Provider; +}; + +template +struct ProviderSelector> { + using type_t = Provider>; +}; + +template +struct ProviderSelector< + T, + std::enable_if_t>>> { + using type_t = Provider()>>; +}; + +template +using provider_t = typename ProviderSelector::type_t; + /// Trait class to extract underlying value type from numerics and /// std::atomics of numerics. template @@ -484,6 +570,21 @@ struct AtomicTraits> { using value_type = T; }; +template +struct AtomicTraits> { + using value_type = typename Provider::value_type; +}; + +template +struct AtomicTraits>> { + using value_type = typename Provider>::value_type; +}; + +template +struct AtomicTraits>> { + using value_type = typename Provider>::value_type; +}; + template using value_t = typename AtomicTraits::value_type; @@ -493,13 +594,13 @@ using signed_t = typename std::conditional_t, std::common_type>::type; /// Helper class to measure and display speed of progress. -template +template class Speedometer { private: - Progress* progress_; // Current amount of work done + const ProgressProvider& progress_provider_; // Current amount of work done double discount_; - using ValueType = value_t; + using ValueType = value_t; using SignedType = signed_t; using Clock = std::chrono::steady_clock; using Time = std::chrono::time_point; @@ -516,7 +617,8 @@ class Speedometer { Duration dur = now - last_start_time_; last_start_time_ = now; - ValueType progress_copy = *progress_; // to avoid progress_ changing below + ValueType progress_copy = + progress_provider_.load(); // to avoid progress_ changing below SignedType progress_increment = SignedType(progress_copy) - SignedType(last_progress_); last_progress_ = progress_copy; @@ -551,7 +653,8 @@ class Speedometer { /// Start computing the speed based on the amount of change in progress. void start() { - last_progress_ = *progress_; + last_progress_ = progress_provider_.load(); + ; last_start_time_ = Clock::now(); } @@ -562,8 +665,8 @@ class Speedometer { /// If discount is 0, all increments are weighted equally. /// If discount is 1, only the most recent increment is /// considered. - Speedometer(Progress* progress, double discount) - : progress_(progress), discount_(discount) { + Speedometer(const ProgressProvider& progress_provider, double discount) + : progress_provider_(progress_provider), discount_(discount) { if (discount < 0 or discount > 1) { throw std::runtime_error("Discount must be in [0, 1]"); } @@ -595,8 +698,9 @@ struct CounterConfig { template class CounterDisplay : public BaseDisplay { protected: - Progress* progress_ = nullptr; // current amount of work done - std::unique_ptr> speedom_; + using ProgressProvider = provider_t; + ProgressProvider progress_provider_; // current amount of work done + std::unique_ptr> speedom_; std::string speed_unit_ = "it/s"; // unit of speed text next to speed std::stringstream ss_; @@ -604,7 +708,7 @@ class CounterDisplay : public BaseDisplay { protected: /// Write the value of progress to the output stream void render_counts_(const std::string& end = " ") { - ss_ << *progress_; + ss_ << progress_provider_.load(); out() << ss_.str() << end; ss_.str(""); } @@ -614,7 +718,7 @@ class CounterDisplay : public BaseDisplay { #if defined(BARKEEP_ENABLE_FMT_FORMAT) if (not format_.empty()) { using namespace fmt::literals; - value_t progress = *progress_; + value_t progress = progress_provider_.load(); if (speedom_) { fmt::print(out(), fmt::runtime(format_), @@ -643,7 +747,7 @@ class CounterDisplay : public BaseDisplay { } #elif defined(BARKEEP_ENABLE_STD_FORMAT) if (not format_.empty()) { - value_t progress = *progress_; + value_t progress = progress_provider_.load(); auto speed = speedom_ ? speedom_->speed() : std::nan(""); out() << std::vformat(format_, std::make_format_args(progress, @@ -673,7 +777,7 @@ class CounterDisplay : public BaseDisplay { } void start() override { - if constexpr (std::is_floating_point_v>) { + if constexpr (std::is_floating_point_v>) { ss_ << std::fixed << std::setprecision(2); } if (speedom_) { speedom_->start(); } @@ -683,16 +787,17 @@ class CounterDisplay : public BaseDisplay { /// Constructor. /// @param progress Variable to be monitored and displayed /// @param cfg Counter parameters - CounterDisplay(Progress* progress, const CounterConfig& cfg = {}) + CounterDisplay(provider_t progress_provider, const CounterConfig& cfg = {}) : BaseDisplay(cfg.out, as_duration(cfg.interval), cfg.message, cfg.format.empty() ? "" : cfg.format + " ", cfg.no_tty), - progress_(progress), + progress_provider_(std::move(progress_provider)), speed_unit_(cfg.speed_unit) { if (cfg.speed) { - speedom_ = std::make_unique>(progress_, *cfg.speed); + speedom_ = std::make_unique>( + progress_provider_, *cfg.speed); } if (displayer_->interval() == Duration{0.}) { displayer_->interval(default_interval_(cfg.no_tty)); @@ -700,12 +805,28 @@ class CounterDisplay : public BaseDisplay { if (cfg.show) { show(); } } + CounterDisplay(Progress* progress_provider, const CounterConfig& cfg = {}) + : CounterDisplay(provider_t(progress_provider), cfg) + {} + + CounterDisplay(Progress progress_provider, const CounterConfig& cfg = {}) + : CounterDisplay(provider_t(std::move(progress_provider)), cfg) + {} + ~CounterDisplay() { done(); } }; /// Convenience factory function to create a shared_ptr to CounterDisplay. /// Prefer this to constructing CounterDisplay directly. -template +template , + typename std::enable_if_t, bool> = true> +auto Counter(Progress&& progress, const CounterConfig& cfg = {}) { + return std::make_shared>(std::forward(progress), cfg); +} + +template > auto Counter(Progress* progress, const CounterConfig& cfg = {}) { return std::make_shared>(progress, cfg); } @@ -741,10 +862,11 @@ struct ProgressBarConfig { template class ProgressBarDisplay : public BaseDisplay { protected: - using ValueType = value_t; + using ProgressProvider = provider_t; + using ValueType = value_t; - Progress* progress_; // work done so far - std::unique_ptr> speedom_; + ProgressProvider progress_provider_; // work done so far + std::unique_ptr> speedom_; std::string speed_unit_ = "it/s"; // unit of speed text next to speed static constexpr size_t width_ = 30; // width of progress bar // (TODO: make customizable?) @@ -756,8 +878,9 @@ class ProgressBarDisplay : public BaseDisplay { /// Compute the shape of the progress bar based on progress and write to /// output stream. void render_progress_bar_(std::ostream* out) { - ValueType progress_copy = *progress_; // to avoid progress_ changing - // during computations below + ValueType progress_copy = + progress_provider_.load(); // to avoid progress_ changing + // during computations below bool complete = progress_copy >= total_; int on = int(ValueType(width_) * progress_copy / total_); size_t partial = size_t(ValueType(bar_parts_.fill.size()) * @@ -810,14 +933,14 @@ class ProgressBarDisplay : public BaseDisplay { /// Progress width is expanded (and right justified) to match width of total. void render_counts_(const std::string& end = " ") { std::stringstream ss, totals; - if (std::is_floating_point_v) { + if (std::is_floating_point_v) { ss << std::fixed << std::setprecision(2); totals << std::fixed << std::setprecision(2); } totals << total_; auto width = static_cast(totals.str().size()); ss.width(width); - ss << std::right << *progress_ << "/" << total_ << end; + ss << std::right << progress_provider_.load() << "/" << total_ << end; out() << ss.str(); } @@ -826,7 +949,7 @@ class ProgressBarDisplay : public BaseDisplay { std::stringstream ss; ss << std::fixed << std::setprecision(2); ss.width(6); - ss << std::right << *progress_ * 100. / total_ << "%" << end; + ss << std::right << progress_provider_.load() * 100. / total_ << "%" << end; out() << ss.str(); } @@ -835,7 +958,7 @@ class ProgressBarDisplay : public BaseDisplay { #if defined(BARKEEP_ENABLE_FMT_FORMAT) if (not format_.empty()) { using namespace fmt::literals; - value_t progress = *progress_; + value_t progress = progress_provider_.load(); std::stringstream bar_ss; render_progress_bar_(&bar_ss); @@ -876,7 +999,7 @@ class ProgressBarDisplay : public BaseDisplay { } #elif defined(BARKEEP_ENABLE_STD_FORMAT) if (not format_.empty()) { - value_t progress = *progress_; + value_t progress = progress_provider_.load(); std::stringstream bar_ss; render_progress_bar_(&bar_ss); @@ -941,14 +1064,14 @@ class ProgressBarDisplay : public BaseDisplay { /// Constructor. /// @param progress Variable to be monitored to measure completion /// @param cfg ProgressBar parameters - ProgressBarDisplay(Progress* progress, + ProgressBarDisplay(provider_t progress_provider, const ProgressBarConfig& cfg = {}) : BaseDisplay(cfg.out, as_duration(cfg.interval), cfg.message, cfg.format.empty() ? "" : cfg.format + " ", cfg.no_tty), - progress_(progress), + progress_provider_(std::move(progress_provider)), speed_unit_(cfg.speed_unit), total_(cfg.total) { if (std::holds_alternative(cfg.style)) { @@ -958,7 +1081,8 @@ class ProgressBarDisplay : public BaseDisplay { std::get(cfg.style))]; } if (cfg.speed) { - speedom_ = std::make_unique>(progress_, *cfg.speed); + speedom_ = std::make_unique>( + progress_provider_, *cfg.speed); } if (displayer_->interval() == Duration{0.}) { displayer_->interval(default_interval_(cfg.no_tty)); @@ -966,14 +1090,32 @@ class ProgressBarDisplay : public BaseDisplay { if (cfg.show) { show(); } } + ProgressBarDisplay(Progress* progress_provider, const ProgressBarConfig& cfg = {}) + : ProgressBarDisplay(provider_t(progress_provider), cfg) + {} + + ProgressBarDisplay(Progress progress_provider, const ProgressBarConfig& cfg = {}) + : ProgressBarDisplay(provider_t(std::move(progress_provider)), cfg) + {} + ~ProgressBarDisplay() { done(); } }; /// Convenience factory function to create a shared_ptr to ProgressBarDisplay. /// Prefer this to constructing ProgressBarDisplay directly. -template +template , + typename std::enable_if_t, bool> = true> +auto ProgressBar(Progress&& progress, + const ProgressBarConfig>& cfg = {}) { + return std::make_shared>(std::forward(progress), + cfg); +} + +template > auto ProgressBar(Progress* progress, - const ProgressBarConfig>& cfg = {}) { + const ProgressBarConfig>& cfg = {}) { return std::make_shared>(progress, cfg); } @@ -1033,13 +1175,13 @@ class CompositeDisplay : public BaseDisplay { /// Convenience factory function to create a shared_ptr to CompositeDisplay. /// Prefer this to constructing CompositeDisplay directly. inline auto Composite(const std::vector>& displays, - std::string delim = " ") { + std::string delim = " ") { return std::make_shared(displays, std::move(delim)); } /// Pipe operator can be used to combine two displays into a Composite. inline auto operator|(std::shared_ptr left, - std::shared_ptr right) { + std::shared_ptr right) { return std::make_shared( std::vector{std::move(left), std::move(right)}); } diff --git a/python/barkeep.cpp b/python/barkeep.cpp index 4842935..666095d 100644 --- a/python/barkeep.cpp +++ b/python/barkeep.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -121,11 +122,13 @@ class Status_ : public StatusDisplay { template class Counter_ : public CounterDisplay { protected: + using ProviderUnderlyingType = typename provider_t::underlying_type; using CounterDisplay::render_; using CounterDisplay::default_interval_; public: - std::shared_ptr work = std::make_shared(0); + std::shared_ptr work = + std::make_shared(0); Counter_(py::object file = py::none(), std::string format = "", @@ -143,8 +146,11 @@ class Counter_ : public CounterDisplay { .interval = interval, .no_tty = no_tty, .show = false}) { + + this->progress_provider_ = work.get(); if (speed) { - this->speedom_ = std::make_unique>(work.get(), *speed); + this->speedom_ = + std::make_unique>>(this->progress_provider_, *speed); } std::shared_ptr fp = nullptr; if (not file.is_none()) { @@ -154,9 +160,10 @@ class Counter_ : public CounterDisplay { interval == 0. ? this->default_interval_(no_tty) : Duration(interval); this->displayer_ = std::make_shared(this, fp, interval_, no_tty); - this->progress_ = work.get(); - assert(this->progress_ != nullptr); + assert(this->progress_provider_.ok()); } + + ~Counter_() { this->done(); } auto& operator+=(value_t v) { *work += v; @@ -177,11 +184,13 @@ class Counter_ : public CounterDisplay { template class ProgressBar_ : public ProgressBarDisplay { protected: + using ProviderUnderlyingType = typename provider_t::underlying_type; using ProgressBarDisplay::render_; using ProgressBarDisplay::default_interval_; public: - std::shared_ptr work = std::make_shared(0); + std::shared_ptr work = + std::make_shared(0); ProgressBar_(py::object file = py::none(), value_t total = 100, @@ -203,8 +212,11 @@ class ProgressBar_ : public ProgressBarDisplay { .interval = interval, .no_tty = no_tty, .show = false}) { + + this->progress_provider_ = work.get(); if (speed) { - this->speedom_ = std::make_unique>(work.get(), *speed); + this->speedom_ = + std::make_unique>>(this->progress_provider_, *speed); } std::shared_ptr fp = nullptr; if (not file.is_none()) { @@ -214,10 +226,11 @@ class ProgressBar_ : public ProgressBarDisplay { interval == 0. ? this->default_interval_(no_tty) : Duration(interval); this->displayer_ = std::make_shared(this, fp, interval_, no_tty); - this->progress_ = work.get(); - assert(this->progress_ != nullptr); + assert(this->progress_provider_.ok()); } + ~ProgressBar_() { this->done(); } + auto& operator+=(value_t v) { *work += v; return *this; @@ -613,4 +626,4 @@ PYBIND11_MODULE(barkeep, m) { } return Composite({self, other}); }); -} \ No newline at end of file +} diff --git a/tests/demo.cpp b/tests/demo.cpp index 284b4fd..d81b69a 100644 --- a/tests/demo.cpp +++ b/tests/demo.cpp @@ -195,6 +195,22 @@ Demo decreasing_bar( // ... } }); +Demo decreasing_bar_lambda( // ... + "decreasing_bar_lambda", + "Decreasing progress bar with lambda", + []() { + unsigned long work{1010}; + auto bar = bk::ProgressBar([&] { return work; }, + { + .total = 1010, + .speed = 1., + }); + for (int i = 0; i < 1010; i++) { + std::this_thread::sleep_for(7ms); + work--; + } + }); + Demo bar_and_counter( "bar_and_counter", "Composite display of a ProgressBar and Counter", diff --git a/tests/test-fmtlib.cpp b/tests/test-fmtlib.cpp index 181bcb9..96c6b38 100644 --- a/tests/test-fmtlib.cpp +++ b/tests/test-fmtlib.cpp @@ -268,4 +268,4 @@ TEST_CASE("Composite bar-counter", "[composite]") { last_count = count; } } -} \ No newline at end of file +} diff --git a/tests/test-stdfmt.cpp b/tests/test-stdfmt.cpp index c98181d..a009a3e 100644 --- a/tests/test-stdfmt.cpp +++ b/tests/test-stdfmt.cpp @@ -264,4 +264,4 @@ TEST_CASE("Composite bar-counter", "[composite]") { last_count = count; } } -} \ No newline at end of file +} diff --git a/tests/test.cpp b/tests/test.cpp index f05dfc2..2f91e16 100644 --- a/tests/test.cpp +++ b/tests/test.cpp @@ -1,6 +1,7 @@ #define CATCH_CONFIG_MAIN #include +#include #include #include #include @@ -298,6 +299,19 @@ auto factory_helper>(bool speedy) { } } +template <> +auto factory_helper>>(bool speedy) { + static float progress; + static std::stringstream hide; + auto lambda = [&] { return progress; }; + if (speedy) { + return ProgressBar(std::move(lambda), + {.out = &hide, .speed = 1, .show = false}); + } else { + return ProgressBar(std::move(lambda), {.out = &hide, .show = false}); + } +} + template <> auto factory_helper(bool speedy) { static size_t progress; @@ -315,6 +329,7 @@ using DisplayTypes = std::tuple, ProgressBarDisplay, + ProgressBarDisplay>, CompositeDisplay>; TEMPLATE_LIST_TEST_CASE("Error cases", "[edges]", DisplayTypes) { @@ -782,4 +797,4 @@ TEST_CASE("Three bars multiline", "[composite]") { check_shrinking_space(interleaved_parts[0], Rich); check_shrinking_space(interleaved_parts[1], Rich); check_shrinking_space(interleaved_parts[2], Rich); -} \ No newline at end of file +}