From aca54cbe9da6060f75f5efa284fd02be70ce51ae Mon Sep 17 00:00:00 2001 From: Arnaud Jamin Date: Fri, 24 Jan 2025 00:57:24 -0500 Subject: [PATCH] CogEngine: Improve Plot window - Save plotted entries between sessions - Add more options (autofit padding, num recorded values, record values when paused - Fix dragging the mouse on a different window over the graph pausing the graph --- .../Source/CogDebug/Private/CogDebugPlot.cpp | 496 ++---------------- .../CogDebug/Private/CogDebugPlotEvent.cpp | 315 +++++++++++ .../CogDebug/Private/CogDebugPlotValue.cpp | 112 ++++ .../Cog/Source/CogDebug/Public/CogDebugPlot.h | 112 ++-- .../Private/CogEngineWindow_Plots.cpp | 385 ++++++++------ .../CogEngine/Public/CogEngineWindow_Plots.h | 70 ++- 6 files changed, 844 insertions(+), 646 deletions(-) create mode 100644 Plugins/Cog/Source/CogDebug/Private/CogDebugPlotEvent.cpp create mode 100644 Plugins/Cog/Source/CogDebug/Private/CogDebugPlotValue.cpp diff --git a/Plugins/Cog/Source/CogDebug/Private/CogDebugPlot.cpp b/Plugins/Cog/Source/CogDebug/Private/CogDebugPlot.cpp index fb34a22..2c384d0 100644 --- a/Plugins/Cog/Source/CogDebug/Private/CogDebugPlot.cpp +++ b/Plugins/Cog/Source/CogDebug/Private/CogDebugPlot.cpp @@ -1,326 +1,24 @@ #include "CogDebugPlot.h" #include "CogDebug.h" -#include "CogDebugHelper.h" -#include "CogImguiHelper.h" #include "Engine/Engine.h" #include "Engine/World.h" FCogDebugPlotEvent FCogDebugPlot::DefaultEvent; -TArray FCogDebugPlot::Plots; -TArray FCogDebugPlot::Events; +TMap FCogDebugPlot::Values; +TMap FCogDebugPlot::Events; +int32 FCogDebugPlot::NumRecordedValues = 2000; bool FCogDebugPlot::IsVisible = false; bool FCogDebugPlot::Pause = false; +bool FCogDebugPlot::RecordValuesWhenPause = true; FName FCogDebugPlot::LastAddedEventPlotName = NAME_None; int32 FCogDebugPlot::LastAddedEventIndex = INDEX_NONE; TMap> FCogDebugPlot::OccupationMap; -//-------------------------------------------------------------------------------------------------------------------------- -// FCogPlotEvent -//-------------------------------------------------------------------------------------------------------------------------- -float FCogDebugPlotEvent::GetActualEndTime(const FCogDebugPlotEntry& Plot) const -{ - const UWorld* World = Plot.World.Get(); - const float WorldTime = World != nullptr ? World->GetTimeSeconds() : 0.0f; - const float ActualEndTime = EndTime != 0.0f ? EndTime : WorldTime; - return ActualEndTime; -} - -//-------------------------------------------------------------------------------------------------------------------------- -uint64 FCogDebugPlotEvent::GetActualEndFrame(const FCogDebugPlotEntry& Plot) const -{ - const float ActualEndFame = EndFrame != 0.0f ? EndFrame : GFrameCounter; - return ActualEndFame; -} - -//-------------------------------------------------------------------------------------------------------------------------- -FCogDebugPlotEvent& FCogDebugPlotEvent::AddParam(const FName Name, bool Value) -{ - if (FCogDebugPlot::IsVisible) - { - AddParam(Name, FString::Printf(TEXT("%s"), Value ? TEXT("True") : TEXT("False"))); - } - - return *this; -} - -//-------------------------------------------------------------------------------------------------------------------------- -FCogDebugPlotEvent& FCogDebugPlotEvent::AddParam(const FName Name, int Value) -{ - if (FCogDebugPlot::IsVisible) - { - AddParam(Name, FString::Printf(TEXT("%d"), Value)); - } - - return *this; -} - -//-------------------------------------------------------------------------------------------------------------------------- -FCogDebugPlotEvent& FCogDebugPlotEvent::AddParam(const FName Name, float Value) -{ - if (FCogDebugPlot::IsVisible) - { - AddParam(Name, FString::Printf(TEXT("%0.2f"), Value)); - } - - return *this; -} - -//-------------------------------------------------------------------------------------------------------------------------- -FCogDebugPlotEvent& FCogDebugPlotEvent::AddParam(const FName Name, FName Value) -{ - if (FCogDebugPlot::IsVisible) - { - AddParam(Name, Value.ToString()); - } - - return *this; -} - -//-------------------------------------------------------------------------------------------------------------------------- -FCogDebugPlotEvent& FCogDebugPlotEvent::AddParam(const FName Name, const FString& Value) -{ - if (FCogDebugPlot::IsVisible) - { - - if (Name == "Name") - { - DisplayName = Value; - } - else - { - FCogDebugPlotEventParams& Param = Params.AddDefaulted_GetRef(); - Param.Name = Name; - Param.Value = Value; - } - } - - return *this; -} - - -//-------------------------------------------------------------------------------------------------------------------------- -// FCogPlotEntry -//-------------------------------------------------------------------------------------------------------------------------- -void FCogDebugPlotEntry::AddPoint(float X, float Y) -{ - if (Values.Capacity == 0) - { - Values.reserve(2000); - } - - if (Values.size() < Values.Capacity) - { - Values.push_back(ImVec2(X, Y)); - } - else - { - Values[ValueOffset] = ImVec2(X, Y); - ValueOffset = (ValueOffset + 1) % Values.size(); - } -} - -//-------------------------------------------------------------------------------------------------------------------------- -FCogDebugPlotEvent& FCogDebugPlotEntry::AddEvent( - const FString& OwnerName, - const bool IsInstant, - const FName EventId, - const int32 Row, - const FColor& Color) -{ - if (Events.Max() < 200) - { - Events.Reserve(200); - } - - //---------------------------- - // Stop if it already exist. - //---------------------------- - StopEvent(EventId); - - FCogDebugPlotEvent* Event = nullptr; - - int32 AddedIndex = 0; - if (Events.Num() < Events.Max()) - { - Event = &Events.AddDefaulted_GetRef(); - AddedIndex = Events.Num() - 1; - } - else - { - Event = &Events[EventOffset]; - AddedIndex = EventOffset; - EventOffset = (EventOffset + 1) % Events.Num(); - } - - Event->Id = EventId; - Event->OwnerName = OwnerName; - Event->DisplayName = EventId.ToString(); - Event->StartTime = Time; - Event->EndTime = IsInstant ? Time : 0.0f; - Event->StartFrame = Frame; - Event->EndFrame = IsInstant ? Frame : 0.0f; - Event->Row = (Row == FCogDebugPlot::AutoRow) ? FCogDebugPlot::FindFreeGraphRow(GraphIndex) : Row; - - if (IsInstant == false) - { - FCogDebugPlot::OccupyGraphRow(GraphIndex, Event->Row); - } - - MaxRow = FMath::Max(Event->Row, MaxRow); - - const FColor BorderColor = FCogDebugHelper::GetAutoColor(EventId, Color).WithAlpha(200); - const FColor FillColor = BorderColor.WithAlpha(100); - Event->BorderColor = FCogImguiHelper::ToImColor(BorderColor); - Event->FillColor = FCogImguiHelper::ToImColor(FillColor); - - FCogDebugPlot::LastAddedEventPlotName = Name; - FCogDebugPlot::LastAddedEventIndex = AddedIndex; - - return *Event; -} - -//-------------------------------------------------------------------------------------------------------------------------- -FCogDebugPlotEvent& FCogDebugPlotEntry::StopEvent(const FName EventId) -{ - FCogDebugPlotEvent* Event = FindLastEventByName(EventId); - if (Event == nullptr) - { - return FCogDebugPlot::DefaultEvent; - } - - if (Event->EndTime == 0.0f) - { - Event->EndTime = Time; - Event->EndFrame = Frame; - - FCogDebugPlot::FreeGraphRow(GraphIndex, Event->Row); - } - - return *Event; -} - -//-------------------------------------------------------------------------------------------------------------------------- -FCogDebugPlotEvent* FCogDebugPlotEntry::GetLastEvent() -{ - if (Events.Num() == 0) - { - return nullptr; - } - - int32 Index = Events.Num() - 1; - if (EventOffset != 0) - { - Index = (Index + EventOffset) % Events.Num(); - } - - return &Events[Index]; -} - -//-------------------------------------------------------------------------------------------------------------------------- -FCogDebugPlotEvent* FCogDebugPlotEntry::FindLastEventByName(FName EventId) -{ - for (int32 i = Events.Num() - 1; i >= 0; --i) - { - //-------------------------------------------------- - // The array cycle so we must offset the index - //-------------------------------------------------- - int32 Index = i; - if (EventOffset != 0) - { - Index = (i + EventOffset) % Events.Num(); - } - - if (Events[Index].Id == EventId) - { - return &Events[Index]; - } - } - - return nullptr; -} - -//-------------------------------------------------------------------------------------------------------------------------- -void FCogDebugPlotEntry::AssignGraphAndAxis(int32 InGraph, ImAxis InYAxis) -{ - GraphIndex = InGraph; - YAxis = InYAxis; -} - -//-------------------------------------------------------------------------------------------------------------------------- -void FCogDebugPlotEntry::ResetGraphAndAxis() -{ - GraphIndex = INDEX_NONE; - YAxis = ImAxis_COUNT; -} - -//-------------------------------------------------------------------------------------------------------------------------- -void FCogDebugPlotEntry::Clear() -{ - FCogDebugPlot::ResetLastAddedEvent(); - - MaxRow = 0; - - if (Values.size() > 0) - { - Values.shrink(0); - ValueOffset = 0; - } - - if (Events.Num() > 0) - { - Events.Empty(); - Events.Shrink(); - EventOffset = 0; - } -} - -//-------------------------------------------------------------------------------------------------------------------------- -bool FCogDebugPlotEntry::FindValue(float x, float& y) const -{ - y = 0.0f; - - bool FoundAfter = false; - bool FoundBefore = false; - - for (int32 i = Values.size() - 1; i >= 0; --i) - { - //-------------------------------------------------- - // The array cycle so we must offset the index - //-------------------------------------------------- - int32 Index = i; - if (ValueOffset != 0) - { - Index = (i + ValueOffset) % Values.size(); - } - - const ImVec2 Point = Values[Index]; - if (Point.x > x) - { - FoundAfter = true; - } - - if (Point.x < x) - { - FoundBefore = true; - } - - if (FoundAfter && FoundBefore) - { - y = Point.y; - return true; - } - } - - return false; -} - -//-------------------------------------------------------------------------------------------------------------------------- -// FCogPlot //-------------------------------------------------------------------------------------------------------------------------- void FCogDebugPlot::Reset() { - Plots.Empty(); + Values.Empty(); Events.Empty(); OccupationMap.Empty(); Pause = false; @@ -330,14 +28,14 @@ void FCogDebugPlot::Reset() //-------------------------------------------------------------------------------------------------------------------------- void FCogDebugPlot::Clear() { - for (FCogDebugPlotEntry& Entry : Plots) + for (auto& kv : Values) { - Entry.Clear(); + kv.Value.Clear(); } - for (FCogDebugPlotEntry& Entry : Events) + for (auto& kv : Events) { - Entry.Clear(); + kv.Value.Clear(); } OccupationMap.Empty(); @@ -345,165 +43,45 @@ void FCogDebugPlot::Clear() } //-------------------------------------------------------------------------------------------------------------------------- -void FCogDebugPlot::ResetLastAddedEvent() -{ - LastAddedEventPlotName = NAME_None; - LastAddedEventIndex = INDEX_NONE; -} - -//-------------------------------------------------------------------------------------------------------------------------- -FCogDebugPlotEvent* FCogDebugPlot::GetLastAddedEvent() -{ - FCogDebugPlotEntry* Plot = FindEntry(true, LastAddedEventPlotName); - if (Plot == nullptr) - { - return nullptr; - } - - return Plot->GetLastEvent(); -} - -//-------------------------------------------------------------------------------------------------------------------------- -FCogDebugPlotEntry* FCogDebugPlot::FindEntry(const FName Name) -{ - if (FCogDebugPlotEntry* Event = Events.FindByPredicate([Name](const FCogDebugPlotEntry& P) { return P.Name == Name; })) - { - return Event; - } - - if (FCogDebugPlotEntry* Plot = Plots.FindByPredicate([Name](const FCogDebugPlotEntry& P) { return P.Name == Name; })) - { - return Plot; - } - - return nullptr; -} - -//-------------------------------------------------------------------------------------------------------------------------- -FCogDebugPlotEntry* FCogDebugPlot::FindEntry(bool IsEvent, const FName Name) -{ - TArray* Entries = IsEvent ? &Events : &Plots; - FCogDebugPlotEntry* Entry = Entries->FindByPredicate([Name](const FCogDebugPlotEntry& P) { return P.Name == Name; }); - return Entry; -} - -//-------------------------------------------------------------------------------------------------------------------------- -FCogDebugPlotEntry* FCogDebugPlot::RegisterPlot(const UObject* WorldContextObject, const FName PlotName, bool IsEventPlot) +bool FCogDebugPlot::ShouldRegisterEntry(const UObject* WorldContextObject, const UWorld*& World) { //---------------------------------------------------------- // When not visible, we don't go further for performances. //---------------------------------------------------------- if (IsVisible == false) { - return nullptr; + return false; } - const UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull); + World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull); if (World == nullptr) { - return nullptr; + return false; } if (FCogDebug::IsDebugActiveForObject(WorldContextObject) == false) { - return nullptr; + return false; } - FCogDebugPlotEntry* EntryPtr = FindEntry(IsEventPlot, PlotName); - if (EntryPtr == nullptr) - { - TArray* Entries = IsEventPlot ? &Events : &Plots; - EntryPtr = &Entries->AddDefaulted_GetRef(); - EntryPtr->Name = PlotName; - EntryPtr->IsEventPlot = IsEventPlot; - Entries->Sort([](const FCogDebugPlotEntry& A, const FCogDebugPlotEntry& B) { return A.Name.ToString().Compare(B.Name.ToString()) < 0; }); - } - - //if (EntryPtr->YAxis == ImAxis_COUNT) - //{ - // return nullptr; - //} - - const float Time = World->GetTimeSeconds(); - if (Time < EntryPtr->Time) - { - EntryPtr->Clear(); - } - - EntryPtr->World = World; - EntryPtr->Time = World->GetTimeSeconds(); - EntryPtr->Frame = GFrameCounter; - - return EntryPtr; + return true; } //-------------------------------------------------------------------------------------------------------------------------- -void FCogDebugPlot::PlotValue(const UObject* WorldContextObject, const FName PlotName, const float Value) +void FCogDebugPlot::InitializeEntry(FCogDebugHistory& OutValue, const UWorld* InWorld, const FName InName) { - FCogDebugPlotEntry* Plot = RegisterPlot(WorldContextObject, PlotName, false); - if (Plot == nullptr) + const float Time = InWorld->GetTimeSeconds(); + if (Time < OutValue.Time) { - return; + OutValue.Clear(); } - Plot->AddPoint(Plot->Time, Value); + OutValue.Name = InName; + OutValue.World = InWorld; + OutValue.Time = InWorld->GetTimeSeconds(); + OutValue.Frame = GFrameCounter; } -//-------------------------------------------------------------------------------------------------------------------------- -FCogDebugPlotEvent& FCogDebugPlot::PlotEvent(const UObject* WorldContextObject, const FName PlotName, const FName EventId, bool IsInstant, const int32 Row, const FColor& Color) -{ - FCogDebugPlotEntry* Plot = RegisterPlot(WorldContextObject, PlotName, true); - if (Plot == nullptr) - { - ResetLastAddedEvent(); - return DefaultEvent; - } - - FCogDebugPlotEvent& Event = Plot->AddEvent(GetNameSafe(WorldContextObject), IsInstant, EventId, Row, Color); - return Event; -} - -//-------------------------------------------------------------------------------------------------------------------------- -FCogDebugPlotEvent& FCogDebugPlot::PlotEventInstant(const UObject* WorldContextObject, const FName PlotName, const FName EventId, const int32 Row, const FColor& Color) -{ - FCogDebugPlotEvent& Event = PlotEvent(WorldContextObject, PlotName, EventId, true, Row, Color); - return Event; -} - -//-------------------------------------------------------------------------------------------------------------------------- -FCogDebugPlotEvent& FCogDebugPlot::PlotEventStart(const UObject* WorldContextObject, const FName PlotName, const FName EventId, const int32 Row, const FColor& Color) -{ - FCogDebugPlotEvent& Event = PlotEvent(WorldContextObject, PlotName, EventId, false, Row, Color); - return Event; -} - -//-------------------------------------------------------------------------------------------------------------------------- -FCogDebugPlotEvent& FCogDebugPlot::PlotEventStop(const UObject* WorldContextObject, const FName PlotName, const FName EventId) -{ - FCogDebugPlotEntry* Plot = RegisterPlot(WorldContextObject, PlotName, true); - if (Plot == nullptr) - { - return DefaultEvent; - } - - FCogDebugPlotEvent& Event = Plot->StopEvent(EventId); - return Event; -} - -//-------------------------------------------------------------------------------------------------------------------------- -FCogDebugPlotEvent& FCogDebugPlot::PlotEventToggle(const UObject* WorldContextObject, const FName PlotName, const FName EventId, const bool ToggleValue, const int32 Row, const FColor& Color) -{ - if (ToggleValue) - { - return PlotEventStart(WorldContextObject, PlotName, EventId, Row, Color); - } - else - { - return PlotEventStop(WorldContextObject, PlotName, EventId); - } -} - - //-------------------------------------------------------------------------------------------------------------------------- void FCogDebugPlot::OccupyGraphRow(const int32 InGraphIndex, const int32 InRow) { @@ -558,3 +136,31 @@ int32 FCogDebugPlot::FindFreeGraphRow(const int32 InGraphIndex) return FreeRow; } +//-------------------------------------------------------------------------------------------------------------------------- +FCogDebugHistory* FCogDebugPlot::FindEntry(const FName InName) +{ + FCogDebugHistory* Entry = Events.Find(InName); + if (Entry != nullptr) + { + return Entry; + } + + Entry = Values.Find(InName); + if (Entry != nullptr) + { + return Entry; + } + + return nullptr; +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogDebugPlot::SetNumRecordedValues(int32 InValue) +{ + NumRecordedValues = InValue; + + for (auto& kv : Values) + { + kv.Value.SetNumRecordedValues(InValue); + } +} diff --git a/Plugins/Cog/Source/CogDebug/Private/CogDebugPlotEvent.cpp b/Plugins/Cog/Source/CogDebug/Private/CogDebugPlotEvent.cpp new file mode 100644 index 0000000..7e7260d --- /dev/null +++ b/Plugins/Cog/Source/CogDebug/Private/CogDebugPlotEvent.cpp @@ -0,0 +1,315 @@ +#include "CogDebugPlot.h" + +#include "CogDebugHelper.h" +#include "CogImguiHelper.h" +#include "Engine/Engine.h" +#include "Engine/World.h" + +//-------------------------------------------------------------------------------------------------------------------------- +float FCogDebugPlotEvent::GetActualEndTime(const FCogDebugHistory& Plot) const +{ + const UWorld* World = Plot.World.Get(); + const float WorldTime = World != nullptr ? World->GetTimeSeconds() : 0.0f; + const float ActualEndTime = EndTime != 0.0f ? EndTime : WorldTime; + return ActualEndTime; +} + +//-------------------------------------------------------------------------------------------------------------------------- +uint64 FCogDebugPlotEvent::GetActualEndFrame(const FCogDebugHistory& Plot) const +{ + const float ActualEndFame = EndFrame != 0.0f ? EndFrame : GFrameCounter; + return ActualEndFame; +} + +//-------------------------------------------------------------------------------------------------------------------------- +FCogDebugPlotEvent& FCogDebugPlotEvent::AddParam(const FName Name, bool Value) +{ + if (FCogDebugPlot::IsVisible) + { + AddParam(Name, FString::Printf(TEXT("%s"), Value ? TEXT("True") : TEXT("False"))); + } + + return *this; +} + +//-------------------------------------------------------------------------------------------------------------------------- +FCogDebugPlotEvent& FCogDebugPlotEvent::AddParam(const FName Name, int Value) +{ + if (FCogDebugPlot::IsVisible) + { + AddParam(Name, FString::Printf(TEXT("%d"), Value)); + } + + return *this; +} + +//-------------------------------------------------------------------------------------------------------------------------- +FCogDebugPlotEvent& FCogDebugPlotEvent::AddParam(const FName Name, float Value) +{ + if (FCogDebugPlot::IsVisible) + { + AddParam(Name, FString::Printf(TEXT("%0.2f"), Value)); + } + + return *this; +} + +//-------------------------------------------------------------------------------------------------------------------------- +FCogDebugPlotEvent& FCogDebugPlotEvent::AddParam(const FName Name, FName Value) +{ + if (FCogDebugPlot::IsVisible) + { + AddParam(Name, Value.ToString()); + } + + return *this; +} + +//-------------------------------------------------------------------------------------------------------------------------- +FCogDebugPlotEvent& FCogDebugPlotEvent::AddParam(const FName Name, const FString& Value) +{ + if (FCogDebugPlot::IsVisible) + { + + if (Name == "Name") + { + DisplayName = Value; + } + else + { + FCogDebugPlotEventParams& Param = Params.AddDefaulted_GetRef(); + Param.Name = Name; + Param.Value = Value; + } + } + + return *this; +} + +//-------------------------------------------------------------------------------------------------------------------------- +FCogDebugPlotEvent& FCogDebugEventHistory::AddEvent( + const FString& OwnerName, + const bool IsInstant, + const FName EventId, + const int32 Row, + const FColor& Color) +{ + if (Events.Max() < 200) + { + Events.Reserve(200); + } + + //---------------------------- + // Stop if it already exist. + //---------------------------- + StopEvent(EventId); + + FCogDebugPlotEvent* Event = nullptr; + + int32 AddedIndex = 0; + if (Events.Num() < Events.Max()) + { + Event = &Events.AddDefaulted_GetRef(); + AddedIndex = Events.Num() - 1; + } + else + { + Event = &Events[EventOffset]; + AddedIndex = EventOffset; + EventOffset = (EventOffset + 1) % Events.Num(); + } + + Event->Id = EventId; + Event->OwnerName = OwnerName; + Event->DisplayName = EventId.ToString(); + Event->StartTime = Time; + Event->EndTime = IsInstant ? Time : 0.0f; + Event->StartFrame = Frame; + Event->EndFrame = IsInstant ? Frame : 0.0f; + Event->Row = (Row == FCogDebugPlot::AutoRow) ? FCogDebugPlot::FindFreeGraphRow(GraphIndex) : Row; + + if (IsInstant == false) + { + FCogDebugPlot::OccupyGraphRow(GraphIndex, Event->Row); + } + + MaxRow = FMath::Max(Event->Row, MaxRow); + + const FColor BorderColor = FCogDebugHelper::GetAutoColor(EventId, Color).WithAlpha(200); + const FColor FillColor = BorderColor.WithAlpha(100); + Event->BorderColor = FCogImguiHelper::ToImColor(BorderColor); + Event->FillColor = FCogImguiHelper::ToImColor(FillColor); + + FCogDebugPlot::LastAddedEventPlotName = Name; + FCogDebugPlot::LastAddedEventIndex = AddedIndex; + + return *Event; +} + +//-------------------------------------------------------------------------------------------------------------------------- +FCogDebugPlotEvent& FCogDebugEventHistory::StopEvent(const FName EventId) +{ + FCogDebugPlotEvent* Event = FindLastEventByName(EventId); + if (Event == nullptr) + { + return FCogDebugPlot::DefaultEvent; + } + + if (Event->EndTime == 0.0f) + { + Event->EndTime = Time; + Event->EndFrame = Frame; + + FCogDebugPlot::FreeGraphRow(GraphIndex, Event->Row); + } + + return *Event; +} + +//-------------------------------------------------------------------------------------------------------------------------- +FCogDebugPlotEvent* FCogDebugEventHistory::GetLastEvent() +{ + if (Events.Num() == 0) + { + return nullptr; + } + + int32 Index = Events.Num() - 1; + if (EventOffset != 0) + { + Index = (Index + EventOffset) % Events.Num(); + } + + return &Events[Index]; +} + +//-------------------------------------------------------------------------------------------------------------------------- +FCogDebugPlotEvent* FCogDebugEventHistory::FindLastEventByName(FName EventId) +{ + for (int32 i = Events.Num() - 1; i >= 0; --i) + { + //-------------------------------------------------- + // The array cycle so we must offset the index + //-------------------------------------------------- + int32 Index = i; + if (EventOffset != 0) + { + Index = (i + EventOffset) % Events.Num(); + } + + if (Events[Index].Id == EventId) + { + return &Events[Index]; + } + } + + return nullptr; +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogDebugEventHistory::Clear() +{ + FCogDebugHistory::Clear(); + + FCogDebugPlot::ResetLastAddedEvent(); + + MaxRow = 0; + + if (Events.Num() > 0) + { + Events.Empty(); + Events.Shrink(); + EventOffset = 0; + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +FCogDebugPlotEvent& FCogDebugPlot::PlotEvent(const UObject* WorldContextObject, const FName PlotName, const FName EventId, bool IsInstant, const int32 Row, const FColor& Color) +{ + FCogDebugEventHistory* EventHistory = RegisterEvent(WorldContextObject, PlotName); + if (EventHistory == nullptr) + { + ResetLastAddedEvent(); + return DefaultEvent; + } + + FCogDebugPlotEvent& Event = EventHistory->AddEvent(GetNameSafe(WorldContextObject), IsInstant, EventId, Row, Color); + return Event; +} + +//-------------------------------------------------------------------------------------------------------------------------- +FCogDebugPlotEvent& FCogDebugPlot::PlotEventInstant(const UObject* WorldContextObject, const FName PlotName, const FName EventId, const int32 Row, const FColor& Color) +{ + FCogDebugPlotEvent& Event = PlotEvent(WorldContextObject, PlotName, EventId, true, Row, Color); + return Event; +} + +//-------------------------------------------------------------------------------------------------------------------------- +FCogDebugPlotEvent& FCogDebugPlot::PlotEventStart(const UObject* WorldContextObject, const FName PlotName, const FName EventId, const int32 Row, const FColor& Color) +{ + FCogDebugPlotEvent& Event = PlotEvent(WorldContextObject, PlotName, EventId, false, Row, Color); + return Event; +} + +//-------------------------------------------------------------------------------------------------------------------------- +FCogDebugPlotEvent& FCogDebugPlot::PlotEventStop(const UObject* WorldContextObject, const FName PlotName, const FName EventId) +{ + FCogDebugEventHistory* EventHistory = RegisterEvent(WorldContextObject, PlotName); + if (EventHistory == nullptr) + { + return DefaultEvent; + } + + FCogDebugPlotEvent& Event = EventHistory->StopEvent(EventId); + return Event; +} + +//-------------------------------------------------------------------------------------------------------------------------- +FCogDebugPlotEvent& FCogDebugPlot::PlotEventToggle(const UObject* WorldContextObject, const FName PlotName, const FName EventId, const bool ToggleValue, const int32 Row, const FColor& Color) +{ + if (ToggleValue) + { + return PlotEventStart(WorldContextObject, PlotName, EventId, Row, Color); + } + else + { + return PlotEventStop(WorldContextObject, PlotName, EventId); + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +FCogDebugEventHistory* FCogDebugPlot::RegisterEvent(const UObject* InWorldContextObject, const FName InName) +{ + + const UWorld* World; + if (ShouldRegisterEntry(InWorldContextObject, World) == false) + { + return nullptr; + } + + FCogDebugEventHistory& Event = Events.FindOrAdd(InName); + Event.Type = FCogDebugHistoryType::Event; + + InitializeEntry(Event, World, InName); + + return &Event; +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogDebugPlot::ResetLastAddedEvent() +{ + LastAddedEventPlotName = NAME_None; + LastAddedEventIndex = INDEX_NONE; +} + +//-------------------------------------------------------------------------------------------------------------------------- +FCogDebugPlotEvent* FCogDebugPlot::GetLastAddedEvent() +{ + FCogDebugEventHistory* EventHistory = Events.Find(LastAddedEventPlotName); + if (EventHistory == nullptr) + { + return nullptr; + } + + return EventHistory->GetLastEvent(); +} \ No newline at end of file diff --git a/Plugins/Cog/Source/CogDebug/Private/CogDebugPlotValue.cpp b/Plugins/Cog/Source/CogDebug/Private/CogDebugPlotValue.cpp new file mode 100644 index 0000000..a9ce059 --- /dev/null +++ b/Plugins/Cog/Source/CogDebug/Private/CogDebugPlotValue.cpp @@ -0,0 +1,112 @@ +#include "CogDebugPlot.h" + +#include "CogImguiHelper.h" +#include "Engine/Engine.h" +#include "Engine/World.h" + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogDebugValueHistory::SetNumRecordedValues(const int32 Value) +{ + Values.reserve(Value); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogDebugValueHistory::AddPoint(float X, float Y) +{ + if (Values.Capacity == 0) + { + Values.reserve(FCogDebugPlot::NumRecordedValues); + } + + if (Values.size() < Values.Capacity) + { + Values.push_back(ImVec2(X, Y)); + } + else + { + Values[ValueOffset] = ImVec2(X, Y); + ValueOffset = (ValueOffset + 1) % Values.size(); + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogDebugValueHistory::Clear() +{ + FCogDebugHistory::Clear(); + + if (Values.size() > 0) + { + Values.shrink(0); + ValueOffset = 0; + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +bool FCogDebugValueHistory::FindValue(float x, float& y) const +{ + y = 0.0f; + + bool FoundAfter = false; + bool FoundBefore = false; + + for (int32 i = Values.size() - 1; i >= 0; --i) + { + //-------------------------------------------------- + // The array cycle so we must offset the index + //-------------------------------------------------- + int32 Index = i; + if (ValueOffset != 0) + { + Index = (i + ValueOffset) % Values.size(); + } + + const ImVec2 Point = Values[Index]; + if (Point.x > x) + { + FoundAfter = true; + } + + if (Point.x < x) + { + FoundBefore = true; + } + + if (FoundAfter && FoundBefore) + { + y = Point.y; + return true; + } + } + + return false; +} + +//-------------------------------------------------------------------------------------------------------------------------- +FCogDebugValueHistory* FCogDebugPlot::RegisterValue(const UObject* InWorldContextObject, const FName InName) +{ + const UWorld* World; + if (ShouldRegisterEntry(InWorldContextObject, World) == false) + { + return nullptr; + } + + FCogDebugValueHistory& Value = Values.FindOrAdd(InName); + Value.Type = FCogDebugHistoryType::Value; + + InitializeEntry(Value, World, InName); + + return &Value; +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogDebugPlot::PlotValue(const UObject* WorldContextObject, const FName PlotName, const float Value) +{ + if (Pause && RecordValuesWhenPause == false) + { return; } + + FCogDebugValueHistory* ValueHistory = RegisterValue(WorldContextObject, PlotName); + if (ValueHistory == nullptr) + { return; } + + ValueHistory->AddPoint(ValueHistory->Time, Value); +} diff --git a/Plugins/Cog/Source/CogDebug/Public/CogDebugPlot.h b/Plugins/Cog/Source/CogDebug/Public/CogDebugPlot.h index 40556ca..bb625d0 100644 --- a/Plugins/Cog/Source/CogDebug/Public/CogDebugPlot.h +++ b/Plugins/Cog/Source/CogDebug/Public/CogDebugPlot.h @@ -7,7 +7,7 @@ #ifdef ENABLE_COG -struct FCogDebugPlotEntry; +struct FCogDebugHistory; //-------------------------------------------------------------------------------------------------------------------------- struct COGDEBUG_API FCogDebugPlotEventParams @@ -19,9 +19,9 @@ struct COGDEBUG_API FCogDebugPlotEventParams //-------------------------------------------------------------------------------------------------------------------------- struct COGDEBUG_API FCogDebugPlotEvent { - float GetActualEndTime(const FCogDebugPlotEntry& Plot) const; + float GetActualEndTime(const FCogDebugHistory& Plot) const; - uint64 GetActualEndFrame(const FCogDebugPlotEntry& Plot) const; + uint64 GetActualEndFrame(const FCogDebugHistory& Plot) const; FCogDebugPlotEvent& AddParam(const FName Name, bool Value); @@ -47,58 +47,68 @@ struct COGDEBUG_API FCogDebugPlotEvent }; //-------------------------------------------------------------------------------------------------------------------------- -struct COGDEBUG_API FCogDebugPlotEntry +enum class FCogDebugHistoryType { - void AssignGraphAndAxis(int32 AssignedRow, ImAxis CurrentYAxis); + Value, + Event, +}; - void AddPoint(float X, float Y); +//-------------------------------------------------------------------------------------------------------------------------- +struct COGDEBUG_API FCogDebugHistory +{ + virtual ~FCogDebugHistory() {} - bool FindValue(float Time, float& Value) const; - - void ResetGraphAndAxis(); - - void Clear(); - - FCogDebugPlotEvent& AddEvent(const FString& OwnerName, bool IsInstant, const FName EventId, const int32 Row, const FColor& Color); - - FCogDebugPlotEvent& StopEvent(const FName EventId); - - FCogDebugPlotEvent* GetLastEvent(); - - FCogDebugPlotEvent* FindLastEventByName(FName EventId); + virtual void Clear() {} FName Name; - bool IsEventPlot = false; - - int32 GraphIndex = INDEX_NONE; - - ImAxis YAxis = ImAxis_COUNT; - float Time = 0; uint64 Frame = 0; - TWeakObjectPtr World; + int32 GraphIndex = 0; + + FCogDebugHistoryType Type = FCogDebugHistoryType::Value; + + TWeakObjectPtr World; +}; + +//-------------------------------------------------------------------------------------------------------------------------- +struct COGDEBUG_API FCogDebugValueHistory : FCogDebugHistory +{ + void AddPoint(float X, float Y); + + bool FindValue(float Time, float& Value) const; + + virtual void Clear() override; + + void SetNumRecordedValues(const int32 Value); - //-------------------------- - // Values - //-------------------------- int32 ValueOffset = 0; ImVector Values; bool ShowValuesMarkers = false; +}; + +//-------------------------------------------------------------------------------------------------------------------------- +struct COGDEBUG_API FCogDebugEventHistory : FCogDebugHistory +{ + FCogDebugPlotEvent& AddEvent(const FString& OwnerName, bool IsInstant, const FName EventId, const int32 Row, const FColor& Color); + + FCogDebugPlotEvent& StopEvent(const FName EventId); + + FCogDebugPlotEvent* GetLastEvent(); + + FCogDebugPlotEvent* FindLastEventByName(FName EventId); + + virtual void Clear() override; - //-------------------------- - // Events - //-------------------------- int32 EventOffset = 0; TArray Events; int32 MaxRow = 1; - }; //-------------------------------------------------------------------------------------------------------------------------- @@ -108,6 +118,11 @@ class COGDEBUG_API FCogDebugPlot public: static constexpr int32 AutoRow = -1; + static constexpr int32 MaxNumGraphs = 5; + + static constexpr int32 MaxNumEntriesPerGraph = 10; + + static void PlotValue(const UObject* WorldContextObject, const FName PlotName, const float Value); static FCogDebugPlotEvent& PlotEvent(const UObject* WorldContextObject, const FName PlotName, const FName EventId, bool IsInstant, const int32 Row = AutoRow, const FColor& Color = FColor::Transparent); @@ -120,29 +135,40 @@ public: static FCogDebugPlotEvent& PlotEventToggle(const UObject* WorldContextObject, const FName PlotName, const FName EventId, const bool ToggleValue, const int32 Row = AutoRow, const FColor& Color = FColor::Transparent); + static FCogDebugHistory* FindEntry(const FName InName); + + static void SetNumRecordedValues(int32 InValue); + static void Reset(); static void Clear(); - static FCogDebugPlotEntry* FindEntry(const FName Name); + static TMap Values; - static FCogDebugPlotEntry* FindEntry(bool IsEvent, const FName Name); - - static TArray Plots; - - static TArray Events; + static TMap Events; static bool IsVisible; static bool Pause; + static bool RecordValuesWhenPause; + + private: - friend struct FCogDebugPlotEntry; + friend struct FCogDebugHistory; + friend struct FCogDebugEventHistory; + friend struct FCogDebugValueHistory; static void ResetLastAddedEvent(); - static FCogDebugPlotEntry* RegisterPlot(const UObject* Owner, const FName PlotName, bool IsEventPlot); - + static FCogDebugValueHistory* RegisterValue(const UObject* InWorldContextObject, const FName InName); + + static FCogDebugEventHistory* RegisterEvent(const UObject* InWorldContextObject, const FName InName); + + static bool ShouldRegisterEntry(const UObject* WorldContextObject, const UWorld*& World); + + static void InitializeEntry(FCogDebugHistory& OutValue, const UWorld* InWorld, const FName InName); + static FCogDebugPlotEvent* GetLastAddedEvent(); static void OccupyGraphRow(const int32 InGraphIndex, const int32 InRow); @@ -151,6 +177,8 @@ private: static int32 FindFreeGraphRow(const int32 InGraphIndex); + static int32 NumRecordedValues; + static FName LastAddedEventPlotName; static int32 LastAddedEventIndex; diff --git a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Plots.cpp b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Plots.cpp index 0399111..937a066 100644 --- a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Plots.cpp +++ b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Plots.cpp @@ -17,6 +17,11 @@ void FCogEngineWindow_Plots::Initialize() Config = GetConfig(); + if (Config != nullptr) + { + RefreshPlotSettings(); + } + FCogDebugPlot::Clear(); } @@ -34,8 +39,9 @@ void FCogEngineWindow_Plots::ResetConfig() Super::ResetConfig(); Config->Reset(); -} + RefreshPlotSettings(); +} //-------------------------------------------------------------------------------------------------------------------------- void FCogEngineWindow_Plots::RenderTick(float DeltaTime) @@ -49,23 +55,6 @@ void FCogEngineWindow_Plots::RenderContent() { Super::RenderContent(); - TArray VisiblePlots; - for (FCogDebugPlotEntry& Plot : FCogDebugPlot::Plots) - { - if (Plot.YAxis != ImAxis_COUNT && Plot.GraphIndex != INDEX_NONE) - { - VisiblePlots.Add(&Plot); - } - } - - for (FCogDebugPlotEntry& Event : FCogDebugPlot::Events) - { - if (Event.YAxis != ImAxis_COUNT && Event.GraphIndex != INDEX_NONE) - { - VisiblePlots.Add(&Event); - } - } - RenderMenu(); if (Config->DockEntries) @@ -86,7 +75,7 @@ void FCogEngineWindow_Plots::RenderContent() RenderAllEntriesNames(ImVec2(0, -1)); ImGui::TableNextColumn(); - RenderPlots(VisiblePlots); + RenderPlots(); ImGui::EndTable(); } @@ -94,12 +83,19 @@ void FCogEngineWindow_Plots::RenderContent() } else { - RenderPlots(VisiblePlots); + RenderPlots(); } bApplyTimeScale = false; } +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Plots::RefreshPlotSettings() +{ + FCogDebugPlot::SetNumRecordedValues(Config->NumRecordedValues); + FCogDebugPlot::RecordValuesWhenPause = Config->RecordValuesWhenPaused; +} + //-------------------------------------------------------------------------------------------------------------------------- void FCogEngineWindow_Plots::RenderMenu() { @@ -116,33 +112,41 @@ void FCogEngineWindow_Plots::RenderMenu() if (ImGui::BeginMenu("Options")) { - if (ImGui::MenuItem("Reset")) - { - FCogDebugPlot::Pause = false; - FCogDebugPlot::Reset(); - ResetConfig(); - } - - ImGui::Separator(); + FCogWindowWidgets::SetNextItemToShortWidth(); + ImGui::SliderInt("Num graphs", &Config->NumGraphs, 1, UCogEngineConfig_Plots::MaxNumGraphs); FCogWindowWidgets::SetNextItemToShortWidth(); - if (ImGui::SliderInt("Num Graphs", &Config->NumGraphs, 1, 5)) + ImGui::SliderInt("Num Y axis", &Config->NumYAxis, 1, 3); + + FCogWindowWidgets::SetNextItemToShortWidth(); + if (ImGui::SliderFloat("Time range", &Config->TimeRange, 1.0f, 100.0f, "%0.0f")) { bApplyTimeScale = true; } FCogWindowWidgets::SetNextItemToShortWidth(); - ImGui::SliderInt("Num YAxis", &Config->NumYAxis, 0, 3); - - FCogWindowWidgets::SetNextItemToShortWidth(); - if (ImGui::SliderFloat("Time range", &Config->TimeRange, 1.0f, 100.0f, "%0.1f")) + if (ImGui::SliderInt("Num recorded values", &Config->NumRecordedValues, 100, 10000)) { - bApplyTimeScale = true; + Config->NumRecordedValues = (Config->NumRecordedValues / 100) * 100; + } + + if (ImGui::IsItemDeactivatedAfterEdit()) + { + RefreshPlotSettings(); } + FCogWindowWidgets::SetNextItemToShortWidth(); + ImGui::SliderFloat("Auto-fit padding", &Config->AutoFitPadding, 0.0f, 0.2f, "%0.2f"); + FCogWindowWidgets::SetNextItemToShortWidth(); ImGui::SliderFloat("Drag pause sensitivity", &Config->DragPauseSensitivity, 1.0f, 50.0f, "%0.0f"); + ImGui::Checkbox("Record values when paused", &Config->RecordValuesWhenPaused); + if (ImGui::IsItemDeactivatedAfterEdit()) + { + RefreshPlotSettings(); + } + FCogWindowWidgets::SetNextItemToShortWidth(); ImGui::Checkbox("Show time bar at game time", &Config->ShowTimeBarAtGameTime); @@ -159,6 +163,16 @@ void FCogEngineWindow_Plots::RenderMenu() FCogImguiHelper::ColorEdit4("Pause background color", Config->PauseBackgroundColor, ColorEditFlags); ImGui::SetItemTooltip("Background color of the plot when paused."); + ImGui::Separator(); + + if (ImGui::MenuItem("Reset Settings")) + { + FCogDebugPlot::Pause = false; + FCogDebugPlot::Reset(); + ResetConfig(); + bApplyTimeScale = true; + } + ImGui::EndMenu(); } @@ -174,20 +188,31 @@ void FCogEngineWindow_Plots::RenderMenu() } //-------------------------------------------------------------------------------------------------------------------------- -void FCogEngineWindow_Plots::RenderEntryName(const int Index, FCogDebugPlotEntry& Entry) +void FCogEngineWindow_Plots::RenderEntryName(const int Index, FCogDebugHistory& Entry) { ImGui::PushID(Index); - const bool IsAssignedToRow = Entry.GraphIndex != INDEX_NONE; - if (ImGui::Selectable(TCHAR_TO_ANSI(*Entry.Name.ToString()), IsAssignedToRow, ImGuiSelectableFlags_AllowDoubleClick)) + bool IsAssignedToGraph = false; + + for (int32 i = 0; i < UCogEngineConfig_Plots::MaxNumGraphs; ++i) { - if (IsAssignedToRow) + FCogEngineConfig_Plots_GraphInfo& GraphInfo = Config->Graphs[i]; + if (GraphInfo.Entries.ContainsByPredicate([Entry](const auto& InEntry) { return InEntry.Name == Entry.Name; })) { - Entry.ResetGraphAndAxis(); + IsAssignedToGraph = true; + break; + } + } + + if (ImGui::Selectable(TCHAR_TO_ANSI(*Entry.Name.ToString()), IsAssignedToGraph, ImGuiSelectableFlags_AllowDoubleClick)) + { + if (IsAssignedToGraph) + { + UnassignToGraphAndAxis(Entry.Name); } else { - Entry.AssignGraphAndAxis(0, ImAxis_Y1); + AssignToGraphAndAxis(Entry.Name, 0, ImAxis_Y1); } } @@ -205,50 +230,46 @@ void FCogEngineWindow_Plots::RenderEntryName(const int Index, FCogDebugPlotEntry //-------------------------------------------------------------------------------------------------------------------------- void FCogEngineWindow_Plots::RenderAllEntriesNames(const ImVec2& InSize) { + const int32 Indent = ImGui::GetFontSize() * 0.5f; + if (ImGui::BeginChild("Entries", InSize)) { - if (Config->DockEntries) - { - ImGui::Indent(6); - } - int Index = 0; if (FCogWindowWidgets::DarkCollapsingHeader("Events", ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::Indent(Indent); if (FCogDebugPlot::Events.IsEmpty()) { ImGui::TextDisabled("No event added yet"); } else { - for (FCogDebugPlotEntry& Event : FCogDebugPlot::Events) + for (auto& kv : FCogDebugPlot::Events) { - RenderEntryName(Index, Event); + RenderEntryName(Index, kv.Value); Index++; } } + ImGui::Unindent(Indent); } if (FCogWindowWidgets::DarkCollapsingHeader("Plots", ImGuiTreeNodeFlags_DefaultOpen)) { - if (FCogDebugPlot::Plots.IsEmpty()) + ImGui::Indent(Indent); + if (FCogDebugPlot::Values.IsEmpty()) { ImGui::TextDisabled("No plot added yet"); } else { - for (FCogDebugPlotEntry& Plot : FCogDebugPlot::Plots) + for (auto& kv : FCogDebugPlot::Values) { - RenderEntryName(Index, Plot); + RenderEntryName(Index, kv.Value); Index++; } } - } - - if (Config->DockEntries) - { - ImGui::Unindent(); + ImGui::Unindent(Indent); } } ImGui::EndChild(); @@ -257,17 +278,14 @@ void FCogEngineWindow_Plots::RenderAllEntriesNames(const ImVec2& InSize) { if (const ImGuiPayload* Payload = ImGui::AcceptDragDropPayload("DragAndDrop")) { - if (FCogDebugPlotEntry* Plot = FCogDebugPlot::FindEntry(FName((const char*)Payload->Data))) - { - Plot->ResetGraphAndAxis(); - } + UnassignToGraphAndAxis(GetDroppedEntryName(Payload)); } ImGui::EndDragDropTarget(); } } //-------------------------------------------------------------------------------------------------------------------------- -void FCogEngineWindow_Plots::RenderPlots(const TArray& VisiblePlots) const +void FCogEngineWindow_Plots::RenderPlots() { if (ImGui::BeginChild("Graph", ImVec2(0, -1))) { @@ -281,45 +299,66 @@ void FCogEngineWindow_Plots::RenderPlots(const TArray& Visi ImPlot::PushStyleColor(ImPlotCol_PlotBg, FCogImguiHelper::ToImVec4(Config->PauseBackgroundColor)); } + ImPlot::PushStyleVar(ImPlotStyleVar_FitPadding, ImVec2(0.0f, Config->AutoFitPadding)); + if (ImPlot::BeginSubplots("", Config->NumGraphs, 1, ImVec2(-1, -1), SubplotsFlags, RowRatios, ColRatios)) { - for (int PlotIndex = 0; PlotIndex < Config->NumGraphs; ++PlotIndex) + for (int32 GraphIndex = 0; GraphIndex < Config->NumGraphs && GraphIndex < UCogEngineConfig_Plots::MaxNumGraphs; ++GraphIndex) { + ImGui::PushID(GraphIndex); + + FCogEngineConfig_Plots_GraphInfo& GraphInfo = Config->Graphs[GraphIndex]; + if (ImPlot::BeginPlot("##Plot", ImVec2(-1, 250))) { - ImPlotAxisFlags HasPlotOnAxisY1 = false; - ImPlotAxisFlags HasPlotOnAxisY2 = false; - ImPlotAxisFlags HasPlotOnAxisY3 = false; - - for (const FCogDebugPlotEntry* PlotPtr : VisiblePlots) - { - HasPlotOnAxisY1 |= PlotPtr->YAxis == ImAxis_Y1 && PlotPtr->GraphIndex == PlotIndex; - HasPlotOnAxisY2 |= PlotPtr->YAxis == ImAxis_Y2 && PlotPtr->GraphIndex == PlotIndex; - HasPlotOnAxisY3 |= PlotPtr->YAxis == ImAxis_Y3 && PlotPtr->GraphIndex == PlotIndex; - } - ImPlot::SetupAxis(ImAxis_X1, nullptr, ImPlotAxisFlags_NoTickLabels | ImPlotAxisFlags_NoGridLines); - if (Config->NumYAxis > 0) - { - ImPlot::SetupAxis(ImAxis_Y1, HasPlotOnAxisY1 ? "" : "[drop here]", (HasPlotOnAxisY1 ? ImPlotAxisFlags_None : (ImPlotAxisFlags_NoTickLabels | ImPlotAxisFlags_NoGridLines)) | ImPlotAxisFlags_AutoFit); - } - - if (Config->NumYAxis > 1) - { - ImPlot::SetupAxis(ImAxis_Y2, HasPlotOnAxisY2 ? "" : "[drop here]", (HasPlotOnAxisY2 ? ImPlotAxisFlags_None : (ImPlotAxisFlags_NoTickLabels | ImPlotAxisFlags_NoGridLines)) | ImPlotAxisFlags_AutoFit | ImPlotAxisFlags_Opposite); - } - - if (Config->NumYAxis > 2) - { - ImPlot::SetupAxis(ImAxis_Y3, HasPlotOnAxisY3 ? "" : "[drop here]", (HasPlotOnAxisY3 ? ImPlotAxisFlags_None : (ImPlotAxisFlags_NoTickLabels | ImPlotAxisFlags_NoGridLines)) | ImPlotAxisFlags_AutoFit | ImPlotAxisFlags_Opposite); - } - //-------------------------------------------------------------------------------------------------- // Set the initial X axis range. After, it is automatically updated to move with the current time. //-------------------------------------------------------------------------------------------------- ImPlot::SetupAxisLimits(ImAxis_X1, 0, Config->TimeRange, ImGuiCond_Appearing); + //-------------------------------------------------------------------------------------------------- + // Setup the Y axis + //-------------------------------------------------------------------------------------------------- + for (int32 YAxisIndex = 0; YAxisIndex <= (ImAxis_Y3 - ImAxis_Y1); ++YAxisIndex) + { + const ImAxis YAxis = ImAxis_Y1 + YAxisIndex; + + bool IsAssigned = false; + int32 YMax = 0; + for (const FCogEngineConfig_Plots_GraphEntryInfo& GraphEntry : GraphInfo.Entries) + { + IsAssigned |= GraphEntry.YAxis == YAxis; + + if (FCogDebugEventHistory* EventHistory = FCogDebugPlot::Events.Find(GraphEntry.Name)) + { + YMax = FMath::Max(FMath::Max(5, YMax), EventHistory->MaxRow); + } + } + + const bool IsAxisVisible = IsAssigned || (YAxisIndex < Config->NumYAxis); + if (IsAxisVisible) + { + ImPlotAxisFlags Flags = IsAssigned ? ImPlotAxisFlags_NoGridLines | ImPlotAxisFlags_AutoFit + : ImPlotAxisFlags_NoTickLabels | ImPlotAxisFlags_NoGridLines | ImPlotAxisFlags_AutoFit; + if (YAxisIndex > 0) + { + Flags |= ImPlotAxisFlags_Opposite; + } + + ImPlot::SetupAxis(YAxis, IsAssigned || (Config->NumYAxis == 1) ? "" : "[drop here]", Flags); + + //-------------------------------------------------------------------------------- + // Set the Y axis limit for Events. + //-------------------------------------------------------------------------------- + if (YMax > 0) + { + ImPlot::SetupAxisLimits(YAxis, 0, YMax, ImGuiCond_Always); + } + } + } + const ImPlotRange& PlotRange = ImPlot::GetCurrentPlot()->Axes[ImAxis_X1].Range; const float TimeRange = PlotRange.Max - PlotRange.Min; @@ -331,7 +370,7 @@ void FCogEngineWindow_Plots::RenderPlots(const TArray& Visi const float Time = GetWorld() ? GetWorld()->GetTimeSeconds() : 0.0; //------------------------------------------------------------------ - // Setup all the Z and Y axis limits. Must be done before calling + // Setup all the X axis limits. Must be done before calling // ImPlot::GetPlotPos or ImPlot::GetPlotSize as it calls SetupLock() //------------------------------------------------------------------ { @@ -347,23 +386,6 @@ void FCogEngineWindow_Plots::RenderPlots(const TArray& Visi { ImPlot::SetupAxisLimits(ImAxis_X1, Time - Config->TimeRange, Time, ImGuiCond_Always); } - - //-------------------------------------------------------------------------------- - // Set the Y axis limit for Events. - //-------------------------------------------------------------------------------- - for (const FCogDebugPlotEntry* PlotPtr : VisiblePlots) - { - if (PlotPtr == nullptr) - { continue; } - - if (PlotPtr->GraphIndex != PlotIndex) - { continue; } - - if (PlotPtr->IsEventPlot) - { - ImPlot::SetupAxisLimits(PlotPtr->YAxis, 0, PlotPtr->MaxRow + 2, ImGuiCond_Always); - } - } } const ImVec2 PlotMin = ImPlot::GetPlotPos(); @@ -373,17 +395,21 @@ void FCogEngineWindow_Plots::RenderPlots(const TArray& Visi //---------------------------------------------------------------- // Pause the scrolling if the user drag inside //---------------------------------------------------------------- - const ImVec2 Mouse = ImGui::GetMousePos(); - if (Mouse.x > PlotMin.x - && Mouse.y > PlotMin.y - && Mouse.x < PlotMax.x - && Mouse.y < PlotMax.y - && ImGui::GetDragDropPayload() == nullptr) + if (ImGui::IsWindowFocused()) { - const ImVec2 Drag = ImGui::GetMouseDragDelta(0); - if (FMath::Abs(Drag.x) > Config->DragPauseSensitivity) + const ImVec2 Mouse = ImGui::GetMousePos(); + if (Mouse.x > PlotMin.x + && Mouse.y > PlotMin.y + && Mouse.x < PlotMax.x + && Mouse.y < PlotMax.y + && ImGui::GetDragDropPayload() == nullptr) { - FCogDebugPlot::Pause = true; + const ImVec2 Drag = ImGui::GetMouseDragDelta(0); + + if (FMath::Abs(Drag.x) > Config->DragPauseSensitivity) + { + FCogDebugPlot::Pause = true; + } } } @@ -417,43 +443,46 @@ void FCogEngineWindow_Plots::RenderPlots(const TArray& Visi } //----------------------------------------------------------- - // Draw all the plots assigned to this row + // Draw all the plots assigned to this graph //----------------------------------------------------------- - for (FCogDebugPlotEntry* PlotPtr : VisiblePlots) + for (FCogEngineConfig_Plots_GraphEntryInfo& Entry : GraphInfo.Entries) { - if (PlotPtr == nullptr) - { continue; } + FCogDebugHistory* History = FCogDebugPlot::FindEntry(Entry.Name); + if (History == nullptr) + { + continue; + } - FCogDebugPlotEntry& Plot = *PlotPtr; - if (Plot.GraphIndex != PlotIndex) - { continue; } - - ImPlot::SetAxis(Plot.YAxis); + ImPlot::SetAxis(Entry.YAxis); ImPlot::SetNextLineStyle(IMPLOT_AUTO_COL); - const auto Label = StringCast(*Plot.Name.ToString()); + const auto Label = StringCast(*Entry.Name.ToString()); //------------------------------------------------------- // Plot Events //------------------------------------------------------- - if (Plot.IsEventPlot) + switch (History->Type) { - RenderEvents(Plot, Label.Get(), PlotMin, PlotMax); - } - //------------------------------------------------------- - // Plot Values - //------------------------------------------------------- - else if (Plot.Values.empty() == false) - { - RenderValues(Plot, Label.Get()); + case FCogDebugHistoryType::Event: + { + RenderEvents(*static_cast(History), Label.Get(), PlotMin, PlotMax); + break; + } + + case FCogDebugHistoryType::Value: + { + RenderValues(*static_cast(History), Label.Get()); + break; + } } + //------------------------------------------------------- // Allow legend item labels to be drag and drop sources //------------------------------------------------------- if (ImPlot::BeginDragDropSourceItem(Label.Get())) { - const auto EntryName = StringCast(*Plot.Name.ToString()); + const auto EntryName = StringCast(*Entry.Name.ToString()); ImGui::SetDragDropPayload("DragAndDrop", EntryName.Get(), EntryName.Length() + 1); ImGui::TextUnformatted(EntryName.Get()); ImPlot::EndDragDropSource(); @@ -467,10 +496,7 @@ void FCogEngineWindow_Plots::RenderPlots(const TArray& Visi { if (const ImGuiPayload* Payload = ImGui::AcceptDragDropPayload("DragAndDrop")) { - if (FCogDebugPlotEntry* Plot = FCogDebugPlot::FindEntry(FName((const char*)Payload->Data))) - { - Plot->AssignGraphAndAxis(PlotIndex, ImAxis_Y1); - } + AssignToGraphAndAxis(GetDroppedEntryName(Payload), GraphIndex, ImAxis_Y1); } ImPlot::EndDragDropTarget(); } @@ -478,16 +504,14 @@ void FCogEngineWindow_Plots::RenderPlots(const TArray& Visi //------------------------------------------------------- // Allow each y-axis to be a drag and drop target //------------------------------------------------------- - for (int y = ImAxis_Y1; y <= ImAxis_Y3; ++y) + for (int32 YAxisIndex = 0; YAxisIndex < Config->NumYAxis; ++YAxisIndex) { - if (ImPlot::BeginDragDropTargetAxis(y)) + const ImAxis YAxis = ImAxis_Y1 + YAxisIndex; + if (ImPlot::BeginDragDropTargetAxis(YAxis)) { if (const ImGuiPayload* Payload = ImGui::AcceptDragDropPayload("DragAndDrop")) { - if (FCogDebugPlotEntry* Plot = FCogDebugPlot::FindEntry(FName((const char*)Payload->Data))) - { - Plot->AssignGraphAndAxis(PlotIndex, y); - } + AssignToGraphAndAxis(GetDroppedEntryName(Payload), GraphIndex, YAxis); } ImPlot::EndDragDropTarget(); } @@ -500,20 +524,21 @@ void FCogEngineWindow_Plots::RenderPlots(const TArray& Visi { if (const ImGuiPayload* Payload = ImGui::AcceptDragDropPayload("DragAndDrop")) { - if (FCogDebugPlotEntry* Plot = FCogDebugPlot::FindEntry(FName((const char*)Payload->Data))) - { - Plot->AssignGraphAndAxis(PlotIndex, ImAxis_Y1); - } + AssignToGraphAndAxis(GetDroppedEntryName(Payload), GraphIndex, ImAxis_Y1); } ImPlot::EndDragDropTarget(); } ImPlot::EndPlot(); } + + ImGui::PopID(); } ImPlot::EndSubplots(); } + ImPlot::PopStyleVar(); + if (PushPlotBgStyle) { ImPlot::PopStyleColor(); @@ -523,8 +548,13 @@ void FCogEngineWindow_Plots::RenderPlots(const TArray& Visi } //-------------------------------------------------------------------------------------------------------------------------- -void FCogEngineWindow_Plots::RenderValues(FCogDebugPlotEntry& Entry, const char* Label) const +void FCogEngineWindow_Plots::RenderValues(FCogDebugValueHistory& Entry, const char* Label) const { + if (Entry.Values.empty()) + { + return; + } + //---------------------------------------------------------------- // Value at cursor tooltip //---------------------------------------------------------------- @@ -568,7 +598,7 @@ void FCogEngineWindow_Plots::RenderValues(FCogDebugPlotEntry& Entry, const char* } //-------------------------------------------------------------------------------------------------------------------------- -void FCogEngineWindow_Plots::RenderEvents(FCogDebugPlotEntry& Entry, const char* Label, const ImVec2& PlotMin, const ImVec2& PlotMax) const +void FCogEngineWindow_Plots::RenderEvents(FCogDebugEventHistory& Entry, const char* Label, const ImVec2& PlotMin, const ImVec2& PlotMax) const { const ImVec2 Mouse = ImGui::GetMousePos(); ImDrawList* PlotDrawList = ImPlot::GetPlotDrawList(); @@ -581,7 +611,7 @@ void FCogEngineWindow_Plots::RenderEvents(FCogDebugPlotEntry& Entry, const char* ImVector DummyData; DummyData.push_back(ImVec2(0, 0)); DummyData.push_back(ImVec2(0, 8)); - ImPlot::PlotLine(Label, &DummyData[0].x, &DummyData[0].y, DummyData.size(), Entry.ValueOffset, 2 * sizeof(float)); + ImPlot::PlotLine(Label, &DummyData[0].x, &DummyData[0].y, DummyData.size(), Entry.EventOffset, 2 * sizeof(float)); const FCogDebugPlotEvent* HoveredEvent = nullptr; @@ -641,7 +671,7 @@ void FCogEngineWindow_Plots::RenderEvents(FCogDebugPlotEntry& Entry, const char* } //-------------------------------------------------------------------------------------------------------------------------- -void FCogEngineWindow_Plots::RenderEventTooltip(const FCogDebugPlotEvent* HoveredEvent, const FCogDebugPlotEntry& Entry) +void FCogEngineWindow_Plots::RenderEventTooltip(const FCogDebugPlotEvent* HoveredEvent, const FCogDebugHistory& Entry) { if (ImPlot::IsPlotHovered() && HoveredEvent != nullptr) { @@ -716,3 +746,54 @@ void FCogEngineWindow_Plots::RenderEventTooltip(const FCogDebugPlotEvent* Hovere } } } + + +//-------------------------------------------------------------------------------------------------------------------------- +FName FCogEngineWindow_Plots::GetDroppedEntryName(const ImGuiPayload* Payload) +{ + return FName(static_cast(Payload->Data)); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Plots::AssignToGraphAndAxis(const FName InName, const int32 InGraphIndex, const ImAxis InYAxis) +{ + UnassignToGraphAndAxis(InName); + + FCogDebugHistory* History = FCogDebugPlot::FindEntry(InName); + if (History == nullptr) + { return; } + + History->GraphIndex = InGraphIndex; + + FCogEngineConfig_Plots_GraphInfo& GraphInfo = Config->Graphs[InGraphIndex]; + + FCogEngineConfig_Plots_GraphEntryInfo* CorrespondingEntry = GraphInfo.Entries.FindByPredicate( + [InName](const auto& InEntry) { return InEntry.Name == InName; }); + + if (CorrespondingEntry == nullptr) + { + GraphInfo.Entries.Add({InName, InYAxis}); + } + else + { + CorrespondingEntry->YAxis = InYAxis; + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Plots::UnassignToGraphAndAxis(const FName InName) +{ + const FCogDebugHistory* History = FCogDebugPlot::FindEntry(InName); + if (History == nullptr) + { return; } + + FCogEngineConfig_Plots_GraphInfo& GraphInfo = Config->Graphs[History->GraphIndex]; + + const int32 Index = GraphInfo.Entries.IndexOfByPredicate([InName](const auto& InEntry) { return InEntry.Name == InName; }); + if (Index != INDEX_NONE) + { + GraphInfo.Entries.RemoveAt(Index); + } +} + + diff --git a/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Plots.h b/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Plots.h index f337506..bc31172 100644 --- a/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Plots.h +++ b/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Plots.h @@ -3,13 +3,17 @@ #include "CoreMinimal.h" #include "CogCommonConfig.h" #include "CogWindow.h" +#include "implot.h" #include "CogEngineWindow_Plots.generated.h" +struct FCogDebugEventHistory; +struct FCogDebugValueHistory; struct ImVec2; struct FCogDebugPlotEvent; -struct FCogDebugPlotEntry; +struct FCogDebugHistory; class UCogEngineConfig_Plots; +//-------------------------------------------------------------------------------------------------------------------------- class COGENGINE_API FCogEngineWindow_Plots : public FCogWindow { typedef FCogWindow Super; @@ -28,23 +32,53 @@ protected: virtual void RenderAllEntriesNames(const ImVec2& InSize); - virtual void RenderEntryName(const int Index, FCogDebugPlotEntry& Entry); + virtual void RenderEntryName(const int Index, FCogDebugHistory& Entry); - virtual void RenderPlots(const TArray& VisiblePlots) const; + virtual void RenderPlots(); virtual void RenderMenu(); - virtual void RenderValues(FCogDebugPlotEntry& Entry, const char* Label) const; + virtual void RenderValues(FCogDebugValueHistory& Entry, const char* Label) const; - virtual void RenderEvents(FCogDebugPlotEntry& Entry, const char* Label, const ImVec2& PlotMin, const ImVec2& PlotMax) const; + virtual void RenderEvents(FCogDebugEventHistory& Entry, const char* Label, const ImVec2& PlotMin, const ImVec2& PlotMax) const; - static void RenderEventTooltip(const FCogDebugPlotEvent* HoveredEvent, const FCogDebugPlotEntry& Entry); + static void RenderEventTooltip(const FCogDebugPlotEvent* HoveredEvent, const FCogDebugHistory& Entry); + virtual void AssignToGraphAndAxis(const FName InName, const int32 InGraphIndex, const ImAxis InYAxis); + + virtual void UnassignToGraphAndAxis(const FName InName); + + virtual void RefreshPlotSettings(); + + static FName GetDroppedEntryName(const ImGuiPayload* Payload); + TObjectPtr Config = nullptr; bool bApplyTimeScale = false; +}; -private: +//-------------------------------------------------------------------------------------------------------------------------- +USTRUCT() +struct FCogEngineConfig_Plots_GraphEntryInfo +{ + GENERATED_BODY() + + UPROPERTY(Config) + FName Name; + + UPROPERTY(Config) + int32 YAxis = 0; +}; + + +//-------------------------------------------------------------------------------------------------------------------------- +USTRUCT() +struct FCogEngineConfig_Plots_GraphInfo +{ + GENERATED_BODY() + + UPROPERTY(Config) + TArray Entries; }; @@ -56,15 +90,23 @@ class UCogEngineConfig_Plots : public UCogCommonConfig public: + static constexpr int32 MaxNumGraphs = 5; + UPROPERTY(Config) int NumGraphs = 1; UPROPERTY(Config) int NumYAxis = 1; + UPROPERTY(Config) + bool RecordValuesWhenPaused = true; + UPROPERTY(Config) float TimeRange = 20.0f; + UPROPERTY(Config) + int32 NumRecordedValues = 2000; + UPROPERTY(Config) bool ShowTimeBarAtGameTime = true; @@ -83,15 +125,29 @@ public: UPROPERTY(Config) bool DockEntries = false; + UPROPERTY(Config) + float AutoFitPadding = 0.1f; + + UPROPERTY(Config) + FCogEngineConfig_Plots_GraphInfo Graphs[MaxNumGraphs]; + virtual void Reset() override { NumGraphs = 1; TimeRange = 20.0f; + RecordValuesWhenPaused = true; ShowTimeBarAtGameTime = true; ShowTimeBarAtCursor = true; ShowValueAtCursor = true; DragPauseSensitivity = 10.0f; PauseBackgroundColor = FColor(10, 0, 0, 255); DockEntries = false; + NumRecordedValues = 2000; + AutoFitPadding = 0.1f; + + for (FCogEngineConfig_Plots_GraphInfo& Graph : Graphs) + { + Graph.Entries.Empty(); + } } }; \ No newline at end of file