Added ImGui early debug events:

- Added to FImGuiDelegates interface early debug delegates called during world tick start.
- Delegates are called in fixed order: multi-context early debug, world early debug (called during world tick start), world debug, multi-context debug (called during world post actor tick or if not available, during world tick start).
- Removed from build script configuration of debug delegates.
This commit is contained in:
Sebastian 2019-04-14 12:16:15 +01:00
parent 867a34e640
commit 4a9d66889b
11 changed files with 143 additions and 91 deletions

View File

@ -11,6 +11,9 @@ Version: 1.15 (2019/04)
- DEPRECIATED old FImGuiModule delegates interface and FImGuiDelegateHandle. - 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. - 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). - Added IMGUI_WITH_OBSOLETE_DELEGATES allowing to strip depreciated interface from builds (that interface will be officially removed in one of later releases).
- Added new ImGui early debug delegates called during world tick start.
- Delegates are called in fixed order: multi-context early debug, world early debug (called during world tick start), world debug, multi-context debug (called during world post actor tick or if not available, during world tick start).
- Removed from build script configuration of debug delegates.
Version: 1.14 (2019/03) Version: 1.14 (2019/03)
- Added SImGuiLayout to resets layout for SImGuiWidget. - Added SImGuiLayout to resets layout for SImGuiWidget.

View File

@ -1,45 +1,11 @@
// Distributed under the MIT License (MIT) (see accompanying LICENSE file) // Distributed under the MIT License (MIT) (see accompanying LICENSE file)
#if UE_4_18_OR_LATER
#define WITH_POST_ACTOR_TICK
#endif
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using UnrealBuildTool; using UnrealBuildTool;
public class ImGui : ModuleRules public class ImGui : ModuleRules
{ {
// Defines when events should be broadcast. Note that at the end of the ImGui frame some global variables might be
// not set and so it seems preferable to use post actor tick (not available in older engine versions) or world tick
// start events. If more control is required Late mode with manually calling events may be used (as in practice
// events are guaranteed to be raised only once per frame).
enum EEventsBroadcastMode
{
OnWorldTickStart, // Broadcast during world tick start event.
#if WITH_POST_ACTOR_TICK
OnPostActorTick, // Broadcast during post actor tick event.
#endif
Late, // Broadcast at the end of the ImGui frame.
}
// Defines order in which multi-context and world-context events are called.
enum EEventsOrder
{
MultiBeforeWorldContext,
WorldBeforeMultiContext,
}
#if WITH_POST_ACTOR_TICK
EEventsBroadcastMode DrawEventsBroadcastMode = EEventsBroadcastMode.OnPostActorTick;
EEventsOrder DrawEventsOrder = EEventsOrder.WorldBeforeMultiContext;
#else
EEventsBroadcastMode DrawEventsBroadcastMode = EEventsBroadcastMode.OnWorldTickStart;
EEventsOrder DrawEventsOrder = EEventsOrder.MultiBeforeWorldContext;
#endif // WITH_POST_ACTOR_TICK
#if WITH_FORWARDED_MODULE_RULES_CTOR #if WITH_FORWARDED_MODULE_RULES_CTOR
public ImGui(ReadOnlyTargetRules Target) : base(Target) public ImGui(ReadOnlyTargetRules Target) : base(Target)
#else #else
@ -128,17 +94,5 @@ public class ImGui : ModuleRules
#endif #endif
PrivateDefinitions.Add(string.Format("RUNTIME_LOADER_ENABLED={0}", bEnableRuntimeLoader ? 1 : 0)); PrivateDefinitions.Add(string.Format("RUNTIME_LOADER_ENABLED={0}", bEnableRuntimeLoader ? 1 : 0));
bool bDrawOnWorldTickStart = (DrawEventsBroadcastMode == EEventsBroadcastMode.OnWorldTickStart);
#if WITH_POST_ACTOR_TICK
bool bDrawOnPostActorTick = (DrawEventsBroadcastMode == EEventsBroadcastMode.OnPostActorTick);
#else
bool bDrawOnPostActorTick = false;
#endif
PrivateDefinitions.Add(string.Format("DRAW_EVENTS_ON_WORLD_TICK_START={0}", bDrawOnWorldTickStart ? 1 : 0));
PrivateDefinitions.Add(string.Format("DRAW_EVENTS_ON_POST_ACTOR_TICK={0}", bDrawOnPostActorTick ? 1 : 0));
PrivateDefinitions.Add(string.Format("DRAW_EVENTS_ORDER_WORLD_BEFORE_MULTI_CONTEXT={0}",
(DrawEventsOrder == EEventsOrder.WorldBeforeMultiContext) ? 1 : 0));
} }
} }

View File

@ -64,7 +64,7 @@ FImGuiContextManager::FImGuiContextManager()
FontAtlas.GetTexDataAsRGBA32(&Pixels, &Width, &Height, &Bpp); FontAtlas.GetTexDataAsRGBA32(&Pixels, &Width, &Height, &Bpp);
FWorldDelegates::OnWorldTickStart.AddRaw(this, &FImGuiContextManager::OnWorldTickStart); FWorldDelegates::OnWorldTickStart.AddRaw(this, &FImGuiContextManager::OnWorldTickStart);
#if DRAW_EVENTS_ON_POST_ACTOR_TICK #if ENGINE_COMPATIBILITY_WITH_WORLD_POST_ACTOR_TICK
FWorldDelegates::OnWorldPostActorTick.AddRaw(this, &FImGuiContextManager::OnWorldPostActorTick); FWorldDelegates::OnWorldPostActorTick.AddRaw(this, &FImGuiContextManager::OnWorldPostActorTick);
#endif #endif
} }
@ -73,7 +73,7 @@ FImGuiContextManager::~FImGuiContextManager()
{ {
// Order matters because contexts can be created during World Tick Start events. // Order matters because contexts can be created during World Tick Start events.
FWorldDelegates::OnWorldTickStart.RemoveAll(this); FWorldDelegates::OnWorldTickStart.RemoveAll(this);
#if DRAW_EVENTS_ON_POST_ACTOR_TICK #if ENGINE_COMPATIBILITY_WITH_WORLD_POST_ACTOR_TICK
FWorldDelegates::OnWorldPostActorTick.RemoveAll(this); FWorldDelegates::OnWorldPostActorTick.RemoveAll(this);
#endif #endif
} }
@ -103,22 +103,23 @@ void FImGuiContextManager::OnWorldTickStart(ELevelTick TickType, float DeltaSeco
if (GWorld) if (GWorld)
{ {
FImGuiContextProxy& ContextProxy = GetWorldContextProxy(*GWorld); FImGuiContextProxy& ContextProxy = GetWorldContextProxy(*GWorld);
// Set as current, so we have right context ready when updating world objects.
ContextProxy.SetAsCurrent(); ContextProxy.SetAsCurrent();
#if DRAW_EVENTS_ON_WORLD_TICK_START ContextProxy.DrawEarlyDebug();
ContextProxy.Draw(); #if !ENGINE_COMPATIBILITY_WITH_WORLD_POST_ACTOR_TICK
ContextProxy.DrawDebug();
#endif #endif
} }
} }
#if DRAW_EVENTS_ON_POST_ACTOR_TICK #if ENGINE_COMPATIBILITY_WITH_WORLD_POST_ACTOR_TICK
void FImGuiContextManager::OnWorldPostActorTick(UWorld* World, ELevelTick TickType, float DeltaSeconds) void FImGuiContextManager::OnWorldPostActorTick(UWorld* World, ELevelTick TickType, float DeltaSeconds)
{ {
FImGuiContextProxy& ContextProxy = GetWorldContextProxy(*GWorld); GetWorldContextProxy(*GWorld).DrawDebug();
ContextProxy.SetAsCurrent();
ContextProxy.Draw();
} }
#endif // DRAW_EVENTS_ON_POST_ACTOR_TICK #endif // ENGINE_COMPATIBILITY_WITH_WORLD_POST_ACTOR_TICK
#if WITH_EDITOR #if WITH_EDITOR
FImGuiContextManager::FContextData& FImGuiContextManager::GetEditorContextData() FImGuiContextManager::FContextData& FImGuiContextManager::GetEditorContextData()

View File

@ -100,7 +100,7 @@ private:
void OnWorldTickStart(ELevelTick TickType, float DeltaSeconds); void OnWorldTickStart(ELevelTick TickType, float DeltaSeconds);
#if DRAW_EVENTS_ON_POST_ACTOR_TICK #if ENGINE_COMPATIBILITY_WITH_WORLD_POST_ACTOR_TICK
void OnWorldPostActorTick(UWorld* World, ELevelTick TickType, float DeltaSeconds); void OnWorldPostActorTick(UWorld* World, ELevelTick TickType, float DeltaSeconds);
#endif #endif

View File

@ -83,24 +83,34 @@ FImGuiContextProxy::FImGuiContextProxy(const FString& InName, int32 InContextInd
BeginFrame(); BeginFrame();
} }
void FImGuiContextProxy::Draw() void FImGuiContextProxy::DrawEarlyDebug()
{ {
if (bIsFrameStarted && !bIsDrawCalled) if (bIsFrameStarted && !bIsDrawEarlyDebugCalled)
{ {
bIsDrawCalled = true; bIsDrawEarlyDebugCalled = true;
SetAsCurrent(); SetAsCurrent();
// Broadcast draw event to allow listeners to draw their controls to this context. // Delegates called in order specified in FImGuiDelegates.
#if DRAW_EVENTS_ORDER_WORLD_BEFORE_MULTI_CONTEXT BroadcastMultiContextEarlyDebug();
BroadcastWorldTick(); BroadcastWorldEarlyDebug();
#endif // DRAW_EVENTS_ORDER_WORLD_BEFORE_MULTI_CONTEXT }
}
BroadcastMultiContextTick(); void FImGuiContextProxy::DrawDebug()
{
if (bIsFrameStarted && !bIsDrawDebugCalled)
{
bIsDrawDebugCalled = true;
#if !DRAW_EVENTS_ORDER_WORLD_BEFORE_MULTI_CONTEXT // Make sure that early debug is always called first to guarantee order specified in FImGuiDelegates.
BroadcastWorldTick(); DrawEarlyDebug();
#endif // !DRAW_EVENTS_ORDER_WORLD_BEFORE_MULTI_CONTEXT
SetAsCurrent();
// Delegates called in order specified in FImGuiDelegates.
BroadcastWorldDebug();
BroadcastMultiContextDebug();
} }
} }
@ -116,7 +126,7 @@ void FImGuiContextProxy::Tick(float DeltaSeconds)
if (bIsFrameStarted) if (bIsFrameStarted)
{ {
// Make sure that draw events are called before the end of the frame. // Make sure that draw events are called before the end of the frame.
Draw(); DrawDebug();
// 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.
@ -149,7 +159,8 @@ void FImGuiContextProxy::BeginFrame(float DeltaTime)
ImGui::NewFrame(); ImGui::NewFrame();
bIsFrameStarted = true; bIsFrameStarted = true;
bIsDrawCalled = false; bIsDrawEarlyDebugCalled = false;
bIsDrawDebugCalled = false;
} }
} }
@ -186,7 +197,28 @@ void FImGuiContextProxy::UpdateDrawData(ImDrawData* DrawData)
} }
} }
void FImGuiContextProxy::BroadcastWorldTick() void FImGuiContextProxy::BroadcastWorldEarlyDebug()
{
if (ContextIndex != Utilities::INVALID_CONTEXT_INDEX)
{
FSimpleMulticastDelegate& WorldEarlyDebugEvent = FImGuiDelegatesContainer::Get().OnWorldEarlyDebug(ContextIndex);
if (WorldEarlyDebugEvent.IsBound())
{
WorldEarlyDebugEvent.Broadcast();
}
}
}
void FImGuiContextProxy::BroadcastMultiContextEarlyDebug()
{
FSimpleMulticastDelegate& MultiContextEarlyDebugEvent = FImGuiDelegatesContainer::Get().OnMultiContextEarlyDebug();
if (MultiContextEarlyDebugEvent.IsBound())
{
MultiContextEarlyDebugEvent.Broadcast();
}
}
void FImGuiContextProxy::BroadcastWorldDebug()
{ {
if (DrawEvent.IsBound()) if (DrawEvent.IsBound())
{ {
@ -203,7 +235,7 @@ void FImGuiContextProxy::BroadcastWorldTick()
} }
} }
void FImGuiContextProxy::BroadcastMultiContextTick() void FImGuiContextProxy::BroadcastMultiContextDebug()
{ {
if (SharedDrawEvent && SharedDrawEvent->IsBound()) if (SharedDrawEvent && SharedDrawEvent->IsBound())
{ {

View File

@ -84,9 +84,11 @@ public:
// Delegate called right before ending the frame to allows listeners draw their controls. // Delegate called right before ending the frame to allows listeners draw their controls.
FSimpleMulticastDelegate& OnDraw() { return DrawEvent; } FSimpleMulticastDelegate& OnDraw() { return DrawEvent; }
// Call draw events to allow listeners draw their widgets. Only one call per frame is processed. If it is not // Call early debug events to allow listeners draw their debug widgets.
// called manually before, then it will be called from the Tick function. void DrawEarlyDebug();
void Draw();
// Call debug events to allow listeners draw their debug widgets.
void DrawDebug();
// Tick to advance context to the next frame. Only one call per frame will be processed. // Tick to advance context to the next frame. Only one call per frame will be processed.
void Tick(float DeltaSeconds); void Tick(float DeltaSeconds);
@ -98,8 +100,11 @@ private:
void UpdateDrawData(ImDrawData* DrawData); void UpdateDrawData(ImDrawData* DrawData);
void BroadcastWorldTick(); void BroadcastWorldEarlyDebug();
void BroadcastMultiContextTick(); void BroadcastMultiContextEarlyDebug();
void BroadcastWorldDebug();
void BroadcastMultiContextDebug();
FImGuiContextPtr Context; FImGuiContextPtr Context;
@ -109,7 +114,8 @@ private:
bool bHasActiveItem = false; bool bHasActiveItem = false;
bool bIsFrameStarted = false; bool bIsFrameStarted = false;
bool bIsDrawCalled = false; bool bIsDrawEarlyDebugCalled = false;
bool bIsDrawDebugCalled = false;
uint32 LastFrameNumber = 0; uint32 LastFrameNumber = 0;

View File

@ -6,6 +6,21 @@
#include "ImGuiDelegatesContainer.h" #include "ImGuiDelegatesContainer.h"
FSimpleMulticastDelegate& FImGuiDelegates::OnWorldEarlyDebug()
{
return OnWorldEarlyDebug(GWorld);
}
FSimpleMulticastDelegate& FImGuiDelegates::OnWorldEarlyDebug(UWorld* World)
{
return FImGuiDelegatesContainer::Get().OnWorldEarlyDebug(World);
}
FSimpleMulticastDelegate& FImGuiDelegates::OnMultiContextEarlyDebug()
{
return FImGuiDelegatesContainer::Get().OnMultiContextEarlyDebug();
}
FSimpleMulticastDelegate& FImGuiDelegates::OnWorldDebug() FSimpleMulticastDelegate& FImGuiDelegates::OnWorldDebug()
{ {
return OnWorldDebug(GWorld); return OnWorldDebug(GWorld);

View File

@ -25,13 +25,15 @@ void FImGuiDelegatesContainer::MoveContainer(FImGuiDelegatesContainer& Dst)
InstancePtr = &Dst; InstancePtr = &Dst;
} }
FSimpleMulticastDelegate& FImGuiDelegatesContainer::OnWorldDebug(UWorld* World) int32 FImGuiDelegatesContainer::GetContextIndex(UWorld* World)
{ {
return OnWorldDebug(Utilities::GetWorldContextIndex(*World)); return Utilities::GetWorldContextIndex(*World);
} }
void FImGuiDelegatesContainer::Clear() void FImGuiDelegatesContainer::Clear()
{ {
WorldDelegates.Empty(); WorldEarlyDebugDelegates.Empty();
MultiContextDelegate.Clear(); WorldDebugDelegates.Empty();
MultiContextEarlyDebugDelegate.Clear();
MultiContextDebugDelegate.Clear();
} }

View File

@ -16,21 +16,34 @@ public:
// If this is an active container move its data to a destination and redirect all future calls to that instance. // 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); static void MoveContainer(FImGuiDelegatesContainer& Dst);
// Get delegate to ImGui world early debug event from known world instance.
FSimpleMulticastDelegate& OnWorldEarlyDebug(UWorld* World) { return OnWorldEarlyDebug(GetContextIndex(World)); }
// Get delegate to ImGui world early debug event from known context index.
FSimpleMulticastDelegate& OnWorldEarlyDebug(int32 ContextIndex) { return WorldEarlyDebugDelegates.FindOrAdd(ContextIndex); }
// Get delegate to ImGui multi-context early debug event.
FSimpleMulticastDelegate& OnMultiContextEarlyDebug() { return MultiContextEarlyDebugDelegate; }
// Get delegate to ImGui world debug event from known world instance. // Get delegate to ImGui world debug event from known world instance.
FSimpleMulticastDelegate& OnWorldDebug(UWorld* World); FSimpleMulticastDelegate& OnWorldDebug(UWorld* World) { return OnWorldDebug(GetContextIndex(World)); }
// Get delegate to ImGui world debug event from known context index. // Get delegate to ImGui world debug event from known context index.
FSimpleMulticastDelegate& OnWorldDebug(int32 ContextIndex) { return WorldDelegates.FindOrAdd(ContextIndex); } FSimpleMulticastDelegate& OnWorldDebug(int32 ContextIndex) { return WorldDebugDelegates.FindOrAdd(ContextIndex); }
// Get delegate to ImGui multi-context debug event. // Get delegate to ImGui multi-context debug event.
FSimpleMulticastDelegate& OnMultiContextDebug() { return MultiContextDelegate; } FSimpleMulticastDelegate& OnMultiContextDebug() { return MultiContextDebugDelegate; }
private: private:
int32 GetContextIndex(UWorld* World);
void Clear(); void Clear();
TMap<int32, FSimpleMulticastDelegate> WorldDelegates; TMap<int32, FSimpleMulticastDelegate> WorldEarlyDebugDelegates;
FSimpleMulticastDelegate MultiContextDelegate; TMap<int32, FSimpleMulticastDelegate> WorldDebugDelegates;
FSimpleMulticastDelegate MultiContextEarlyDebugDelegate;
FSimpleMulticastDelegate MultiContextDebugDelegate;
// Default container instance. // Default container instance.
static FImGuiDelegatesContainer DefaultInstance; static FImGuiDelegatesContainer DefaultInstance;

View File

@ -23,3 +23,8 @@
// that renames FStringClassReference to FSoftClassPath, so it is still possible tu use the old type name in code. // that renames FStringClassReference to FSoftClassPath, so it is still possible tu use the old type name in code.
// The old header forwards to the new one but if used it outputs a warning, so we want to avoid it. // The old header forwards to the new one but if used it outputs a warning, so we want to avoid it.
#define ENGINE_COMPATIBILITY_LEGACY_STRING_CLASS_REF BELOW_ENGINE_VERSION(4, 18) #define ENGINE_COMPATIBILITY_LEGACY_STRING_CLASS_REF BELOW_ENGINE_VERSION(4, 18)
// Starting from version 4.18 engine has a world post actor tick event which if available, provides a good opportunity
// to call debug delegates after world actors are already updated.
#define ENGINE_COMPATIBILITY_WITH_WORLD_POST_ACTOR_TICK FROM_ENGINE_VERSION(4, 18)

View File

@ -6,16 +6,37 @@
/** /**
* Delegates to ImGui debug events called. World delegates are called once per frame to draw debug for owning world * Delegates to ImGui debug events. World delegates are called once per frame during world updates and have invocation
* and are automatically cleared when that world becomes invalid. Multi-context delegates are called once for every * lists cleared after their worlds become invalid. Multi-context delegates are called once for every updated world.
* world that needs to be debugged. * Early debug delegates are called during world tick start and debug delegates are called during world post actor tick
* In engine version 4.18 or later delegates are called during OnWorldPostActorTick event while in older versions * or in engine versions below 4.18 during world tick start.
* 4.18 during OnWorldTickStart event. Calling behaviours can be changed in build configuration. *
* Order of events is defined in a way that multi-context delegates can be used to draw headers and/or footers:
* multi-context early debug, world early debug, world debug, multi-context debug.
*/ */
class IMGUI_API FImGuiDelegates class IMGUI_API FImGuiDelegates
{ {
public: public:
/**
* Get a delegate to ImGui world early debug event for current world (GWorld).
* @returns Simple multicast delegate to debug events called once per frame to debug current world
*/
static FSimpleMulticastDelegate& OnWorldEarlyDebug();
/**
* Get a delegate to ImGui world early 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& OnWorldEarlyDebug(UWorld* World);
/**
* Get a delegate to ImGui multi-context early debug event.
* @returns Simple multicast delegate to debug events called once per frame for every world to debug
*/
static FSimpleMulticastDelegate& OnMultiContextEarlyDebug();
/** /**
* Get a delegate to ImGui world debug event for current world (GWorld). * 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 * @returns Simple multicast delegate to debug events called once per frame to debug current world