]> git.ipfire.org Git - pakfire.git/commitdiff
progressbar: Make this a lot smoother
authorMichael Tremer <michael.tremer@ipfire.org>
Tue, 19 Jul 2022 16:20:38 +0000 (16:20 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Tue, 19 Jul 2022 16:20:38 +0000 (16:20 +0000)
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 <michael.tremer@ipfire.org>
src/libpakfire/progressbar.c

index 273c7a2c8f6f099e045725e54a611e98aa112689..c4681bf693560d2fb2e2b1012bcc29b18414e839 100644 (file)
 #include <pakfire/progressbar.h>
 #include <pakfire/util.h>
 
-#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