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.
This commit is contained in:
Sebastian 2020-07-31 10:24:53 +01:00
parent 9eed8f93b9
commit a76f4bc451
8 changed files with 176 additions and 37 deletions

View File

@ -8,6 +8,8 @@ Change History
Version: 1.21 (2020/07) Version: 1.21 (2020/07)
Improving stability Improving stability
- Fixed a crash in the input handler caused by invalidated by hot-reload instance trying to unregister a delegate. - 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) Version: 1.20 (2020/06)
Transition to IWYU and maintenance: Transition to IWYU and maintenance:

View File

@ -2,25 +2,49 @@
#include "ImGuiDelegatesContainer.h" #include "ImGuiDelegatesContainer.h"
#include "ImGuiModule.h"
#include "Utilities/RedirectingHandle.h"
#include "Utilities/WorldContextIndex.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<FImGuiDelegatesContainer>
{
FImGuiDelegatesContainerHandle(FImGuiDelegatesContainer& InDefaultContainer)
: Utilities::TRedirectingHandle<FImGuiDelegatesContainer>(InDefaultContainer)
{
if (FImGuiModule* Module = FModuleManager::GetModulePtr<FImGuiModule>("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 // 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. // keep pointer to a more recent version.
if (InstancePtr == &DefaultInstance) if (GetHandle().IsDefault())
{ {
Dst = MoveTemp(DefaultInstance); OtherContainerHandle.Get() = MoveTemp(GetHandle().Get());
DefaultInstance.Clear(); GetHandle().Get().Clear();
} }
// Update pointer to the most recent version. // Update pointer to the most recent version.
InstancePtr = &Dst; GetHandle().SetParent(&OtherContainerHandle);
} }
int32 FImGuiDelegatesContainer::GetContextIndex(UWorld* World) int32 FImGuiDelegatesContainer::GetContextIndex(UWorld* World)

View File

@ -6,15 +6,20 @@
#include <Delegates/Delegate.h> #include <Delegates/Delegate.h>
struct FImGuiDelegatesContainerHandle;
struct FImGuiDelegatesContainer struct FImGuiDelegatesContainer
{ {
public: public:
// Get the current instance (can change during hot-reloading). // 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. // Get the handle to the container instance (can attach to other handles in hot-reloaded modules).
static void MoveContainer(FImGuiDelegatesContainer& Dst); 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. // Get delegate to ImGui world early debug event from known world instance.
FSimpleMulticastDelegate& OnWorldEarlyDebug(UWorld* World) { return OnWorldEarlyDebug(GetContextIndex(World)); } FSimpleMulticastDelegate& OnWorldEarlyDebug(UWorld* World) { return OnWorldEarlyDebug(GetContextIndex(World)); }
@ -44,10 +49,4 @@ private:
TMap<int32, FSimpleMulticastDelegate> WorldDebugDelegates; TMap<int32, FSimpleMulticastDelegate> WorldDebugDelegates;
FSimpleMulticastDelegate MultiContextEarlyDebugDelegate; FSimpleMulticastDelegate MultiContextEarlyDebugDelegate;
FSimpleMulticastDelegate MultiContextDebugDelegate; FSimpleMulticastDelegate MultiContextDebugDelegate;
// Default container instance.
static FImGuiDelegatesContainer DefaultInstance;
// Pointer to the container instance that can be overwritten during hot-reloading.
static FImGuiDelegatesContainer* InstancePtr;
}; };

View File

@ -18,12 +18,28 @@
#endif // PLATFORM_WINDOWS #endif // PLATFORM_WINDOWS
#if WITH_EDITOR #if WITH_EDITOR
// Global ImGui context pointer.
ImGuiContext* GImGuiContextPtr = nullptr; #include "ImGuiModule.h"
// Handle to the global ImGui context pointer. #include "Utilities/RedirectingHandle.h"
ImGuiContext** GImGuiContextPtrHandle = &GImGuiContextPtr;
// Redirecting handle which will automatically bind to another one, if a different instance of the module is loaded.
struct FImGuiContextHandle : public Utilities::TRedirectingHandle<ImGuiContext*>
{
FImGuiContextHandle(ImGuiContext*& InDefaultContext)
: Utilities::TRedirectingHandle<ImGuiContext*>(InDefaultContext)
{
if (FImGuiModule* Module = FModuleManager::GetModulePtr<FImGuiModule>("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. // Get the global ImGui context pointer (GImGui) indirectly to allow redirections in obsolete modules.
#define GImGui (*GImGuiContextPtrHandle) #define GImGui (ImGuiContextPtrHandle.Get())
#endif // WITH_EDITOR #endif // WITH_EDITOR
#include "imgui.cpp" #include "imgui.cpp"
@ -41,14 +57,14 @@ ImGuiContext** GImGuiContextPtrHandle = &GImGuiContextPtr;
namespace ImGuiImplementation namespace ImGuiImplementation
{ {
#if WITH_EDITOR #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 #endif // WITH_EDITOR
} }

View File

@ -2,16 +2,16 @@
#pragma once #pragma once
struct ImGuiContext; struct FImGuiContextHandle;
// Gives access to selected ImGui implementation features. // Gives access to selected ImGui implementation features.
namespace ImGuiImplementation namespace ImGuiImplementation
{ {
#if WITH_EDITOR #if WITH_EDITOR
// Get the handle to the ImGui Context pointer. // Get the handle to the ImGui Context pointer.
ImGuiContext** GetImGuiContextHandle(); FImGuiContextHandle& GetContextHandle();
// Set the ImGui Context pointer handle. // Set the ImGui Context pointer handle.
void SetImGuiContextHandle(ImGuiContext** Handle); void SetParentContextHandle(FImGuiContextHandle& Parent);
#endif // WITH_EDITOR #endif // WITH_EDITOR
} }

View File

@ -149,9 +149,9 @@ void FImGuiModule::ShutdownModule()
if (&LoadedModule != this) if (&LoadedModule != this)
{ {
// Statically bound functions will be still made to the obsolete module so we need to // 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) if (bMoveProperties)
{ {
@ -170,14 +170,14 @@ void FImGuiModule::SetProperties(const FImGuiModuleProperties& Properties)
ImGuiModuleManager->GetProperties() = 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 #endif

View File

@ -0,0 +1,96 @@
// Distributed under the MIT License (MIT) (see accompanying LICENSE file)
#pragma once
#include <Delegates/Delegate.h>
#include <Delegates/DelegateCombinations.h>
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<typename T>
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;
};
}

View File

@ -164,11 +164,13 @@ public:
virtual void StartupModule() override; virtual void StartupModule() override;
virtual void ShutdownModule() override; virtual void ShutdownModule() override;
private: private:
#if WITH_EDITOR #if WITH_EDITOR
friend struct FImGuiContextHandle;
friend struct FImGuiDelegatesContainerHandle;
virtual void SetProperties(const FImGuiModuleProperties& Properties); virtual void SetProperties(const FImGuiModuleProperties& Properties);
virtual struct ImGuiContext** GetImGuiContextHandle(); virtual FImGuiContextHandle& GetImGuiContextHandle();
virtual struct FImGuiDelegatesContainer& GetDelegatesContainer(); virtual FImGuiDelegatesContainerHandle& GetDelegatesContainerHandle();
#endif #endif
}; };