Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Allow to manually redraw the progress bar #688

Open
ClementNerma opened this issue Jan 20, 2025 · 15 comments
Open

Allow to manually redraw the progress bar #688

ClementNerma opened this issue Jan 20, 2025 · 15 comments

Comments

@ClementNerma
Copy link

Hi!

I've been using this crate a lot and it's been amazing, thanks for making it <3

I only have one problem currently, which is that the progress bar doesn't update visually when position changes happen too quickly. Here is an example:

use indicatif::ProgressBar;

static LEN: u64 = 10000;

fn main() {
    let pb = ProgressBar::new(LEN);

    for _ in 0..LEN {
        pb.inc(1);
        // pb.tick();
    }

    std::thread::sleep(std::time::Duration::from_secs(10000));
}

If you run this program, you will see when we reach the sleep portion that the program only has a part of the progress bar filled. Every time you run the program, the final position is a little bit different.

Uncommenting the pb.tick(); line worsens the problem as the bar gets stuck on position 10 / 10000 (I understand the problem with the previous code why not sure why for this one?)

So I'd like to be able to manually refresh the progress bar and redraw it. Something like pb.redraw() or pb.refresh(). It would obviously greatly increase latency and this example code would become a lot slower, but that would solve the problem I have.

I didn't find a way to do this currently ; would it be possible to add this feature?

Many thanks :)

@jaheba
Copy link
Contributor

jaheba commented Jan 20, 2025

Hi,

not 100% sure I understand your issue. But have you had a look at enable_steady_tick?

That will decouple rendering of the progress bar from your calls to pb.inc().

@ClementNerma
Copy link
Author

ClementNerma commented Jan 20, 2025

Hmm seems like [enable_steady_tick] works fine. Not really sure why.

But basically I'd like to manually redraw the progress bar without having a separate thread. I'd like the output to be refreshed synchronously when the progress bar changes.

When you look at the example code I provided, I would expect the call to .tick() to actually refresh the output and draw the progress bar with its current position.

@jaheba
Copy link
Contributor

jaheba commented Jan 20, 2025

Not every call to inc will update the progress bar for performance reasons.

Normally you would call .finish() or .abandon() once the work is done. This will also force a re-render of the progress bar. As a hack, you could also call set_tab_width(8), which also forces the bar to be rerendered.

AFAIK, there is no other way to enforce that the bar is drawn.

@ClementNerma
Copy link
Author

Would it be possible to add a method that allows to force re-rendering of the progress bar? To ensure it is completely synchronized with its internal state.

@jaheba
Copy link
Contributor

jaheba commented Jan 20, 2025

Can you talk a bit about your use-case? I'm curious to learn why the intended ways don't match your needs.


In my view it should be fine to add a .force_draw() method or something akin to it to indicatif, but that's @djc decision to make.

There is also suspend, which is essentially just a force draw:

    for _ in 0..LEN {
        pb.inc(1);
    }

    pb.suspend(|| {});

Alternatively you can use console::Term as a draw target to enforce rendering on pb.tick():

    pb.set_draw_target(ProgressDrawTarget::term_like(Box::new(Term::stderr())));

    for _ in 0..LEN {
        pb.inc(1);
    }

    pb.tick();

@ClementNerma
Copy link
Author

I have several use cases where I had this problem, one of them is a progress bar to follow the copy of lots of small files. After the copying is done, the program continues by doing other things, but keeps the progress bar displayed as it provides informations on how much time was spent and how many files were copied.

Using just .inc() and/or .tick() when the progress bar isn't synchronized with its .position(). Enabling .enable_steady_stick() works fine but I'd prefer to not spin another thread just to tick a progress bar.

@jaheba
Copy link
Contributor

jaheba commented Jan 21, 2025

I guess the canonical way would be to enable the steady tick, since that would also update the ETA, etc. I also think it's fine to have a separate thread to manage that.

But I also see little harm in allowing users to force a redraw.

@ClementNerma
Copy link
Author

This is a pretty niche use case for sure, in most cases I agree it's better to use [`enable_steady_stick'].
Thanks for making a PR :)

@djc
Copy link
Member

djc commented Jan 21, 2025

Why doesn't ProgressBar::tick() do what you want? What is the high-level motivation here?

@ClementNerma
Copy link
Author

ProgressBar::tick() doesn't seem to redraw the progress bar if it's been redrawn too recently.

What I want to do is to ensure, at one specific point in time, that the displayed progress bar matches the position.

I don't want to have a delay between the moment I call .inc() and the moment it's updated on the screen. I want it to happen synchronously.

@djc
Copy link
Member

djc commented Jan 22, 2025

What I want to do is to ensure, at one specific point in time, that the displayed progress bar matches the position.

I don't want to have a delay between the moment I call .inc() and the moment it's updated on the screen. I want it to happen synchronously.

Your exact requirements still aren't making sense to me. Either you call tick() or inc() often enough that rate limiting might occur but then it should redraw pretty soon on one of the other calls, or you call it rarely (that is, less than once every 50ms) and it should redraw every time you call it anyway. Your comments seem to indicate you need to call it rarely but then require that drawing matches other things you draw but given that in that case no rate limiting occurs anyway, I don't see a problem.

@ClementNerma
Copy link
Author

Sorry if I didn't explain it well (english is not my first language so it's pretty hard to explain some things).
I actually call .inc() very often, like multiple times per second, and I'd like the redraw to happen instantly without having to use a separate thread.

@djc
Copy link
Member

djc commented Jan 22, 2025

I actually call .inc() very often, like multiple times per second, and I'd like the redraw to happen instantly without having to use a separate thread.

The rate limiting is in place to make sure you don't inadvertently incur a relatively large overhead just from drawing the progress bar, though. So why is it important to you to draw every time you call inc()? If you need to synchronize drawing with some other stuff you have going on, perhaps it would be better to not call inc() on every iteration but call it with a larger delta() every few iterations?

@ClementNerma
Copy link
Author

Ok so let me explain the concrete problem (maybe I should have started from that):

I'm currently building a shell (akin to Bash / ZSH / Fish / etc.) which exposes some functions to create and manipulate a progress bar.

Due to the nature of shell scripting, there can be other outputs from other commands at the same time that the progress bar is making progress. So it's important that the output is more or less instantly updated when the progress bar's advancement changes.

One other problem I've had is when the progress bar is making changes rapidly, and the program exits before the progress bar has updated the terminal's output. In such case, we end up with the problem that occurs in the first example I gave, in which when the program ends the progress bar's displayed position does not match its internal state.

Hence why I'd like to have instant synchronization, even if it indeed creates a large overhead. But this is something I can handle or customize in my own shell if necessary.

@djc
Copy link
Member

djc commented Feb 18, 2025

Okay, and #689 would address your use case?

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants