Skip to main content

FpsMetrics.ixx File

Module defining the FpsMetrics class for frame rate analysis and monitoring. More...

Included Headers

#include <deque> #include <numeric> #include <helios.engine.tooling.FrameStats>

Namespaces Index

namespacehelios
namespaceengine

Main engine module aggregating core infrastructure and game systems. More...

namespacetooling

Tooling utilities for diagnostics, metrics and developer overlays. More...

Classes Index

classFpsMetrics

Aggregates and analyzes frame timing data over a rolling window. More...

Description

Module defining the FpsMetrics class for frame rate analysis and monitoring.

File Listing

The file content with the documentation metadata removed is:

1/**
2 * @file FpsMetrics.ixx
3 * @brief Module defining the FpsMetrics class for frame rate analysis and monitoring.
4 */
5module;
6
7#include <deque>
8#include <numeric>
9
10export module helios.engine.tooling.FpsMetrics;
11
12import helios.engine.tooling.FrameStats;
13
14export namespace helios::engine::tooling {
15
16 /**
17 * @class FpsMetrics
18 * @brief Aggregates and analyzes frame timing data over a rolling window.
19 *
20 * FpsMetrics collects FrameStats data from multiple frames and calculates
21 * average FPS and frame times. It maintains a configurable history buffer
22 * for smoothing out momentary fluctuations in frame rate.
23 *
24 * This class is particularly useful for debugging, profiling, and displaying
25 * performance metrics in development tools or debug overlays.
26 *
27 * @par Usage Example:
28 * ```cpp
29 * helios::engine::tooling::FpsMetrics metrics;
30 * metrics.setHistorySize(120);
31 *
32 * // In game loop:
33 * helios::engine::tooling::FrameStats stats = framePacer.sync();
34 * metrics.addFrame(stats);
35 *
36 * float fps = metrics.getFps();
37 * float avgFrameTime = metrics.getFrameTimeMs();
38 * ```
39 */
40 class FpsMetrics {
41
42 /**
43 * @brief Stores a rolling history of frame statistics.
44 *
45 * `history_` holds the full timing data (work time, wait time, total frame time)
46 * for a specified number of recent frames. This buffer is used to compute
47 * average frame rate and frame time metrics over a configurable window.
48 *
49 * The use of a `std::deque` allows efficient addition and removal
50 * of frame timing records while maintaining the order of events.
51 */
52 std::deque<helios::engine::tooling::FrameStats> history_;
53
54 /**
55 * @brief Size of the history buffer for storing frame statistics.
56 *
57 * Determines the maximum number of frames for which the frame timing data
58 * is retained to calculate performance metrics such as average FPS and
59 * frame times. A larger history size smooths out short-term variations
60 * but may reduce responsiveness to sudden changes in performance.
61 *
62 * This variable is initialized to a default value of 60 but can be adjusted
63 * based on the application's requirements for performance analysis.
64 */
65 size_t historySize_ = 60;
66
67 /**
68 * @brief Stores the calculated average frames per second (FPS).
69 *
70 * The `avgFps_` variable holds the computed average FPS value based on the
71 * accumulated frame timing data in `history_`. It is recomputed lazily when
72 * one of the query functions (e.g. `getFps()`) is called after new frames
73 * have been added.
74 */
75 float avgFps_ = 0.0f;
76
77 /**
78 * @brief Stores the average frame time in milliseconds.
79 *
80 * Represents the total frame time (including work and wait) averaged over
81 * the frames currently stored in the history buffer. The value is stored
82 * in milliseconds and recomputed lazily when queried via the public getters
83 * after new frames have been added.
84 */
85 float avgFrameTime_ = 0.0f;
86
87 /**
88 * @brief Tracks the most recent frame's work time in milliseconds.
89 *
90 * Represents the duration of work performed during the last processed frame.
91 * This value is derived from the most recent `FrameStats` in the history and
92 * expressed in milliseconds. It is updated when the metrics are recomputed
93 * (lazy evaluation triggered by the getters).
94 */
95 float lastWorkTime_ = 0.0f;
96
97 /**
98 * @brief Tracks the idle time in milliseconds for the most recent frame.
99 *
100 * Represents the duration the system waited (idle time) during the last
101 * processed frame. This value is derived from the most recent `FrameStats`
102 * in the history and expressed in milliseconds. It is updated when the
103 * metrics are recomputed (lazy evaluation triggered by the getters).
104 */
105 float lastWaitTime_ = 0.0f;
106
107 /**
108 * @brief Tracks the total number of frames processed since initialization.
109 *
110 * The frameCount_ variable accumulates the count of frames that have been
111 * added via `addFrame()`. It starts at 0 and increments with each call,
112 * serving as a global counter independent of the current history size.
113 */
114 unsigned long long frameCount_ = 0;
115
116 /**
117 * @brief Indicates whether cached metrics need to be recomputed.
118 *
119 * Set to true whenever new frame data is added or the history changes in a
120 * way that invalidates the cached values. The next call to one of the public
121 * query functions will trigger a recomputation in `update()`.
122 */
123 bool needsUpdate_ = true;
124
125 /**
126 * @brief Recomputes cached metrics if needed.
127 *
128 * If `needsUpdate_` is true and the history is non-empty, this function
129 * updates `lastWorkTime_`, `lastWaitTime_`, `avgFrameTime_` and `avgFps_`
130 * based on the current contents of `history_` and then clears the flag.
131 *
132 * If the history is empty or the cache is already up to date, this function
133 * returns immediately without modifying any values.
134 *
135 * @note This function is safe to call in hot paths and does not throw under
136 * normal conditions; it is therefore marked `noexcept`.
137 */
138 void update() noexcept {
139 if (!needsUpdate_ || history_.empty()) {
140 return;
141 }
142
143 const auto& stats = history_.back();
144
145 lastWorkTime_ = stats.workTime * 1000.0f;
146 lastWaitTime_ = stats.waitTime * 1000.0f;
147
148 float sumMs = 0.0f;
149 for (const auto& s : history_) {
150 sumMs += s.totalFrameTime * 1000.0f;
151 }
152
153 avgFrameTime_ = sumMs / static_cast<float>(history_.size());
154 avgFps_ = (avgFrameTime_ > 0.0001f) ? (1000.0f / avgFrameTime_) : 0.0f;
155
156 needsUpdate_ = false;
157 }
158
159 public:
160
161 /**
162 * @brief Adds a frame's statistics to the metrics history.
163 *
164 * Processes the provided FrameStats, updates the rolling history,
165 * and marks cached values as dirty so they will be recomputed on
166 * the next query. Old frames are automatically removed when the
167 * history buffer exceeds the configured size. Additionally, the
168 * `frameCount_` member is increased by one for each call.
169 *
170 * @param stats Frame statistics to add to the history.
171 *
172 * @note When used together with FramePacer, a typical usage pattern is:
173 * @code
174 * helios::engine::tooling::FrameStats stats = framePacer.sync();
175 * metrics.addFrame(stats);
176 * @endcode
177 * This should usually be called once per frame at the end of the
178 * frame loop, after timing information for the current frame has
179 * been measured.
180 */
182 needsUpdate_ = true;
183
184 history_.push_back(stats);
185 if (history_.size() > historySize_) {
186 history_.pop_front();
187 }
188
189 frameCount_++;
190 }
191
192 /**
193 * @brief Sets the size of the frame history buffer.
194 *
195 * Determines how many frames are used for calculating averages.
196 * Larger values produce smoother metrics but respond slower to changes.
197 *
198 * @param size Number of frames to keep in history (default: 60).
199 *
200 * @note Common values: 30 for roughly half-second average at 60 FPS,
201 * 60 for roughly one-second average at 60 FPS.
202 */
203 void setHistorySize(size_t size) {
204 historySize_ = size;
205 while (history_.size() > historySize_) {
206 history_.pop_front();
207 }
208 needsUpdate_ = true;
209 }
210
211 /**
212 * @brief Gets the current history buffer size.
213 *
214 * @return Maximum number of frames kept in history.
215 */
216 [[nodiscard]] size_t getHistorySize() const noexcept {
217 return historySize_;
218 }
219
220 /**
221 * @brief Gets the average frames per second.
222 *
223 * Triggers a lazy recomputation of the underlying metrics if
224 * new frames have been added since the last query.
225 *
226 * @return Average FPS calculated over the frames stored in the history buffer.
227 */
228 [[nodiscard]] float getFps() noexcept {
229 update();
230 return avgFps_;
231 }
232
233 /**
234 * @brief Gets the average frame time in milliseconds.
235 *
236 * Triggers a lazy recomputation of the underlying metrics if needed.
237 *
238 * @return Average total frame time (work + wait) in milliseconds.
239 */
240 [[nodiscard]] float getFrameTimeMs() noexcept {
241 update();
242 return avgFrameTime_;
243 }
244
245 /**
246 * @brief Gets the most recent frame's work time.
247 *
248 * Triggers a lazy recomputation if required.
249 *
250 * @return Work time of the last processed frame in milliseconds.
251 */
252 [[nodiscard]] float getWorkTimeMs() noexcept {
253 update();
254 return lastWorkTime_;
255 }
256
257 /**
258 * @brief Gets the most recent frame's idle time.
259 *
260 * Triggers a lazy recomputation if required.
261 *
262 * @return Wait/idle time of the last processed frame in milliseconds.
263 */
264 [[nodiscard]] float getIdleTimeMs() noexcept {
265 update();
266 return lastWaitTime_;
267 }
268
269 /**
270 * @brief Gets the frame count.
271 *
272 * @return The total number of frames that have been added via `addFrame()`.
273 */
274 [[nodiscard]] unsigned long long getFrameCount() const noexcept {
275 return frameCount_;
276 }
277
278 /**
279 * @brief Gets the average frame time in seconds.
280 *
281 * Convenience wrapper returning the same value as `getFrameTimeMs()`
282 * converted from milliseconds to seconds.
283 *
284 * @return Average total frame time (work + wait) in seconds.
285 */
286 [[nodiscard]] float getFrameTimeSeconds() noexcept {
287 update();
288 return avgFrameTime_ * 0.001f;
289 }
290
291 /**
292 * @brief Gets the most recent frame's work time in seconds.
293 *
294 * Convenience wrapper returning the same value as `getWorkTimeMs()`
295 * converted from milliseconds to seconds.
296 *
297 * @return Work time of the last processed frame in seconds.
298 */
299 [[nodiscard]] float getWorkTimeSeconds() noexcept {
300 update();
301 return lastWorkTime_ * 0.001f;
302 }
303
304 /**
305 * @brief Gets the most recent frame's idle time in seconds.
306 *
307 * Convenience wrapper returning the same value as `getIdleTimeMs()`
308 * converted from milliseconds to seconds.
309 *
310 * @return Wait/idle time of the last processed frame in seconds.
311 */
312 [[nodiscard]] float getIdleTimeSeconds() noexcept {
313 update();
314 return lastWaitTime_ * 0.001f;
315 }
316
317 /**
318 * @brief Gets the complete frame history.
319 *
320 * @return Const reference to the deque containing the full frame
321 * statistics history.
322 *
323 * @note Useful for rendering frame time graphs or diagnostic views
324 * in debug overlays.
325 */
326 [[nodiscard]] const std::deque<helios::engine::tooling::FrameStats>& getHistory() const noexcept {
327 return history_;
328 }
329
330 /**
331 * @brief Clears all collected metrics and resets to initial state.
332 *
333 * Removes all stored frame statistics and resets cached metrics and
334 * counters to their default values. After calling this function,
335 * all query functions will report zeros until new frames are added.
336 */
337 void reset() {
338 history_.clear();
339 avgFps_ = 0.0f;
340 avgFrameTime_ = 0.0f;
341 lastWorkTime_ = 0.0f;
342 lastWaitTime_ = 0.0f;
343 frameCount_ = 0;
344 needsUpdate_ = false;
345 }
346 };
347}

Generated via doxygen2docusaurus 2.0.0 by Doxygen 1.15.0.