ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
common.c File Reference

🔧 Shared SIMD utilities: initialization, cleanup, and architecture-specific resource management More...

Go to the source code of this file.

Functions

void build_ramp64 (uint8_t ramp64[RAMP64_SIZE], const char *ascii_chars)
 
double calculate_cache_eviction_score (uint64_t last_access_time, uint32_t access_count, uint64_t creation_time, uint64_t current_time)
 
utf8_palette_cache_t * get_utf8_palette_cache (const char *ascii_chars)
 
void build_utf8_luminance_cache (const char *ascii_chars, utf8_char_t cache[256])
 
void build_utf8_ramp64_cache (const char *ascii_chars, utf8_char_t cache64[64], uint8_t char_index_ramp[64])
 
void simd_caches_destroy_all (void)
 

Detailed Description

🔧 Shared SIMD utilities: initialization, cleanup, and architecture-specific resource management

Definition in file video/simd/common.c.

Function Documentation

◆ build_ramp64()

void build_ramp64 ( uint8_t  ramp64[RAMP64_SIZE],
const char *  ascii_chars 
)

Definition at line 33 of file video/simd/common.c.

33 {
34 if (!ascii_chars) {
35 // Fallback to space character
36 for (int i = 0; i < RAMP64_SIZE; i++) {
37 ramp64[i] = ' ';
38 }
39 return;
40 }
41
42 // Use UTF-8 palette functions for proper character handling
43 utf8_palette_t *utf8_pal = utf8_palette_create(ascii_chars);
44 if (!utf8_pal) {
45 // Fallback to space character
46 for (int i = 0; i < RAMP64_SIZE; i++) {
47 ramp64[i] = ' ';
48 }
49 return;
50 }
51
52 size_t char_count = utf8_palette_get_char_count(utf8_pal);
53 if (char_count == 0) {
54 // No valid characters found, use space
55 for (int i = 0; i < RAMP64_SIZE; i++) {
56 ramp64[i] = ' ';
57 }
58 utf8_palette_destroy(utf8_pal);
59 return;
60 }
61
62 // Build the ramp64 lookup using UTF-8 character indices
63 for (int i = 0; i < RAMP64_SIZE; i++) {
64 // Map 0-63 to 0-(char_count-1) using proper character indexing
65 size_t char_idx = (i * (char_count - 1) + (RAMP64_SIZE - 1) / 2) / (RAMP64_SIZE - 1);
66 if (char_idx >= char_count) {
67 char_idx = char_count - 1;
68 }
69
70 // Get the first byte of the character at this character index
71 const utf8_char_info_t *char_info = utf8_palette_get_char(utf8_pal, char_idx);
72 if (char_info) {
73 ramp64[i] = (uint8_t)char_info->bytes[0];
74 } else {
75 ramp64[i] = ' '; // Fallback
76 }
77 }
78
79 utf8_palette_destroy(utf8_pal);
80}
void utf8_palette_destroy(utf8_palette_t *palette)
Definition palette.c:485
size_t utf8_palette_get_char_count(const utf8_palette_t *palette)
Definition palette.c:502
const utf8_char_info_t * utf8_palette_get_char(const utf8_palette_t *palette, size_t index)
Definition palette.c:494
utf8_palette_t * utf8_palette_create(const char *palette_string)
Definition palette.c:363

References utf8_palette_create(), utf8_palette_destroy(), utf8_palette_get_char(), and utf8_palette_get_char_count().

◆ build_utf8_luminance_cache()

void build_utf8_luminance_cache ( const char *  ascii_chars,
utf8_char_t  cache[256] 
)

Definition at line 394 of file video/simd/common.c.

394 {
395 if (!ascii_chars || !cache)
396 return;
397
398 // Parse characters
399 typedef struct {
400 const char *start;
401 int byte_len;
402 } char_info_t;
403
404 char_info_t char_infos[256];
405 int char_count = 0;
406 const char *p = ascii_chars;
407
408 while (*p && char_count < 255) {
409 char_infos[char_count].start = p;
410
411 if ((*p & 0xE0) == 0xC0) {
412 char_infos[char_count].byte_len = 2;
413 p += 2;
414 } else if ((*p & 0xF0) == 0xE0) {
415 char_infos[char_count].byte_len = 3;
416 p += 3;
417 } else if ((*p & 0xF8) == 0xF0) {
418 char_infos[char_count].byte_len = 4;
419 p += 4;
420 } else {
421 // ASCII characters (0x00-0x7F) and invalid sequences: treat as single byte
422 char_infos[char_count].byte_len = 1;
423 p++;
424 }
425 char_count++;
426 }
427
428 // Handle empty string case
429 if (char_count == 0)
430 return;
431
432 // Build 256-entry cache
433 for (int i = 0; i < 256; i++) {
434 int char_idx = char_count > 1 ? (i * (char_count - 1) + 127) / 255 : 0;
435 if (char_idx >= char_count)
436 char_idx = char_count - 1;
437
438 cache[i].byte_len = char_infos[char_idx].byte_len;
439 memcpy(cache[i].utf8_bytes, char_infos[char_idx].start, char_infos[char_idx].byte_len);
440 if (cache[i].byte_len < 4) {
441 cache[i].utf8_bytes[cache[i].byte_len] = '\0';
442 }
443 }
444}

Referenced by get_utf8_palette_cache().

◆ build_utf8_ramp64_cache()

void build_utf8_ramp64_cache ( const char *  ascii_chars,
utf8_char_t  cache64[64],
uint8_t  char_index_ramp[64] 
)

Definition at line 447 of file video/simd/common.c.

447 {
448 if (!ascii_chars || !cache64 || !char_index_ramp)
449 return;
450
451 // Reuse the luminance cache building logic but for 64 entries
452 // (Same UTF-8 parsing as above, but map to 64 entries instead of 256)
453
454 // Parse characters (same as above)
455 typedef struct {
456 const char *start;
457 int byte_len;
458 } char_info_t;
459
460 char_info_t char_infos[256];
461 int char_count = 0;
462 const char *p = ascii_chars;
463
464 while (*p && char_count < 255) {
465 char_infos[char_count].start = p;
466
467 if ((*p & 0xE0) == 0xC0) {
468 char_infos[char_count].byte_len = 2;
469 p += 2;
470 } else if ((*p & 0xF0) == 0xE0) {
471 char_infos[char_count].byte_len = 3;
472 p += 3;
473 } else if ((*p & 0xF8) == 0xF0) {
474 char_infos[char_count].byte_len = 4;
475 p += 4;
476 } else {
477 // ASCII characters (0x00-0x7F) and invalid sequences: treat as single byte
478 char_infos[char_count].byte_len = 1;
479 p++;
480 }
481 char_count++;
482 }
483
484 // Handle empty string case
485 if (char_count == 0)
486 return;
487
488 // Build 64-entry cache and index ramp
489 for (int i = 0; i < 64; i++) {
490 int char_idx = char_count > 1 ? (i * (char_count - 1) + 31) / 63 : 0;
491 if (char_idx >= char_count)
492 char_idx = char_count - 1;
493
494 // Store character index for SIMD lookup
495 char_index_ramp[i] = (uint8_t)char_idx;
496
497 // Cache UTF-8 character
498 cache64[i].byte_len = char_infos[char_idx].byte_len;
499 memcpy(cache64[i].utf8_bytes, char_infos[char_idx].start, char_infos[char_idx].byte_len);
500 if (cache64[i].byte_len < 4) {
501 cache64[i].utf8_bytes[cache64[i].byte_len] = '\0';
502 }
503 }
504}

Referenced by get_utf8_palette_cache().

◆ calculate_cache_eviction_score()

double calculate_cache_eviction_score ( uint64_t  last_access_time,
uint32_t  access_count,
uint64_t  creation_time,
uint64_t  current_time 
)

Definition at line 83 of file video/simd/common.c.

84 {
85 // Protect against unsigned underflow if times are inconsistent (clock adjustments, etc.)
86 uint64_t age_ns = (current_time >= last_access_time) ? (current_time - last_access_time) : 0;
87 uint64_t total_age_ns = (current_time >= creation_time) ? (current_time - creation_time) : 0;
88
89 uint64_t age_seconds = age_ns / NS_PER_SEC_INT;
90 uint64_t total_age_seconds = total_age_ns / NS_PER_SEC_INT;
91
92 // Frequency factor: high-use palettes get protection (logarithmic scaling)
93 double frequency_factor = 1.0 + log10(1.0 + access_count);
94
95 // Aging factor: frequency bonus decays over time (5-minute half-life)
96 double aging_factor = exp(-(double)age_seconds / CACHE_FREQUENCY_DECAY_TIME);
97
98 // Recent access bonus: strong protection for recently used (1-minute protection)
99 double recency_bonus = exp(-(double)age_seconds / CACHE_RECENCY_SCALE);
100
101 // Cache lifetime penalty: prevent immortal caches (1-hour max lifetime)
102 double lifetime_penalty = total_age_seconds > CACHE_MAX_LIFETIME ? 0.5 : 1.0;
103
104 // Final score: higher = keep longer
105 return (frequency_factor * aging_factor + recency_bonus) * lifetime_penalty;
106}

Referenced by get_utf8_palette_cache().

◆ get_utf8_palette_cache()

utf8_palette_cache_t * get_utf8_palette_cache ( const char *  ascii_chars)

Definition at line 285 of file video/simd/common.c.

285 {
286 if (!ascii_chars)
287 return NULL;
288
289 // Check for empty string
290 if (ascii_chars[0] == '\0')
291 return NULL;
292
293 // Create hash of palette for cache key
294 uint32_t palette_hash = hash_palette_string(ascii_chars);
295
296 // Ensure the cache system is initialized
297 init_utf8_cache_system();
298
299 // First try: read lock for cache lookup (allows multiple concurrent readers)
300 rwlock_rdlock(&g_utf8_cache_rwlock);
301
302 // Check if cache exists
303 utf8_palette_cache_t *cache = NULL;
304 HASH_FIND_INT(g_utf8_cache_table, &palette_hash, cache);
305 if (cache) {
306 // Cache hit: Update access tracking (atomics are thread-safe under rdlock)
307 uint64_t current_time = time_get_ns();
308 atomic_store(&cache->last_access_time, current_time);
309 uint32_t new_access_count = atomic_fetch_add(&cache->access_count, 1) + 1;
310
311 // Every 10th access: Update heap position (requires write lock)
312 if (new_access_count % 10 == 0) {
313 // Save the key before releasing read lock
314 uint32_t saved_key = cache->key;
315
316 // Release read lock and upgrade to write lock
317 rwlock_rdunlock(&g_utf8_cache_rwlock);
318 rwlock_wrlock(&g_utf8_cache_rwlock);
319
320 // Re-lookup cache entry after lock upgrade
321 // Another thread could have evicted this entry while we were upgrading locks
322 utf8_palette_cache_t *cache_relookup = NULL;
323 HASH_FIND_INT(g_utf8_cache_table, &saved_key, cache_relookup);
324 if (cache_relookup) {
325 // Cache entry still exists - safe to update heap position
326 uint64_t last_access = atomic_load(&cache_relookup->last_access_time);
327 uint32_t access_count = atomic_load(&cache_relookup->access_count);
328 double new_score =
329 calculate_cache_eviction_score(last_access, access_count, cache_relookup->creation_time, current_time);
330 utf8_heap_update_score(cache_relookup, new_score);
331 }
332 // If cache_relookup is NULL, entry was evicted - skip update (not an error)
333
334 rwlock_wrunlock(&g_utf8_cache_rwlock);
335 } else {
336 rwlock_rdunlock(&g_utf8_cache_rwlock);
337 }
338
339 return cache;
340 }
341
342 // Cache miss: Need to create new entry
343 // Release read lock and acquire write lock
344 rwlock_rdunlock(&g_utf8_cache_rwlock);
345 rwlock_wrlock(&g_utf8_cache_rwlock);
346
347 // Double-check: another thread might have created it while we upgraded locks
348 cache = NULL;
349 HASH_FIND_INT(g_utf8_cache_table, &palette_hash, cache);
350 if (cache) {
351 // Found it! Just update access tracking and return
352 uint64_t current_time = time_get_ns();
353 atomic_store(&cache->last_access_time, current_time);
354 atomic_fetch_add(&cache->access_count, 1);
355 rwlock_wrunlock(&g_utf8_cache_rwlock);
356 return cache;
357 }
358
359 // Create new cache entry (holding write lock)
360 cache = SAFE_MALLOC(sizeof(utf8_palette_cache_t), utf8_palette_cache_t *);
361 if (!cache) {
362 rwlock_wrunlock(&g_utf8_cache_rwlock);
363 return NULL;
364 }
365 memset(cache, 0, sizeof(utf8_palette_cache_t));
366
367 // Set the key for uthash
368 cache->key = palette_hash;
369
370 // Build both cache types
371 build_utf8_luminance_cache(ascii_chars, cache->cache);
372 build_utf8_ramp64_cache(ascii_chars, cache->cache64, cache->char_index_ramp);
373
374 // Store palette hash for validation
375 SAFE_STRNCPY(cache->palette_hash, ascii_chars, sizeof(cache->palette_hash));
376 cache->is_valid = true;
377
378 // Initialize eviction tracking
379 uint64_t current_time = time_get_ns();
380 atomic_store(&cache->last_access_time, current_time);
381 atomic_store(&cache->access_count, 1); // First access
382 cache->creation_time = current_time;
383
384 // Store in hash table with guaranteed eviction support
385 try_insert_with_eviction_utf8(palette_hash, cache);
386
387 log_debug("UTF8_CACHE: Created new cache for palette='%s' (hash=0x%x)", ascii_chars, palette_hash);
388
389 rwlock_wrunlock(&g_utf8_cache_rwlock);
390 return cache;
391}
uint64_t time_get_ns(void)
Definition util/time.c:48
void build_utf8_luminance_cache(const char *ascii_chars, utf8_char_t cache[256])
double calculate_cache_eviction_score(uint64_t last_access_time, uint32_t access_count, uint64_t creation_time, uint64_t current_time)
void build_utf8_ramp64_cache(const char *ascii_chars, utf8_char_t cache64[64], uint8_t char_index_ramp[64])

References build_utf8_luminance_cache(), build_utf8_ramp64_cache(), calculate_cache_eviction_score(), and time_get_ns().

Referenced by image_print(), image_print_16color(), image_print_16color_dithered(), image_print_16color_dithered_with_background(), image_print_color(), and session_display_create().

◆ simd_caches_destroy_all()

void simd_caches_destroy_all ( void  )

Definition at line 511 of file video/simd/common.c.

511 {
512 log_dev("SIMD_CACHE: Starting cleanup of all SIMD caches");
513
514 // Only destroy caches if they were ever initialized
515 if (atomic_load(&g_utf8_cache_initialized)) {
516 // Destroy shared UTF-8 palette cache (write lock for cleanup)
517 rwlock_wrlock(&g_utf8_cache_rwlock);
518 if (g_utf8_cache_table) {
519 // Free all UTF-8 cache entries using HASH_ITER
520 utf8_palette_cache_t *cache, *tmp;
521 HASH_ITER(hh, g_utf8_cache_table, cache, tmp) {
522 HASH_DEL(g_utf8_cache_table, cache);
523 SAFE_FREE(cache);
524 }
525 g_utf8_cache_table = NULL;
526 log_debug("UTF8_CACHE: Destroyed shared UTF-8 palette cache");
527 }
528 // Clean up heap arrays
529 if (g_utf8_heap) {
530 SAFE_FREE(g_utf8_heap);
531 g_utf8_heap = NULL;
532 g_utf8_heap_size = 0;
533 }
534 // Reset initialization flag so system can be reinitialized
535 atomic_store(&g_utf8_cache_initialized, false);
536 rwlock_wrunlock(&g_utf8_cache_rwlock);
537 }
538
539 // Call architecture-specific cache cleanup functions
540 // Note: Only ONE SIMD implementation is compiled based on highest available instruction set
541 // Higher instruction sets (AVX2, SSSE3) handle cleanup for lower ones (SSE2)
542#if SIMD_SUPPORT_NEON
543 neon_caches_destroy();
544#elif SIMD_SUPPORT_AVX2
545 avx2_caches_destroy();
546#elif SIMD_SUPPORT_SSSE3
547 ssse3_caches_destroy();
548#elif SIMD_SUPPORT_SSE2
549 sse2_caches_destroy();
550#elif SIMD_SUPPORT_SVE
551 sve_caches_destroy();
552#endif
553
554 log_dev("SIMD_CACHE: All SIMD caches destroyed");
555}

Referenced by asciichat_shared_destroy(), and server_main().