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

1687 lines
66 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SPacketView.h"
#include "Fonts/FontMeasure.h"
#include "Fonts/SlateFontInfo.h"
#include "Framework/Application/SlateApplication.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "HAL/PlatformTime.h"
#include "Rendering/DrawElements.h"
#include "TraceServices/Model/NetProfiler.h"
#include "Styling/CoreStyle.h"
#include "Widgets/Layout/SScrollBar.h"
// Insights
#include "Insights/Common/PaintUtils.h"
#include "Insights/Common/Stopwatch.h"
#include "Insights/Common/TimeUtils.h"
#include "Insights/InsightsManager.h"
#include "Insights/InsightsStyle.h"
#include "Insights/NetworkingProfiler/NetworkingProfilerManager.h"
#include "Insights/NetworkingProfiler/Widgets/SNetworkingProfilerWindow.h"
#include "Insights/NetworkingProfiler/Widgets/SPacketContentView.h"
#include <limits>
////////////////////////////////////////////////////////////////////////////////////////////////////
#define LOCTEXT_NAMESPACE "SPacketView"
////////////////////////////////////////////////////////////////////////////////////////////////////
SPacketView::SPacketView()
: ProfilerWindow(), PacketSeries(MakeShared<FNetworkPacketSeries>())
{
Reset();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
SPacketView::~SPacketView()
{
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void SPacketView::Reset()
{
// ProfilerWindow
GameInstanceIndex = 0;
ConnectionIndex = 0;
ConnectionMode = Trace::ENetProfilerConnectionMode::Outgoing;
Viewport.Reset();
FAxisViewportInt32& ViewportX = Viewport.GetHorizontalAxisViewport();
ViewportX.SetScaleLimits(0.0001f, 16.0f); // 10000 [sample/px] to 16 [px/sample]
ViewportX.SetScale(5.0f);
FAxisViewportDouble& ViewportY = Viewport.GetVerticalAxisViewport();
ViewportY.SetScaleLimits(0.0001, 50.0);
ViewportY.SetScale(0.02);
bIsViewportDirty = true;
PacketSeries->Reset();
bIsStateDirty = true;
bIsAutoZoomEnabled = true;
AutoZoomViewportPos = ViewportX.GetPos();
AutoZoomViewportScale = ViewportX.GetScale();
AutoZoomViewportSize = 0.0f;
AnalysisSyncNextTimestamp = 0;
ConnectionChangeCount = 0;
MousePosition = FVector2D::ZeroVector;
MousePositionOnButtonDown = FVector2D::ZeroVector;
ViewportPosXOnButtonDown = 0.0f;
MousePositionOnButtonUp = FVector2D::ZeroVector;
bIsLMB_Pressed = false;
bIsRMB_Pressed = false;
bIsScrolling = false;
SelectionStartPacketIndex = 0;
SelectionEndPacketIndex = 0;
LastSelectedPacketIndex = 0;
SelectedSample.Reset();
HoveredSample.Reset();
TooltipDesiredOpacity = 0.9f;
TooltipOpacity = 0.0f;
// ThisGeometry
CursorType = ECursorType::Default;
NumUpdatedPackets = 0;
UpdateDurationHistory.Reset();
DrawDurationHistory.Reset();
OnPaintDurationHistory.Reset();
LastOnPaintTime = FPlatformTime::Cycles64();
if (ProfilerWindow.IsValid())
{
SetConnection(ProfilerWindow->GetSelectedGameInstanceIndex(), ProfilerWindow->GetSelectedConnectionIndex(), ProfilerWindow->GetSelectedConnectionMode());
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void SPacketView::SetConnection(uint32 InGameInstanceIndex, uint32 InConnectionIndex, Trace::ENetProfilerConnectionMode InConnectionMode)
{
GameInstanceIndex = InGameInstanceIndex;
ConnectionIndex = InConnectionIndex;
ConnectionMode = InConnectionMode;
HoveredSample.Reset();
SelectedSample.Reset();
SelectionStartPacketIndex = 0;
SelectionEndPacketIndex = 0;
LastSelectedPacketIndex = 0;
OnSelectionChanged();
bIsStateDirty = true;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void SPacketView::Construct(const FArguments& InArgs, TSharedPtr<SNetworkingProfilerWindow> InProfilerWindow)
{
ProfilerWindow = InProfilerWindow;
ChildSlot
[SNew(SOverlay)
.Visibility(EVisibility::SelfHitTestInvisible)
+ SOverlay::Slot()
.VAlign(VAlign_Top)
.Padding(FMargin(0, 0, 0, 0))
[SAssignNew(HorizontalScrollBar, SScrollBar)
.Orientation(Orient_Horizontal)
.AlwaysShowScrollbar(false)
.Visibility(EVisibility::Visible)
.Thickness(FVector2D(5.0f, 5.0f))
.RenderOpacity(0.75)
.OnUserScrolled(this, &SPacketView::HorizontalScrollBar_OnUserScrolled)]];
UpdateHorizontalScrollBar();
BindCommands();
Reset();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void SPacketView::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime)
{
if (ThisGeometry != AllottedGeometry || bIsViewportDirty)
{
bIsViewportDirty = false;
const float ViewWidth = AllottedGeometry.GetLocalSize().X;
const float ViewHeight = AllottedGeometry.GetLocalSize().Y;
Viewport.SetSize(ViewWidth, ViewHeight);
bIsStateDirty = true;
}
ThisGeometry = AllottedGeometry;
FAxisViewportInt32& ViewportX = Viewport.GetHorizontalAxisViewport();
if (!bIsScrolling)
{
// Elastic snap to horizontal limits.
if (ViewportX.UpdatePosWithinLimits())
{
bIsStateDirty = true;
}
}
// Disable auto-zoom if viewport's position or scale has changed.
if (AutoZoomViewportPos != ViewportX.GetPos() ||
AutoZoomViewportScale != ViewportX.GetScale())
{
bIsAutoZoomEnabled = false;
}
// Update auto-zoom if viewport size has changed.
bool bAutoZoom = bIsAutoZoomEnabled && AutoZoomViewportSize != ViewportX.GetSize();
const uint64 Time = FPlatformTime::Cycles64();
if (Time > AnalysisSyncNextTimestamp)
{
const uint64 WaitTime = static_cast<uint64>(0.1 / FPlatformTime::GetSecondsPerCycle64()); // 100ms
AnalysisSyncNextTimestamp = Time + WaitTime;
TSharedPtr<const Trace::IAnalysisSession> Session = FInsightsManager::Get()->GetSession();
if (Session.IsValid())
{
Trace::FAnalysisSessionReadScope SessionReadScope(*Session.Get());
const Trace::INetProfilerProvider& NetProfilerProvider = Trace::ReadNetProfilerProvider(*Session.Get());
const uint32 NewConnectionChangeCount = NetProfilerProvider.GetConnectionChangeCount();
if (NewConnectionChangeCount != ConnectionChangeCount)
{
ConnectionChangeCount = NewConnectionChangeCount;
bIsStateDirty = true;
if (bIsAutoZoomEnabled)
{
bAutoZoom = true;
}
}
}
}
if (bAutoZoom)
{
AutoZoom();
}
if (bIsStateDirty)
{
bIsStateDirty = false;
UpdateState();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool SPacketView::IsConnectionValid(const Trace::INetProfilerProvider& NetProfilerProvider, const uint32 InGameInstanceIndex, const uint32 InConnectionIndex, const Trace::ENetProfilerConnectionMode InConnectionMode)
{
// TODO: return NetProfilerProvider.IsConnectionValid(GameInstanceIndex, ConnectionIndex, ConnectionMode);
const uint32 GameInstanceCount = NetProfilerProvider.GetGameInstanceCount();
if (InGameInstanceIndex >= GameInstanceCount)
{
return false;
}
bool bIsValidConnection = false;
NetProfilerProvider.ReadConnections(InGameInstanceIndex, [InConnectionIndex, InConnectionMode, &bIsValidConnection](const Trace::FNetProfilerConnection& Connection)
{
if (InConnectionIndex == Connection.ConnectionIndex)
{
if ((InConnectionMode == Trace::ENetProfilerConnectionMode::Outgoing && Connection.bHasOutgoingData) ||
(InConnectionMode == Trace::ENetProfilerConnectionMode::Incoming && Connection.bHasIncomingData))
{
bIsValidConnection = true;
}
}
});
return bIsValidConnection;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void SPacketView::UpdateState()
{
FStopwatch Stopwatch;
Stopwatch.Start();
//////////////////////////////////////////////////
struct FPacketFilter
{
bool bByNetId = false;
uint32 NetId = 0;
bool bByEventType = false;
uint32 EventTypeIndex = 0;
};
FPacketFilter Filter;
if (ProfilerWindow)
{
TSharedPtr<SPacketContentView> PacketContentView = ProfilerWindow->GetPacketContentView();
if (PacketContentView)
{
Filter.bByNetId = PacketContentView->IsFilterByNetIdEnabled();
Filter.NetId = PacketContentView->GetFilterNetId();
Filter.bByEventType = PacketContentView->IsFilterByEventTypeEnabled();
Filter.EventTypeIndex = PacketContentView->GetFilterEventTypeIndex();
}
}
//////////////////////////////////////////////////
FAxisViewportInt32& ViewportX = Viewport.GetHorizontalAxisViewport();
const int32 PreviousMaxValue = ViewportX.GetMaxValue();
ViewportX.SetMinMaxInterval(0, 0);
// Reset stats.
PacketSeries->NumAggregatedPackets = 0;
NumUpdatedPackets = 0;
FNetworkPacketSeriesBuilder Builder(*PacketSeries, Viewport);
TSharedPtr<const Trace::IAnalysisSession> Session = FInsightsManager::Get()->GetSession();
if (Session.IsValid())
{
Trace::FAnalysisSessionReadScope SessionReadScope(*Session.Get());
const Trace::INetProfilerProvider& NetProfilerProvider = Trace::ReadNetProfilerProvider(*Session.Get());
if (IsConnectionValid(NetProfilerProvider, GameInstanceIndex, ConnectionIndex, ConnectionMode))
{
const uint32 NumPackets = NetProfilerProvider.GetPacketCount(ConnectionIndex, ConnectionMode);
ViewportX.SetMinMaxInterval(0, NumPackets);
if (NumPackets > 0)
{
const int32 MinIndex = ViewportX.GetValueAtOffset(0.0f);
const int32 MaxIndex = ViewportX.GetValueAtOffset(ViewportX.GetSize());
const uint32 PacketStartIndex = static_cast<uint32>(FMath::Max(0, MinIndex));
const uint32 PacketEndIndex = FMath::Min(NumPackets - 1, static_cast<uint32>(FMath::Max(0, MaxIndex)));
if (PacketStartIndex <= PacketEndIndex)
{
int32 PacketIndex = PacketStartIndex;
NetProfilerProvider.EnumeratePackets(ConnectionIndex, ConnectionMode, PacketStartIndex, PacketEndIndex, [this, &Builder, &PacketIndex, &Filter, &NetProfilerProvider](const Trace::FNetProfilerPacket& Packet)
{
FNetworkPacketAggregatedSample* SamplePtr = Builder.AddPacket(PacketIndex++, Packet);
if (SamplePtr)
{
if (SamplePtr->NumPackets == 1)
{
SamplePtr->bAtLeastOnePacketMatchesFilter = !Filter.bByNetId && !Filter.bByEventType;
}
if (!SamplePtr->bAtLeastOnePacketMatchesFilter && (Filter.bByNetId || Filter.bByEventType))
{
bool bFilterMatch = false;
const uint32 StartPos = 0;
const uint32 EndPos = Packet.ContentSizeInBits;
NetProfilerProvider.EnumeratePacketContentEventsByPosition(ConnectionIndex, ConnectionMode, PacketIndex - 1, StartPos, EndPos, [this, &bFilterMatch, &Filter, &NetProfilerProvider](const Trace::FNetProfilerContentEvent& Event)
{
if (!bFilterMatch)
{
bool bEventMatchesFilter = true;
if (Filter.bByEventType && Filter.EventTypeIndex != Event.EventTypeIndex)
{
bEventMatchesFilter = false;
}
if (bEventMatchesFilter && Filter.bByNetId)
{
uint32 NetId = uint32(-1);
if (Event.ObjectInstanceIndex != 0)
{
NetProfilerProvider.ReadObject(GameInstanceIndex, Event.ObjectInstanceIndex, [&NetId](const Trace::FNetProfilerObjectInstance& ObjectInstance)
{
NetId = ObjectInstance.NetId;
});
}
if (Filter.NetId != NetId)
{
bEventMatchesFilter = false;
}
}
if (bEventMatchesFilter)
{
bFilterMatch = true;
}
}
});
if (bFilterMatch)
{
SamplePtr->bAtLeastOnePacketMatchesFilter = true;
}
}
}
});
}
}
}
}
// Init series with mock data.
if (false)
{
constexpr int32 NumPackets = 100000;
ViewportX.SetMinMaxInterval(0, NumPackets);
const int32 StartIndex = FMath::Max(0, ViewportX.GetValueAtOffset(0.0f));
const int32 EndIndex = FMath::Min(NumPackets, ViewportX.GetValueAtOffset(ViewportX.GetSize()));
for (int32 PacketIndex = StartIndex; PacketIndex < EndIndex; ++PacketIndex)
{
FRandomStream RandomStream((PacketIndex * PacketIndex * PacketIndex) ^ 0x2c2c57ed);
int64 Size = RandomStream.RandRange(0, 2000);
Trace::ENetProfilerDeliveryStatus Status = Trace::ENetProfilerDeliveryStatus::Unknown;
const float Fraction = RandomStream.GetFraction();
if (Fraction < 0.01) // 1%
{
Status = Trace::ENetProfilerDeliveryStatus::Dropped;
}
else if (Fraction < 0.05) // 4%
{
Status = Trace::ENetProfilerDeliveryStatus::Delivered;
}
const double Timestamp = ((double)PacketIndex * 100.0) / (double)NumPackets + RandomStream.GetFraction() * 0.1;
Trace::FNetProfilerPacket Packet;
Packet.TimeStamp = static_cast<Trace::FNetProfilerTimeStamp>(Timestamp);
Packet.SequenceNumber = PacketIndex;
Packet.ContentSizeInBits = Size;
Packet.TotalPacketSizeInBytes = (Size + 7) / 8;
Packet.DeliveryStatus = Status;
Builder.AddPacket(PacketIndex, Packet);
}
}
NumUpdatedPackets += Builder.GetNumAddedPackets();
if (bIsAutoZoomEnabled && ViewportX.GetMaxValue() != PreviousMaxValue)
{
// Forces auto-zoom to be updated in the next Tick(), like after a viewport resize.
AutoZoomViewportSize = 0.0f;
}
Stopwatch.Stop();
UpdateDurationHistory.AddValue(Stopwatch.AccumulatedTime);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
FNetworkPacketSampleRef SPacketView::GetSample(const int32 InPacketIndex)
{
FNetworkPacketSampleRef SampleRef;
SampleRef.Series = PacketSeries;
TSharedPtr<const Trace::IAnalysisSession> Session = FInsightsManager::Get()->GetSession();
if (Session.IsValid())
{
Trace::FAnalysisSessionReadScope SessionReadScope(*Session.Get());
const Trace::INetProfilerProvider& NetProfilerProvider = Trace::ReadNetProfilerProvider(*Session.Get());
if (IsConnectionValid(NetProfilerProvider, GameInstanceIndex, ConnectionIndex, ConnectionMode))
{
const uint32 NumPackets = NetProfilerProvider.GetPacketCount(ConnectionIndex, ConnectionMode);
if (InPacketIndex >= 0 && InPacketIndex < static_cast<int32>(NumPackets))
{
NetProfilerProvider.EnumeratePackets(ConnectionIndex, ConnectionMode, InPacketIndex, InPacketIndex, [InPacketIndex, &SampleRef](const Trace::FNetProfilerPacket& Packet)
{
SampleRef.Sample = MakeShared<FNetworkPacketAggregatedSample>();
SampleRef.Sample->AddPacket(InPacketIndex, Packet);
});
}
}
}
return SampleRef;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
FNetworkPacketSampleRef SPacketView::GetSampleAtMousePosition(float X, float Y)
{
if (!bIsStateDirty)
{
float SampleW = Viewport.GetSampleWidth();
int32 SampleIndex = FMath::FloorToInt(X / SampleW);
if (SampleIndex >= 0)
{
if (PacketSeries->NumAggregatedPackets > 0 &&
SampleIndex < PacketSeries->Samples.Num())
{
const FNetworkPacketAggregatedSample& Sample = PacketSeries->Samples[SampleIndex];
if (Sample.NumPackets > 0)
{
const FAxisViewportDouble& ViewportY = Viewport.GetVerticalAxisViewport();
const float ViewHeight = FMath::RoundToFloat(Viewport.GetHeight());
const float BaselineY = FMath::RoundToFloat(ViewportY.GetOffsetForValue(0.0));
// const float ValueY = FMath::RoundToFloat(ViewportY.GetOffsetForValue(static_cast<double>(Sample.LargestPacket.ContentSizeInBits)));
const float ValueY = FMath::RoundToFloat(ViewportY.GetOffsetForValue(static_cast<double>(Sample.LargestPacket.TotalSizeInBytes * 8)));
constexpr float ToleranceY = 3.0f; // [pixels]
const float BottomY = FMath::Min(ViewHeight, ViewHeight - BaselineY + ToleranceY);
const float TopY = FMath::Max(0.0f, ViewHeight - ValueY - ToleranceY);
if (Y >= TopY && Y < BottomY)
{
return FNetworkPacketSampleRef(PacketSeries, MakeShared<FNetworkPacketAggregatedSample>(Sample));
}
}
}
}
}
return FNetworkPacketSampleRef(nullptr, nullptr);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void SPacketView::SelectSampleAtMousePosition(float X, float Y, const FPointerEvent& MouseEvent)
{
FNetworkPacketSampleRef SampleRef = GetSampleAtMousePosition(X, Y);
if (!SampleRef.IsValid())
{
SampleRef = GetSampleAtMousePosition(X - 1.0f, Y);
}
if (!SampleRef.IsValid())
{
SampleRef = GetSampleAtMousePosition(X + 1.0f, Y);
}
bool bRaiseSelectionChanged = false;
if (SampleRef.IsValid())
{
const int32 SelectedPacketIndex = SampleRef.Sample->LargestPacket.Index;
if (MouseEvent.GetModifierKeys().IsShiftDown())
{
if (SelectedPacketIndex >= LastSelectedPacketIndex)
{
// Extend selection toward right.
if (SelectionStartPacketIndex != LastSelectedPacketIndex ||
SelectionEndPacketIndex != SelectedPacketIndex + 1)
{
SelectionStartPacketIndex = LastSelectedPacketIndex;
SelectionEndPacketIndex = SelectedPacketIndex + 1;
bRaiseSelectionChanged = true;
}
LastSelectedPacketIndex = SelectionStartPacketIndex;
}
else
{
// Extend selection toward left.
if (SelectionEndPacketIndex != SelectedPacketIndex + 1)
{
SelectionStartPacketIndex = SelectedPacketIndex;
SelectionEndPacketIndex = LastSelectedPacketIndex + 1;
bRaiseSelectionChanged = true;
}
LastSelectedPacketIndex = SelectionEndPacketIndex - 1;
}
}
else
{
if (SelectionStartPacketIndex != SelectedPacketIndex ||
SelectionEndPacketIndex != SelectedPacketIndex + 1)
{
SelectionStartPacketIndex = SelectedPacketIndex;
SelectionEndPacketIndex = SelectedPacketIndex + 1;
bRaiseSelectionChanged = true;
}
LastSelectedPacketIndex = SelectedPacketIndex;
}
}
else
{
if (SelectionStartPacketIndex != 0 ||
SelectionEndPacketIndex != 0)
{
SelectionStartPacketIndex = 0;
SelectionEndPacketIndex = 0;
LastSelectedPacketIndex = 0;
bRaiseSelectionChanged = true;
}
}
if (SelectionEndPacketIndex == SelectionStartPacketIndex + 1)
{
if (!SelectedSample.Equals(SampleRef))
{
SelectedSample = SampleRef;
}
}
else
{
SelectedSample.Reset();
}
if (bRaiseSelectionChanged)
{
OnSelectionChanged();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void SPacketView::OnSelectionChanged()
{
if (ProfilerWindow.IsValid())
{
if (SelectedSample.IsValid())
{
const uint32 BitSize = SelectedSample.Sample->LargestPacket.TotalSizeInBytes * 8;
ProfilerWindow->SetSelectedPacket(SelectedSample.Sample->LargestPacket.Index, SelectedSample.Sample->LargestPacket.Index + 1, BitSize);
ProfilerWindow->SetSelectedBitRange(0, BitSize);
}
else
{
ProfilerWindow->SetSelectedPacket(SelectionStartPacketIndex, SelectionEndPacketIndex);
ProfilerWindow->SetSelectedBitRange(0, 0);
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
const TCHAR* StatusToString(Trace::ENetProfilerDeliveryStatus Status)
{
switch (Status)
{
case Trace::ENetProfilerDeliveryStatus::Delivered: return TEXT("Delivered");
case Trace::ENetProfilerDeliveryStatus::Dropped: return TEXT("Dropped");
case Trace::ENetProfilerDeliveryStatus::Unknown:
default: return TEXT("Unknown");
};
}
const TCHAR* AggregatedStatusToString(Trace::ENetProfilerDeliveryStatus Status)
{
switch (Status)
{
case Trace::ENetProfilerDeliveryStatus::Delivered: return TEXT("all packets are Delivered");
case Trace::ENetProfilerDeliveryStatus::Dropped: return TEXT("at least one Dropped packet");
case Trace::ENetProfilerDeliveryStatus::Unknown:
default: return TEXT("Unknown");
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
int32 SPacketView::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const
{
const bool bEnabled = ShouldBeEnabled(bParentEnabled);
const ESlateDrawEffect DrawEffects = bEnabled ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect;
FDrawContext DrawContext(AllottedGeometry, MyCullingRect, InWidgetStyle, DrawEffects, OutDrawElements, LayerId);
const TSharedRef<FSlateFontMeasure> FontMeasureService = FSlateApplication::Get().GetRenderer()->GetFontMeasureService();
FSlateFontInfo SummaryFont = FCoreStyle::GetDefaultFontStyle("Regular", 8);
const FSlateBrush* WhiteBrush = FInsightsStyle::Get().GetBrush("WhiteBrush");
const float ViewWidth = AllottedGeometry.Size.X;
const float ViewHeight = AllottedGeometry.Size.Y;
int32 NumDrawSamples = 0;
//////////////////////////////////////////////////
{
FStopwatch Stopwatch;
Stopwatch.Start();
FPacketViewDrawHelper Helper(DrawContext, Viewport);
Helper.DrawBackground();
// Draw the horizontal axis grid.
DrawHorizontalAxisGrid(DrawContext, WhiteBrush, SummaryFont);
Helper.DrawCached(*PacketSeries);
NumDrawSamples = Helper.GetNumDrawSamples();
// Highlight the selected and/or hovered sample.
bool bIsSelectedAndHovered = SelectedSample.Equals(HoveredSample);
if (SelectedSample.IsValid())
{
Helper.DrawSampleHighlight(*SelectedSample.Sample, bIsSelectedAndHovered ? FPacketViewDrawHelper::EHighlightMode::SelectedAndHovered : FPacketViewDrawHelper::EHighlightMode::Selected);
}
if (HoveredSample.IsValid() && !bIsSelectedAndHovered)
{
Helper.DrawSampleHighlight(*HoveredSample.Sample, FPacketViewDrawHelper::EHighlightMode::Hovered);
}
if (SelectionEndPacketIndex > SelectionStartPacketIndex + 1)
{
Helper.DrawSelection(SelectionStartPacketIndex, SelectionEndPacketIndex);
}
// Draw the vertical axis grid.
DrawVerticalAxisGrid(DrawContext, WhiteBrush, SummaryFont);
// Draw tooltip for hovered sample.
if (HoveredSample.IsValid())
{
if (TooltipOpacity < TooltipDesiredOpacity)
{
TooltipOpacity = TooltipOpacity * 0.9f + TooltipDesiredOpacity * 0.1f;
}
else
{
TooltipOpacity = TooltipDesiredOpacity;
}
const double Precision = 0.01; // 10ms
int NumLines;
FString Text;
uint32 UnusedBits = HoveredSample.Sample->LargestPacket.TotalSizeInBytes * 8 - HoveredSample.Sample->LargestPacket.ContentSizeInBits;
if (HoveredSample.Sample->NumPackets == 1)
{
NumLines = 6;
Text = FString::Format(TEXT("Packet Index: {0}\n"
"Sequence Number: {1}\n"
"Content Size: {2} bits\n"
"Total Size: {3} bytes ({4} unused bits)\n"
"Timestamp: {5}\n"
"Status: {6}"),
{FText::AsNumber(HoveredSample.Sample->LargestPacket.Index).ToString(),
FText::AsNumber(HoveredSample.Sample->LargestPacket.SequenceNumber).ToString(),
FText::AsNumber(HoveredSample.Sample->LargestPacket.ContentSizeInBits).ToString(),
FText::AsNumber(HoveredSample.Sample->LargestPacket.TotalSizeInBytes).ToString(),
FText::AsNumber(UnusedBits).ToString(),
TimeUtils::FormatTimeHMS(HoveredSample.Sample->LargestPacket.TimeStamp, Precision),
::StatusToString(HoveredSample.Sample->LargestPacket.Status)});
}
else
{
NumLines = 9;
Text = FString::Format(TEXT("{0} network packets\n"
"({1})\n"
"Largest Packet\n"
" Index: {2}\n"
" Sequance Number: {3}\n"
" Content Size: {4} bits\n"
" Total Size: {5} bytes ({6} unused bits)\n"
" Timestamp: {7}\n"
" Status: {8}"),
{HoveredSample.Sample->NumPackets,
::AggregatedStatusToString(HoveredSample.Sample->AggregatedStatus),
FText::AsNumber(HoveredSample.Sample->LargestPacket.Index).ToString(),
FText::AsNumber(HoveredSample.Sample->LargestPacket.SequenceNumber).ToString(),
FText::AsNumber(HoveredSample.Sample->LargestPacket.ContentSizeInBits).ToString(),
FText::AsNumber(HoveredSample.Sample->LargestPacket.TotalSizeInBytes).ToString(),
FText::AsNumber(UnusedBits).ToString(),
TimeUtils::FormatTimeHMS(HoveredSample.Sample->LargestPacket.TimeStamp, Precision),
::StatusToString(HoveredSample.Sample->LargestPacket.Status)});
}
FVector2D TextSize = FontMeasureService->Measure(Text, SummaryFont);
const float DX = 2.0f;
const float W2 = TextSize.X / 2 + DX;
const FAxisViewportInt32& ViewportX = Viewport.GetHorizontalAxisViewport();
float X1 = ViewportX.GetOffsetForValue(HoveredSample.Sample->LargestPacket.Index);
float CX = X1 + FMath::RoundToFloat(Viewport.GetSampleWidth() / 2);
if (CX + W2 > ViewportX.GetSize())
{
CX = FMath::RoundToFloat(ViewportX.GetSize() - W2);
}
if (CX - W2 < 0)
{
CX = W2;
}
const float Y = 10.0f;
const float H = 2.0f + 13.0f * NumLines;
DrawContext.DrawBox(CX - W2, Y, 2 * W2, H, WhiteBrush, FLinearColor(0.7, 0.7, 0.7, TooltipOpacity));
DrawContext.LayerId++;
DrawContext.DrawText(CX - W2 + DX, Y + 1.0f, Text, SummaryFont, FLinearColor(0.0, 0.0, 0.0, TooltipOpacity));
DrawContext.LayerId++;
}
Stopwatch.Stop();
DrawDurationHistory.AddValue(Stopwatch.AccumulatedTime);
}
//////////////////////////////////////////////////
const bool bShouldDisplayDebugInfo = FInsightsManager::Get()->IsDebugInfoEnabled();
if (bShouldDisplayDebugInfo)
{
const float MaxFontCharHeight = FontMeasureService->Measure(TEXT("!"), SummaryFont).Y;
const float DbgDY = MaxFontCharHeight;
const float DbgW = 280.0f;
const float DbgH = DbgDY * 5 + 3.0f;
const float DbgX = ViewWidth - DbgW - 20.0f;
float DbgY = 7.0f;
DrawContext.LayerId++;
DrawContext.DrawBox(DbgX - 2.0f, DbgY - 2.0f, DbgW, DbgH, WhiteBrush, FLinearColor(1.0, 1.0, 1.0, 0.9));
DrawContext.LayerId++;
FLinearColor DbgTextColor(0.0, 0.0, 0.0, 0.9);
// Time interval since last OnPaint call.
const uint64 CurrentTime = FPlatformTime::Cycles64();
const uint64 OnPaintDuration = CurrentTime - LastOnPaintTime;
LastOnPaintTime = CurrentTime;
OnPaintDurationHistory.AddValue(OnPaintDuration); // saved for last 32 OnPaint calls
const uint64 AvgOnPaintDuration = OnPaintDurationHistory.ComputeAverage();
const uint64 AvgOnPaintDurationMs = FStopwatch::Cycles64ToMilliseconds(AvgOnPaintDuration);
const double AvgOnPaintFps = AvgOnPaintDurationMs != 0 ? 1.0 / FStopwatch::Cycles64ToSeconds(AvgOnPaintDuration) : 0.0;
const uint64 AvgUpdateDurationMs = FStopwatch::Cycles64ToMilliseconds(UpdateDurationHistory.ComputeAverage());
const uint64 AvgDrawDurationMs = FStopwatch::Cycles64ToMilliseconds(DrawDurationHistory.ComputeAverage());
// Draw performance info.
DrawContext.DrawText(
DbgX, DbgY,
FString::Printf(TEXT("U: %llu ms, D: %llu ms + %llu ms = %llu ms (%d fps)"),
AvgUpdateDurationMs, // caching time
AvgDrawDurationMs, // drawing time
AvgOnPaintDurationMs - AvgDrawDurationMs, // other overhead to OnPaint calls
AvgOnPaintDurationMs, // average time between two OnPaint calls
FMath::RoundToInt(AvgOnPaintFps)), // framerate of OnPaint calls
SummaryFont, DbgTextColor);
DbgY += DbgDY;
// Draw number of draw calls.
DrawContext.DrawText(
DbgX, DbgY,
FString::Printf(TEXT("U: %s packets, D: %s samples"),
*FText::AsNumber(NumUpdatedPackets).ToString(),
*FText::AsNumber(NumDrawSamples).ToString()),
SummaryFont, DbgTextColor);
DbgY += DbgDY;
// Draw viewport's horizontal info.
DrawContext.DrawText(
DbgX, DbgY,
Viewport.GetHorizontalAxisViewport().ToDebugString(TEXT("X"), TEXT("packet")),
SummaryFont, DbgTextColor);
DbgY += DbgDY;
// Draw viewport's vertical info.
DrawContext.DrawText(
DbgX, DbgY,
Viewport.GetVerticalAxisViewport().ToDebugString(TEXT("Y")),
SummaryFont, DbgTextColor);
DbgY += DbgDY;
// Draw connection info.
DrawContext.DrawText(
DbgX, DbgY,
FString::Printf(TEXT("Game Instance %d, Connection %d (%s)"),
GameInstanceIndex,
ConnectionIndex,
(ConnectionMode == Trace::ENetProfilerConnectionMode::Outgoing) ? TEXT("Outgoing") : TEXT("Incoming")),
SummaryFont, DbgTextColor);
DbgY += DbgDY;
}
return SCompoundWidget::OnPaint(Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled && IsEnabled());
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void SPacketView::DrawVerticalAxisGrid(FDrawContext& DrawContext, const FSlateBrush* Brush, const FSlateFontInfo& Font) const
{
const FAxisViewportDouble& ViewportY = Viewport.GetVerticalAxisViewport();
const float RoundedViewHeight = FMath::RoundToFloat(ViewportY.GetSize());
constexpr float MinDY = 32.0f; // min vertical distance between horizontal grid lines
const double TopValue = ViewportY.GetValueAtOffset(RoundedViewHeight);
const double GridValue = ViewportY.GetValueAtOffset(MinDY);
const double BottomValue = ViewportY.GetValueAtOffset(0.0f);
const double Delta = GridValue - BottomValue;
if (Delta > 0.0)
{
int64 DeltaBits = static_cast<int64>(Delta);
if (DeltaBits <= 0)
{
DeltaBits = 1;
}
// Compute rounding based on magnitude of visible range of values (Delta).
int64 Power10 = 1;
int64 Delta10 = DeltaBits;
while (Delta10 > 0)
{
Delta10 /= 10;
Power10 *= 10;
}
if (Power10 >= 100)
{
Power10 /= 100;
}
else
{
Power10 = 1;
}
const double Grid = static_cast<double>(((DeltaBits + Power10 - 1) / Power10) * Power10); // next value divisible with a multiple of 10
const double StartValue = FMath::GridSnap(BottomValue, Grid);
const float ViewWidth = Viewport.GetWidth();
const FLinearColor GridColor(0.0f, 0.0f, 0.0f, 0.1f);
const FLinearColor TextBgColor(0.05f, 0.05f, 0.05f, 1.0f);
const FLinearColor TextColor(1.0f, 1.0f, 1.0f, 1.0f);
const TSharedRef<FSlateFontMeasure> FontMeasureService = FSlateApplication::Get().GetRenderer()->GetFontMeasureService();
for (double Value = StartValue; Value < TopValue; Value += Grid)
{
const float Y = RoundedViewHeight - FMath::RoundToFloat(ViewportY.GetOffsetForValue(Value));
// Draw horizontal grid line.
DrawContext.DrawBox(0, Y, ViewWidth, 1, Brush, GridColor);
const int64 ValueBits = static_cast<int64>(Value);
const FString Text = (ValueBits == 0) ? TEXT("0") : FString::Format(TEXT("{0} bits"), {FText::AsNumber(ValueBits).ToString()});
const FVector2D TextSize = FontMeasureService->Measure(Text, Font);
constexpr float TextH = 14.0f;
// Draw background for value text.
DrawContext.DrawBox(ViewWidth - TextSize.X - 4.0f, Y - TextH, TextSize.X + 4.0f, TextH, Brush, TextBgColor);
// Draw value text.
DrawContext.DrawText(ViewWidth - TextSize.X - 2.0f, Y - TextH + 1.0f, Text, Font, TextColor);
}
DrawContext.LayerId++;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void SPacketView::DrawHorizontalAxisGrid(FDrawContext& DrawContext, const FSlateBrush* Brush, const FSlateFontInfo& Font) const
{
const FAxisViewportInt32& ViewportX = Viewport.GetHorizontalAxisViewport();
const float RoundedViewWidth = FMath::RoundToFloat(ViewportX.GetSize());
constexpr float MinDX = 100.0f; // min horizontal distance between vertical grid lines
const int32 LeftIndex = ViewportX.GetValueAtOffset(0.0f);
const int32 GridIndex = ViewportX.GetValueAtOffset(MinDX);
const int32 RightIndex = ViewportX.GetValueAtOffset(RoundedViewWidth);
const int32 Delta = GridIndex - LeftIndex;
if (Delta > 0)
{
// Compute rounding based on magnitude of visible range of samples (Delta).
int32 Power10 = 1;
int32 Delta10 = Delta;
while (Delta10 > 0)
{
Delta10 /= 10;
Power10 *= 10;
}
if (Power10 >= 100)
{
Power10 /= 100;
}
else
{
Power10 = 1;
}
const int32 Grid = ((Delta + Power10 - 1) / Power10) * Power10; // next value divisible with a multiple of 10
// Skip grid lines for negative indices.
double StartIndex = ((LeftIndex + Grid - 1) / Grid) * Grid;
while (StartIndex < 0)
{
StartIndex += Grid;
}
const float ViewHeight = Viewport.GetHeight();
const FLinearColor GridColor(0.0f, 0.0f, 0.0f, 0.1f);
// const FLinearColor TextBgColor(0.05f, 0.05f, 0.05f, 1.0f);
// const FLinearColor TextColor(1.0f, 1.0f, 1.0f, 1.0f);
const FLinearColor TopTextColor(1.0f, 1.0f, 1.0f, 0.7f);
// const TSharedRef<FSlateFontMeasure> FontMeasureService = FSlateApplication::Get().GetRenderer()->GetFontMeasureService();
for (int32 Index = StartIndex; Index < RightIndex; Index += Grid)
{
const float X = FMath::RoundToFloat(ViewportX.GetOffsetForValue(Index));
// Draw vertical grid line.
DrawContext.DrawBox(X, 0, 1, ViewHeight, Brush, GridColor);
const FString Text = FText::AsNumber(Index).ToString();
// const FVector2D TextSize = FontMeasureService->Measure(Text, Font);
// constexpr float TextH = 14.0f;
// Draw background for index text.
// DrawContext.DrawBox(X, ViewHeight - TextH, TextSize.X + 4.0f, TextH, Brush, TextBgColor);
// Draw index text.
// DrawContext.DrawText(X + 2.0f, ViewHeight - TextH + 1.0f, Text, Font, TextColor);
DrawContext.DrawText(X + 2.0f, 10.0f, Text, Font, TopTextColor);
}
DrawContext.LayerId++;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
FReply SPacketView::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
FReply Reply = FReply::Unhandled();
MousePositionOnButtonDown = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());
ViewportPosXOnButtonDown = Viewport.GetHorizontalAxisViewport().GetPos();
if (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton)
{
bIsLMB_Pressed = true;
// Capture mouse.
Reply = FReply::Handled().CaptureMouse(SharedThis(this));
}
else if (MouseEvent.GetEffectingButton() == EKeys::RightMouseButton)
{
bIsRMB_Pressed = true;
// Capture mouse, so we can scroll outside this widget.
Reply = FReply::Handled().CaptureMouse(SharedThis(this));
}
return Reply;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
FReply SPacketView::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
FReply Reply = FReply::Unhandled();
MousePositionOnButtonUp = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());
const bool bIsValidForMouseClick = MousePositionOnButtonUp.Equals(MousePositionOnButtonDown, MOUSE_SNAP_DISTANCE);
if (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton)
{
if (bIsLMB_Pressed)
{
if (bIsScrolling)
{
bIsScrolling = false;
CursorType = ECursorType::Default;
}
else if (bIsValidForMouseClick)
{
SelectSampleAtMousePosition(MousePositionOnButtonUp.X, MousePositionOnButtonUp.Y, MouseEvent);
}
bIsLMB_Pressed = false;
// Release the mouse.
Reply = FReply::Handled().ReleaseMouseCapture();
}
}
else if (MouseEvent.GetEffectingButton() == EKeys::RightMouseButton)
{
if (bIsRMB_Pressed)
{
if (bIsScrolling)
{
bIsScrolling = false;
CursorType = ECursorType::Default;
}
else if (bIsValidForMouseClick)
{
ShowContextMenu(MouseEvent);
}
bIsRMB_Pressed = false;
// Release mouse as we no longer scroll.
Reply = FReply::Handled().ReleaseMouseCapture();
}
}
return Reply;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
FReply SPacketView::OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
FReply Reply = FReply::Unhandled();
MousePosition = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());
if (!MouseEvent.GetCursorDelta().IsZero())
{
if (MouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton) ||
MouseEvent.IsMouseButtonDown(EKeys::RightMouseButton))
{
if (HasMouseCapture())
{
if (!bIsScrolling)
{
bIsScrolling = true;
CursorType = ECursorType::Hand;
HoveredSample.Reset();
}
FAxisViewportInt32& ViewportX = Viewport.GetHorizontalAxisViewport();
const float PosX = ViewportPosXOnButtonDown + (MousePositionOnButtonDown.X - MousePosition.X);
ViewportX.ScrollAtValue(ViewportX.GetValueAtPos(PosX)); // align viewport position with sample
UpdateHorizontalScrollBar();
bIsStateDirty = true;
}
}
else
{
if (!HoveredSample.IsValid())
{
TooltipOpacity = 0.0f;
}
HoveredSample = GetSampleAtMousePosition(MousePosition.X, MousePosition.Y);
if (!HoveredSample.IsValid())
{
HoveredSample = GetSampleAtMousePosition(MousePosition.X - 1.0f, MousePosition.Y);
}
if (!HoveredSample.IsValid())
{
HoveredSample = GetSampleAtMousePosition(MousePosition.X + 1.0f, MousePosition.Y);
}
}
Reply = FReply::Handled();
}
return Reply;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void SPacketView::OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void SPacketView::OnMouseLeave(const FPointerEvent& MouseEvent)
{
if (!HasMouseCapture())
{
bIsLMB_Pressed = false;
bIsRMB_Pressed = false;
HoveredSample.Reset();
CursorType = ECursorType::Default;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
FReply SPacketView::OnMouseWheel(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
MousePosition = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());
if (MouseEvent.GetModifierKeys().IsShiftDown())
{
FAxisViewportDouble& ViewportY = Viewport.GetVerticalAxisViewport();
// Zoom in/out vertically.
const float Delta = MouseEvent.GetWheelDelta();
constexpr float ZoomStep = 0.25f; // as percent
float ScaleY;
if (Delta > 0)
{
ScaleY = ViewportY.GetScale() * FMath::Pow(1.0f + ZoomStep, Delta);
}
else
{
ScaleY = ViewportY.GetScale() * FMath::Pow(1.0f / (1.0f + ZoomStep), -Delta);
}
ViewportY.SetScale(ScaleY);
// UpdateVerticalScrollBar();
}
else // if (MouseEvent.GetModifierKeys().IsControlDown())
{
// Zoom in/out horizontally.
const float Delta = MouseEvent.GetWheelDelta();
ZoomHorizontally(Delta, MousePosition.X);
}
return FReply::Handled();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
FReply SPacketView::OnMouseButtonDoubleClick(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
return FReply::Unhandled();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
FCursorReply SPacketView::OnCursorQuery(const FGeometry& MyGeometry, const FPointerEvent& CursorEvent) const
{
FCursorReply CursorReply = FCursorReply::Unhandled();
if (CursorType == ECursorType::Arrow)
{
CursorReply = FCursorReply::Cursor(EMouseCursor::ResizeLeftRight);
}
else if (CursorType == ECursorType::Hand)
{
CursorReply = FCursorReply::Cursor(EMouseCursor::GrabHand);
}
return CursorReply;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
FReply SPacketView::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent)
{
if (InKeyEvent.GetKey() == EKeys::A)
{
if (InKeyEvent.GetModifierKeys().IsControlDown())
{
const FAxisViewportInt32& ViewportX = Viewport.GetHorizontalAxisViewport();
const int32 LastPacketIndex = ViewportX.GetMaxValue();
// Select all packets.
SelectionStartPacketIndex = 0;
SelectionEndPacketIndex = LastPacketIndex;
LastSelectedPacketIndex = 0;
SelectedSample.Reset();
if (SelectionEndPacketIndex == SelectionStartPacketIndex + 1)
{
SelectedSample = GetSample(SelectionStartPacketIndex);
}
OnSelectionChanged();
return FReply::Handled();
}
}
else if (InKeyEvent.GetKey() == EKeys::Left)
{
if (InKeyEvent.GetModifierKeys().IsShiftDown())
{
if (InKeyEvent.GetModifierKeys().IsControlDown())
{
ShrinkRightSideOfSelectedInterval();
}
else
{
ExtendLeftSideOfSelectedInterval();
}
}
else
{
SelectPreviousPacket();
}
return FReply::Handled();
}
else if (InKeyEvent.GetKey() == EKeys::Right)
{
if (InKeyEvent.GetModifierKeys().IsShiftDown())
{
if (InKeyEvent.GetModifierKeys().IsControlDown())
{
ShrinkLeftSideOfSelectedInterval();
}
else
{
ExtendRightSideOfSelectedInterval();
}
}
else
{
SelectNextPacket();
}
return FReply::Handled();
}
return SCompoundWidget::OnKeyDown(MyGeometry, InKeyEvent);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void SPacketView::EnsurePacketIsVisible(const int InPacketIndex)
{
FAxisViewportInt32& ViewportX = Viewport.GetHorizontalAxisViewport();
const float LeftX = ViewportX.GetPosForValue(InPacketIndex);
const float RightX = LeftX + ViewportX.GetSampleSize();
if (RightX > ViewportX.GetPos() + ViewportX.GetSize())
{
const int32 VisibleSampleCount = FMath::FloorToInt(ViewportX.GetSize() / ViewportX.GetSampleSize());
ViewportX.ScrollAtValue(InPacketIndex - VisibleSampleCount + 1);
UpdateHorizontalScrollBar();
bIsStateDirty = true;
}
if (LeftX < ViewportX.GetPos())
{
ViewportX.ScrollAtValue(InPacketIndex);
UpdateHorizontalScrollBar();
bIsStateDirty = true;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void SPacketView::SetSelectedPacket(const int32 InPacketIndex)
{
const FAxisViewportInt32& ViewportX = Viewport.GetHorizontalAxisViewport();
const int32 PacketCount = ViewportX.GetMaxValue();
if (PacketCount > 0)
{
const int32 PacketIndex = FMath::Min(FMath::Max(0, InPacketIndex), PacketCount - 1);
SelectionStartPacketIndex = PacketIndex;
SelectionEndPacketIndex = PacketIndex + 1;
LastSelectedPacketIndex = PacketIndex;
EnsurePacketIsVisible(LastSelectedPacketIndex);
UpdateSelectedSample();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void SPacketView::SelectPacketBySequenceNumber(const uint32 InSequenceNumber)
{
// Find the PacketIndex from sequence number
TSharedPtr<const Trace::IAnalysisSession> Session = FInsightsManager::Get()->GetSession();
if (Session.IsValid())
{
Trace::FAnalysisSessionReadScope SessionReadScope(*Session.Get());
const Trace::INetProfilerProvider& NetProfilerProvider = Trace::ReadNetProfilerProvider(*Session.Get());
const int32 PacketId = NetProfilerProvider.FindPacketIndexFromPacketSequence(ConnectionIndex, ConnectionMode, InSequenceNumber);
if (PacketId != -1)
{
SetSelectedPacket(PacketId);
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void SPacketView::SelectPreviousPacket()
{
const FAxisViewportInt32& ViewportX = Viewport.GetHorizontalAxisViewport();
const int32 PacketCount = ViewportX.GetMaxValue();
if (PacketCount > 0)
{
if (SelectionStartPacketIndex >= SelectionEndPacketIndex) // no selection?
{
// Select the first packet.
SelectionStartPacketIndex = 0;
SelectionEndPacketIndex = 1;
}
else
{
if (SelectionStartPacketIndex > 0)
{
// Select the previous packet.
SelectionStartPacketIndex--;
}
SelectionEndPacketIndex = SelectionStartPacketIndex + 1;
}
LastSelectedPacketIndex = SelectionStartPacketIndex;
EnsurePacketIsVisible(LastSelectedPacketIndex);
UpdateSelectedSample();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void SPacketView::SelectNextPacket()
{
const FAxisViewportInt32& ViewportX = Viewport.GetHorizontalAxisViewport();
const int32 PacketCount = ViewportX.GetMaxValue();
if (PacketCount > 0)
{
if (SelectionStartPacketIndex >= SelectionEndPacketIndex) // no selection?
{
// Select the last packet.
SelectionStartPacketIndex = PacketCount - 1;
SelectionEndPacketIndex = PacketCount;
}
else
{
if (SelectionEndPacketIndex < PacketCount)
{
// Select the next packet.
SelectionEndPacketIndex++;
}
SelectionStartPacketIndex = SelectionEndPacketIndex - 1;
}
LastSelectedPacketIndex = SelectionStartPacketIndex;
EnsurePacketIsVisible(LastSelectedPacketIndex);
UpdateSelectedSample();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void SPacketView::ExtendLeftSideOfSelectedInterval()
{
const FAxisViewportInt32& ViewportX = Viewport.GetHorizontalAxisViewport();
const int32 PacketCount = ViewportX.GetMaxValue();
if (PacketCount > 0)
{
bool bSelectionChanged = false;
if (SelectionStartPacketIndex >= SelectionEndPacketIndex) // no selection?
{
// Select the first packet.
SelectionStartPacketIndex = 0;
SelectionEndPacketIndex = 1;
bSelectionChanged = true;
}
else
{
if (SelectionStartPacketIndex > 0)
{
// Extend left side of selected interval.
SelectionStartPacketIndex--;
bSelectionChanged = true;
}
}
if (bSelectionChanged)
{
LastSelectedPacketIndex = SelectionStartPacketIndex;
EnsurePacketIsVisible(LastSelectedPacketIndex);
UpdateSelectedSample();
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void SPacketView::ShrinkLeftSideOfSelectedInterval()
{
const FAxisViewportInt32& ViewportX = Viewport.GetHorizontalAxisViewport();
const int32 PacketCount = ViewportX.GetMaxValue();
if (PacketCount > 0)
{
if (SelectionStartPacketIndex + 1 < SelectionEndPacketIndex)
{
// Shrink left side of selected interval.
SelectionStartPacketIndex++;
LastSelectedPacketIndex = SelectionStartPacketIndex;
EnsurePacketIsVisible(LastSelectedPacketIndex);
UpdateSelectedSample();
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void SPacketView::ExtendRightSideOfSelectedInterval()
{
const FAxisViewportInt32& ViewportX = Viewport.GetHorizontalAxisViewport();
const int32 PacketCount = ViewportX.GetMaxValue();
if (PacketCount > 0)
{
bool bSelectionChanged = false;
if (SelectionStartPacketIndex >= SelectionEndPacketIndex) // no selection?
{
// Select the last packet.
SelectionStartPacketIndex = PacketCount - 1;
SelectionEndPacketIndex = PacketCount;
bSelectionChanged = true;
}
else
{
if (SelectionEndPacketIndex < PacketCount)
{
// Extend right side of selected interval.
SelectionEndPacketIndex++;
bSelectionChanged = true;
}
}
if (bSelectionChanged)
{
LastSelectedPacketIndex = SelectionEndPacketIndex - 1;
EnsurePacketIsVisible(LastSelectedPacketIndex);
UpdateSelectedSample();
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void SPacketView::ShrinkRightSideOfSelectedInterval()
{
const FAxisViewportInt32& ViewportX = Viewport.GetHorizontalAxisViewport();
const int32 PacketCount = ViewportX.GetMaxValue();
if (PacketCount > 0)
{
if (SelectionEndPacketIndex - 1 > SelectionStartPacketIndex)
{
// Shrink right side of selected interval.
SelectionEndPacketIndex--;
LastSelectedPacketIndex = SelectionEndPacketIndex - 1;
EnsurePacketIsVisible(LastSelectedPacketIndex);
UpdateSelectedSample();
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void SPacketView::UpdateSelectedSample()
{
SelectedSample.Reset();
if (SelectionEndPacketIndex == SelectionStartPacketIndex + 1)
{
SelectedSample = GetSample(SelectionStartPacketIndex);
}
OnSelectionChanged();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void SPacketView::ShowContextMenu(const FPointerEvent& MouseEvent)
{
const bool bShouldCloseWindowAfterMenuSelection = true;
FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, NULL);
MenuBuilder.BeginSection("Misc");
{
FUIAction Action_AutoZoom(
FExecuteAction::CreateSP(this, &SPacketView::ContextMenu_AutoZoom_Execute),
FCanExecuteAction::CreateSP(this, &SPacketView::ContextMenu_AutoZoom_CanExecute),
FIsActionChecked::CreateSP(this, &SPacketView::ContextMenu_AutoZoom_IsChecked));
MenuBuilder.AddMenuEntry(
LOCTEXT("ContextMenu_AutoZoom", "Auto Zoom"),
LOCTEXT("ContextMenu_AutoZoom_Desc", "Enable auto zoom. Makes entire graph series to fit into view."),
FSlateIcon(),
Action_AutoZoom,
NAME_None,
EUserInterfaceActionType::ToggleButton);
}
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);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void SPacketView::ContextMenu_AutoZoom_Execute()
{
bIsAutoZoomEnabled = !bIsAutoZoomEnabled;
if (bIsAutoZoomEnabled)
{
AutoZoom();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool SPacketView::ContextMenu_AutoZoom_CanExecute()
{
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool SPacketView::ContextMenu_AutoZoom_IsChecked()
{
return bIsAutoZoomEnabled;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void SPacketView::AutoZoom()
{
FAxisViewportInt32& ViewportX = Viewport.GetHorizontalAxisViewport();
AutoZoomViewportPos = ViewportX.GetMinPos();
ViewportX.ScrollAtPos(AutoZoomViewportPos);
AutoZoomViewportSize = ViewportX.GetSize();
if (AutoZoomViewportSize > 0.0f &&
ViewportX.GetMaxValue() - ViewportX.GetMinValue() > 0)
{
float DX = ViewportX.GetMaxPos() - ViewportX.GetMinPos();
// Auto zoom in.
while (DX < AutoZoomViewportSize)
{
const float OldScale = ViewportX.GetScale();
ViewportX.RelativeZoomWithFixedOffset(+0.1f, 0.0f);
ViewportX.ScrollAtPos(AutoZoomViewportPos);
DX = ViewportX.GetMaxPos() - ViewportX.GetMinPos();
if (OldScale == ViewportX.GetScale())
{
break;
}
}
// Auto zoom out (until entire session frame range fits into view).
while (DX > AutoZoomViewportSize)
{
const float OldScale = ViewportX.GetScale();
ViewportX.RelativeZoomWithFixedOffset(-0.1f, 0.0f);
ViewportX.ScrollAtPos(AutoZoomViewportPos);
DX = ViewportX.GetMaxPos() - ViewportX.GetMinPos();
if (OldScale == ViewportX.GetScale())
{
break;
}
}
}
AutoZoomViewportScale = ViewportX.GetScale();
UpdateHorizontalScrollBar();
bIsStateDirty = true;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void SPacketView::BindCommands()
{
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void SPacketView::HorizontalScrollBar_OnUserScrolled(float ScrollOffset)
{
FAxisViewportInt32& ViewportX = Viewport.GetHorizontalAxisViewport();
ViewportX.OnUserScrolled(HorizontalScrollBar, ScrollOffset);
bIsStateDirty = true;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void SPacketView::UpdateHorizontalScrollBar()
{
FAxisViewportInt32& ViewportX = Viewport.GetHorizontalAxisViewport();
ViewportX.UpdateScrollBar(HorizontalScrollBar);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void SPacketView::ZoomHorizontally(const float Delta, const float X)
{
FAxisViewportInt32& ViewportX = Viewport.GetHorizontalAxisViewport();
ViewportX.RelativeZoomWithFixedOffset(Delta, X);
ViewportX.ScrollAtValue(ViewportX.GetValueAtPos(ViewportX.GetPos())); // align viewport position with sample
UpdateHorizontalScrollBar();
bIsStateDirty = true;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
#undef LOCTEXT_NAMESPACE