p-ranav / indicators

Activity Indicators for Modern C++
MIT License
3.04k stars 237 forks source link

`DynamicProgress` does not work well when printing other logs at the same time #138

Closed yczhang-nv closed 1 month ago

yczhang-nv commented 1 month ago

Currently I'm trying to use DynamicProgress along with my own logger, which outputs logs to the same terminal as the progress bars. I would like to let the DynamicProgress to always display as the last line of the output, so that it does not cover the log output.

I've looked into the method mentioned in #107 and #108, and it works well with one ProgressBar. But when I integrated the method with DynamicProgress, only a small portion of DynamicProgress show in the terminal.

Below is a code snippet that reproduces the issue:

#include <indicators/cursor_control.hpp>
#include <indicators/progress_bar.hpp>
#include <indicators/progress_spinner.hpp>
#include <indicators/dynamic_progress.hpp>
#include <boost/iostreams/filter/line.hpp>
#include <boost/iostreams/filtering_streambuf.hpp>
#include <vector>

int main() {
    using namespace indicators;
    struct LineInsertingFilter : boost::iostreams::line_filter
{
    std::string do_filter(const std::string& line)
    {
        return "\n\033[A\033[1L" + line;
    }
};
   auto* stdout_buf = std::cout.rdbuf();  // get stdout streambuf

   // create a filtering_ostreambuf with our filter and the stdout streambuf as a sink
   boost::iostreams::filtering_ostreambuf filtering_buf{};
   filtering_buf.push(LineInsertingFilter());
   filtering_buf.push(*stdout_buf);

   std::cout.rdbuf(&filtering_buf);  // configure std::cout to use our streambuf

   std::ostream os(stdout_buf);  // create local ostream acting as std::cout normally would

   auto bar1 = std::make_unique<ProgressBar>(
       option::BarWidth{50},
       option::ForegroundColor{Color::red},
       option::ShowElapsedTime{true},
       option::ShowRemainingTime{true},
       option::PrefixText{"5c90d4a2d1a8: Downloading "},
       indicators::option::FontStyles{std::vector<indicators::FontStyle>{indicators::FontStyle::bold}},
       option::Stream{os});

   auto bar2 = std::make_unique<ProgressBar>(
       option::BarWidth{50},
       option::ForegroundColor{Color::yellow},
       option::ShowElapsedTime{true},
       option::ShowRemainingTime{true},
       option::PrefixText{"22337bfd13a9: Downloading "},
       indicators::option::FontStyles{std::vector<indicators::FontStyle>{indicators::FontStyle::bold}},
       option::Stream{os});

   auto bar3 = std::make_unique<ProgressBar>(
       option::BarWidth{50},
       option::ForegroundColor{Color::green},
       option::ShowElapsedTime{true},
       option::ShowRemainingTime{true},
       option::PrefixText{"10f26c680a34: Downloading "},
       indicators::option::FontStyles{std::vector<indicators::FontStyle>{indicators::FontStyle::bold}},
       option::Stream{os});

   auto bar4 = std::make_unique<ProgressBar>(
       option::BarWidth{50},
       option::ForegroundColor{Color::white},
       option::ShowElapsedTime{true},
       option::ShowRemainingTime{true},
       option::PrefixText{"6364e0d7a283: Downloading "},
       indicators::option::FontStyles{std::vector<indicators::FontStyle>{indicators::FontStyle::bold}},
       option::Stream{os});

   auto bar5 = std::make_unique<ProgressBar>(
       option::BarWidth{50},
       option::ForegroundColor{Color::blue},
       option::ShowElapsedTime{true},
       option::ShowRemainingTime{true},
       option::PrefixText{"ff1356ba118b: Downloading "},
       indicators::option::FontStyles{std::vector<indicators::FontStyle>{indicators::FontStyle::bold}},
       option::Stream{os});

   auto bar6 = std::make_unique<ProgressBar>(
       option::BarWidth{50},
       option::ForegroundColor{Color::cyan},
       option::ShowElapsedTime{true},
       option::ShowRemainingTime{true},
       option::PrefixText{"5a17453338b4: Downloading "},
       indicators::option::FontStyles{std::vector<indicators::FontStyle>{indicators::FontStyle::bold}},
       option::Stream{os});

   std::cout << termcolor::bold << termcolor::white << "Pulling image foo:bar/baz\n";

   // Construct with 3 progress bars. We'll add 3 more at a later point
   DynamicProgress<ProgressBar> bars(bar1, bar2, bar3);

   // Do not hide bars when completed
   bars.set_option(option::HideBarWhenComplete{false});

   std::thread fourth_job, fifth_job, sixth_job;

   auto job4 = [&bars](size_t i) {
       while (true)
       {
           bars[i].tick();
           if (bars[i].is_completed())
           {
               bars[i].set_option(option::PrefixText{"6364e0d7a283: Pull complete "});
               bars[i].mark_as_completed();
               break;
           }
           std::this_thread::sleep_for(std::chrono::milliseconds(50));
       }
   };

   auto job5 = [&bars](size_t i) {
       while (true)
       {
           bars[i].tick();
           if (bars[i].is_completed())
           {
               bars[i].set_option(option::PrefixText{"ff1356ba118b: Pull complete "});
               bars[i].mark_as_completed();
               break;
           }
           std::this_thread::sleep_for(std::chrono::milliseconds(100));
       }
   };

   auto job6 = [&bars](size_t i) {
       while (true)
       {
           bars[i].tick();
           if (bars[i].is_completed())
           {
               bars[i].set_option(option::PrefixText{"5a17453338b4: Pull complete "});
               bars[i].mark_as_completed();
               break;
           }
           std::this_thread::sleep_for(std::chrono::milliseconds(40));
       }
   };

   auto job1 = [&bars, &bar6, &sixth_job, &job6]() {
       while (true)
       {
           bars[0].tick();
           if (bars[0].is_completed())
           {
               bars[0].set_option(option::PrefixText{"5c90d4a2d1a8: Pull complete "});
               // bar1 is completed, adding bar6
               auto i    = bars.push_back(std::move(bar6));
               sixth_job = std::thread(job6, i);
               sixth_job.join();
               break;
           }
           std::this_thread::sleep_for(std::chrono::milliseconds(140));
       }
   };

   auto job2 = [&bars, &bar5, &fifth_job, &job5]() {
       while (true)
       {
           bars[1].tick();
           if (bars[1].is_completed())
           {
               bars[1].set_option(option::PrefixText{"22337bfd13a9: Pull complete "});
               // bar2 is completed, adding bar5
               auto i    = bars.push_back(std::move(bar5));
               fifth_job = std::thread(job5, i);
               fifth_job.join();
               break;
           }
           std::this_thread::sleep_for(std::chrono::milliseconds(25));
       }
   };

   auto job3 = [&bars, &bar4, &fourth_job, &job4]() {
       while (true)
       {
           bars[2].tick();
           if (bars[2].is_completed())
           {
               bars[2].set_option(option::PrefixText{"10f26c680a34: Pull complete "});
               // bar3 is completed, adding bar4
               auto i     = bars.push_back(std::move(bar4));
               fourth_job = std::thread(job4, i);
               fourth_job.join();
               break;
           }
           std::this_thread::sleep_for(std::chrono::milliseconds(50));
       }
   };

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

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

  return 0;
}

And the progress bar looks like this (there should be 6 bars in total, but only one of them are displaying):

Screenshot from 2024-10-01 09-31-35

I'll appreciate any helps to get this to work.

yczhang-nv commented 1 month ago

The blocker for this is the DynamicProgress is hard coded to output to std::cout, which is mentioned in #123 . The workaround in #107 can work with multiple progress bars by re-implementing the print_progress() function of DynamicProgress by redirecting the output to the customized ostream.