EM_Task/TraceInsights/Private/Insights/Widgets/STimingView.cpp
Boshuang Zhao 5144a49c9b add
2026-02-13 16:18:33 +08:00

4163 lines
152 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "STimingView.h"
#include "Containers/ArrayBuilder.h"
#include "Containers/MapBuilder.h"
#include "EditorStyleSet.h"
#include "Features/IModularFeatures.h"
#include "Fonts/FontMeasure.h"
#include "Fonts/SlateFontInfo.h"
#include "Framework/Application/MenuStack.h"
#include "Framework/Application/SlateApplication.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "HAL/PlatformTime.h"
#include "Layout/WidgetPath.h"
#include "Misc/Paths.h"
#include "Rendering/DrawElements.h"
#include "SlateOptMacros.h"
#include "Styling/CoreStyle.h"
#include "Styling/SlateBrush.h"
#include "TraceServices/AnalysisService.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Input/SCheckBox.h"
#include "Widgets/Input/SComboButton.h"
#include "Widgets/Layout/SScrollBar.h"
#include "Widgets/Layout/SSpacer.h"
#include "Widgets/SOverlay.h"
#include "Widgets/Text/STextBlock.h"
// Desktop Platform
#include "DesktopPlatformModule.h"
#include "IDesktopPlatform.h"
#include "Insights/ViewModels/ThreadTrackEvent.h"
// Insights
#include "Insights/Common/PaintUtils.h"
#include "Insights/Common/Stopwatch.h"
#include "Insights/Common/TimeUtils.h"
#include "Insights/ITimingViewExtender.h"
#include "Insights/InsightsManager.h"
#include "Insights/InsightsStyle.h"
#include "Insights/LoadingProfiler/LoadingProfilerManager.h"
#include "Insights/LoadingProfiler/Widgets/SLoadingProfilerWindow.h"
#include "Insights/Table/Widgets/STableTreeView.h"
#include "Insights/Tests/TimingProfilerTests.h"
#include "Insights/TimingProfilerCommon.h"
#include "Insights/TimingProfilerManager.h"
#include "Insights/ViewModels/BaseTimingTrack.h"
#include "Insights/ViewModels/DrawHelpers.h"
#include "Insights/ViewModels/FileActivityTimingTrack.h"
#include "Insights/ViewModels/FrameTimingTrack.h"
#include "Insights/ViewModels/GraphSeries.h"
#include "Insights/ViewModels/GraphTrack.h"
#include "Insights/ViewModels/LoadingTimingTrack.h"
#include "Insights/ViewModels/MarkersTimingTrack.h"
#include "Insights/ViewModels/MinimalTimerExporter.h"
#include "Insights/ViewModels/ThreadTimingTrack.h"
#include "Insights/ViewModels/TimeRulerTrack.h"
#include "Insights/ViewModels/TimingEventSearch.h"
#include "Insights/ViewModels/TimingGraphTrack.h"
#include "Insights/ViewModels/TimingViewDrawHelper.h"
#include "Insights/Widgets/SStatsView.h"
#include "Insights/Widgets/STimersView.h"
#include "Insights/Widgets/STimingProfilerWindow.h"
#include "Insights/Widgets/STimingViewTrackList.h"
#include <limits>
////////////////////////////////////////////////////////////////////////////////////////////////////
#define LOCTEXT_NAMESPACE "STimingView"
#define ACTIVATE_BENCHMARK 0
// start auto generated ids from a big number (MSB set to 1) to avoid collisions with ids for gpu/cpu tracks based on 32bit timeline index
uint64 FBaseTimingTrack::IdGenerator = (1ULL << 63);
const TCHAR* GetFileActivityTypeName(Trace::EFileActivityType Type);
uint32 GetFileActivityTypeColor(Trace::EFileActivityType Type);
namespace Insights
{
const FName TimingViewExtenderFeatureName(TEXT("TimingViewExtender"));
}
////////////////////////////////////////////////////////////////////////////////////////////////////
STimingView::STimingView()
: bScrollableTracksOrderIsDirty(false), FrameSharedState(MakeShared<FFrameSharedState>(this)), ThreadTimingSharedState(MakeShared<FThreadTimingSharedState>(this)), LoadingSharedState(MakeShared<FLoadingSharedState>(this)), bAssetLoadingMode(false), FileActivitySharedState(MakeShared<FFileActivitySharedState>(this)), TimeRulerTrack(MakeShared<FTimeRulerTrack>()), DefaultTimeMarker(MakeShared<Insights::FTimeMarker>()), MarkersTrack(MakeShared<FMarkersTimingTrack>()), GraphTrack(MakeShared<FTimingGraphTrack>()), WhiteBrush(FInsightsStyle::Get().GetBrush("WhiteBrush")), MainFont(FCoreStyle::GetDefaultFontStyle("Regular", 8))
{
DefaultTimeMarker->SetName(TEXT(""));
DefaultTimeMarker->SetColor(FLinearColor(0.85f, 0.5f, 0.03f, 0.5f));
GraphTrack->SetName(TEXT("Main Graph Track"));
IModularFeatures::Get().RegisterModularFeature(Insights::TimingViewExtenderFeatureName, FrameSharedState.Get());
IModularFeatures::Get().RegisterModularFeature(Insights::TimingViewExtenderFeatureName, ThreadTimingSharedState.Get());
IModularFeatures::Get().RegisterModularFeature(Insights::TimingViewExtenderFeatureName, LoadingSharedState.Get());
IModularFeatures::Get().RegisterModularFeature(Insights::TimingViewExtenderFeatureName, FileActivitySharedState.Get());
ExtensionOverlay = SNew(SOverlay).Visibility(EVisibility::SelfHitTestInvisible);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
STimingView::~STimingView()
{
AllTracks.Reset();
TopDockedTracks.Reset();
BottomDockedTracks.Reset();
ScrollableTracks.Reset();
ForegroundTracks.Reset();
for (Insights::ITimingViewExtender* Extender: GetExtenders())
{
Extender->OnEndSession(*this);
}
IModularFeatures::Get().UnregisterModularFeature(Insights::TimingViewExtenderFeatureName, FileActivitySharedState.Get());
IModularFeatures::Get().UnregisterModularFeature(Insights::TimingViewExtenderFeatureName, LoadingSharedState.Get());
IModularFeatures::Get().UnregisterModularFeature(Insights::TimingViewExtenderFeatureName, ThreadTimingSharedState.Get());
IModularFeatures::Get().UnregisterModularFeature(Insights::TimingViewExtenderFeatureName, FrameSharedState.Get());
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::Construct(const FArguments& InArgs)
{
ChildSlot
[SNew(SOverlay)
.Visibility(EVisibility::SelfHitTestInvisible)
+ SOverlay::Slot()
.VAlign(VAlign_Bottom)
.Padding(FMargin(0, 0, 12, 0))
[SAssignNew(HorizontalScrollBar, SScrollBar)
.Orientation(Orient_Horizontal)
.AlwaysShowScrollbar(false)
.Visibility(EVisibility::Visible)
.Thickness(FVector2D(5.0f, 5.0f))
.OnUserScrolled(this, &STimingView::HorizontalScrollBar_OnUserScrolled)]
+ SOverlay::Slot()
.HAlign(HAlign_Right)
.Padding(FMargin(0.0f, 0.0f, 0.0f, 0.0f))
[SAssignNew(VerticalScrollBar, SScrollBar)
.Orientation(Orient_Vertical)
.AlwaysShowScrollbar(false)
.Visibility(EVisibility::Visible)
.Thickness(FVector2D(5.0f, 5.0f))
.OnUserScrolled(this, &STimingView::VerticalScrollBar_OnUserScrolled)]
+ SOverlay::Slot()
.HAlign(HAlign_Left)
.VAlign(VAlign_Top)
.Padding(FMargin(0.0f, 0.0f, 0.0f, 0.0f))
[
// Tracks Filter
SNew(SComboButton)
.ComboButtonStyle(FEditorStyle::Get(), "GenericFilters.ComboButtonStyle")
.ForegroundColor(FLinearColor::White)
.ToolTipText(LOCTEXT("TracksFilterToolTip", "Filter timing tracks."))
.OnGetMenuContent(this, &STimingView::MakeTracksFilterMenu)
.HasDownArrow(true)
.ContentPadding(FMargin(1.0f, 1.0f, 1.0f, 1.0f))
.ButtonContent()
[SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.Padding(0.0f)
.AutoWidth()
[SNew(STextBlock)
.TextStyle(FEditorStyle::Get(), "GenericFilters.TextStyle")
.Font(FEditorStyle::Get().GetFontStyle("FontAwesome.9"))
.Text(FText::FromString(FString(TEXT("\xf0b0"))) /*fa-filter*/)]
+ SHorizontalBox::Slot()
.Padding(FMargin(2.0f, 0.0f, 0.0f, 0.0f))
.AutoWidth()
[SNew(STextBlock)
.TextStyle(FEditorStyle::Get(), "GenericFilters.TextStyle")
.Text(LOCTEXT("TracksFilter", "Tracks"))]]]
+ SOverlay::Slot()
.HAlign(HAlign_Right)
.VAlign(VAlign_Top)
.Padding(FMargin(0.0f, 0.0f, 0.0f, 0.0f))
[SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.Padding(0.0f)
.AutoWidth()
[SNew(SCheckBox)
.Style(FEditorStyle::Get(), "ToggleButtonCheckbox")
.HAlign(HAlign_Center)
.Padding(FMargin(0.0f, 2.0f, 1.0f, 4.0f))
.OnCheckStateChanged(this, &STimingView::AutoScroll_OnCheckStateChanged)
.IsChecked(this, &STimingView::AutoScroll_IsChecked)
.ToolTipText(LOCTEXT("AutoScroll_Tooltip", "Auto Scroll"))
[SNew(STextBlock)
.Text(LOCTEXT("AutoScroll_Button", " >> "))
.TextStyle(FEditorStyle::Get(), TEXT("Profiler.Caption"))]]
+ SHorizontalBox::Slot()
.Padding(0.0f)
.AutoWidth()
[SNew(SComboButton)
.ComboButtonStyle(FEditorStyle::Get(), "GenericFilters.ComboButtonStyle")
.ForegroundColor(FLinearColor::White)
//.ToolTipText(LOCTEXT("AutoScrollOptions_ToolTip", "Auto-scroll options"))
.OnGetMenuContent(this, &STimingView::MakeAutoScrollOptionsMenu)
.HasDownArrow(true)
.ContentPadding(FMargin(0.0f, 0.0f, 0.0f, 0.0f))
.ButtonContent()
[SNew(SBorder)
.Padding(0.0f)]]]
+ SOverlay::Slot()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
.Padding(FMargin(0.0f, 0.0f, 0.0f, 0.0f))
[ExtensionOverlay.ToSharedRef()]];
UpdateHorizontalScrollBar();
UpdateVerticalScrollBar();
BindCommands();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::HideAllDefaultTracks()
{
FrameSharedState->HideAllFrameTracks();
ThreadTimingSharedState->HideAllGpuTracks();
ThreadTimingSharedState->HideAllCpuTracks();
LoadingSharedState->HideAllLoadingTracks();
FileActivitySharedState->HideAllIoTracks();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::Reset(bool bIsFirstReset)
{
if (!bIsFirstReset)
{
for (Insights::ITimingViewExtender* Extender: GetExtenders())
{
Extender->OnEndSession(*this);
}
}
//////////////////////////////////////////////////
Viewport.Reset();
//////////////////////////////////////////////////
for (auto& KV: AllTracks)
{
KV.Value->SetLocation(ETimingTrackLocation::None);
}
AllTracks.Reset();
TopDockedTracks.Reset();
BottomDockedTracks.Reset();
ScrollableTracks.Reset();
ForegroundTracks.Reset();
bScrollableTracksOrderIsDirty = false;
FTimingEventsTrack::bUseDownSampling = true;
//////////////////////////////////////////////////
TimeRulerTrack->Reset();
AddTopDockedTrack(TimeRulerTrack);
TimeRulerTrack->AddTimeMarker(DefaultTimeMarker);
SetTimeMarker(std::numeric_limits<double>::infinity());
#if 0 // test for multiple time markers
TSharedRef<Insights::FTimeMarker> TimeMarkerA = DefaultTimeMarker;
TimeMarkerA->SetName(TEXT("A"));
TimeMarkerA->SetColor(FLinearColor(0.85f, 0.5f, 0.03f, 0.5f));
TSharedRef<Insights::FTimeMarker> TimeMarkerB = MakeShared<Insights::FTimeMarker>();
TimeRulerTrack->AddTimeMarker(TimeMarkerB);
TimeMarkerB->SetName(TEXT("B"));
TimeMarkerB->SetColor(FLinearColor(0.03f, 0.85f, 0.5f, 0.5f));
TSharedRef<Insights::FTimeMarker> TimeMarkerC = MakeShared<Insights::FTimeMarker>();
TimeRulerTrack->AddTimeMarker(TimeMarkerC);
TimeMarkerC->SetName(TEXT("C"));
TimeMarkerC->SetColor(FLinearColor(0.03f, 0.5f, 0.85f, 0.5f));
TimeMarkerA->SetTime(0.0f);
TimeMarkerB->SetTime(1.0f);
TimeMarkerC->SetTime(2.0f);
#endif
MarkersTrack->Reset();
AddTopDockedTrack(MarkersTrack);
GraphTrack->Reset();
GraphTrack->SetOrder(FTimingTrackOrder::First);
constexpr double GraphTrackHeight = 200.0;
GraphTrack->SetHeight(static_cast<float>(GraphTrackHeight));
GraphTrack->GetSharedValueViewport().SetBaselineY(GraphTrackHeight - 1.0);
GraphTrack->GetSharedValueViewport().SetScaleY(GraphTrackHeight / 0.1); // 100ms
GraphTrack->AddDefaultFrameSeries();
GraphTrack->SetVisibilityFlag(false);
AddTopDockedTrack(GraphTrack);
//////////////////////////////////////////////////
ExtensionOverlay->ClearChildren();
//////////////////////////////////////////////////
MousePosition = FVector2D::ZeroVector;
MousePositionOnButtonDown = FVector2D::ZeroVector;
ViewportStartTimeOnButtonDown = 0.0;
ViewportScrollPosYOnButtonDown = 0.0f;
MousePositionOnButtonUp = FVector2D::ZeroVector;
LastScrollPosY = 0.0f;
bIsLMB_Pressed = false;
bIsRMB_Pressed = false;
bIsSpaceBarKeyPressed = false;
bIsDragging = false;
bAutoScroll = false;
bIsAutoScrollFrameAligned = true;
AutoScrollFrameType = TraceFrameType_Game;
AutoScrollViewportOffsetPercent = 0.1; // scrolls forward 10% of viewport's width
AutoScrollMinDelay = 0.3; // [seconds]
LastAutoScrollTime = 0;
bIsPanning = false;
PanningMode = EPanningMode::None;
OverscrollLeft = 0.0f;
OverscrollRight = 0.0f;
OverscrollTop = 0.0f;
OverscrollBottom = 0.0f;
bIsSelecting = false;
SelectionStartTime = 0.0;
SelectionEndTime = 0.0;
if (HoveredTrack.IsValid())
{
HoveredTrack.Reset();
OnHoveredTrackChangedDelegate.Broadcast(HoveredTrack);
}
if (HoveredEvent.IsValid())
{
HoveredEvent.Reset();
OnHoveredEventChangedDelegate.Broadcast(HoveredEvent);
}
if (SelectedTrack.IsValid())
{
SelectedTrack.Reset();
OnSelectedTrackChangedDelegate.Broadcast(SelectedTrack);
}
if (SelectedEvent.IsValid())
{
SelectedEvent.Reset();
OnSelectedEventChangedDelegate.Broadcast(SelectedEvent);
}
if (TimingEventFilter.IsValid())
{
TimingEventFilter.Reset();
}
bPreventThrottling = false;
Tooltip.Reset();
LastSelectionType = ESelectionType::None;
// ThisGeometry
bDrawTopSeparatorLine = false;
bDrawBottomSeparatorLine = false;
//////////////////////////////////////////////////
NumUpdatedEvents = 0;
PreUpdateTracksDurationHistory.Reset();
PreUpdateTracksDurationHistory.AddValue(0);
UpdateTracksDurationHistory.Reset();
UpdateTracksDurationHistory.AddValue(0);
PostUpdateTracksDurationHistory.Reset();
PostUpdateTracksDurationHistory.AddValue(0);
TickDurationHistory.Reset();
TickDurationHistory.AddValue(0);
PreDrawTracksDurationHistory.Reset();
PreDrawTracksDurationHistory.AddValue(0);
DrawTracksDurationHistory.Reset();
DrawTracksDurationHistory.AddValue(0);
PostDrawTracksDurationHistory.Reset();
PostDrawTracksDurationHistory.AddValue(0);
OnPaintDeltaTimeHistory.Reset();
OnPaintDeltaTimeHistory.AddValue(0);
LastOnPaintTime = FPlatformTime::Cycles64();
//////////////////////////////////////////////////
for (Insights::ITimingViewExtender* Extender: GetExtenders())
{
Extender->OnBeginSession(*this);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool STimingView::IsGpuTrackVisible() const
{
return ThreadTimingSharedState && ThreadTimingSharedState->IsGpuTrackVisible();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool STimingView::IsCpuTrackVisible(uint32 InThreadId) const
{
return ThreadTimingSharedState && ThreadTimingSharedState->IsCpuTrackVisible(InThreadId);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime)
{
// SCompoundWidget::Tick(AllottedGeometry, InCurrentTime, InDeltaTime);
FStopwatch TickStopwatch;
TickStopwatch.Start();
ThisGeometry = AllottedGeometry;
bPreventThrottling = false;
constexpr float OverscrollFadeSpeed = 2.0f;
if (OverscrollLeft > 0.0f)
{
OverscrollLeft = FMath::Max(0.0f, OverscrollLeft - InDeltaTime * OverscrollFadeSpeed);
}
if (OverscrollRight > 0.0f)
{
OverscrollRight = FMath::Max(0.0f, OverscrollRight - InDeltaTime * OverscrollFadeSpeed);
}
if (OverscrollTop > 0.0f)
{
OverscrollTop = FMath::Max(0.0f, OverscrollTop - InDeltaTime * OverscrollFadeSpeed);
}
if (OverscrollBottom > 0.0f)
{
OverscrollBottom = FMath::Max(0.0f, OverscrollBottom - InDeltaTime * OverscrollFadeSpeed);
}
const float ViewWidth = AllottedGeometry.GetLocalSize().X;
const float ViewHeight = AllottedGeometry.GetLocalSize().Y;
////////////////////////////////////////////////////////////////////////////////////////////////////
// Update viewport.
Viewport.UpdateSize(ViewWidth, ViewHeight);
if (!bIsPanning && !bAutoScroll)
{
// Elastic snap to horizontal time limits.
if (Viewport.EnforceHorizontalScrollLimits(0.5)) // 0.5 is the interpolation factor
{
UpdateHorizontalScrollBar();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// Check the analysis session time.
TSharedPtr<const Trace::IAnalysisSession> Session = FInsightsManager::Get()->GetSession();
if (Session)
{
double SessionTime = 0.0;
{
Trace::FAnalysisSessionReadScope SessionReadScope(*Session.Get());
SessionTime = Session->GetDurationSeconds();
}
// Check if horizontal scroll area has changed.
if (SessionTime > Viewport.GetMaxValidTime() &&
SessionTime != DBL_MAX &&
SessionTime != std::numeric_limits<double>::infinity())
{
const double PreviousSessionTime = Viewport.GetMaxValidTime();
if ((PreviousSessionTime >= Viewport.GetStartTime() && PreviousSessionTime <= Viewport.GetEndTime()) ||
(SessionTime >= Viewport.GetStartTime() && SessionTime <= Viewport.GetEndTime()))
{
Viewport.AddDirtyFlags(ETimingTrackViewportDirtyFlags::HClippedSessionTimeChanged);
}
// UE_LOG(TimingProfiler, Log, TEXT("Session Duration: %g"), DT);
Viewport.SetMaxValidTime(SessionTime);
UpdateHorizontalScrollBar();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
if (bIsPanning)
{
// Disable auto-scroll if user starts panning manually.
bAutoScroll = false;
}
if (bAutoScroll)
{
const uint64 CurrentTime = FPlatformTime::Cycles64();
if (static_cast<double>(CurrentTime - LastAutoScrollTime) * FPlatformTime::GetSecondsPerCycle64() > AutoScrollMinDelay)
{
const double ViewportDuration = Viewport.GetEndTime() - Viewport.GetStartTime(); // width of the viewport in [seconds]
const double AutoScrollViewportOffsetTime = ViewportDuration * AutoScrollViewportOffsetPercent;
// By default, align the current session time with the offseted right side of the viewport.
double MinStartTime = Viewport.GetMaxValidTime() - ViewportDuration + AutoScrollViewportOffsetTime;
if (bIsAutoScrollFrameAligned)
{
if (Session.IsValid())
{
Trace::FAnalysisSessionReadScope SessionReadScope(*Session.Get());
const Trace::IFrameProvider& FramesProvider = Trace::ReadFrameProvider(*Session.Get());
const uint64 FrameCount = FramesProvider.GetFrameCount(AutoScrollFrameType);
if (FrameCount > 0)
{
// Search the last frame with EndTime <= SessionTime.
uint64 FrameIndex = FrameCount;
while (FrameIndex > 0)
{
const Trace::FFrame* FramePtr = FramesProvider.GetFrame(AutoScrollFrameType, --FrameIndex);
if (FramePtr && FramePtr->EndTime <= Viewport.GetMaxValidTime())
{
// Align the start time of the frame with the right side of the viewport.
MinStartTime = FramePtr->EndTime - ViewportDuration + AutoScrollViewportOffsetTime;
break;
}
}
// Get the frame at the center of the viewport.
Trace::FFrame Frame;
const double ViewportCenter = MinStartTime + ViewportDuration / 2;
if (FramesProvider.GetFrameFromTime(AutoScrollFrameType, ViewportCenter, Frame))
{
if (Frame.EndTime > ViewportCenter)
{
// Align the start time of the frame with the center of the viewport.
MinStartTime = Frame.StartTime - ViewportDuration / 2;
}
}
}
}
}
ScrollAtTime(MinStartTime);
LastAutoScrollTime = CurrentTime;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
if (Session)
{
// Tick plugin extenders.
// Each extender can add/remove tracks and/or change order of tracks.
for (Insights::ITimingViewExtender* Extender: GetExtenders())
{
Extender->Tick(*this, *Session.Get());
}
// Re-sort now if we need to
UpdateScrollableTracksOrder();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// Animate the (vertical) layout transition (i.e. compact mode <-> normal mode).
Viewport.UpdateLayout();
TimeRulerTrack->SetSelection(bIsSelecting, SelectionStartTime, SelectionEndTime);
////////////////////////////////////////////////////////////////////////////////////////////////////
class FTimingTrackUpdateContext: public ITimingTrackUpdateContext
{
public:
explicit FTimingTrackUpdateContext(STimingView* InTimingView, double InCurrentTime, float InDeltaTime): TimingView(InTimingView), CurrentTime(InCurrentTime), DeltaTime(InDeltaTime) {}
virtual const FTimingTrackViewport& GetViewport() const override { return TimingView->GetViewport(); }
virtual const FVector2D& GetMousePosition() const override { return TimingView->GetMousePosition(); }
virtual const TSharedPtr<const ITimingEvent> GetHoveredEvent() const override { return TimingView->GetHoveredEvent(); }
virtual const TSharedPtr<const ITimingEvent> GetSelectedEvent() const override { return TimingView->GetSelectedEvent(); }
virtual const TSharedPtr<ITimingEventFilter> GetEventFilter() const override { return TimingView->GetEventFilter(); }
virtual double GetCurrentTime() const override { return CurrentTime; }
virtual float GetDeltaTime() const override { return DeltaTime; }
public:
STimingView* TimingView;
double CurrentTime;
float DeltaTime;
};
FTimingTrackUpdateContext UpdateContext(this, InCurrentTime, InDeltaTime);
////////////////////////////////////////////////////////////////////////////////////////////////////
// Pre-Update.
// The tracks needs to update their size.
{
FStopwatch PreUpdateTracksStopwatch;
PreUpdateTracksStopwatch.Start();
for (TSharedPtr<FBaseTimingTrack>& TrackPtr: TopDockedTracks)
{
if (TrackPtr->IsVisible())
{
TrackPtr->PreUpdate(UpdateContext);
}
}
for (TSharedPtr<FBaseTimingTrack>& TrackPtr: BottomDockedTracks)
{
if (TrackPtr->IsVisible())
{
TrackPtr->PreUpdate(UpdateContext);
}
}
for (TSharedPtr<FBaseTimingTrack>& TrackPtr: ScrollableTracks)
{
if (TrackPtr->IsVisible())
{
TrackPtr->PreUpdate(UpdateContext);
}
}
for (TSharedPtr<FBaseTimingTrack>& TrackPtr: ForegroundTracks)
{
if (TrackPtr->IsVisible())
{
TrackPtr->PreUpdate(UpdateContext);
}
}
PreUpdateTracksStopwatch.Stop();
PreUpdateTracksDurationHistory.AddValue(PreUpdateTracksStopwatch.AccumulatedTime);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// Update Y postion for the visible top docked tracks.
// Compute the total height of top docked areas.
int32 NumVisibleTopDockedTracks = 0;
float TopOffset = 0.0f;
for (TSharedPtr<FBaseTimingTrack>& TrackPtr: TopDockedTracks)
{
TrackPtr->SetPosY(TopOffset);
if (TrackPtr->IsVisible())
{
TopOffset += TrackPtr->GetHeight();
NumVisibleTopDockedTracks++;
}
}
if (NumVisibleTopDockedTracks > 0)
{
bDrawTopSeparatorLine = true;
TopOffset += 2.0f;
}
else
{
bDrawTopSeparatorLine = false;
}
Viewport.SetTopOffset(TopOffset);
////////////////////////////////////////////////////////////////////////////////////////////////////
// Update Y postion for the visible bottom docked tracks.
// Compute the total height of bottom docked areas.
float BottomOffset = 0.0f;
for (TSharedPtr<FBaseTimingTrack>& TrackPtr: BottomDockedTracks)
{
if (TrackPtr->IsVisible())
{
BottomOffset += TrackPtr->GetHeight();
}
}
float BottomOffsetY = Viewport.GetHeight() - BottomOffset;
int32 NumVisibleBottomDockedTracks = 0;
for (TSharedPtr<FBaseTimingTrack>& TrackPtr: BottomDockedTracks)
{
TrackPtr->SetPosY(BottomOffsetY);
if (TrackPtr->IsVisible())
{
BottomOffsetY += TrackPtr->GetHeight();
NumVisibleBottomDockedTracks++;
}
}
if (NumVisibleBottomDockedTracks > 0)
{
bDrawBottomSeparatorLine = true;
BottomOffset += 2.0f;
}
else
{
bDrawBottomSeparatorLine = false;
}
Viewport.SetBottomOffset(BottomOffset);
////////////////////////////////////////////////////////////////////////////////////////////////////
// Compute the total height of visible scrollable tracks.
float ScrollHeight = 0.0f;
for (TSharedPtr<FBaseTimingTrack>& TrackPtr: ScrollableTracks)
{
if (TrackPtr->IsVisible())
{
ScrollHeight += TrackPtr->GetHeight();
}
}
ScrollHeight += 1.0f; // allow 1 pixel at the bottom (for last horizontal line)
////////////////////////////////////////////////////////////////////////////////////////////////////
// Check if vertical scroll area has changed.
bool bScrollHeightChanged = false;
if (ScrollHeight != Viewport.GetScrollHeight())
{
bScrollHeightChanged = true;
Viewport.SetScrollHeight(ScrollHeight);
UpdateVerticalScrollBar();
}
// Set the VerticalScrollBar padding so it is limited to the scrollable area.
VerticalScrollBar->SetPadding(FMargin(0.0f, TopOffset, 0.0f, FMath::Max(BottomOffset, 12.0f)));
////////////////////////////////////////////////////////////////////////////////////////////////////
const float InitialScrollPosY = Viewport.GetScrollPosY();
TSharedPtr<FBaseTimingTrack> SelectedScrollableTrack;
if (SelectedTrack.IsValid() && SelectedTrack->IsVisible())
{
if (ScrollableTracks.Contains(SelectedTrack))
{
SelectedScrollableTrack = SelectedTrack;
}
}
const float InitialPinnedTrackPosY = SelectedScrollableTrack.IsValid() ? SelectedScrollableTrack->GetPosY() : 0.0f;
// Update the Y position for visible scrollable tracks.
UpdatePositionForScrollableTracks();
// The selected track will be pinned (keeps Y pos fixed unless user scrolls vertically).
if (SelectedScrollableTrack.IsValid())
{
const float ScrollingDY = LastScrollPosY - InitialScrollPosY;
const float PinnedTrackPosY = SelectedScrollableTrack->GetPosY();
const float AdjustmentDY = InitialPinnedTrackPosY - PinnedTrackPosY + ScrollingDY;
if (!FMath::IsNearlyZero(AdjustmentDY, 0.5f))
{
ViewportScrollPosYOnButtonDown -= AdjustmentDY;
ScrollAtPosY(InitialScrollPosY - AdjustmentDY);
UpdatePositionForScrollableTracks();
}
}
// Elastic snap to vertical scroll limits.
if (!bIsPanning)
{
const float DY = Viewport.GetScrollHeight() - Viewport.GetScrollableAreaHeight() + 7.0f; // +7 is to allow some space for the horizontal scrollbar
const float MinY = FMath::Min(DY, 0.0f);
const float MaxY = DY - MinY;
float ScrollPosY = Viewport.GetScrollPosY();
if (ScrollPosY < MinY)
{
if (bScrollHeightChanged || Viewport.IsDirty(ETimingTrackViewportDirtyFlags::VLayoutChanged))
{
ScrollPosY = MinY;
}
else
{
constexpr float U = 0.5f;
ScrollPosY = ScrollPosY * U + (1.0f - U) * MinY;
if (FMath::IsNearlyEqual(ScrollPosY, MinY, 0.5f))
{
ScrollPosY = MinY;
}
}
}
else if (ScrollPosY > MaxY)
{
if (bScrollHeightChanged || Viewport.IsDirty(ETimingTrackViewportDirtyFlags::VLayoutChanged))
{
ScrollPosY = MaxY;
}
else
{
constexpr float U = 0.5f;
ScrollPosY = ScrollPosY * U + (1.0f - U) * MaxY;
if (FMath::IsNearlyEqual(ScrollPosY, MaxY, 0.5f))
{
ScrollPosY = MaxY;
}
}
if (ScrollPosY < MinY)
{
ScrollPosY = MinY;
}
}
if (ScrollPosY != Viewport.GetScrollPosY())
{
ScrollAtPosY(ScrollPosY);
UpdatePositionForScrollableTracks();
}
}
LastScrollPosY = Viewport.GetScrollPosY();
////////////////////////////////////////////////////////////////////////////////////////////////////
// At this point it is assumed all tracks have proper position and size.
// Update.
{
FStopwatch UpdateTracksStopwatch;
UpdateTracksStopwatch.Start();
for (TSharedPtr<FBaseTimingTrack>& TrackPtr: TopDockedTracks)
{
if (TrackPtr->IsVisible())
{
TrackPtr->Update(UpdateContext);
}
}
for (TSharedPtr<FBaseTimingTrack>& TrackPtr: BottomDockedTracks)
{
if (TrackPtr->IsVisible())
{
TrackPtr->Update(UpdateContext);
}
}
for (TSharedPtr<FBaseTimingTrack>& TrackPtr: ScrollableTracks)
{
if (TrackPtr->IsVisible())
{
TrackPtr->Update(UpdateContext);
}
}
for (TSharedPtr<FBaseTimingTrack>& TrackPtr: ForegroundTracks)
{
if (TrackPtr->IsVisible())
{
TrackPtr->Update(UpdateContext);
}
}
UpdateTracksStopwatch.Stop();
UpdateTracksDurationHistory.AddValue(UpdateTracksStopwatch.AccumulatedTime);
}
//////////////////////////////////////////////////
// Post-Update.
{
FStopwatch PostUpdateTracksStopwatch;
PostUpdateTracksStopwatch.Start();
for (TSharedPtr<FBaseTimingTrack>& TrackPtr: TopDockedTracks)
{
if (TrackPtr->IsVisible())
{
TrackPtr->PostUpdate(UpdateContext);
}
}
for (TSharedPtr<FBaseTimingTrack>& TrackPtr: BottomDockedTracks)
{
if (TrackPtr->IsVisible())
{
TrackPtr->PostUpdate(UpdateContext);
}
}
for (TSharedPtr<FBaseTimingTrack>& TrackPtr: ScrollableTracks)
{
if (TrackPtr->IsVisible())
{
TrackPtr->PostUpdate(UpdateContext);
}
}
for (TSharedPtr<FBaseTimingTrack>& TrackPtr: ForegroundTracks)
{
if (TrackPtr->IsVisible())
{
TrackPtr->PostUpdate(UpdateContext);
}
}
PostUpdateTracksStopwatch.Stop();
PostUpdateTracksDurationHistory.AddValue(PostUpdateTracksStopwatch.AccumulatedTime);
}
//////////////////////////////////////////////////
Tooltip.Update();
if (!MousePosition.IsZero())
{
Tooltip.SetPosition(MousePosition, 0.0f, Viewport.GetWidth() - 12.0f, 0.0f, Viewport.GetHeight() - 12.0f); // -12.0f is to avoid overlaping the scrollbars
}
//////////////////////////////////////////////////
// Reset hovered/selected flags for all tracks.
for (TSharedPtr<FBaseTimingTrack>& TrackPtr: ScrollableTracks)
{
if (TrackPtr->IsVisible())
{
TrackPtr->SetHoveredState(false);
TrackPtr->SetSelectedFlag(false);
}
}
// Set the hovered flag for the actual hovered track, if any.
if (HoveredTrack.IsValid())
{
HoveredTrack->SetHoveredState(true);
}
// Set the selected flag for the actual selected track, if any.
if (SelectedTrack.IsValid())
{
SelectedTrack->SetSelectedFlag(true);
}
//////////////////////////////////////////////////
Viewport.ResetDirtyFlags();
TickStopwatch.Stop();
TickDurationHistory.AddValue(TickStopwatch.AccumulatedTime);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::UpdatePositionForScrollableTracks()
{
// Update the Y postion for visible scrollable tracks.
float ScrollableTrackPosY = Viewport.GetTopOffset() - Viewport.GetScrollPosY();
for (TSharedPtr<FBaseTimingTrack>& TrackPtr: ScrollableTracks)
{
TrackPtr->SetPosY(ScrollableTrackPosY); // set pos y also for the hidden tracks
if (TrackPtr->IsVisible())
{
ScrollableTrackPosY += TrackPtr->GetHeight();
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
int32 STimingView::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const
{
FStopwatch Stopwatch;
Stopwatch.Start();
const bool bEnabled = ShouldBeEnabled(bParentEnabled);
const ESlateDrawEffect DrawEffects = bEnabled ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect;
FDrawContext DrawContext(AllottedGeometry, MyCullingRect, InWidgetStyle, DrawEffects, OutDrawElements, LayerId);
#if 0 // Enabling this may further increase UI performance (TODO: profile if this is really needed again).
// Avoids multiple resizes of Slate's draw elements buffers.
OutDrawElements.GetRootDrawLayer().DrawElements.Reserve(50000);
#endif
const TSharedRef<FSlateFontMeasure> FontMeasureService = FSlateApplication::Get().GetRenderer()->GetFontMeasureService();
const float ViewWidth = AllottedGeometry.GetLocalSize().X;
const float ViewHeight = AllottedGeometry.GetLocalSize().Y;
#if 0 // Enabling this may further increase UI performance (TODO: profile if this is really needed again).
// Warm up Slate vertex/index buffers to avoid initial freezes due to multiple resizes of those buffers.
static bool bWarmingUp = false;
if (!bWarmingUp)
{
bWarmingUp = true;
FRandomStream RandomStream(0);
const int32 Count = 1'000'000;
for (int32 Index = 0; Index < Count; ++Index)
{
float X = ViewWidth * RandomStream.GetFraction();
float Y = ViewHeight * RandomStream.GetFraction();
FLinearColor Color(RandomStream.GetFraction(), RandomStream.GetFraction(), RandomStream.GetFraction(), 1.0f);
DrawContext.DrawBox(X, Y, 1.0f, 1.0f, WhiteBrush, Color);
}
LayerId++;
LayerId++;
}
#endif
////////////////////////////////////////////////////////////////////////////////////////////////////
class FTimingTrackDrawContext: public ITimingTrackDrawContext
{
public:
explicit FTimingTrackDrawContext(const STimingView* InTimingView, FDrawContext& InDrawContext, const FTimingViewDrawHelper& InHelper)
: TimingView(InTimingView), DrawContext(InDrawContext), Helper(InHelper)
{}
virtual const FTimingTrackViewport& GetViewport() const override { return TimingView->GetViewport(); }
virtual const FVector2D& GetMousePosition() const override { return TimingView->GetMousePosition(); }
virtual const TSharedPtr<const ITimingEvent> GetHoveredEvent() const override { return TimingView->GetHoveredEvent(); }
virtual const TSharedPtr<const ITimingEvent> GetSelectedEvent() const override { return TimingView->GetSelectedEvent(); }
virtual const TSharedPtr<ITimingEventFilter> GetEventFilter() const override { return TimingView->GetEventFilter(); }
virtual FDrawContext& GetDrawContext() const override { return DrawContext; }
virtual const ITimingViewDrawHelper& GetHelper() const override { return Helper; }
public:
const STimingView* TimingView;
FDrawContext& DrawContext;
const FTimingViewDrawHelper& Helper;
};
FTimingViewDrawHelper Helper(DrawContext, Viewport);
FTimingTrackDrawContext TimingDrawContext(this, DrawContext, Helper);
////////////////////////////////////////////////////////////////////////////////////////////////////
// Draw background.
Helper.DrawBackground();
//////////////////////////////////////////////////
// Pre-Draw
{
FStopwatch PreDrawTracksStopwatch;
PreDrawTracksStopwatch.Start();
for (const TSharedPtr<FBaseTimingTrack>& TrackPtr: ScrollableTracks)
{
if (TrackPtr->IsVisible())
{
TrackPtr->PreDraw(TimingDrawContext);
}
}
for (const TSharedPtr<FBaseTimingTrack>& TrackPtr: TopDockedTracks)
{
if (TrackPtr->IsVisible())
{
TrackPtr->PreDraw(TimingDrawContext);
}
}
for (const TSharedPtr<FBaseTimingTrack>& TrackPtr: BottomDockedTracks)
{
if (TrackPtr->IsVisible())
{
TrackPtr->PreDraw(TimingDrawContext);
}
}
for (const TSharedPtr<FBaseTimingTrack>& TrackPtr: ForegroundTracks)
{
if (TrackPtr->IsVisible())
{
TrackPtr->PreDraw(TimingDrawContext);
}
}
PreDrawTracksStopwatch.Stop();
PreDrawTracksDurationHistory.AddValue(PreDrawTracksStopwatch.AccumulatedTime);
}
//////////////////////////////////////////////////
// Draw
{
FStopwatch DrawTracksStopwatch;
DrawTracksStopwatch.Start();
Helper.BeginDrawTracks();
const FVector2D Position = AllottedGeometry.GetAbsolutePosition();
const float Scale = AllottedGeometry.GetAccumulatedLayoutTransform().GetScale();
// Draw the scrollable tracks.
{
const float TopY = Viewport.GetTopOffset();
const float BottomY = Viewport.GetHeight() - Viewport.GetBottomOffset();
const float L = Position.X;
const float R = Position.X + (Viewport.GetWidth() * Scale);
const float T = Position.Y + (TopY * Scale);
const float B = Position.Y + (BottomY * Scale);
const FSlateClippingZone ClipZone(FVector2D(L, T), FVector2D(R, T), FVector2D(L, B), FVector2D(R, B));
DrawContext.ElementList.PushClip(ClipZone);
for (const TSharedPtr<FBaseTimingTrack>& TrackPtr: ScrollableTracks)
{
if (TrackPtr->IsVisible())
{
if (TrackPtr->GetPosY() + TrackPtr->GetHeight() <= TopY)
{
continue;
}
if (TrackPtr->GetPosY() >= BottomY)
{
break;
}
TrackPtr->Draw(TimingDrawContext);
}
}
DrawContext.ElementList.PopClip();
}
// Draw the top docked tracks.
{
const float L = Position.X;
const float R = Position.X + (Viewport.GetWidth() * Scale);
const float T = Position.Y;
const float B = Position.Y + (Viewport.GetTopOffset() * Scale);
const FSlateClippingZone ClipZone(FVector2D(L, T), FVector2D(R, T), FVector2D(L, B), FVector2D(R, B));
DrawContext.ElementList.PushClip(ClipZone);
for (const TSharedPtr<FBaseTimingTrack>& TrackPtr: TopDockedTracks)
{
if (TrackPtr->IsVisible())
{
TrackPtr->Draw(TimingDrawContext);
}
}
if (bDrawTopSeparatorLine)
{
// Draw separator line between top docked tracks and scrollable tracks.
DrawContext.DrawBox(0.0f, Viewport.GetTopOffset() - 2.0f, Viewport.GetWidth(), 2.0f, WhiteBrush, FLinearColor(0.01f, 0.01f, 0.01f, 1.0f));
++DrawContext.LayerId;
}
DrawContext.ElementList.PopClip();
}
// Draw the bottom docked tracks.
{
const float L = Position.X;
const float R = Position.X + (Viewport.GetWidth() * Scale);
const float T = Position.Y + ((Viewport.GetHeight() - Viewport.GetBottomOffset()) * Scale);
const float B = Position.Y + (Viewport.GetHeight() * Scale);
const FSlateClippingZone ClipZone(FVector2D(L, T), FVector2D(R, T), FVector2D(L, B), FVector2D(R, B));
DrawContext.ElementList.PushClip(ClipZone);
for (const TSharedPtr<FBaseTimingTrack>& TrackPtr: BottomDockedTracks)
{
if (TrackPtr->IsVisible())
{
TrackPtr->Draw(TimingDrawContext);
}
}
if (bDrawBottomSeparatorLine)
{
// Draw separator line between top docked tracks and scrollable tracks.
DrawContext.DrawBox(0.0f, Viewport.GetHeight() - Viewport.GetBottomOffset(), Viewport.GetWidth(), 2.0f, WhiteBrush, FLinearColor(0.01f, 0.01f, 0.01f, 1.0f));
++DrawContext.LayerId;
}
DrawContext.ElementList.PopClip();
}
// Draw the foreground tracks.
for (const TSharedPtr<FBaseTimingTrack>& TrackPtr: ForegroundTracks)
{
if (TrackPtr->IsVisible())
{
TrackPtr->Draw(TimingDrawContext);
}
}
Helper.EndDrawTracks();
DrawTracksStopwatch.Stop();
DrawTracksDurationHistory.AddValue(DrawTracksStopwatch.AccumulatedTime);
}
//////////////////////////////////////////////////
// Draw the selected and/or hovered event.
if (ITimingEvent::AreValidAndEquals(SelectedEvent, HoveredEvent))
{
const TSharedRef<const FBaseTimingTrack> TrackPtr = SelectedEvent->GetTrack();
// Highlight the selected and hovered timing event (if any).
if (TrackPtr->IsVisible())
{
SelectedEvent->GetTrack()->DrawEvent(TimingDrawContext, *SelectedEvent, EDrawEventMode::SelectedAndHovered);
}
}
else
{
// Highlight the selected timing event (if any).
if (SelectedEvent.IsValid())
{
const TSharedRef<const FBaseTimingTrack> TrackPtr = SelectedEvent->GetTrack();
if (TrackPtr->IsVisible())
{
SelectedEvent->GetTrack()->DrawEvent(TimingDrawContext, *SelectedEvent, EDrawEventMode::Selected);
}
}
// Highlight the hovered timing event (if any).
if (HoveredEvent.IsValid())
{
const TSharedRef<const FBaseTimingTrack> TrackPtr = HoveredEvent->GetTrack();
if (TrackPtr->IsVisible())
{
HoveredEvent->GetTrack()->DrawEvent(TimingDrawContext, *HoveredEvent, EDrawEventMode::Hovered);
}
}
}
// Draw the time range selection.
FDrawHelpers::DrawTimeRangeSelection(DrawContext, Viewport, SelectionStartTime, SelectionEndTime, WhiteBrush, MainFont);
//////////////////////////////////////////////////
// Post-Draw
{
FStopwatch PostDrawTracksStopwatch;
PostDrawTracksStopwatch.Start();
for (const TSharedPtr<FBaseTimingTrack>& TrackPtr: ScrollableTracks)
{
if (TrackPtr->IsVisible())
{
TrackPtr->PostDraw(TimingDrawContext);
}
}
for (const TSharedPtr<FBaseTimingTrack>& TrackPtr: TopDockedTracks)
{
if (TrackPtr->IsVisible())
{
TrackPtr->PostDraw(TimingDrawContext);
}
}
for (const TSharedPtr<FBaseTimingTrack>& TrackPtr: BottomDockedTracks)
{
if (TrackPtr->IsVisible())
{
TrackPtr->PostDraw(TimingDrawContext);
}
}
for (const TSharedPtr<FBaseTimingTrack>& TrackPtr: ForegroundTracks)
{
if (TrackPtr->IsVisible())
{
TrackPtr->PostDraw(TimingDrawContext);
}
}
PostDrawTracksStopwatch.Stop();
PostDrawTracksDurationHistory.AddValue(PostDrawTracksStopwatch.AccumulatedTime);
}
//////////////////////////////////////////////////
// Draw tooltip with info about hovered event.
Tooltip.Draw(DrawContext);
// Fill background for the "Tracks" filter combobox.
DrawContext.DrawBox(0.0f, 0.0f, 66.0f, 24.0f, WhiteBrush, FLinearColor(0.05f, 0.05f, 0.05f, 1.0f));
// Fill background for the "Auto-scroll" toggle button.
DrawContext.DrawBox(ViewWidth - 40.0f, 0.0f, 40.0f, 24.0f, WhiteBrush, FLinearColor(0.05f, 0.05f, 0.05f, 1.0f));
//////////////////////////////////////////////////
// Draw the overscroll indication lines.
constexpr float OverscrollLineSize = 1.0f;
constexpr int32 OverscrollLineCount = 8;
if (OverscrollLeft > 0.0f)
{
// TODO: single box with gradient opacity
const float OverscrollLineY = Viewport.GetTopOffset();
const float OverscrollLineH = Viewport.GetScrollableAreaHeight();
for (int32 LineIndex = 0; LineIndex < OverscrollLineCount; ++LineIndex)
{
const float Opacity = OverscrollLeft * static_cast<float>(OverscrollLineCount - LineIndex) / static_cast<float>(OverscrollLineCount);
DrawContext.DrawBox(LineIndex * OverscrollLineSize, OverscrollLineY, OverscrollLineSize, OverscrollLineH, WhiteBrush, FLinearColor(1.0f, 0.1f, 0.1f, Opacity));
}
}
if (OverscrollRight > 0.0f)
{
const float OverscrollLineY = Viewport.GetTopOffset();
const float OverscrollLineH = Viewport.GetScrollableAreaHeight();
for (int32 LineIndex = 0; LineIndex < OverscrollLineCount; ++LineIndex)
{
const float Opacity = OverscrollRight * static_cast<float>(OverscrollLineCount - LineIndex) / static_cast<float>(OverscrollLineCount);
DrawContext.DrawBox(ViewWidth - (1 + LineIndex) * OverscrollLineSize, OverscrollLineY, OverscrollLineSize, OverscrollLineH, WhiteBrush, FLinearColor(1.0f, 0.1f, 0.1f, Opacity));
}
}
if (OverscrollTop > 0.0f)
{
const float OverscrollLineY = Viewport.GetTopOffset();
for (int32 LineIndex = 0; LineIndex < OverscrollLineCount; ++LineIndex)
{
const float Opacity = OverscrollTop * static_cast<float>(OverscrollLineCount - LineIndex) / static_cast<float>(OverscrollLineCount);
DrawContext.DrawBox(0.0f, OverscrollLineY + LineIndex * OverscrollLineSize, ViewWidth, OverscrollLineSize, WhiteBrush, FLinearColor(1.0f, 0.1f, 0.1f, Opacity));
}
}
if (OverscrollBottom > 0.0f)
{
const float OverscrollLineY = ViewHeight - Viewport.GetBottomOffset();
for (int32 LineIndex = 0; LineIndex < OverscrollLineCount; ++LineIndex)
{
const float Opacity = OverscrollBottom * static_cast<float>(OverscrollLineCount - LineIndex) / static_cast<float>(OverscrollLineCount);
DrawContext.DrawBox(0.0f, OverscrollLineY - (1 + LineIndex) * OverscrollLineSize, ViewWidth, OverscrollLineSize, WhiteBrush, FLinearColor(1.0f, 0.1f, 0.1f, Opacity));
}
}
//////////////////////////////////////////////////
const bool bShouldDisplayDebugInfo = FInsightsManager::Get()->IsDebugInfoEnabled();
if (bShouldDisplayDebugInfo)
{
const FSlateFontInfo& SummaryFont = MainFont;
const float MaxFontCharHeight = FontMeasureService->Measure(TEXT("!"), SummaryFont).Y;
const float DbgDY = MaxFontCharHeight;
const float DbgW = 320.0f;
const float DbgH = DbgDY * 9 + 3.0f;
const float DbgX = ViewWidth - DbgW - 20.0f;
float DbgY = Viewport.GetTopOffset() + 10.0f;
DrawContext.LayerId++;
DrawContext.DrawBox(DbgX - 2.0f, DbgY - 2.0f, DbgW, DbgH, WhiteBrush, FLinearColor(1.0f, 1.0f, 1.0f, 0.9f));
DrawContext.LayerId++;
FLinearColor DbgTextColor(0.0f, 0.0f, 0.0f, 0.9f);
//////////////////////////////////////////////////
// Display the "Draw" performance info.
// Time interval since last OnPaint call.
const uint64 CurrentTime = FPlatformTime::Cycles64();
const uint64 OnPaintDeltaTime = CurrentTime - LastOnPaintTime;
LastOnPaintTime = CurrentTime;
OnPaintDeltaTimeHistory.AddValue(OnPaintDeltaTime); // saved for last 32 OnPaint calls
const uint64 AvgOnPaintDeltaTime = OnPaintDeltaTimeHistory.ComputeAverage();
const uint64 AvgOnPaintDeltaTimeMs = FStopwatch::Cycles64ToMilliseconds(AvgOnPaintDeltaTime);
const double AvgOnPaintFps = AvgOnPaintDeltaTimeMs != 0 ? 1.0 / FStopwatch::Cycles64ToSeconds(AvgOnPaintDeltaTime) : 0.0;
const uint64 AvgPreDrawTracksDurationMs = FStopwatch::Cycles64ToMilliseconds(PreDrawTracksDurationHistory.ComputeAverage());
const uint64 AvgDrawTracksDurationMs = FStopwatch::Cycles64ToMilliseconds(DrawTracksDurationHistory.ComputeAverage());
const uint64 AvgPostDrawTracksDurationMs = FStopwatch::Cycles64ToMilliseconds(PostDrawTracksDurationHistory.ComputeAverage());
const uint64 AvgTotalDrawDurationMs = FStopwatch::Cycles64ToMilliseconds(TotalDrawDurationHistory.ComputeAverage());
DrawContext.DrawText(
DbgX, DbgY,
FString::Printf(TEXT("D: %llu ms + %llu ms + %llu ms + %llu ms = %llu ms | + %llu ms = %llu ms (%d fps)"),
AvgPreDrawTracksDurationMs, // pre-draw tracks time
AvgDrawTracksDurationMs, // draw tracks time
AvgPostDrawTracksDurationMs, // post-draw tracks time
AvgTotalDrawDurationMs - AvgPreDrawTracksDurationMs - AvgDrawTracksDurationMs - AvgPostDrawTracksDurationMs, // other draw code
AvgTotalDrawDurationMs,
AvgOnPaintDeltaTimeMs - AvgTotalDrawDurationMs, // other overhead to OnPaint calls
AvgOnPaintDeltaTimeMs, // average time between two OnPaint calls
FMath::RoundToInt(AvgOnPaintFps)), // framerate of OnPaint calls
SummaryFont, DbgTextColor);
DbgY += DbgDY;
//////////////////////////////////////////////////
// Display the "update" performance info.
const uint64 AvgPreUpdateTracksDurationMs = FStopwatch::Cycles64ToMilliseconds(PreUpdateTracksDurationHistory.ComputeAverage());
const uint64 AvgUpdateTracksDurationMs = FStopwatch::Cycles64ToMilliseconds(UpdateTracksDurationHistory.ComputeAverage());
const uint64 AvgPostUpdateTracksDurationMs = FStopwatch::Cycles64ToMilliseconds(PostUpdateTracksDurationHistory.ComputeAverage());
const uint64 AvgTickDurationMs = FStopwatch::Cycles64ToMilliseconds(TickDurationHistory.ComputeAverage());
DrawContext.DrawText(
DbgX, DbgY,
FString::Printf(TEXT("U avg: %llu ms + %llu ms + %llu ms + %llu ms = %llu ms"),
AvgPreUpdateTracksDurationMs,
AvgUpdateTracksDurationMs,
AvgPostUpdateTracksDurationMs,
AvgTickDurationMs - AvgPreUpdateTracksDurationMs - AvgUpdateTracksDurationMs - AvgPostUpdateTracksDurationMs,
AvgTickDurationMs),
SummaryFont, DbgTextColor);
DbgY += DbgDY;
//////////////////////////////////////////////////
// Display timing events stats.
DrawContext.DrawText(
DbgX, DbgY,
FString::Format(TEXT("{0} events : {1} ({2}) boxes, {3} borders, {4} texts"),
{
FText::AsNumber(Helper.GetNumEvents()).ToString(),
FText::AsNumber(Helper.GetNumDrawBoxes()).ToString(),
FText::AsPercent((double)Helper.GetNumDrawBoxes() / (Helper.GetNumDrawBoxes() + Helper.GetNumMergedBoxes())).ToString(),
FText::AsNumber(Helper.GetNumDrawBorders()).ToString(),
FText::AsNumber(Helper.GetNumDrawTexts()).ToString(),
// OutDrawElements.GetRootDrawLayer().GetElementCount(),
}),
SummaryFont, DbgTextColor);
DbgY += DbgDY;
//////////////////////////////////////////////////
// Display time markers stats.
if (MarkersTrack->IsVisible())
{
DrawContext.DrawText(
DbgX, DbgY,
FString::Format(TEXT("{0} logs : {1} boxes, {2} texts"),
{
FText::AsNumber(MarkersTrack->GetNumLogMessages()).ToString(),
FText::AsNumber(MarkersTrack->GetNumBoxes()).ToString(),
FText::AsNumber(MarkersTrack->GetNumTexts()).ToString(),
}),
SummaryFont, DbgTextColor);
DbgY += DbgDY;
}
//////////////////////////////////////////////////
// Display Graph track stats.
if (GraphTrack)
{
DrawContext.DrawText(
DbgX, DbgY,
FString::Format(TEXT("{0} events : {1} points, {2} lines, {3} boxes"),
{
FText::AsNumber(GraphTrack->GetNumAddedEvents()).ToString(),
FText::AsNumber(GraphTrack->GetNumDrawPoints()).ToString(),
FText::AsNumber(GraphTrack->GetNumDrawLines()).ToString(),
FText::AsNumber(GraphTrack->GetNumDrawBoxes()).ToString(),
}),
SummaryFont, DbgTextColor);
DbgY += DbgDY;
}
//////////////////////////////////////////////////
// Display viewport's horizontal info.
DrawContext.DrawText(
DbgX, DbgY,
FString::Printf(TEXT("SX: %g, ST: %g, ET: %s"),
Viewport.GetScaleX(),
Viewport.GetStartTime(),
*TimeUtils::FormatTimeAuto(Viewport.GetMaxValidTime())),
SummaryFont, DbgTextColor);
DbgY += DbgDY;
//////////////////////////////////////////////////
// Display viewport's vertical info.
DrawContext.DrawText(
DbgX, DbgY,
FString::Printf(TEXT("Y: %.2f, H: %g, VH: %g"),
Viewport.GetScrollPosY(),
Viewport.GetScrollHeight(),
Viewport.GetHeight()),
SummaryFont, DbgTextColor);
DbgY += DbgDY;
//////////////////////////////////////////////////
// Display input related debug info.
FString InputStr = FString::Printf(TEXT("(%.0f, %.0f)"), MousePosition.X, MousePosition.Y);
if (bIsSpaceBarKeyPressed)
InputStr += " Space";
if (bIsLMB_Pressed)
InputStr += " LMB";
if (bIsRMB_Pressed)
InputStr += " RMB";
if (bIsPanning)
InputStr += " Panning";
if (bIsSelecting)
InputStr += " Selecting";
if (bIsDragging)
InputStr += " Dragging";
if (TimeRulerTrack->IsScrubbing())
InputStr += " Scrubbing";
DrawContext.DrawText(DbgX, DbgY, InputStr, SummaryFont, DbgTextColor);
DbgY += DbgDY;
}
//////////////////////////////////////////////////
Stopwatch.Stop();
TotalDrawDurationHistory.AddValue(Stopwatch.AccumulatedTime);
return SCompoundWidget::OnPaint(Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled && IsEnabled());
}
////////////////////////////////////////////////////////////////////////////////////////////////////
const TCHAR* STimingView::GetLocationName(ETimingTrackLocation Location)
{
switch (Location)
{
case ETimingTrackLocation::TopDocked: return TEXT("Top Docked");
case ETimingTrackLocation::BottomDocked: return TEXT("Bottom Docked");
case ETimingTrackLocation::Scrollable: return TEXT("Scrollable");
case ETimingTrackLocation::Foreground: return TEXT("Foreground");
default: return nullptr;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::AddTrack(TSharedPtr<FBaseTimingTrack> Track, ETimingTrackLocation Location)
{
check(Track.IsValid());
check(Location == ETimingTrackLocation::Scrollable ||
Location == ETimingTrackLocation::TopDocked ||
Location == ETimingTrackLocation::BottomDocked ||
Location == ETimingTrackLocation::Foreground);
const TCHAR* LocationName = GetLocationName(Location);
TArray<TSharedPtr<FBaseTimingTrack>>& TrackList = const_cast<TArray<TSharedPtr<FBaseTimingTrack>>&>(GetTrackList(Location));
const int32 MaxNumTracks = 1000;
if (TrackList.Num() >= MaxNumTracks)
{
UE_LOG(TimingProfiler, Warning, TEXT("Too many tracks already created (%d tracks)! Ignoring %s track : %s (\"%s\")"),
TrackList.Num(),
LocationName,
*Track->GetTypeName().ToString(),
*Track->GetName());
return;
}
UE_LOG(TimingProfiler, Log, TEXT("New %s Track (%d) : %s (\"%s\")"),
LocationName,
TrackList.Num() + 1,
*Track->GetTypeName().ToString(),
*Track->GetName());
ensure(Track->GetLocation() == ETimingTrackLocation::None);
Track->SetLocation(Location);
check(!AllTracks.Contains(Track->GetId()));
AllTracks.Add(Track->GetId(), Track);
TrackList.Add(Track);
Algo::SortBy(TrackList, &FBaseTimingTrack::GetOrder);
if (Location == ETimingTrackLocation::Scrollable)
{
InvalidateScrollableTracksOrder();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool STimingView::RemoveTrack(TSharedPtr<FBaseTimingTrack> Track)
{
check(Track.IsValid());
if (AllTracks.Remove(Track->GetId()) > 0)
{
const ETimingTrackLocation Location = Track->GetLocation();
check(Location == ETimingTrackLocation::Scrollable ||
Location == ETimingTrackLocation::TopDocked ||
Location == ETimingTrackLocation::BottomDocked ||
Location == ETimingTrackLocation::Foreground);
Track->SetLocation(ETimingTrackLocation::None);
const TCHAR* LocationName = GetLocationName(Location);
TArray<TSharedPtr<FBaseTimingTrack>>& TrackList = const_cast<TArray<TSharedPtr<FBaseTimingTrack>>&>(GetTrackList(Location));
TrackList.Remove(Track);
if (Location == ETimingTrackLocation::Scrollable)
{
InvalidateScrollableTracksOrder();
}
UE_LOG(TimingProfiler, Log, TEXT("Removed %s Track (%d) : %s (\"%s\")"),
LocationName,
TrackList.Num(),
*Track->GetTypeName().ToString(),
*Track->GetName());
return true;
}
return false;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::HideAllScrollableTracks()
{
for (TSharedPtr<FBaseTimingTrack>& Track: ScrollableTracks)
{
Track->Hide();
}
OnTrackVisibilityChanged();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::InvalidateScrollableTracksOrder()
{
bScrollableTracksOrderIsDirty = true;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::UpdateScrollableTracksOrder()
{
if (bScrollableTracksOrderIsDirty)
{
Algo::SortBy(ScrollableTracks, &FBaseTimingTrack::GetOrder);
bScrollableTracksOrderIsDirty = false;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
int32 STimingView::GetFirstScrollableTrackOrder() const
{
return (ScrollableTracks.Num() > 0) ? ScrollableTracks[0]->GetOrder() : 1;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
int32 STimingView::GetLastScrollableTrackOrder() const
{
return (ScrollableTracks.Num() > 0) ? ScrollableTracks.Last()->GetOrder() : -1;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
FReply STimingView::AllowTracksToProcessOnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
for (TSharedPtr<FBaseTimingTrack>& TrackPtr: TopDockedTracks)
{
if (TrackPtr->IsVisible())
{
FReply Reply = TrackPtr->OnMouseButtonDown(MyGeometry, MouseEvent);
if (Reply.IsEventHandled())
{
return Reply;
}
}
}
for (TSharedPtr<FBaseTimingTrack>& TrackPtr: BottomDockedTracks)
{
if (TrackPtr->IsVisible())
{
FReply Reply = TrackPtr->OnMouseButtonDown(MyGeometry, MouseEvent);
if (Reply.IsEventHandled())
{
return Reply;
}
}
}
for (TSharedPtr<FBaseTimingTrack>& TrackPtr: ScrollableTracks)
{
if (TrackPtr->IsVisible())
{
FReply Reply = TrackPtr->OnMouseButtonDown(MyGeometry, MouseEvent);
if (Reply.IsEventHandled())
{
return Reply;
}
}
}
for (TSharedPtr<FBaseTimingTrack>& TrackPtr: ForegroundTracks)
{
if (TrackPtr->IsVisible())
{
FReply Reply = TrackPtr->OnMouseButtonDown(MyGeometry, MouseEvent);
if (Reply.IsEventHandled())
{
return Reply;
}
}
}
return FReply::Unhandled();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
FReply STimingView::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
FReply Reply = AllowTracksToProcessOnMouseButtonDown(MyGeometry, MouseEvent);
if (Reply.IsEventHandled())
{
return Reply;
}
MousePositionOnButtonDown = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());
MousePosition = MousePositionOnButtonDown;
bool bStartPanningSelectingOrScrubbing = false;
bool bStartPanning = false;
bool bStartSelecting = false;
bool bStartScrubbing = false;
if (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton)
{
if (!bIsRMB_Pressed)
{
bIsLMB_Pressed = true;
bStartPanningSelectingOrScrubbing = true;
SelectHoveredTimingTrack();
}
}
else if (MouseEvent.GetEffectingButton() == EKeys::RightMouseButton)
{
if (!bIsLMB_Pressed)
{
bIsRMB_Pressed = true;
bStartPanningSelectingOrScrubbing = true;
SelectHoveredTimingTrack();
}
}
TSharedPtr<Insights::FTimeMarker> ScrubbingTimeMarker = nullptr;
if (bStartPanningSelectingOrScrubbing)
{
bool bIsHoveringTimeRulerTrack = false;
if (TimeRulerTrack->IsVisible())
{
bIsHoveringTimeRulerTrack = MousePositionOnButtonDown.Y >= TimeRulerTrack->GetPosY() &&
MousePositionOnButtonDown.Y < TimeRulerTrack->GetPosY() + TimeRulerTrack->GetHeight();
if (bIsHoveringTimeRulerTrack)
{
if (MouseEvent.GetModifierKeys().IsControlDown())
{
ScrubbingTimeMarker = DefaultTimeMarker;
}
else
{
ScrubbingTimeMarker = TimeRulerTrack->GetTimeMarkerAtPos(MousePositionOnButtonDown, Viewport);
}
}
}
if (bIsSpaceBarKeyPressed)
{
bStartPanning = true;
}
else if (ScrubbingTimeMarker)
{
bStartScrubbing = true;
}
else if (bIsHoveringTimeRulerTrack || (MouseEvent.GetModifierKeys().IsControlDown() && MouseEvent.GetModifierKeys().IsShiftDown()))
{
bStartSelecting = true;
}
else
{
bStartPanning = true;
}
// Capture mouse, so we can drag outside this widget.
Reply = FReply::Handled().CaptureMouse(SharedThis(this));
}
if (bPreventThrottling)
{
Reply.PreventThrottling();
}
if (bStartScrubbing)
{
bIsPanning = false;
bIsDragging = false;
TimeRulerTrack->StartScrubbing(ScrubbingTimeMarker.ToSharedRef());
}
else if (bStartPanning)
{
bIsPanning = true;
bIsDragging = false;
TimeRulerTrack->StopScrubbing();
ViewportStartTimeOnButtonDown = Viewport.GetStartTime();
ViewportScrollPosYOnButtonDown = Viewport.GetScrollPosY();
if (MouseEvent.GetModifierKeys().IsControlDown())
{
// Allow panning only horizontally.
PanningMode = EPanningMode::Horizontal;
}
else if (MouseEvent.GetModifierKeys().IsShiftDown())
{
// Allow panning only vertically.
PanningMode = EPanningMode::Vertical;
}
else
{
// Allow panning both horizontally and vertically.
PanningMode = EPanningMode::HorizontalAndVertical;
}
}
else if (bStartSelecting)
{
bIsSelecting = true;
bIsDragging = false;
TimeRulerTrack->StopScrubbing();
SelectionStartTime = Viewport.SlateUnitsToTime(MousePositionOnButtonDown.X);
SelectionEndTime = SelectionStartTime;
LastSelectionType = ESelectionType::None;
RaiseSelectionChanging();
}
return Reply;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
FReply STimingView::AllowTracksToProcessOnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
for (TSharedPtr<FBaseTimingTrack>& TrackPtr: TopDockedTracks)
{
if (TrackPtr->IsVisible())
{
FReply Reply = TrackPtr->OnMouseButtonUp(MyGeometry, MouseEvent);
if (Reply.IsEventHandled())
{
return Reply;
}
}
}
for (TSharedPtr<FBaseTimingTrack>& TrackPtr: BottomDockedTracks)
{
if (TrackPtr->IsVisible())
{
FReply Reply = TrackPtr->OnMouseButtonUp(MyGeometry, MouseEvent);
if (Reply.IsEventHandled())
{
return Reply;
}
}
}
for (TSharedPtr<FBaseTimingTrack>& TrackPtr: ScrollableTracks)
{
if (TrackPtr->IsVisible())
{
FReply Reply = TrackPtr->OnMouseButtonUp(MyGeometry, MouseEvent);
if (Reply.IsEventHandled())
{
return Reply;
}
}
}
for (TSharedPtr<FBaseTimingTrack>& TrackPtr: ForegroundTracks)
{
if (TrackPtr->IsVisible())
{
FReply Reply = TrackPtr->OnMouseButtonUp(MyGeometry, MouseEvent);
if (Reply.IsEventHandled())
{
return Reply;
}
}
}
return FReply::Unhandled();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
FReply STimingView::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
FReply Reply = AllowTracksToProcessOnMouseButtonUp(MyGeometry, MouseEvent);
if (Reply.IsEventHandled())
{
return Reply;
}
MousePositionOnButtonUp = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());
MousePosition = MousePositionOnButtonUp;
const bool bIsValidForMouseClick = MousePositionOnButtonUp.Equals(MousePositionOnButtonDown, 2.0f);
if (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton)
{
if (bIsLMB_Pressed)
{
if (bIsPanning)
{
PanningMode = EPanningMode::None;
bIsPanning = false;
}
else if (bIsSelecting)
{
RaiseSelectionChanged();
bIsSelecting = false;
}
else if (TimeRulerTrack->IsScrubbing())
{
RaiseTimeMarkerChanged();
TimeRulerTrack->StopScrubbing();
}
if (bIsValidForMouseClick)
{
// Select the hovered timing event (if any).
UpdateHoveredTimingEvent(MousePositionOnButtonUp.X, MousePositionOnButtonUp.Y);
SelectHoveredTimingTrack();
SelectHoveredTimingEvent();
// When clicking on an empty space...
if (!SelectedEvent.IsValid())
{
// ...reset selection.
SelectionEndTime = SelectionStartTime = 0.0;
LastSelectionType = ESelectionType::None;
RaiseSelectionChanged();
}
}
bIsDragging = false;
// Release mouse as we no longer drag.
Reply = FReply::Handled().ReleaseMouseCapture();
bIsLMB_Pressed = false;
}
}
else if (MouseEvent.GetEffectingButton() == EKeys::RightMouseButton)
{
if (bIsRMB_Pressed)
{
if (bIsPanning)
{
PanningMode = EPanningMode::None;
bIsPanning = false;
}
else if (bIsSelecting)
{
RaiseSelectionChanged();
bIsSelecting = false;
}
else if (TimeRulerTrack->IsScrubbing())
{
RaiseTimeMarkerChanged();
TimeRulerTrack->StopScrubbing();
}
if (bIsValidForMouseClick)
{
SelectHoveredTimingTrack();
ShowContextMenu(MouseEvent);
}
bIsDragging = false;
// Release mouse as we no longer drag.
Reply = FReply::Handled().ReleaseMouseCapture();
bIsRMB_Pressed = false;
}
}
return Reply;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
FReply STimingView::AllowTracksToProcessOnMouseButtonDoubleClick(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
for (TSharedPtr<FBaseTimingTrack>& TrackPtr: TopDockedTracks)
{
if (TrackPtr->IsVisible())
{
FReply Reply = TrackPtr->OnMouseButtonDoubleClick(MyGeometry, MouseEvent);
if (Reply.IsEventHandled())
{
return Reply;
}
}
}
for (TSharedPtr<FBaseTimingTrack>& TrackPtr: BottomDockedTracks)
{
if (TrackPtr->IsVisible())
{
FReply Reply = TrackPtr->OnMouseButtonDoubleClick(MyGeometry, MouseEvent);
if (Reply.IsEventHandled())
{
return Reply;
}
}
}
for (TSharedPtr<FBaseTimingTrack>& TrackPtr: ScrollableTracks)
{
if (TrackPtr->IsVisible())
{
FReply Reply = TrackPtr->OnMouseButtonDoubleClick(MyGeometry, MouseEvent);
if (Reply.IsEventHandled())
{
return Reply;
}
}
}
for (TSharedPtr<FBaseTimingTrack>& TrackPtr: ForegroundTracks)
{
if (TrackPtr->IsVisible())
{
FReply Reply = TrackPtr->OnMouseButtonDoubleClick(MyGeometry, MouseEvent);
if (Reply.IsEventHandled())
{
return Reply;
}
}
}
return FReply::Unhandled();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
FReply STimingView::OnMouseButtonDoubleClick(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
FReply Reply = AllowTracksToProcessOnMouseButtonDoubleClick(MyGeometry, MouseEvent);
if (Reply.IsEventHandled())
{
return Reply;
}
if (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton)
{
if (HoveredEvent.IsValid())
{
if (MouseEvent.GetModifierKeys().IsControlDown())
{
const double EndTime = Viewport.RestrictEndTime(HoveredEvent->GetEndTime());
SelectTimeInterval(HoveredEvent->GetStartTime(), EndTime - HoveredEvent->GetStartTime());
}
else
{
SetEventFilter(HoveredEvent->GetTrack()->GetFilterByEvent(HoveredEvent));
}
}
else
{
if (TimingEventFilter.IsValid())
{
TimingEventFilter.Reset();
Viewport.AddDirtyFlags(ETimingTrackViewportDirtyFlags::HInvalidated);
}
}
Reply = FReply::Handled();
}
return Reply;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
FReply STimingView::OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
FReply Reply = FReply::Unhandled();
MousePosition = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());
if (!MouseEvent.GetCursorDelta().IsZero())
{
if (bIsPanning)
{
if (HasMouseCapture())
{
bIsDragging = true;
if ((int32)PanningMode & (int32)EPanningMode::Horizontal)
{
const double StartTime = ViewportStartTimeOnButtonDown + static_cast<double>(MousePositionOnButtonDown.X - MousePosition.X) / Viewport.GetScaleX();
ScrollAtTime(StartTime);
}
if ((int32)PanningMode & (int32)EPanningMode::Vertical)
{
const float ScrollPosY = ViewportScrollPosYOnButtonDown + (MousePositionOnButtonDown.Y - MousePosition.Y);
ScrollAtPosY(ScrollPosY);
}
}
}
else if (bIsSelecting)
{
if (HasMouseCapture())
{
bIsDragging = true;
SelectionStartTime = Viewport.SlateUnitsToTime(MousePositionOnButtonDown.X);
SelectionEndTime = Viewport.SlateUnitsToTime(MousePosition.X);
if (SelectionStartTime > SelectionEndTime)
{
double Temp = SelectionStartTime;
SelectionStartTime = SelectionEndTime;
SelectionEndTime = Temp;
}
LastSelectionType = ESelectionType::TimeRange;
RaiseSelectionChanging();
}
}
else if (TimeRulerTrack->IsScrubbing())
{
if (HasMouseCapture())
{
bIsDragging = true;
TSharedRef<Insights::FTimeMarker> ScrubbingTimeMarker = TimeRulerTrack->GetScrubbingTimeMarker();
ScrubbingTimeMarker->SetTime(Viewport.SlateUnitsToTime(MousePosition.X));
RaiseTimeMarkerChanging();
}
}
else
{
UpdateHoveredTimingEvent(MousePosition.X, MousePosition.Y);
}
Reply = FReply::Handled();
}
return Reply;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::OnMouseLeave(const FPointerEvent& MouseEvent)
{
if (!HasMouseCapture())
{
// No longer dragging (unless we have mouse capture).
bIsDragging = false;
bIsPanning = false;
bIsSelecting = false;
bIsLMB_Pressed = false;
bIsRMB_Pressed = false;
MousePosition = FVector2D::ZeroVector;
if (HoveredTrack.IsValid())
{
HoveredTrack.Reset();
OnHoveredTrackChangedDelegate.Broadcast(HoveredTrack);
}
if (HoveredEvent.IsValid())
{
HoveredEvent.Reset();
OnHoveredEventChangedDelegate.Broadcast(HoveredEvent);
}
Tooltip.SetDesiredOpacity(0.0f);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
FReply STimingView::OnMouseWheel(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
if (MouseEvent.GetModifierKeys().IsShiftDown())
{
// MousePosition = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());
if (GraphTrack->IsVisible() &&
MousePosition.Y >= GraphTrack->GetPosY() &&
MousePosition.Y < GraphTrack->GetPosY() + GraphTrack->GetHeight())
{
// Zoom in/out vertically.
const double Delta = MouseEvent.GetWheelDelta();
constexpr double ZoomStep = 0.25; // as percent
constexpr double MinScaleY = 0.0001;
constexpr double MaxScaleY = 1.0e10;
double ScaleY = GraphTrack->GetSharedValueViewport().GetScaleY();
if (Delta > 0)
{
ScaleY *= FMath::Pow(1.0 + ZoomStep, Delta);
if (ScaleY > MaxScaleY)
{
ScaleY = MaxScaleY;
}
}
else
{
ScaleY *= FMath::Pow(1.0 / (1.0 + ZoomStep), -Delta);
if (ScaleY < MinScaleY)
{
ScaleY = MinScaleY;
}
}
GraphTrack->GetSharedValueViewport().SetScaleY(ScaleY);
for (const TSharedPtr<FGraphSeries>& Series: GraphTrack->GetSeries())
{
if (Series->IsUsingSharedViewport())
{
Series->SetScaleY(ScaleY);
Series->SetDirtyFlag();
}
}
}
else
{
// Scroll vertically.
constexpr float ScrollSpeedY = 16.0f * 3;
const float NewScrollPosY = Viewport.GetScrollPosY() - ScrollSpeedY * MouseEvent.GetWheelDelta();
ScrollAtPosY(EnforceVerticalScrollLimits(NewScrollPosY));
}
}
else if (MouseEvent.GetModifierKeys().IsControlDown())
{
// Scroll horizontally.
const double ScrollSpeedX = Viewport.GetDurationForViewportDX(16.0 * 3);
const double NewStartTime = Viewport.GetStartTime() - ScrollSpeedX * MouseEvent.GetWheelDelta();
ScrollAtTime(EnforceHorizontalScrollLimits(NewStartTime));
}
else
{
// Zoom in/out horizontally.
const double Delta = MouseEvent.GetWheelDelta();
// MousePosition = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());
if (Viewport.RelativeZoomWithFixedX(Delta, MousePosition.X))
{
UpdateHorizontalScrollBar();
}
}
return FReply::Handled();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::OnDragEnter(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent)
{
SCompoundWidget::OnDragEnter(MyGeometry, DragDropEvent);
// TSharedPtr<FStatIDDragDropOp> Operation = DragDropEvent.GetOperationAs<FStatIDDragDropOp>();
// if (Operation.IsValid())
//{
// Operation->ShowOK();
// }
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::OnDragLeave(const FDragDropEvent& DragDropEvent)
{
SCompoundWidget::OnDragLeave(DragDropEvent);
// TSharedPtr<FStatIDDragDropOp> Operation = DragDropEvent.GetOperationAs<FStatIDDragDropOp>();
// if (Operation.IsValid())
//{
// Operation->ShowError();
// }
}
////////////////////////////////////////////////////////////////////////////////////////////////////
FReply STimingView::OnDragOver(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent)
{
return SCompoundWidget::OnDragOver(MyGeometry, DragDropEvent);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
FReply STimingView::OnDrop(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent)
{
// TSharedPtr<FStatIDDragDropOp> Operation = DragDropEvent.GetOperationAs<FStatIDDragDropOp>();
// if (Operation.IsValid())
//{
// return FReply::Handled();
// }
return SCompoundWidget::OnDrop(MyGeometry, DragDropEvent);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
FCursorReply STimingView::OnCursorQuery(const FGeometry& MyGeometry, const FPointerEvent& CursorEvent) const
{
if (bIsPanning)
{
if (bIsDragging)
{
// return FCursorReply::Cursor(EMouseCursor::GrabHandClosed);
return FCursorReply::Cursor(EMouseCursor::GrabHand);
}
}
else if (bIsSelecting)
{
if (bIsDragging)
{
return FCursorReply::Cursor(EMouseCursor::ResizeLeftRight);
}
}
else if (bIsSpaceBarKeyPressed)
{
return FCursorReply::Cursor(EMouseCursor::GrabHand);
}
return FCursorReply::Unhandled();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
FReply STimingView::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent)
{
if (InKeyEvent.GetKey() == EKeys::B)
{
// Toggle Bookmarks.
if (MarkersTrack->IsVisible())
{
if (!MarkersTrack->IsBookmarksTrack())
{
SetDrawOnlyBookmarks(true);
}
else
{
SetTimeMarkersVisible(false);
}
}
else
{
SetTimeMarkersVisible(true);
SetDrawOnlyBookmarks(true);
}
return FReply::Handled();
}
else if (InKeyEvent.GetKey() == EKeys::M)
{
// Toggle Time Markers.
if (MarkersTrack->IsVisible())
{
if (MarkersTrack->IsBookmarksTrack())
{
SetDrawOnlyBookmarks(false);
}
else
{
SetTimeMarkersVisible(false);
}
}
else
{
SetTimeMarkersVisible(true);
SetDrawOnlyBookmarks(false);
}
return FReply::Handled();
}
else if (InKeyEvent.GetKey() == EKeys::F)
{
FrameSelection();
return FReply::Handled();
}
else if (InKeyEvent.GetKey() == EKeys::C)
{
if (InKeyEvent.GetModifierKeys().IsControlDown())
{
if (SelectedEvent.IsValid())
{
SelectedEvent->GetTrack()->OnClipboardCopyEvent(*SelectedEvent);
}
}
else
{
Viewport.SwitchLayoutCompactMode();
return FReply::Handled();
}
}
else if (InKeyEvent.GetKey() == EKeys::V)
{
ToggleAutoHideEmptyTracks();
return FReply::Handled();
}
else if (InKeyEvent.GetKey() == EKeys::Equals ||
InKeyEvent.GetKey() == EKeys::Add)
{
// Zoom In
const double ScaleX = Viewport.GetScaleX() * 1.25;
if (Viewport.ZoomWithFixedX(ScaleX, Viewport.GetWidth() / 2))
{
UpdateHorizontalScrollBar();
}
return FReply::Handled();
}
else if (InKeyEvent.GetKey() == EKeys::Hyphen ||
InKeyEvent.GetKey() == EKeys::Subtract)
{
// Zoom Out
const double ScaleX = Viewport.GetScaleX() / 1.25;
if (Viewport.ZoomWithFixedX(ScaleX, Viewport.GetWidth() / 2))
{
UpdateHorizontalScrollBar();
}
return FReply::Handled();
}
else if (InKeyEvent.GetKey() == EKeys::Left)
{
if (InKeyEvent.GetModifierKeys().IsControlDown())
{
// Scroll Left
const double NewStartTime = Viewport.GetStartTime() - Viewport.GetDuration() * 0.05;
ScrollAtTime(EnforceHorizontalScrollLimits(NewStartTime));
}
else
{
SelectLeftTimingEvent();
}
return FReply::Handled();
}
else if (InKeyEvent.GetKey() == EKeys::Right)
{
if (InKeyEvent.GetModifierKeys().IsControlDown())
{
// Scroll Right
const double NewStartTime = Viewport.GetStartTime() + Viewport.GetDuration() * 0.05;
ScrollAtTime(EnforceHorizontalScrollLimits(NewStartTime));
}
else
{
SelectRightTimingEvent();
}
return FReply::Handled();
}
else if (InKeyEvent.GetKey() == EKeys::Up)
{
if (InKeyEvent.GetModifierKeys().IsControlDown())
{
// Scroll Up
const float NewScrollPosY = Viewport.GetScrollPosY() - 16.0f * 3;
ScrollAtPosY(EnforceVerticalScrollLimits(NewScrollPosY));
}
else
{
SelectUpTimingEvent();
}
return FReply::Handled();
}
else if (InKeyEvent.GetKey() == EKeys::Down)
{
if (InKeyEvent.GetModifierKeys().IsControlDown())
{
// Scroll Down
const float NewScrollPosY = Viewport.GetScrollPosY() + 16.0f * 3;
ScrollAtPosY(EnforceVerticalScrollLimits(NewScrollPosY));
}
else
{
SelectDownTimingEvent();
}
return FReply::Handled();
}
else if (InKeyEvent.GetKey() == EKeys::Enter)
{
if (SelectedEvent.IsValid())
{
const double Duration = Viewport.RestrictDuration(SelectedEvent->GetStartTime(), SelectedEvent->GetEndTime());
SelectTimeInterval(SelectedEvent->GetStartTime(), Duration);
}
return FReply::Handled();
}
else if (InKeyEvent.GetKey() == EKeys::SpaceBar)
{
bIsSpaceBarKeyPressed = true;
FSlateApplication::Get().QueryCursor();
return FReply::Handled();
}
else if (InKeyEvent.GetKey() == EKeys::D) // debug: toggles down-sampling on/off
{
FTimingEventsTrack::bUseDownSampling = !FTimingEventsTrack::bUseDownSampling;
Viewport.AddDirtyFlags(ETimingTrackViewportDirtyFlags::HInvalidated);
return FReply::Handled();
}
else if (InKeyEvent.GetKey() == EKeys::G)
{
ShowHideGraphTrack_Execute();
return FReply::Handled();
}
else if (InKeyEvent.GetKey() == EKeys::R)
{
FrameSharedState->ShowHideAllFrameTracks();
return FReply::Handled();
}
else if (InKeyEvent.GetKey() == EKeys::Y)
{
ThreadTimingSharedState->ShowHideAllGpuTracks();
return FReply::Handled();
}
else if (InKeyEvent.GetKey() == EKeys::U)
{
ThreadTimingSharedState->ShowHideAllCpuTracks();
return FReply::Handled();
}
else if (InKeyEvent.GetKey() == EKeys::L)
{
LoadingSharedState->ShowHideAllLoadingTracks();
return FReply::Handled();
}
else if (InKeyEvent.GetKey() == EKeys::I)
{
FileActivitySharedState->ShowHideAllIoTracks();
return FReply::Handled();
}
else if (InKeyEvent.GetKey() == EKeys::O)
{
FileActivitySharedState->ToggleBackgroundEvents();
return FReply::Handled();
}
else if (InKeyEvent.GetKey() == EKeys::One)
{
LoadingSharedState->SetColorSchema(0);
return FReply::Handled();
}
else if (InKeyEvent.GetKey() == EKeys::Two)
{
LoadingSharedState->SetColorSchema(1);
return FReply::Handled();
}
else if (InKeyEvent.GetKey() == EKeys::Three)
{
LoadingSharedState->SetColorSchema(2);
return FReply::Handled();
}
else if (InKeyEvent.GetKey() == EKeys::Four)
{
LoadingSharedState->SetColorSchema(3);
return FReply::Handled();
}
#if ACTIVATE_BENCHMARK
else if (InKeyEvent.GetKey() == EKeys::Z)
{
FTimingProfilerTests::FCheckValues CheckValues;
FTimingProfilerTests::RunEnumerateBenchmark(FTimingProfilerTests::FEnumerateTestParams(), CheckValues);
return FReply::Handled();
}
#endif
return SCompoundWidget::OnKeyDown(MyGeometry, InKeyEvent);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
FReply STimingView::OnKeyUp(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent)
{
if (InKeyEvent.GetKey() == EKeys::SpaceBar)
{
bIsSpaceBarKeyPressed = false;
FSlateApplication::Get().QueryCursor();
return FReply::Handled();
}
return SCompoundWidget::OnKeyUp(MyGeometry, InKeyEvent);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::ShowContextMenu(const FPointerEvent& MouseEvent)
{
// TSharedPtr<FUICommandList> ProfilerCommandList = FTimingProfilerManager::Get()->GetCommandList();
// const FTimingProfilerCommands& ProfilerCommands = FTimingProfilerManager::GetCommands();
// const FTimingViewCommands& Commands = FTimingViewCommands::Get();
const bool bShouldCloseWindowAfterMenuSelection = true;
// FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, ProfilerCommandList);
FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, NULL);
if (HoveredTrack.IsValid())
{
MenuBuilder.BeginSection(TEXT("Track"), FText::FromString(HoveredTrack->GetName()));
CreateTrackLocationMenu(MenuBuilder, HoveredTrack.ToSharedRef());
MenuBuilder.EndSection();
if (SelectionStartTime != SelectionEndTime || SelectedEvent.IsValid())
{
// Add export menu options if there's a selected event
MenuBuilder.BeginSection(TEXT("EventExport"), LOCTEXT("ContextMenu_EventExportSection", "Export Event"));
{
MenuBuilder.AddMenuEntry(
LOCTEXT("ContextMenu_ExportEvent_AllTrack", "Export to JSON (All Track)"),
LOCTEXT("ContextMenu_ExportEvent_AllTrack_Desc", "Export this event and its children from all track to JSON file."),
FSlateIcon(),
FUIAction(FExecuteAction::CreateSP(this, &STimingView::ExportSelectedEventToJSON)),
NAME_None, EUserInterfaceActionType::Button);
MenuBuilder.AddMenuEntry(
LOCTEXT("ContextMenu_ExportEvent_CurrentTrack", "Export to JSON (Current Track)"),
LOCTEXT("ContextMenu_ExportEvent_CurrentTrack_Desc", "Export this event and its children from current track to JSON file."),
FSlateIcon(),
FUIAction(FExecuteAction::CreateSP(this, &STimingView::ExportSelectedEventToJSON_CurrentTrack)),
NAME_None, EUserInterfaceActionType::Button);
}
MenuBuilder.EndSection();
}
HoveredTrack->BuildContextMenu(MenuBuilder);
}
else
{
MenuBuilder.BeginSection(TEXT("Empty"));
{
MenuBuilder.AddMenuEntry(
LOCTEXT("ContextMenu_NA", "N/A"),
LOCTEXT("ContextMenu_NA_Desc", "No actions available."),
FSlateIcon(),
FUIAction(FExecuteAction(), FCanExecuteAction::CreateLambda([]()
{
return false;
})),
NAME_None, EUserInterfaceActionType::Button);
}
MenuBuilder.EndSection();
}
// MenuBuilder.BeginSection(TEXT("Misc"), LOCTEXT("Miscellaneous", "Miscellaneous"));
//{
// MenuBuilder.AddMenuEntry(Commands.ShowAllGpuTracks);
// MenuBuilder.AddMenuEntry(Commands.ShowAllCpuTracks);
// MenuBuilder.AddMenuEntry(ProfilerCommands.ToggleTimersViewVisibility);
// MenuBuilder.AddMenuEntry(ProfilerCommands.ToggleStatsCountersViewVisibility);
// }
// MenuBuilder.EndSection();
TSharedRef<SWidget> MenuWidget = MenuBuilder.MakeWidget();
FWidgetPath EventPath = MouseEvent.GetEventPath() != nullptr ? *MouseEvent.GetEventPath() : FWidgetPath();
const FVector2D ScreenSpacePosition = MouseEvent.GetScreenSpacePosition();
FSlateApplication::Get().PushMenu(SharedThis(this), EventPath, MenuWidget, ScreenSpacePosition, FPopupTransitionEffect::ContextMenu);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
FString STimingView::GetSelectedEventName() const
{
FString EventName;
if (SelectedEvent.IsValid() && SelectedEvent->Is<FThreadTrackEvent>())
{
const FThreadTrackEvent& ThreadEvent = SelectedEvent->As<FThreadTrackEvent>();
// Get the analysis session
TSharedPtr<const Trace::IAnalysisSession> Session = FInsightsManager::Get()->GetSession();
if (Session.IsValid())
{
Trace::FAnalysisSessionReadScope SessionReadScope(*Session.Get());
const Trace::ITimingProfilerProvider* TimingProfilerProvider = Trace::ReadTimingProfilerProvider(*Session.Get());
if (TimingProfilerProvider)
{
const Trace::ITimingProfilerTimerReader* TimerReader = nullptr;
TimingProfilerProvider->ReadTimers([&TimerReader](const Trace::ITimingProfilerTimerReader& Out)
{
TimerReader = &Out;
});
if (TimerReader)
{
const Trace::FTimingProfilerTimer* Timer = TimerReader->GetTimer(ThreadEvent.GetTimerIndex());
if (Timer != nullptr && Timer->Name != nullptr)
{
EventName = Timer->Name;
}
}
}
}
}
if (!EventName.IsEmpty())
{
FString SanitizedFileName = EventName;
SanitizedFileName = FPaths::MakeValidFileName(SanitizedFileName);
SanitizedFileName.ReplaceInline(TEXT(" "), TEXT("_"));
return SanitizedFileName;
}
EventName = FString::Printf(TEXT("Timeline_%.3f-%.3f"), SelectionStartTime, SelectionEndTime);
return EventName;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::ExportSelectedEventToJSON()
{
// Get the analysis session
TSharedPtr<const Trace::IAnalysisSession> Session = FInsightsManager::Get()->GetSession();
if (!Session.IsValid())
{
UE_LOG(LogTemp, Error, TEXT("No analysis session available for export."));
return;
}
TraceTimer::FExportFilter ExportFilter(TEXT("ExportSelectedEventToJSON"));
// Get the selected event's time range
ExportFilter.StartTime = SelectedEvent.IsValid() ? SelectedEvent->GetStartTime() : SelectionStartTime;
ExportFilter.EndTime = SelectedEvent.IsValid() ? SelectedEvent->GetEndTime() : SelectionEndTime;
// Get the selected event's name
FString SanitizedEventName = GetSelectedEventName();
// Prompt user for save location
TArray<FString> OutFilenames;
bool bFileSaved = false;
if (IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get())
{
FString DefaultFileName = FString::Printf(TEXT("Export_%s_AllTrack.json"), *SanitizedEventName);
bFileSaved = DesktopPlatform->SaveFileDialog(
FSlateApplication::Get().FindBestParentWindowHandleForDialogs(nullptr),
TEXT("Export Event to JSON"),
FPaths::ProjectSavedDir(),
DefaultFileName,
TEXT("JSON Files (*.json)|*.json"),
0, // No flags
OutFilenames);
}
if (bFileSaved && OutFilenames.Num() > 0)
{
const FString& OutputFilename = OutFilenames[0];
// Use FMinimalTimerExporter to export events in the selected time range (all threads)
if (FMinimalTimerExporter::ExportTimingEventsToJSON(*Session, OutputFilename, ExportFilter))
{
UE_LOG(LogTemp, Display, TEXT("Successfully exported event to %s"), *OutputFilename);
}
else
{
UE_LOG(LogTemp, Error, TEXT("Failed to export event to %s"), *OutputFilename);
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::ExportSelectedEventToJSON_CurrentTrack()
{
// Get the analysis session
TSharedPtr<const Trace::IAnalysisSession> Session = FInsightsManager::Get()->GetSession();
if (!Session.IsValid())
{
UE_LOG(LogTemp, Error, TEXT("No analysis session available for export."));
return;
}
TraceTimer::FExportFilter ExportFilter(TEXT("ExportSelectedEventToJSON_CurrentTrack"));
// Get the selected event's time range
ExportFilter.StartTime = SelectedEvent.IsValid() ? SelectedEvent->GetStartTime() : SelectionStartTime;
ExportFilter.EndTime = SelectedEvent.IsValid() ? SelectedEvent->GetEndTime() : SelectionEndTime;
FString SanitizedEventName = GetSelectedEventName();
// Prompt user for save location
TArray<FString> OutFilenames;
bool bFileSaved = false;
if (IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get())
{
FString DefaultFileName = FString::Printf(TEXT("Export_%s_CurrentTrack.json"), *SanitizedEventName);
bFileSaved = DesktopPlatform->SaveFileDialog(
FSlateApplication::Get().FindBestParentWindowHandleForDialogs(nullptr),
TEXT("Export Event to JSON (Current Track)"),
FPaths::ProjectSavedDir(),
DefaultFileName,
TEXT("JSON Files (*.json)|*.json"),
0, // No flags
OutFilenames);
}
if (bFileSaved && OutFilenames.Num() > 0)
{
const FString& OutputFilename = OutFilenames[0];
// Get the current track ID from the selected event's track
FBaseTimingTrack* TrackPtr = SelectedTrack.IsValid() ? SelectedTrack.Get() : HoveredTrack.Get();
if (const FThreadTimingTrack* ThreadTrack = static_cast<const FThreadTimingTrack*>(TrackPtr))
{
ExportFilter.TrackIndex = ThreadTrack->GetTimelineIndex();
}
// Use FMinimalTimerExporter to export events in the selected time range (all threads)
if (FMinimalTimerExporter::ExportTimingEventsToJSON(*Session, OutputFilename, ExportFilter))
{
UE_LOG(LogTemp, Display, TEXT("Successfully exported event to %s"), *OutputFilename);
}
else
{
UE_LOG(LogTemp, Error, TEXT("Failed to export event to %s"), *OutputFilename);
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::CreateTrackLocationMenu(FMenuBuilder& MenuBuilder, TSharedRef<FBaseTimingTrack> Track)
{
if (EnumHasAnyFlags(Track->GetValidLocations(), ETimingTrackLocation::TopDocked))
{
if (Track->GetLocation() == ETimingTrackLocation::TopDocked)
{
MenuBuilder.AddMenuEntry(
LOCTEXT("ContextMenu_LocationTopDocked", "Location: Top Docked Tracks"),
LOCTEXT("ContextMenu_LocationTopDocked_Desc", "This track is in the list of top docked tracks."),
FSlateIcon(),
FUIAction(FExecuteAction(), FCanExecuteAction::CreateLambda([]
{
return false;
})),
NAME_None, EUserInterfaceActionType::Button);
}
else
{
MenuBuilder.AddMenuEntry(
LOCTEXT("ContextMenu_DockToTop", "Dock To Top"),
LOCTEXT("ContextMenu_DockToTop_Desc", "Dock this track to the top."),
FSlateIcon(),
FUIAction(FExecuteAction::CreateSP(this, &STimingView::ChangeTrackLocation, Track, ETimingTrackLocation::TopDocked),
FCanExecuteAction::CreateSP(this, &STimingView::CanChangeTrackLocation, Track, ETimingTrackLocation::TopDocked)),
NAME_None,
EUserInterfaceActionType::Button);
}
}
if (EnumHasAnyFlags(Track->GetValidLocations(), ETimingTrackLocation::Scrollable))
{
if (Track->GetLocation() == ETimingTrackLocation::Scrollable)
{
MenuBuilder.AddMenuEntry(
LOCTEXT("ContextMenu_LocationScrollable", "Location: Scrollable Tracks"),
LOCTEXT("ContextMenu_LocationScrollable_Desc", "This track is in the list of scrollable tracks."),
FSlateIcon(),
FUIAction(FExecuteAction(), FCanExecuteAction::CreateLambda([]
{
return false;
})),
NAME_None, EUserInterfaceActionType::Button);
}
else
{
MenuBuilder.AddMenuEntry(
LOCTEXT("ContextMenu_MoveToScrollable", "Move to Scrollable Tracks"),
LOCTEXT("ContextMenu_MoveToScrollable_Desc", "Move this track to the list of scrollable tracks."),
FSlateIcon(),
FUIAction(FExecuteAction::CreateSP(this, &STimingView::ChangeTrackLocation, Track, ETimingTrackLocation::Scrollable),
FCanExecuteAction::CreateSP(this, &STimingView::CanChangeTrackLocation, Track, ETimingTrackLocation::Scrollable)),
NAME_None,
EUserInterfaceActionType::Button);
}
}
if (EnumHasAnyFlags(Track->GetValidLocations(), ETimingTrackLocation::BottomDocked))
{
if (Track->GetLocation() == ETimingTrackLocation::BottomDocked)
{
MenuBuilder.AddMenuEntry(
LOCTEXT("ContextMenu_LocationBottomDocked", "Location: Bottom Docked Tracks"),
LOCTEXT("ContextMenu_LocationBottomDocked_Desc", "This track is in the list of bottom docked tracks."),
FSlateIcon(),
FUIAction(FExecuteAction(), FCanExecuteAction::CreateLambda([]
{
return false;
})),
NAME_None, EUserInterfaceActionType::Button);
}
else
{
MenuBuilder.AddMenuEntry(
LOCTEXT("ContextMenu_DockToBottom", "Dock To Bottom"),
LOCTEXT("ContextMenu_DockToBottom_Desc", "Dock this track to the bottom."),
FSlateIcon(),
FUIAction(FExecuteAction::CreateSP(this, &STimingView::ChangeTrackLocation, Track, ETimingTrackLocation::BottomDocked),
FCanExecuteAction::CreateSP(this, &STimingView::CanChangeTrackLocation, Track, ETimingTrackLocation::BottomDocked)),
NAME_None,
EUserInterfaceActionType::Button);
}
}
if (EnumHasAnyFlags(Track->GetValidLocations(), ETimingTrackLocation::Foreground))
{
if (Track->GetLocation() == ETimingTrackLocation::Foreground)
{
MenuBuilder.AddMenuEntry(
LOCTEXT("ContextMenu_LocationForeground", "Location: Foreground Tracks"),
LOCTEXT("ContextMenu_LocationForeground_Desc", "This track is in the list of foreground tracks."),
FSlateIcon(),
FUIAction(FExecuteAction(), FCanExecuteAction::CreateLambda([]
{
return false;
})),
NAME_None, EUserInterfaceActionType::Button);
}
else
{
MenuBuilder.AddMenuEntry(
LOCTEXT("ContextMenu_MoveToForeground", "Move to Foreground Tracks"),
LOCTEXT("ContextMenu_MoveToForeground_Desc", "Move this track to the list of foreground tracks."),
FSlateIcon(),
FUIAction(FExecuteAction::CreateSP(this, &STimingView::ChangeTrackLocation, Track, ETimingTrackLocation::Foreground),
FCanExecuteAction::CreateSP(this, &STimingView::CanChangeTrackLocation, Track, ETimingTrackLocation::Foreground)),
NAME_None,
EUserInterfaceActionType::Button);
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::ChangeTrackLocation(TSharedRef<FBaseTimingTrack> Track, ETimingTrackLocation NewLocation)
{
if (ensure(CanChangeTrackLocation(Track, NewLocation)))
{
switch (Track->GetLocation())
{
case ETimingTrackLocation::Scrollable:
ensure(RemoveScrollableTrack(Track));
break;
case ETimingTrackLocation::TopDocked:
ensure(RemoveTopDockedTrack(Track));
break;
case ETimingTrackLocation::BottomDocked:
ensure(RemoveBottomDockedTrack(Track));
break;
case ETimingTrackLocation::Foreground:
ensure(RemoveForegroundTrack(Track));
break;
}
switch (NewLocation)
{
case ETimingTrackLocation::Scrollable:
AddScrollableTrack(Track);
break;
case ETimingTrackLocation::TopDocked:
AddTopDockedTrack(Track);
break;
case ETimingTrackLocation::BottomDocked:
AddBottomDockedTrack(Track);
break;
case ETimingTrackLocation::Foreground:
AddForegroundTrack(Track);
break;
}
OnTrackVisibilityChanged();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool STimingView::CanChangeTrackLocation(TSharedRef<FBaseTimingTrack> Track, ETimingTrackLocation NewLocation) const
{
return EnumHasAnyFlags(Track->GetValidLocations(), NewLocation) && Track->GetLocation() != NewLocation;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::BindCommands()
{
// FTimingViewCommands::Register();
// const FTimingViewCommands& Commands = FTimingViewCommands::Get();
//
// TSharedPtr<FUICommandList> CommandList = FTimingProfilerManager::Get()->GetCommandList();
// CommandList->MapAction(
// Commands.ShowAllGpuTracks,
// FExecuteAction::CreateSP(this, &STimingView::ShowHideAllGpuTracks_Execute),
// FCanExecuteAction(), //FCanExecuteAction::CreateLambda([] { return true; }),
// FIsActionChecked::CreateSP(this, &STimingView::ShowHideAllGpuTracks_IsChecked));
// CommandList->MapAction(
// Commands.ShowAllCpuTracks,
// FExecuteAction::CreateSP(this, &STimingView::ShowHideAllCpuTracks_Execute),
// FCanExecuteAction(), //FCanExecuteAction::CreateLambda([] { return true; }),
// FIsActionChecked::CreateSP(this, &STimingView::ShowHideAllCpuTracks_IsChecked));
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::AutoScroll_OnCheckStateChanged(ECheckBoxState NewRadioState)
{
bAutoScroll = (NewRadioState == ECheckBoxState::Checked);
Viewport.AddDirtyFlags(ETimingTrackViewportDirtyFlags::HInvalidated);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
ECheckBoxState STimingView::AutoScroll_IsChecked() const
{
return bAutoScroll ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::AutoScrollFrameAligned_Execute()
{
bIsAutoScrollFrameAligned = !bIsAutoScrollFrameAligned;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool STimingView::AutoScrollFrameAligned_IsChecked() const
{
return bIsAutoScrollFrameAligned;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::AutoScrollFrameType_Execute(ETraceFrameType FrameType)
{
AutoScrollFrameType = FrameType;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool STimingView::AutoScrollFrameType_CanExecute(ETraceFrameType FrameType) const
{
return bIsAutoScrollFrameAligned;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool STimingView::AutoScrollFrameType_IsChecked(ETraceFrameType FrameType) const
{
return AutoScrollFrameType == FrameType;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::AutoScrollViewportOffset_Execute(double Percent)
{
AutoScrollViewportOffsetPercent = Percent;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool STimingView::AutoScrollViewportOffset_IsChecked(double Percent) const
{
return AutoScrollViewportOffsetPercent == Percent;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::AutoScrollDelay_Execute(double Delay)
{
AutoScrollMinDelay = Delay;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool STimingView::AutoScrollDelay_IsChecked(double Delay) const
{
return AutoScrollMinDelay == Delay;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
double STimingView::EnforceHorizontalScrollLimits(const double InStartTime)
{
double NewStartTime = InStartTime;
double MinT, MaxT;
Viewport.GetHorizontalScrollLimits(MinT, MaxT);
if (NewStartTime > MaxT)
{
NewStartTime = MaxT;
OverscrollRight = 1.0f;
}
if (NewStartTime < MinT)
{
NewStartTime = MinT;
OverscrollLeft = 1.0f;
}
return NewStartTime;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
float STimingView::EnforceVerticalScrollLimits(const float InScrollPosY)
{
float NewScrollPosY = InScrollPosY;
const float DY = Viewport.GetScrollHeight() - Viewport.GetScrollableAreaHeight() + 7.0f; // +7 is to allow some space for the horizontal scrollbar
const float MinY = FMath::Min(DY, 0.0f);
const float MaxY = DY - MinY;
if (NewScrollPosY > MaxY)
{
NewScrollPosY = MaxY;
OverscrollBottom = 1.0f;
}
if (NewScrollPosY < MinY)
{
NewScrollPosY = MinY;
OverscrollTop = 1.0f;
}
return NewScrollPosY;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::HorizontalScrollBar_OnUserScrolled(float ScrollOffset)
{
// Disable auto-scroll if user starts scrolling with horizontal scrollbar.
bAutoScroll = false;
Viewport.OnUserScrolled(HorizontalScrollBar, ScrollOffset);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::UpdateHorizontalScrollBar()
{
Viewport.UpdateScrollBar(HorizontalScrollBar);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::VerticalScrollBar_OnUserScrolled(float ScrollOffset)
{
Viewport.OnUserScrolledY(VerticalScrollBar, ScrollOffset);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::UpdateVerticalScrollBar()
{
Viewport.UpdateScrollBarY(VerticalScrollBar);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::ScrollAtPosY(float ScrollPosY)
{
if (ScrollPosY != Viewport.GetScrollPosY())
{
Viewport.SetScrollPosY(ScrollPosY);
UpdateVerticalScrollBar();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::ScrollAtTime(double StartTime)
{
if (Viewport.ScrollAtTime(StartTime))
{
UpdateHorizontalScrollBar();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::CenterOnTimeInterval(double IntervalStartTime, double IntervalDuration)
{
if (Viewport.CenterOnTimeInterval(IntervalStartTime, IntervalDuration))
{
Viewport.EnforceHorizontalScrollLimits(1.0); // 1.0 is to disable interpolation
UpdateHorizontalScrollBar();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::BringIntoView(double StartTime, double EndTime)
{
EndTime = Viewport.RestrictEndTime(EndTime);
// Increase interval with 8% (of view size) on each side.
const double DT = Viewport.GetDuration() * 0.08;
StartTime -= DT;
EndTime += DT;
double NewStartTime = Viewport.GetStartTime();
if (EndTime > Viewport.GetEndTime())
{
NewStartTime += EndTime - Viewport.GetEndTime();
}
if (StartTime < NewStartTime)
{
NewStartTime = StartTime;
}
ScrollAtTime(NewStartTime);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::SelectTimeInterval(double IntervalStartTime, double IntervalDuration)
{
SelectionStartTime = IntervalStartTime;
SelectionEndTime = IntervalStartTime + IntervalDuration;
LastSelectionType = ESelectionType::TimeRange;
RaiseSelectionChanged();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::RaiseSelectionChanging()
{
OnSelectionChangedDelegate.Broadcast(Insights::ETimeChangedFlags::Interactive, SelectionStartTime, SelectionEndTime);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::RaiseSelectionChanged()
{
OnSelectionChangedDelegate.Broadcast(Insights::ETimeChangedFlags::None, SelectionStartTime, SelectionEndTime);
FTimingProfilerManager::Get()->SetSelectedTimeRange(SelectionStartTime, SelectionEndTime);
if (SelectionStartTime < SelectionEndTime)
{
UpdateAggregatedStats();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::RaiseTimeMarkerChanging()
{
const double TimeMarker = GetTimeMarker();
OnTimeMarkerChangedDelegate.Broadcast(Insights::ETimeChangedFlags::Interactive, TimeMarker);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::RaiseTimeMarkerChanged()
{
const double TimeMarker = GetTimeMarker();
OnTimeMarkerChangedDelegate.Broadcast(Insights::ETimeChangedFlags::None, TimeMarker);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
double STimingView::GetTimeMarker() const
{
return DefaultTimeMarker->GetTime();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::SetTimeMarker(double InMarkerTime)
{
DefaultTimeMarker->SetTime(InMarkerTime);
RaiseTimeMarkerChanged();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::AddOverlayWidget(const TSharedRef<SWidget>& InWidget)
{
if (ExtensionOverlay.IsValid())
{
ExtensionOverlay->AddSlot()
[InWidget];
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::UpdateAggregatedStats()
{
if (bAssetLoadingMode)
{
TSharedPtr<SLoadingProfilerWindow> LoadingProfilerWnd = FLoadingProfilerManager::Get()->GetProfilerWindow();
if (LoadingProfilerWnd.IsValid())
{
LoadingProfilerWnd->UpdateTableTreeViews();
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::SetAndCenterOnTimeMarker(double Time)
{
SetTimeMarker(Time);
double MinT, MaxT;
Viewport.GetHorizontalScrollLimits(MinT, MaxT);
const double ViewportDuration = Viewport.GetDuration();
MinT += ViewportDuration / 2;
MaxT += ViewportDuration / 2;
Time = FMath::Clamp<double>(Time, MinT, MaxT);
Time = Viewport.AlignTimeToPixel(Time);
CenterOnTimeInterval(Time, 0.0);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::SelectToTimeMarker(double Time)
{
const double TimeMarker = GetTimeMarker();
if (TimeMarker < Time)
{
SelectTimeInterval(TimeMarker, Time - TimeMarker);
}
else
{
SelectTimeInterval(Time, TimeMarker - Time);
}
SetTimeMarker(Time);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::SetTimeMarkersVisible(bool bIsMarkersTrackVisible)
{
if (MarkersTrack->IsVisible() != bIsMarkersTrackVisible)
{
MarkersTrack->SetVisibilityFlag(bIsMarkersTrackVisible);
if (MarkersTrack->IsVisible())
{
if (Viewport.GetScrollPosY() != 0.0f)
{
UE_LOG(TimingProfiler, Log, TEXT("SetTimeMarkersVisible!!!"));
Viewport.SetScrollPosY(Viewport.GetScrollPosY() + MarkersTrack->GetHeight());
}
MarkersTrack->SetDirtyFlag();
}
else
{
UE_LOG(TimingProfiler, Log, TEXT("SetTimeMarkersVisible!!!"));
Viewport.SetScrollPosY(Viewport.GetScrollPosY() - MarkersTrack->GetHeight());
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::SetDrawOnlyBookmarks(bool bIsBookmarksTrack)
{
if (MarkersTrack->IsBookmarksTrack() != bIsBookmarksTrack)
{
const float PrevHeight = MarkersTrack->GetHeight();
MarkersTrack->SetBookmarksTrackFlag(bIsBookmarksTrack);
if (MarkersTrack->IsVisible())
{
if (Viewport.GetScrollPosY() != 0.0f)
{
UE_LOG(TimingProfiler, Log, TEXT("SetDrawOnlyBookmarks!!!"));
Viewport.SetScrollPosY(Viewport.GetScrollPosY() + MarkersTrack->GetHeight() - PrevHeight);
}
MarkersTrack->SetDirtyFlag();
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
const TSharedPtr<FBaseTimingTrack> STimingView::GetTrackAt(float InPosX, float InPosY) const
{
TSharedPtr<FBaseTimingTrack> FoundTrack;
if (InPosY < 0.0f)
{
// above viewport
}
else if (InPosY < Viewport.GetTopOffset())
{
// Top Docked Tracks
for (const TSharedPtr<FBaseTimingTrack>& TrackPtr: TopDockedTracks)
{
const FBaseTimingTrack& Track = *TrackPtr;
if (TrackPtr->IsVisible())
{
if (InPosY >= Track.GetPosY() && InPosY < Track.GetPosY() + Track.GetHeight())
{
FoundTrack = TrackPtr;
break;
}
}
}
}
else if (InPosY < Viewport.GetHeight() - Viewport.GetBottomOffset())
{
// Scrollable Tracks
for (const TSharedPtr<FBaseTimingTrack>& TrackPtr: ScrollableTracks)
{
const FBaseTimingTrack& Track = *TrackPtr;
if (Track.IsVisible())
{
if (InPosY >= Track.GetPosY() && InPosY < Track.GetPosY() + Track.GetHeight())
{
FoundTrack = TrackPtr;
break;
}
}
}
}
else if (InPosY < Viewport.GetHeight())
{
// Bottom Docked Tracks
for (const TSharedPtr<FBaseTimingTrack>& TrackPtr: BottomDockedTracks)
{
const FBaseTimingTrack& Track = *TrackPtr;
if (TrackPtr->IsVisible())
{
if (InPosY >= Track.GetPosY() && InPosY < Track.GetPosY() + Track.GetHeight())
{
FoundTrack = TrackPtr;
break;
}
}
}
}
else
{
// below viewport
}
return FoundTrack;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::UpdateHoveredTimingEvent(float InMousePosX, float InMousePosY)
{
TSharedPtr<FBaseTimingTrack> NewHoveredTrack = GetTrackAt(InMousePosX, InMousePosY);
if (NewHoveredTrack != HoveredTrack)
{
HoveredTrack = NewHoveredTrack;
OnHoveredTrackChangedDelegate.Broadcast(HoveredTrack);
}
TSharedPtr<const ITimingEvent> NewHoveredEvent;
if (HoveredTrack.IsValid())
{
FStopwatch Stopwatch;
Stopwatch.Start();
NewHoveredEvent = HoveredTrack->GetEvent(InMousePosX, InMousePosY, Viewport);
Stopwatch.Stop();
const double DT = Stopwatch.GetAccumulatedTime();
if (DT > 0.001)
{
UE_LOG(TimingProfiler, Log, TEXT("HoveredTrack [%g, %g] GetEvent: %.1f ms"), InMousePosX, InMousePosY, DT * 1000.0);
}
}
if (NewHoveredEvent.IsValid())
{
if (!HoveredEvent.IsValid() || !NewHoveredEvent->Equals(*HoveredEvent))
{
FStopwatch Stopwatch;
Stopwatch.Start();
HoveredEvent = NewHoveredEvent;
ensure(HoveredTrack == HoveredEvent->GetTrack());
HoveredTrack->UpdateEventStats(const_cast<ITimingEvent&>(*HoveredEvent));
Stopwatch.Update();
const double T1 = Stopwatch.GetAccumulatedTime();
HoveredTrack->InitTooltip(Tooltip, *HoveredEvent);
Stopwatch.Update();
const double T2 = Stopwatch.GetAccumulatedTime();
OnHoveredEventChangedDelegate.Broadcast(HoveredEvent);
Stopwatch.Update();
const double T3 = Stopwatch.GetAccumulatedTime();
if (T3 > 0.001)
{
UE_LOG(TimingProfiler, Log, TEXT("HoveredTrack [%g, %g] Tooltip: %.1f ms (%.1f + %.1f + %.1f)"),
InMousePosX, InMousePosY, T3 * 1000.0, T1 * 1000.0, (T2 - T1) * 1000.0, (T3 - T2) * 1000.0);
}
}
Tooltip.SetDesiredOpacity(1.0f);
}
else
{
if (HoveredEvent.IsValid())
{
HoveredEvent.Reset();
OnHoveredEventChangedDelegate.Broadcast(HoveredEvent);
}
Tooltip.SetDesiredOpacity(0.0f);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::OnSelectedTimingEventChanged()
{
if (!bAssetLoadingMode)
{
if (SelectedEvent.IsValid())
{
SelectedEvent->GetTrack()->UpdateEventStats(const_cast<ITimingEvent&>(*SelectedEvent));
SelectedEvent->GetTrack()->OnEventSelected(*SelectedEvent);
}
}
OnSelectedEventChangedDelegate.Broadcast(SelectedEvent);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::SelectHoveredTimingTrack()
{
if (SelectedTrack != HoveredTrack)
{
SelectedTrack = HoveredTrack;
OnSelectedTrackChangedDelegate.Broadcast(SelectedTrack);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::SelectHoveredTimingEvent()
{
if (SelectedEvent != HoveredEvent)
{
SelectedEvent = HoveredEvent;
if (SelectedEvent.IsValid())
{
LastSelectionType = ESelectionType::TimingEvent;
BringIntoView(SelectedEvent->GetStartTime(), SelectedEvent->GetEndTime());
}
OnSelectedTimingEventChanged();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::SelectLeftTimingEvent()
{
if (SelectedEvent.IsValid())
{
const uint32 Depth = SelectedEvent->GetDepth();
const double StartTime = SelectedEvent->GetStartTime();
const double EndTime = SelectedEvent->GetEndTime();
auto EventFilter = [Depth, StartTime, EndTime](double EventStartTime, double EventEndTime, uint32 EventDepth)
{
return EventDepth == Depth && (EventStartTime < StartTime || EventEndTime < EndTime);
};
const TSharedPtr<const ITimingEvent> LeftEvent = SelectedEvent->GetTrack()->SearchEvent(
FTimingEventSearchParameters(0.0, StartTime, ETimingEventSearchFlags::SearchAll, EventFilter));
if (LeftEvent.IsValid())
{
SelectedEvent = LeftEvent;
BringIntoView(SelectedEvent->GetStartTime(), SelectedEvent->GetEndTime());
OnSelectedTimingEventChanged();
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::SelectRightTimingEvent()
{
if (SelectedEvent.IsValid())
{
const uint32 Depth = SelectedEvent->GetDepth();
const double StartTime = SelectedEvent->GetStartTime();
const double EndTime = SelectedEvent->GetEndTime();
auto EventFilter = [Depth, StartTime, EndTime](double EventStartTime, double EventEndTime, uint32 EventDepth)
{
return EventDepth == Depth && (EventStartTime > StartTime || EventEndTime > EndTime);
};
const TSharedPtr<const ITimingEvent> RightEvent = SelectedEvent->GetTrack()->SearchEvent(
FTimingEventSearchParameters(EndTime, Viewport.GetMaxValidTime(), ETimingEventSearchFlags::StopAtFirstMatch, EventFilter));
if (RightEvent.IsValid())
{
SelectedEvent = RightEvent;
BringIntoView(SelectedEvent->GetStartTime(), SelectedEvent->GetEndTime());
OnSelectedTimingEventChanged();
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::SelectUpTimingEvent()
{
if (SelectedEvent.IsValid() &&
SelectedEvent->GetDepth() > 0)
{
const uint32 Depth = SelectedEvent->GetDepth() - 1;
const double StartTime = SelectedEvent->GetStartTime();
const double EndTime = SelectedEvent->GetEndTime();
auto EventFilter = [Depth, StartTime, EndTime](double EventStartTime, double EventEndTime, uint32 EventDepth)
{
return EventDepth == Depth && EventStartTime <= EndTime && EventEndTime >= StartTime;
};
const TSharedPtr<const ITimingEvent> UpEvent = SelectedEvent->GetTrack()->SearchEvent(
FTimingEventSearchParameters(StartTime, EndTime, ETimingEventSearchFlags::StopAtFirstMatch, EventFilter));
if (UpEvent.IsValid())
{
SelectedEvent = UpEvent;
BringIntoView(SelectedEvent->GetStartTime(), SelectedEvent->GetEndTime());
OnSelectedTimingEventChanged();
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::SelectDownTimingEvent()
{
if (SelectedEvent.IsValid())
{
const uint32 Depth = SelectedEvent->GetDepth() + 1;
const double StartTime = SelectedEvent->GetStartTime();
const double EndTime = SelectedEvent->GetEndTime();
double LargestDuration = 0.0;
auto EventFilter = [Depth, StartTime, EndTime, &LargestDuration](double EventStartTime, double EventEndTime, uint32 EventDepth)
{
const double Duration = EventEndTime - EventStartTime;
return Duration > LargestDuration && EventDepth == Depth && EventStartTime <= EndTime && EventEndTime >= StartTime;
};
auto EventMatched = [&LargestDuration](double EventStartTime, double EventEndTime, uint32 EventDepth)
{
const double Duration = EventEndTime - EventStartTime;
LargestDuration = Duration;
};
const TSharedPtr<const ITimingEvent> DownEvent = SelectedEvent->GetTrack()->SearchEvent(
FTimingEventSearchParameters(StartTime, EndTime, ETimingEventSearchFlags::SearchAll, EventFilter, EventMatched));
if (DownEvent.IsValid())
{
SelectedEvent = DownEvent;
BringIntoView(SelectedEvent->GetStartTime(), SelectedEvent->GetEndTime());
OnSelectedTimingEventChanged();
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::FrameSelection()
{
double StartTime, EndTime;
ESelectionType Type = ESelectionType::None;
if (LastSelectionType == ESelectionType::TimingEvent)
{
// Try framing the selected timing event.
if (SelectedEvent.IsValid())
{
Type = ESelectionType::TimingEvent;
}
// Next time, try framing the selected time range.
LastSelectionType = ESelectionType::TimeRange;
}
else if (LastSelectionType == ESelectionType::TimeRange)
{
// Try framing the selected time range.
if (SelectionEndTime > SelectionStartTime)
{
Type = ESelectionType::TimeRange;
}
// Next time, try framing the selected timing event.
LastSelectionType = ESelectionType::TimingEvent;
}
// If no last selection or last selection is empty...
if (LastSelectionType == ESelectionType::None || Type == ESelectionType::None)
{
// First, try framing the selected timing event...
if (SelectedEvent.IsValid())
{
Type = ESelectionType::TimingEvent;
}
else // ...otherwise, try framing the selected time range
{
Type = ESelectionType::TimeRange;
}
}
if (Type == ESelectionType::TimingEvent)
{
// Frame the selected event.
StartTime = SelectedEvent->GetStartTime();
EndTime = Viewport.RestrictEndTime(SelectedEvent->GetEndTime());
if (EndTime == StartTime)
{
EndTime += 1.0 / Viewport.GetScaleX(); // +1px
}
}
else
{
// Frame the selected time range.
StartTime = SelectionStartTime;
EndTime = Viewport.RestrictEndTime(SelectionEndTime);
}
if (EndTime > StartTime)
{
const double Duration = EndTime - StartTime;
if (Viewport.ZoomOnTimeInterval(StartTime - Duration * 0.1, Duration * 1.2))
{
UpdateHorizontalScrollBar();
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::SetEventFilter(const TSharedPtr<ITimingEventFilter> InEventFilter)
{
TimingEventFilter = InEventFilter;
Viewport.AddDirtyFlags(ETimingTrackViewportDirtyFlags::HInvalidated);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::ToggleEventFilterByEventType(const uint64 EventType)
{
if (IsFilterByEventType(EventType))
{
SetEventFilter(nullptr); // reset filter
}
else
{
TSharedRef<FTimingEventFilterByEventType> NewEventFilter = MakeShared<FTimingEventFilterByEventType>(EventType);
SetEventFilter(NewEventFilter); // set new filter
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool STimingView::IsFilterByEventType(const uint64 EventType) const
{
if (TimingEventFilter.IsValid() &&
TimingEventFilter->Is<FTimingEventFilterByEventType>())
{
const FTimingEventFilterByEventType& EventFilterByEventType = TimingEventFilter->As<FTimingEventFilterByEventType>();
return EventFilterByEventType.GetEventType() == EventType;
}
return false;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
TSharedRef<SWidget> STimingView::MakeAutoScrollOptionsMenu()
{
FMenuBuilder MenuBuilder(/*bInShouldCloseWindowAfterMenuSelection=*/true, nullptr);
MenuBuilder.BeginSection("QuickFilter", LOCTEXT("AutoScrollHeading", "Auto Scroll Options"));
{
MenuBuilder.AddMenuEntry(
LOCTEXT("AutoScrollFrameAligned", "Frame Aligned"),
LOCTEXT("AutoScrollFrameAligned_Tooltip", "Align the viewport's center position with the start time of a frame."),
FSlateIcon(),
FUIAction(FExecuteAction::CreateSP(this, &STimingView::AutoScrollFrameAligned_Execute),
FCanExecuteAction(),
FIsActionChecked::CreateSP(this, &STimingView::AutoScrollFrameAligned_IsChecked)),
NAME_None,
EUserInterfaceActionType::ToggleButton);
MenuBuilder.AddMenuEntry(
LOCTEXT("AutoScrollFrameTypeGame", "Align with Game Frames"),
LOCTEXT("AutoScrollFrameTypeGame_Tooltip", "Align the viewport's center position with the start time of a Game frame."),
FSlateIcon(),
FUIAction(FExecuteAction::CreateSP(this, &STimingView::AutoScrollFrameType_Execute, TraceFrameType_Game),
FCanExecuteAction::CreateSP(this, &STimingView::AutoScrollFrameType_CanExecute, TraceFrameType_Game),
FIsActionChecked::CreateSP(this, &STimingView::AutoScrollFrameType_IsChecked, TraceFrameType_Game)),
NAME_None,
EUserInterfaceActionType::RadioButton);
MenuBuilder.AddMenuEntry(
LOCTEXT("AutoScrollFrameTypeRendering", "Align with Rendering Frames"),
LOCTEXT("AutoScrollFrameTypeRendering_Tooltip", "Align the viewport's center position with the start time of a Rendering frame."),
FSlateIcon(),
FUIAction(FExecuteAction::CreateSP(this, &STimingView::AutoScrollFrameType_Execute, TraceFrameType_Rendering),
FCanExecuteAction::CreateSP(this, &STimingView::AutoScrollFrameType_CanExecute, TraceFrameType_Rendering),
FIsActionChecked::CreateSP(this, &STimingView::AutoScrollFrameType_IsChecked, TraceFrameType_Rendering)),
NAME_None,
EUserInterfaceActionType::RadioButton);
MenuBuilder.AddMenuSeparator();
MenuBuilder.AddMenuEntry(
LOCTEXT("AutoScrollViewportOffset-10", "Viewport Offset: -10%"),
LOCTEXT("AutoScrollViewportOffset-10_Tooltip", "Set the viewport offset to -10% (i.e. backward) of the viewport's width.\nAvoids flickering as the end of session will be outside of the viewport."),
FSlateIcon(),
FUIAction(FExecuteAction::CreateSP(this, &STimingView::AutoScrollViewportOffset_Execute, -0.1),
FCanExecuteAction(),
FIsActionChecked::CreateSP(this, &STimingView::AutoScrollViewportOffset_IsChecked, -0.1)),
NAME_None,
EUserInterfaceActionType::RadioButton);
MenuBuilder.AddMenuEntry(
LOCTEXT("AutoScrollViewportOffset0", "Viewport Offset: 0"),
LOCTEXT("AutoScrollViewportOffset0_Tooltip", "Set the viewport offset to 0."),
FSlateIcon(),
FUIAction(FExecuteAction::CreateSP(this, &STimingView::AutoScrollViewportOffset_Execute, 0.0),
FCanExecuteAction(),
FIsActionChecked::CreateSP(this, &STimingView::AutoScrollViewportOffset_IsChecked, 0.0)),
NAME_None,
EUserInterfaceActionType::RadioButton);
MenuBuilder.AddMenuEntry(
LOCTEXT("AutoScrollViewportOffset+10", "Viewport Offset: +10%"),
LOCTEXT("AutoScrollViewportOffset+10_Tooltip", "Set the viewport offset to +10% (i.e. forward) of the viewport's width.\nAllows 10% empty space on the right side of the viewport."),
FSlateIcon(),
FUIAction(FExecuteAction::CreateSP(this, &STimingView::AutoScrollViewportOffset_Execute, +0.1),
FCanExecuteAction(),
FIsActionChecked::CreateSP(this, &STimingView::AutoScrollViewportOffset_IsChecked, +0.1)),
NAME_None,
EUserInterfaceActionType::RadioButton);
MenuBuilder.AddMenuSeparator();
MenuBuilder.AddMenuEntry(
LOCTEXT("AutoScrollDelay0", "Delay: 0"),
LOCTEXT("AutoScrollDelay0_Tooltip", "Set the time delay of the auto-scroll update to 0."),
FSlateIcon(),
FUIAction(FExecuteAction::CreateSP(this, &STimingView::AutoScrollDelay_Execute, 0.0),
FCanExecuteAction(),
FIsActionChecked::CreateSP(this, &STimingView::AutoScrollDelay_IsChecked, 0.0)),
NAME_None,
EUserInterfaceActionType::RadioButton);
MenuBuilder.AddMenuEntry(
LOCTEXT("AutoScrollDelay300ms", "Delay: 300ms"),
LOCTEXT("AutoScrollDelay300ms_Tooltip", "Set the time delay of the auto-scroll update to 300ms."),
FSlateIcon(),
FUIAction(FExecuteAction::CreateSP(this, &STimingView::AutoScrollDelay_Execute, 0.3),
FCanExecuteAction(),
FIsActionChecked::CreateSP(this, &STimingView::AutoScrollDelay_IsChecked, 0.3)),
NAME_None,
EUserInterfaceActionType::RadioButton);
MenuBuilder.AddMenuEntry(
LOCTEXT("AutoScrollDelay1s", "Delay: 1s"),
LOCTEXT("AutoScrollDelay1s_Tooltip", "Set the time delay of the auto-scroll update to 1s."),
FSlateIcon(),
FUIAction(FExecuteAction::CreateSP(this, &STimingView::AutoScrollDelay_Execute, 1.0),
FCanExecuteAction(),
FIsActionChecked::CreateSP(this, &STimingView::AutoScrollDelay_IsChecked, 1.0)),
NAME_None,
EUserInterfaceActionType::RadioButton);
MenuBuilder.AddMenuEntry(
LOCTEXT("AutoScrollDelay3s", "Delay: 3s"),
LOCTEXT("AutoScrollDelay3s_Tooltip", "Set the time delay of the auto-scroll update to 3s."),
FSlateIcon(),
FUIAction(FExecuteAction::CreateSP(this, &STimingView::AutoScrollDelay_Execute, 3.0),
FCanExecuteAction(),
FIsActionChecked::CreateSP(this, &STimingView::AutoScrollDelay_IsChecked, 3.0)),
NAME_None,
EUserInterfaceActionType::RadioButton);
}
return MenuBuilder.MakeWidget();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
TSharedRef<SWidget> STimingView::MakeTracksFilterMenu()
{
FMenuBuilder MenuBuilder(/*bInShouldCloseWindowAfterMenuSelection=*/true, nullptr);
CreateAllTracksMenu(MenuBuilder);
MenuBuilder.BeginSection("QuickFilter", LOCTEXT("TracksFilterHeading", "Quick Filter"));
{
// const FTimingViewCommands& Commands = FTimingViewCommands::Get();
// TODO: MenuBuilder.AddMenuEntry(Commands.AutoHideGraphTrack);
MenuBuilder.AddMenuEntry(
LOCTEXT("ShowGraphTrack", "Graph Track - G"),
LOCTEXT("ShowGraphTrack_Tooltip", "Show/hide the Graph track"),
FSlateIcon(),
FUIAction(FExecuteAction::CreateSP(this, &STimingView::ShowHideGraphTrack_Execute),
FCanExecuteAction(),
FIsActionChecked::CreateSP(this, &STimingView::ShowHideGraphTrack_IsChecked)),
NAME_None,
EUserInterfaceActionType::ToggleButton);
MenuBuilder.AddMenuSeparator("QuickFilterSeparator");
// TODO: MenuBuilder.AddMenuEntry(Commands.AutoHideEmptyTracks);
MenuBuilder.AddMenuEntry(
LOCTEXT("AutoHideEmptyTracks", "Auto Hide Empty Tracks - V"),
LOCTEXT("AutoHideEmptyTracks_Tooltip", "Auto hide empty tracks (ones without timing events in current viewport)"),
FSlateIcon(),
FUIAction(FExecuteAction::CreateSP(this, &STimingView::ToggleAutoHideEmptyTracks),
FCanExecuteAction(),
FIsActionChecked::CreateSP(this, &STimingView::IsAutoHideEmptyTracksEnabled)),
NAME_None,
EUserInterfaceActionType::ToggleButton);
}
MenuBuilder.EndSection();
// Let any plugin extend the filter menu.
for (Insights::ITimingViewExtender* Extender: GetExtenders())
{
Extender->ExtendFilterMenu(*this, MenuBuilder);
}
return MenuBuilder.MakeWidget();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::CreateAllTracksMenu(FMenuBuilder& MenuBuilder)
{
MenuBuilder.BeginSection("AllTracks", LOCTEXT("AllTracksHeading", "All Tracks"));
if (TopDockedTracks.Num() > 0)
{
MenuBuilder.AddSubMenu(
LOCTEXT("TopDockedTracks", "Top Docked Tracks"),
LOCTEXT("TopDockedTracks_Tooltip", "Show/hide individual top docked tracks"),
FNewMenuDelegate::CreateLambda([this](FMenuBuilder& InSubMenuBuilder)
{
InSubMenuBuilder.AddWidget(
SNew(SBox)
.MaxDesiredHeight(300.0f)
.MinDesiredWidth(300.0f)
.MaxDesiredWidth(300.0f)
[SNew(STimingViewTrackList, SharedThis(this), ETimingTrackLocation::TopDocked)],
FText(), true);
}));
}
if (BottomDockedTracks.Num() > 0)
{
MenuBuilder.AddSubMenu(
LOCTEXT("BottomDockedTracks", "Bottom Docked Tracks"),
LOCTEXT("BottomDockedTracks_Tooltip", "Show/hide individual bottom docked tracks"),
FNewMenuDelegate::CreateLambda([this](FMenuBuilder& InSubMenuBuilder)
{
InSubMenuBuilder.AddWidget(
SNew(SBox)
.MaxDesiredHeight(300.0f)
.MinDesiredWidth(300.0f)
.MaxDesiredWidth(300.0f)
[SNew(STimingViewTrackList, SharedThis(this), ETimingTrackLocation::BottomDocked)],
FText(), true);
}));
}
if (ScrollableTracks.Num() > 0)
{
MenuBuilder.AddSubMenu(
LOCTEXT("ScrollableTracks", "Scrollable Tracks"),
LOCTEXT("ScrollableTracks_Tooltip", "Show/hide individual scrollable tracks"),
FNewMenuDelegate::CreateLambda([this](FMenuBuilder& InSubMenuBuilder)
{
InSubMenuBuilder.AddWidget(
SNew(SBox)
.MaxDesiredHeight(300.0f)
.MinDesiredWidth(300.0f)
.MaxDesiredWidth(300.0f)
[SNew(STimingViewTrackList, SharedThis(this), ETimingTrackLocation::Scrollable)],
FText(), true);
}));
}
if (ForegroundTracks.Num() > 0)
{
MenuBuilder.AddSubMenu(
LOCTEXT("ForegroundTracks", "Foreground Tracks"),
LOCTEXT("ForegroundTracks_Tooltip", "Show/hide individual foreground tracks"),
FNewMenuDelegate::CreateLambda([this](FMenuBuilder& InSubMenuBuilder)
{
InSubMenuBuilder.AddWidget(
SNew(SBox)
.MaxDesiredHeight(300.0f)
.MinDesiredWidth(300.0f)
.MaxDesiredWidth(300.0f)
[SNew(STimingViewTrackList, SharedThis(this), ETimingTrackLocation::Foreground)],
FText(), true);
}));
}
MenuBuilder.EndSection();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool STimingView::ShowHideGraphTrack_IsChecked() const
{
return GraphTrack->IsVisible();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::ShowHideGraphTrack_Execute()
{
GraphTrack->ToggleVisibility();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool STimingView::IsAutoHideEmptyTracksEnabled() const
{
return (Viewport.GetLayout().TargetMinTimelineH == 0.0f);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::ToggleAutoHideEmptyTracks()
{
Viewport.ToggleLayoutMinTrackHeight();
for (const TSharedPtr<FBaseTimingTrack>& TrackPtr: ScrollableTracks)
{
TrackPtr->SetHeight(0.0f);
}
ScrollAtPosY(0.0f);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool STimingView::ToggleTrackVisibility_IsChecked(uint64 InTrackId) const
{
const TSharedPtr<FBaseTimingTrack>* const TrackPtrPtr = AllTracks.Find(InTrackId);
if (TrackPtrPtr)
{
return (*TrackPtrPtr)->IsVisible();
}
return false;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::ToggleTrackVisibility_Execute(uint64 InTrackId)
{
const TSharedPtr<FBaseTimingTrack>* TrackPtrPtr = AllTracks.Find(InTrackId);
if (TrackPtrPtr)
{
(*TrackPtrPtr)->ToggleVisibility();
OnTrackVisibilityChanged();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::OnTrackVisibilityChanged()
{
if (HoveredTrack.IsValid())
{
HoveredTrack.Reset();
OnHoveredTrackChangedDelegate.Broadcast(HoveredTrack);
}
if (HoveredEvent.IsValid())
{
HoveredEvent.Reset();
OnHoveredEventChangedDelegate.Broadcast(HoveredEvent);
}
if (SelectedTrack.IsValid())
{
SelectedTrack.Reset();
OnSelectedTrackChangedDelegate.Broadcast(SelectedTrack);
}
if (SelectedEvent.IsValid())
{
SelectedEvent.Reset();
OnSelectedEventChangedDelegate.Broadcast(SelectedEvent);
}
Tooltip.SetDesiredOpacity(0.0f);
// TODO: ThreadFilterChangedEvent.Broadcast();
FTimingProfilerManager::Get()->OnThreadFilterChanged();
if (SelectionStartTime < SelectionEndTime)
{
UpdateAggregatedStats();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void STimingView::PreventThrottling()
{
bPreventThrottling = true;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
TSharedPtr<FBaseTimingTrack> STimingView::FindTrack(uint64 InTrackId)
{
TSharedPtr<FBaseTimingTrack>* TrackPtrPtr = AllTracks.Find(InTrackId);
return TrackPtrPtr ? *TrackPtrPtr : nullptr;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
TArray<Insights::ITimingViewExtender*> STimingView::GetExtenders() const
{
return IModularFeatures::Get().GetModularFeatureImplementations<Insights::ITimingViewExtender>(Insights::TimingViewExtenderFeatureName);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
#undef ACTIVATE_BENCHMARK
#undef LOCTEXT_NAMESPACE