From: Michael Tremer Date: Tue, 19 Jul 2022 16:20:38 +0000 (+0000) Subject: progressbar: Make this a lot smoother X-Git-Tag: 0.9.28~667 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e4a94c7aa7f232883303dd2bd98ddcd733f5a8c6;p=pakfire.git progressbar: Make this a lot smoother Previously, the progress bar was redrawn very often which did not look great on the terminal. We are now redrawing the bar about 20 times a second which gives us a smooth experience. Signed-off-by: Michael Tremer --- diff --git a/src/libpakfire/progressbar.c b/src/libpakfire/progressbar.c index 273c7a2c8..c4681bf69 100644 --- a/src/libpakfire/progressbar.c +++ b/src/libpakfire/progressbar.c @@ -33,7 +33,18 @@ #include #include -#define REDRAW_TIMEOUT 250 +#define DRAWS_PER_SECOND 20 + +static const struct itimerspec TIMER = { + .it_interval = { + .tv_sec = 0, + .tv_nsec = 1000000000 / DRAWS_PER_SECOND, + }, + .it_value = { + .tv_sec = 0, + .tv_nsec = 1000000000 / DRAWS_PER_SECOND, + }, +}; struct pakfire_progressbar_widget { STAILQ_ENTRY(pakfire_progressbar_widget) nodes; @@ -61,11 +72,9 @@ struct pakfire_progressbar { // The progress unsigned long value; unsigned long value_max; - unsigned long value_redraw; - unsigned long update_interval; struct timespec time_start; - struct timespec time_redraw; + timer_t timer; // Widgets STAILQ_HEAD(widgets, pakfire_progressbar_widget) widgets; @@ -121,10 +130,96 @@ static void pakfire_progressbar_free_widgets(struct pakfire_progressbar* p) { } static void pakfire_progressbar_free(struct pakfire_progressbar* p) { + // Delete the timer + if (p->timer) + timer_delete(p->timer); + pakfire_progressbar_free_widgets(p); free(p); } +static int pakfire_progressbar_draw(struct pakfire_progressbar* p) { + struct pakfire_progressbar_widget* widget; + + // Update terminal size if not set + if (!terminal.cols) + pakfire_progressbar_update_terminal_size(SIGWINCH); + + unsigned int cols_left = terminal.cols - p->num_widgets - 2; + unsigned int i = 0; + + // Create an array with the result of all print functions + const char* elements[p->num_widgets]; + + // Reset all elements + for (i = 0; i < p->num_widgets; i++) + elements[i] = NULL; + + // Reset i + i = 0; + + // Process all non-expandable widgets in the first pass + STAILQ_FOREACH(widget, &p->widgets, nodes) { + const char* element = NULL; + + // Clear any previous content + if (*widget->buffer) + memset(widget->buffer, '\0', sizeof(widget->buffer)); + + if (!widget->expandable) { + element = widget->print(p, widget, 0, widget->data); + if (element) + cols_left -= strlen(element); + } + + elements[i++] = element; + } + + // How many expandable widgets are left? + int num_expandables = p->num_widgets - i; + + // How much space do we allocate to each of them? + int width = cols_left - num_expandables; + + i = 0; + + // Process all expandable widgets + STAILQ_FOREACH(widget, &p->widgets, nodes) { + const char* element = elements[i]; + + if (widget->expandable) { + element = widget->print(p, widget, width, widget->data); + } + + elements[i++] = element; + } + + // Reset the line + fputs("\r", p->f); + + // Print all elements + for (i = 0; i < p->num_widgets; i++) { + // Skip anything that returned nothing + if (!elements[i]) + continue; + + fputs(" ", p->f); + fputs(elements[i], p->f); + } + + // Flush everything + fflush(p->f); + + return 0; +} + +static void __pakfire_progressbar_draw(union sigval data) { + struct pakfire_progressbar* p = (struct pakfire_progressbar*)data.sival_ptr; + + // Draw unconditionally + pakfire_progressbar_draw(p); +} + PAKFIRE_EXPORT int pakfire_progressbar_create( struct pakfire_progressbar** progressbar, FILE* f) { struct pakfire_progressbar* p = calloc(1, sizeof(*p)); @@ -147,9 +242,27 @@ PAKFIRE_EXPORT int pakfire_progressbar_create( // Setup widgets STAILQ_INIT(&p->widgets); + struct sigevent sigevent = { + .sigev_notify = SIGEV_THREAD, + .sigev_notify_function = __pakfire_progressbar_draw, + .sigev_value = { + .sival_ptr = p, + }, + }; + + // Create timer + int r = timer_create(CLOCK_REALTIME, &sigevent, &p->timer); + if (r) + goto ERROR; + // Done *progressbar = p; return 0; + +ERROR: + pakfire_progressbar_free(p); + + return r; } PAKFIRE_EXPORT struct pakfire_progressbar* pakfire_progressbar_ref(struct pakfire_progressbar* p) { @@ -169,7 +282,7 @@ PAKFIRE_EXPORT struct pakfire_progressbar* pakfire_progressbar_unref(struct pakf static time_t pakfire_progressbar_elapsed_time(struct pakfire_progressbar* p) { struct timespec now; - int r = clock_gettime(CLOCK_MONOTONIC, &now); + int r = clock_gettime(CLOCK_REALTIME, &now); if (r) return r; @@ -186,23 +299,35 @@ PAKFIRE_EXPORT int pakfire_progressbar_start(struct pakfire_progressbar* p, unsi // Set maximum value pakfire_progressbar_set_max(p, value); + // Start timer + int r = timer_settime(p->timer, 0, &TIMER, NULL); + if (r) + return r; + // Set start time - int r = clock_gettime(CLOCK_MONOTONIC, &p->time_start); + r = clock_gettime(CLOCK_REALTIME, &p->time_start); if (r) return r; - return pakfire_progressbar_update(p, 0); -} + // Set initial value + r = pakfire_progressbar_update(p, 0); + if (r) + return r; -static int pakfire_progressbar_redraw(struct pakfire_progressbar* p); + // Perform an initial draw + r = pakfire_progressbar_draw(p); + if (r) + return r; + + return r; +} PAKFIRE_EXPORT int pakfire_progressbar_update(struct pakfire_progressbar* p, unsigned long value) { if (p->status == PAKFIRE_PROGRESSBAR_INIT) return EINVAL; p->value = value; - - return pakfire_progressbar_redraw(p); + return 0; } PAKFIRE_EXPORT int pakfire_progressbar_increment(struct pakfire_progressbar* p, unsigned long value) { @@ -221,6 +346,9 @@ PAKFIRE_EXPORT int pakfire_progressbar_finish(struct pakfire_progressbar* p) { if (r) return r; + // Perform a final draw + pakfire_progressbar_draw(p); + // Finish line r = fputs("\n", p->f); if (r <= 0 || r == EOF) @@ -236,34 +364,16 @@ PAKFIRE_EXPORT int pakfire_progressbar_reset(struct pakfire_progressbar* p) { // Reset all values p->value = 0; p->value_max = 0; - p->value_redraw = 0; - p->update_interval = 0; p->time_start.tv_sec = 0; p->time_start.tv_nsec = 0; - p->time_redraw.tv_sec = 0; - p->time_redraw.tv_nsec = 0; return 0; } PAKFIRE_EXPORT void pakfire_progressbar_set_max( struct pakfire_progressbar* p, unsigned long value) { - // Do nothing if nothing has changed - if (p->value_max == value) - return; - // Store maximum value p->value_max = value; - - // Redraw immediately - p->value_redraw = 0; - - // Update terminal size if not set - if (!terminal.cols) - pakfire_progressbar_update_terminal_size(SIGWINCH); - - // Calculate update interval - p->update_interval = p->value_max / terminal.cols; } static int pakfire_progressbar_add_widget(struct pakfire_progressbar* p, @@ -288,118 +398,7 @@ static int pakfire_progressbar_add_widget(struct pakfire_progressbar* p, return 0; } -/* - This functions determines whether this progressbar needs to be redrawn - and sets any markers to determine the next redraw. -*/ -static int pakfire_progressbar_needs_redraw(struct pakfire_progressbar* p) { - struct timespec now; - - // Fetch the current time - int r = clock_gettime(CLOCK_MONOTONIC, &now); - if (r) - return r; - - // Redraw when we surpassed the next redraw value - if (p->value >= p->value_redraw) - goto REDRAW; - - // Redraw when we hit the timeout - if (timespec_lt(&p->time_redraw, &now)) - goto REDRAW; - - // Redraw when we are finished - if (p->status == PAKFIRE_PROGRESSBAR_FINISHED) - goto REDRAW; - - // No need to redraw - return 0; - -REDRAW: - // Compute next redraw steps - p->value_redraw = p->value + p->update_interval; - - struct timespec timeout = timespec_from_ms(REDRAW_TIMEOUT); - p->time_redraw = timespec_add(&now, &timeout); - - return 1; -} - -static int pakfire_progressbar_redraw(struct pakfire_progressbar* p) { - struct pakfire_progressbar_widget* widget; - - // Return when we should not be redrawing - if (!pakfire_progressbar_needs_redraw(p)) - return 0; - - // Update terminal size if not set - if (!terminal.cols) - pakfire_progressbar_update_terminal_size(SIGWINCH); - - unsigned int cols_left = terminal.cols - p->num_widgets - 2; - unsigned int i = 0; - - // Create an array with the result of all print functions - const char* elements[p->num_widgets]; - - // Reset all elements - for (i = 0; i < p->num_widgets; i++) - elements[i] = NULL; - - // Reset i - i = 0; - - // Process all non-expandable widgets in the first pass - STAILQ_FOREACH(widget, &p->widgets, nodes) { - const char* element = NULL; - - // Clear any previous content - if (*widget->buffer) - memset(widget->buffer, '\0', sizeof(widget->buffer)); - - if (!widget->expandable) { - element = widget->print(p, widget, 0, widget->data); - if (element) - cols_left -= strlen(element); - } - - elements[i++] = element; - } - - // How many expandable widgets are left? - int num_expandables = p->num_widgets - i; - - // How much space do we allocate to each of them? - int width = cols_left - num_expandables; - - i = 0; - // Process all expandable widgets - STAILQ_FOREACH(widget, &p->widgets, nodes) { - const char* element = elements[i]; - - if (widget->expandable) { - element = widget->print(p, widget, width, widget->data); - } - - elements[i++] = element; - } - - // Reset the line - fputs("\r", p->f); - - // Print all elements - for (i = 0; i < p->num_widgets; i++) { - // Skip anything that returned nothing - if (!elements[i]) - continue; - - fputs(" ", p->f); - fputs(elements[i], p->f); - } - - return 0; -} // String widget