Using C + + to realize a sample code of command line progress bar

Time:2021-3-3

origin

Recently, the gwpca method for remote sensing image fusion is too slow when the bandwidth is relatively large. I need a progress bar to indicate it. Then I went to the library of the progress bar and found that the corresponding library of C / C + + on GitHub does not seem to be able to run under vs. I spent some time writing one.

effect

realization

There are several factors that need to be considered

  • Percentage completed
  • Execution speed
  • Time of execution
  • Time remaining

In addition, the introduction of progress bar can’t destroy the existing execution structure. It’s better to be similar to the tqdm Library of Pythonstart , updateSo for C language, we need a timer to redraw the progress bar regularly (it is impossible to redraw the progress bar every time it is updated). Therefore, the whole progress bar contains two classes, one is the progress bar class, and the other is the timer class. In addition, we need to consider the issue of thread safety.

// Progress.hpp
#pragma once

#include <ctime>
#include <chrono>
#include <iostream>
#include <iomanip>
#include "Timer.hpp"


using namespace std::chrono;

class ProgressBar
{
protected:
  //The length of the progress bar (excluding the front suffix)
	unsigned int ncols;
  //Quantity completed
	std::atomic<unsigned int> finishedNum;
  //Last completed quantity
	unsigned int lastNum;
  //Total
	unsigned int totalNum;
  //Coefficient between progress bar length and percentage
	double colsRatio;
  //Start time
	steady_clock::time_point beginTime;
  //Last redraw time
	steady_clock::time_point lastTime;
  //Redraw cycle
	milliseconds interval;
	Timer timer;
public:
	ProgressBar(unsigned int totalNum, milliseconds interval) : totalNum(totalNum), interval(interval), finishedNum(0), lastNum(0), ncols(80), colsRatio(0.8) {}
  //Start
	void start();
  //Done
	void finish();
  //Update
	void update() { return this->update(1); }
  //Update multiple quantities at a time
	void update(unsigned int num) { this->finishedNum += num; }
  //Get the length of progress bar
	unsigned int getCols() { return this->ncols; }
  //Set progress bar length
	void setCols(unsigned int ncols) { this->ncols = ncols; this->colsRatio = ncols / 100; }
  //Redraw
	void show();
};
void ProgressBar::start() {
  //Record the start time and initialize the timer
	this->beginTime = steady_clock::now();
	this->lastTime = this->beginTime;
	//The timer is used to call the redraw function regularly
	this->timer.start(this->interval.count(), std::bind(&ProgressBar::show, this));
}

//Redraw函数
void ProgressBar::show() {
  //Clear last drawn content
	std::cout << "\r";
  //Record the time point of redrawing
	steady_clock::time_point now = steady_clock::now();
	//Get completed quantity
	unsigned int tmpFinished = this->finishedNum.load();
	//Gets the time interval from the start time and the last redraw time
	auto timeFromStart = now - this->beginTime;
	auto timeFromLast = now - this->lastTime;
	//The number of completed this time
	unsigned int gap = tmpFinished - this->lastNum;
	//Calculation speed
	double rate = gap / duration<double>(timeFromLast).count();
	//Percentage to be displayed
	double present = (100.0 * tmpFinished) / this->totalNum;
	//Print percentage
	std::cout << std::setprecision(1) << std::fixed << present << "%|";
	//Calculate how many = symbols should be drawn
	int barWidth = present * this->colsRatio;
	//Print completed and incomplete progress bars
	std::cout << std::setw(barWidth) << std::setfill('=') << "=";
	std::cout << std::setw(this->ncols - barWidth) << std::setfill(' ') << "|";

	//Printing speed
	std::cout << std::setprecision(1) << std::fixed << rate << "op/s|";
	//The next two parts are printing time passed and remaining time
	int timeFromStartCount = duration<double>(timeFromStart).count();

	std::time_t tfs = timeFromStartCount;
	tm tmfs;
	gmtime_s(&tmfs, &tfs);
	std::cout << std::put_time(&tmfs, "%X") << "|";

	int timeLast;
	if (rate != 0) {
    //The remaining time is estimated by the speed of this time and the number of unfinished projects
		timeLast = (this->totalNum - tmpFinished) / rate;
	}
	else {
		timeLast = INT_MAX;
	}

	if ((this->totalNum - tmpFinished) == 0) {
		timeLast = 0;
	}


	std::time_t tl = timeLast;
	tm tml;
	gmtime_s(&tml, &tl);
	std::cout << std::put_time(&tml, "%X");


	this->lastNum = tmpFinished;
	this->lastTime = now;
}

void ProgressBar::finish() {
  //Stop timer
	this->timer.stop();
	std::cout << std::endl;
}

// Timer.hpp
#pragma once
#include <functional>
#include <chrono>
#include <thread>
#include <atomic>
#include <memory>
#include <mutex>
#include <condition_variable>

using namespace std::chrono;

class Timer
{
public:
	Timer() : _expired(true), _try_to_expire(false)
	{}

	Timer(const Timer& timer)
	{
		_expired = timer._expired.load();
		_try_to_expire = timer._try_to_expire.load();
	}

	~Timer()
	{
		stop();
	}

	void start(int interval, std::function<void()> task)
	{
		// is started, do not start again
		if (_expired == false)
			return;

		// start async timer, launch thread and wait in that thread
		_expired = false;
		std::thread([this, interval, task]() {
			while (!_try_to_expire)
			{
				// sleep every interval and do the task again and again until times up
				std::this_thread::sleep_for(std::chrono::milliseconds(interval));
				task();
			}

			{
				// timer be stopped, update the condition variable expired and wake main thread
				std::lock_guard<std::mutex> locker(_mutex);
				_expired = true;
				_expired_cond.notify_one();
			}
		}).detach();
	}

	void startOnce(int delay, std::function<void()> task)
	{
		std::thread([delay, task]() {
			std::this_thread::sleep_for(std::chrono::milliseconds(delay));
			task();
		}).detach();
	}

	void stop()
	{
		// do not stop again
		if (_expired)
			return;

		if (_try_to_expire)
			return;

		// wait until timer 
		_try_to_expire = true; // change this bool value to make timer while loop stop
		{
			std::unique_lock<std::mutex> locker(_mutex);
			_expired_cond.wait(locker, [this] {return _expired == true; });

			// reset the timer
			if (_expired == true)
				_try_to_expire = false;
		}
	}

private:
	std::atomic<bool> _expired; // timer stopped status
	std::atomic<bool> _try_to_expire; // timer is in stop process
	std::mutex _mutex;
	std::condition_variable _expired_cond;
};

Timer class is a direct copy of an article

Functions that can be added

Readers can adjust the structure by themselves and add some interesting small functions. For example, the symbol used to express the completed content can be replaced with a favorite symbol, or a color can be added

There are also some complex functions, such as grouping progress bar, etc. However, because I don’t have this requirement, I haven’t studied it. Readers can study it by themselves

So far, this article about using C + + to implement a command-line progress bar example code is introduced here. For more related C + + command-line progress bar content, please search previous articles of developer or continue to browse the following related articles. I hope you can support developer more in the future!