From 6ad9ec43bca0d93053b679881769cac7e7efdd4d Mon Sep 17 00:00:00 2001 From: Giedrius Jonikas Date: Sat, 2 Nov 2024 12:26:59 +0000 Subject: [PATCH 1/2] MultiProgressBar now buffers the output text to a single write This is to avoid multiple small writes to the terminal, which can cause flickering. --- .../progressbar/multi_progress_bar.cpp | 35 ++++++++++++------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/libdnf5-cli/progressbar/multi_progress_bar.cpp b/libdnf5-cli/progressbar/multi_progress_bar.cpp index 450d8d13d..c6f07c5e7 100644 --- a/libdnf5-cli/progressbar/multi_progress_bar.cpp +++ b/libdnf5-cli/progressbar/multi_progress_bar.cpp @@ -98,15 +98,22 @@ std::size_t MultiProgressBar::get_total_num_of_bars() const noexcept { std::ostream & operator<<(std::ostream & stream, MultiProgressBar & mbar) { const bool is_interactive{tty::is_interactive()}; + // We'll buffer the output text to a single string and print it all at once. + // This is to avoid multiple writes to the terminal, which can cause flickering. + static std::ostringstream text_buffer; + text_buffer.str(""); + text_buffer.clear(); + if (is_interactive && mbar.num_of_lines_to_clear > 0) { - stream << tty::clear_line; + text_buffer << tty::clear_line; for (std::size_t i = 1; i < mbar.num_of_lines_to_clear; i++) { - stream << tty::cursor_up << tty::clear_line; + text_buffer << tty::cursor_up << tty::clear_line; } - stream << "\r"; + text_buffer << "\r"; } else if (mbar.line_printed) { - stream << std::endl; + text_buffer << std::endl; } + mbar.num_of_lines_to_clear = 0; mbar.line_printed = false; @@ -124,8 +131,8 @@ std::ostream & operator<<(std::ostream & stream, MultiProgressBar & mbar) { } bar->set_number(numbers.back()); numbers.pop_back(); - stream << *bar; - stream << std::endl; + text_buffer << *bar; + text_buffer << std::endl; mbar.bars_done.push_back(bar); // TODO(dmach): use iterator mbar.bars_todo.erase(mbar.bars_todo.begin() + static_cast(i)); @@ -148,9 +155,9 @@ std::ostream & operator<<(std::ostream & stream, MultiProgressBar & mbar) { continue; } if (mbar.line_printed) { - stream << std::endl; + text_buffer << std::endl; } - stream << *bar; + text_buffer << *bar; mbar.line_printed = true; mbar.num_of_lines_to_clear++; mbar.num_of_lines_to_clear += bar->get_messages().size(); @@ -178,12 +185,12 @@ std::ostream & operator<<(std::ostream & stream, MultiProgressBar & mbar) { if ((mbar.bars_all.size() >= mbar.total_bar_visible_limit) && (is_interactive || mbar.bars_todo.empty())) { if (mbar.line_printed) { - stream << std::endl; + text_buffer << std::endl; } // print divider int terminal_width = tty::get_width(); - stream << std::string(static_cast(terminal_width), '-'); - stream << std::endl; + text_buffer << std::string(static_cast(terminal_width), '-'); + text_buffer << std::endl; // print Total progress bar mbar.total.set_number(static_cast(mbar.bars_done.size())); @@ -196,11 +203,13 @@ std::ostream & operator<<(std::ostream & stream, MultiProgressBar & mbar) { mbar.total.set_state(ProgressBarState::SUCCESS); } - stream << mbar.total; - stream << std::endl; + text_buffer << mbar.total; + text_buffer << std::endl; mbar.num_of_lines_to_clear += 3; } + stream << text_buffer.str(); // Single syscall to output all commands + return stream; } From d2167053d281203f9dc61cd5d02392d0099d016b Mon Sep 17 00:00:00 2001 From: Giedrius Jonikas Date: Thu, 7 Nov 2024 18:01:20 +0000 Subject: [PATCH 2/2] Optimise multi_progress_bar tty control sequences Instead of sending multiple cursor_up and clear_line sequences, send a single command to move the cursor up multiple lines. This makes the tty::clear_line redundant, because the next update will overwrite the previous lines anyway. --- libdnf5-cli/progressbar/multi_progress_bar.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libdnf5-cli/progressbar/multi_progress_bar.cpp b/libdnf5-cli/progressbar/multi_progress_bar.cpp index c6f07c5e7..8cd9eb499 100644 --- a/libdnf5-cli/progressbar/multi_progress_bar.cpp +++ b/libdnf5-cli/progressbar/multi_progress_bar.cpp @@ -105,9 +105,9 @@ std::ostream & operator<<(std::ostream & stream, MultiProgressBar & mbar) { text_buffer.clear(); if (is_interactive && mbar.num_of_lines_to_clear > 0) { - text_buffer << tty::clear_line; - for (std::size_t i = 1; i < mbar.num_of_lines_to_clear; i++) { - text_buffer << tty::cursor_up << tty::clear_line; + if (mbar.num_of_lines_to_clear > 1) { + // Move the cursor up by the number of lines we want to write over + text_buffer << "\033[" << (mbar.num_of_lines_to_clear - 1) << "A"; } text_buffer << "\r"; } else if (mbar.line_printed) {