Helios Engine
A modular ECS based data-oriented C++23 game engine framework
Loading...
Searching...
No Matches
profiler.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <helios/assert.hpp>
9#include <helios/profile/details/memory_dispatch.hpp>
11
12#include <cstddef>
13#include <cstdint>
14#include <memory>
15#include <optional>
16#include <source_location>
17#include <span>
18#include <string_view>
19
20namespace helios::profile {
21
22/**
23 * @brief Global profiler registry and multi-backend fan-out dispatcher.
24 * @details Backends are registered before `Finalize()` and keyed by concrete
25 * type via `MultiTypeMap`.
26 */
27class Profiler {
28public:
29 /**
30 * @brief Returns the singleton profiler instance.
31 * @return Reference to the profiler
32 */
33 [[nodiscard]] static Profiler& Instance() noexcept;
34
35 /**
36 * @brief Clears all registered backends and resets finalization state.
37 * @warning Not thread-safe. Call only when no zones are active.
38 */
39 void Clear() noexcept;
40
41 /**
42 * @brief Partitions zone storage and marks the profiler ready for use.
43 * @details Backends must be explicitly registered via `AddBackend()`
44 * before calling `Finalize()`. No backend is added automatically.
45 * @warning Not thread-safe.
46 * Call before worker threads start.
47 */
48 void Finalize() noexcept;
49
50 /**
51 * @brief Calls `Startup()` on all registered backends.
52 * @warning Not thread-safe.
53 * Call only during single-threaded startup.
54 */
55 void Startup() noexcept;
56
57 /**
58 * @brief Calls `Shutdown()` on all registered backends.
59 * @warning Not thread-safe.
60 * Call only during single-threaded startup.
61 */
62 void Shutdown() noexcept;
63
64 /**
65 * @brief Registers and owns a backend of type `T`.
66 * @warning Not thread-safe.
67 * Call only during single-threaded startup.
68 * @tparam T Concrete backend type
69 * @tparam Args Constructor argument types
70 * @param args Arguments forwarded to `T`'s constructor
71 * @return Reference to the registered backend
72 */
73 template <ProfilerBackendTrait T, typename... Args>
74 T& AddBackend(Args&&... args) noexcept;
75
76 /**
77 * @brief Removes a backend by type.
78 * @warning Not thread-safe.
79 * Triggers assertion if finalize has been called yet.
80 * Call only when no zones are active.
81 * @tparam T Concrete backend type
82 */
83 template <ProfilerBackendTrait T>
84 void RemoveBackend() noexcept;
85
86 /**
87 * @brief Removes a backend by type index.
88 * @warning Not thread-safe.
89 * Triggers assertion if finalize has been called yet.
90 * Call only when no zones are active.
91 * @param index Backend type index
92 */
93 void RemoveBackend(BackendTypeIndex index) noexcept;
94
95 /**
96 * @brief Gets a backend by type.
97 * @warning Not thread-safe.
98 * Triggers assertion if the backend is not registered.
99 * @tparam T Concrete backend type
100 * @return Reference to the backend
101 */
102 template <ProfilerBackendTrait T>
103 [[nodiscard]] T& Get() noexcept {
104 return static_cast<T&>(*backends_.Get<T>().backend);
105 }
106
107 /**
108 * @brief Gets a backend by type (const).
109 * @warning Not thread-safe.
110 * Triggers assertion if the backend is not registered.
111 * @tparam T Concrete backend type
112 * @return Const reference to the backend
113 */
114 template <ProfilerBackendTrait T>
115 [[nodiscard]] const T& Get() const noexcept {
116 return static_cast<const T&>(*backends_.Get<T>().backend);
117 }
118
119 /**
120 * @brief Gets a backend by type index.
121 * @warning Not thread-safe.
122 * Triggers assertion if the backend is not registered.
123 * @param index Backend type index
124 * @return Reference to the backend
125 */
126 [[nodiscard]] Backend& Get(BackendTypeIndex index) noexcept;
127
128 /**
129 * @brief Gets a backend by type index (const).
130 * @warning Not thread-safe.
131 * Triggers assertion if the backend is not registered.
132 * @param index Backend type index
133 * @return Const reference to the backend
134 */
135 [[nodiscard]] const Backend& Get(BackendTypeIndex index) const noexcept;
136
137 /**
138 * @brief Tries to get a backend by type.
139 * @warning Not thread-safe.
140 * @tparam T Concrete backend type
141 * @return Pointer to the backend, or `nullptr` if not registered
142 */
143 template <ProfilerBackendTrait T>
144 [[nodiscard]] T* TryGet() noexcept;
145
146 /**
147 * @brief Tries to get a backend by type (const).
148 * @warning Not thread-safe.
149 * @tparam T Concrete backend type
150 * @return Const pointer to the backend, or `nullptr` if not registered
151 */
152 template <ProfilerBackendTrait T>
153 [[nodiscard]] const T* TryGet() const noexcept;
154
155 /**
156 * @brief Tries to get a backend by type index.
157 * @warning Not thread-safe.
158 * @param index Backend type index
159 * @return Pointer to the backend, or `nullptr` if not registered
160 */
161 [[nodiscard]] Backend* TryGet(BackendTypeIndex index) noexcept;
162
163 /**
164 * @brief Tries to get a backend by type index (const).
165 * @warning Not thread-safe.
166 * @param index Backend type index
167 * @return Const pointer to the backend, or `nullptr` if not registered
168 */
169 [[nodiscard]] const Backend* TryGet(BackendTypeIndex index) const noexcept;
170
171 /**
172 * @brief Checks whether a backend type is registered.
173 * @warning Not thread-safe.
174 * @tparam T Concrete backend type
175 * @return True if registered
176 */
177 template <ProfilerBackendTrait T>
178 [[nodiscard]] bool Contains() const noexcept {
179 return backends_.Contains<T>();
180 }
181
182 /**
183 * @brief Checks whether a backend type index is registered.
184 * @warning Not thread-safe.
185 * @param index Backend type index
186 * @return True if registered
187 */
188 [[nodiscard]] bool Contains(BackendTypeIndex index) const noexcept {
189 return backends_.Contains(index);
190 }
191
192 /**
193 * @brief Returns whether the profiler has been finalized.
194 * @warning Not thread-safe.
195 * @return True after `Finalize()`
196 */
197 [[nodiscard]] bool Finalized() const noexcept { return finalized_; }
198
199 /**
200 * @brief Returns the zone storage byte offset for backend type `T`.
201 * @warning Not thread-safe.
202 * Triggers assertion if the backend is not registered or not finalized.
203 * @tparam T Concrete backend type
204 * @return Storage offset in bytes
205 */
206 template <ProfilerBackendTrait T>
207 [[nodiscard]] size_t StorageOffset() const noexcept {
208 return backends_.Get<T>().storage_offset;
209 }
210
211 /**
212 * @brief Returns the number of registered backends.
213 * @warning Not thread-safe.
214 * @return Backend count
215 */
216 [[nodiscard]] size_t BackendCount() const noexcept {
217 return backends_.TypeCount();
218 }
219
220 /**
221 * @brief Dispatches a zone begin event to all registered backends.
222 * @details Thread-safe after `Finalize()` when backends honor the dispatch
223 * contract.
224 * @param spec Zone specification
225 * @param storage Contiguous storage for backend-specific zone data
226 */
227 void BeginZone(const ZoneSpec& spec, std::span<std::byte> storage) noexcept;
228
229 /**
230 * @brief Dispatches a zone end event to all registered backends.
231 * @details Thread-safe after `Finalize()` when backends honor the dispatch
232 * contract.
233 * @param storage Contiguous storage for backend-specific zone data
234 */
235 void EndZone(std::span<std::byte> storage) noexcept;
236
237 /**
238 * @brief Dispatches a zone text event to all registered backends.
239 * @details Thread-safe after `Finalize()` when backends honor the dispatch
240 * contract.
241 * @param storage Contiguous storage for backend-specific zone data
242 * @param text Text to attach to the zone
243 */
244 void ZoneText(std::span<std::byte> storage, std::string_view text) noexcept;
245
246 /**
247 * @brief Dispatches a zone value event to all registered backends.
248 * @details Thread-safe after `Finalize()` when backends honor the dispatch
249 * contract.
250 * @param storage Contiguous storage for backend-specific zone data
251 * @param value Numeric value to attach to the zone
252 */
253 void ZoneValue(std::span<std::byte> storage, uint64_t value) noexcept;
254
255 /**
256 * @brief Dispatches a zone name event to all registered backends.
257 * @details Thread-safe after `Finalize()` when backends honor the dispatch
258 * contract.
259 * @param storage Contiguous storage for backend-specific zone data
260 * @param name Name to attach to the zone
261 */
262 void ZoneName(std::span<std::byte> storage, std::string_view name) noexcept;
263
264 /**
265 * @brief Dispatches a frame mark event to all registered backends.
266 * @details Thread-safe after `Finalize()` when backends honor the dispatch
267 * contract.
268 */
269 void FrameMark() noexcept;
270
271 /**
272 * @brief Dispatches a frame mark event with a name to all registered
273 * backends.
274 * @details Thread-safe after `Finalize()` when backends honor the dispatch
275 * contract.
276 * @param name Name of the frame mark
277 */
278 void FrameMark(CStringView name) noexcept;
279
280 /**
281 * @brief Dispatches a frame mark start event to all registered backends.
282 * @details Thread-safe after `Finalize()` when backends honor the dispatch
283 * contract.
284 * @param name Name of the frame mark
285 */
286 void FrameMarkStart(CStringView name) noexcept;
287
288 /**
289 * @brief Dispatches a frame mark end event to all registered backends.
290 * @details Thread-safe after `Finalize()` when backends honor the dispatch
291 * contract.
292 * @param name Name of the frame mark
293 */
294 void FrameMarkEnd(CStringView name) noexcept;
295
296 /**
297 * @brief Dispatches a message event to all registered backends.
298 * @details Thread-safe after `Finalize()` when backends honor the dispatch
299 * contract.
300 * @param text Text of the message
301 * @param color Color of the message
302 */
303 void Message(std::string_view text, uint32_t color) noexcept;
304
305 /**
306 * @brief Dispatches a thread name event to all registered backends.
307 * @details Thread-safe after `Finalize()` when backends honor the dispatch
308 * contract.
309 * @param name Name of the thread
310 */
311 void SetThreadName(CStringView name) noexcept;
312
313 /**
314 * @brief Dispatches a plot event to all registered backends.
315 * @details Thread-safe after `Finalize()` when backends honor the dispatch
316 * contract.
317 * @param name Name of the plot
318 * @param value Value to plot
319 */
320 void Plot(CStringView name, double value) noexcept;
321
322 /**
323 * @brief Dispatches a plot configuration event to all registered backends.
324 * @details Thread-safe after `Finalize()` when backends honor the dispatch
325 * contract.
326 * @param name Name of the plot
327 * @param type Plot format type
328 * @param step Whether to use step interpolation
329 * @param fill Whether to fill the area under the plot line
330 * @param color Color of the plot
331 */
332 void PlotConfig(CStringView name, PlotFormat type, bool step, bool fill,
333 uint32_t color) noexcept;
334
335 /**
336 * @brief Dispatches an allocation event to all registered backends.
337 * @details Thread-safe after `Finalize()` when backends honor the dispatch
338 * contract.
339 * @param ptr Pointer to the allocated memory
340 * @param size Size of the allocated memory in bytes
341 * @param name Optional name of the allocation
342 * @param depth Call stack depth of the allocation event
343 * @param loc Source location of the allocation event
344 */
345 void Alloc(const void* ptr, size_t size, std::optional<CStringView> name,
346 int depth, std::source_location loc) noexcept;
347
348 /**
349 * @brief Dispatches a deallocation event to all registered backends.
350 * @details Thread-safe after `Finalize()` when backends honor the dispatch
351 * contract.
352 * @param ptr Pointer to the deallocated memory
353 * @param name Optional name of the deallocation
354 * @param depth Call stack depth of the deallocation event
355 * @param loc Source location of the deallocation event
356 */
357 void Free(const void* ptr, std::optional<CStringView> name, int depth,
358 std::source_location loc) noexcept;
359
360 /**
361 * @brief Dispatches a memory discard event to all registered backends.
362 * @details Thread-safe after `Finalize()` when backends honor the dispatch
363 * contract.
364 * @param name Optional name of the discard event
365 */
366 void MemoryDiscard(CStringView name) noexcept;
367
368 /**
369 * @brief Dispatches a memory discard event to all registered backends.
370 * @details Thread-safe after `Finalize()` when backends honor the dispatch
371 * contract.
372 * @param name Optional name of the discard event
373 * @param depth Call stack depth of the discard event
374 */
375 void MemoryDiscard(CStringView name, int depth) noexcept;
376
377private:
378 /// @brief Owned backend slot stored in the profiler registry.
379 struct BackendEntry {
380 std::unique_ptr<Backend> backend;
381 size_t storage_offset = 0;
382 size_t storage_size = 0;
383 };
384
385 using BackendMap = container::MultiTypeMap<BackendEntry>;
386
387 Profiler() noexcept = default;
388 ~Profiler() noexcept;
389
390 [[nodiscard]] BackendEntry* FindEntry(BackendTypeIndex index) noexcept {
391 return backends_.TryGet(index);
392 }
393
394 [[nodiscard]] const BackendEntry* FindEntry(
395 BackendTypeIndex index) const noexcept {
396 return backends_.TryGet(index);
397 }
398
399 BackendMap backends_;
400 bool finalized_ = false;
401};
402
403inline Profiler& Profiler::Instance() noexcept {
404 static Profiler instance;
405 return instance;
406}
407
408inline void Profiler::Clear() noexcept {
409 details::ScopedMemoryDispatchSuspend suspend;
410 backends_.ResetAll();
411 finalized_ = false;
413}
414
415inline void Profiler::RemoveBackend(BackendTypeIndex index) noexcept {
416 HELIOS_ASSERT(!finalized_,
417 "Cannot remove profiling backends after Finalize()!");
418 details::ScopedMemoryDispatchSuspend suspend;
419 backends_.Remove(index);
420}
421
422template <ProfilerBackendTrait T, typename... Args>
423inline T& Profiler::AddBackend(Args&&... args) noexcept {
424 HELIOS_ASSERT(!finalized_, "Cannot add profiling backends after Finalize()!");
425 details::ScopedMemoryDispatchSuspend suspend;
426 const auto [it, inserted] = backends_.TryEmplace<T>(
427 BackendEntry{std::make_unique<T>(std::forward<Args>(args)...), 0});
428 HELIOS_ASSERT(inserted, "Backend type '{}' already registered!",
430 return static_cast<T&>(*it->second.backend);
431}
432
433template <ProfilerBackendTrait T>
437
438inline Backend& Profiler::Get(BackendTypeIndex index) noexcept {
439 BackendEntry* const entry = backends_.TryGet(index);
440 HELIOS_ASSERT(entry != nullptr, "Backend not found!");
441 return *entry->backend;
442}
443
444inline const Backend& Profiler::Get(BackendTypeIndex index) const noexcept {
445 const BackendEntry* const entry = backends_.TryGet(index);
446 HELIOS_ASSERT(entry != nullptr, "Backend not found!");
447 return *entry->backend;
448}
449
451 const BackendEntry* const entry = FindEntry(index);
452 return entry != nullptr ? entry->backend.get() : nullptr;
453}
454
455inline const Backend* Profiler::TryGet(BackendTypeIndex index) const noexcept {
456 const BackendEntry* const entry = FindEntry(index);
457 return entry != nullptr ? entry->backend.get() : nullptr;
458}
459
460template <ProfilerBackendTrait T>
461inline T* Profiler::TryGet() noexcept {
462 const BackendEntry* const entry = backends_.TryGet<T>();
463 return entry != nullptr ? static_cast<T*>(entry->backend.get()) : nullptr;
464}
465
466template <ProfilerBackendTrait T>
467inline const T* Profiler::TryGet() const noexcept {
468 const BackendEntry* const entry = backends_.TryGet<T>();
469 return entry != nullptr ? static_cast<const T*>(entry->backend.get())
470 : nullptr;
471}
472
473namespace details {
474
475/**
476 * @brief Returns the zone storage byte offset assigned to backend type `T`.
477 * @details Valid after `Finalize()`. For testing and diagnostics only.
478 * @tparam T Concrete backend type
479 * @return Storage offset in bytes
480 */
481template <ProfilerBackendTrait T>
482[[nodiscard]] inline size_t BackendStorageOffset() noexcept {
483 return Profiler::Instance().StorageOffset<T>();
484}
485
486/**
487 * @brief Returns the number of active backend slots after finalization.
488 * @return Number of registered backends
489 */
490[[nodiscard]] inline size_t ActiveBackendCount() noexcept {
492}
493
494} // namespace details
495
496} // namespace helios::profile
#define HELIOS_ASSERT(condition,...)
Assertion macro that aborts execution in debug builds.
Definition assert.hpp:259
Generic type-indexed map that stores one Storage instance per registered type key.
constexpr Storage * TryGet() noexcept
Tries to get Storage for type T.
Abstract profiler backend interface.
Definition backend.hpp:30
void ZoneText(std::span< std::byte > storage, std::string_view text) noexcept
Dispatches a zone text event to all registered backends.
Definition profiler.cpp:145
bool Contains() const noexcept
Checks whether a backend type is registered.
Definition profiler.hpp:178
bool Contains(BackendTypeIndex index) const noexcept
Checks whether a backend type index is registered.
Definition profiler.hpp:188
void Startup() noexcept
Calls Startup() on all registered backends.
Definition profiler.cpp:76
void Clear() noexcept
Clears all registered backends and resets finalization state.
Definition profiler.hpp:408
static Profiler & Instance() noexcept
Returns the singleton profiler instance.
Definition profiler.hpp:403
void RemoveBackend() noexcept
Removes a backend by type.
Definition profiler.hpp:434
bool Finalized() const noexcept
Returns whether the profiler has been finalized.
Definition profiler.hpp:197
const T & Get() const noexcept
Gets a backend by type (const).
Definition profiler.hpp:115
void FrameMark() noexcept
Dispatches a frame mark event to all registered backends.
Definition profiler.cpp:181
void EndZone(std::span< std::byte > storage) noexcept
Dispatches a zone end event to all registered backends.
Definition profiler.cpp:134
T & AddBackend(Args &&... args) noexcept
Registers and owns a backend of type T.
Definition profiler.hpp:423
T * TryGet() noexcept
Tries to get a backend by type.
Definition profiler.hpp:461
void Finalize() noexcept
Partitions zone storage and marks the profiler ready for use.
Definition profiler.cpp:96
void Shutdown() noexcept
Calls Shutdown() on all registered backends.
Definition profiler.cpp:86
T & Get() noexcept
Gets a backend by type.
Definition profiler.hpp:103
size_t StorageOffset() const noexcept
Returns the zone storage byte offset for backend type T.
Definition profiler.hpp:207
void ZoneName(std::span< std::byte > storage, std::string_view name) noexcept
Dispatches a zone name event to all registered backends.
Definition profiler.cpp:169
void BeginZone(const ZoneSpec &spec, std::span< std::byte > storage) noexcept
Dispatches a zone begin event to all registered backends.
Definition profiler.cpp:122
size_t BackendCount() const noexcept
Returns the number of registered backends.
Definition profiler.hpp:216
void ZoneValue(std::span< std::byte > storage, uint64_t value) noexcept
Dispatches a zone value event to all registered backends.
Definition profiler.cpp:157
static constexpr TypeIndex From() noexcept
Constructs a TypeIndex from a type T.
Trait to identify concrete profiler backend types.
Definition backend.hpp:190
void ResetProfilerFinalized() noexcept
Definition profiler.cpp:58
size_t BackendStorageOffset() noexcept
Returns the zone storage byte offset assigned to backend type T.
Definition profiler.hpp:482
size_t ActiveBackendCount() noexcept
Returns the number of active backend slots after finalization.
Definition profiler.hpp:490
void Alloc(const void *ptr, size_t size, std::optional< CStringView > name, int depth, std::source_location loc=std::source_location::current()) noexcept
Profile an allocation of memory.
Definition memory.hpp:20
void Free(const void *ptr, std::optional< CStringView > name, int depth, std::source_location loc=std::source_location::current()) noexcept
Profile a deallocation of memory.
Definition memory.hpp:34
utils::TypeIndex BackendTypeIndex
Type alias for profiler backend type indices.
Definition backend.hpp:183
PlotFormat
Plot value format for timeline plots.
Definition common.hpp:10
void FrameMarkEnd(CStringView name) noexcept
Marks the end of a named frame region.
Definition frame.hpp:33
void SetThreadName(CStringView name) noexcept
Sets the name of the current thread on all active backends.
Definition message.hpp:24
void FrameMarkStart(CStringView name) noexcept
Marks the start of a named frame region.
Definition frame.hpp:25
void Message(std::string_view text, uint32_t color=0) noexcept
Emits a timeline message on all active backends.
Definition message.hpp:16
void PlotConfig(CStringView name, PlotFormat type, bool step, bool fill, uint32_t color) noexcept
Configures a plot channel on all active backends.
Definition plot.hpp:27
void Plot(CStringView name, double value) noexcept
Records a plot sample on all active backends.
Definition plot.hpp:15
void FrameMark() noexcept
Marks the end of the current frame on all active backends.
Definition frame.hpp:9
void MemoryDiscard(CStringView name) noexcept
Profile memory discard.
Definition memory.hpp:44
constexpr std::string_view TypeNameOf() noexcept
Retrieves the unqualified type name of T.
BasicCStringView< char > CStringView
A view of a null-terminated C string.
STL namespace.
Immutable zone description for a single instrumentation site.
Definition common.hpp:22