Files
UnrealEngine/Engine/Source/Editor/VirtualizationEditor/Private/SVirtualAssetsStatistics.cpp
2025-05-18 13:04:45 +08:00

424 lines
12 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SVirtualAssetsStatistics.h"
#include "Containers/Array.h"
#include "Containers/UnrealString.h"
#include "Delegates/Delegate.h"
#include "Fonts/SlateFontInfo.h"
#include "FrameNumberTimeEvaluator.h"
#include "Framework/Commands/UIAction.h"
#include "Framework/Notifications/NotificationManager.h"
#include "Framework/Text/TextLayout.h"
#include "HAL/PlatformCrt.h"
#include "HAL/PlatformTime.h"
#include "Internationalization/Internationalization.h"
#include "Internationalization/FastDecimalFormat.h"
#include "Layout/BasicLayoutWidgetSlot.h"
#include "Layout/Children.h"
#include "Layout/Margin.h"
#include "Logging/MessageLog.h"
#include "Logging/TokenizedMessage.h"
#include "Misc/Attribute.h"
#include "Misc/Paths.h"
#include "Misc/ScopeLock.h"
#include "SlotBase.h"
#include "Styling/CoreStyle.h"
#include "Styling/SlateColor.h"
#include "Styling/StyleColors.h"
#include "Textures/SlateIcon.h"
#include "ToolMenu.h"
#include "ToolMenuDelegates.h"
#include "ToolMenuSection.h"
#include "Types/WidgetActiveTimerDelegate.h"
#include "UObject/UObjectGlobals.h"
#include "Virtualization/VirtualizationTypes.h"
#include "Widgets/Layout/SGridPanel.h"
#include "Widgets/Layout/SScrollBox.h"
#include "Widgets/Notifications/SNotificationList.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/Text/STextBlock.h"
class SWidget;
#define LOCTEXT_NAMESPACE "VirtualizationEditor"
namespace
{
FString SingleDecimalFormat(double Value)
{
const FNumberFormattingOptions NumberFormattingOptions = FNumberFormattingOptions()
.SetUseGrouping(true)
.SetMinimumFractionalDigits(1)
.SetMaximumFractionalDigits(1);
return FastDecimalFormat::NumberToString(Value, ExpressionParser::GetLocalizedNumberFormattingRules(), NumberFormattingOptions);
}
} //namespace
SVirtualAssetsStatisticsDialog::SVirtualAssetsStatisticsDialog()
{
using namespace UE::Virtualization;
// TODO - need a way to make this work once the system is initialized
// Register our VA notification delegate with the event
if (IVirtualizationSystem::IsInitialized())
{
IVirtualizationSystem& System = IVirtualizationSystem::Get();
System.GetNotificationEvent().AddRaw(this, &SVirtualAssetsStatisticsDialog::OnNotificationEvent);
}
}
SVirtualAssetsStatisticsDialog::~SVirtualAssetsStatisticsDialog()
{
using namespace UE::Virtualization;
// Unregister our VA notification delegate with the event
IVirtualizationSystem& System = IVirtualizationSystem::Get();
System.GetNotificationEvent().RemoveAll(this);
}
void SVirtualAssetsStatisticsDialog::OnNotificationEvent(UE::Virtualization::IVirtualizationSystem::ENotification Notification, const FIoHash& PayloadId)
{
using namespace UE::Virtualization;
FScopeLock SocpeLock(&NotificationCS);
switch (Notification)
{
case IVirtualizationSystem::ENotification::PullBegunNotification:
{
IsPulling = true;
NumPullRequests++;
break;
}
case IVirtualizationSystem::ENotification::PullEndedNotification:
{
if (IsPulling == true)
{
NumPullRequests--;
IsPulling = NumPullRequests!=0;
}
break;
}
case IVirtualizationSystem::ENotification::PullFailedNotification:
{
NumPullRequestFailures++;
break;
}
default:
break;
}
}
void SVirtualAssetsStatisticsDialog::Construct(const FArguments& InArgs)
{
this->ChildSlot
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 20, 0, 0)
.Expose(GridSlot)
[
SAssignNew(ScrollBox, SScrollBox)
.Orientation(EOrientation::Orient_Horizontal)
.ScrollBarAlwaysVisible(false)
+ SScrollBox::Slot()
[
GetGridPanel()
]
]
];
RegisterActiveTimer(0.25f, FWidgetActiveTimerDelegate::CreateSP(this, &SVirtualAssetsStatisticsDialog::UpdateGridPanels));
}
EActiveTimerReturnType SVirtualAssetsStatisticsDialog::UpdateGridPanels(double InCurrentTime, float InDeltaTime)
{
ScrollBox->ClearChildren();
ScrollBox->AddSlot()
[
GetGridPanel()
];
SlatePrepass(GetPrepassLayoutScaleMultiplier());
const float PullNotifactionTimeLimit=1.0f;
// Only show the pull notification if we have been pulling for more than a second..
if (NumPullRequests != 0)
{
PullNotificationTimer += InDeltaTime;
}
else
{
PullNotificationTimer = 0.0f;
}
if ( PullNotificationTimer>PullNotifactionTimeLimit && PullRequestNotificationItem.IsValid()==false )
{
// No existing notification or the existing one has finished
FNotificationInfo Info(LOCTEXT("PayloadSyncNotifcation", "Syncing Asset Payloads"));
Info.bFireAndForget = false;
Info.bUseLargeFont = false;
Info.bUseThrobber = false;
Info.FadeOutDuration = 0.5f;
Info.ExpireDuration = 0.0f;
PullRequestNotificationItem = FSlateNotificationManager::Get().AddNotification(Info);
if (PullRequestNotificationItem.IsValid())
{
PullRequestNotificationItem->SetCompletionState(SNotificationItem::CS_Pending);
}
}
if ( NumPullRequestFailures>0 && PullRequestFailedNotificationItem.IsValid()==false )
{
// No existing notification or the existing one has finished
FNotificationInfo Info(LOCTEXT("PayloadFailedNotifcation", "Failed to sync some Virtual Asset payloads from available backends.\nSome assets may no longer be usable.."));
Info.bFireAndForget = false;
Info.bUseLargeFont = false;
Info.bUseThrobber = false;
Info.FadeOutDuration = 0.5f;
Info.ExpireDuration = 0.0f;
Info.Image = FAppStyle::GetBrush(TEXT("MessageLog.Warning"));
Info.ButtonDetails.Add(FNotificationButtonInfo(LOCTEXT("PullFailedIgnore", "Ignore"), LOCTEXT("PullFailedIgnoreToolTip", "Ignore future warnings"), FSimpleDelegate::CreateSP(this, &SVirtualAssetsStatisticsDialog::OnWarningReasonIgnore), SNotificationItem::CS_None));
Info.ButtonDetails.Add(FNotificationButtonInfo(LOCTEXT("PullFailedOK", "Ok"), LOCTEXT("PullFailedOkToolTip", "Notify future warnings"), FSimpleDelegate::CreateSP(this, &SVirtualAssetsStatisticsDialog::OnWarningReasonOk), SNotificationItem::CS_None));
Info.HyperlinkText = LOCTEXT("PullFailed_ShowLog", "Show Message Log");
Info.Hyperlink = FSimpleDelegate::CreateStatic([]() { FMessageLog("LogVirtualization").Open(EMessageSeverity::Warning, true); });
PullRequestFailedNotificationItem = FSlateNotificationManager::Get().AddNotification(Info);
}
if ( NumPullRequests==0 && PullRequestNotificationItem.IsValid()==true )
{
PullRequestNotificationItem->SetCompletionState(SNotificationItem::CS_Success);
PullRequestNotificationItem->ExpireAndFadeout();
PullRequestNotificationItem.Reset();
}
return EActiveTimerReturnType::Continue;
}
void SVirtualAssetsStatisticsDialog::OnWarningReasonOk()
{
if (PullRequestFailedNotificationItem.IsValid() == true)
{
PullRequestFailedNotificationItem->ExpireAndFadeout();
PullRequestFailedNotificationItem.Reset();
NumPullRequestFailures = 0;
}
}
void SVirtualAssetsStatisticsDialog::OnWarningReasonIgnore()
{
if (PullRequestFailedNotificationItem.IsValid() == true)
{
PullRequestFailedNotificationItem->ExpireAndFadeout();
}
}
TSharedRef<SWidget> SVirtualAssetsStatisticsDialog::GetGridPanel()
{
using namespace UE::Virtualization;
const float RowMargin = 0.0f;
const float TitleMargin = 10.0f;
const float ColumnMargin = 10.0f;
const float BorderPadding = ColumnMargin / 2.0f;
const FMargin StdMargin(ColumnMargin, RowMargin);
const FSlateColor TitleColor = FStyleColors::AccentWhite;
const FSlateFontInfo TitleFont = FCoreStyle::GetDefaultFontStyle("Bold", 10);
FSlateColor Color = FStyleColors::Foreground;
FSlateFontInfo Font = FCoreStyle::GetDefaultFontStyle("Regular", 10);
struct FPanels
{
TSharedPtr<SGridPanel> Names;
TSharedPtr<SGridPanel> Pull;
TSharedPtr<SGridPanel> Cache;
TSharedPtr<SGridPanel> Push;
} Panels;
// Early out if the system is disabled
if (IVirtualizationSystem::Get().IsEnabled() == false)
{
return SNew(STextBlock)
.Margin(FMargin(ColumnMargin, RowMargin))
.ColorAndOpacity(TitleColor)
.Font(TitleFont)
.Justification(ETextJustify::Center)
.Text(LOCTEXT("Disabled", "Virtual Assets Are Disabled For This Project"));
}
TSharedRef<SHorizontalBox> Panel = SNew(SHorizontalBox);
Panel->AddSlot()
.Padding(BorderPadding)
.AutoWidth()
[
SAssignNew(Panels.Names, SGridPanel)
+ SGridPanel::Slot(0, 0)
[
SNew(STextBlock)
.Margin(FMargin(ColumnMargin + (BorderPadding / 2.0f), RowMargin + (BorderPadding / 2.0f)))
.ColorAndOpacity(TitleColor)
.Font(TitleFont)
.Justification(ETextJustify::Left)
]
+SGridPanel::Slot(0, 1)
[
SNew(STextBlock)
.Margin(FMargin(ColumnMargin, RowMargin, 0.0f, TitleMargin))
.ColorAndOpacity(TitleColor)
.Font(TitleFont)
.Justification(ETextJustify::Left)
.Text(LOCTEXT("Backend", "Backend"))
]
];
auto CreateGridPanels = [&](FText&& Label)
{
TSharedPtr<SGridPanel> GridPanel;
Panel->AddSlot()
.Padding(BorderPadding)
.AutoWidth()
[
SNew(SBorder)
.Padding(BorderPadding)
[
SAssignNew(GridPanel, SGridPanel)
+ SGridPanel::Slot(1, 0)
[
SNew(STextBlock)
.Margin(FMargin(ColumnMargin, RowMargin))
.ColorAndOpacity(TitleColor)
.Font(TitleFont)
.Justification(ETextJustify::Center)
.Text(MoveTemp(Label))
]
+ SGridPanel::Slot(0, 1)
[
SNew(STextBlock)
.Margin(FMargin(ColumnMargin, RowMargin, 0.0f, TitleMargin))
.ColorAndOpacity(TitleColor)
.Font(TitleFont)
.Justification(ETextJustify::Center)
.Text(LOCTEXT("Count", "Count"))
]
+ SGridPanel::Slot(1, 1)
[
SNew(STextBlock)
.Margin(FMargin(ColumnMargin, RowMargin, 0.0f, TitleMargin))
.ColorAndOpacity(TitleColor)
.Font(TitleFont)
.Justification(ETextJustify::Center)
.Text(LOCTEXT("Size", "Size (MiB)"))
]
+ SGridPanel::Slot(2, 1)
[
SNew(STextBlock)
.Margin(FMargin(ColumnMargin, RowMargin, 0.0f, TitleMargin))
.ColorAndOpacity(TitleColor)
.Font(TitleFont)
.Justification(ETextJustify::Center)
.Text(LOCTEXT("Time", "Avg (ms)"))
]
]
];
return GridPanel;
};
Panels.Pull = CreateGridPanels(LOCTEXT("Download", "Download"));
Panels.Cache = CreateGridPanels(LOCTEXT("Cache", "Cache"));
Panels.Push = CreateGridPanels(LOCTEXT("Upload", "Upload"));
int32 RowIndex = 2;
auto DisplayPayloadActivityInfo = [&StdMargin, &Color, &Font, &RowIndex, &Panels](const FString& Name, const FPayloadActivityInfo& PayloadActivityInfo)
{
Panels.Names->AddSlot(0, RowIndex)
[
SNew(STextBlock)
.Margin(StdMargin)
.ColorAndOpacity(Color)
.Font(Font)
.Justification(ETextJustify::Left)
.Text(FText::FromString(Name))
];
auto FillPanelDetails = [&StdMargin, &Color, &Font, &RowIndex](TSharedPtr<SGridPanel>& Panel, const FPayloadActivityInfo::FActivity& Activity)
{
Panel->AddSlot(0, RowIndex)
[
SNew(STextBlock)
.Margin(StdMargin)
.ColorAndOpacity(Color)
.Font(Font)
.Justification(ETextJustify::Center)
.Text(FText::FromString(FString::Printf(TEXT("%" INT64_FMT), Activity.PayloadCount)))
];
const double TotalBytesMiB = static_cast<double>(Activity.TotalBytes) / (1024.0 * 1024.0);
Panel->AddSlot(1, RowIndex)
[
SNew(STextBlock)
.Margin(StdMargin)
.ColorAndOpacity(Color)
.Font(Font)
.Justification(ETextJustify::Center)
.Text(FText::FromString(SingleDecimalFormat(TotalBytesMiB)))
];
const double TotalTime = static_cast<double>(FPlatformTime::ToMilliseconds64(Activity.CyclesSpent));
const double Avg = Activity.PayloadCount > 0 ? TotalTime / static_cast<double>(Activity.PayloadCount) : 0.0;
Panel->AddSlot(2, RowIndex)
[
SNew(STextBlock)
.Margin(StdMargin)
.ColorAndOpacity(Color)
.Font(Font)
.Justification(ETextJustify::Center)
.Text(FText::FromString(SingleDecimalFormat(Avg)))
];
};
FillPanelDetails(Panels.Pull, PayloadActivityInfo.Pull);
FillPanelDetails(Panels.Cache, PayloadActivityInfo.Cache);
FillPanelDetails(Panels.Push, PayloadActivityInfo.Push);
RowIndex++;
};
TArray<FBackendStats> BackendStats = IVirtualizationSystem::Get().GetBackendStatistics();
for (const FBackendStats& Stats : BackendStats)
{
DisplayPayloadActivityInfo(Stats.ConfigName, Stats.PayloadActivity);
}
FPayloadActivityInfo AccumulatedPayloadAcitvityInfo = IVirtualizationSystem::Get().GetSystemStatistics();
Color = TitleColor;
Font = TitleFont;
DisplayPayloadActivityInfo(FString("Total"), AccumulatedPayloadAcitvityInfo);
return Panel;
}
#undef LOCTEXT_NAMESPACE