Files
UnrealEngine/Engine/Source/Developer/TraceInsightsFrontend/Private/InsightsFrontend/Widgets/SConnectionWindow.cpp
2025-05-18 13:04:45 +08:00

436 lines
12 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SConnectionWindow.h"
#include "Async/TaskGraphInterfaces.h"
#include "Internationalization/Internationalization.h"
#include "SlateOptMacros.h"
#include "SocketSubsystem.h"
#include "Styling/StyleColors.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Input/SEditableTextBox.h"
#include "Widgets/Layout/SBorder.h"
#include "Widgets/Layout/SBox.h"
#include "Widgets/Notifications/SNotificationList.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/SOverlay.h"
#include "Widgets/Text/STextBlock.h"
// TraceAnalysis
#include "Trace/ControlClient.h"
#include "Trace/StoreConnection.h"
// TraceInsightsCore
#include "InsightsCore/Version.h"
// TraceInsightsFrontend
#include "InsightsFrontend/Common/InsightsFrontendStyle.h"
#include "InsightsFrontend/Common/Log.h"
#define LOCTEXT_NAMESPACE "UE::Insights::SConnectionWindow"
namespace UE::Insights
{
// Should match GDefaultChannels (from Runtime\Core\Private\ProfilingDebugging\TraceAuxiliary.cpp).
// We cannot use GDefaultChannels directly as UE_TRACE_ENABLED may be off.
static const TCHAR* GInsightsDefaultChannelPreset = TEXT("cpu,gpu,frame,log,bookmark,screenshot,region");
////////////////////////////////////////////////////////////////////////////////////////////////////
SConnectionWindow::SConnectionWindow()
{
}
////////////////////////////////////////////////////////////////////////////////////////////////////
SConnectionWindow::~SConnectionWindow()
{
if (ConnectTask && !ConnectTask->IsComplete())
{
FTaskGraphInterface::Get().WaitUntilTaskCompletes(ConnectTask);
ConnectTask = nullptr;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SConnectionWindow::Construct(const FArguments& InArgs, TSharedRef<UE::Trace::FStoreConnection> InTraceStoreConnection)
{
TraceStoreConnection = InTraceStoreConnection;
ChildSlot
[
SNew(SOverlay)
// Version
+ SOverlay::Slot()
.HAlign(HAlign_Right)
.VAlign(VAlign_Top)
.Padding(0.0f, -16.0f, 4.0f, 0.0f)
[
SNew(STextBlock)
.Clipping(EWidgetClipping::ClipToBoundsWithoutIntersecting)
.Text(FText::FromString(TEXT(UNREAL_INSIGHTS_VERSION_STRING_EX)))
.ColorAndOpacity(FLinearColor(0.15f, 0.15f, 0.15f, 1.0f))
]
+ SOverlay::Slot()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
.Padding(0.0f, 0.0f, 0.0f, 0.0f)
[
SNew(SBox)
[
SNew(SBorder)
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
.Padding(0.0f)
.BorderImage(FAppStyle::Get().GetBrush("WhiteBrush"))
.BorderBackgroundColor(FSlateColor(EStyleColor::Panel))
]
]
// Overlay slot for the main window area
+ SOverlay::Slot()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
[
SAssignNew(MainContentPanel, SVerticalBox)
+ SVerticalBox::Slot()
.HAlign(HAlign_Fill)
.AutoHeight()
.Padding(3.0f, 3.0f)
[
ConstructConnectPanel()
]
]
];
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
////////////////////////////////////////////////////////////////////////////////////////////////////
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
TSharedRef<SWidget> SConnectionWindow::ConstructConnectPanel()
{
FText InitialChannelsExampleText = FText::FromString(FString::Printf(TEXT("default,counter,stats,file,loadtime,assetloadtime,task\ndefault=%s"), GInsightsDefaultChannelPreset));
TSharedRef<SWidget> Widget = SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.HAlign(HAlign_Fill)
.Padding(12.0f, 12.0f, 12.0f, 0.0f)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
[
SNew(SBox)
.MinDesiredWidth(180.0f)
.HAlign(HAlign_Right)
[
SNew(STextBlock)
.Text(LOCTEXT("TraceRecorderAddressText", "Trace recorder IP address"))
]
]
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
.VAlign(VAlign_Center)
.Padding(6.0f, 0.0f, 0.0f, 0.0f)
[
SNew(SBox)
.MinDesiredWidth(120.0f)
[
SAssignNew(TraceRecorderAddressTextBox, SEditableTextBox)
]
]
]
+ SVerticalBox::Slot()
.AutoHeight()
.HAlign(HAlign_Fill)
.Padding(12.0f, 8.0f, 12.0f, 0.0f)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
[
SNew(SBox)
.MinDesiredWidth(180.0f)
.HAlign(HAlign_Right)
[
SNew(STextBlock)
.Text(LOCTEXT("RunningInstanceAddressText", "Running instance IP address"))
]
]
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
.VAlign(VAlign_Center)
.Padding(6.0f, 0.0f, 0.0f, 0.0f)
[
SNew(SBox)
.MinDesiredWidth(120.0f)
[
SAssignNew(RunningInstanceAddressTextBox, SEditableTextBox)
]
]
]
+ SVerticalBox::Slot()
.AutoHeight()
.HAlign(HAlign_Fill)
.Padding(12.0f, 8.0f, 12.0f, 0.0f)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
[
SNew(SBox)
.MinDesiredWidth(180.0f)
.HAlign(HAlign_Right)
[
SNew(STextBlock)
.Text(LOCTEXT("InitialChannelsText", "Initial channels"))
]
]
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
.VAlign(VAlign_Center)
.Padding(6.0f, 0.0f, 0.0f, 0.0f)
[
SNew(SBox)
.MinDesiredWidth(120.0f)
[
SAssignNew(ChannelsTextBox, SEditableTextBox)
]
]
]
+ SVerticalBox::Slot()
.AutoHeight()
.HAlign(HAlign_Fill)
.Padding(198.0f, 4.0f, 12.0f, 0.0f)
[
SNew(STextBlock)
.Text(LOCTEXT("InitialChannelsNoteText", "Comma-separated list of channels/presets to enable when connected."))
]
+ SVerticalBox::Slot()
.AutoHeight()
.HAlign(HAlign_Fill)
.Padding(198.0f, 2.0f, 12.0f, 0.0f)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Top)
.Padding(FMargin(0.0f, 4.0f, 0.0f, 0.0f))
[
SNew(STextBlock)
.Text(LOCTEXT("InitialChannelsExamplesTitle", "Examples"))
]
+ SHorizontalBox::Slot()
.Padding(6.0f, 0.0f, 0.0f, 0.0f)
[
SNew(SEditableTextBox)
.IsReadOnly(true)
.Text(InitialChannelsExampleText)
]
]
+ SVerticalBox::Slot()
.AutoHeight()
.HAlign(HAlign_Fill)
.Padding(198.0f, 2.0f, 12.0f, 0.0f)
[
SNew(STextBlock)
.Text(LOCTEXT("InitialChannelsNote2Text", "Some channels/presets (like \"memory\") cannot be enabled on late connections."))
]
+ SVerticalBox::Slot()
.AutoHeight()
.HAlign(HAlign_Fill)
.Padding(12.0f, 8.0f, 12.0f, 12.0f)
[
SNew(SBox)
.HAlign(HAlign_Right)
[
SNew(SButton)
.ButtonStyle(&FAppStyle::Get().GetWidgetStyle<FButtonStyle>("PrimaryButton"))
.Text(LOCTEXT("Connect", "Connect"))
.ToolTipText(LOCTEXT("ConnectToolTip", "Late connect the running instance at specified address with the trace recorder."))
.OnClicked(this, &SConnectionWindow::Connect_OnClicked)
.IsEnabled_Lambda([this]() { return !bIsConnecting; })
]
]
// Notification area overlay
+ SVerticalBox::Slot()
.FillHeight(1.0f)
.HAlign(HAlign_Right)
.VAlign(VAlign_Bottom)
.Padding(16.0f)
[
SAssignNew(NotificationList, SNotificationList)
];
const FText LocalHost = FText::FromString(TEXT("127.0.0.1"));
if (TraceStoreConnection->IsLocalHost())
{
TSharedPtr<FInternetAddr> RecorderAddr;
if (ISocketSubsystem* Sockets = ISocketSubsystem::Get())
{
bool bCanBindAll = false;
RecorderAddr = Sockets->GetLocalHostAddr(*GLog, bCanBindAll);
}
if (RecorderAddr.IsValid())
{
const FString RecorderAddrStr = RecorderAddr->ToString(false);
TraceRecorderAddressTextBox->SetText(FText::FromString(RecorderAddrStr));
}
else
{
TraceRecorderAddressTextBox->SetText(LocalHost);
}
}
else // remote trace store
{
uint32 StoreAddress = 0;
uint32 StorePort = 0;
if (TraceStoreConnection->GetStoreAddressAndPort(StoreAddress, StorePort))
{
const FString RecorderAddrStr = FString::Printf(TEXT("%u.%u.%u.%u"), (StoreAddress >> 24) & 0xFF, (StoreAddress >> 16) & 0xFF, (StoreAddress >> 8) & 0xFF, StoreAddress & 0xFF);
TraceRecorderAddressTextBox->SetText(FText::FromString(RecorderAddrStr));
}
else
{
TraceRecorderAddressTextBox->SetText(FText::FromString(TraceStoreConnection->GetLastStoreHost()));
}
}
RunningInstanceAddressTextBox->SetText(LocalHost);
ChannelsTextBox->SetText(FText::FromStringView(TEXT("default")));
return Widget;
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
////////////////////////////////////////////////////////////////////////////////////////////////////
FReply SConnectionWindow::Connect_OnClicked()
{
FText TraceRecorderAddressText = TraceRecorderAddressTextBox->GetText();
if (TraceRecorderAddressText.IsEmptyOrWhitespace())
{
// nothing to do
return FReply::Handled();
}
const FString& TraceRecorderAddressStr = TraceRecorderAddressText.ToString();
FText RunningInstanceAddressText = RunningInstanceAddressTextBox->GetText();
if (RunningInstanceAddressText.IsEmptyOrWhitespace())
{
// nothing to do
return FReply::Handled();
}
const FString& RunningInstanceAddressStr = RunningInstanceAddressText.ToString();
FGraphEventArray Prerequisites;
FGraphEventArray* PrerequisitesPtr = nullptr;
if (ConnectTask.IsValid())
{
Prerequisites.Add(ConnectTask);
PrerequisitesPtr = &Prerequisites;
}
const FString ChannelsExpandedStr = ChannelsTextBox->GetText().ToString().Replace(TEXT("default"), GInsightsDefaultChannelPreset);
FGraphEventRef PreConnectTask = FFunctionGraphTask::CreateAndDispatchWhenReady(
[this, TraceRecorderAddressStr, RunningInstanceAddressStr, ChannelsExpandedStr]
{
bIsConnecting = true;
UE_LOG(LogInsightsFrontend, Log, TEXT("[Connection] Try connecting to \"%s\"..."), *RunningInstanceAddressStr);
UE::Trace::FControlClient ControlClient;
FString IPStr, PortStr;
RunningInstanceAddressStr.Split(TEXT(":"), &IPStr, &PortStr);
uint16 Port = 1985;
if (!PortStr.IsEmpty())
{
Port = uint16(FCString::Atoi(*PortStr));
}
else
{
IPStr = RunningInstanceAddressStr;
}
if (ControlClient.Connect(*IPStr, Port))
{
UE_LOG(LogInsightsFrontend, Log, TEXT("[Connection] SendSendTo(\"%s\")..."), *TraceRecorderAddressStr);
ControlClient.SendSendTo(*TraceRecorderAddressStr);
UE_LOG(LogInsightsFrontend, Log, TEXT("[Connection] ToggleChannel(\"%s\")..."), *ChannelsExpandedStr);
ControlClient.SendToggleChannel(*ChannelsExpandedStr, true);
bIsConnectedSuccessfully = true;
}
else
{
bIsConnectedSuccessfully = false;
}
},
TStatId{}, PrerequisitesPtr, ENamedThreads::AnyBackgroundThreadNormalTask);
ConnectTask = FFunctionGraphTask::CreateAndDispatchWhenReady(
[this, RunningInstanceAddressStr]
{
if (bIsConnectedSuccessfully)
{
UE_LOG(LogInsightsFrontend, Log, TEXT("[Connection] Successfully connected."));
FNotificationInfo NotificationInfo(FText::Format(LOCTEXT("ConnectSuccess", "Successfully connected to \"{0}\"!"), FText::FromString(RunningInstanceAddressStr)));
NotificationInfo.bFireAndForget = false;
NotificationInfo.bUseLargeFont = false;
NotificationInfo.bUseSuccessFailIcons = true;
NotificationInfo.ExpireDuration = 10.0f;
TSharedRef<SNotificationItem> NotificationItem = NotificationList->AddNotification(NotificationInfo);
NotificationItem->SetCompletionState(SNotificationItem::CS_Success);
NotificationItem->ExpireAndFadeout();
}
else
{
UE_LOG(LogInsightsFrontend, Warning, TEXT("[Connection] Failed to connect to \"%s\"!"), *RunningInstanceAddressStr);
FNotificationInfo NotificationInfo(FText::Format(LOCTEXT("ConnectFailed", "Failed to connect to \"{0}\"!"), FText::FromString(RunningInstanceAddressStr)));
NotificationInfo.bFireAndForget = false;
NotificationInfo.bUseLargeFont = false;
NotificationInfo.bUseSuccessFailIcons = true;
NotificationInfo.ExpireDuration = 10.0f;
TSharedRef<SNotificationItem> NotificationItem = NotificationList->AddNotification(NotificationInfo);
NotificationItem->SetCompletionState(SNotificationItem::CS_Fail);
NotificationItem->ExpireAndFadeout();
}
bIsConnecting = false;
},
TStatId{}, PreConnectTask, ENamedThreads::GameThread);
return FReply::Handled();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
} // namespace UE::Insights
#undef LOCTEXT_NAMESPACE