From a76f4bc45121df430cdcc1300ceaf387ff56f4ad Mon Sep 17 00:00:00 2001 From: Sebastian Date: Fri, 31 Jul 2020 10:24:53 +0100 Subject: [PATCH] Improved hot-reload stability and support for reloading after recompiling outside of the editor: - Hot-reloading from inside of the editor and after recompiling from the outside should be equally supported and work together. - Improved robustness of the delegates redirections. There should be no lost delegates when reloading (either after in-editor or outside recompilation). - Refactored code to use the same redirection template for context and delegates container. --- CHANGES.md | 2 + .../ImGui/Private/ImGuiDelegatesContainer.cpp | 38 ++++++-- .../ImGui/Private/ImGuiDelegatesContainer.h | 17 ++-- Source/ImGui/Private/ImGuiImplementation.cpp | 34 +++++-- Source/ImGui/Private/ImGuiImplementation.h | 6 +- Source/ImGui/Private/ImGuiModule.cpp | 12 +-- .../Private/Utilities/RedirectingHandle.h | 96 +++++++++++++++++++ Source/ImGui/Public/ImGuiModule.h | 8 +- 8 files changed, 176 insertions(+), 37 deletions(-) create mode 100644 Source/ImGui/Private/Utilities/RedirectingHandle.h diff --git a/CHANGES.md b/CHANGES.md index b8e0040..9dc9abd 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,8 @@ Change History Version: 1.21 (2020/07) Improving stability - Fixed a crash in the input handler caused by invalidated by hot-reload instance trying to unregister a delegate. +- Improved hot-reload stability and support for reloading after recompiling outside of the editor. Both methods should be equally supported and work together. +- Improved behaviour of delegates when hot-reloading. Version: 1.20 (2020/06) Transition to IWYU and maintenance: diff --git a/Source/ImGui/Private/ImGuiDelegatesContainer.cpp b/Source/ImGui/Private/ImGuiDelegatesContainer.cpp index 147596d..2f035dc 100644 --- a/Source/ImGui/Private/ImGuiDelegatesContainer.cpp +++ b/Source/ImGui/Private/ImGuiDelegatesContainer.cpp @@ -2,25 +2,49 @@ #include "ImGuiDelegatesContainer.h" +#include "ImGuiModule.h" +#include "Utilities/RedirectingHandle.h" #include "Utilities/WorldContextIndex.h" -FImGuiDelegatesContainer FImGuiDelegatesContainer::DefaultInstance; +// Redirecting handle which will automatically bind to another one, if a different instance of the module is loaded. +struct FImGuiDelegatesContainerHandle : Utilities::TRedirectingHandle +{ + FImGuiDelegatesContainerHandle(FImGuiDelegatesContainer& InDefaultContainer) + : Utilities::TRedirectingHandle(InDefaultContainer) + { + if (FImGuiModule* Module = FModuleManager::GetModulePtr("ImGui")) + { + SetParent(&Module->GetDelegatesContainerHandle()); + } + } +}; -FImGuiDelegatesContainer* FImGuiDelegatesContainer::InstancePtr = &FImGuiDelegatesContainer::DefaultInstance; +static FImGuiDelegatesContainer DelegatesContainer; +static FImGuiDelegatesContainerHandle DelegatesHandle(DelegatesContainer); -void FImGuiDelegatesContainer::MoveContainer(FImGuiDelegatesContainer& Dst) +FImGuiDelegatesContainer& FImGuiDelegatesContainer::Get() +{ + return GetHandle().Get(); +} + +FImGuiDelegatesContainerHandle& FImGuiDelegatesContainer::GetHandle() +{ + return DelegatesHandle; +} + +void FImGuiDelegatesContainer::MoveContainer(FImGuiDelegatesContainerHandle& OtherContainerHandle) { // 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) + if (GetHandle().IsDefault()) { - Dst = MoveTemp(DefaultInstance); - DefaultInstance.Clear(); + OtherContainerHandle.Get() = MoveTemp(GetHandle().Get()); + GetHandle().Get().Clear(); } // Update pointer to the most recent version. - InstancePtr = &Dst; + GetHandle().SetParent(&OtherContainerHandle); } int32 FImGuiDelegatesContainer::GetContextIndex(UWorld* World) diff --git a/Source/ImGui/Private/ImGuiDelegatesContainer.h b/Source/ImGui/Private/ImGuiDelegatesContainer.h index 6d864b6..30be160 100644 --- a/Source/ImGui/Private/ImGuiDelegatesContainer.h +++ b/Source/ImGui/Private/ImGuiDelegatesContainer.h @@ -6,15 +6,20 @@ #include +struct FImGuiDelegatesContainerHandle; + struct FImGuiDelegatesContainer { public: // Get the current instance (can change during hot-reloading). - static FImGuiDelegatesContainer& Get() { return *InstancePtr; } + static FImGuiDelegatesContainer& Get(); - // 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 the handle to the container instance (can attach to other handles in hot-reloaded modules). + static FImGuiDelegatesContainerHandle& GetHandle(); + + // Redirect to the other container and if this one is still active move its data to the other one. + static void MoveContainer(FImGuiDelegatesContainerHandle& OtherContainerHandle); // Get delegate to ImGui world early debug event from known world instance. FSimpleMulticastDelegate& OnWorldEarlyDebug(UWorld* World) { return OnWorldEarlyDebug(GetContextIndex(World)); } @@ -44,10 +49,4 @@ private: TMap WorldDebugDelegates; FSimpleMulticastDelegate MultiContextEarlyDebugDelegate; FSimpleMulticastDelegate MultiContextDebugDelegate; - - // 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/ImGuiImplementation.cpp b/Source/ImGui/Private/ImGuiImplementation.cpp index 0abdd94..f66deca 100644 --- a/Source/ImGui/Private/ImGuiImplementation.cpp +++ b/Source/ImGui/Private/ImGuiImplementation.cpp @@ -18,12 +18,28 @@ #endif // PLATFORM_WINDOWS #if WITH_EDITOR -// Global ImGui context pointer. -ImGuiContext* GImGuiContextPtr = nullptr; -// Handle to the global ImGui context pointer. -ImGuiContext** GImGuiContextPtrHandle = &GImGuiContextPtr; + +#include "ImGuiModule.h" +#include "Utilities/RedirectingHandle.h" + +// Redirecting handle which will automatically bind to another one, if a different instance of the module is loaded. +struct FImGuiContextHandle : public Utilities::TRedirectingHandle +{ + FImGuiContextHandle(ImGuiContext*& InDefaultContext) + : Utilities::TRedirectingHandle(InDefaultContext) + { + if (FImGuiModule* Module = FModuleManager::GetModulePtr("ImGui")) + { + SetParent(&Module->GetImGuiContextHandle()); + } + } +}; + +static ImGuiContext* ImGuiContextPtr = nullptr; +static FImGuiContextHandle ImGuiContextPtrHandle(ImGuiContextPtr); + // Get the global ImGui context pointer (GImGui) indirectly to allow redirections in obsolete modules. -#define GImGui (*GImGuiContextPtrHandle) +#define GImGui (ImGuiContextPtrHandle.Get()) #endif // WITH_EDITOR #include "imgui.cpp" @@ -41,14 +57,14 @@ ImGuiContext** GImGuiContextPtrHandle = &GImGuiContextPtr; namespace ImGuiImplementation { #if WITH_EDITOR - ImGuiContext** GetImGuiContextHandle() + FImGuiContextHandle& GetContextHandle() { - return GImGuiContextPtrHandle; + return ImGuiContextPtrHandle; } - void SetImGuiContextHandle(ImGuiContext** Handle) + void SetParentContextHandle(FImGuiContextHandle& Parent) { - GImGuiContextPtrHandle = Handle; + ImGuiContextPtrHandle.SetParent(&Parent); } #endif // WITH_EDITOR } diff --git a/Source/ImGui/Private/ImGuiImplementation.h b/Source/ImGui/Private/ImGuiImplementation.h index aa40d23..f289e33 100644 --- a/Source/ImGui/Private/ImGuiImplementation.h +++ b/Source/ImGui/Private/ImGuiImplementation.h @@ -2,16 +2,16 @@ #pragma once -struct ImGuiContext; +struct FImGuiContextHandle; // Gives access to selected ImGui implementation features. namespace ImGuiImplementation { #if WITH_EDITOR // Get the handle to the ImGui Context pointer. - ImGuiContext** GetImGuiContextHandle(); + FImGuiContextHandle& GetContextHandle(); // Set the ImGui Context pointer handle. - void SetImGuiContextHandle(ImGuiContext** Handle); + void SetParentContextHandle(FImGuiContextHandle& Parent); #endif // WITH_EDITOR } diff --git a/Source/ImGui/Private/ImGuiModule.cpp b/Source/ImGui/Private/ImGuiModule.cpp index e7d7ac3..2b58d9a 100644 --- a/Source/ImGui/Private/ImGuiModule.cpp +++ b/Source/ImGui/Private/ImGuiModule.cpp @@ -149,9 +149,9 @@ void FImGuiModule::ShutdownModule() if (&LoadedModule != this) { // Statically bound functions will be still made to the obsolete module so we need to - ImGuiImplementation::SetImGuiContextHandle(LoadedModule.GetImGuiContextHandle()); + ImGuiImplementation::SetParentContextHandle(LoadedModule.GetImGuiContextHandle()); - FImGuiDelegatesContainer::MoveContainer(LoadedModule.GetDelegatesContainer()); + FImGuiDelegatesContainer::MoveContainer(LoadedModule.GetDelegatesContainerHandle()); if (bMoveProperties) { @@ -170,14 +170,14 @@ void FImGuiModule::SetProperties(const FImGuiModuleProperties& Properties) ImGuiModuleManager->GetProperties() = Properties; } -ImGuiContext** FImGuiModule::GetImGuiContextHandle() +FImGuiContextHandle& FImGuiModule::GetImGuiContextHandle() { - return ImGuiImplementation::GetImGuiContextHandle(); + return ImGuiImplementation::GetContextHandle(); } -FImGuiDelegatesContainer& FImGuiModule::GetDelegatesContainer() +FImGuiDelegatesContainerHandle& FImGuiModule::GetDelegatesContainerHandle() { - return FImGuiDelegatesContainer::Get(); + return FImGuiDelegatesContainer::GetHandle(); } #endif diff --git a/Source/ImGui/Private/Utilities/RedirectingHandle.h b/Source/ImGui/Private/Utilities/RedirectingHandle.h new file mode 100644 index 0000000..abec0d1 --- /dev/null +++ b/Source/ImGui/Private/Utilities/RedirectingHandle.h @@ -0,0 +1,96 @@ +// Distributed under the MIT License (MIT) (see accompanying LICENSE file) + +#pragma once + +#include +#include + + +namespace Utilities +{ + // Handle initialized as a pointer to a default value, but once attached it will follow the parent handle. + // When detached it will revert back to the default value. Intended for cross-module redirections. + template + struct TRedirectingHandle + { + TRedirectingHandle(T& InDefaultValue) + : Handle(&InDefaultValue) + , DefaultHandle(&InDefaultValue) + { + } + + ~TRedirectingHandle() + { + // Broadcast null pointer as a request to detach. + OnRedirectionUpdate.Broadcast(nullptr); + } + + // Check whether this handle points to the default value. + bool IsDefault() const + { + return (Handle == DefaultHandle); + } + + // Get the current value. + T& Get() const + { + return *Handle; + } + + // Set the other handle as a parent to this one. Passing null or itself will result with detaching from + // the current parent (if any) and reverting back to the default value. + void SetParent(TRedirectingHandle* InParent) + { + if (InParent != Parent) + { + if (Parent) + { + Parent->OnRedirectionUpdate.RemoveAll(this); + } + + // Protecting from setting itself as a parent. + Parent = (Parent != this) ? InParent : nullptr; + + if (Parent) + { + Parent->OnRedirectionUpdate.AddRaw(this, &TRedirectingHandle::UpdateRedirection); + } + + SetHandle((Parent) ? Parent->Handle : DefaultHandle); + } + } + + protected: + + void UpdateRedirection(T* InHandle) + { + if (InHandle) + { + SetHandle(InHandle); + } + else + { + // Interpreting null as a signal to detach. + SetParent(nullptr); + } + } + + void SetHandle(T* InHandle) + { + if (InHandle != Handle) + { + Handle = InHandle; + OnRedirectionUpdate.Broadcast(Handle); + } + } + + T* Handle; + T* DefaultHandle; + + TRedirectingHandle* Parent = nullptr; + + // Event with a new handle value or null pointer as a signal to detach. + DECLARE_MULTICAST_DELEGATE_OneParam(FRedirectionUpdateDelegate, T*); + FRedirectionUpdateDelegate OnRedirectionUpdate; + }; +} diff --git a/Source/ImGui/Public/ImGuiModule.h b/Source/ImGui/Public/ImGuiModule.h index 1bc2b75..239854c 100644 --- a/Source/ImGui/Public/ImGuiModule.h +++ b/Source/ImGui/Public/ImGuiModule.h @@ -164,11 +164,13 @@ public: virtual void StartupModule() override; virtual void ShutdownModule() override; - private: +private: #if WITH_EDITOR + friend struct FImGuiContextHandle; + friend struct FImGuiDelegatesContainerHandle; virtual void SetProperties(const FImGuiModuleProperties& Properties); - virtual struct ImGuiContext** GetImGuiContextHandle(); - virtual struct FImGuiDelegatesContainer& GetDelegatesContainer(); + virtual FImGuiContextHandle& GetImGuiContextHandle(); + virtual FImGuiDelegatesContainerHandle& GetDelegatesContainerHandle(); #endif };