rsalmei / alive-progress

A new kind of Progress Bar, with real-time throughput, ETA, and very cool animations!
MIT License
5.53k stars 206 forks source link

As a suggestion for the showtime demo #199

Closed kdschlosser closed 2 years ago

kdschlosser commented 2 years ago

The use of this line of code in exhibit.py line 189 is no good.

            time.sleep(max(0., start + frame * sleep - current))

That is because the resolution of time.sleep is horrible. time.sleep also does not return exactly when it is supposed to but instead will return when it feels like it.

That is the reason why you limit the frame rate to 60fps.

you can remove that limitation by using this function instead of time.sleep

def spinning_wheel(wait):
    wait = wait * 1000.0
    strt = time.time() * 1000
    stp = time.time() * 1000
    while stp - strt < wait:
        stp = time.time() * 1000

200 fps

200fps

500 fps

500fps

TheTechRobo commented 2 years ago

Um, it's supposed to be limited to 60fps. How is the 500fps or even the 200fps a good demo?

kdschlosser commented 2 years ago

Well if by chance you are say using the progress bar to indicate files that are being compiled the example of 60fps shows that it would hold up the compilation because of how slow it is to redraw the screen. So when I ran the showtime and entered 200.0 for the fps and it was moving as slow as it was the first thought was "well this isn't gonna work because it's too slow" But something told me to investigate a little further and as it turns out it's not slow it's a limitation that has been put in place because of the inaccuracies of time.sleep The other thing is not being able to hit the target. so say I enter 40FPS as a target time.sleep not returning exactly when it is supposed to is going to cause the program to run slower then it should be. It fixes that issue but at the same time removes the hard limit of 60fps that is caused by using time.sleep

TheTechRobo commented 2 years ago

But it would completely screw up the spinner

kdschlosser commented 2 years ago

and how does it do that? you have a defaulted setting of 15fps.

You haven't even tested it. I know this because you would not have made the comment above about it screwing up the spinners which it does NOT do. You aren't even the maintainer /author of this library so why are you even commenting?

TheTechRobo commented 2 years ago

No, I have not tested it. But just your showtime GIFs both show that it would. If I am coming across as rude or misunderstanding what you're, I'm sorry - it was not intentional.

rsalmei commented 2 years ago

Hi @kdschlosser, I'm the author. @TheTechRobo informally helps me in a lot of tickets, and I'm glad he does 👍

Regarding time.sleep(), I'm well aware of its inaccuracy, especially in Windows, which comes in the ~10ms range... It's abhorring.

That line you cited not good was actually an outstanding achievement. Instead of trying to adjust the timing between frames, I controlled the time since the very first frame. That way, I can smooth out inaccuracies in time.sleep along the whole animation course. Trust me it stabilized much better than any other method I came up with before.

But I didn't quite get your suggestion, care to explain it better? And also, why would you try 200 or more frames per second? I cap this refresh rate internally to 60fps not because of technical reasons, but because of human reasons! We humans can't see that fast, and I can limit the CPU usage to saner limits at the same time.

kdschlosser commented 2 years ago

I was looking for a progress bar library that handles displaying multiple bars at the same time. Your library caught my eye tho it doesn't work how I need it to. I need to be able to have 32 bars that gets updated from 32 different threads. The speed in which they are going to be updated is fast. new bars will be created in the space after one that finishes. and process goes on for several minutes.

The use of "frames per second" to describe what is taking place with your library is a really bad term to use because that is not what it is inferring. The only time the screen is going to get redrawn by your library is when an update is performed and that limit of 60 means the bar is only able to be updated 60 times in a second. I took that as the limit because of whatever the library is going internally. So if I have a bar that has a min value of 0 and a max value of 1000 it would take 16.66 seconds. But if my side of the program can run those 1000 updates in 5 seconds than your library would be slowing mine down. That is not the case in reality but that is what your showtime example infers. I I keyed in 200 for the target FPS meaning I need to update the bar 200 times a second and showtime only displays 60fps that leads a person to believe that 60 times per second is the cap or the maximum number of times per second that a bar is able to be updated at.

The showtime is only for showing what your library is capable of doing. forcing a restriction that limits how fast it can update doesn't allow that to happen. I understand why the cap is there and that cap is due to problems with time.sleep. If you think about it the running of showtime should have no consideration for resource hogging as it is a demo and by design it should gobble up all that it can in order to show the best it has to offer. using time.sleep for the simple purpose of stalling a program is not a good tradeoff for using a mechanism that is more accurate. the function above takes no consideration for resources and it will gobble up 100% of a single CPU core when running. and that's fine that it does that because it is accurate and also because showtime is a demo.

What you did with timing how long it took the code to run to offset the wait time is what should be done. On Windows the smallest amount of sleep time that is achievable is 16ms when using time.sleep. Unfortunately Python does not take advantage of the CREATE_WAITABLE_TIMER_HIGH_RESOLUTION macro which creates a wait timer that has a 20 microsecond resolution (available in Windows 10+) which is much better than a traditional timer resolution of 15.6 milliseconds.

1000 / 15.6 = 64.10 frames per second.

I can write a ctypes binder to the Windows API to create a proper sleep routine that will not cause 100% consumption of a CPU core. But seeing as how it really shouldn't matter because this is a demo than using the code example I outlined above does the job and removes that 60 as the cap.

You can do whatever it is you want. From a user standpoint I was simply pointing out a limitation in the library that doesn't need to be there and how to correct that limitation. The end game is the library does not support displaying more then one bar at a time from multiple threads where the threads would be updating the bars. So it is of no real use to me anyhow. When I saw the example and it having all of the bars running like they are it led me into thinking it is able to work properly with threads.

rsalmei commented 2 years ago

The use of "frames per second" to describe what is taking place with your library is a really bad term The only time the screen is going to get redrawn by your library is when an update is performed

Those are entirely wrong. The alive-progress is, as its name implies, alive, i.e. it is multithreaded, and will refresh itself regardless of what happens in your processing. The term "frames per second" is an excellent term, which describes exactly what it is inferring. I even created an FPS calibration profile so you can adjust how much these two systems interact with each other. The objective is to make the user feel if his processing is fast or slow, and by how much. That's why I hard-coded a maximum of 60 fps, because I felt that more was not worth it, I couldn't see any differences with more than 60 fps, so why waste the CPU cycles? A monitoring system has to be light, no point in using up all client's CPU, when he has actual work to be done.

So if I have a bar that has a min value of 0 and a max value of 1000 it would take 16.66 seconds.

No no no, you got it all wrong. You can have a bar from 0 to 1 billion, and make it run in 1 second or in 1 hour. Again, both are completely disconnected! I'll compute the FPS calibration profile above, define a "psychologically accurate" representation of the speed of your processing, and use that to refresh the bar as much as needed. In other words, I calculate how many updates I have to skip or introduce for a human to perceive the processing speed. If it is too slow, I defined a minimum of 4 fps, if it is too fast 60 fps.

Anyway, I think you got the point now. In essence yes, showtime is a demo, but a demo I also want to showcase the hidden features of the implementation, especially its efficiency and lightness on resources. I even went the extra mile and implemented a Spinner Compiler! This means all animation frames are pre-computed before playing, exchanging a little more memory for less CPU. That is key in getting ALL those spinners running concurrently, smoothly, and so lightly. Imagine someone running showtime for a few minutes, and his laptop turns its fans on and gets louder... I don't want that, my code doesn't do that to people's machines. Play with the spinner compiler, it is awesome.

Thank you for your thoughts!

kdschlosser commented 2 years ago

So the progress bars update all on their own? how would that work? it's supposed to show the progress of the program that is running. If it updates all on it's own the bar could finish before or after the program has finished doing what it needs to do. That doesn't make any sense.

and if each bar runs in it's won thread how come I don't see threading used anywhere? how come the showtime runs in a while loop?

I tried making 10 bars from 10 separate threads and updating them separately, It didn't work.

TheTechRobo commented 2 years ago

I tried making 10 bars from 10 separate threads and updating them separately, It didn't work.

You're probably running into #20. The bars would be competing for terminal space.

rsalmei commented 2 years ago

You didn't search it that much I suppose, because it is right in the core/progress: https://github.com/rsalmei/alive-progress/blob/main/alive_progress/core/progress.py#L210-L213

The renderer engine runs in a separate thread. It sleeps most of the time, and wakes up occasionally to render the configured widgets. This sleep can be aborted anytime by the print and logging hooks, where I make the bar clear its trail, print the user content, and immediately refresh the bar below it. There's a Critical Condition between these modules. If the user provided the total number of items, I can infer when the processing will finish. But even if the user program does not respect that it still works because I used a context manager for the bar. This means that, when the user program finishes I receive a method call, so I can prepare and print what I call a "receipt", i.e. the actual number of items processed, how long it took, and how many were processed per second.

The bar would never finish before or after the user program because the user program controls it. The main context manager I created returns a handle bar, which the user uses to send commands into the bar. If the user aborts it before the expected I explained above, if the user decides to move on after the expected, the bar will just continue counting up, if the user program has a super long last step, the bar will stop there too, which makes the super cool SPINNER much more important, so he can see the process did not hang. This shows the multithreaded engine too, because even without ANY user input, the spinner will happily spin...

That's it, I hope it is clearer, and thank you for your interest.

Regarding multiple bars at the same time, I still didn't have the time to implement them. It requires a different controller because the terminal has only one cursor, and multiple bars refreshing at possibly different speeds require a sharp adjustment, probably enqueuing the redraw requests and moving the cursor accordingly, and a whole lot of critical sections more... It is not easy at all, that's why I couldn't write it yet. This is a very requested feature, as cited above, in #20.