From 36d7971a967968536483181b65d8552064970d66 Mon Sep 17 00:00:00 2001
From: Patrick Wuttke
Date: Tue, 17 Feb 2026 10:36:50 +0100
Subject: [PATCH] Added getTransientValue() and getRuntimeValue() utility
functions and made formatTemp() return a smart struct to simplify managing
the texts lifetime in nested calls.
---
public/raid/imutil.hpp | 266 ++++++++++++++++++++++++++++++++++++++++-
1 file changed, 263 insertions(+), 3 deletions(-)
diff --git a/public/raid/imutil.hpp b/public/raid/imutil.hpp
index dfd7ff3..6ec5935 100644
--- a/public/raid/imutil.hpp
+++ b/public/raid/imutil.hpp
@@ -4,10 +4,16 @@
#if !defined(RAID_PUBLIC_RAID_IMUTIL_HPP_INCLUDED)
#define RAID_PUBLIC_RAID_IMUTIL_HPP_INCLUDED 1
+#include
#include
#include
+#include
+#include
#include
+#include
+#include
#include
+#include
namespace raid
{
@@ -17,6 +23,27 @@ namespace impl
template
inline std::string gFormatBuffer;
+struct FormatBuffer
+{
+ std::string data;
+ bool used = false;
+};
+
+inline thread_local std::vector gFormatBuffers;
+
+[[nodiscard]]
+inline std::size_t findFormatBuffer()
+{
+ for (std::size_t idx = 0; idx < gFormatBuffers.size(); ++idx)
+ {
+ if (!gFormatBuffers[idx].used) {
+ return idx;
+ }
+ }
+ gFormatBuffers.emplace_back();
+ return gFormatBuffers.size() - 1;
+}
+
template
struct imgui_data_type;
@@ -32,7 +59,155 @@ template<> struct imgui_data_type : std::integral_constant struct imgui_data_type : std::integral_constant {};
template<> struct imgui_data_type : std::integral_constant {};
template<> struct imgui_data_type : std::integral_constant {};
-}
+
+struct TransientStorageEntry
+{
+ ImGuiID id;
+ int lastFrame;
+ std::unique_ptr value; // pointer to avoid problems when resizing
+};
+inline std::vector gTransientStorage;
+
+struct RuntimeStorageEntry
+{
+ std::unique_ptr value;
+ unsigned references = 0;
+};
+inline std::vector gRuntimeStorage;
+} // namespace impl
+
+class TempText
+{
+private:
+ static constexpr std::size_t kInvalidIdx = std::numeric_limits::max();
+
+ std::size_t mBufferIdx = kInvalidIdx;
+public:
+ TempText() = default;
+ explicit TempText(std::size_t bufferIdx) : mBufferIdx(bufferIdx)
+ {
+ MIJIN_ASSERT(impl::gFormatBuffers[bufferIdx].used, "invalid format buffer state");
+ }
+ TempText(const TempText&) = delete;
+ TempText(TempText&& other) noexcept : mBufferIdx(std::exchange(other.mBufferIdx, kInvalidIdx)) {}
+ ~TempText() noexcept
+ {
+ release();
+ }
+ TempText& operator=(const TempText&) = delete;
+ TempText& operator=(TempText&& other) noexcept
+ {
+ if (this != &other)
+ {
+ release();
+ mBufferIdx = std::exchange(other.mBufferIdx, kInvalidIdx);
+ }
+ return *this;
+ }
+
+ operator const char*() const noexcept
+ {
+ return get();
+ }
+
+ operator std::string_view() const noexcept
+ {
+ return std::string_view(get());
+ }
+
+ [[nodiscard]]
+ const char* get() const noexcept
+ {
+ MIJIN_ASSERT(mBufferIdx != kInvalidIdx, "invalid temp text");
+ return impl::gFormatBuffers[mBufferIdx].data.c_str();
+ }
+private:
+ void release()
+ {
+ if (mBufferIdx != kInvalidIdx)
+ {
+ MIJIN_ASSERT(impl::gFormatBuffers[mBufferIdx].used, "invalid format buffer state");
+ impl::gFormatBuffers[mBufferIdx].used = false;
+ }
+ }
+};
+
+template
+class [[nodiscard]] ValueKey
+{
+private:
+ static const std::size_t kInvalidIdx = std::numeric_limits::max();
+
+ std::size_t mIdx = kInvalidIdx;
+public:
+ ValueKey() = default;
+ ValueKey(const ValueKey& other) noexcept : mIdx(other.mIdx) {
+ increaseReferences();
+ }
+ ValueKey(ValueKey&& other) noexcept : mIdx(std::exchange(other.mIdx, kInvalidIdx)) {}
+ explicit ValueKey(std::size_t idx) noexcept : mIdx(idx)
+ {
+ if (idx != kInvalidIdx) {
+ MIJIN_ASSERT(getEntry().value != nullptr && getEntry().value->type() == typeid(T), "invalid runtime value index");
+ }
+ increaseReferences();
+ }
+ ~ValueKey() noexcept
+ {
+ decreaseReferences();
+ }
+ ValueKey& operator=(const ValueKey& other) noexcept
+ {
+ if (this != &other)
+ {
+ decreaseReferences();
+ mIdx = other.mIdx;
+ increaseReferences();
+ }
+ return *this;
+ }
+ ValueKey& operator=(ValueKey&& other) noexcept
+ {
+ if (this != &other)
+ {
+ decreaseReferences();
+ mIdx = std::exchange(other.mIdx, kInvalidIdx);
+ }
+ return *this;
+ }
+
+ operator bool() const noexcept { return isValid(); }
+ bool operator!() const noexcept { return !isValid(); }
+
+ [[nodiscard]]
+ bool isValid() const noexcept { return mIdx != kInvalidIdx; }
+
+ [[nodiscard]]
+ T& get() const noexcept { return std::any_cast(*getEntry().value); }
+private:
+ [[nodiscard]]
+ impl::RuntimeStorageEntry& getEntry() const noexcept
+ {
+ return impl::gRuntimeStorage.at(mIdx);
+ }
+
+ void increaseReferences() const noexcept
+ {
+ if (mIdx != kInvalidIdx) {
+ ++getEntry().references;
+ }
+ }
+
+ void decreaseReferences() const noexcept
+ {
+ if (mIdx != kInvalidIdx)
+ {
+ if (--getEntry().references == 0) {
+ getEntry().value.reset();
+ }
+ }
+ }
+};
template
[[nodiscard]]
@@ -45,9 +220,94 @@ const char* formatTemp(std::string& formatBuffer, std::format_string f
template
[[nodiscard]]
-const char* formatTemp(std::format_string fmt, TArgs&&... args)
+TempText formatTemp(std::format_string fmt, TArgs&&... args)
{
- return formatTemp(impl::gFormatBuffer, fmt, std::forward(args)...);
+ const std::size_t bufferIdx = impl::findFormatBuffer();
+ impl::FormatBuffer& buffer = impl::gFormatBuffers[bufferIdx];
+ buffer.used = true;
+ buffer.data.clear();
+ std::format_to(std::back_inserter(buffer.data), fmt, std::forward(args)...);
+ return TempText(bufferIdx);
+}
+
+template
+[[nodiscard]]
+T& getTransientValue(ImGuiID valueID, int lifetimeFrames = 1, bool* existed = nullptr)
+{
+ const int currentFrame = ImGui::GetFrameCount();
+
+ // find existing
+ for (impl::TransientStorageEntry& entry : impl::gTransientStorage)
+ {
+ if (entry.id == valueID && entry.value->type() == typeid(T) && entry.lastFrame >= currentFrame)
+ {
+ if (existed != nullptr) {
+ *existed = true;
+ }
+ entry.lastFrame = currentFrame + lifetimeFrames;
+ return std::any_cast(*entry.value);
+ }
+ }
+ if (existed != nullptr) {
+ *existed = false;
+ }
+
+ // find free slot
+ for (impl::TransientStorageEntry& entry : impl::gTransientStorage)
+ {
+ if (entry.lastFrame < currentFrame)
+ {
+ entry.id = valueID;
+ entry.lastFrame = currentFrame + lifetimeFrames;
+ return entry.value->emplace();
+ }
+ }
+
+ // create new slot
+ impl::gTransientStorage.push_back({
+ .id = valueID,
+ .lastFrame = currentFrame + lifetimeFrames,
+ .value = std::make_unique()
+ });
+ return impl::gTransientStorage.back().value->emplace();
+}
+
+template
+[[nodiscard]]
+T& getTransientValue(ImGuiID valueID, bool* existed)
+{
+ return getTransientValue(valueID, 1, existed);
+}
+
+template
+ValueKey makeRuntimeValue(TArgs&&... args)
+{
+ // find free slot
+ for (std::size_t idx = 0; idx < impl::gRuntimeStorage.size(); ++idx)
+ {
+ impl::RuntimeStorageEntry& entry = impl::gRuntimeStorage.at(idx);
+ if (entry.value == nullptr)
+ {
+ entry.value = std::make_unique(std::in_place_type_t{}, std::forward(args)...);
+ return ValueKey(idx);
+ }
+ }
+
+ // create new slot
+ impl::gRuntimeStorage.push_back({
+ .value = std::make_unique(std::in_place_type_t{}, std::forward(args)...)
+ });
+ return ValueKey(impl::gRuntimeStorage.size() - 1);
+}
+
+template
+[[nodiscard]]
+T& getRuntimeValue(ValueKey& key)
+{
+ if (!key.isValid()) {
+ key = makeRuntimeValue();
+ }
+ return key.get();
}
template