Added interface to register ImGui debug delegates.

This commit is contained in:
Sebastian 2017-10-03 21:16:17 +01:00
parent 1a6aa98f51
commit 00ad746267
7 changed files with 211 additions and 7 deletions

View File

@ -72,7 +72,7 @@ void FImGuiContextManager::Tick(float DeltaSeconds)
if (ContextData.CanTick()) if (ContextData.CanTick())
{ {
ContextData.ContextProxy.SetAsCurrent(); ContextData.ContextProxy.SetAsCurrent();
ContextData.ContextProxy.Tick(DeltaSeconds); ContextData.ContextProxy.Tick(DeltaSeconds, &DrawMultiContextEvent);
} }
} }
} }
@ -105,7 +105,7 @@ FImGuiContextManager::FContextData& FImGuiContextManager::GetStandaloneWorldCont
} }
#endif // !WITH_EDITOR #endif // !WITH_EDITOR
FImGuiContextManager::FContextData& FImGuiContextManager::GetWorldContextData(const UWorld& World) FImGuiContextManager::FContextData& FImGuiContextManager::GetWorldContextData(const UWorld& World, int32* OutIndex)
{ {
using namespace Utilities; using namespace Utilities;
@ -140,5 +140,9 @@ FImGuiContextManager::FContextData& FImGuiContextManager::GetWorldContextData(co
} }
#endif #endif
if (OutIndex)
{
*OutIndex = Index;
}
return *Data; return *Data;
} }

View File

@ -34,6 +34,9 @@ public:
// Get or create ImGui context proxy for given world. // Get or create ImGui context proxy for given world.
FORCEINLINE FImGuiContextProxy& GetWorldContextProxy(const UWorld& World) { return GetWorldContextData(World).ContextProxy; } FORCEINLINE FImGuiContextProxy& GetWorldContextProxy(const UWorld& World) { return GetWorldContextData(World).ContextProxy; }
// Get or create ImGui context proxy for given world. Additionally get context index for that proxy.
FORCEINLINE FImGuiContextProxy& GetWorldContextProxy(const UWorld& World, int32& OutContextIndex) { return GetWorldContextData(World, &OutContextIndex).ContextProxy; }
// Get context proxy by index, or null if context with that index doesn't exist. // Get context proxy by index, or null if context with that index doesn't exist.
FORCEINLINE FImGuiContextProxy* GetContextProxy(int32 ContextIndex) FORCEINLINE FImGuiContextProxy* GetContextProxy(int32 ContextIndex)
{ {
@ -41,6 +44,10 @@ public:
return Data ? &(Data->ContextProxy) : nullptr; return Data ? &(Data->ContextProxy) : nullptr;
} }
// Delegate called for all contexts in manager, right after calling context specific draw event. Allows listeners
// draw the same content to multiple contexts.
FSimpleMulticastDelegate& OnDrawMultiContext() { return DrawMultiContextEvent; }
void Tick(float DeltaSeconds); void Tick(float DeltaSeconds);
private: private:
@ -87,9 +94,11 @@ private:
FContextData& GetStandaloneWorldContextData(); FContextData& GetStandaloneWorldContextData();
#endif #endif
FContextData& GetWorldContextData(const UWorld& World); FContextData& GetWorldContextData(const UWorld& World, int32* OutContextIndex = nullptr);
TMap<int32, FContextData> Contexts; TMap<int32, FContextData> Contexts;
FImGuiDemo ImGuiDemo; FImGuiDemo ImGuiDemo;
FSimpleMulticastDelegate DrawMultiContextEvent;
}; };

View File

@ -117,7 +117,7 @@ FImGuiContextProxy::~FImGuiContextProxy()
} }
} }
void FImGuiContextProxy::Tick(float DeltaSeconds) void FImGuiContextProxy::Tick(float DeltaSeconds, FSimpleMulticastDelegate* SharedDrawEvent)
{ {
if (bIsFrameStarted) if (bIsFrameStarted)
{ {
@ -126,6 +126,10 @@ void FImGuiContextProxy::Tick(float DeltaSeconds)
{ {
DrawEvent.Broadcast(); DrawEvent.Broadcast();
} }
if (SharedDrawEvent && SharedDrawEvent->IsBound())
{
SharedDrawEvent->Broadcast();
}
// Ending frame will produce render output that we capture and store for later use. This also puts context to // Ending frame will produce render output that we capture and store for later use. This also puts context to
// state in which it does not allow to draw controls, so we want to immediately start a new frame. // state in which it does not allow to draw controls, so we want to immediately start a new frame.

View File

@ -53,7 +53,8 @@ public:
FSimpleMulticastDelegate& OnDraw() { return DrawEvent; } FSimpleMulticastDelegate& OnDraw() { return DrawEvent; }
// Tick to advance context to the next frame. // Tick to advance context to the next frame.
void Tick(float DeltaSeconds); // @param SharedDrawEvent - Shared draw event provided from outside to be called right after context own event
void Tick(float DeltaSeconds, FSimpleMulticastDelegate* SharedDrawEvent = nullptr);
private: private:

View File

@ -3,6 +3,8 @@
#include "ImGuiPrivatePCH.h" #include "ImGuiPrivatePCH.h"
#include "ImGuiModuleManager.h" #include "ImGuiModuleManager.h"
#include "Utilities/WorldContext.h"
#include "Utilities/WorldContextIndex.h"
#include <IPluginManager.h> #include <IPluginManager.h>
@ -10,8 +12,77 @@
#define LOCTEXT_NAMESPACE "FImGuiModule" #define LOCTEXT_NAMESPACE "FImGuiModule"
struct EDelegateCategory
{
enum
{
// Default per-context draw events.
Default,
// Multi-context draw event defined in context manager.
MultiContext
};
};
static FImGuiModuleManager* ModuleManager = nullptr; static FImGuiModuleManager* ModuleManager = nullptr;
#if WITH_EDITOR
FImGuiDelegateHandle FImGuiModule::AddEditorImGuiDelegate(const FImGuiDelegate& Delegate)
{
checkf(ModuleManager, TEXT("Null pointer to internal module implementation. Is module available?"));
return { ModuleManager->GetContextManager().GetEditorContextProxy().OnDraw().Add(Delegate),
EDelegateCategory::Default, Utilities::EDITOR_CONTEXT_INDEX };
}
#endif
FImGuiDelegateHandle FImGuiModule::AddWorldImGuiDelegate(const FImGuiDelegate& Delegate)
{
checkf(ModuleManager, TEXT("Null pointer to internal module implementation. Is module available?"));
#if WITH_EDITOR
checkf(GEngine, TEXT("Null GEngine. AddWorldImGuiDelegate should be only called with GEngine initialized."));
const FWorldContext* WorldContext = Utilities::GetWorldContext(GEngine->GameViewport);
if (!WorldContext)
{
WorldContext = Utilities::GetWorldContextFromNetMode(ENetMode::NM_DedicatedServer);
}
checkf(WorldContext, TEXT("Couldn't find current world. AddWorldImGuiDelegate should be only called from a valid world."));
int32 Index;
FImGuiContextProxy& Proxy = ModuleManager->GetContextManager().GetWorldContextProxy(*WorldContext->World(), Index);
#else
const int32 Index = Utilities::STANDALONE_GAME_CONTEXT_INDEX;
FImGuiContextProxy& Proxy = ModuleManager->GetContextManager().GetWorldContextProxy();
#endif
return{ Proxy.OnDraw().Add(Delegate), EDelegateCategory::Default, Index };
}
FImGuiDelegateHandle FImGuiModule::AddMultiContextImGuiDelegate(const FImGuiDelegate& Delegate)
{
checkf(ModuleManager, TEXT("Null pointer to internal module implementation. Is module available?"));
return { ModuleManager->GetContextManager().OnDrawMultiContext().Add(Delegate), EDelegateCategory::MultiContext };
}
void FImGuiModule::RemoveImGuiDelegate(const FImGuiDelegateHandle& Handle)
{
if (ModuleManager)
{
if (Handle.Category == EDelegateCategory::MultiContext)
{
ModuleManager->GetContextManager().OnDrawMultiContext().Remove(Handle.Handle);
}
else if (auto* Proxy = ModuleManager->GetContextManager().GetContextProxy(Handle.Index))
{
Proxy->OnDraw().Remove(Handle.Handle);
}
}
}
void FImGuiModule::StartupModule() void FImGuiModule::StartupModule()
{ {
checkf(!ModuleManager, TEXT("Instance of Module Manager already exists. Instance should be created only during module startup.")); checkf(!ModuleManager, TEXT("Instance of Module Manager already exists. Instance should be created only during module startup."));
@ -23,12 +94,12 @@ void FImGuiModule::StartupModule()
void FImGuiModule::ShutdownModule() void FImGuiModule::ShutdownModule()
{ {
checkf(ModuleManager, TEXT("Null Module Manager. Manager instance should be deleted during module shutdown.")); checkf(ModuleManager, TEXT("Null Module Manager. Manager instance should be deleted during module shutdown."));
// Before we shutdown we need to delete manager that will do all necessary cleanup. // Before we shutdown we need to delete manager that will do all necessary cleanup.
delete ModuleManager; delete ModuleManager;
ModuleManager = nullptr; ModuleManager = nullptr;
} }
#undef LOCTEXT_NAMESPACE #undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FImGuiModule, ImGui) IMPLEMENT_MODULE(FImGuiModule, ImGui)

View File

@ -0,0 +1,55 @@
// Distributed under the MIT License (MIT) (see accompanying LICENSE file)
#pragma once
#include <Core.h>
/** Delegate that allows to subscribe for ImGui events. */
typedef FSimpleMulticastDelegate::FDelegate FImGuiDelegate;
/**
* Handle to ImGui delegate. Contains additional information locating delegates in different contexts.
*/
class FImGuiDelegateHandle
{
public:
FImGuiDelegateHandle() = default;
bool IsValid() const
{
return Handle.IsValid();
}
void Reset()
{
Handle.Reset();
Index = 0;
}
private:
FImGuiDelegateHandle(const FDelegateHandle& InHandle, int32 InCategory, int32 InIndex = 0)
: Handle(InHandle)
, Category(InCategory)
, Index(InIndex)
{
}
friend bool operator==(const FImGuiDelegateHandle& Lhs, const FImGuiDelegateHandle& Rhs)
{
return Lhs.Handle == Rhs.Handle && Lhs.Category == Rhs.Category && Lhs.Index == Rhs.Index;
}
friend bool operator!=(const FImGuiDelegateHandle& Lhs, const FImGuiDelegateHandle& Rhs)
{
return !(Lhs == Rhs);
}
FDelegateHandle Handle;
int32 Category = 0;
int32 Index = 0;
friend class FImGuiModule;
};

View File

@ -2,6 +2,8 @@
#pragma once #pragma once
#include "ImGuiDelegates.h"
#include <ModuleManager.h> #include <ModuleManager.h>
@ -9,6 +11,64 @@ class FImGuiModule : public IModuleInterface
{ {
public: public:
/**
* Singleton-like access to this module's interface. This is just for convenience!
* Beware of calling this during the shutdown phase, though. Your module might have been unloaded already.
*
* @return Returns singleton instance, loading the module on demand if needed
*/
static inline FImGuiModule& Get()
{
return FModuleManager::LoadModuleChecked<FImGuiModule>("ImGui");
}
/**
* Checks to see if this module is loaded and ready. It is only valid to call Get() if IsAvailable() returns true.
*
* @return True if the module is loaded and ready to use
*/
static inline bool IsAvailable()
{
return FModuleManager::Get().IsModuleLoaded("ImGui");
}
#if WITH_EDITOR
/**
* Add a delegate called at the end of editor debug frame to draw debug controls in its ImGui context, creating
* that context on demand.
*
* @param Delegate - Delegate that we want to add (@see FImGuiDelegate::Create...)
* @returns Returns handle that can be used to remove delegate (@see RemoveImGuiDelegate)
*/
virtual FImGuiDelegateHandle AddEditorImGuiDelegate(const FImGuiDelegate& Delegate);
#endif
/**
* Add a delegate called at the end of current world debug frame to draw debug controls in its ImGui context,
* creating that context on demand.
* This function will throw if called outside of a world context (i.e. current world cannot be found).
*
* @param Delegate - Delegate that we want to add (@see FImGuiDelegate::Create...)
* @returns Returns handle that can be used to remove delegate (@see RemoveImGuiDelegate)
*/
virtual FImGuiDelegateHandle AddWorldImGuiDelegate(const FImGuiDelegate& Delegate);
/**
* Add shared delegate called for each ImGui context at the end of debug frame, after calling context specific
* delegate. This delegate will be used for any ImGui context, created before or after it is registered.
*
* @param Delegate - Delegate that we want to add (@see FImGuiDelegate::Create...)
* @returns Returns handle that can be used to remove delegate (@see RemoveImGuiDelegate)
*/
virtual FImGuiDelegateHandle AddMultiContextImGuiDelegate(const FImGuiDelegate& Delegate);
/**
* Remove delegate added with any version of Add...ImGuiDelegate
*
* @param Handle - Delegate handle that was returned by adding function
*/
virtual void RemoveImGuiDelegate(const FImGuiDelegateHandle& Handle);
/** IModuleInterface implementation */ /** IModuleInterface implementation */
virtual void StartupModule() override; virtual void StartupModule() override;
virtual void ShutdownModule() override; virtual void ShutdownModule() override;