MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
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