ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
src/common/session/render.c
Go to the documentation of this file.
1#include <ascii-chat/otel.h>
13
16#include "session/render.h"
17#include "session/capture.h"
18#include "session/display.h"
19#include "session/pipeline.h"
24#include <ascii-chat/common.h>
25#include <fcntl.h>
26#include <unistd.h>
27#include <ascii-chat/log/log.h>
34#include <libplatform/terminal.h>
35#include <libplatform/compiler.h>
36#include <libplatform/system.h>
37#include <libplatform/terminal.h>
40
41#include <stddef.h>
42#include <stdbool.h>
43#include <stdio.h>
44#include <inttypes.h>
45#ifndef _WIN32
46#include <unistd.h>
47#endif
48
49/* ============================================================================
50 * Unified Render Loop Implementation
51 * ============================================================================ */
52
53// Global flags for snapshot mode timing
55uint64_t g_snapshot_first_frame_rendered_ns = 0; // Timestamp when first frame was rendered (for terminal output)
56uint64_t g_snapshot_first_capture_ns = 0; // Timestamp when first frame was captured (for video output)
57uint64_t g_snapshot_actual_duration_ms = 0; // Actual elapsed time in ms when capture thread hits snapshot timeout
58uint64_t g_snapshot_last_capture_elapsed_ns = 0; // Elapsed time from first to last frame capture (for PTS distribution)
59
63 void *user_data) {
64 // Validate required parameters
65 log_debug("capture=%p, display=%p, mode=%s", (void *)capture, (void *)display,
66 capture ? "SYNCHRONOUS" : "EVENT-DRIVEN");
67
68 if (!display) {
69 SET_ERRNO(ERROR_INVALID_PARAM, "display context is NULL");
71 }
72
73 if (!should_exit) {
74 SET_ERRNO(ERROR_INVALID_PARAM, "should_exit callback is NULL");
76 }
77
78 // Validate mode: either capture context OR custom callbacks, not both
79 if (!capture && !capture_cb) {
80 SET_ERRNO(ERROR_INVALID_PARAM, "must provide either capture context or capture callback");
82 }
83
84 if (capture && capture_cb) {
85 SET_ERRNO(ERROR_INVALID_PARAM, "cannot provide both capture context and capture callback");
87 }
88
89 // In event-driven mode, both capture_cb and sleep_cb must be provided together
90 if (capture_cb && !sleep_cb) {
91 SET_ERRNO(ERROR_INVALID_PARAM, "capture_cb requires sleep_cb (must provide both or neither)");
93 }
94
95 if (sleep_cb && !capture_cb) {
96 SET_ERRNO(ERROR_INVALID_PARAM, "sleep_cb requires capture_cb (must provide both or neither)");
98 }
99
100 // SYNCHRONOUS MODE: Use threaded pipeline for capture + render
101 if (capture != NULL) {
102 log_debug("[SESSION_RENDER_LOOP_SYNCHRONOUS] Creating pipeline... (capture=%p, display=%p)", (void *)capture,
103 (void *)display);
104 session_pipeline_t *pipeline = NULL;
105 asciichat_error_t pipeline_err = session_pipeline_create(capture, display, &pipeline);
106 log_debug("[SESSION_RENDER_LOOP_SYNCHRONOUS] pipeline_create returned: %d (pipeline=%p)", pipeline_err,
107 (void *)pipeline);
108 if (pipeline_err != ASCIICHAT_OK) {
109 log_error("[SESSION_RENDER_LOOP_SYNCHRONOUS] Pipeline creation failed %s",
110 asciichat_error_format(NULL, pipeline_err));
111 return pipeline_err;
112 }
113 log_debug("[SESSION_RENDER_LOOP_SYNCHRONOUS] Pipeline created successfully, running main loop...");
114
115 // Disable logging to stdout/stderr during rendering (same as event-driven mode).
116 // This prevents logs from mixing with ASCII art output. Logs still go to file.
118 asciichat_error_t run_err = session_pipeline_run_main(pipeline, should_exit, keyboard_handler, user_data);
120
121 asciichat_error_t drain_err = session_pipeline_destroy(pipeline);
122 // The encoder no longer writes here. Use capture duration, excluding time spent draining.
123 if (GET_OPTION(snapshot_mode) && display && g_snapshot_first_capture_ns > 0)
125
126 return run_err == ASCIICHAT_OK ? drain_err : run_err;
127 } else {
128 log_warn("capture is NULL - will not use pipeline (capture_cb=%p)", (void *)capture_cb);
129 }
130
131 // Snapshot mode state tracking
132 bool snapshot_mode = GET_OPTION(snapshot_mode);
133 bool snapshot_done = false;
134 uint64_t frames_rendered_since_first = 0; // Track frames rendered after first frame (for snapshot delay)
135 // NOTE: g_snapshot_first_frame_rendered is set by display.c when first frame is rendered via platform_write_all()
136
137 log_debug("display=%p capture=%p capture_cb=%p snapshot_mode=%s snapshot_delay=%.2f", (void *)display,
138 (void *)capture, (void *)capture_cb, snapshot_mode ? "YES" : "NO",
139 snapshot_mode ? GET_OPTION(snapshot_delay) : 0.0);
140 if (should_exit(user_data)) {
141 log_warn("EXIT signal already set at entry, aborting render loop");
142 return ASCIICHAT_OK;
143 }
144
145 // Keyboard input initialization (if keyboard handler is provided)
146 // Disable keyboard only in snapshot mode
147 // Keyboard is pre-initialized from asciichat_shared_init()
148 // Allow keyboard input in all other modes if handler provided
149 bool keyboard_enabled = keyboard_handler && !snapshot_mode;
150
151 // Pause mode state tracking (for pause/unpause transitions in event-driven mode)
152 bool initial_paused_frame_rendered = false;
153 bool is_paused = false;
154 log_debug("Keyboard setup - handler=%p snapshot=%s enabled=%s", (void *)keyboard_handler,
155 snapshot_mode ? "YES" : "NO", keyboard_enabled ? "YES" : "NO");
156
157 // Log TTY detection status for debugging keyboard issues
158 log_debug("TTY Status: stdin_tty=%d stdout_tty=%d interactive=%d keyboard_enabled=%d", terminal_is_stdin_tty(),
159 terminal_is_stdout_tty(), terminal_is_interactive(), keyboard_enabled);
160
161 // Frame rate timing (event-driven mode only)
162 uint64_t frame_count = 0;
163 uint64_t frame_start_ns = 0;
164
165 // Event-driven mode: disable console logging during rendering to prevent logs from corrupting frame display
166 // This must be done BEFORE the render loop to avoid repeated lock/unlock cycles that could cause deadlocks
167 log_debug("[EVENT_DRIVEN_MODE] Entering render loop. snapshot_mode=%s", snapshot_mode ? "YES" : "NO");
169
170 asciichat_error_t capture_result = ASCIICHAT_OK;
171 // Main render loop
172 int loop_iteration = 0;
173 log_debug("[RENDER_LOOP_START] snapshot_mode=%s, snapshot_delay=%.2f", snapshot_mode ? "YES" : "NO",
174 snapshot_mode ? GET_OPTION(snapshot_delay) : 0.0);
175
176 // Wait for splash animation to complete before rendering first frame
177 // This prevents ASCII art from mixing with splash screen output
178 // But skip for immediate snapshots (snapshot-delay=0) which need to output first frame immediately
179 static bool splash_wait_done = false;
180 if (!splash_wait_done) {
181 splash_wait_done = true;
182 if (!(GET_OPTION(snapshot_mode) && GET_OPTION(snapshot_delay) == 0.0)) {
183 // Re-enable logging briefly for splash wait, then disable again
187 }
188 }
189
190 // In snapshot mode, we must render at least one frame before checking should_exit
191 // to ensure the snapshot delay timer can start
192 bool force_first_iteration = snapshot_mode && frame_count == 0;
193
194 // Register display context globally so signal handlers (Ctrl+C) can access it
195 // This enables Ctrl+C to close the help screen instead of quitting when help is active
197
198 while (force_first_iteration || (!should_exit(user_data) && !(snapshot_mode && snapshot_done))) {
199 force_first_iteration = false;
200 loop_iteration++;
201 log_debug("[LOOP_ITER] iteration=%d, snapshot_done=%s, frame_count=%lu", loop_iteration,
202 snapshot_done ? "YES" : "NO", frame_count);
203 if (loop_iteration % 60 == 0) {
204 log_debug("iteration %d, should_exit check returning false", loop_iteration);
205 }
206 // Snapshot mode: exit at start of iteration if done
207 // This prevents frame 2+ from being captured when snapshot_delay has elapsed
208 if (snapshot_mode && snapshot_done && frame_count > 0) {
209 log_debug("[SNAPSHOT_EXIT] Snapshot mode: exiting at loop iteration start (snapshot_done=true, frames=%lu)",
210 frame_count);
211 break;
212 }
213
214 log_debug_every(US_PER_SEC_INT, "frame %lu", frame_count);
215 // Frame timing - measure total time to maintain target FPS
216 frame_start_ns = time_get_ns();
217
218 // Frame capture and timing - mode-dependent
219 image_t *image = NULL;
220 uint64_t capture_start_ns = 0;
221 uint64_t capture_end_ns = 0;
222 uint64_t pre_convert_ns = 0;
223 uint64_t post_convert_ns = 0;
224 uint64_t conversion_elapsed_ns = 0;
225
226 // EVENT-DRIVEN MODE: Frames come from callbacks
227 // Both sleep_cb and capture_cb are guaranteed non-NULL by validation above
228 sleep_cb(user_data);
229 ASCII_OTEL_SCOPE(PIPELINE_DISPLAY);
231 image = capture_cb(user_data);
232
233 if (!image) {
235 if (errno_peek_since(frame_scope, &failure)) {
236 capture_result = failure.code;
237 break;
238 }
239 // No frame available - this is normal in async modes (network latency, etc.)
240 // Just continue to next iteration, don't exit
241 continue;
242 }
243
244 // Event-driven mode: increment frame count
245 frame_count++;
246
247 // Convert image to ASCII using display context
248 // Handles all palette, terminal caps, width, height, stretch settings
249 pre_convert_ns = time_get_ns();
250 char *ascii_frame = session_display_convert_to_ascii_reuse(display, image);
251 post_convert_ns = time_get_ns();
252 conversion_elapsed_ns = post_convert_ns - pre_convert_ns;
253
254 if (frame_count <= 3) {
255 log_debug("RENDER_LOOP[%lu]: image=%p, ascii_frame=%p (conversion took %u ms)", frame_count, (void *)image,
256 (void *)ascii_frame, (unsigned)(conversion_elapsed_ns / NS_PER_MS_INT));
257 }
258
259 // Declare variables that need scope beyond the if (ascii_frame) block
260 bool is_paused_frame = initial_paused_frame_rendered && is_paused;
261 bool output_paused_frame = snapshot_mode && is_paused_frame;
262 uint64_t pre_render_ns = 0, post_render_ns = 0;
263
264 if (ascii_frame) {
265 log_debug_every(US_PER_SEC_INT, "ascii_frame ready (len=%zu)", strlen(ascii_frame));
266
267 // Always attempt to render frames; the display context will handle filtering based on:
268 // - TTY mode: render all frames with cursor control (even in snapshot mode, for animation)
269 // - Piped mode: render all frames without cursor control (for continuous capture)
270 // - Snapshot mode on non-TTY: only the display context renders the final frame
271 // BUT: don't render if splash screen is still animating - wait for it to finish
272 bool splash_running = splash_is_running();
273 bool should_write = !splash_running;
274 if (frame_count == 1) {
275 log_dev("[RENDER_FRAME] Frame 1: splash_is_running=%s, should_write=%s", splash_running ? "YES" : "NO",
276 should_write ? "YES" : "NO");
277 }
278 if (should_write) {
279 // is_final = true when: snapshot done, or paused frame (for both snapshot and pause modes)
280 // Profile: render frame
281 pre_render_ns = time_get_ns();
282
283 if (frame_count <= 5 || frame_count % 10 == 0) {
284 log_debug("Frame %lu - calling session_display_render_frame, display=%p", (unsigned long)frame_count,
285 (void *)display);
286 }
287
288 // Check if SIGINT handler cancelled help mode (Ctrl+C while help is open).
289 // The signal handler sets an atomic flag instead of touching state directly.
290 if (display && keyboard_help_check_signal_cancel()) {
291 keyboard_help_toggle(display);
292 log_debug("Ctrl+C cancelled help screen - returning to ASCII art");
293 }
294
295 session_display_render_frame(display, ascii_frame);
296
297 // Snapshot mode: increment frame counter for all displayed frames
298 // Counter starts at 1 for the first displayed frame, then 2, 3, 4... for subsequent frames
299 // Timer is started by display.c when g_snapshot_first_frame_rendered is set
300 if (snapshot_mode && g_snapshot_first_frame_rendered) {
301 frames_rendered_since_first++;
302 log_debug_every(US_PER_SEC_INT, "[SNAPSHOT] Frame rendered: frames_rendered_since_first=%lu",
303 frames_rendered_since_first);
304 }
305 }
306
307 // Keyboard input polling (if enabled) - MUST come before snapshot exit check so help screen can be toggled
308 // Allow keyboard in snapshot mode too (for help screen toggle debugging)
309 // Only enable keyboard if BOTH stdin AND stdout are TTYs to avoid buffering issues
310 // when tcsetattr() modifies the tty line discipline
311 if (keyboard_enabled && keyboard_handler) {
312 uint64_t keyboard_start_ns = time_get_ns();
314 double keyboard_elapsed_ns = (double)time_elapsed_ns(keyboard_start_ns, time_get_ns());
315 if (keyboard_elapsed_ns >= 0.0) {
316 char _duration_str[TIME_DURATION_BUFFER_SIZE];
317 time_pretty((uint64_t)keyboard_elapsed_ns, -1, _duration_str, sizeof(_duration_str));
318 log_dev_every(1 * NS_PER_SEC_INT, "RENDER[%lu] Keyboard read complete (key=%d) in %s",
319 (unsigned long)frame_count, key, _duration_str);
320 }
321 if (key != KEY_NONE) {
322 log_debug("KEYBOARD: Key pressed: code=%d char='%c'", key, (key >= 32 && key < 127) ? key : '?');
323 // Normal keyboard handler
324 log_debug("KEYBOARD: Normal handler taking key %d", key);
325 keyboard_handler(capture, key, user_data);
326 }
327 }
328
329 // Free frame before checking exit conditions to avoid double-free
330 session_display_release_ascii(display, ascii_frame);
331 }
332
333 // Snapshot mode: check if elapsed time has reached snapshot_delay duration (terminal render timing)
334 // Separate from capture timing - both should measure their own snapshot_delay
335 if (snapshot_mode && !snapshot_done) {
337 double snapshot_delay = GET_OPTION(snapshot_delay);
338 uint64_t now_ns = time_get_ns();
339 uint64_t elapsed_ns = now_ns - g_snapshot_first_frame_rendered_ns;
340 double elapsed_sec = (double)elapsed_ns / (double)NS_PER_SEC_INT;
341
342 if (frames_rendered_since_first == 1 || frames_rendered_since_first % 20 == 0) {
343 log_debug("SNAPSHOT: RENDER_CHECK iter=%lu elapsed=%.3f target=%.2f (from first_frame_rendered)",
344 frames_rendered_since_first, elapsed_sec, snapshot_delay);
345 }
346
347 // snapshot_delay=0 means exit after first frame
348 // snapshot_delay>0 means wait that many seconds before exiting
349 // Exit is based on elapsed wall-clock time, not frame count
350 bool should_exit = (snapshot_delay == 0.0) || (elapsed_sec >= snapshot_delay);
351
352 if (should_exit) {
353 log_debug("SNAPSHOT: RENDER_DONE at iteration %lu - elapsed=%.3f target=%.2f (setting snapshot_done=true)",
354 frames_rendered_since_first, elapsed_sec, snapshot_delay);
355 // We don't end frames with newlines so the next log would print on the same line as the frame's
356 // last row without an \n here. We only need this \n in stdout when interactive,
357 // so piped snapshots don't have a weird newline in stdout that they don't need.
358 if (terminal_is_interactive()) {
359 ui_controller_printf(STDOUT_FILENO, "\n");
360 }
361 snapshot_done = true;
362 }
363 } else {
364 if (frames_rendered_since_first == 0) {
365 log_debug("SNAPSHOT: Waiting for first_frame_rendered (g_snapshot_first_frame_rendered=%d, "
366 "g_snapshot_first_frame_rendered_ns=%llu)",
368 }
369 }
370 }
371
372 // Exit conditions: snapshot mode exits after capturing the final frame or initial paused frame
373 if (snapshot_mode && (snapshot_done || output_paused_frame)) {
374 log_debug("SNAPSHOT: EXIT CONDITION MET - snapshot_done=%d, output_paused_frame=%d", snapshot_done,
375 output_paused_frame);
376
377 // Calculate elapsed time from first frame captured (for video file)
378 // This is separate from render time - video should span from first capture to last capture
379 double capture_elapsed_sec = 0.0;
381 uint64_t now_ns = time_get_ns();
382 uint64_t elapsed_ns = now_ns - g_snapshot_first_capture_ns;
383 capture_elapsed_sec = (double)elapsed_ns / (double)NS_PER_SEC_INT;
384 log_debug("SNAPSHOT: EXIT - Capture elapsed: %.3f seconds, render elapsed: %.3f seconds, "
385 "g_snapshot_first_capture_ns=%llu",
386 capture_elapsed_sec,
389 : 0.0,
390 (unsigned long long)g_snapshot_first_capture_ns);
391 } else {
392 log_warn("SNAPSHOT: EXIT condition met but g_snapshot_first_capture_ns is 0!");
393 }
394
395 // Pass actual capture elapsed time to encoder for correct video duration
396 log_debug("SNAPSHOT: About to call session_display_set_snapshot_actual_duration with duration=%.3f, display=%p",
397 capture_elapsed_sec, (void *)display);
398 if (display) {
399 session_display_set_snapshot_actual_duration(display, capture_elapsed_sec);
400 } else {
401 log_warn("SNAPSHOT: display context is NULL!");
402 }
403
404 // Signal application to exit in snapshot mode
406 break;
407 }
408
409 // Frame rendering and timing details (moved down since snapshot exit is now above it)
410 if (ascii_frame) {
411
412 // Measure frame completion right after rendering, BEFORE keyboard polling
413 // This gives us accurate timing for just the core frame operations
414 uint64_t frame_end_render_ns = time_get_ns();
415
416 // Calculate each phase duration
417 uint64_t prestart_ms =
418 (capture_start_ns > frame_start_ns) ? (capture_start_ns - frame_start_ns) / NS_PER_MS_INT : 0;
419 uint64_t capture_ms =
420 (capture_end_ns > capture_start_ns) ? (capture_end_ns - capture_start_ns) / NS_PER_MS_INT : 0;
421 uint64_t convert_ms = conversion_elapsed_ns / NS_PER_MS_INT;
422 uint64_t render_ms =
423 (post_render_ns > pre_render_ns && post_render_ns > 0) ? (post_render_ns - pre_render_ns) / NS_PER_MS_INT : 0;
424 uint64_t total_ms =
425 (frame_end_render_ns > frame_start_ns) ? (frame_end_render_ns - frame_start_ns) / NS_PER_MS_INT : 0;
426
427 // Log phase breakdown every 5 frames
428 if (frame_count % 5 == 0) {
430 2 * NS_PER_SEC_INT,
431 "PHASE_BREAKDOWN[%lu]: prestart=%llu ms, capture=%llu ms, convert=%llu ms, render=%llu ms (total=%llu ms)",
432 frame_count, prestart_ms, capture_ms, convert_ms, render_ms, total_ms);
433 }
434 } else {
435 // Snapshot mode: even if frame conversion failed, check if we should exit
436 // This ensures snapshot_delay is honored even if display context isn't rendering
437 if (snapshot_mode && snapshot_done) {
438 break;
439 }
440 }
441
442 // In event-driven mode, frame rate limiting is handled by the sleep_cb callback
443 // Synchronous mode (with capture context) uses the pipeline and returns early above
444
445 // Note: Images returned by media sources are cached/reused and should NOT be destroyed
446 // The image pointers are managed by the source and will be cleaned up on source shutdown
447 } // while (!should_exit(user_data)) {
448
449 // Re-enable console logging after rendering completes
451 if (!snapshot_mode && terminal_is_interactive()) {
452 ui_controller_printf(STDOUT_FILENO, "\n");
453 }
454
455 // Keyboard input cleanup (if it was initialized)
456 if (keyboard_enabled) {
459 log_debug_every(2 * NS_PER_SEC_INT, "Keyboard input disabled");
460 }
461
462 // Unregister global display context (for signal handlers)
464
465 return capture_result;
466}
Application-level callbacks for library code.
#define APP_CALLBACK_VOID(callback_name)
bool errno_peek_since(asciichat_errno_scope_t scope, asciichat_error_context_t *out)
asciichat_errno_scope_t errno_checkpoint(void)
const char * asciichat_error_format(const char *message, asciichat_error_t code)
Synchronized per-thread pending error stacks and value snapshots. SET_ERRNO pushes,...
#define SET_ERRNO(code,...)
📹 Unified media capture abstraction for session-based video sources
⚙️ Common definitions, error codes, macros, and types shared throughout the application
🎞️ FFmpeg-based media decoder for video and audio streams
unsigned long long uint64_t
Definition common.h:63
asciichat_error_t
Error and exit codes - unified status values (0-255).
Definition error_codes.h:44
@ ASCIICHAT_OK
Definition error_codes.h:46
@ ERROR_INVALID_PARAM
#define log_warn(...)
Log a WARN message.
Definition log/log.h:621
#define log_dev(...)
Log a DEV message (most verbose, development only).
Definition log/log.h:581
#define log_error(...)
Log an ERROR message.
Definition log/log.h:634
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:595
void log_set_terminal_output(bool enabled)
Control stderr output to terminal.
Definition log.c:746
#define NS_PER_SEC_INT
Definition time.h:169
int time_pretty(uint64_t nanoseconds, int decimals, char *buffer, size_t buffer_size)
Format nanoseconds as a compact duration with configurable sub-minute precision.
Definition time.c:374
#define TIME_DURATION_BUFFER_SIZE
Definition time.h:40
#define NS_PER_MS_INT
Definition time.h:168
uint64_t time_elapsed_ns(uint64_t start_ns, uint64_t end_ns)
Calculate elapsed time with wraparound safety.
Definition time.c:123
#define US_PER_SEC_INT
Definition time.h:173
#define GET_OPTION(field)
Safely get a specific option field (lock-free read).
Definition options.h:1336
keyboard_key_t
Options for interactive line editing.
Definition keyboard.h:53
@ KEY_NONE
No key pressed or no input available.
Definition keyboard.h:54
void keyboard_help_toggle(session_display_ctx_t *ctx)
Toggle keyboard help on/off.
bool(* session_should_exit_fn)(void *user_data)
Exit condition callback type.
void(* session_keyboard_handler_fn)(session_capture_ctx_t *capture, int key, void *user_data)
Keyboard input handler callback type.
bool splash_is_running(void)
Check if splash screen animation is currently running.
Definition splash.c:445
void(* session_sleep_for_frame_fn)(void *user_data)
Sleep callback for custom frame timing.
struct session_display_ctx session_display_ctx_t
session display config is intentionally a small, concrete record. Fields such as snapshot_mode,...
image_t *(* session_capture_fn)(void *user_data)
Capture callback for event-driven frame source.
void session_display_set_snapshot_actual_duration(session_display_ctx_t *ctx, double actual_duration_sec)
Set the actual wall-clock duration for snapshot mode frame timing.
asciichat_error_t session_render_loop(session_capture_ctx_t *capture, session_display_ctx_t *display, session_should_exit_fn should_exit, session_capture_fn capture_cb, session_sleep_for_frame_fn sleep_cb, session_keyboard_handler_fn keyboard_handler, void *user_data)
Unified render loop for all display modes.
struct session_capture_ctx session_capture_ctx_t
session capture config keeps the values that move through the subsystem: type, path,...
void session_display_render_frame(session_display_ctx_t *ctx, const char *frame_data)
Render an ASCII frame to the terminal.
void session_display_set_global_context(session_display_ctx_t *ctx)
Set the global display context (for signal handlers like Ctrl+C).
bool keyboard_help_check_signal_cancel(void)
Check if help screen closure was requested by signal handler.
void splash_wait_for_animation(void)
Wait for splash animation thread to fully exit.
Definition splash.c:451
🔊 Audio Capture and Playback Interface for ascii-chat
🆘 Interactive keyboard help overlay for session keyboard shortcuts
📝 Logging API with multiple log levels and terminal output control
#define log_debug_every(interval_us, fmt,...)
Rate-limited DEBUG logging.
Definition log/log.h:749
#define log_dev_every(interval_us, fmt,...)
Rate-limited DEV logging.
Definition log/log.h:746
void signal_exit(void)
Definition main.c:253
bool should_exit(void)
Definition main.c:249
⚙️ Unified options parsing system for ascii-chat with builder pattern and lock-free access
#define ASCII_OTEL_SCOPE(id)
Definition otel.h:72
asciichat_error_t session_pipeline_run_main(session_pipeline_t *pipeline, session_should_exit_fn should_exit, session_keyboard_handler_fn keyboard_handler, void *user_data)
Definition pipeline.c:630
asciichat_error_t session_pipeline_create(session_capture_ctx_t *capture, session_display_ctx_t *display, session_pipeline_t **out)
Definition pipeline.c:541
bool g_snapshot_first_frame_rendered
asciichat_error_t session_pipeline_destroy(session_pipeline_t *pipeline)
Definition pipeline.c:749
Three-thread render pipeline: capture → display/encode.
struct session_pipeline_s session_pipeline_t
Definition pipeline.h:22
Read ANSI ASCII frames from file descriptor, chunked by height.
Interactive search filtering for terminal screens.
🎬 Unified media source abstraction for webcam, files, and stdin
Intro and status screen display for ascii-chat with animated rainbow effects.
void session_display_release_ascii(session_display_ctx_t *ctx, char *ascii)
char * session_display_convert_to_ascii_reuse(session_display_ctx_t *ctx, const image_t *image)
🖥️ Unified terminal display abstraction for session-based rendering
uint64_t g_snapshot_last_capture_elapsed_ns
uint64_t g_snapshot_first_frame_rendered_ns
uint64_t g_snapshot_first_capture_ns
uint64_t g_snapshot_actual_duration_ms
Centralized render loop abstraction for all display modes.
When code needs to work with asciichat errno snapshot, it keeps total, copied here instead of scatter...
SIMD-friendly storage for one RGB pixel. r, g, and b hold the color channels and padding keeps the la...
⏱️ High-precision timing utilities using sokol_time.h and uthash
void ui_controller_remove(ui_screen_t screen)
asciichat_error_t ui_controller_printf(int fd, const char *format,...)
@ UI_SCREEN_MEDIA
@ UI_SCREEN_HELP
keyboard_key_t ui_input_read_key(ui_screen_t screen)
Definition tui/input.c:353