MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
LiveArena.hpp
Go to the documentation of this file.
1#pragma once
2
4
5namespace MayaFlux {
6
7namespace internal {
8
9 /**
10 * @brief Turns on exposure for the MF_LIVE_EXPOSE macros in the linked library.
11 *
12 * Called at load time by any translation unit compiled with MAYAFLUX_LIVE.
13 * Library code such as the Creator factories cannot see the caller's define,
14 * so they consult this flag instead. There is no way to turn it off again.
15 */
16 MAYAFLUX_API void enable_live_exposure() noexcept;
17
18 /**
19 * @brief Reports whether live exposure has been enabled.
20 *
21 * @return true once any translation unit compiled with MAYAFLUX_LIVE has
22 * loaded, false otherwise.
23 */
24 MAYAFLUX_API bool live_exposure_enabled() noexcept;
25
26 /**
27 * @brief Maximum number of objects that can be registered in the live arena.
28 */
29 inline constexpr std::size_t LIVE_ARENA_MAX_ENTRIES = 256;
30
31 /**
32 * @brief Maximum length of a live arena key, including null terminator.
33 */
34 inline constexpr std::size_t LIVE_ARENA_KEY_MAX = 64;
35
36 /**
37 * @brief Default capacity of the live arena in bytes.
38 */
39 inline constexpr std::size_t LIVE_ARENA_CAPACITY = static_cast<const std::size_t>(16 * 1024 * 1024);
40
41 // =========================================================================
42
43 /**
44 * @brief Single directory entry mapping a string key to a location within the arena.
45 *
46 * offset == UINT32_MAX indicates an externally owned object whose pointer
47 * is stored in g_live_arena_shared_ptrs at the same index.
48 */
51 uint32_t offset;
52 uint32_t size;
54 };
55
56 /**
57 * @brief POD header block placed at the start of the arena storage.
58 *
59 * The directory array lives here. All offsets in LiveArenaEntry are
60 * relative to the first byte of object storage that follows this header.
61 */
66
67 // =========================================================================
68
69 /**
70 * @brief Raw backing storage for the live arena with external linkage.
71 *
72 * Exported so the JIT can locate it via DynamicLibrarySearchGenerator.
73 * The first sizeof(LiveArenaHeader) bytes are the directory.
74 * Object storage begins immediately after.
75 */
76 alignas(std::max_align_t) extern MAYAFLUX_API unsigned char g_live_arena_storage[LIVE_ARENA_CAPACITY];
77
78 /**
79 * @brief Byte offset of the bump pointer from the start of the object region.
80 */
81 extern MAYAFLUX_API uint32_t g_live_arena_bump;
82
83 /**
84 * @brief Shared ownership table for objects registered via expose_live.
85 *
86 * Stores shared_ptr<void> so live_cast can reconstruct a typed shared_ptr<T>
87 * via static_pointer_cast. Null for bump-allocated entries.
88 */
89 extern MAYAFLUX_API std::shared_ptr<void> g_live_arena_shared_ptrs[LIVE_ARENA_MAX_ENTRIES];
90
91 /**
92 * @brief Whether the MF_LIVE_EXPOSE macros publish objects to the arena.
93 *
94 * Set through enable_live_exposure() and read through live_exposure_enabled().
95 */
96 extern MAYAFLUX_API std::atomic<bool> g_live_exposure_enabled;
97
98 // =========================================================================
99
100 /**
101 * @brief Returns a pointer to the header block at the start of arena storage.
102 */
104 {
105 return reinterpret_cast<LiveArenaHeader*>(g_live_arena_storage);
106 }
107
108 /**
109 * @brief Returns a pointer to the object region (immediately after the header).
110 */
111 inline unsigned char* live_arena_object_region() noexcept
112 {
113 return g_live_arena_storage + sizeof(LiveArenaHeader);
114 }
115
116 /**
117 * @brief Aligns @p value up to the next multiple of @p align.
118 */
119 inline uint32_t align_up(uint32_t value, uint32_t align) noexcept
120 {
121 return (value + align - 1U) & ~(align - 1U);
122 }
123
124 /**
125 * @brief Locates an existing directory entry by key.
126 * @return Pointer to the matching entry, or nullptr if not found.
127 */
128 inline LiveArenaEntry* live_arena_find(const char* key) noexcept
129 {
130 auto* hdr = live_arena_header();
131 for (uint32_t i = 0; i < hdr->entry_count; ++i) {
132 if (hdr->entries[i].occupied
133 && std::strncmp(hdr->entries[i].key, key, LIVE_ARENA_KEY_MAX) == 0) {
134 return &hdr->entries[i];
135 }
136 }
137 return nullptr;
138 }
139
140 /**
141 * @brief Allocates @p size bytes from the arena with @p alignment.
142 *
143 * Writes a directory entry under @p key. Returns nullptr if the arena
144 * is full, the key is already registered, or capacity is exceeded.
145 */
146 inline void* live_arena_alloc(const char* key, uint32_t size, uint32_t alignment) noexcept
147 {
148 auto* hdr = live_arena_header();
149
150 if (hdr->entry_count >= LIVE_ARENA_MAX_ENTRIES) {
151 return nullptr;
152 }
153
154 if (live_arena_find(key)) {
155 return nullptr;
156 }
157
158 const auto object_region_capacity = static_cast<uint32_t>(LIVE_ARENA_CAPACITY - sizeof(LiveArenaHeader));
159
160 const uint32_t aligned_bump = align_up(g_live_arena_bump, alignment);
161
162 if (aligned_bump + size > object_region_capacity) {
163 return nullptr;
164 }
165
166 const uint32_t idx = hdr->entry_count;
167 auto& entry = hdr->entries[idx];
168 std::strncpy(entry.key, key, LIVE_ARENA_KEY_MAX - 1);
169 entry.key[LIVE_ARENA_KEY_MAX - 1] = '\0';
170 entry.offset = aligned_bump;
171 entry.size = size;
172 entry.occupied = true;
173
174 g_live_arena_bump = aligned_bump + size;
175 ++hdr->entry_count;
176
177 return live_arena_object_region() + aligned_bump;
178 }
179
180 /**
181 * @brief Registers an externally owned shared_ptr under @p key.
182 *
183 * Stores shared_ptr<void> in g_live_arena_shared_ptrs at the entry index.
184 * offset is set to UINT32_MAX to distinguish from bump-allocated entries.
185 *
186 * @return true on success, false if the key is already registered or
187 * the directory is full.
188 */
189 inline bool live_arena_expose(const char* key, std::shared_ptr<void> shared) noexcept
190 {
191 auto* hdr = live_arena_header();
192
193 if (hdr->entry_count >= LIVE_ARENA_MAX_ENTRIES) {
194 return false;
195 }
196
197 if (live_arena_find(key)) {
198 return false;
199 }
200
201 const uint32_t idx = hdr->entry_count;
202 auto& entry = hdr->entries[idx];
203 std::strncpy(entry.key, key, LIVE_ARENA_KEY_MAX - 1);
204 entry.key[LIVE_ARENA_KEY_MAX - 1] = '\0';
205 entry.offset = UINT32_MAX;
206 entry.size = UINT32_MAX;
207 entry.occupied = true;
208
209 g_live_arena_shared_ptrs[idx] = std::move(shared);
210
211 ++hdr->entry_count;
212 return true;
213 }
214
215 /**
216 * @brief Extracts the unqualified type name from a compiler-generated function signature.
217 *
218 * Parses __PRETTY_FUNCTION__ to extract the final component of a fully qualified
219 * type name. Used by MF_LIVE_EXPOSE_AUTO to derive stable per-type key prefixes
220 * without requiring the caller to supply a string literal.
221 *
222 * @tparam T Type to name.
223 * @return string_view into static storage valid for the process lifetime.
224 */
225 template <typename T>
226 constexpr std::string_view live_type_name() noexcept
227 {
228 return ::MayaFlux::Reflect::short_type_name<T>();
229 }
230
231 /**
232 * @brief Formats a live arena key as "TypeName_N" into @p buf.
233 *
234 * @param buf Destination buffer of at least LIVE_ARENA_KEY_MAX bytes.
235 * @param name Unqualified type name, typically from live_type_name<T>().
236 * @param count Index appended after the name.
237 */
238 inline void live_format_key(char* buf, std::string_view name, uint32_t count) noexcept
239 {
240 std::snprintf(buf, LIVE_ARENA_KEY_MAX, "%.*s_%u",
241 static_cast<int>(name.size()), name.data(), count);
242 }
243
244} // namespace MayaFlux::internal
245
246#ifdef MAYAFLUX_LIVE
247namespace internal {
248 /**
249 * @brief Enables live exposure at load time in any translation unit compiled with MAYAFLUX_LIVE.
250 */
251 [[maybe_unused]] static const bool live_exposure_registration = []() noexcept {
253 return true;
254 }();
255}
256#endif
257
258/**
259 * @brief Exposes @p ptr to the live arena under a user-supplied key when live exposure is enabled.
260 *
261 * Exposure is enabled by defining MAYAFLUX_LIVE before including MayaFlux
262 * headers in the client. Without it the macro does nothing at runtime.
263 */
264#define MF_LIVE_EXPOSE(key, ptr) \
265 do { \
266 if (::MayaFlux::internal::live_exposure_enabled()) \
267 ::MayaFlux::expose_live((key), (ptr)); \
268 } while (0)
269
270/**
271 * @brief Auto-expose variant that deduces the key prefix from the shared_ptr element type.
272 *
273 * Generates keys of the form "TypeName_N" where N is the first index not yet
274 * taken for that type, so each type keeps one sequence across every call site:
275 * Sine_0, Sine_1, Phasor_0, Phasor_1, etc.
276 *
277 * Use when no explicit name is available at the call site (e.g. read_* methods).
278 *
279 * @param ptr shared_ptr whose element_type drives the key prefix.
280 */
281#define MF_LIVE_EXPOSE_AUTO(ptr) \
282 do { \
283 if (::MayaFlux::internal::live_exposure_enabled()) { \
284 using _MfT = typename std::remove_cvref_t<decltype(ptr)>::element_type; \
285 ::MayaFlux::expose_live_indexed( \
286 ::MayaFlux::internal::live_type_name<_MfT>(), (ptr)); \
287 } \
288 } while (0)
289
290/**
291 * @brief Auto-expose variant with an explicit name prefix.
292 *
293 * Generates keys of the form "name_N" where N is the first index not yet taken
294 * for that name, so every overload of a Creator factory shares one sequence.
295 * Use inside Creator factories where the method name is a string literal.
296 *
297 * @param name String literal key prefix, typically the factory name.
298 * @param ptr shared_ptr to expose.
299 */
300#define MF_LIVE_EXPOSE_NAMED(name, ptr) \
301 do { \
302 if (::MayaFlux::internal::live_exposure_enabled()) { \
303 ::MayaFlux::expose_live_indexed( \
304 std::string_view { (name) }, (ptr)); \
305 } \
306 } while (0)
307// =============================================================================
308
309/**
310 * @brief Constructs a T in-place inside the live arena and registers it under @p key.
311 *
312 * The returned shared_ptr is stable for the process lifetime and participates
313 * in normal shared_ptr ref-counting. The arena owns the underlying storage;
314 * the shared_ptr uses a no-op deleter so the arena remains the sole allocator.
315 *
316 * Reachable from JIT'd code immediately via live_cast<T>(key).
317 *
318 * @tparam T Type to construct.
319 * @param key Null-terminated string used to locate the object from the JIT.
320 * Must be unique within the arena.
321 * @param args Arguments forwarded to T's constructor.
322 * @return shared_ptr to the constructed object, or nullptr if the arena is
323 * full or @p key is already registered.
324 */
325template <typename T, typename... Args>
326std::shared_ptr<T> make_live(const char* key, Args&&... args)
327{
328 void* mem = internal::live_arena_alloc(
329 key,
330 static_cast<uint32_t>(sizeof(T)),
331 static_cast<uint32_t>(alignof(T)));
332
333 if (!mem) {
334 return nullptr;
335 }
336
337 T* obj = ::new (mem) T(std::forward<Args>(args)...);
338 return std::shared_ptr<T>(obj, [](T*) { });
339}
340
341/**
342 * @brief Exposes an existing shared_ptr-managed object to the live arena under @p key.
343 *
344 * The shared_ptr ref-count is incremented; the object remains alive at least
345 * as long as the arena entry exists. Reachable from JIT'd code via live_cast<T>(key).
346 *
347 * @tparam T Type of the object.
348 * @param key Null-terminated string used to locate the object from the JIT.
349 * @param obj shared_ptr to the existing object.
350 * @return true on success, false if @p key is already registered or the
351 * directory is full.
352 */
353template <typename T>
354bool expose_live(const char* key, const std::shared_ptr<T>& obj) noexcept
355{
356 return internal::live_arena_expose(key, std::static_pointer_cast<void>(obj));
357}
358
359/**
360 * @brief Exposes an existing raw pointer to the live arena under @p key.
361 *
362 * Lifetime management remains entirely the caller's responsibility.
363 * The arena stores a no-op shared_ptr so live_cast returns a valid
364 * shared_ptr without affecting the object's actual lifetime.
365 *
366 * @tparam T Type of the object.
367 * @param key Null-terminated string used to locate the object from the JIT.
368 * @param ptr Raw pointer to the existing object.
369 * @return true on success, false if @p key is already registered or the
370 * directory is full.
371 */
372template <typename T>
373bool expose_live(const char* key, T* ptr) noexcept
374{
375 return internal::live_arena_expose(key, std::shared_ptr<void>(ptr, [](void*) { }));
376}
377
378/**
379 * @brief Exposes @p obj under the first free key of the form "prefix_N".
380 *
381 * N starts at 0 and advances past keys already in the arena, so every call
382 * site sharing a prefix, such as the overloads of one Creator factory, draws
383 * from one sequence and none of them collide.
384 *
385 * @tparam T Type of the object.
386 * @param prefix Key prefix.
387 * @param obj shared_ptr to the existing object.
388 * @return true on success, false if the directory is full.
389 */
390template <typename T>
391bool expose_live_indexed(std::string_view prefix, const std::shared_ptr<T>& obj) noexcept
392{
394 return false;
395 }
396
398 for (uint32_t index = 0; index < internal::LIVE_ARENA_MAX_ENTRIES; ++index) {
399 internal::live_format_key(key, prefix, index);
400 if (internal::live_arena_expose(key, std::static_pointer_cast<void>(obj))) {
401 return true;
402 }
403 }
404 return false;
405}
406
407/**
408 * @brief Internal implementation for live_cast. Resolves through the export
409 * table so JIT'd code can reach arena globals across DLL boundaries.
410 *
411 * @param key Null-terminated arena key.
412 * @return Raw pointer to the registered object, or nullptr.
413 */
414MAYAFLUX_API void* live_cast_impl(const char* key) noexcept;
415
416/**
417 * @brief Retrieves a live-arena object by key as a shared_ptr<T>.
418 *
419 * Delegates the directory walk to live_cast_impl so the call resolves
420 * through the DLL export table on all platforms. The returned shared_ptr
421 * uses a no-op deleter; lifetime is managed by the arena or the original
422 * expose_live caller.
423 *
424 * @tparam T Expected type of the registered object.
425 * @param key Null-terminated string used at make_live or expose_live time.
426 * @return shared_ptr<T>, or nullptr if the key is not found.
427 */
428template <typename T>
429std::shared_ptr<T> live_cast(const char* key) noexcept
430{
431 void* ptr = live_cast_impl(key);
432 if (!ptr) {
433 return nullptr;
434 }
435 return std::shared_ptr<T>(static_cast<T*>(ptr), [](T*) { });
436}
437
438/**
439 * @brief Dumps all live arena entries to stderr.
440 *
441 * Prints each occupied entry's index, key, and offset, followed by
442 * the total entry count and bump pointer value. Intended for diagnostic
443 * use during development; output goes to stderr unconditionally.
444 */
445MAYAFLUX_API void live_arena_dump() noexcept;
446
447} // namespace MayaFlux
bool size
glm::vec3 value
std::string name
Definition VKDevice.cpp:123
uint32_t index
Definition VKDevice.cpp:122
size_t count
const uint8_t * ptr
void enable_live_exposure() noexcept
Turns on exposure for the MF_LIVE_EXPOSE macros in the linked library.
Definition LiveArena.cpp:9
constexpr std::size_t LIVE_ARENA_KEY_MAX
Maximum length of a live arena key, including null terminator.
Definition LiveArena.hpp:34
uint32_t align_up(uint32_t value, uint32_t align) noexcept
Aligns value up to the next multiple of align.
constexpr std::size_t LIVE_ARENA_MAX_ENTRIES
Maximum number of objects that can be registered in the live arena.
Definition LiveArena.hpp:29
constexpr std::string_view live_type_name() noexcept
Extracts the unqualified type name from a compiler-generated function signature.
uint32_t g_live_arena_bump
Byte offset of the bump pointer from the start of the object region.
Definition LiveArena.cpp:20
constexpr std::size_t LIVE_ARENA_CAPACITY
Default capacity of the live arena in bytes.
Definition LiveArena.hpp:39
bool live_exposure_enabled() noexcept
Reports whether live exposure has been enabled.
Definition LiveArena.cpp:14
void live_format_key(char *buf, std::string_view name, uint32_t count) noexcept
Formats a live arena key as "TypeName_N" into buf.
LiveArenaHeader * live_arena_header() noexcept
Returns a pointer to the header block at the start of arena storage.
unsigned char * live_arena_object_region() noexcept
Returns a pointer to the object region (immediately after the header).
bool live_arena_expose(const char *key, std::shared_ptr< void > shared) noexcept
Registers an externally owned shared_ptr under key.
std::atomic< bool > g_live_exposure_enabled
Whether the MF_LIVE_EXPOSE macros publish objects to the arena.
Definition LiveArena.cpp:7
LiveArenaEntry * live_arena_find(const char *key) noexcept
Locates an existing directory entry by key.
void * live_arena_alloc(const char *key, uint32_t size, uint32_t alignment) noexcept
Allocates size bytes from the arena with alignment.
unsigned char g_live_arena_storage[LIVE_ARENA_CAPACITY]
Raw backing storage for the live arena with external linkage.
Definition LiveArena.cpp:19
std::shared_ptr< void > g_live_arena_shared_ptrs[LIVE_ARENA_MAX_ENTRIES]
Shared ownership table for objects registered via expose_live.
Definition LiveArena.cpp:21
MAYAFLUX_API void * live_cast_impl(const char *key) noexcept
Internal implementation for live_cast.
Definition LiveArena.cpp:25
bool expose_live(const char *key, const std::shared_ptr< T > &obj) noexcept
Exposes an existing shared_ptr-managed object to the live arena under key.
MAYAFLUX_API void live_arena_dump() noexcept
Dumps all live arena entries to stderr.
Definition LiveArena.cpp:44
std::shared_ptr< T > live_cast(const char *key) noexcept
Retrieves a live-arena object by key as a shared_ptr<T>.
std::shared_ptr< T > make_live(const char *key, Args &&... args)
Constructs a T in-place inside the live arena and registers it under key.
bool expose_live_indexed(std::string_view prefix, const std::shared_ptr< T > &obj) noexcept
Exposes obj under the first free key of the form "prefix_N".
Main namespace for the Maya Flux audio engine.
Definition Runtime.cpp:12
Single directory entry mapping a string key to a location within the arena.
Definition LiveArena.hpp:49
LiveArenaEntry entries[LIVE_ARENA_MAX_ENTRIES]
Definition LiveArena.hpp:64
POD header block placed at the start of the arena storage.
Definition LiveArena.hpp:62