Added getTransientValue() and getRuntimeValue() utility functions and made formatTemp() return a smart struct to simplify managing the texts lifetime in nested calls.

This commit is contained in:
Patrick Wuttke
2026-02-17 10:36:50 +01:00
parent a193826eb1
commit 36d7971a96

View File

@@ -4,10 +4,16 @@
#if !defined(RAID_PUBLIC_RAID_IMUTIL_HPP_INCLUDED)
#define RAID_PUBLIC_RAID_IMUTIL_HPP_INCLUDED 1
#include <any>
#include <cstdint>
#include <format>
#include <limits>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
#include <imgui.h>
#include <mijin/debug/assert.hpp>
namespace raid
{
@@ -17,6 +23,27 @@ namespace impl
template<std::size_t I>
inline std::string gFormatBuffer;
struct FormatBuffer
{
std::string data;
bool used = false;
};
inline thread_local std::vector<FormatBuffer> 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<typename T>
struct imgui_data_type;
@@ -32,7 +59,155 @@ template<> struct imgui_data_type<float> : std::integral_constant<ImGuiDataType,
template<> struct imgui_data_type<double> : std::integral_constant<ImGuiDataType, ImGuiDataType_Double> {};
template<> struct imgui_data_type<bool> : std::integral_constant<ImGuiDataType, ImGuiDataType_Bool> {};
template<> struct imgui_data_type<const char*> : std::integral_constant<ImGuiDataType, ImGuiDataType_String> {};
}
struct TransientStorageEntry
{
ImGuiID id;
int lastFrame;
std::unique_ptr<std::any> value; // pointer to avoid problems when resizing
};
inline std::vector<TransientStorageEntry> gTransientStorage;
struct RuntimeStorageEntry
{
std::unique_ptr<std::any> value;
unsigned references = 0;
};
inline std::vector<RuntimeStorageEntry> gRuntimeStorage;
} // namespace impl
class TempText
{
private:
static constexpr std::size_t kInvalidIdx = std::numeric_limits<std::size_t>::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<typename T>
class [[nodiscard]] ValueKey
{
private:
static const std::size_t kInvalidIdx = std::numeric_limits<std::size_t>::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<T&>(*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<typename... TArgs>
[[nodiscard]]
@@ -45,9 +220,94 @@ const char* formatTemp(std::string& formatBuffer, std::format_string<TArgs...> f
template<std::size_t I = 0, typename... TArgs>
[[nodiscard]]
const char* formatTemp(std::format_string<TArgs...> fmt, TArgs&&... args)
TempText formatTemp(std::format_string<TArgs...> fmt, TArgs&&... args)
{
return formatTemp(impl::gFormatBuffer<I>, fmt, std::forward<TArgs>(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<TArgs>(args)...);
return TempText(bufferIdx);
}
template<typename T>
[[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<T&>(*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<T>();
}
}
// create new slot
impl::gTransientStorage.push_back({
.id = valueID,
.lastFrame = currentFrame + lifetimeFrames,
.value = std::make_unique<std::any>()
});
return impl::gTransientStorage.back().value->emplace<T>();
}
template<typename T>
[[nodiscard]]
T& getTransientValue(ImGuiID valueID, bool* existed)
{
return getTransientValue<T>(valueID, 1, existed);
}
template<typename T, typename... TArgs>
ValueKey<T> 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::any>(std::in_place_type_t<T>{}, std::forward<TArgs>(args)...);
return ValueKey<T>(idx);
}
}
// create new slot
impl::gRuntimeStorage.push_back({
.value = std::make_unique<std::any>(std::in_place_type_t<T>{}, std::forward<TArgs>(args)...)
});
return ValueKey<T>(impl::gRuntimeStorage.size() - 1);
}
template<typename T>
[[nodiscard]]
T& getRuntimeValue(ValueKey<T>& key)
{
if (!key.isValid()) {
key = makeRuntimeValue<T>();
}
return key.get();
}
template<typename T>