From 867a34e640aeac81243fb8ca90e662dc9fd2823c Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 10 Apr 2019 20:19:11 +0100 Subject: [PATCH] Changed interface to register ImGui debug delegates: - Added new FImGuiDelegates interface for ImGui debug delegates. - Added FImGuiDelegatesContainer for ImGui delegates. - Added code preserving delegates during hot-reloading and moving them to a new module. - Depreciated old FImGuiModule delegates interface and FImGuiDelegateHandle. - Delegates registered with depreciated interface are redirected and get benefit of being preserved during hot-reloading. This can be controlled with IMGUI_REDIRECT_OBSOLETE_DELEGATES. - Added IMGUI_WITH_OBSOLETE_DELEGATES allowing to strip depreciated interface from builds. - Modified context manager and context proxy to work with FImGuiDelegatesContainer. --- CHANGES.md | 7 +++ ImGui.uplugin | 2 +- Source/ImGui/Private/ImGuiContextManager.cpp | 6 +++ Source/ImGui/Private/ImGuiContextManager.h | 4 +- Source/ImGui/Private/ImGuiContextProxy.cpp | 50 ++++++++++++++----- Source/ImGui/Private/ImGuiContextProxy.h | 9 +++- Source/ImGui/Private/ImGuiDelegates.cpp | 22 ++++++++ .../ImGui/Private/ImGuiDelegatesContainer.cpp | 37 ++++++++++++++ .../ImGui/Private/ImGuiDelegatesContainer.h | 40 +++++++++++++++ Source/ImGui/Private/ImGuiModule.cpp | 44 ++++++++++++++++ .../Private/Utilities/WorldContextIndex.h | 5 ++ Source/ImGui/Public/ImGuiDelegates.h | 39 +++++++++++++++ Source/ImGui/Public/ImGuiModule.h | 5 ++ 13 files changed, 253 insertions(+), 17 deletions(-) create mode 100644 Source/ImGui/Private/ImGuiDelegates.cpp create mode 100644 Source/ImGui/Private/ImGuiDelegatesContainer.cpp create mode 100644 Source/ImGui/Private/ImGuiDelegatesContainer.h diff --git a/CHANGES.md b/CHANGES.md index ad8a9a0..ef62a2b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,13 @@ Versions marked as 'unofficial' are labelled only for the needs of this changelo Change History -------------- +Version: 1.15 (2019/04) +- Added new FImGuiDelegates interface for ImGui debug delegates. +- Added code preserving delegates during hot-reloading and moving them to a new module. +- DEPRECIATED old FImGuiModule delegates interface and FImGuiDelegateHandle. +- Delegates registered with depreciated interface are redirected and get benefit of being preserved during hot-reloading. This can be controlled with IMGUI_REDIRECT_OBSOLETE_DELEGATES. +- Added IMGUI_WITH_OBSOLETE_DELEGATES allowing to strip depreciated interface from builds (that interface will be officially removed in one of later releases). + Version: 1.14 (2019/03) - Added SImGuiLayout to resets layout for SImGuiWidget. - Refactored rendering in SImGuiWidget to take advantage of layout reset. diff --git a/ImGui.uplugin b/ImGui.uplugin index 1e95a39..6bc3553 100644 --- a/ImGui.uplugin +++ b/ImGui.uplugin @@ -1,7 +1,7 @@ { "FileVersion": 3, "Version": 1, - "VersionName": "1.14", + "VersionName": "1.15", "FriendlyName": "ImGui", "Description": "", "Category": "Debug", diff --git a/Source/ImGui/Private/ImGuiContextManager.cpp b/Source/ImGui/Private/ImGuiContextManager.cpp index ad8e769..bd419a7 100644 --- a/Source/ImGui/Private/ImGuiContextManager.cpp +++ b/Source/ImGui/Private/ImGuiContextManager.cpp @@ -4,6 +4,7 @@ #include "ImGuiContextManager.h" +#include "ImGuiDelegatesContainer.h" #include "ImGuiImplementation.h" #include "Utilities/ScopeGuards.h" #include "Utilities/WorldContext.h" @@ -89,6 +90,11 @@ void FImGuiContextManager::Tick(float DeltaSeconds) { ContextData.ContextProxy.Tick(DeltaSeconds); } + else + { + // Clear to make sure that we don't store objects registered for world that is no longer valid. + FImGuiDelegatesContainer::Get().OnWorldDebug(Pair.Key).Clear(); + } } } diff --git a/Source/ImGui/Private/ImGuiContextManager.h b/Source/ImGui/Private/ImGuiContextManager.h index f810f77..d3c48dc 100644 --- a/Source/ImGui/Private/ImGuiContextManager.h +++ b/Source/ImGui/Private/ImGuiContextManager.h @@ -72,7 +72,7 @@ private: { FContextData(const FString& ContextName, int32 ContextIndex, FSimpleMulticastDelegate& SharedDrawEvent, ImFontAtlas& FontAtlas, int32 InPIEInstance = -1) : PIEInstance(InPIEInstance) - , ContextProxy(ContextName, &SharedDrawEvent, &FontAtlas) + , ContextProxy(ContextName, ContextIndex, &SharedDrawEvent, &FontAtlas) { } @@ -87,7 +87,7 @@ private: struct FContextData { FContextData(const FString& ContextName, int32 ContextIndex, FSimpleMulticastDelegate& SharedDrawEvent, ImFontAtlas& FontAtlas) - : ContextProxy(ContextName, &SharedDrawEvent, &FontAtlas) + : ContextProxy(ContextName, ContextIndex, &SharedDrawEvent, &FontAtlas) { } diff --git a/Source/ImGui/Private/ImGuiContextProxy.cpp b/Source/ImGui/Private/ImGuiContextProxy.cpp index ffa6cc9..bf92fa3 100644 --- a/Source/ImGui/Private/ImGuiContextProxy.cpp +++ b/Source/ImGui/Private/ImGuiContextProxy.cpp @@ -4,6 +4,7 @@ #include "ImGuiContextProxy.h" +#include "ImGuiDelegatesContainer.h" #include "ImGuiImplementation.h" #include "ImGuiInteroperability.h" @@ -52,8 +53,9 @@ FImGuiContextProxy::FImGuiContextPtr::~FImGuiContextPtr() } } -FImGuiContextProxy::FImGuiContextProxy(const FString& InName, FSimpleMulticastDelegate* InSharedDrawEvent, ImFontAtlas* InFontAtlas) +FImGuiContextProxy::FImGuiContextProxy(const FString& InName, int32 InContextIndex, FSimpleMulticastDelegate* InSharedDrawEvent, ImFontAtlas* InFontAtlas) : Name(InName) + , ContextIndex(InContextIndex) , SharedDrawEvent(InSharedDrawEvent) , IniFilename(TCHAR_TO_ANSI(*GetIniFile(InName))) { @@ -91,22 +93,13 @@ void FImGuiContextProxy::Draw() // Broadcast draw event to allow listeners to draw their controls to this context. #if DRAW_EVENTS_ORDER_WORLD_BEFORE_MULTI_CONTEXT - if (DrawEvent.IsBound()) - { - DrawEvent.Broadcast(); - } + BroadcastWorldTick(); #endif // DRAW_EVENTS_ORDER_WORLD_BEFORE_MULTI_CONTEXT - if (SharedDrawEvent && SharedDrawEvent->IsBound()) - { - SharedDrawEvent->Broadcast(); - } + BroadcastMultiContextTick(); #if !DRAW_EVENTS_ORDER_WORLD_BEFORE_MULTI_CONTEXT - if (DrawEvent.IsBound()) - { - DrawEvent.Broadcast(); - } + BroadcastWorldTick(); #endif // !DRAW_EVENTS_ORDER_WORLD_BEFORE_MULTI_CONTEXT } } @@ -192,3 +185,34 @@ void FImGuiContextProxy::UpdateDrawData(ImDrawData* DrawData) DrawLists.Empty(); } } + +void FImGuiContextProxy::BroadcastWorldTick() +{ + if (DrawEvent.IsBound()) + { + DrawEvent.Broadcast(); + } + + if (ContextIndex != Utilities::INVALID_CONTEXT_INDEX) + { + FSimpleMulticastDelegate& WorldDebugEvent = FImGuiDelegatesContainer::Get().OnWorldDebug(ContextIndex); + if (WorldDebugEvent.IsBound()) + { + WorldDebugEvent.Broadcast(); + } + } +} + +void FImGuiContextProxy::BroadcastMultiContextTick() +{ + if (SharedDrawEvent && SharedDrawEvent->IsBound()) + { + SharedDrawEvent->Broadcast(); + } + + FSimpleMulticastDelegate& MultiContextDebugEvent = FImGuiDelegatesContainer::Get().OnMultiContextDebug(); + if (MultiContextDebugEvent.IsBound()) + { + MultiContextDebugEvent.Broadcast(); + } +} diff --git a/Source/ImGui/Private/ImGuiContextProxy.h b/Source/ImGui/Private/ImGuiContextProxy.h index b91f16b..433b4f2 100644 --- a/Source/ImGui/Private/ImGuiContextProxy.h +++ b/Source/ImGui/Private/ImGuiContextProxy.h @@ -4,6 +4,8 @@ #include "ImGuiDrawData.h" +#include "Utilities/WorldContextIndex.h" + #include #include @@ -41,7 +43,7 @@ class FImGuiContextProxy public: - FImGuiContextProxy(const FString& Name, FSimpleMulticastDelegate* InSharedDrawEvent, ImFontAtlas* InFontAtlas); + FImGuiContextProxy(const FString& Name, int32 InContextIndex, FSimpleMulticastDelegate* InSharedDrawEvent, ImFontAtlas* InFontAtlas); FImGuiContextProxy(const FImGuiContextProxy&) = delete; FImGuiContextProxy& operator=(const FImGuiContextProxy&) = delete; @@ -96,6 +98,9 @@ private: void UpdateDrawData(ImDrawData* DrawData); + void BroadcastWorldTick(); + void BroadcastMultiContextTick(); + FImGuiContextPtr Context; FVector2D DisplaySize = FVector2D::ZeroVector; @@ -115,6 +120,8 @@ private: TArray DrawLists; + int32 ContextIndex = Utilities::INVALID_CONTEXT_INDEX; + FString Name; std::string IniFilename; }; diff --git a/Source/ImGui/Private/ImGuiDelegates.cpp b/Source/ImGui/Private/ImGuiDelegates.cpp new file mode 100644 index 0000000..46b78c2 --- /dev/null +++ b/Source/ImGui/Private/ImGuiDelegates.cpp @@ -0,0 +1,22 @@ +// Distributed under the MIT License (MIT) (see accompanying LICENSE file) + +#include "ImGuiPrivatePCH.h" + +#include "ImGuiDelegates.h" +#include "ImGuiDelegatesContainer.h" + + +FSimpleMulticastDelegate& FImGuiDelegates::OnWorldDebug() +{ + return OnWorldDebug(GWorld); +} + +FSimpleMulticastDelegate& FImGuiDelegates::OnWorldDebug(UWorld* World) +{ + return FImGuiDelegatesContainer::Get().OnWorldDebug(World); +} + +FSimpleMulticastDelegate& FImGuiDelegates::OnMultiContextDebug() +{ + return FImGuiDelegatesContainer::Get().OnMultiContextDebug(); +} diff --git a/Source/ImGui/Private/ImGuiDelegatesContainer.cpp b/Source/ImGui/Private/ImGuiDelegatesContainer.cpp new file mode 100644 index 0000000..6565e6d --- /dev/null +++ b/Source/ImGui/Private/ImGuiDelegatesContainer.cpp @@ -0,0 +1,37 @@ +// Distributed under the MIT License (MIT) (see accompanying LICENSE file) + +#include "ImGuiPrivatePCH.h" + +#include "ImGuiDelegatesContainer.h" + +#include "Utilities/WorldContextIndex.h" + + +FImGuiDelegatesContainer FImGuiDelegatesContainer::DefaultInstance; + +FImGuiDelegatesContainer* FImGuiDelegatesContainer::InstancePtr = &FImGuiDelegatesContainer::DefaultInstance; + +void FImGuiDelegatesContainer::MoveContainer(FImGuiDelegatesContainer& Dst) +{ + // Only move data if pointer points to default instance, otherwise our data has already been moved and we only + // keep pointer to a more recent version. + if (InstancePtr == &DefaultInstance) + { + Dst = MoveTemp(DefaultInstance); + DefaultInstance.Clear(); + } + + // Update pointer to the most recent version. + InstancePtr = &Dst; +} + +FSimpleMulticastDelegate& FImGuiDelegatesContainer::OnWorldDebug(UWorld* World) +{ + return OnWorldDebug(Utilities::GetWorldContextIndex(*World)); +} + +void FImGuiDelegatesContainer::Clear() +{ + WorldDelegates.Empty(); + MultiContextDelegate.Clear(); +} diff --git a/Source/ImGui/Private/ImGuiDelegatesContainer.h b/Source/ImGui/Private/ImGuiDelegatesContainer.h new file mode 100644 index 0000000..fa07aac --- /dev/null +++ b/Source/ImGui/Private/ImGuiDelegatesContainer.h @@ -0,0 +1,40 @@ +// Distributed under the MIT License (MIT) (see accompanying LICENSE file) + +#pragma once + +#include +#include + + +struct FImGuiDelegatesContainer +{ +public: + + // Get the current instance (can change during hot-reloading). + static FImGuiDelegatesContainer& Get() { return *InstancePtr; } + + // If this is an active container move its data to a destination and redirect all future calls to that instance. + static void MoveContainer(FImGuiDelegatesContainer& Dst); + + // Get delegate to ImGui world debug event from known world instance. + FSimpleMulticastDelegate& OnWorldDebug(UWorld* World); + + // Get delegate to ImGui world debug event from known context index. + FSimpleMulticastDelegate& OnWorldDebug(int32 ContextIndex) { return WorldDelegates.FindOrAdd(ContextIndex); } + + // Get delegate to ImGui multi-context debug event. + FSimpleMulticastDelegate& OnMultiContextDebug() { return MultiContextDelegate; } + +private: + + void Clear(); + + TMap WorldDelegates; + FSimpleMulticastDelegate MultiContextDelegate; + + // Default container instance. + static FImGuiDelegatesContainer DefaultInstance; + + // Pointer to the container instance that can be overwritten during hot-reloading. + static FImGuiDelegatesContainer* InstancePtr; +}; diff --git a/Source/ImGui/Private/ImGuiModule.cpp b/Source/ImGui/Private/ImGuiModule.cpp index 6a619c2..92b5d1a 100644 --- a/Source/ImGui/Private/ImGuiModule.cpp +++ b/Source/ImGui/Private/ImGuiModule.cpp @@ -4,6 +4,7 @@ #include "ImGuiModuleManager.h" +#include "ImGuiDelegatesContainer.h" #include "ImGuiTextureHandle.h" #include "TextureManager.h" #include "Utilities/WorldContext.h" @@ -16,6 +17,8 @@ #include +#define IMGUI_REDIRECT_OBSOLETE_DELEGATES 1 + #define LOCTEXT_NAMESPACE "FImGuiModule" @@ -38,18 +41,29 @@ static FImGuiModuleManager* ImGuiModuleManager = nullptr; static FImGuiEditor* ImGuiEditor = nullptr; #endif +#if IMGUI_WITH_OBSOLETE_DELEGATES + #if WITH_EDITOR FImGuiDelegateHandle FImGuiModule::AddEditorImGuiDelegate(const FImGuiDelegate& Delegate) { +#if IMGUI_REDIRECT_OBSOLETE_DELEGATES + return { FImGuiDelegatesContainer::Get().OnWorldDebug(Utilities::EDITOR_CONTEXT_INDEX).Add(Delegate), + EDelegateCategory::Default, Utilities::EDITOR_CONTEXT_INDEX }; +#else checkf(ImGuiModuleManager, TEXT("Null pointer to internal module implementation. Is module available?")); return { ImGuiModuleManager->GetContextManager().GetEditorContextProxy().OnDraw().Add(Delegate), EDelegateCategory::Default, Utilities::EDITOR_CONTEXT_INDEX }; +#endif // IMGUI_REDIRECT_OBSOLETE_DELEGATES } #endif FImGuiDelegateHandle FImGuiModule::AddWorldImGuiDelegate(const FImGuiDelegate& Delegate) { +#if IMGUI_REDIRECT_OBSOLETE_DELEGATES + const int32 ContextIndex = Utilities::GetWorldContextIndex((UWorld*)GWorld); + return { FImGuiDelegatesContainer::Get().OnWorldDebug(ContextIndex).Add(Delegate), EDelegateCategory::Default, ContextIndex }; +#else checkf(ImGuiModuleManager, TEXT("Null pointer to internal module implementation. Is module available?")); #if WITH_EDITOR @@ -71,17 +85,32 @@ FImGuiDelegateHandle FImGuiModule::AddWorldImGuiDelegate(const FImGuiDelegate& D #endif return{ Proxy.OnDraw().Add(Delegate), EDelegateCategory::Default, Index }; +#endif // IMGUI_REDIRECT_OBSOLETE_DELEGATES } FImGuiDelegateHandle FImGuiModule::AddMultiContextImGuiDelegate(const FImGuiDelegate& Delegate) { +#if IMGUI_REDIRECT_OBSOLETE_DELEGATES + return { FImGuiDelegatesContainer::Get().OnMultiContextDebug().Add(Delegate), EDelegateCategory::MultiContext }; +#else checkf(ImGuiModuleManager, TEXT("Null pointer to internal module implementation. Is module available?")); return { ImGuiModuleManager->GetContextManager().OnDrawMultiContext().Add(Delegate), EDelegateCategory::MultiContext }; +#endif } void FImGuiModule::RemoveImGuiDelegate(const FImGuiDelegateHandle& Handle) { +#if IMGUI_REDIRECT_OBSOLETE_DELEGATES + if (Handle.Category == EDelegateCategory::MultiContext) + { + FImGuiDelegatesContainer::Get().OnMultiContextDebug().Remove(Handle.Handle); + } + else + { + FImGuiDelegatesContainer::Get().OnWorldDebug(Handle.Index).Remove(Handle.Handle); + } +#else if (ImGuiModuleManager) { if (Handle.Category == EDelegateCategory::MultiContext) @@ -93,8 +122,11 @@ void FImGuiModule::RemoveImGuiDelegate(const FImGuiDelegateHandle& Handle) Proxy->OnDraw().Remove(Handle.Handle); } } +#endif } +#endif // IMGUI_WITH_OBSOLETE_DELEGATES + FImGuiTextureHandle FImGuiModule::FindTextureHandle(const FName& Name) { const TextureIndex Index = ImGuiModuleManager->GetTextureManager().FindTextureIndex(Name); @@ -154,6 +186,10 @@ void FImGuiModule::ShutdownModule() // deleted. This can cause troubles after hot-reload when code in other modules calls ImGui interface functions // which are statically bound to the obsolete module. To keep ImGui code functional we can redirect context handle // to point to the new module. + + // When shutting down during hot-reloading, we might want to rewire handles used in statically bound functions + // or move data to a new module. + FModuleManager::Get().OnModulesChanged().AddLambda([this] (FName Name, EModuleChangeReason Reason) { if (Reason == EModuleChangeReason::ModuleLoaded && Name == "ImGui") @@ -161,8 +197,11 @@ void FImGuiModule::ShutdownModule() FImGuiModule& LoadedModule = FImGuiModule::Get(); if (&LoadedModule != this) { + // Statically bound functions will be still made to the obsolete module so we need to ImGuiImplementation::SetImGuiContextHandle(LoadedModule.GetImGuiContextHandle()); + FImGuiDelegatesContainer::MoveContainer(LoadedModule.GetDelegatesContainer()); + if (bMoveProperties) { bMoveProperties = false; @@ -184,6 +223,11 @@ ImGuiContext** FImGuiModule::GetImGuiContextHandle() { return ImGuiImplementation::GetImGuiContextHandle(); } + +FImGuiDelegatesContainer& FImGuiModule::GetDelegatesContainer() +{ + return FImGuiDelegatesContainer::Get(); +} #endif FImGuiModuleProperties& FImGuiModule::GetProperties() diff --git a/Source/ImGui/Private/Utilities/WorldContextIndex.h b/Source/ImGui/Private/Utilities/WorldContextIndex.h index 6bf8c8e..39b36b2 100644 --- a/Source/ImGui/Private/Utilities/WorldContextIndex.h +++ b/Source/ImGui/Private/Utilities/WorldContextIndex.h @@ -56,6 +56,11 @@ namespace Utilities return (World.WorldType == EWorldType::Editor) ? EDITOR_CONTEXT_INDEX : GetWorldContextIndex(World.GetGameInstance()); } + FORCEINLINE int32 GetWorldContextIndex(const UWorld* World) + { + return World ? GetWorldContextIndex(*World) : INVALID_CONTEXT_INDEX; + } + #else template diff --git a/Source/ImGui/Public/ImGuiDelegates.h b/Source/ImGui/Public/ImGuiDelegates.h index a4514dc..cf9c986 100644 --- a/Source/ImGui/Public/ImGuiDelegates.h +++ b/Source/ImGui/Public/ImGuiDelegates.h @@ -5,6 +5,43 @@ #include +/** + * Delegates to ImGui debug events called. World delegates are called once per frame to draw debug for owning world + * and are automatically cleared when that world becomes invalid. Multi-context delegates are called once for every + * world that needs to be debugged. + * In engine version 4.18 or later delegates are called during OnWorldPostActorTick event while in older versions + * 4.18 during OnWorldTickStart event. Calling behaviours can be changed in build configuration. + */ +class IMGUI_API FImGuiDelegates +{ +public: + + /** + * Get a delegate to ImGui world debug event for current world (GWorld). + * @returns Simple multicast delegate to debug events called once per frame to debug current world + */ + static FSimpleMulticastDelegate& OnWorldDebug(); + + /** + * Get a delegate to ImGui world debug event for given world. + * @param World - World for which we need a delegate + * @returns Simple multicast delegate to debug events called once per frame to debug given world + */ + static FSimpleMulticastDelegate& OnWorldDebug(UWorld* World); + + /** + * Get a delegate to ImGui multi-context debug event. + * @returns Simple multicast delegate to debug events called once per frame for every world to debug + */ + static FSimpleMulticastDelegate& OnMultiContextDebug(); +}; + + +/** Enable to support legacy ImGui delegates API. */ +#define IMGUI_WITH_OBSOLETE_DELEGATES 1 + +#if IMGUI_WITH_OBSOLETE_DELEGATES + /** Delegate that allows to subscribe for ImGui events. */ typedef FSimpleMulticastDelegate::FDelegate FImGuiDelegate; @@ -53,3 +90,5 @@ private: friend class FImGuiModule; }; + +#endif // IMGUI_WITH_OBSOLETE_DELEGATES diff --git a/Source/ImGui/Public/ImGuiModule.h b/Source/ImGui/Public/ImGuiModule.h index b5486bc..e8c6f42 100644 --- a/Source/ImGui/Public/ImGuiModule.h +++ b/Source/ImGui/Public/ImGuiModule.h @@ -34,6 +34,8 @@ public: return FModuleManager::Get().IsModuleLoaded("ImGui"); } +#if IMGUI_WITH_OBSOLETE_DELEGATES + #if WITH_EDITOR /** * Add a delegate called at the end of editor debug frame to draw debug controls in its ImGui context, creating @@ -71,6 +73,8 @@ public: */ virtual void RemoveImGuiDelegate(const FImGuiDelegateHandle& Handle); +#endif // #if IMGUI_WITH_OBSOLETE_DELEGATES + /** * If it exists, get a handle to the texture with given resource name. * @@ -165,5 +169,6 @@ public: #if WITH_EDITOR virtual void SetProperties(const FImGuiModuleProperties& Properties); virtual struct ImGuiContext** GetImGuiContextHandle(); + virtual struct FImGuiDelegatesContainer& GetDelegatesContainer(); #endif };