MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at mir/inline-method 316 lines 8.0 kB view raw
1#ifndef PROGRESS_H 2#define PROGRESS_H 3 4#include <stdio.h> 5#include <stdbool.h> 6#include <stdint.h> 7#include <stdatomic.h> 8#include <stdlib.h> 9#include <string.h> 10#include <time.h> 11 12#ifdef _WIN32 13#define WIN32_LEAN_AND_MEAN 14#include <windows.h> 15#include <io.h> 16#define PROGRESS_ISATTY(fd) _isatty(fd) 17#define PROGRESS_FILENO(f) _fileno(f) 18#else 19#include <unistd.h> 20#include <sys/ioctl.h> 21#define PROGRESS_ISATTY(fd) isatty(fd) 22#define PROGRESS_FILENO(f) fileno(f) 23#endif 24 25#define PROGRESS_MSG_SIZE 256 26 27#ifdef __has_include 28#if __has_include(<pthread.h>) 29#include <pthread.h> 30#define PROGRESS_HAS_PTHREADS 1 31#endif 32#endif 33 34typedef struct { 35#if defined(PROGRESS_HAS_PTHREADS) 36 pthread_mutex_t mtx; 37#elif defined(_WIN32) 38 CRITICAL_SECTION cs; 39#else 40 int dummy; 41#endif 42} progress_mutex_t; 43 44typedef struct { 45 FILE *terminal; 46 bool is_windows_terminal; 47 bool supports_ansi; 48 bool dont_print_on_dumb; 49 uint64_t start_time_ns; 50 uint64_t prev_refresh_ns; 51 uint64_t refresh_rate_ns; 52 uint64_t initial_delay_ns; 53 bool done; 54 bool timer_valid; 55 size_t columns_written; 56 progress_mutex_t mutex; 57 char buffer[PROGRESS_MSG_SIZE]; 58 char msg_buffer[PROGRESS_MSG_SIZE]; 59} progress_t; 60 61static inline void progress_mutex_init(progress_mutex_t *m) { 62#if defined(PROGRESS_HAS_PTHREADS) 63 pthread_mutex_init(&m->mtx, NULL); 64#elif defined(_WIN32) 65 InitializeCriticalSection(&m->cs); 66#else 67 (void)m; 68#endif 69} 70 71static inline void progress_mutex_destroy(progress_mutex_t *m) { 72#if defined(PROGRESS_HAS_PTHREADS) 73 pthread_mutex_destroy(&m->mtx); 74#elif defined(_WIN32) 75 DeleteCriticalSection(&m->cs); 76#else 77 (void)m; 78#endif 79} 80 81static inline void progress_mutex_lock(progress_mutex_t *m) { 82#if defined(PROGRESS_HAS_PTHREADS) 83 pthread_mutex_lock(&m->mtx); 84#elif defined(_WIN32) 85 EnterCriticalSection(&m->cs); 86#else 87 (void)m; 88#endif 89} 90 91static inline void progress_mutex_unlock(progress_mutex_t *m) { 92#if defined(PROGRESS_HAS_PTHREADS) 93 pthread_mutex_unlock(&m->mtx); 94#elif defined(_WIN32) 95 LeaveCriticalSection(&m->cs); 96#else 97 (void)m; 98#endif 99} 100 101static inline bool progress_mutex_trylock(progress_mutex_t *m) { 102#if defined(PROGRESS_HAS_PTHREADS) 103 return pthread_mutex_trylock(&m->mtx) == 0; 104#elif defined(_WIN32) 105 return TryEnterCriticalSection(&m->cs) != 0; 106#else 107 (void)m; 108 return true; 109#endif 110} 111 112static inline uint64_t progress_now_ns(void) { 113 struct timespec ts; 114#if defined(CLOCK_MONOTONIC) 115 if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) { 116 return (uint64_t)ts.tv_sec * 1000000000ULL + (uint64_t)ts.tv_nsec; 117 } 118#elif defined(_WIN32) 119 static LARGE_INTEGER freq = {0}; 120 if (freq.QuadPart == 0) { 121 QueryPerformanceFrequency(&freq); 122 } 123 LARGE_INTEGER counter; 124 QueryPerformanceCounter(&counter); 125 return (uint64_t)(counter.QuadPart * 1000000000ULL / freq.QuadPart); 126#endif 127 if (clock_gettime(CLOCK_REALTIME, &ts) == 0) { 128 return (uint64_t)ts.tv_sec * 1000000000ULL + (uint64_t)ts.tv_nsec; 129 } 130 return 0; 131} 132 133static inline bool progress_detect_ansi(FILE *f) { 134 if (!f) return false; 135 136 int fd = PROGRESS_FILENO(f); 137 if (!PROGRESS_ISATTY(fd)) return false; 138 139#ifdef _WIN32 140 HANDLE h = (HANDLE)_get_osfhandle(fd); 141 DWORD mode; 142 if (GetConsoleMode(h, &mode)) { 143 if (SetConsoleMode(h, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING)) return true; 144 } 145 const char *term = getenv("TERM"); 146 return (term && strstr(term, "xterm")) 147 || (term && strstr(term, "vt100")) 148 || (term && strstr(term, "color")) 149 || (term && strstr(term, "ansi")); 150#else 151 const char *term = getenv("TERM"); 152 if (!term) return false; 153 if (strcmp(term, "dumb") == 0) return false; 154 return true; 155#endif 156} 157 158static void progress_refresh_locked(progress_t *p); 159static void progress_clear_locked(progress_t *p, size_t *end); 160 161static inline void progress_start(progress_t *p, const char *message) { 162 memset(p, 0, sizeof(*p)); 163 progress_mutex_init(&p->mutex); 164 165 FILE *f = stderr; 166 int fd = PROGRESS_FILENO(f); 167 168 if (PROGRESS_ISATTY(fd)) { 169 p->terminal = f; 170 p->supports_ansi = progress_detect_ansi(f); 171#ifdef _WIN32 172 if (!p->supports_ansi) p->is_windows_terminal = true; 173#endif 174 } else { 175 p->terminal = f; 176 p->supports_ansi = false; 177 p->is_windows_terminal = false; 178 } 179 180 if (message) { 181 strncpy(p->msg_buffer, message, PROGRESS_MSG_SIZE - 1); 182 p->msg_buffer[PROGRESS_MSG_SIZE - 1] = '\0'; 183 } else p->msg_buffer[0] = '\0'; 184 185 p->refresh_rate_ns = 50 * 1000000ULL; 186 p->initial_delay_ns = 500 * 1000000ULL; 187 p->start_time_ns = progress_now_ns(); 188 p->prev_refresh_ns = 0; 189 p->timer_valid = (p->start_time_ns != 0); 190 191 p->columns_written = 0; 192 p->done = false; 193 194 progress_refresh_locked(p); 195} 196 197static inline void progress_maybe_refresh(progress_t *p) { 198 if (!p->timer_valid) return; 199 if (!progress_mutex_trylock(&p->mutex)) return; 200 201 uint64_t now = progress_now_ns(); 202 uint64_t elapsed = now - p->start_time_ns; 203 204 if (elapsed < p->initial_delay_ns) { 205 progress_mutex_unlock(&p->mutex); 206 return; 207 } 208 209 if (now < p->prev_refresh_ns || (now - p->prev_refresh_ns) < p->refresh_rate_ns) { 210 progress_mutex_unlock(&p->mutex); 211 return; 212 } 213 214 progress_refresh_locked(p); 215 progress_mutex_unlock(&p->mutex); 216} 217 218static inline void progress_update(progress_t *p, const char *message) { 219 progress_mutex_lock(&p->mutex); 220 if (message) { 221 strncpy(p->msg_buffer, message, PROGRESS_MSG_SIZE - 1); 222 p->msg_buffer[PROGRESS_MSG_SIZE - 1] = '\0'; 223 } else p->msg_buffer[0] = '\0'; 224 225 progress_mutex_unlock(&p->mutex); 226 progress_maybe_refresh(p); 227} 228 229static inline void progress_refresh(progress_t *p) { 230 if (!progress_mutex_trylock(&p->mutex)) return; 231 progress_refresh_locked(p); 232 progress_mutex_unlock(&p->mutex); 233} 234 235static void progress_clear_locked(progress_t *p, size_t *end) { 236 if (!p->terminal) return; 237 238 size_t pos = *end; 239 240 if (p->columns_written > 0) { 241 if (p->supports_ansi) { 242 int written = snprintf(p->buffer + pos, sizeof(p->buffer) - pos, "\x1b[%zuD\x1b[0K", p->columns_written); 243 if (written > 0) pos += (size_t)written; 244 } else if (p->is_windows_terminal) { 245#ifdef _WIN32 246 HANDLE h = (HANDLE)_get_osfhandle(PROGRESS_FILENO(p->terminal)); 247 CONSOLE_SCREEN_BUFFER_INFO info; 248 if (GetConsoleScreenBufferInfo(h, &info)) { 249 COORD cursor = { 250 .X = (SHORT)(info.dwCursorPosition.X - (SHORT)p->columns_written), 251 .Y = info.dwCursorPosition.Y 252 }; 253 if (cursor.X < 0) cursor.X = 0; 254 255 DWORD fill_len = (DWORD)(info.dwSize.X - cursor.X); 256 DWORD written; 257 FillConsoleOutputAttribute(h, info.wAttributes, fill_len, cursor, &written); 258 FillConsoleOutputCharacterA(h, ' ', fill_len, cursor, &written); 259 SetConsoleCursorPosition(h, cursor); 260 } 261#endif 262 } else { 263 if (pos < sizeof(p->buffer)) p->buffer[pos++] = '\n'; 264 } 265 p->columns_written = 0; 266 } 267 268 *end = pos; 269} 270 271static void progress_refresh_locked(progress_t *p) { 272 bool is_dumb = !p->supports_ansi && !p->is_windows_terminal; 273 if (is_dumb && p->dont_print_on_dumb) return; 274 if (!p->terminal) return; 275 276 size_t end = 0; 277 progress_clear_locked(p, &end); 278 279 if (!p->done) { 280 if (p->msg_buffer[0]) { 281 int written = snprintf(p->buffer + end, sizeof(p->buffer) - end, " %s", p->msg_buffer); 282 if (written > 0) { 283 size_t amt = ( 284 (size_t)written < sizeof(p->buffer) - end) 285 ? (size_t)written 286 : sizeof(p->buffer) - end - 1; 287 end += amt; 288 p->columns_written = amt; 289 } 290 } 291 } 292 293 if (end > 0) { 294 fwrite(p->buffer, 1, end, p->terminal); 295 fflush(p->terminal); 296 } 297 298 p->prev_refresh_ns = progress_now_ns(); 299} 300 301static inline void progress_stop(progress_t *p) { 302 progress_mutex_lock(&p->mutex); 303 p->done = true; 304 305 size_t end = 0; 306 progress_clear_locked(p, &end); 307 if (end > 0 && p->terminal) { 308 fwrite(p->buffer, 1, end, p->terminal); 309 fflush(p->terminal); 310 } 311 312 progress_mutex_unlock(&p->mutex); 313 progress_mutex_destroy(&p->mutex); 314} 315 316#endif