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.
This commit is contained in:
Sebastian 2019-04-10 20:19:11 +01:00
parent 2b5d871609
commit 867a34e640
13 changed files with 253 additions and 17 deletions

View File

@ -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.

View File

@ -1,7 +1,7 @@
{
"FileVersion": 3,
"Version": 1,
"VersionName": "1.14",
"VersionName": "1.15",
"FriendlyName": "ImGui",
"Description": "",
"Category": "Debug",

View File

@ -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();
}
}
}

View File

@ -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)
{
}

View File

@ -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();
}
}

View File

@ -4,6 +4,8 @@
#include "ImGuiDrawData.h"
#include "Utilities/WorldContextIndex.h"
#include <ICursor.h>
#include <imgui.h>
@ -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<FImGuiDrawList> DrawLists;
int32 ContextIndex = Utilities::INVALID_CONTEXT_INDEX;
FString Name;
std::string IniFilename;
};

View File

@ -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();
}

View File

@ -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();
}

View File

@ -0,0 +1,40 @@
// Distributed under the MIT License (MIT) (see accompanying LICENSE file)
#pragma once
#include <Containers/Map.h>
#include <Delegates/Delegate.h>
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<int32, FSimpleMulticastDelegate> WorldDelegates;
FSimpleMulticastDelegate MultiContextDelegate;
// Default container instance.
static FImGuiDelegatesContainer DefaultInstance;
// Pointer to the container instance that can be overwritten during hot-reloading.
static FImGuiDelegatesContainer* InstancePtr;
};

View File

@ -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 <IPluginManager.h>
#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()

View File

@ -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<typename T>

View File

@ -5,6 +5,43 @@
#include <Core.h>
/**
* 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

View File

@ -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
};