Overview
The render thread module manages per-client rendering threads that generate personalized ASCII frames at 60fps and mix audio streams at 172fps. This module embodies the real-time performance guarantees of ascii-chat, ensuring every client receives smooth video and low-latency audio regardless of the number of connected clients. The module implements sophisticated rate limiting, precise timing control, and thread lifecycle management to achieve professional-grade performance.
Implementation: src/server/render.c, src/server/render.h
Key Responsibilities:
- Manage per-client video rendering threads (60fps per client)
- Manage per-client audio rendering threads (172fps per client)
- Coordinate frame generation timing and rate limiting
- Ensure thread-safe access to client state and media buffers
- Provide graceful thread lifecycle management
- Handle platform-specific timing and synchronization
Per-Client Threading Model
Each connected client spawns exactly 2 dedicated threads:
Video Render Thread
Purpose: Generate personalized ASCII frames at 60fps Performance: 16.67ms intervals (60 frames per second) Operations:
- Collects video frames from all active clients (via stream.c)
- Creates composite frame with appropriate grid layout
- Converts composite to ASCII using client's terminal capabilities
- Queues ASCII frame in client's video packet queue
- Rate limits to exactly 60fps with precise timing
Thread Implementation:
static void *video_render_thread_func(void *arg) {
struct timespec last_frame_time;
clock_gettime(CLOCK_MONOTONIC, &last_frame_time);
while (true) {
!atomic_load(&client->video_render_thread_running) ||
!atomic_load(&client->active)) {
break;
}
ascii_frame_t *frame = generate_personalized_ascii_frame(client_id);
if (frame) {
}
struct timespec current_time;
clock_gettime(CLOCK_MONOTONIC, ¤t_time);
double elapsed = timespec_diff_ms(¤t_time, &last_frame_time);
if (elapsed < 16.67) {
}
last_frame_time = current_time;
}
return NULL;
}
int packet_queue_enqueue(packet_queue_t *queue, packet_type_t type, const void *data, size_t data_len, uint32_t client_id, bool copy_data)
Enqueue a packet into the queue.
atomic_bool g_server_should_exit
Global shutdown flag from main.c.
Rate Limiting Strategy:
- Uses CLOCK_MONOTONIC for precise timing
- Calculates elapsed time since last frame
- Sleeps only if ahead of schedule
- Prevents CPU spinning under light load
- Maintains exactly 60fps timing
Audio Render Thread
Purpose: Mix audio streams at 172fps (excluding client's own audio) Performance: 5.8ms intervals (172 frames per second) Operations:
- Reads audio samples from all clients via audio mixer
- Excludes client's own audio to prevent echo
- Mixes remaining audio streams with ducking and compression
- Queues mixed audio in client's audio packet queue
- Rate limits to exactly 172fps (5.8ms intervals)
Thread Implementation:
static void *audio_render_thread_func(void *arg) {
while (true) {
!atomic_load(&client->audio_render_thread_running) ||
!atomic_load(&client->active)) {
break;
}
audio_sample_t *mixed_audio = mixer_get_mixed_audio(mixer, client_id);
if (mixed_audio) {
mixed_audio_size);
}
}
return NULL;
}
mixer_t * g_audio_mixer
Global audio mixer from main.c.
Main mixer structure for multi-source audio processing.
Audio Processing Features:
- Echo cancellation (excludes client's own audio)
- Audio ducking (quiets other clients when client speaks)
- Compression (normalizes audio levels)
- Noise gate (suppresses background noise)
- Low-latency delivery (5.8ms intervals)
Rate Limiting and Timing Control
Timing Precision
Platform-Specific Timing:
- Windows: Uses Sleep() with millisecond precision
- POSIX: Uses condition variables for responsive interruption
- High-resolution timing with clock_gettime()
- Interruptible sleep for responsive shutdown
Rate Limiting Algorithm:
double elapsed = timespec_diff_ms(¤t_time, &last_frame_time);
if (elapsed < target_interval) {
} else {
}
Timing Guarantees:
- Video: Exactly 60fps (16.67ms intervals)
- Audio: Exactly 172fps (5.8ms intervals)
- Platform-specific high-resolution timers
- Interruptible sleep for responsive shutdown
CPU Usage Management
Prevent CPU Spinning:
- Sleep when ahead of schedule
- Skip sleep only when behind schedule
- Minimal CPU usage under light load
- Responsive to load changes
Catch-Up Strategy:
- Behind schedule: Skip sleep to catch up
- Prevents frame accumulation
- Maintains real-time performance
- Adaptive to load changes
Thread Safety and Synchronization
Client State Access
All client state access uses the snapshot pattern:
bool should_continue = client->video_render_thread_running &&
client->active;
uint32_t client_id_snapshot = client->client_id;
unsigned short width_snapshot = client->width;
unsigned short height_snapshot = client->height;
if (should_continue) {
generate_frame(client_id_snapshot, width_snapshot, height_snapshot,
&caps_snapshot);
}
Complete terminal capabilities structure.
uint32_t capabilities
Capability flags bitmask (terminal_capability_flags_t)
Snapshot Benefits:
- Minimal lock holding time
- No blocking operations while holding locks
- Prevents deadlocks from complex call chains
- Enables parallel processing across clients
Thread Control Flags
Atomic Thread Control:
video_render_thread_running: Atomic boolean for thread control
audio_render_thread_running: Atomic boolean for thread control
active: Atomic boolean for client connection status
g_server_should_exit: Atomic boolean for server shutdown
Shutdown Coordination:
- Threads check flags frequently in loops
- Clean thread exit on flag change
- No blocking operations that can't be interrupted
- Responsive shutdown (threads exit within frame interval)
Media Buffer Access
Video Buffer Access:
- Double-buffer system (atomic operations)
- Thread-safe frame reading
- Always get latest available frame
- Frame dropping under load handled gracefully
Audio Buffer Access:
- Lock-free ring buffer
- Thread-safe audio sample reading
- Automatic overflow handling
- Low-latency access
Thread Lifecycle Management
Thread Creation
Creation Order:
- Video render thread created first (60fps requirement)
- Audio render thread created second (172fps requirement)
Creation Process:
video_render_thread_func,
(
void *)(uintptr_t)client->
client_id) != 0) {
return -1;
}
audio_render_thread_func,
(
void *)(uintptr_t)client->
client_id) != 0) {
return -1;
}
return 0;
}
int create_client_render_threads(server_context_t *server_ctx, client_info_t *client)
Create and initialize per-client rendering threads.
Per-client state structure for server-side client management.
atomic_bool video_render_thread_running
asciichat_thread_t audio_render_thread
atomic_bool audio_render_thread_running
asciichat_thread_t video_render_thread
Thread Termination
Termination Sequence:
- Set thread running flags to false
- Threads detect flag change and exit loops
- Join threads to ensure complete cleanup
- Clear thread handles
Termination Process:
Timeout Handling:
- Thread join timeouts prevent blocking
- Logged warnings for timeout cases
- Graceful degradation on thread hang
- Clean shutdown even if threads don't exit cleanly
Platform Abstraction Integration
Cross-Platform Timing
Windows Timing:
- Uses Sleep() with millisecond precision
- Requires timer resolution adjustment (timeBeginPeriod)
- Minimal overhead for sleep operations
POSIX Timing:
- Uses condition variables for responsive interruption
- High-resolution timing with clock_gettime()
- Interruptible sleep for shutdown responsiveness
Cross-Platform Threading
Windows Threading:
- Uses platform abstraction layer
- Handles Windows-specific thread initialization
- Platform-safe thread join operations
POSIX Threading:
- Uses pthreads via platform abstraction
- Standard POSIX thread operations
- Cross-platform thread lifecycle management
Integration with Other Modules
Integration with client.c
Called By:
Provides To:
- Render thread management functions
- Thread lifecycle coordination
Integration with stream.c
Called By:
- Video render threads call
generate_personalized_ascii_frame()
- Frame generation at 60fps per client
Provides To:
- Per-client frame generation requests
- Terminal capability awareness
Integration with mixer.c
Called By:
- Audio render threads call
mixer_get_mixed_audio()
- Audio mixing at 172fps per client
Provides To:
- Per-client audio mixing requests
- Echo cancellation (excludes client's own audio)
Performance Characteristics
Linear Scaling:
- Each client gets dedicated render threads
- No shared bottlenecks between clients
- Performance scales linearly up to 9+ clients
- Real-time guarantees maintained per client
CPU Usage:
- Minimal CPU usage when ahead of schedule
- Adaptive to load changes
- No CPU spinning under light load
- Efficient sleep operations
Timing Precision:
- Exactly 60fps for video (16.67ms intervals)
- Exactly 172fps for audio (5.8ms intervals)
- Platform-specific high-resolution timers
- Consistent timing across platforms
Best Practices
DO:
- Always check shutdown flags in loops
- Use snapshot pattern for client state access
- Sleep when ahead of schedule to prevent CPU spinning
- Use precise timing for rate limiting
- Join threads with timeout to prevent blocking
DON'T:
- Don't hold locks during frame generation
- Don't skip shutdown flag checks
- Don't use blocking operations without timeout
- Don't ignore thread join failures
- Don't modify thread control flags without synchronization
- See also
- src/server/render.c
-
src/server/render.h
-
Server Overview
-
Client Management
-
Stream Generation
-
topic_mixer
-
Concurrency Documentation