Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multi-thread console text animations with std::cout

I'm trying to create a function that can animate multiple strings to the console at the same time. By "animate," I mean print a character, wait a specified amount of time, then print the next character, and so on.

This is what I've tried so far:

/**
 @param msg        Message to animate
 @param sleep_time Time to wait between each letter
 @param wait       Whether or not to wait for the current thread to join before returning
*/
void animate(const std::string& msg, const unsigned long long sleep_time, const bool wait = true)
{
    const std::atomic<std::chrono::milliseconds> t_sleep_time =
        std::chrono::milliseconds(sleep_time);

    std::stringstream msg_strm;
    msg_strm << msg;

    std::thread animate_thread([&msg_strm, &t_sleep_time]() -> void
    {
        char letter;

        while ((letter = msg_strm.get()) != EOF)
        {
            std::cout << letter << std::flush;
            std::this_thread::sleep_for(t_sleep_time.load());
        }

        return;
    });

    if (wait)
    {
        animate_thread.join();
    }
    else
    {
        animate_thread.detach();
    }
}

This is the driver code for it:

int main()
{
    animate("Hello", 500, false);
    std::cout << '\n' << std::endl;
    animate("Welcome", 400, true);
    std::cout << "\n\nEnd" << std::endl;
}

And this is the output ("Wecome" animates as sluggishly):


Welcome

End

What happened to "Hello"? I'm very new to multi-threading, so a detailed explanation would be very much appreciated. Ideally, what I'd like to happen, is to have "Hello" animating on one line and "Welcome" on the next. Is this possible?

like image 380
Drake Johnson Avatar asked Dec 05 '25 05:12

Drake Johnson


1 Answers

First of all msg_strm lives on the stack thus you cannot pass it by value to the thread because it goes out of scope and that is why, most probable Hello was not showing. Also another problem that you have is that you are calling detach thus the program might exit before the first thread finish.

To achieve what you are trying to do I suggest to use ANSI escape codes. Therefore the following might not work on all command prompts. Also note that std::cout is not thread safe if you print in steps.

#include <atomic>
#include <iostream>
#include <string>
#include <thread>

std::atomic<int> g_lines = 1;

std::thread animate(const std::string& msg, const unsigned long long sleep_time)
{
    // NOTE: `[=]` means capture all variables used by value. Note that globals
    // are not captured.  Also note that capture by value is needed because
    // `msg` can go out-of-scope.
    return std::thread([=] {
        auto line = g_lines++;

        for (size_t column = 1; column <= msg.size(); column++)
        {
            // using `ANSI escape codes` move the cursor to the correct
            // position; \x1B[{line};{column}H

            std::cout << "\x1B[" + std::to_string(line) + ";"
                             + std::to_string(column) + "H" + msg[column - 1];

            std::this_thread::sleep_for(std::chrono::milliseconds(sleep_time));
        }
    });
}

int main()
{
    auto t1 = animate("Hello", 500);
    auto t2 = animate("Welcome", 400);

    // you need to join all threads else if you call detach, the program might
    // exit before all threads finish.
    t1.join();
    t2.join();

    std::cout << "\n\nEnd" << std::endl;
}
like image 178
bertubezz Avatar answered Dec 07 '25 21:12

bertubezz



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!