20 static std::recursive_mutex s_stdout_mutex;
21 return s_stdout_mutex;
67 auto
add(std::
size_t count) noexcept ->
void;
73 return m_processed.load(std::memory_order_acquire);
79 [[nodiscard]]
auto get_total() const noexcept -> std::
size_t {
86 [[nodiscard]]
auto get_progress() const noexcept ->
double;
91 auto
start() noexcept ->
void;
96 auto
finish() noexcept ->
void;
114 [[nodiscard]] static auto
format_duration(std::chrono::seconds duration) noexcept -> std::
string;
119 static constexpr std::string_view CLEAR_LINE =
"\r\033[K";
120 static constexpr std::string_view RESET =
"\033[0m";
121 static constexpr std::string_view BOLD =
"\033[1m";
122 static constexpr std::string_view DIM =
"\033[2m";
125 static constexpr std::string_view GREEN =
"\033[32m";
126 static constexpr std::string_view CYAN =
"\033[36m";
127 static constexpr std::string_view YELLOW =
"\033[33m";
128 static constexpr std::string_view MAGENTA =
"\033[35m";
129 static constexpr std::string_view WHITE =
"\033[37m";
130 static constexpr std::string_view BLUE =
"\033[34m";
131 static constexpr std::string_view RED =
"\033[31m";
134 static constexpr std::string_view BRIGHT_GREEN =
"\033[92m";
135 static constexpr std::string_view BRIGHT_CYAN =
"\033[96m";
136 static constexpr std::string_view BRIGHT_YELLOW =
"\033[93m";
137 static constexpr std::string_view BRIGHT_MAGENTA =
"\033[95m";
138 static constexpr std::string_view BRIGHT_WHITE =
"\033[97m";
141 static constexpr std::string_view BG_CYAN =
"\033[46m";
142 static constexpr std::string_view BG_BLUE =
"\033[44m";
143 static constexpr std::string_view BG_MAGENTA =
"\033[45m";
147 static constexpr std::array<std::string_view, 9> BLOCK_CHARS = {
148 " ",
"▏",
"▎",
"▍",
"▌",
"▋",
"▊",
"▉",
"█"};
151 static constexpr std::array<std::string_view, 10> SPINNER_CHARS = {
152 "⠋",
"⠙",
"⠹",
"⠸",
"⠼",
"⠴",
"⠦",
"⠧",
"⠇",
"⠏"};
154 auto update_display() noexcept ->
void;
155 auto render_bar(
double progress) const noexcept ->
void;
156 auto update_loop() noexcept ->
void;
157 auto stop_update_thread() noexcept ->
void;
159 [[nodiscard]] static auto format_number(std::
size_t num) noexcept -> std::
string;
160 [[nodiscard]] auto get_elapsed() const noexcept -> std::chrono::seconds;
161 [[nodiscard]] auto calculate_eta() const noexcept -> std::chrono::seconds;
162 [[nodiscard]] auto calculate_rate() const noexcept ->
double;
163 [[nodiscard]] auto get_progress_color() const noexcept -> std::string_view;
165 const std::
size_t m_total;
166 std::atomic<std::
size_t> m_processed{0};
167 std::chrono::steady_clock::time_point m_start_time;
169 mutable std::mutex m_display_mutex;
170 bool m_finished{
false};
171 mutable std::size_t m_spinner_index{0};
174 std::atomic<bool> m_should_stop{
false};
175 std::thread m_update_thread;
179 : m_total{total}, m_start_time{std::chrono::steady_clock::now()}, m_config{config} {
180 if (total == 0)
throw std::invalid_argument(
"ProgressTracker: total must be greater than 0");
184 stop_update_thread();
189 : m_total{other.m_total}, m_processed{other.m_processed.load(std::memory_order_acquire)}, m_start_time{other.m_start_time}, m_config{other.m_config}, m_finished{other.m_finished} {
190 other.stop_update_thread();
191 other.m_finished =
true;
195 if (
this != &other) {
196 stop_update_thread();
197 if (!m_finished) clear_line();
198 other.stop_update_thread();
200 const_cast<std::size_t&
>(m_total) = other.m_total;
201 m_processed.store(other.m_processed.load(std::memory_order_acquire), std::memory_order_release);
202 m_start_time = other.m_start_time;
203 m_config = other.m_config;
204 m_finished = other.m_finished;
206 other.m_finished =
true;
212 m_processed.fetch_add(1, std::memory_order_acq_rel);
216 m_processed.fetch_add(count, std::memory_order_acq_rel);
220 const auto current = m_processed.load(std::memory_order_acquire);
221 return m_total > 0 ?
static_cast<double>(current) /
static_cast<double>(m_total) : 0.0;
224inline auto ProgressTracker::get_elapsed() const noexcept -> std::chrono::seconds {
225 const auto elapsed = std::chrono::steady_clock::now() - m_start_time;
226 return std::chrono::duration_cast<std::chrono::seconds>(elapsed);
229inline auto ProgressTracker::calculate_rate() const noexcept ->
double {
230 const auto elapsed = get_elapsed().count();
231 const auto current = m_processed.load(std::memory_order_acquire);
232 return elapsed > 0 ?
static_cast<double>(current) /
static_cast<double>(elapsed) : 0.0;
235inline auto ProgressTracker::calculate_eta() const noexcept -> std::chrono::seconds {
236 const auto current = m_processed.load(std::memory_order_acquire);
237 const auto rate = calculate_rate();
239 if (rate > 0.0 && current < m_total) {
240 const auto remaining = m_total - current;
241 return std::chrono::seconds{
static_cast<long>(remaining / rate)};
243 return std::chrono::seconds{0};
247 const auto m_totalseconds = duration.count();
249 if (m_totalseconds < 60)
return fmt::format(
"{}s", m_totalseconds);
250 if (m_totalseconds < 3600) {
251 const auto minutes = m_totalseconds / 60;
252 const auto seconds = m_totalseconds % 60;
253 return fmt::format(
"{}m {:02d}s", minutes, seconds);
256 const auto hours = m_totalseconds / 3600;
257 const auto minutes = (m_totalseconds % 3600) / 60;
258 return fmt::format(
"{}h {:02d}m", hours, minutes);
261inline auto ProgressTracker::format_number(std::size_t num)
noexcept -> std::string {
262 if (num < 1000)
return fmt::format(
"{}", num);
263 if (num < 1000000)
return fmt::format(
"{:.1f}k", num / 1000.0);
264 return fmt::format(
"{:.1f}M", num / 1000000.0);
270 fmt::print(
"{}", AnsiCodes::CLEAR_LINE);
276inline auto ProgressTracker::get_progress_color() const noexcept -> std::string_view {
278 return AnsiCodes::BRIGHT_GREEN;
281inline auto ProgressTracker::render_bar(
double progress)
const noexcept ->
void {
284 const auto& colors = m_config.use_colors;
285 progress = std::clamp(progress, 0.0, 1.0);
287 const auto filled_width = m_config.bar_width * progress;
288 const auto full_blocks =
static_cast<std::size_t
>(filled_width);
289 const auto remainder = filled_width -
static_cast<double>(full_blocks);
290 const auto partial_block_idx =
static_cast<std::size_t
>(remainder * 8.0);
293 if (colors) fmt::print(
"{}", AnsiCodes::DIM);
295 if (colors) fmt::print(
"{}", AnsiCodes::RESET);
298 for (std::size_t i = 0; i < m_config.bar_width; ++i) {
299 if (i < full_blocks) {
301 fmt::print(
"{}", get_progress_color());
303 fmt::print(
"{}", BLOCK_CHARS[8]);
304 }
else if (i == full_blocks) {
306 fmt::print(
"{}", get_progress_color());
308 fmt::print(
"{}", BLOCK_CHARS[partial_block_idx]);
310 if (colors) fmt::print(
"{}", AnsiCodes::DIM);
315 if (colors) fmt::print(
"{}", AnsiCodes::RESET);
318 if (colors) fmt::print(
"{}", AnsiCodes::DIM);
320 if (colors) fmt::print(
"{}", AnsiCodes::RESET);
327inline auto ProgressTracker::update_display() noexcept ->
void {
328 std::lock_guard lock{m_display_mutex};
330 if (m_finished)
return;
333 const auto current = m_processed.load(std::memory_order_acquire);
335 const auto elapsed = get_elapsed();
344 if (progress < 1.0) {
345 if (colors) fmt::print(
"{}", AnsiCodes::BRIGHT_CYAN);
346 fmt::print(
"{}", SPINNER_CHARS[m_spinner_index % SPINNER_CHARS.size()]);
348 if (colors) fmt::print(
"{}", AnsiCodes::RESET);
351 if (colors) fmt::print(
"{}", AnsiCodes::BRIGHT_GREEN);
353 if (colors) fmt::print(
"{}", AnsiCodes::RESET);
358 if (!m_config.
label.empty()) {
359 if (colors) fmt::print(
"{}", AnsiCodes::BOLD);
360 fmt::print(
"{}", m_config.
label);
361 if (colors) fmt::print(
"{}", AnsiCodes::RESET);
365 render_bar(progress);
368 if (progress >= 1.0) {
369 if (colors) fmt::print(
"{}{}", AnsiCodes::BRIGHT_GREEN, AnsiCodes::BOLD);
371 if (colors) fmt::print(
"{}", AnsiCodes::RESET);
373 if (colors) fmt::print(
"{}", get_progress_color());
374 fmt::print(
"{:>3.0f}%", progress * 100.0);
375 if (colors) fmt::print(
"{}", AnsiCodes::RESET);
380 if (colors) fmt::print(
"{}", AnsiCodes::DIM);
382 if (colors) fmt::print(
"{}", AnsiCodes::RESET);
385 if (colors) fmt::print(
"{}", AnsiCodes::BRIGHT_WHITE);
386 fmt::print(
"{}", format_number(current));
387 if (colors) fmt::print(
"{}", AnsiCodes::RESET);
389 if (colors) fmt::print(
"{}", AnsiCodes::DIM);
391 if (colors) fmt::print(
"{}", AnsiCodes::RESET);
393 if (colors) fmt::print(
"{}", AnsiCodes::BRIGHT_CYAN);
394 fmt::print(
"{}", format_number(m_total));
395 if (colors) fmt::print(
"{}", AnsiCodes::RESET);
399 if (colors) fmt::print(
"{}", AnsiCodes::DIM);
401 if (colors) fmt::print(
"{}", AnsiCodes::RESET);
403 if (colors) fmt::print(
"{}", AnsiCodes::MAGENTA);
405 if (colors) fmt::print(
"{}", AnsiCodes::RESET);
408 if (m_config.
show_eta && current < m_total) {
409 const auto eta = calculate_eta();
410 if (eta.count() > 0) {
412 if (colors) fmt::print(
"{}", AnsiCodes::DIM);
414 if (colors) fmt::print(
"{}", AnsiCodes::RESET);
416 if (colors) fmt::print(
"{}", AnsiCodes::YELLOW);
418 if (colors) fmt::print(
"{}", AnsiCodes::RESET);
424 const auto rate = calculate_rate();
427 if (colors) fmt::print(
"{}", AnsiCodes::DIM);
429 if (colors) fmt::print(
"{}", AnsiCodes::RESET);
431 if (colors) fmt::print(
"{}", AnsiCodes::BRIGHT_MAGENTA);
432 fmt::print(
"{:.1f}/s", rate);
433 if (colors) fmt::print(
"{}", AnsiCodes::RESET);
443inline auto ProgressTracker::update_loop() noexcept ->
void {
444 while (!m_should_stop.load(std::memory_order_acquire)) {
446 if (m_processed.load(std::memory_order_acquire) >= m_total)
break;
454 if (!m_update_thread.joinable() && !m_finished) {
455 m_should_stop.store(
false, std::memory_order_release);
457 m_update_thread = std::thread(&ProgressTracker::update_loop,
this);
461inline auto ProgressTracker::stop_update_thread() noexcept ->
void {
462 if (m_update_thread.joinable()) {
463 m_should_stop.store(
true, std::memory_order_release);
464 m_update_thread.join();
469 stop_update_thread();
471 std::lock_guard lock{m_display_mutex};
473 if (m_finished)
return;
477 const auto elapsed = get_elapsed();
486 if (colors) fmt::print(
"{}", AnsiCodes::BRIGHT_GREEN);
487 fmt::print(
" {} Complete! │ ", m_config.
label);
488 if (colors) fmt::print(
"{}", AnsiCodes::RESET);
491 fmt::print(
" Items: ");
492 if (colors) fmt::print(
"{}", AnsiCodes::BRIGHT_CYAN);
493 fmt::print(
"{}", format_number(m_total));
494 if (colors) fmt::print(
"{}", AnsiCodes::RESET);
498 fmt::print(
"⏱ Time: ");
499 if (colors) fmt::print(
"{}", AnsiCodes::MAGENTA);
501 if (colors) fmt::print(
"{}", AnsiCodes::RESET);
Thread-safe progress tracker with visual progress bar.
Definition progresstracker.hpp:28
ProgressTracker & operator=(const ProgressTracker &)=delete
auto get_total() const noexcept -> std::size_t
Gets total item count (thread-safe).
Definition progresstracker.hpp:79
static auto clear_line() noexcept -> void
Clears the current progress line.
Definition progresstracker.hpp:267
auto increment() noexcept -> void
Increments the progress counter (thread-safe, no immediate display update).
Definition progresstracker.hpp:211
auto get_processed() const noexcept -> std::size_t
Gets current progress count (thread-safe).
Definition progresstracker.hpp:72
~ProgressTracker() noexcept
Definition progresstracker.hpp:183
auto add(std::size_t count) noexcept -> void
Adds multiple items to progress (thread-safe, no immediate display update).
Definition progresstracker.hpp:215
static auto format_duration(std::chrono::seconds duration) noexcept -> std::string
Formats a duration given in seconds into a human-readable string.
Definition progresstracker.hpp:246
auto start() noexcept -> void
Starts the background update thread and shows initial display.
Definition progresstracker.hpp:453
auto get_progress() const noexcept -> double
Gets current progress ratio [0.0, 1.0] (thread-safe).
Definition progresstracker.hpp:219
auto finish() noexcept -> void
Stops the update thread and displays final completion message.
Definition progresstracker.hpp:468
ProgressTracker(std::size_t total, Config config={})
Constructs a progress tracker.
Definition progresstracker.hpp:178
Definition progresstracker.hpp:16
std::recursive_mutex & get_stdout_mutex()
Returns the global recursive mutex used to synchronize stdout access.
Definition progresstracker.hpp:19
Definition progresstracker.hpp:30
bool use_gradient
Definition progresstracker.hpp:35
Config()
Definition progresstracker.hpp:39
std::size_t bar_width
Definition progresstracker.hpp:31
std::string label
Definition progresstracker.hpp:36
bool show_rate
Definition progresstracker.hpp:33
std::chrono::milliseconds update_interval
Definition progresstracker.hpp:37
bool show_eta
Definition progresstracker.hpp:32
bool use_colors
Definition progresstracker.hpp:34