7#include <ascii-chat/common.h>
8#include <ascii-chat/video/simd/common.h>
9#include <ascii-chat/uthash/uthash.h>
10#include <ascii-chat/video/palette.h>
11#include <ascii-chat/util/fnv1a.h>
12#include <ascii-chat/util/time.h>
13#include <ascii-chat/platform/init.h>
21#include <ascii-chat/video/simd/neon.h>
22#elif SIMD_SUPPORT_AVX2
23#include <ascii-chat/video/simd/avx2.h>
24#elif SIMD_SUPPORT_SSSE3
25#include <ascii-chat/video/simd/ssse3.h>
26#elif SIMD_SUPPORT_SSE2
27#include <ascii-chat/video/simd/sse2.h>
29#include <ascii-chat/video/simd/sve.h>
33void build_ramp64(uint8_t ramp64[RAMP64_SIZE],
const char *ascii_chars) {
36 for (
int i = 0; i < RAMP64_SIZE; i++) {
46 for (
int i = 0; i < RAMP64_SIZE; i++) {
53 if (char_count == 0) {
55 for (
int i = 0; i < RAMP64_SIZE; i++) {
63 for (
int i = 0; i < RAMP64_SIZE; i++) {
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;
73 ramp64[i] = (uint8_t)char_info->bytes[0];
84 uint64_t current_time) {
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;
89 uint64_t age_seconds = age_ns / NS_PER_SEC_INT;
90 uint64_t total_age_seconds = total_age_ns / NS_PER_SEC_INT;
93 double frequency_factor = 1.0 + log10(1.0 + access_count);
96 double aging_factor = exp(-(
double)age_seconds / CACHE_FREQUENCY_DECAY_TIME);
99 double recency_bonus = exp(-(
double)age_seconds / CACHE_RECENCY_SCALE);
102 double lifetime_penalty = total_age_seconds > CACHE_MAX_LIFETIME ? 0.5 : 1.0;
105 return (frequency_factor * aging_factor + recency_bonus) * lifetime_penalty;
109static inline uint32_t hash_palette_string(
const char *palette) {
110 return fnv1a_hash_string(palette);
114static bool try_insert_with_eviction_utf8(uint32_t hash, utf8_palette_cache_t *new_cache);
117static utf8_palette_cache_t *g_utf8_cache_table = NULL;
118static rwlock_t g_utf8_cache_rwlock = {0};
119static _Atomic(
bool) g_utf8_cache_initialized =
false;
122static utf8_palette_cache_t **g_utf8_heap = NULL;
123static size_t g_utf8_heap_size = 0;
124static const size_t g_utf8_heap_capacity = 2048;
129static void init_utf8_cache_system(
void) {
131 if (atomic_load(&g_utf8_cache_initialized)) {
137 static static_mutex_t init_bootstrap_mutex = STATIC_MUTEX_INIT;
139 static_mutex_lock(&init_bootstrap_mutex);
142 if (!atomic_load(&g_utf8_cache_initialized)) {
147 g_utf8_cache_table = NULL;
148 g_utf8_heap = SAFE_MALLOC(g_utf8_heap_capacity *
sizeof(utf8_palette_cache_t *), utf8_palette_cache_t **);
149 g_utf8_heap_size = 0;
152 atomic_store(&g_utf8_cache_initialized,
true);
155 static_mutex_unlock(&init_bootstrap_mutex);
159static void utf8_heap_swap(
size_t i,
size_t j) {
160 utf8_palette_cache_t *temp = g_utf8_heap[i];
161 g_utf8_heap[i] = g_utf8_heap[j];
162 g_utf8_heap[j] = temp;
165 g_utf8_heap[i]->heap_index = i;
166 g_utf8_heap[j]->heap_index = j;
169static void utf8_heap_bubble_up(
size_t index) {
171 size_t parent = (index - 1) / 2;
172 if (g_utf8_heap[index]->cached_score >= g_utf8_heap[parent]->cached_score)
174 utf8_heap_swap(index, parent);
179static void utf8_heap_bubble_down(
size_t index) {
181 size_t left = 2 * index + 1;
182 size_t right = 2 * index + 2;
183 size_t smallest = index;
185 if (left < g_utf8_heap_size && g_utf8_heap[left]->cached_score < g_utf8_heap[smallest]->cached_score) {
188 if (right < g_utf8_heap_size && g_utf8_heap[right]->cached_score < g_utf8_heap[smallest]->cached_score) {
192 if (smallest == index)
194 utf8_heap_swap(index, smallest);
199static void utf8_heap_insert(utf8_palette_cache_t *cache,
double score) {
200 if (g_utf8_heap_size >= g_utf8_heap_capacity) {
201 log_error(
"UTF8_HEAP: Heap capacity exceeded");
205 cache->cached_score = score;
206 cache->heap_index = g_utf8_heap_size;
207 g_utf8_heap[g_utf8_heap_size] = cache;
210 utf8_heap_bubble_up(cache->heap_index);
213static utf8_palette_cache_t *utf8_heap_extract_min(
void) {
214 if (g_utf8_heap_size == 0) {
218 utf8_palette_cache_t *min_cache = g_utf8_heap[0];
222 if (g_utf8_heap_size > 0) {
223 g_utf8_heap[0] = g_utf8_heap[g_utf8_heap_size];
224 g_utf8_heap[0]->heap_index = 0;
225 utf8_heap_bubble_down(0);
231static void utf8_heap_update_score(utf8_palette_cache_t *cache,
double new_score) {
232 double old_score = cache->cached_score;
233 cache->cached_score = new_score;
235 if (new_score < old_score) {
236 utf8_heap_bubble_up(cache->heap_index);
238 utf8_heap_bubble_down(cache->heap_index);
245static bool try_insert_with_eviction_utf8(uint32_t hash, utf8_palette_cache_t *new_cache) {
248 new_cache->key = hash;
251 size_t entry_count = HASH_COUNT(g_utf8_cache_table);
252 if (entry_count >= g_utf8_heap_capacity) {
254 utf8_palette_cache_t *victim_cache = utf8_heap_extract_min();
256 uint32_t victim_key = victim_cache->key;
259 uint32_t victim_access_count = atomic_load(&victim_cache->access_count);
261 uint64_t victim_age = (current_time - atomic_load(&victim_cache->last_access_time)) / NS_PER_SEC_INT;
263 log_debug(
"UTF8_CACHE_EVICTION: Proactive min-heap eviction hash=0x%x (age=%lus, count=%u)", victim_key,
264 victim_age, victim_access_count);
266 HASH_DEL(g_utf8_cache_table, victim_cache);
267 SAFE_FREE(victim_cache);
273 HASH_ADD_INT(g_utf8_cache_table, key, new_cache);
278 utf8_heap_insert(new_cache, initial_score);
290 if (ascii_chars[0] ==
'\0')
294 uint32_t palette_hash = hash_palette_string(ascii_chars);
297 init_utf8_cache_system();
300 rwlock_rdlock(&g_utf8_cache_rwlock);
303 utf8_palette_cache_t *cache = NULL;
304 HASH_FIND_INT(g_utf8_cache_table, &palette_hash, cache);
308 atomic_store(&cache->last_access_time, current_time);
309 uint32_t new_access_count = atomic_fetch_add(&cache->access_count, 1) + 1;
312 if (new_access_count % 10 == 0) {
314 uint32_t saved_key = cache->key;
317 rwlock_rdunlock(&g_utf8_cache_rwlock);
318 rwlock_wrlock(&g_utf8_cache_rwlock);
322 utf8_palette_cache_t *cache_relookup = NULL;
323 HASH_FIND_INT(g_utf8_cache_table, &saved_key, cache_relookup);
324 if (cache_relookup) {
326 uint64_t last_access = atomic_load(&cache_relookup->last_access_time);
327 uint32_t access_count = atomic_load(&cache_relookup->access_count);
330 utf8_heap_update_score(cache_relookup, new_score);
334 rwlock_wrunlock(&g_utf8_cache_rwlock);
336 rwlock_rdunlock(&g_utf8_cache_rwlock);
344 rwlock_rdunlock(&g_utf8_cache_rwlock);
345 rwlock_wrlock(&g_utf8_cache_rwlock);
349 HASH_FIND_INT(g_utf8_cache_table, &palette_hash, cache);
353 atomic_store(&cache->last_access_time, current_time);
354 atomic_fetch_add(&cache->access_count, 1);
355 rwlock_wrunlock(&g_utf8_cache_rwlock);
360 cache = SAFE_MALLOC(
sizeof(utf8_palette_cache_t), utf8_palette_cache_t *);
362 rwlock_wrunlock(&g_utf8_cache_rwlock);
365 memset(cache, 0,
sizeof(utf8_palette_cache_t));
368 cache->key = palette_hash;
375 SAFE_STRNCPY(cache->palette_hash, ascii_chars,
sizeof(cache->palette_hash));
376 cache->is_valid =
true;
380 atomic_store(&cache->last_access_time, current_time);
381 atomic_store(&cache->access_count, 1);
382 cache->creation_time = current_time;
385 try_insert_with_eviction_utf8(palette_hash, cache);
387 log_debug(
"UTF8_CACHE: Created new cache for palette='%s' (hash=0x%x)", ascii_chars, palette_hash);
389 rwlock_wrunlock(&g_utf8_cache_rwlock);
395 if (!ascii_chars || !cache)
404 char_info_t char_infos[256];
406 const char *p = ascii_chars;
408 while (*p && char_count < 255) {
409 char_infos[char_count].start = p;
411 if ((*p & 0xE0) == 0xC0) {
412 char_infos[char_count].byte_len = 2;
414 }
else if ((*p & 0xF0) == 0xE0) {
415 char_infos[char_count].byte_len = 3;
417 }
else if ((*p & 0xF8) == 0xF0) {
418 char_infos[char_count].byte_len = 4;
422 char_infos[char_count].byte_len = 1;
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;
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';
448 if (!ascii_chars || !cache64 || !char_index_ramp)
460 char_info_t char_infos[256];
462 const char *p = ascii_chars;
464 while (*p && char_count < 255) {
465 char_infos[char_count].start = p;
467 if ((*p & 0xE0) == 0xC0) {
468 char_infos[char_count].byte_len = 2;
470 }
else if ((*p & 0xF0) == 0xE0) {
471 char_infos[char_count].byte_len = 3;
473 }
else if ((*p & 0xF8) == 0xF0) {
474 char_infos[char_count].byte_len = 4;
478 char_infos[char_count].byte_len = 1;
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;
495 char_index_ramp[i] = (uint8_t)char_idx;
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';
512 log_dev(
"SIMD_CACHE: Starting cleanup of all SIMD caches");
515 if (atomic_load(&g_utf8_cache_initialized)) {
517 rwlock_wrlock(&g_utf8_cache_rwlock);
518 if (g_utf8_cache_table) {
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);
525 g_utf8_cache_table = NULL;
526 log_debug(
"UTF8_CACHE: Destroyed shared UTF-8 palette cache");
530 SAFE_FREE(g_utf8_heap);
532 g_utf8_heap_size = 0;
535 atomic_store(&g_utf8_cache_initialized,
false);
536 rwlock_wrunlock(&g_utf8_cache_rwlock);
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();
554 log_dev(
"SIMD_CACHE: All SIMD caches destroyed");
void utf8_palette_destroy(utf8_palette_t *palette)
size_t utf8_palette_get_char_count(const utf8_palette_t *palette)
const utf8_char_info_t * utf8_palette_get_char(const utf8_palette_t *palette, size_t index)
utf8_palette_t * utf8_palette_create(const char *palette_string)
int rwlock_init(rwlock_t *rwlock)
uint64_t time_get_ns(void)
void build_ramp64(uint8_t ramp64[RAMP64_SIZE], const char *ascii_chars)
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 simd_caches_destroy_all(void)
void build_utf8_ramp64_cache(const char *ascii_chars, utf8_char_t cache64[64], uint8_t char_index_ramp[64])
utf8_palette_cache_t * get_utf8_palette_cache(const char *ascii_chars)