HiveBrain v1.2.0
Get Started
← Back to all entries
patterncMajor

Loading... animating dots in C

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
dotsloadinganimating

Problem

I've recently wanted to make a "Loading..." display in C where the dots print one at a time in order and then reset:

Suprisingly, there isn't much on the internet for doing this well, so I figured I would make a simple program for it.

#include 
#include 

int main(void)
{
    int msec = 0;
    const int trigger = 500; // ms
    const int printWidth = 4;
    int counter = 0;
    clock_t before = clock();

    while (1)
    {
        fputs("Loading", stdout);
        clock_t difference = clock() - before;
        msec = difference * 1000 / CLOCKS_PER_SEC;
        if (msec >= trigger)
        {
            counter++;
            msec = 0;
            before = clock();
        }
        for (int i = 0; i < counter; ++i)
        {            
            fputc('.', stdout);
        }
        for (int i = 0; i < printWidth - counter; ++i)
        {
            fputc(' ', stdout);
        }
        fputc('\r', stdout);
        fflush(stdout);

        if (counter == printWidth)
        {
            counter = 0;
        }
    }
}


The output given is seen in the .gif above.

I know this can be done better. Any suggestions for improvement?

Solution

Too frenetic

I ran your program but it was very frenetic. It was constantly clearing the "Loading" prompt and reprinting it which resulted in a flickering effect. In addition, the cursor also moved around in a flickery manner (similar to the green box in the animated image).

To improve this, I would do two things:

-
Don't constantly draw when nothing has changed. Instead, sleep until the next trigger time and then redraw. This also has the added benefit of not using 100% of your cpu. Presumably, you will have another thread running which is doing some loading work, and you don't want this thread to hog cpu time.

-
You don't need to clear and redraw the whole prompt until you get to the last dot. Up until that point, you can just draw one dot at a time.

Rewrite

I rewrote your program with the above two fixes in mind:

#include 
#include 

int main(void)
{
    const int trigger = 500; // ms
    const int numDots = 4;
    const char prompt[] = "Loading";

    while (1) {
        // Return and clear with spaces, then return and print prompt.
        printf("\r%*s\r%s", sizeof(prompt) - 1 + numDots, "", prompt);
        fflush(stdout);

        // Print numDots number of dots, one every trigger milliseconds.
        for (int i = 0; i < numDots; i++) {
            usleep(trigger * 1000);
            fputc('.', stdout);
            fflush(stdout);
        }
    }
}


Rewrite 2

In response to the comment where @syb0rg indicated that the main thread should not sleep, here is what I would do in that case:

#include 
#include 

static void redrawPrompt(void);
static void doWork(void);

int main(void)
{
    const int trigger   = (CLOCKS_PER_SEC * 500) / 1000;  // 500 ms in clocks.
    clock_t   prevClock = clock() - trigger;

    while (1) {
        clock_t curClock = clock();

        if (curClock - prevClock >= trigger) {
            prevClock = curClock;
            redrawPrompt();
        }
        doWork();
    }
}

static void redrawPrompt(void)
{
    static int  numDots;
    const  int  maxDots = 4;
    const  char prompt[] = "Loading";

    // Return and clear with spaces, then return and print prompt.
    printf("\r%*s\r%s", sizeof(prompt) - 1 + maxDots, "", prompt);
    for (int i = 0; i  maxDots)
        numDots = 0;
}

static void doWork(void)
{
    // This function does loading work but returns at least every 500 ms.
}

Code Snippets

#include <stdio.h>
#include <unistd.h>

int main(void)
{
    const int trigger = 500; // ms
    const int numDots = 4;
    const char prompt[] = "Loading";

    while (1) {
        // Return and clear with spaces, then return and print prompt.
        printf("\r%*s\r%s", sizeof(prompt) - 1 + numDots, "", prompt);
        fflush(stdout);

        // Print numDots number of dots, one every trigger milliseconds.
        for (int i = 0; i < numDots; i++) {
            usleep(trigger * 1000);
            fputc('.', stdout);
            fflush(stdout);
        }
    }
}
#include <stdio.h>
#include <time.h>

static void redrawPrompt(void);
static void doWork(void);

int main(void)
{
    const int trigger   = (CLOCKS_PER_SEC * 500) / 1000;  // 500 ms in clocks.
    clock_t   prevClock = clock() - trigger;

    while (1) {
        clock_t curClock = clock();

        if (curClock - prevClock >= trigger) {
            prevClock = curClock;
            redrawPrompt();
        }
        doWork();
    }
}

static void redrawPrompt(void)
{
    static int  numDots;
    const  int  maxDots = 4;
    const  char prompt[] = "Loading";

    // Return and clear with spaces, then return and print prompt.
    printf("\r%*s\r%s", sizeof(prompt) - 1 + maxDots, "", prompt);
    for (int i = 0; i < numDots; i++)
        fputc('.', stdout);
    fflush(stdout);
    if (++numDots > maxDots)
        numDots = 0;
}

static void doWork(void)
{
    // This function does loading work but returns at least every 500 ms.
}

Context

StackExchange Code Review Q#139440, answer score: 33

Revisions (0)

No revisions yet.