C++17 In Detail

03 February 2020

Activity Indicators - Example of a Modern C++ Library

Indicators

In this blog post, we’ll show you how to write a library that displays a task activity indication for multithreading applications. Have a look at several essential Modern C++ techniques and how to combine them to write good code.

Let’s dive in!

This article is a guest post from Pranav Srinivas Kumar:

Pranav has 5+ years of industry experience in C++, focusing on safety-critical embedded software. He works at Permobil, researching on drive-assist technology for powered wheelchairs. Pranav frequently publishes hobby projects on GitHub.

Introduction

Progress bars and loading spinners are a valuable part of my daily life as a software engineer. Installing libraries with apt or pip? Training a neural network with TensorFlow? Copying files to an external hard drive? They’re everywhere. How long should I wait before I CTRL-C this process? Is it even doing anything?

We need activity indicators. They keep us engaged - we are more likely to finish tasks when there is a clear indication of progress. Here’s a page from Apple’s Human Interface Guidelines wiki regarding progress indicators.

Inspired by tqdm and indicatif, I’ve recently published a library called indicators that provides configurable activity indicators for use in C++ command-line applications. In this post, I’ll present a minimal implementation of two indicators: ProgressBar and MultiProgress. ProgressBar will provide an interface to model and manage a single, thread-safe progress bar. MultiProgress will provide an interface to manage multiple progress bars simultaneously.

Although the indicators library supports C++11 and higher, we will assume C++17 support for this post.

Designing a Progress bar

Let’s set some expectations. Our progress bar must be:

  • Thread-safe - we’re able to update the progress bar state from multiple threads
  • Informative - we’re able to provide useful stats, e.g., percentage completed, time elapsed etc.
  • Configurable - we’re able to set the bar width, color and style

Let’s assume progress is measured as a float in [0.0f, 100.0f] with the semantics: 25.0f implies 25% completed. We can provide an .set_progress(float) method that users can use to update the progress bar state.

#include <atomic>
#include <mutex>
#include <iostream>

class ProgressBar {
public: 
  void set_progress(float value) {
    std::unique_lock lock{mutex_};  // CTAD (C++17)
    progress_ = value;
  }

private:
  std::mutex mutex_;
  float progress_{0.0f};
};

Layout

Now, let’s focus on the layout. We want a progress bar that (1) spans a specific width, (2) progresses from left to right, (3) shows percentage completion, and (4) maybe shows some status text. Here’s a minimalist design:

[{...completed} {remaining...}] {percentage} {status_text}
 ◄-------- Bar Width --------►

Example:
[■■■■■■■■■■■■■■■■■■■■■■-------] 71% Extracting Archive

Below are some setters that users can use to configure our progress bar. Note the use of std::unique_lock - we want to be able to change properties like status_text from multiple threads based on application state.

public:
  // [...]

  void set_bar_width(size_t width) {
    std::unique_lock lock{mutex_};
    bar_width_ = width;    
  }

  void fill_bar_progress_with(const std::string& chars) {
    std::unique_lock lock{mutex_};
    fill_ = chars;    
  }

  void fill_bar_remainder_with(const std::string& chars) {
    std::unique_lock lock{mutex_};
    remainder_ = chars;    
  }

  void set_status_text(const std::string& status) {
    std::unique_lock lock{mutex_};
    status_text_ = status;    
  }

private:
  // [...]

  size_t bar_width_{60};
  std::string fill_{"#"}, remainder_{" "}, status_text_{""};  

If the width of our bar is 60 characters, then the completed portion of our bar should span 60 * progress_ / 100 characters. We can use this logic in a .write_progress() to write our bar to a stream, e.g., console.

Let’s add an .update method that sets the progress and immediately prints the bar to the stream.

public:   
  // [...]

  void update(float value, std::ostream &os = std::cout) {
    set_progress(value);
    write_progress(os);
  }

  void write_progress(std::ostream &os = std::cout) {
    std::unique_lock lock{mutex_};

    // No need to write once progress is 100%
    if (progress_ > 100.0f) return;

    // Move cursor to the first position on the same line and flush 
    os << "\r" << std::flush;

    // Start bar
    os << "[";

    const auto completed = static_cast<size_t>(progress_ * static_cast<float>(bar_width_) / 100.0);
    for (size_t i = 0; i < bar_width_; ++i) {
      if (i <= completed) 
        os << fill_;
      else 
        os << remainder_;
    }

    // End bar
    os << "]";

    // Write progress percentage
    os << " " << std::min(static_cast<size_t>(progress_), size_t(100)) << "%"; 

    // Write status text
    os << " " << status_text_;
  }

We’re choosing to use std::ostream here so we can use this class for unit testing, mocking, and writing to log files.

Note that use of os << "\r" <<. We don’t want to print our progress bar in a newline after each change; we want to update the progress bar in-place. So, we use the RETURN character to go back to the first position on the same line.

Example

Time to test this out. Let’s write a simple program that configures a ProgressBar object and updates its state. For a little extra bling, I’m going to use the excellent termcolor library.

#include "progress_bar.hpp"
#include "termcolor.hpp"
#include <thread>

int main() {
  std::cout << termcolor::bold << termcolor::yellow;

  ProgressBar bar;
  bar.set_bar_width(50);
  bar.fill_bar_progress_with("■");
  bar.fill_bar_remainder_with(" ");

  for (size_t i = 1; i <= 100; ++i) {
    bar.update(i);
    std::this_thread::sleep_for(std::chrono::milliseconds(50));
  }

  std::cout << termcolor::reset;
}

Progress Bar

Great. We have a thread-safe progress bar class that is reasonably configurable. How do we handle more than one? As it stands, if we use more than one progress bar, their stdout will overlap.

Managing Multiple Progress bars

We need a management class that can refer to multiple progress bars and prints them nicely - one bar per line to the console. Something like Docker’s parallel download progress bars:

Docker

Here are some design considerations:

  • What is the ownership model? Does MultiProgress own a collection of progress bars or does it simply refer to them?
  • Can each progress bar be updated independently in a thread-safe manner?
  • How dynamic is this multi-progress bar class? Can one dynamically add and remove progress bars as and when progress is completed?

For simplicity, let’s assume that our MultiProgress class manages a fixed number of progress bars and this number is known at compile-time, e.g., MultiProgress<3> bars;

Constructing MultiProgress

I like the idea of our MultiProgress class not owning the progress bars but simply referring to them. This way, we can construct progress bars and use them as is or as part of a multi-progress bar indicator (or both).

So how do we achieve this? Based on the above docker example, we know the MultiProgress class needs to hold a container, e.g., an array of indicators. We don’t want to store raw pointers to progress bars. We also can’t use a vector of references; the component type of containers like vectors needs to be assignable and references are not assignable.

We can use std::reference_wrapper instead. reference_wrapper<T> is a CopyConstructible and Assignable wrapper around a reference to an object of type T. Instances of std::reference_wrapper<T> are objects (they can be copied or stored in containers) but they are implicitly convertible to T&, so that they can be used as arguments with the functions that take the underlying type by reference.

Let’s allow the user to specify the number of progress bars to manage and have the user also provide references to each bar in the constructor:

#include <atomic>
#include <mutex>
#include <functional>
#include <array>
#include <iostream>

template <typename Indicator, size_t count>
class MultiProgress {
public:
  template <typename... Indicators,
              typename = typename std::enable_if_t<(sizeof...(Indicators) == count)>>
  explicit MultiProgress(Indicators &... bars) : bars_({bars...}) {}

private:
  std::array<std::reference_wrapper<Indicator>, count> bars_;
};

Note that MultiProgress takes a template Indicator. This allows for easily extending this class to support other types of progress indicators, e.g., progress spinners, block progress bars, or other specializations.

Also note that our use of std::reference_wrapper comes with a hidden assumption - the Indicators referred to by a MultiProgress object need to outlast the MultiProgress object itself. Else our bars_ array will be referring to objects that are already destroyed.

Constructing MultiProgress now looks like below. This object is configured for exactly 3 bars - the constructor will accept exactly 3 arguments and the object will hold references to these bars.

MultiProgress<ProgressBar, 3> bars(bar1, bar2, bar3);

Updating Progress

Our .update method will simply loop over all the bars we’re managing and call each one’s .set_progress method.

// multi_progress.hpp
// [...]
public:
  template <size_t index>
  typename std::enable_if_t<(index >= 0 && index < count), void> 
  update(float value, std::ostream &os = std::cout) {
    bars_[index].get().set_progress(value);
  }

Okay, now our code can update the progress of each bar. We aren’t printing anything yet, though.

Printing progress

Let’s work on printing all these bars. We need to iterate over each bar and print its progress. When printing repeatedly, we need to move the cursor up some number of lines (once for each bar) before printing the bars. This is to ensure that we’re printing “in place” - to give the effect the we’re updating that bar. Not doing this will cause the .write_progress to keep printing in new lines.

public:
  template <size_t index>
  typename std::enable_if<(index >= 0 && index < count), void>::type 
  update(float value, std::ostream &os = std::cout) {
    // [...]
    write_progress(os);
  }

  void write_progress(std::ostream &os = std::cout) {
    std::unique_lock lock{mutex_};

    // Move cursor up if needed
    if (started_)
      for (size_t i = 0; i < count; ++i)
        os << "\x1b[A";

    // Write each bar
    for (auto &bar : bars_) {
      bar.get().write_progress();
      os << "\n";
    }

    if (!started_)
      started_ = true;
  }

private:
  // [...]
  std::mutex mutex_;
  std::atomic<bool> started_{false};

Note that we’re simply reusing code written in the ProgressBar class - set_progress and write_progress.

Example

Time to test this out. Let’s create three progress bars: bar1, bar2, and bar3. Create a MultiProgress object for managing these bars.

We want to update the state of these bars in different threads and at different rates. In the example below, bar1 is updated every 100 ms, bar2 every 200 ms, and bar3 every 60 ms.

#include "progress_bar.hpp"
#include "multi_progress.hpp"
#include "termcolor.hpp"
#include <thread>

int main() {
  std::cout << termcolor::bold << termcolor::green << "\n\n" << std::endl;

  ProgressBar bar1, bar2, bar3;
  MultiProgress<ProgressBar, 3> bars(bar1, bar2, bar3);

  // Job for the first bar
  auto job1 = [&bars]() {
    for (size_t i = 0; i <= 100; ++i) {
      bars.update<0>(i);
      std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
  };

  // Job for the second bar
  auto job2 = [&bars]() {
    for (size_t i = 0; i <= 100; ++i) {
      bars.update<1>(i);
      std::this_thread::sleep_for(std::chrono::milliseconds(200));
    }
  };

  // Job for the third bar
  auto job3 = [&bars]() {
    for (size_t i = 0; i <= 100; ++i) {
      bars.update<2>(i);
      std::this_thread::sleep_for(std::chrono::milliseconds(60));
    }
  };

  std::thread first_job(job1);
  std::thread second_job(job2);
  std::thread third_job(job3);

  first_job.join();
  second_job.join();
  third_job.join();

  std::cout << termcolor::reset << std::endl;

  return 0;
}

Multi progress

As you can imagine, it should be easy from here to add additional style-related properties to the ProgressBar class, e.g., foreground color, background color, etc. There is plenty of room to get creative.

Conclusions

In this post, we have explored some activity indicators with C++17 - a thread-safe progress bar and a multi-progress indicator. The indicators library provides a few additional classes, e.g., block progress bars and progress spinners, along with a slightly richer interface for presenting stats, e.g., estimated time remaining.

Thanks for reading!

C++17 In Detail
© 2017, Bartlomiej Filipek, Blogger platform
Disclaimer: Any opinions expressed herein are in no way representative of those of my employers. All data and information provided on this site is for informational purposes only. I try to write complete and accurate articles, but the web-site will not be liable for any errors, omissions, or delays in this information or any losses, injuries, or damages arising from its display or use.
This site contains ads or referral links, which provide me with a commission. Thank you for your understanding.