Skip to content

Instantly share code, notes, and snippets.

@wolfv
Created January 7, 2023 12:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wolfv/4f567fe9a17e449a6305b5ea50afa542 to your computer and use it in GitHub Desktop.
Save wolfv/4f567fe9a17e449a6305b5ea50afa542 to your computer and use it in GitHub Desktop.
Immediate mode rendering progress bars
#include <chrono>
#include <iostream>
#include <string>
#include <thread>
#include <random>
#include <sys/ioctl.h>
namespace cursor
{
class CursorMovementTriple
{
public:
CursorMovementTriple(const char* esc, int n, const char* mod)
: m_esc(esc)
, m_mod(mod)
, m_n(n)
{
}
const char* m_esc;
const char* m_mod;
int m_n;
};
inline std::ostream& operator<<(std::ostream& o, const CursorMovementTriple& m)
{
o << m.m_esc << m.m_n << m.m_mod;
return o;
}
class CursorMod
{
public:
CursorMod(const char* mod)
: m_mod(mod)
{
}
std::ostream& operator<<(std::ostream& o)
{
o << m_mod;
return o;
}
const char* m_mod;
};
inline std::ostream& operator<<(std::ostream& o, const CursorMod& m)
{
o << m.m_mod;
return o;
}
inline auto up(int n)
{
return CursorMovementTriple("\x1b[", n, "A");
}
inline auto down(int n)
{
return CursorMovementTriple("\x1b[", n, "B");
}
inline auto forward(int n)
{
return CursorMovementTriple("\x1b[", n, "C");
}
inline auto back(int n)
{
return CursorMovementTriple("\x1b[", n, "D");
}
inline auto next_line(int n)
{
return CursorMovementTriple("\x1b[", n, "E");
}
inline auto prev_line(int n)
{
return CursorMovementTriple("\x1b[", n, "F");
}
inline auto horizontal_abs(int n)
{
return CursorMovementTriple("\x1b[", n, "G");
}
inline auto home()
{
return CursorMod("\x1b[H");
}
inline auto erase_display(int n = 0)
{
return CursorMovementTriple("\x1b[", n, "J");
}
inline auto erase_line(int n = 0)
{
return CursorMovementTriple("\x1b[", n, "K");
}
inline auto scroll_up(int n = 1)
{
return CursorMovementTriple("\x1b[", n, "S");
}
inline auto scroll_down(int n = 1)
{
return CursorMovementTriple("\x1b[", n, "T");
}
inline auto show()
{
return CursorMod("\x1b[?25h");
}
inline auto hide()
{
return CursorMod("\x1b[?25l");
}
inline auto pos()
{
return CursorMod("\x1b[R");
}
inline auto delete_line(int n = 1)
{
return CursorMovementTriple("\x1b[", n, "M");
}
inline auto alternate_screen()
{
return CursorMod("\x1b[?1049h");
}
inline auto main_screen()
{
return CursorMod("\x1b[?1049l");
}
} // namespace cursor
struct progress_bar_state {
size_t total;
size_t current;
std::chrono::time_point<std::chrono::system_clock> start;
std::string suffix;
std::string prefix;
bool draw_speed;
bool draw_time;
bool is_bytes;
std::mutex* mtx = nullptr;
void set_state(progress_bar_state state) {
if (mtx)
mtx->lock();
total = state.total;
current = state.current;
start = state.start;
suffix = state.suffix;
prefix = state.prefix;
draw_speed = state.draw_speed;
draw_time = state.draw_time;
is_bytes = state.is_bytes;
if (mtx)
mtx->unlock();
}
};
int get_console_width()
{
#ifndef _WIN32
struct winsize w;
ioctl(0, TIOCGWINSZ, &w);
return w.ws_col;
#else
CONSOLE_SCREEN_BUFFER_INFO coninfo;
auto res = GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo);
return coninfo.dwSize.X;
#endif
}
void progress_bar_render(const progress_bar_state& state) {
// render progress bar
auto terminal_width = get_console_width();
std::string suffix = state.suffix;
if (state.draw_speed) {
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - state.start).count();
auto speed = (double)state.current / (double)elapsed;
if (state.is_bytes) {
if (speed > 1024 * 1024) {
suffix += " " + std::to_string(speed / (1024 * 1024)) + " MB/s";
} else if (speed > 1024) {
suffix += " " + std::to_string(speed / 1024) + " KB/s";
} else {
suffix += " " + std::to_string(speed) + " B/s";
}
}
}
auto bar_width = terminal_width - 4 - suffix.size() - state.prefix.size();
auto progress = (double)state.current / (double)state.total;
auto pos = (size_t)(bar_width * progress);
std::cout << state.prefix << " [";
for (size_t i = 0; i < bar_width; ++i) {
if (i < pos) std::cout << "=";
else if (i == pos) std::cout << ">";
else std::cout << " ";
}
std::cout << "] " << suffix << "\n" << std::flush;
}
class progress_bar_manager {
public:
void start() {
running = true;
render_thread = std::thread(&progress_bar_manager::render_loop, this);
}
void add_progress_bar(std::shared_ptr<progress_bar_state> state) {
std::lock_guard<std::mutex> lock(mutex);
state->mtx = &mutex;
states.push_back(state);
}
void render_loop() {
int i = 0;
while (running) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::lock_guard<std::mutex> lock(mutex);
if (i)
std::cout << cursor::up(states.size());
for (auto& state : states) {
progress_bar_render(*state);
}
i = 1;
}
}
~progress_bar_manager() {
running = false;
render_thread.join();
}
private:
bool running = false;
std::mutex mutex;
std::thread render_thread;
std::vector<std::shared_ptr<progress_bar_state>> states;
};
std::shared_ptr<progress_bar_state> create_initial_state(size_t total, std::string name) {
auto state = std::make_shared<progress_bar_state>();
state->total = total;
state->current = 0;
state->start = std::chrono::system_clock::now();
state->suffix = "0%";
state->prefix = "Progress " + name;
state->draw_speed = true;
state->draw_time = true;
state->is_bytes = true;
return state;
}
static auto dev = std::random_device();
progress_bar_state get_new_state(const progress_bar_state& old) {
progress_bar_state new_state = old;
new_state.current += dev() % 10000;
new_state.suffix = std::to_string((size_t)(100.0 * (double)new_state.current / (double)new_state.total)) + "%";
return new_state;
}
int main() {
auto s1 = create_initial_state(100000, "1");
auto s2 = create_initial_state(100000, "2");
auto s3 = create_initial_state(100000, "3");
progress_bar_manager manager;
manager.add_progress_bar(s1);
manager.add_progress_bar(s2);
manager.add_progress_bar(s3);
manager.start();
for (size_t i = 0; i < 100; ++i) {
s1->set_state(get_new_state(*s1));
s2->set_state(get_new_state(*s2));
s3->set_state(get_new_state(*s3));
std::this_thread::sleep_for(std::chrono::milliseconds(dev() % 100));
}
// std::cout << std::endl;
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment