From 4a9d66889bdfd49421e875c3f249f606240ce3c2 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sun, 14 Apr 2019 12:16:15 +0100 Subject: [PATCH] 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. --- CHANGES.md | 3 + Source/ImGui/ImGui.Build.cs | 46 -------------- Source/ImGui/Private/ImGuiContextManager.cpp | 19 +++--- Source/ImGui/Private/ImGuiContextManager.h | 2 +- Source/ImGui/Private/ImGuiContextProxy.cpp | 62 ++++++++++++++----- Source/ImGui/Private/ImGuiContextProxy.h | 18 ++++-- Source/ImGui/Private/ImGuiDelegates.cpp | 15 +++++ .../ImGui/Private/ImGuiDelegatesContainer.cpp | 10 +-- .../ImGui/Private/ImGuiDelegatesContainer.h | 23 +++++-- Source/ImGui/Private/VersionCompatibility.h | 5 ++ Source/ImGui/Public/ImGuiDelegates.h | 31 ++++++++-- 11 files changed, 143 insertions(+), 91 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index ef62a2b..365e229 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -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. diff --git a/Source/ImGui/ImGui.Build.cs b/Source/ImGui/ImGui.Build.cs index c882062..550d943 100644 --- a/Source/ImGui/ImGui.Build.cs +++ b/Source/ImGui/ImGui.Build.cs @@ -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)); } } diff --git a/Source/ImGui/Private/ImGuiContextManager.cpp b/Source/ImGui/Private/ImGuiContextManager.cpp index bd419a7..388e60f 100644 --- a/Source/ImGui/Private/ImGuiContextManager.cpp +++ b/Source/ImGui/Private/ImGuiContextManager.cpp @@ -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() diff --git a/Source/ImGui/Private/ImGuiContextManager.h b/Source/ImGui/Private/ImGuiContextManager.h index d3c48dc..a2a3208 100644 --- a/Source/ImGui/Private/ImGuiContextManager.h +++ b/Source/ImGui/Private/ImGuiContextManager.h @@ -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 diff --git a/Source/ImGui/Private/ImGuiContextProxy.cpp b/Source/ImGui/Private/ImGuiContextProxy.cpp index bf92fa3..38916e6 100644 --- a/Source/ImGui/Private/ImGuiContextProxy.cpp +++ b/Source/ImGui/Private/ImGuiContextProxy.cpp @@ -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()) { diff --git a/Source/ImGui/Private/ImGuiContextProxy.h b/Source/ImGui/Private/ImGuiContextProxy.h index 433b4f2..8809866 100644 --- a/Source/ImGui/Private/ImGuiContextProxy.h +++ b/Source/ImGui/Private/ImGuiContextProxy.h @@ -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; diff --git a/Source/ImGui/Private/ImGuiDelegates.cpp b/Source/ImGui/Private/ImGuiDelegates.cpp index 46b78c2..7ac36bd 100644 --- a/Source/ImGui/Private/ImGuiDelegates.cpp +++ b/Source/ImGui/Private/ImGuiDelegates.cpp @@ -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); diff --git a/Source/ImGui/Private/ImGuiDelegatesContainer.cpp b/Source/ImGui/Private/ImGuiDelegatesContainer.cpp index 6565e6d..93e5cf5 100644 --- a/Source/ImGui/Private/ImGuiDelegatesContainer.cpp +++ b/Source/ImGui/Private/ImGuiDelegatesContainer.cpp @@ -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(); } diff --git a/Source/ImGui/Private/ImGuiDelegatesContainer.h b/Source/ImGui/Private/ImGuiDelegatesContainer.h index fa07aac..6d864b6 100644 --- a/Source/ImGui/Private/ImGuiDelegatesContainer.h +++ b/Source/ImGui/Private/ImGuiDelegatesContainer.h @@ -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 WorldDelegates; - FSimpleMulticastDelegate MultiContextDelegate; + TMap WorldEarlyDebugDelegates; + TMap WorldDebugDelegates; + FSimpleMulticastDelegate MultiContextEarlyDebugDelegate; + FSimpleMulticastDelegate MultiContextDebugDelegate; // Default container instance. static FImGuiDelegatesContainer DefaultInstance; diff --git a/Source/ImGui/Private/VersionCompatibility.h b/Source/ImGui/Private/VersionCompatibility.h index 04e6005..339cf57 100644 --- a/Source/ImGui/Private/VersionCompatibility.h +++ b/Source/ImGui/Private/VersionCompatibility.h @@ -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) + diff --git a/Source/ImGui/Public/ImGuiDelegates.h b/Source/ImGui/Public/ImGuiDelegates.h index cf9c986..d59f93d 100644 --- a/Source/ImGui/Public/ImGuiDelegates.h +++ b/Source/ImGui/Public/ImGuiDelegates.h @@ -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