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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user