Interruptible sleep function with platform-specific optimizations.
Provides a sleep mechanism that can be interrupted by the global shutdown signal, enabling responsive thread termination. The implementation varies by platform to optimize for responsiveness vs CPU usage.
This is the core video processing thread that generates personalized ASCII art frames for a specific client at 60fps. Each connected client gets their own dedicated video thread, providing linear performance scaling and personalized rendering based on terminal capabilities.
338 {
339 client_info_t *client = (client_info_t *)arg;
340 if (!client) {
341 log_error("NULL client pointer in video render thread");
342 return NULL;
343 }
344
345
346
347 uint32_t thread_client_id = atomic_load(&client->client_id);
348 socket_t thread_socket = client->socket;
349 bool is_webrtc = (thread_socket == INVALID_SOCKET_VALUE);
350 (void)is_webrtc;
351
352 log_debug("Video render thread: client_id=%u, webrtc=%d", thread_client_id, is_webrtc);
353
354
356
357 bool has_caps = client->has_terminal_caps;
358 int desired_fps = has_caps ? client->terminal_caps.desired_fps : 0;
359 if (has_caps && desired_fps > 0) {
360 client_fps = desired_fps;
361 log_debug("Client %u requested FPS: %d (has_caps=%d, desired_fps=%d)", thread_client_id, client_fps, has_caps,
362 desired_fps);
363 } else {
364 log_debug("Client %u using default FPS: %d (has_caps=%d, desired_fps=%d)", thread_client_id, client_fps, has_caps,
365 desired_fps);
366 }
367
368 int base_frame_interval_ms = 1000 / client_fps;
369 log_debug("Client %u render interval: %dms (%d FPS)", thread_client_id, base_frame_interval_ms, client_fps);
370
371
372 fps_t video_fps_tracker = {0};
373 fps_init(&video_fps_tracker, client_fps,
"SERVER VIDEO");
374
375
376 adaptive_sleep_state_t sleep_state = {0};
377 adaptive_sleep_config_t config = {
378 .baseline_sleep_ns = (uint64_t)(NS_PER_SEC_INT / client_fps),
379 .min_speed_multiplier = 1.0,
380 .max_speed_multiplier = 1.0,
381 .speedup_rate = 0.0,
382 .slowdown_rate = 0.0
383 };
385
386 log_info("Video render loop STARTING for client %u", thread_client_id);
387
388 bool should_continue = true;
389 while (should_continue && !atomic_load(&
g_server_should_exit) && !atomic_load(&client->shutting_down)) {
390 log_dev_every(10 * NS_PER_MS_INT, "Video render loop iteration for client %u", thread_client_id);
391
392
394 log_debug("Video render thread stopping for client %u (g_server_should_exit)", thread_client_id);
395 break;
396 }
397
398 bool video_running = atomic_load(&client->video_render_thread_running);
399 bool active = atomic_load(&client->active);
400 bool shutting_down = atomic_load(&client->shutting_down);
401
402 should_continue = video_running && active && !shutting_down;
403
404 if (!should_continue) {
405 log_debug("Video render thread stopping for client %u (should_continue=false: video_running=%d, active=%d, "
406 "shutting_down=%d)",
407 thread_client_id, video_running, active, shutting_down);
408 break;
409 }
410
411
412
414
415
417
418
419 should_continue = atomic_load(&client->video_render_thread_running) && atomic_load(&client->active) &&
420 !atomic_load(&client->shutting_down);
421 if (!should_continue) {
422 break;
423 }
424
425
426
427
428
429 uint32_t client_id_snapshot = atomic_load(&client->client_id);
430 unsigned short width_snapshot = atomic_load(&client->width);
431 unsigned short height_snapshot = atomic_load(&client->height);
432 bool active_snapshot = atomic_load(&client->active);
433
434
435 if (!active_snapshot) {
436 break;
437 }
438
439
440 size_t frame_size = 0;
441
442
444
445
446 static bool last_has_sources = false;
447 if (has_video_sources != last_has_sources) {
448 log_warn("DIAGNOSTIC: Client %u video sources: %s", thread_client_id,
449 has_video_sources ? "AVAILABLE" : "UNAVAILABLE");
450 last_has_sources = has_video_sources;
451 }
452
453 log_debug_every(5 * NS_PER_MS_INT,
454 "Video render iteration for client %u: has_video_sources=%d, width=%u, height=%u", thread_client_id,
455 has_video_sources, width_snapshot, height_snapshot);
456
457
458 if (width_snapshot == 0 || height_snapshot == 0) {
459 log_dev_every(5 * NS_PER_MS_INT,
460 "Skipping frame generation for client %u: dimensions not yet received (width=%u, height=%u)",
461 thread_client_id, width_snapshot, height_snapshot);
462 continue;
463 }
464
465 if (has_video_sources) {
466 int sources_count = 0;
467
468
469
470 static uint32_t frame_gen_count = 0;
471 static uint64_t frame_gen_start_time = 0;
472
473 frame_gen_count++;
474 if (frame_gen_count == 1) {
475 frame_gen_start_time = current_time_ns;
476 }
477
478
479 if (frame_gen_count % 120 == 0) {
480 uint64_t elapsed_ns = current_time_ns - frame_gen_start_time;
481 double gen_fps = (120.0 / (elapsed_ns / (double)NS_PER_SEC_INT));
482 log_warn("DIAGNOSTIC: Client %u LOOP running at %.1f FPS (120 iterations in %.2fs)", thread_client_id, gen_fps,
483 elapsed_ns / (double)NS_PER_SEC_INT);
484 }
485
486 log_dev_every(5 * NS_PER_MS_INT,
487 "About to call create_mixed_ascii_frame_for_client for client %u with dims %ux%u", thread_client_id,
488 width_snapshot, height_snapshot);
490 false, &frame_size, NULL, &sources_count);
491
492
493 static uint32_t last_frame_hash = -1;
494 uint32_t current_frame_hash = 0;
495 bool frame_is_new = false;
496 if (ascii_frame && frame_size > 0) {
497 for (size_t i = 0; i < frame_size && i < 1000; i++) {
498 current_frame_hash = (uint32_t)((uint64_t)current_frame_hash * 31 + ((unsigned char *)ascii_frame)[i]);
499 }
500 if (current_frame_hash != last_frame_hash) {
501 log_info("RENDER_FRAME CHANGE: Client %u frame #%zu sources=%d hash=0x%08x (prev=0x%08x)", thread_client_id,
502 frame_size, sources_count, current_frame_hash, last_frame_hash);
503 last_frame_hash = current_frame_hash;
504 frame_is_new = true;
505 } else {
506 log_dev_every(25000, "RENDER_FRAME DUPLICATE: Client %u frame #%zu sources=%d hash=0x%08x (no change)",
507 thread_client_id, frame_size, sources_count, current_frame_hash);
508 frame_is_new = false;
509 }
510 }
511
512 log_dev_every(5 * NS_PER_MS_INT,
513 "create_mixed_ascii_frame_for_client returned: ascii_frame=%p, frame_size=%zu, sources_count=%d",
514 (void *)ascii_frame, frame_size, sources_count);
515
516
517 if (ascii_frame && frame_size > 0) {
518 log_debug_every(5 * NS_PER_MS_INT, "Buffering frame for client %u (size=%zu)", thread_client_id, frame_size);
519
520
521 atomic_store(&client->last_rendered_grid_sources, sources_count);
522
523
524
525 video_frame_buffer_t *vfb_snapshot = client->outgoing_video_buffer;
526
527 if (vfb_snapshot) {
529 if (write_frame) {
530
531 if (write_frame->data && frame_size <= vfb_snapshot->allocated_buffer_size) {
532 memcpy(write_frame->data, ascii_frame, frame_size);
533 write_frame->size = frame_size;
534 write_frame->capture_timestamp_ns = current_time_ns;
535
536
537
538 if (frame_is_new) {
540
543 char commit_duration_str[32];
545 sizeof(commit_duration_str));
546
547 static uint32_t commits_count = 0;
548 static uint64_t commits_start_time = 0;
549 commits_count++;
550 if (commits_count == 1) {
551 commits_start_time = commit_end_ns;
552 }
553 if (commits_count % 10 == 0) {
554 uint64_t elapsed_ns = commit_end_ns - commits_start_time;
555 double commit_fps = (10.0 / (elapsed_ns / (double)NS_PER_SEC_INT));
556 log_warn("DIAGNOSTIC: Client %u UNIQUE frames being sent at %.1f FPS (10 commits counted)",
557 thread_client_id, commit_fps);
558 }
559
560 log_info("[FRAME_COMMIT_TIMING] Client %u frame commit took %s (hash=0x%08x)", thread_client_id,
561 commit_duration_str, current_frame_hash);
562 } else {
563
564 log_dev_every(25000, "Skipping commit for duplicate frame for client %u (hash=0x%08x)",
565 thread_client_id, current_frame_hash);
566 }
567
568
569 char pretty_size[64];
571
572
573 uint32_t ascii_hash = 0;
574 for (size_t i = 0; i < frame_size && i < 1000; i++) {
575 ascii_hash = (uint32_t)((((uint64_t)ascii_hash << 5) - ascii_hash) + (unsigned char)ascii_frame[i]);
576 }
577 log_dev_every(5 * NS_PER_MS_INT, "Client %u: Rendered ASCII frame size=%s hash=0x%08x sources=%d",
578 thread_client_id, pretty_size, ascii_hash, sources_count);
579
580 } else {
581 log_warn("Frame too large for buffer: %zu > %zu", frame_size, vfb_snapshot->allocated_buffer_size);
582 }
583
584
585 fps_frame_ns(&video_fps_tracker, current_time_ns,
"frame rendered");
586 }
587 }
588
589 SAFE_FREE(ascii_frame);
590 } else {
591
592 log_dev_every(10 * NS_PER_MS_INT, "Per-client render: No video sources available for client %u",
593 client_id_snapshot);
594 }
595 } else {
596
597
598 log_debug("Skipping frame generation for client %u (no video sources)", thread_client_id);
599 }
600 }
601
602#ifdef DEBUG_THREADS
603 log_debug("Video render thread stopped for client %u", thread_client_id);
604#endif
605
606
608
609 return NULL;
610}
bool any_clients_sending_video(void)
Check if any connected clients are currently sending video.
char * create_mixed_ascii_frame_for_client(uint32_t target_client_id, unsigned short width, unsigned short height, bool wants_stretch, size_t *out_size, bool *out_grid_changed, int *out_sources_count)
Generate personalized ASCII frame for a specific client.
int format_duration_ns(double nanoseconds, char *buffer, size_t buffer_size)
video_frame_t * video_frame_begin_write(video_frame_buffer_t *vfb)
void video_frame_commit(video_frame_buffer_t *vfb)