ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
util/time.c
Go to the documentation of this file.
1
9
10#undef SOKOL_TIME_INCLUDED
11#define SOKOL_TIME_IMPL
12#include <ascii-chat-deps/sokol/sokol_time.h>
13#include <ascii-chat/common.h>
18#include <stdlib.h>
19#include <string.h>
20#include <stdio.h>
21#include <time.h>
22#include <ctype.h>
23#include <ascii-chat/atomic.h>
24
25// ============================================================================
26// Global State
27// ============================================================================
28
32static struct {
36} g_timer_manager = {
37 .timers = NULL,
38 .lifecycle = LIFECYCLE_INIT,
39};
40
44static bool g_sokol_time_initialized = false;
45
46// ============================================================================
47// Human-Readable Time Formatting Helper
48// ============================================================================
49
69static int format_human_readable_time_ago(double total_seconds, int minutes, int hours, int days, int months, int years,
70 const char *suffix, char *buffer, size_t buffer_size) {
71 int written = 0;
72
73 if (total_seconds < 3) {
74 written = safe_snprintf(buffer, buffer_size, "a moment %s", suffix);
75 } else if (total_seconds < 10) {
76 written = safe_snprintf(buffer, buffer_size, "a few seconds %s", suffix);
77 } else if (total_seconds < 45) {
78 written = safe_snprintf(buffer, buffer_size, "%d seconds %s", (int)total_seconds, suffix);
79 } else if (total_seconds < 90) {
80 written = safe_snprintf(buffer, buffer_size, "a minute %s", suffix);
81 } else if (minutes < 45) {
82 written = safe_snprintf(buffer, buffer_size, "%d minutes %s", minutes, suffix);
83 } else if (minutes < 90) {
84 written = safe_snprintf(buffer, buffer_size, "an hour %s", suffix);
85 } else if (hours < 22) {
86 written = safe_snprintf(buffer, buffer_size, "%d hours %s", hours, suffix);
87 } else if (hours < 36) {
88 written = safe_snprintf(buffer, buffer_size, "a day %s", suffix);
89 } else if (days < 25) {
90 written = safe_snprintf(buffer, buffer_size, "%d days %s", days, suffix);
91 } else if (days < 45) {
92 written = safe_snprintf(buffer, buffer_size, "a month %s", suffix);
93 } else if (months < 11) {
94 written = safe_snprintf(buffer, buffer_size, "%d months %s", months, suffix);
95 } else if (months < 18) {
96 written = safe_snprintf(buffer, buffer_size, "a year %s", suffix);
97 } else {
98 written = safe_snprintf(buffer, buffer_size, "%d years %s", years, suffix);
99 }
100
101 return written;
102}
103
104// ============================================================================
105// Core Monotonic Timing Implementation
106// ============================================================================
107
109 // sokol_time provides monotonic clock that never goes backwards
110 // stm_ns() converts ticks to nanoseconds
111 // Ensure sokol_time is initialized before use (defensive programming)
112 if (!g_sokol_time_initialized) {
113 stm_setup();
114 g_sokol_time_initialized = true;
115 }
116 return (uint64_t)stm_ns(stm_now());
117}
118
120#ifdef _WIN32
121 // Windows: Use GetSystemTimePreciseAsFileTime (Win8+) for high-precision wall clock
122 // FILETIME is 100-nanosecond intervals since January 1, 1601
123 FILETIME ft;
124 GetSystemTimePreciseAsFileTime(&ft);
125 // Convert FILETIME to nanoseconds
126 uint64_t filetime = ((uint64_t)ft.dwHighDateTime << 32) | ft.dwLowDateTime;
127 // Convert 100-nanosecond intervals to nanoseconds
128 return filetime * 100;
129#else
130 // Get wall-clock (real-time) timestamp
131 struct timespec ts;
132 if (clock_gettime(CLOCK_REALTIME, &ts) != 0) {
133 // Fallback on error (unlikely)
134 return 0;
135 }
136 return time_timespec_to_ns(&ts);
137#endif
138}
139
141 // Use platform abstraction for cross-platform sleep
142 // Convert nanoseconds to microseconds (1 us = 1000 ns)
143 unsigned int usec = (unsigned int)(ns / 1000);
144 if (usec == 0 && ns > 0) {
145 usec = 1; // Minimum 1 microsecond
146 }
147 platform_sleep_us(usec);
148}
149
151 // Handle wraparound (defensive - uint64_t won't wrap in practice at nanosecond resolution)
152 // but this is safe and handles any theoretical edge cases
153 if (end_ns >= start_ns) {
154 return end_ns - start_ns;
155 }
156 // Wraparound case (extremely unlikely)
157 return (UINT64_MAX - start_ns) + end_ns + 1;
158}
159
160// ============================================================================
161// Conversion Functions (moved from inline in header to avoid circular deps)
162// ============================================================================
163
164uint64_t time_timespec_to_ns(const struct timespec *ts) {
165 if (!ts) {
166 SET_ERRNO(ERROR_INVALID_PARAM, "null ts");
167 return 0;
168 }
169 // Use __uint128_t to prevent overflow during multiplication of large time values
170 __uint128_t result = (__uint128_t)ts->tv_sec * NS_PER_SEC_INT;
171 if (result > UINT64_MAX) {
172 SET_ERRNO(ERROR_INVALID_PARAM, "time_timespec_to_ns: timespec value would overflow");
173 return UINT64_MAX;
174 }
175 return (uint64_t)result + (uint64_t)ts->tv_nsec;
176}
177
178void time_ns_to_timespec(uint64_t ns, struct timespec *ts) {
179 if (!ts) {
180 SET_ERRNO(ERROR_INVALID_PARAM, "null ts");
181 return;
182 }
183 ts->tv_sec = (time_t)(ns / NS_PER_SEC_INT);
184 ts->tv_nsec = (long)(ns % NS_PER_SEC_INT);
185}
186
187// ============================================================================
188// Timer System Implementation
189// ============================================================================
190
192 if (lifecycle_is_initialized(&g_timer_manager.lifecycle)) {
193 return true;
194 }
195
196 // Initialize sokol_time
197 stm_setup();
198
199 // Initialize rwlock and lifecycle together
200 g_timer_manager.lifecycle.sync_type = LIFECYCLE_SYNC_RWLOCK;
201 g_timer_manager.lifecycle.sync.rwlock = &g_timer_manager.rwlock;
202 if (!lifecycle_init(&g_timer_manager.lifecycle, "timer_manager")) {
203 SET_ERRNO(ERROR_PLATFORM_INIT, "Failed to initialize timer system (already initialized by another thread)");
204 return true; // Another thread already initialized it
205 }
206
207 g_timer_manager.timers = NULL;
208
209 log_dev("Timer system initialized");
210 return true;
211}
212
214 if (!lifecycle_is_initialized(&g_timer_manager.lifecycle)) {
215 return;
216 }
217
218 rwlock_wrlock(&g_timer_manager.rwlock);
219
220 // Free all timer records
221 int timer_count = 0;
222 timer_record_t *current, *tmp;
223 HASH_ITER(hh, g_timer_manager.timers, current, tmp) {
224 HASH_DEL(g_timer_manager.timers, current);
225 SAFE_FREE(current->name);
226 SAFE_FREE(current);
227 timer_count++;
228 }
229
230 g_timer_manager.timers = NULL;
231
232 rwlock_wrunlock(&g_timer_manager.rwlock);
233
234 /* Shutdown lifecycle and destroy rwlock together */
235 lifecycle_shutdown(&g_timer_manager.lifecycle);
236
237 log_debug("Timer system cleaned up (freed %d timers)", timer_count);
238}
239
240bool timer_start(const char *name) {
241 if (!lifecycle_is_initialized(&g_timer_manager.lifecycle)) {
242 SET_ERRNO(ERROR_INVALID_STATE, "Timer system not initialized");
243 return false;
244 }
245
246 if (!name) {
247 SET_ERRNO(ERROR_INVALID_PARAM, "Timer name is NULL");
248 return false;
249 }
250
251 rwlock_wrlock(&g_timer_manager.rwlock);
252
253 // Check if timer already exists - this is an error, timers must be unique
254 timer_record_t *existing = NULL;
255 HASH_FIND_STR(g_timer_manager.timers, name, existing);
256
257 if (existing) {
258 rwlock_wrunlock(&g_timer_manager.rwlock);
259 SET_ERRNO(ERROR_INVALID_STATE, "Timer '%s' already exists - timers must have unique names", name);
260 return false;
261 }
262
263 // Create new timer record
265 if (!timer) {
266 rwlock_wrunlock(&g_timer_manager.rwlock);
267 SET_ERRNO(ERROR_MEMORY, "Failed to allocate timer record");
268 return false;
269 }
270
271 // Use platform-safe string duplication
272 size_t name_len = strlen(name) + 1;
273 timer->name = SAFE_MALLOC(name_len, char *);
274 if (!timer->name) {
275 SAFE_FREE(timer);
276 rwlock_wrunlock(&g_timer_manager.rwlock);
277 SET_ERRNO(ERROR_MEMORY, "Failed to allocate timer name");
278 return false;
279 }
280 SAFE_STRNCPY(timer->name, name, name_len);
281
282 timer->start_ticks = stm_now();
283
284 // Add to hashtable
285 HASH_ADD_KEYPTR(hh, g_timer_manager.timers, timer->name, strlen(timer->name), timer);
286
287 rwlock_wrunlock(&g_timer_manager.rwlock);
288 return true;
289}
290
291double timer_stop(const char *name) {
292 if (!lifecycle_is_initialized(&g_timer_manager.lifecycle)) {
293 SET_ERRNO(ERROR_INVALID_STATE, "Timer system not initialized");
294 return -1.0;
295 }
296
297 if (!name) {
298 SET_ERRNO(ERROR_INVALID_PARAM, "Timer name is NULL");
299 return -1.0;
300 }
301
302 rwlock_wrlock(&g_timer_manager.rwlock);
303
304 // Find timer
305 timer_record_t *timer = NULL;
306 HASH_FIND_STR(g_timer_manager.timers, name, timer);
307
308 if (!timer) {
309 rwlock_wrunlock(&g_timer_manager.rwlock);
310 log_dev("Timer '%s' not found (may have already been stopped)", name);
311 return -1.0;
312 }
313
314 // Calculate elapsed time in nanoseconds for maximum precision
315 uint64_t end_ticks = stm_now();
316 uint64_t elapsed_ticks = stm_diff(end_ticks, timer->start_ticks);
317 double elapsed_ns = stm_ns(elapsed_ticks);
318
319 // Format duration for human-readable logging
320 char duration_str[32];
321 time_pretty((uint64_t)elapsed_ns, -1, duration_str, sizeof(duration_str));
322
323 // Log the result (dev level - only shown with --verbose)
324 log_dev("Timer '%s': %s", name, duration_str);
325
326 // Save pointers to free BEFORE removing from hashtable
327 char *timer_name = timer->name;
328
329 // Remove from hashtable (HASH_DEL only removes from hash, doesn't free memory)
330 HASH_DEL(g_timer_manager.timers, timer);
331
332 // Free timer->name first (this was allocated separately)
333 SAFE_FREE(timer_name);
334
335 // Nullify the timer->name field so debug_free doesn't try to access it
336 // when freeing the timer structure itself
337 timer->name = NULL;
338
339 // Free the timer record itself
340 SAFE_FREE(timer);
341
342 rwlock_wrunlock(&g_timer_manager.rwlock);
343 return elapsed_ns; // Return nanoseconds
344}
345
347 return lifecycle_is_initialized(&g_timer_manager.lifecycle);
348}
349
350// ============================================================================
351// Time Formatting Implementation
352// ============================================================================
353
354int format_uptime_hms(int hours, int minutes, int seconds, char *buffer, size_t buffer_size) {
355 if (!buffer || buffer_size == 0) {
356 return -1;
357 }
358
359 // Validate components
360 if (hours < 0 || minutes < 0 || minutes >= 60 || seconds < 0 || seconds >= 60) {
361 return -1;
362 }
363
364 // Format as HH:MM:SS with zero-padding
365 int written = safe_snprintf(buffer, buffer_size, "%02d:%02d:%02d", hours, minutes, seconds);
366
367 if (written < 0 || (size_t)written >= buffer_size) {
368 return -1;
369 }
370
371 return written;
372}
373
374// ============================================================================
375// Pretty Time Formatting with Spaces and Configurable Precision
376// ============================================================================
377
383static int strip_trailing_zeros(char *str) {
384 if (!str || !*str)
385 return 0;
386
387 int len = (int)strlen(str);
388 if (len <= 0)
389 return len;
390
391 // Find decimal point
392 char *decimal = strchr(str, '.');
393 if (!decimal)
394 return len; // No decimal point
395
396 // Find where the number ends (space indicates unit follows)
397 char *space = strchr(decimal, ' ');
398 int end_pos = space ? (int)(space - str) - 1 : len - 1;
399
400 // Strip trailing zeros from the fractional part
401 int pos = end_pos;
402 while (pos > (int)(decimal - str) && str[pos] == '0') {
403 pos--;
404 }
405
406 // If we end up at the decimal point itself, remove it too
407 if (pos == (int)(decimal - str)) {
408 pos--;
409 }
410
411 // Shift everything after pos (the unit) forward, or just null-terminate if no unit
412 if (space) {
413 // Move unit to new position
414 int space_idx = (int)(space - str);
415 memmove(str + pos + 1, str + space_idx, len - space_idx + 1);
416 } else {
417 // Just null-terminate
418 str[pos + 1] = '\0';
419 }
420
421 return (int)strlen(str);
422}
423
424int time_pretty(uint64_t nanoseconds, int decimals, char *buffer, size_t buffer_size) {
425 if (!buffer || buffer_size == 0) {
426 return -1;
427 }
428
429 // Handle zero
430 if (nanoseconds == 0) {
431 int written = safe_snprintf(buffer, buffer_size, "0 ns");
432 return (written < 0 || (size_t)written >= buffer_size) ? -1 : written;
433 }
434
435 // Determine decimals for this range if not explicitly set
436 int actual_decimals = decimals;
437 if (decimals == -1) {
438 if (nanoseconds < NS_PER_MIN_INT) {
439 actual_decimals = (nanoseconds < NS_PER_SEC_INT) ? 3 : 2; // ns/µs/ms or seconds
440 } else {
441 actual_decimals = 3; // colon notation
442 }
443 }
444
445 // Clamp decimals to valid range
446 if (actual_decimals < 0)
447 actual_decimals = 0;
448 if (actual_decimals > 9)
449 actual_decimals = 9;
450
451 int written = 0;
452
453 // Nanoseconds (< 1µs)
454 if (nanoseconds < NS_PER_US_INT) {
455 written = safe_snprintf(buffer, buffer_size, "%llu ns", (unsigned long long)nanoseconds);
456 }
457 // Microseconds (< 1ms)
458 else if (nanoseconds < NS_PER_MS_INT) {
459 double us = (double)nanoseconds / NS_PER_US;
460 char temp_buf[64];
461 // Use a direct snprintf with large enough buffer first
462 snprintf(temp_buf, sizeof(temp_buf), "%.*f µs", actual_decimals, us);
463 strip_trailing_zeros(temp_buf);
464 written = (int)strlen(temp_buf);
465 if (written > 0 && written < (int)buffer_size) {
466 SAFE_STRNCPY(buffer, temp_buf, buffer_size);
467 } else {
468 written = -1;
469 }
470 }
471 // Milliseconds (< 1s)
472 else if (nanoseconds < NS_PER_SEC_INT) {
473 double ms = (double)nanoseconds / NS_PER_MS;
474 char temp_buf[64];
475 snprintf(temp_buf, sizeof(temp_buf), "%.*f ms", actual_decimals, ms);
476 strip_trailing_zeros(temp_buf);
477 written = (int)strlen(temp_buf);
478 if (written > 0 && written < (int)buffer_size) {
479 SAFE_STRNCPY(buffer, temp_buf, buffer_size);
480 } else {
481 written = -1;
482 }
483 }
484 // Seconds (< 1 minute)
485 else if (nanoseconds < NS_PER_MIN_INT) {
486 double s = (double)nanoseconds / NS_PER_SEC;
487 char temp_buf[64];
488 snprintf(temp_buf, sizeof(temp_buf), "%.*f s", actual_decimals, s);
489 strip_trailing_zeros(temp_buf);
490 written = (int)strlen(temp_buf);
491 if (written > 0 && written < (int)buffer_size) {
492 SAFE_STRNCPY(buffer, temp_buf, buffer_size);
493 } else {
494 written = -1;
495 }
496 }
497 // Minutes (< 1 hour) - use colon notation: M:SS.frac
498 else if (nanoseconds < NS_PER_HOUR_INT) {
499 uint64_t total_seconds = nanoseconds / NS_PER_SEC_INT;
500 uint64_t minutes = total_seconds / 60;
501 uint64_t seconds = total_seconds % 60;
502 uint64_t remainder_ns = nanoseconds % NS_PER_SEC_INT;
503 double fraction = (double)remainder_ns / NS_PER_SEC;
504
505 char temp_buf[64];
506 if (actual_decimals == 0) {
507 snprintf(temp_buf, sizeof(temp_buf), "%llu:%02llu", (unsigned long long)minutes, (unsigned long long)seconds);
508 } else {
509 snprintf(temp_buf, sizeof(temp_buf), "%llu:%02llu.%.*f", (unsigned long long)minutes, (unsigned long long)seconds,
510 actual_decimals, fraction);
511 strip_trailing_zeros(temp_buf);
512 }
513
514 written = (int)strlen(temp_buf);
515 if (written > 0 && written < (int)buffer_size) {
516 SAFE_STRNCPY(buffer, temp_buf, buffer_size);
517 } else {
518 written = -1;
519 }
520 }
521 // Hours+ - use colon notation: H:MM:SS or HH:MM:SS
522 else {
523 uint64_t total_seconds = nanoseconds / NS_PER_SEC_INT;
524 uint64_t hours = total_seconds / 3600;
525 uint64_t remaining = total_seconds % 3600;
526 uint64_t minutes = remaining / 60;
527 uint64_t seconds = remaining % 60;
528
529 written = safe_snprintf(buffer, buffer_size, "%llu:%02llu:%02llu", (unsigned long long)hours,
530 (unsigned long long)minutes, (unsigned long long)seconds);
531 }
532
533 if (written < 0 || (size_t)written >= buffer_size) {
534 return -1;
535 }
536
537 return written;
538}
539
540int time_pretty_now(int decimals, char *buffer, size_t buffer_size) {
541 uint64_t ns = time_get_ns();
542 return time_pretty(ns, decimals, buffer, buffer_size);
543}
544
545// ============================================================================
546// Human-Readable Time Formatting (moment.js style)
547// ============================================================================
548
549int time_human_readable_unsigned(uint64_t nanoseconds, char *buffer, size_t buffer_size) {
550 if (!buffer || buffer_size == 0) {
551 return -1;
552 }
553
554 // Convert nanoseconds to seconds for threshold checking
555 double total_seconds = (double)nanoseconds / NS_PER_SEC;
556 int minutes = (int)(total_seconds / 60);
557 int hours = minutes / 60;
558 int days = hours / 24;
559 int months = days / 30; // Approximate
560 int years = months / 12;
561
562 int written =
563 format_human_readable_time_ago(total_seconds, minutes, hours, days, months, years, "ago", buffer, buffer_size);
564
565 if (written < 0 || (size_t)written >= buffer_size) {
566 return -1;
567 }
568
569 return written;
570}
571
572int time_human_readable(int64_t nanoseconds, char *buffer, size_t buffer_size) {
573 if (!buffer || buffer_size == 0) {
574 return -1;
575 }
576
577 // Determine direction (past vs future)
578 bool is_future = nanoseconds < 0;
579 uint64_t abs_ns = is_future ? (uint64_t)(-nanoseconds) : (uint64_t)nanoseconds;
580
581 // Convert nanoseconds to seconds for threshold checking
582 double total_seconds = (double)abs_ns / NS_PER_SEC;
583 int minutes = (int)(total_seconds / 60);
584 int hours = minutes / 60;
585 int days = hours / 24;
586 int months = days / 30; // Approximate
587 int years = months / 12;
588
589 const char *suffix = is_future ? "from now" : "ago";
590 int written =
591 format_human_readable_time_ago(total_seconds, minutes, hours, days, months, years, suffix, buffer, buffer_size);
592
593 if (written < 0 || (size_t)written >= buffer_size) {
594 return -1;
595 }
596
597 return written;
598}
599
600int time_human_readable_now(char *buffer, size_t buffer_size) {
601 int64_t ns = (int64_t)time_get_ns();
602 return time_human_readable(ns, buffer, buffer_size);
603}
604
605// ============================================================================
606// Adaptive Sleep Implementation
607// ============================================================================
608
610 if (!state || !config) {
611 return;
612 }
613
614 // Copy configuration
615 state->config = *config;
616
617 // A multiplier of 1.0 preserves the configured interval. Clamp it for
618 // configurations whose allowed range does not include the baseline.
619 state->current_speed_multiplier = 1.0;
620 if (state->current_speed_multiplier < config->min_speed_multiplier) {
622 }
623 if (state->current_speed_multiplier > config->max_speed_multiplier) {
625 }
626
628}
629
630uint64_t adaptive_sleep_calculate(adaptive_sleep_state_t *state, size_t queue_depth, size_t target_depth) {
631 if (!state) {
632 return 0;
633 }
634
635 const adaptive_sleep_config_t *cfg = &state->config;
636
637 // Determine if we need to speed up or slow down
638 double desired_multiplier;
639
640 if (queue_depth > target_depth) {
641 // Queue is building up - speed up processing
642 // The more items in queue, the faster we should process
643 size_t backlog = queue_depth - target_depth;
644
645 // Calculate desired speed based on backlog
646 // Each item above target increases speed proportionally
647 // This ensures we drain the queue faster the more backed up we are
648 double backlog_factor = 1.0 + ((double)backlog / (double)(target_depth + 1));
649 desired_multiplier = cfg->min_speed_multiplier * backlog_factor;
650
651 // Clamp to max speed
652 if (desired_multiplier > cfg->max_speed_multiplier) {
653 desired_multiplier = cfg->max_speed_multiplier;
654 }
655
656 // Ramp up gradually based on speedup_rate
657 double delta = desired_multiplier - state->current_speed_multiplier;
658 state->current_speed_multiplier += delta * cfg->speedup_rate;
659
660 } else {
661 // Queue is at or below target - slow down to baseline
662 desired_multiplier = 1.0;
663 if (desired_multiplier < cfg->min_speed_multiplier) {
664 desired_multiplier = cfg->min_speed_multiplier;
665 }
666 if (desired_multiplier > cfg->max_speed_multiplier) {
667 desired_multiplier = cfg->max_speed_multiplier;
668 }
669
670 // Ramp down gradually based on slowdown_rate
671 double delta = desired_multiplier - state->current_speed_multiplier;
672 state->current_speed_multiplier += delta * cfg->slowdown_rate;
673 }
674
675 // Ensure we stay within configured bounds
678 }
681 }
682
683 // Calculate actual sleep time: baseline / speed_multiplier
684 // Higher multiplier = shorter sleep = faster processing
686
687 // Store for debugging
688 state->last_sleep_ns = sleep_ns;
689
690 return sleep_ns;
691}
692
693void adaptive_sleep_do(adaptive_sleep_state_t *state, size_t queue_depth, size_t target_depth) {
694 uint64_t sleep_ns = adaptive_sleep_calculate(state, queue_depth, target_depth);
695
696 if (sleep_ns > 0) {
697 platform_sleep_ns(sleep_ns);
698 }
699}
700
701/* ============================================================================
702 * Time Format Validation and Formatting Implementation
703 * ============================================================================ */
704
709static const char SUPPORTED_SPECIFIERS[] = "YmmdHMSaAbBjwuIpZzcxXFTsGgV";
710
711bool time_format_is_valid_strftime(const char *format_str) {
712 if (!format_str) {
713 return false;
714 }
715
716 for (const char *p = format_str; *p; p++) {
717 if (*p == '%') {
718 p++;
719 if (!*p) {
720 /* Unterminated % at end of string */
721 log_error("Invalid time format: unterminated %% at end");
722 return false;
723 }
724
725 if (*p == '%') {
726 /* Escaped %% - this is valid, just skip it */
727 continue;
728 }
729
730 /* Optional: flag characters (-, 0, +, space) */
731 if (*p == '-' || *p == '0' || *p == '+' || *p == ' ') {
732 p++;
733 if (!*p) {
734 log_error("Invalid time format: flag character without specifier");
735 return false;
736 }
737 }
738
739 /* Optional: width specifier (digits or *) */
740 if (*p == '*' || isdigit((unsigned char)*p)) {
741 while (*p && (isdigit((unsigned char)*p) || *p == '*')) {
742 p++;
743 }
744 if (!*p) {
745 log_error("Invalid time format: width specifier without specifier character");
746 return false;
747 }
748 }
749
750 /* Optional: precision specifier (.digits or .*) */
751 if (*p == '.') {
752 p++;
753 if (!*p) {
754 log_error("Invalid time format: precision specifier incomplete");
755 return false;
756 }
757 while (*p && (isdigit((unsigned char)*p) || *p == '*')) {
758 p++;
759 }
760 if (!*p) {
761 log_error("Invalid time format: precision specifier without specifier character");
762 return false;
763 }
764 }
765
766 /* Optional: modifier characters (E for %Ex, O for %Ox) */
767 if (*p == 'E' || *p == 'O') {
768 p++;
769 if (!*p) {
770 log_error("Invalid time format: modifier character without specifier");
771 return false;
772 }
773 }
774
775 /* Validate that the final character is a supported specifier */
776 if (!strchr(SUPPORTED_SPECIFIERS, *p)) {
777 log_error("Invalid time format: unsupported specifier %%%c (at position %ld)", *p, (p - format_str));
778 return false;
779 }
780 }
781 }
782
783 return true;
784}
785
786int time_format_now(const char *format_str, char *buf, size_t buf_size) {
787 if (!format_str || !buf || buf_size < 2) {
788 SET_ERRNO(ERROR_INVALID_PARAM, "time_format_now: invalid arguments - format_str=%p, buf=%p, buf_size=%zu",
789 format_str, buf, buf_size);
790 return 0;
791 }
792
793 /* Get current wall-clock time in nanoseconds */
795
796 /* Extract seconds and nanoseconds */
797 time_t seconds = (time_t)(ts_ns / NS_PER_SEC_INT);
798
799 /* Convert seconds to struct tm */
800 struct tm tm_info;
801 platform_localtime(&seconds, &tm_info);
802
803 /* Format using strftime */
804 size_t len = strftime(buf, buf_size, format_str, &tm_info);
805
806 /* Check for strftime errors or buffer overflow */
807 if (len == 0) {
808 /* strftime returned 0 - either error or exact buffer fill */
809 log_debug("strftime returned 0 for format: %s", format_str);
810 return 0;
811 }
812
813 if (len >= buf_size - 1) {
814 /* Buffer would overflow - strftime filled entire buffer or hit exact size */
815 log_error("time_format_now: buffer too small (need %zu, have %zu)", len + 1, buf_size);
816 return 0;
817 }
818
819 return (int)len;
820}
821
822asciichat_error_t time_format_safe(const char *format_str, char *buf, size_t buf_size) {
823 /* Check NULL pointers */
824 if (!format_str) {
825 return SET_ERRNO(ERROR_INVALID_STATE, "time_format_safe: format_str is NULL");
826 }
827
828 if (!buf) {
829 return SET_ERRNO(ERROR_INVALID_STATE, "time_format_safe: buf is NULL");
830 }
831
832 /* Check minimum buffer size */
833 if (buf_size < 64) {
834 return SET_ERRNO(ERROR_INVALID_STATE, "time_format_safe: buffer too small (minimum 64 bytes, got %zu)", buf_size);
835 }
836
837 /* Validate format string */
838 if (!time_format_is_valid_strftime(format_str)) {
839 return SET_ERRNO(ERROR_INVALID_STATE, "time_format_safe: invalid time format: %s", format_str);
840 }
841
842 /* Format the time */
843 int result = time_format_now(format_str, buf, buf_size);
844 if (result <= 0) {
845 return SET_ERRNO(ERROR_INVALID_STATE, "time_format_safe: strftime formatting failed");
846 }
847
848 return ASCIICHAT_OK;
849}
Platform abstraction layer umbrella header providing unified cross-platform API.
⚠️‼️ Comprehensive thread-local error context system for ascii-chat
⚛️ Atomic operations abstraction layer with debug tracking
⚙️ Common definitions, error codes, macros, and types shared throughout the application
Error and exit codes - unified status values (0-255)
int buffer_size
Size of circular buffer.
Definition grep.c:90
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:414
#define SAFE_FREE(ptr)
Definition common.h:376
#define SAFE_MALLOC(size, cast)
Definition common.h:264
unsigned long long uint64_t
Definition common.h:59
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49
@ ERROR_INVALID_STATE
@ ERROR_PLATFORM_INIT
Definition error_codes.h:60
@ ERROR_MEMORY
Definition error_codes.h:56
@ ASCIICHAT_OK
Definition error_codes.h:51
@ ERROR_INVALID_PARAM
#define log_dev(...)
Log a DEV message (most verbose, development only)
Definition log/log.h:534
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548
double timer_stop(const char *name)
Stop a named timer and return elapsed time.
Definition util/time.c:291
int time_pretty_now(int decimals, char *buffer, size_t buffer_size)
Format current monotonic time as pretty duration.
Definition util/time.c:540
bool timer_start(const char *name)
Start a named timer.
Definition util/time.c:240
bool time_format_is_valid_strftime(const char *format_str)
Validate strftime format string against known safe specifiers.
Definition util/time.c:711
uint64_t time_get_ns(void)
Get current monotonic time in nanoseconds.
Definition util/time.c:108
void time_ns_to_timespec(uint64_t ns, struct timespec *ts)
Convert nanoseconds to struct timespec.
Definition util/time.c:178
uint64_t time_get_realtime_ns(void)
Get current wall-clock (real) time in nanoseconds.
Definition util/time.c:119
#define NS_PER_MIN_INT
Definition time.h:172
#define NS_PER_SEC_INT
Definition time.h:157
uint64_t adaptive_sleep_calculate(adaptive_sleep_state_t *state, size_t queue_depth, size_t target_depth)
Calculate adaptive sleep time based on queue depth.
Definition util/time.c:630
int time_pretty(uint64_t nanoseconds, int decimals, char *buffer, size_t buffer_size)
Format nanoseconds as pretty duration with spaces and configurable precision.
Definition util/time.c:424
int time_format_now(const char *format_str, char *buf, size_t buf_size)
Format current time using strftime format string.
Definition util/time.c:786
uint64_t time_timespec_to_ns(const struct timespec *ts)
Convert struct timespec to nanoseconds.
Definition util/time.c:164
#define NS_PER_HOUR_INT
Definition time.h:173
void timer_system_destroy(void)
Cleanup the timing system.
Definition util/time.c:213
#define NS_PER_MS_INT
Definition time.h:156
int time_human_readable_unsigned(uint64_t nanoseconds, char *buffer, size_t buffer_size)
Format elapsed time as human-readable relative duration (unsigned version)
Definition util/time.c:549
#define NS_PER_MS
Definition time.h:147
void adaptive_sleep_init(adaptive_sleep_state_t *state, const adaptive_sleep_config_t *config)
Initialize adaptive sleep state with configuration.
Definition util/time.c:609
int time_human_readable(int64_t nanoseconds, char *buffer, size_t buffer_size)
Format nanoseconds as human-readable relative duration (moment.js style)
Definition util/time.c:572
uint64_t time_elapsed_ns(uint64_t start_ns, uint64_t end_ns)
Calculate elapsed time with wraparound safety.
Definition util/time.c:150
#define NS_PER_US_INT
Definition time.h:155
int format_uptime_hms(int hours, int minutes, int seconds, char *buffer, size_t buffer_size)
Format uptime as HH:MM:SS string.
Definition util/time.c:354
void time_sleep_ns(uint64_t ns)
Sleep for specified nanoseconds.
Definition util/time.c:140
bool timer_is_initialized(void)
Check if timing system is initialized.
Definition util/time.c:346
bool timer_system_init(void)
Initialize the timing system.
Definition util/time.c:191
void adaptive_sleep_do(adaptive_sleep_state_t *state, size_t queue_depth, size_t target_depth)
Calculate sleep time and immediately sleep for that duration.
Definition util/time.c:693
#define NS_PER_US
Definition time.h:146
asciichat_error_t time_format_safe(const char *format_str, char *buf, size_t buf_size)
Safe wrapper for time formatting with validation and error codes.
Definition util/time.c:822
int time_human_readable_now(char *buffer, size_t buffer_size)
Format current monotonic time as human-readable relative duration.
Definition util/time.c:600
#define NS_PER_SEC
Definition time.h:148
#define rwlock_wrunlock(lock)
Release a write lock (with debug tracking in debug builds)
Definition rwlock.h:349
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:148
void platform_sleep_ns(uint64_t ns)
Platform-safe sleep function with nanosecond precision.
asciichat_error_t platform_localtime(const time_t *timer, struct tm *result)
Platform-safe localtime wrapper.
Definition util.c:50
#define rwlock_wrlock(lock)
Acquire a write lock (with debug tracking in debug builds)
Definition rwlock.h:313
void platform_sleep_us(unsigned int us)
High-precision sleep function with microsecond precision.
bool lifecycle_is_initialized(const lifecycle_t *lc)
Definition lifecycle.c:155
bool lifecycle_init(lifecycle_t *lc, const char *name)
Definition lifecycle.c:26
bool lifecycle_shutdown(lifecycle_t *lc)
Definition lifecycle.c:108
Lock-free module lifecycle state machine using pure stdatomic.
@ LIFECYCLE_SYNC_RWLOCK
Contains rwlock_t pointer.
Definition lifecycle.h:51
#define LIFECYCLE_INIT
Static initializer for module-global lifecycle variables (no sync primitive)
Definition lifecycle.h:68
Cross-platform read-write lock interface for ascii-chat.
Configuration for adaptive sleep behavior.
Definition time.h:677
double min_speed_multiplier
Minimum speed (max sleep) - usually 1.0 (baseline speed)
Definition time.h:679
double max_speed_multiplier
Maximum speed (min sleep) - e.g., 4.0 = process 4x faster.
Definition time.h:680
uint64_t baseline_sleep_ns
Normal sleep time in nanoseconds (when queue is at target)
Definition time.h:678
double speedup_rate
Ramp-up rate when queue builds (0.0-1.0, higher = faster ramp)
Definition time.h:681
double slowdown_rate
Ramp-down rate when queue empties (0.0-1.0, higher = faster ramp)
Definition time.h:682
Runtime state for adaptive sleep.
Definition time.h:693
double current_speed_multiplier
Current speed state (1.0 = baseline, >1.0 = faster)
Definition time.h:695
uint64_t last_sleep_ns
Last calculated sleep time (for debugging)
Definition time.h:696
adaptive_sleep_config_t config
Configuration (copied, not referenced)
Definition time.h:694
Read-write lock type (POSIX: pthread_rwlock_t with debug tracking)
Definition rwlock.h:61
Individual timer record for a named timing operation.
Definition time.h:248
uint64_t start_ticks
Start time in sokol ticks.
Definition time.h:250
char * name
Timer name (heap-allocated, unique key)
Definition time.h:249
⏱️ High-precision timing utilities using sokol_time.h and uthash
rwlock_t rwlock
Read-write lock for thread-safe access (uthash requires external locking)
Definition util/time.c:34
lifecycle_t lifecycle
Lifecycle state machine for init/shutdown synchronization.
Definition util/time.c:35
timer_record_t * timers
Hash table of active timers (uthash head pointer)
Definition util/time.c:33