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.
- 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 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)
- Added SImGuiLayout to resets layout for SImGuiWidget.

View File

@ -1,45 +1,11 @@
// 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.IO;
using UnrealBuildTool;
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
public ImGui(ReadOnlyTargetRules Target) : base(Target)
#else
@ -128,17 +94,5 @@ public class ImGui : ModuleRules
#endif
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);
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);
#endif
}
@ -73,7 +73,7 @@ FImGuiContextManager::~FImGuiContextManager()
{
// Order matters because contexts can be created during World Tick Start events.
FWorldDelegates::OnWorldTickStart.RemoveAll(this);
#if DRAW_EVENTS_ON_POST_ACTOR_TICK
#if ENGINE_COMPATIBILITY_WITH_WORLD_POST_ACTOR_TICK
FWorldDelegates::OnWorldPostActorTick.RemoveAll(this);
#endif
}
@ -103,22 +103,23 @@ void FImGuiContextManager::OnWorldTickStart(ELevelTick TickType, float DeltaSeco
if (GWorld)
{
FImGuiContextProxy& ContextProxy = GetWorldContextProxy(*GWorld);
// Set as current, so we have right context ready when updating world objects.
ContextProxy.SetAsCurrent();
#if DRAW_EVENTS_ON_WORLD_TICK_START
ContextProxy.Draw();
ContextProxy.DrawEarlyDebug();
#if !ENGINE_COMPATIBILITY_WITH_WORLD_POST_ACTOR_TICK
ContextProxy.DrawDebug();
#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)
{
FImGuiContextProxy& ContextProxy = GetWorldContextProxy(*GWorld);
ContextProxy.SetAsCurrent();
ContextProxy.Draw();
GetWorldContextProxy(*GWorld).DrawDebug();
}
#endif // DRAW_EVENTS_ON_POST_ACTOR_TICK
#endif // ENGINE_COMPATIBILITY_WITH_WORLD_POST_ACTOR_TICK
#if WITH_EDITOR
FImGuiContextManager::FContextData& FImGuiContextManager::GetEditorContextData()

View File

@ -100,7 +100,7 @@ private:
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);
#endif

View File

@ -83,24 +83,34 @@ FImGuiContextProxy::FImGuiContextProxy(const FString& InName, int32 InContextInd
BeginFrame();
}
void FImGuiContextProxy::Draw()
void FImGuiContextProxy::DrawEarlyDebug()
{
if (bIsFrameStarted && !bIsDrawCalled)
if (bIsFrameStarted && !bIsDrawEarlyDebugCalled)
{
bIsDrawCalled = true;
bIsDrawEarlyDebugCalled = true;
SetAsCurrent();
// Broadcast draw event to allow listeners to draw their controls to this context.
#if DRAW_EVENTS_ORDER_WORLD_BEFORE_MULTI_CONTEXT
BroadcastWorldTick();
#endif // DRAW_EVENTS_ORDER_WORLD_BEFORE_MULTI_CONTEXT
// Delegates called in order specified in FImGuiDelegates.
BroadcastMultiContextEarlyDebug();
BroadcastWorldEarlyDebug();
}
}
BroadcastMultiContextTick();
void FImGuiContextProxy::DrawDebug()
{
if (bIsFrameStarted && !bIsDrawDebugCalled)
{
bIsDrawDebugCalled = true;
#if !DRAW_EVENTS_ORDER_WORLD_BEFORE_MULTI_CONTEXT
BroadcastWorldTick();
#endif // !DRAW_EVENTS_ORDER_WORLD_BEFORE_MULTI_CONTEXT
// Make sure that early debug is always called first to guarantee order specified in FImGuiDelegates.
DrawEarlyDebug();
SetAsCurrent();
// Delegates called in order specified in FImGuiDelegates.
BroadcastWorldDebug();
BroadcastMultiContextDebug();
}
}
@ -116,7 +126,7 @@ void FImGuiContextProxy::Tick(float DeltaSeconds)
if (bIsFrameStarted)
{
// 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
// 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();
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())
{
@ -203,7 +235,7 @@ void FImGuiContextProxy::BroadcastWorldTick()
}
}
void FImGuiContextProxy::BroadcastMultiContextTick()
void FImGuiContextProxy::BroadcastMultiContextDebug()
{
if (SharedDrawEvent && SharedDrawEvent->IsBound())
{

View File

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

View File

@ -6,6 +6,21 @@
#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()
{
return OnWorldDebug(GWorld);

View File

@ -25,13 +25,15 @@ void FImGuiDelegatesContainer::MoveContainer(FImGuiDelegatesContainer& 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()
{
WorldDelegates.Empty();
MultiContextDelegate.Clear();
WorldEarlyDebugDelegates.Empty();
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.
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.
FSimpleMulticastDelegate& OnWorldDebug(UWorld* World);
FSimpleMulticastDelegate& OnWorldDebug(UWorld* World) { return OnWorldDebug(GetContextIndex(World)); }
// 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.
FSimpleMulticastDelegate& OnMultiContextDebug() { return MultiContextDelegate; }
FSimpleMulticastDelegate& OnMultiContextDebug() { return MultiContextDebugDelegate; }
private:
int32 GetContextIndex(UWorld* World);
void Clear();
TMap<int32, FSimpleMulticastDelegate> WorldDelegates;
FSimpleMulticastDelegate MultiContextDelegate;
TMap<int32, FSimpleMulticastDelegate> WorldEarlyDebugDelegates;
TMap<int32, FSimpleMulticastDelegate> WorldDebugDelegates;
FSimpleMulticastDelegate MultiContextEarlyDebugDelegate;
FSimpleMulticastDelegate MultiContextDebugDelegate;
// Default container instance.
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.
// 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)
// 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
* 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.
* Delegates to ImGui debug events. World delegates are called once per frame during world updates and have invocation
* lists cleared after their worlds become invalid. Multi-context delegates are called once for every updated world.
* Early debug delegates are called during world tick start and debug delegates are called during world post actor tick
* or in engine versions below 4.18 during world tick start.
*
* 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
{
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).
* @returns Simple multicast delegate to debug events called once per frame to debug current world