ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
fps_counter.c
Go to the documentation of this file.
1
6
8#include <ascii-chat/common.h>
9#include <libplatform/terminal.h>
11#include <string.h>
12
13/* ============================================================================
14 * FPS Counter Implementation
15 * ============================================================================ */
16
17#define FPS_WINDOW_SIZE 30
18
45
46/* ============================================================================
47 * Lifecycle Functions
48 * ============================================================================ */
49
51 fps_counter_t *counter = SAFE_CALLOC(1, sizeof(fps_counter_t), fps_counter_t *);
52 if (!counter) {
53 return NULL;
54 }
55
56 counter->last_fps = -1;
57 counter->effective_fps = 60;
58 counter->report_start = time_get_ns();
59
60 // Initialize circular buffer pointers
61 counter->head = 0;
62 counter->count = 0;
63 memset(counter->frame_times, 0, sizeof(counter->frame_times));
64
65 return counter;
66}
67
69 if (counter) {
70 SAFE_FREE(counter);
71 }
72}
73
74/* ============================================================================
75 * FPS Measurement Functions
76 * ============================================================================ */
77
79 fps_counter_tick_at(counter, time_get_ns());
80}
81
83 if (counter) {
84 counter->head = counter->count = 0;
85 memset(counter->frame_times, 0, sizeof(counter->frame_times));
86 }
87}
88
90 if (!counter) {
91 return;
92 }
93
94 if (counter->count) {
95 uint64_t previous = counter->frame_times[(counter->head + FPS_WINDOW_SIZE - 1) % FPS_WINDOW_SIZE];
96 if (now <= previous || now - previous >= 2000000000ULL)
97 fps_counter_reset(counter);
98 }
99
100 // Record current time at head position
101 counter->frame_times[counter->head] = now;
102
103 // Advance head pointer with wraparound
104 counter->head = (counter->head + 1) % FPS_WINDOW_SIZE;
105
106 // Track how many valid entries we have (up to FPS_WINDOW_SIZE)
107 if (counter->count < FPS_WINDOW_SIZE) {
108 counter->count++;
109 }
110}
111
113 return fps_counter_get_at(counter, time_get_ns());
114}
115
117 if (!counter || counter->count < 2) {
118 return 0.0f;
119 }
120
121 // Find indices of oldest and newest timestamps in the buffer
122 int oldest_idx = (counter->head - counter->count + FPS_WINDOW_SIZE) % FPS_WINDOW_SIZE;
123 int newest_idx = (counter->head - 1 + FPS_WINDOW_SIZE) % FPS_WINDOW_SIZE;
124
125 // Get the elapsed time between oldest and newest frames
126 uint64_t oldest_time = counter->frame_times[oldest_idx];
127 uint64_t newest_time = counter->frame_times[newest_idx];
128 if (now < newest_time || now - newest_time >= 2000000000ULL)
129 return 0.0f;
130 // Include time since the last completed frame so stalled output decays immediately.
131 uint64_t elapsed_ns = now - oldest_time;
132
133 // Avoid division by zero
134 if (elapsed_ns == 0) {
135 return 0.0f;
136 }
137
138 // FPS = (number of frames) / elapsed_time
139 // We have (count - 1) frames across the elapsed time
140 // (count - 1) frames means count timestamps, so count-1 intervals
141 return (float)(counter->count - 1) * 1e9f / (float)elapsed_ns;
142}
143
144bool fps_counter_set_visible(fps_counter_t *counter, bool visible) {
145 if (!counter)
146 return false;
147 bool changed = counter->visible != visible;
148 counter->visible = visible;
149 if (changed)
150 counter->last_fps = -1;
151 return changed;
152}
153
154void fps_counter_set_effective_fps(fps_counter_t *counter, unsigned int fps) {
155 if (counter) {
156 counter->effective_fps = fps > 0 ? fps : 60;
157 counter->last_fps = -1;
158 }
159}
160
161void fps_counter_frame_begin(fps_counter_t *counter, bool measure) {
162 if (!counter)
163 return;
164 counter->current_frame_id = 0;
165 counter->measuring = measure;
166 counter->written = counter->failed = false;
167 counter->frame_write_ns = 0;
168}
169
171 if (!counter)
172 return false;
173 fps_counter_frame_begin(counter, frame_id && frame_id != counter->completed_frame_id);
174 counter->current_frame_id = frame_id;
175 return counter->measuring;
176}
177
179 if (counter && counter->measuring)
180 counter->write_start = time_get_ns();
181}
182
183void fps_counter_write_end(fps_counter_t *counter, bool complete) {
184 if (!counter || !counter->measuring)
185 return;
186 counter->frame_write_ns += time_get_ns() - counter->write_start;
187 counter->failed |= !complete;
188 counter->written |= complete;
189}
190
192 if (!counter)
193 return;
194 if (counter->measuring && counter->written && !counter->failed) {
195 fps_counter_tick_at(counter, now);
196 if (counter->current_frame_id)
197 counter->completed_frame_id = counter->current_frame_id;
198 counter->report_writes += counter->frame_write_ns;
199 counter->report_frames++;
200 }
201 counter->measuring = false;
202}
203
204void fps_counter_render(fps_counter_t *counter, int fd, int columns, bool redrawn) {
205 if (!counter)
206 return;
207 uint64_t now = time_get_ns();
208 float measured = fps_counter_get_at(counter, now);
209 // A UI overlay can repaint for longer than the media counter's inactivity
210 // window. Keep the last visible value during a forced overlay redraw so
211 // opening keyboard help does not make an otherwise-live stream display 0.
212 int value = measured >= 999.0f ? 999 : (int)(measured + 0.5f);
213 if (redrawn && value == 0 && counter->last_fps > 0)
214 value = counter->last_fps;
215 // `FPS:ddd/ddd` is ten columns wide. Start nine columns before the
216 // terminal's rightmost column so the complete overlay stays on one line.
217 if (counter->visible && columns >= 10 && (redrawn || value != counter->last_fps)) {
218 // Restore the cursor so the right-margin overlay cannot wrap the next write.
219 const char *style = terminal_has_dark_background() ? "\033[97;40m" : "\033[30;47m";
220 if (ui_output_printf(fd, "\0337\033[1;%dH%sFPS:%3d/%u\033[0m\0338", columns - 9, style, value,
221 counter->effective_fps) == ASCIICHAT_OK)
222 counter->last_fps = value;
223 }
224 if (now - counter->report_start >= 3 * NS_PER_SEC_INT) {
225 if (counter->report_frames)
226 log_debug("FPS_OUTPUT: frames=%llu elapsed_ms=%.3f write_ms=%.3f", (unsigned long long)counter->report_frames,
227 (double)(now - counter->report_start) / NS_PER_MS_INT, (double)counter->report_writes / NS_PER_MS_INT);
228 counter->report_start = now;
229 counter->report_writes = counter->report_frames = 0;
230 }
231}
⚙️ Common definitions, error codes, macros, and types shared throughout the application
bool fps_counter_frame_begin_id(fps_counter_t *counter, uint64_t frame_id)
#define FPS_WINDOW_SIZE
Number of frames to average over.
Definition fps_counter.c:17
⏱️ FPS counter for measuring output write throughput
#define SAFE_FREE(ptr)
Definition common.h:377
#define SAFE_CALLOC(count, size, cast)
Definition common.h:275
unsigned long long uint64_t
Definition common.h:63
@ ASCIICHAT_OK
Definition error_codes.h:46
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:595
#define NS_PER_SEC_INT
Definition time.h:169
#define NS_PER_MS_INT
Definition time.h:168
void fps_counter_reset(fps_counter_t *counter)
Definition fps_counter.c:82
void fps_counter_frame_begin(fps_counter_t *counter, bool measure)
void fps_counter_frame_end(fps_counter_t *counter, uint64_t now)
void fps_counter_write_begin(fps_counter_t *counter)
float fps_counter_get(fps_counter_t *counter)
Get the current FPS value.
void fps_counter_tick(fps_counter_t *counter)
Record a frame timestamp.
Definition fps_counter.c:78
void fps_counter_set_effective_fps(fps_counter_t *counter, unsigned int fps)
float fps_counter_get_at(fps_counter_t *counter, uint64_t now)
bool fps_counter_set_visible(fps_counter_t *counter, bool visible)
void fps_counter_destroy(fps_counter_t *counter)
Destroy FPS counter and free resources.
Definition fps_counter.c:68
void fps_counter_tick_at(fps_counter_t *counter, uint64_t now)
Definition fps_counter.c:89
void fps_counter_render(fps_counter_t *counter, int fd, int columns, bool redrawn)
void fps_counter_write_end(fps_counter_t *counter, bool complete)
struct fps_counter_s fps_counter_t
Opaque FPS counter handle.
Definition fps_counter.h:40
fps_counter_t * fps_counter_create(void)
Create a new FPS counter.
Definition fps_counter.c:50
asciichat_error_t ui_output_printf(int fd, const char *format,...)
Definition output.c:26
🔤 String Manipulation and Shell Escaping Utilities
Per-renderer timing state for the on-screen FPS overlay.
Definition fps_counter.c:28
uint64_t frame_write_ns
Definition fps_counter.c:37
uint64_t report_start
Definition fps_counter.c:38
int count
Number of valid entries in buffer (0 to FPS_WINDOW_SIZE).
Definition fps_counter.c:43
uint64_t completed_frame_id
Definition fps_counter.c:35
uint64_t write_start
Definition fps_counter.c:36
uint64_t report_writes
Definition fps_counter.c:39
int head
Current write position (0 to FPS_WINDOW_SIZE-1).
Definition fps_counter.c:42
uint64_t frame_times[30]
Circular buffer of timestamps (ns).
Definition fps_counter.c:41
uint64_t report_frames
Definition fps_counter.c:40
uint64_t current_frame_id
Definition fps_counter.c:35
unsigned int effective_fps
Definition fps_counter.c:30