436 lines
12 KiB
C++
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
|