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