// Copyright Epic Games, Inc. All Rights Reserved. #include "SNetStatsView.h" #include "EditorStyleSet.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "SlateOptMacros.h" #include "Templates/UniquePtr.h" #include "TraceServices/AnalysisService.h" #include "TraceServices/Model/NetProfiler.h" #include "Widgets/Input/SCheckBox.h" #include "Widgets/Layout/SScrollBox.h" #include "Widgets/Input/SSearchBox.h" #include "Widgets/Images/SImage.h" #include "Widgets/Layout/SGridPanel.h" #include "Widgets/Layout/SSeparator.h" #include "Widgets/SToolTip.h" #include "Widgets/Views/STableViewBase.h" // Insights #include "Insights/Table/ViewModels/Table.h" #include "Insights/Table/ViewModels/TableColumn.h" #include "Insights/NetworkingProfiler/NetworkingProfilerManager.h" #include "Insights/NetworkingProfiler/ViewModels/NetStatsViewColumnFactory.h" #include "Insights/NetworkingProfiler/Widgets/SNetStatsViewTooltip.h" #include "Insights/NetworkingProfiler/Widgets/SNetStatsTableRow.h" #include "Insights/NetworkingProfiler/Widgets/SNetworkingProfilerWindow.h" #include "Insights/NetworkingProfiler/Widgets/SPacketContentView.h" #define LOCTEXT_NAMESPACE "SNetStatsView" //////////////////////////////////////////////////////////////////////////////////////////////////// SNetStatsView::SNetStatsView() : ProfilerWindow(), Table(MakeShared()), bExpansionSaved(false), bFilterOutZeroCountEvents(false), GroupingMode(ENetEventGroupingMode::Flat), AvailableSorters(), CurrentSorter(nullptr), ColumnBeingSorted(GetDefaultColumnBeingSorted()), ColumnSortMode(GetDefaultColumnSortMode()), NextTimestamp(0), ObjectsChangeCount(0), GameInstanceIndex(0), ConnectionIndex(0), ConnectionMode(Trace::ENetProfilerConnectionMode::Outgoing), StatsPacketStartIndex(0), StatsPacketEndIndex(0), StatsStartPosition(0), StatsEndPosition(0) { FMemory::Memset(bNetEventTypeIsVisible, 1); } //////////////////////////////////////////////////////////////////////////////////////////////////// SNetStatsView::~SNetStatsView() { // Remove ourselves from the Insights manager. if (FInsightsManager::Get().IsValid()) { FInsightsManager::Get()->GetSessionChangedEvent().RemoveAll(this); } } //////////////////////////////////////////////////////////////////////////////////////////////////// BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION void SNetStatsView::Construct(const FArguments& InArgs, TSharedPtr InProfilerWindow) { ProfilerWindow = InProfilerWindow; SAssignNew(ExternalScrollbar, SScrollBar) .AlwaysShowScrollbar(true); ChildSlot [SNew(SVerticalBox) + SVerticalBox::Slot() .VAlign(VAlign_Center) .AutoHeight() [SNew(SBorder) .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) .Padding(2.0f) [SNew(SVerticalBox) + SVerticalBox::Slot() .VAlign(VAlign_Center) .Padding(2.0f) .AutoHeight() [SNew(SHorizontalBox) // Search box + SHorizontalBox::Slot() .VAlign(VAlign_Center) .Padding(2.0f) .FillWidth(1.0f) [SAssignNew(SearchBox, SSearchBox) .HintText(LOCTEXT("SearchBoxHint", "Search net events or groups")) .OnTextChanged(this, &SNetStatsView::SearchBox_OnTextChanged) .IsEnabled(this, &SNetStatsView::SearchBox_IsEnabled) .ToolTipText(LOCTEXT("FilterSearchHint", "Type here to search net events or groups"))] // Filter out net event types with zero instance count + SHorizontalBox::Slot() .VAlign(VAlign_Center) .Padding(2.0f) .AutoWidth() [SNew(SCheckBox) .Style(FEditorStyle::Get(), "ToggleButtonCheckbox") .HAlign(HAlign_Center) .Padding(2.0f) .OnCheckStateChanged(this, &SNetStatsView::FilterOutZeroCountEvents_OnCheckStateChanged) .IsChecked(this, &SNetStatsView::FilterOutZeroCountEvents_IsChecked) .ToolTipText(LOCTEXT("FilterOutZeroCountEvents_Tooltip", "Filter out the net event types having zero total instance count (aggregated stats).")) [ // TODO: SNew(SImage) SNew(STextBlock) .Text(LOCTEXT("FilterOutZeroCountEvents_Button", " !0 ")) .TextStyle(FEditorStyle::Get(), TEXT("Profiler.Caption"))]]] // Group by + SVerticalBox::Slot() .VAlign(VAlign_Center) .Padding(2.0f) .AutoHeight() [SNew(SHorizontalBox) + SHorizontalBox::Slot() .FillWidth(1.0f) .VAlign(VAlign_Center) [SNew(STextBlock) .Text(LOCTEXT("GroupByText", "Group by"))] + SHorizontalBox::Slot() .FillWidth(2.0f) .VAlign(VAlign_Center) [SAssignNew(GroupByComboBox, SComboBox>) .ToolTipText(this, &SNetStatsView::GroupBy_GetSelectedTooltipText) .OptionsSource(&GroupByOptionsSource) .OnSelectionChanged(this, &SNetStatsView::GroupBy_OnSelectionChanged) .OnGenerateWidget(this, &SNetStatsView::GroupBy_OnGenerateWidget) [SNew(STextBlock) .Text(this, &SNetStatsView::GroupBy_GetSelectedText)]]] // TODO: Check boxes for: NetEvent, ... /* + SVerticalBox::Slot() .VAlign(VAlign_Center) .Padding(2.0f) .AutoHeight() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .Padding(FMargin(0.0f,0.0f,1.0f,0.0f)) .FillWidth(1.0f) [ GetToggleButtonForNetEventType(ENetEventNodeType::NetEvent) ] //+ SHorizontalBox::Slot() //.Padding(FMargin(1.0f,0.0f,1.0f,0.0f)) //.FillWidth(1.0f) //[ // GetToggleButtonForNetEventType(ENetEventNodeType::NetEvent1) //] //+ SHorizontalBox::Slot() .//Padding(FMargin(1.0f,0.0f,1.0f,0.0f)) //.FillWidth(1.0f) //[ // GetToggleButtonForNetEventType(ENetEventNodeType::NetEvent2) //] ] */ ]] // Tree view + SVerticalBox::Slot() .FillHeight(1.0f) .Padding(0.0f, 6.0f, 0.0f, 0.0f) [SNew(SHorizontalBox) + SHorizontalBox::Slot() .FillWidth(1.0f) .Padding(0.0f) [SNew(SScrollBox) .Orientation(Orient_Horizontal) + SScrollBox::Slot() [SNew(SBorder) .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) .Padding(0.0f) [SAssignNew(TreeView, STreeView) .ExternalScrollbar(ExternalScrollbar) .SelectionMode(ESelectionMode::Multi) .TreeItemsSource(&FilteredGroupNodes) .OnGetChildren(this, &SNetStatsView::TreeView_OnGetChildren) .OnGenerateRow(this, &SNetStatsView::TreeView_OnGenerateRow) .OnSelectionChanged(this, &SNetStatsView::TreeView_OnSelectionChanged) .OnMouseButtonDoubleClick(this, &SNetStatsView::TreeView_OnMouseButtonDoubleClick) .OnContextMenuOpening(FOnContextMenuOpening::CreateSP(this, &SNetStatsView::TreeView_GetMenuContent)) .ItemHeight(12.0f) .HeaderRow( SAssignNew(TreeViewHeaderRow, SHeaderRow) .Visibility(EVisibility::Visible))]]] + SHorizontalBox::Slot() .AutoWidth() .Padding(0.0f) [SNew(SBox) .WidthOverride(FOptionalSize(13.0f)) [ExternalScrollbar.ToSharedRef()]]]]; InitializeAndShowHeaderColumns(); // BindCommands(); // Create the search filters: text based, type based etc. TextFilter = MakeShared(FNetEventNodeTextFilter::FItemToStringArray::CreateSP(this, &SNetStatsView::HandleItemToStringArray)); Filters = MakeShared(); Filters->Add(TextFilter); CreateGroupByOptionsSources(); CreateSortings(); // Register ourselves with the Insights manager. FInsightsManager::Get()->GetSessionChangedEvent().AddSP(this, &SNetStatsView::InsightsManager_OnSessionChanged); // Update the Session (i.e. when analysis session was already started). InsightsManager_OnSessionChanged(); } END_SLATE_FUNCTION_BUILD_OPTIMIZATION //////////////////////////////////////////////////////////////////////////////////////////////////// TSharedPtr SNetStatsView::TreeView_GetMenuContent() { const TArray SelectedNodes = TreeView->GetSelectedItems(); const int32 NumSelectedNodes = SelectedNodes.Num(); FNetEventNodePtr SelectedNode = NumSelectedNodes ? SelectedNodes[0] : nullptr; const TSharedPtr HoveredColumnPtr = Table->FindColumn(HoveredColumnId); FText SelectionStr; FText PropertyName; FText PropertyValue; if (NumSelectedNodes == 0) { SelectionStr = LOCTEXT("NothingSelected", "Nothing selected"); } else if (NumSelectedNodes == 1) { if (HoveredColumnPtr != nullptr) { PropertyName = HoveredColumnPtr->GetShortName(); PropertyValue = HoveredColumnPtr->GetValueAsTooltipText(*SelectedNode); } FString ItemName = SelectedNode->GetName().ToString(); const int32 MaxStringLen = 64; if (ItemName.Len() > MaxStringLen) { ItemName = ItemName.Left(MaxStringLen) + TEXT("..."); } SelectionStr = FText::FromString(ItemName); } else { SelectionStr = LOCTEXT("MultipleSelection", "Multiple selection"); } const bool bShouldCloseWindowAfterMenuSelection = true; FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, NULL); // Selection menu MenuBuilder.BeginSection("Selection", LOCTEXT("ContextMenu_Header_Selection", "Selection")); { struct FLocal { static bool ReturnFalse() { return false; } }; FUIAction DummyUIAction; DummyUIAction.CanExecuteAction = FCanExecuteAction::CreateStatic(&FLocal::ReturnFalse); MenuBuilder.AddMenuEntry( SelectionStr, LOCTEXT("ContextMenu_Selection", "Currently selected items"), FSlateIcon(FEditorStyle::GetStyleSetName(), "@missing.icon"), DummyUIAction, NAME_None, EUserInterfaceActionType::Button); } MenuBuilder.EndSection(); MenuBuilder.BeginSection("Misc", LOCTEXT("ContextMenu_Header_Misc", "Miscellaneous")); { /*TODO FUIAction Action_CopySelectedToClipboard ( FExecuteAction::CreateSP(this, &SNetStatsView::ContextMenu_CopySelectedToClipboard_Execute), FCanExecuteAction::CreateSP(this, &SNetStatsView::ContextMenu_CopySelectedToClipboard_CanExecute) ); MenuBuilder.AddMenuEntry ( LOCTEXT("ContextMenu_Header_Misc_CopySelectedToClipboard", "Copy To Clipboard"), LOCTEXT("ContextMenu_Header_Misc_CopySelectedToClipboard_Desc", "Copies selection to clipboard"), FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.Misc.CopyToClipboard"), Action_CopySelectedToClipboard, NAME_None, EUserInterfaceActionType::Button ); */ MenuBuilder.AddSubMenu( LOCTEXT("ContextMenu_Header_Misc_Sort", "Sort By"), LOCTEXT("ContextMenu_Header_Misc_Sort_Desc", "Sort by column"), FNewMenuDelegate::CreateSP(this, &SNetStatsView::TreeView_BuildSortByMenu), false, FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.Misc.SortBy")); } MenuBuilder.EndSection(); MenuBuilder.BeginSection("Columns", LOCTEXT("ContextMenu_Header_Columns", "Columns")); { MenuBuilder.AddSubMenu( LOCTEXT("ContextMenu_Header_Columns_View", "View Column"), LOCTEXT("ContextMenu_Header_Columns_View_Desc", "Hides or shows columns"), FNewMenuDelegate::CreateSP(this, &SNetStatsView::TreeView_BuildViewColumnMenu), false, FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.EventGraph.ViewColumn")); FUIAction Action_ShowAllColumns( FExecuteAction::CreateSP(this, &SNetStatsView::ContextMenu_ShowAllColumns_Execute), FCanExecuteAction::CreateSP(this, &SNetStatsView::ContextMenu_ShowAllColumns_CanExecute)); MenuBuilder.AddMenuEntry( LOCTEXT("ContextMenu_Header_Columns_ShowAllColumns", "Show All Columns"), LOCTEXT("ContextMenu_Header_Columns_ShowAllColumns_Desc", "Resets tree view to show all columns"), FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.EventGraph.ResetColumn"), Action_ShowAllColumns, NAME_None, EUserInterfaceActionType::Button); FUIAction Action_ShowMinMaxMedColumns( FExecuteAction::CreateSP(this, &SNetStatsView::ContextMenu_ShowMinMaxMedColumns_Execute), FCanExecuteAction::CreateSP(this, &SNetStatsView::ContextMenu_ShowMinMaxMedColumns_CanExecute)); MenuBuilder.AddMenuEntry( LOCTEXT("ContextMenu_Header_Columns_ShowMinMaxMedColumns", "Reset Columns to Min/Max/Median Preset"), LOCTEXT("ContextMenu_Header_Columns_ShowMinMaxMedColumns_Desc", "Resets columns to Min/Max/Median preset"), FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.EventGraph.ResetColumn"), Action_ShowMinMaxMedColumns, NAME_None, EUserInterfaceActionType::Button); FUIAction Action_ResetColumns( FExecuteAction::CreateSP(this, &SNetStatsView::ContextMenu_ResetColumns_Execute), FCanExecuteAction::CreateSP(this, &SNetStatsView::ContextMenu_ResetColumns_CanExecute)); MenuBuilder.AddMenuEntry( LOCTEXT("ContextMenu_Header_Columns_ResetColumns", "Reset Columns to Default"), LOCTEXT("ContextMenu_Header_Columns_ResetColumns_Desc", "Resets columns to default"), FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.EventGraph.ResetColumn"), Action_ResetColumns, NAME_None, EUserInterfaceActionType::Button); } MenuBuilder.EndSection(); return MenuBuilder.MakeWidget(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void SNetStatsView::TreeView_BuildSortByMenu(FMenuBuilder& MenuBuilder) { // TODO: Refactor later @see TSharedPtr SCascadePreviewViewportToolBar::GenerateViewMenu() const MenuBuilder.BeginSection("ColumnName", LOCTEXT("ContextMenu_Header_Misc_ColumnName", "Column Name")); for (const TSharedRef& ColumnRef: Table->GetColumns()) { const Insights::FTableColumn& Column = *ColumnRef; if (Column.IsVisible() && Column.CanBeSorted()) { FUIAction Action_SortByColumn( FExecuteAction::CreateSP(this, &SNetStatsView::ContextMenu_SortByColumn_Execute, Column.GetId()), FCanExecuteAction::CreateSP(this, &SNetStatsView::ContextMenu_SortByColumn_CanExecute, Column.GetId()), FIsActionChecked::CreateSP(this, &SNetStatsView::ContextMenu_SortByColumn_IsChecked, Column.GetId())); MenuBuilder.AddMenuEntry( Column.GetTitleName(), Column.GetDescription(), FSlateIcon(), Action_SortByColumn, NAME_None, EUserInterfaceActionType::RadioButton); } } MenuBuilder.EndSection(); //----------------------------------------------------------------------------- MenuBuilder.BeginSection("SortMode", LOCTEXT("ContextMenu_Header_Misc_Sort_SortMode", "Sort Mode")); { FUIAction Action_SortAscending( FExecuteAction::CreateSP(this, &SNetStatsView::ContextMenu_SortMode_Execute, EColumnSortMode::Ascending), FCanExecuteAction::CreateSP(this, &SNetStatsView::ContextMenu_SortMode_CanExecute, EColumnSortMode::Ascending), FIsActionChecked::CreateSP(this, &SNetStatsView::ContextMenu_SortMode_IsChecked, EColumnSortMode::Ascending)); MenuBuilder.AddMenuEntry( LOCTEXT("ContextMenu_Header_Misc_Sort_SortAscending", "Sort Ascending"), LOCTEXT("ContextMenu_Header_Misc_Sort_SortAscending_Desc", "Sorts ascending"), FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.Misc.SortAscending"), Action_SortAscending, NAME_None, EUserInterfaceActionType::RadioButton); FUIAction Action_SortDescending( FExecuteAction::CreateSP(this, &SNetStatsView::ContextMenu_SortMode_Execute, EColumnSortMode::Descending), FCanExecuteAction::CreateSP(this, &SNetStatsView::ContextMenu_SortMode_CanExecute, EColumnSortMode::Descending), FIsActionChecked::CreateSP(this, &SNetStatsView::ContextMenu_SortMode_IsChecked, EColumnSortMode::Descending)); MenuBuilder.AddMenuEntry( LOCTEXT("ContextMenu_Header_Misc_Sort_SortDescending", "Sort Descending"), LOCTEXT("ContextMenu_Header_Misc_Sort_SortDescending_Desc", "Sorts descending"), FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.Misc.SortDescending"), Action_SortDescending, NAME_None, EUserInterfaceActionType::RadioButton); } MenuBuilder.EndSection(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void SNetStatsView::TreeView_BuildViewColumnMenu(FMenuBuilder& MenuBuilder) { MenuBuilder.BeginSection("ViewColumn", LOCTEXT("ContextMenu_Header_Columns_View", "View Column")); for (const TSharedRef& ColumnRef: Table->GetColumns()) { const Insights::FTableColumn& Column = *ColumnRef; FUIAction Action_ToggleColumn( FExecuteAction::CreateSP(this, &SNetStatsView::ToggleColumnVisibility, Column.GetId()), FCanExecuteAction::CreateSP(this, &SNetStatsView::CanToggleColumnVisibility, Column.GetId()), FIsActionChecked::CreateSP(this, &SNetStatsView::IsColumnVisible, Column.GetId())); MenuBuilder.AddMenuEntry( Column.GetTitleName(), Column.GetDescription(), FSlateIcon(), Action_ToggleColumn, NAME_None, EUserInterfaceActionType::ToggleButton); } MenuBuilder.EndSection(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void SNetStatsView::InitializeAndShowHeaderColumns() { // Create columns. TArray> Columns; FNetStatsViewColumnFactory::CreateNetStatsViewColumns(Columns); Table->SetColumns(Columns); // Show columns. for (const TSharedRef& ColumnRef: Table->GetColumns()) { if (ColumnRef->ShouldBeVisible()) { ShowColumn(ColumnRef->GetId()); } } } //////////////////////////////////////////////////////////////////////////////////////////////////// FText SNetStatsView::GetColumnHeaderText(const FName ColumnId) const { const Insights::FTableColumn& Column = *Table->FindColumnChecked(ColumnId); return Column.GetShortName(); } //////////////////////////////////////////////////////////////////////////////////////////////////// TSharedRef SNetStatsView::TreeViewHeaderRow_GenerateColumnMenu(const Insights::FTableColumn& Column) { bool bIsMenuVisible = false; const bool bShouldCloseWindowAfterMenuSelection = true; FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, NULL); { if (Column.CanBeHidden()) { MenuBuilder.BeginSection("Column", LOCTEXT("TreeViewHeaderRow_Header_Column", "Column")); FUIAction Action_HideColumn( FExecuteAction::CreateSP(this, &SNetStatsView::HideColumn, Column.GetId()), FCanExecuteAction::CreateSP(this, &SNetStatsView::CanHideColumn, Column.GetId())); MenuBuilder.AddMenuEntry( LOCTEXT("TreeViewHeaderRow_HideColumn", "Hide"), LOCTEXT("TreeViewHeaderRow_HideColumn_Desc", "Hides the selected column"), FSlateIcon(), Action_HideColumn, NAME_None, EUserInterfaceActionType::Button); bIsMenuVisible = true; MenuBuilder.EndSection(); } if (Column.CanBeSorted()) { MenuBuilder.BeginSection("SortMode", LOCTEXT("ContextMenu_Header_Misc_Sort_SortMode", "Sort Mode")); FUIAction Action_SortAscending( FExecuteAction::CreateSP(this, &SNetStatsView::HeaderMenu_SortMode_Execute, Column.GetId(), EColumnSortMode::Ascending), FCanExecuteAction::CreateSP(this, &SNetStatsView::HeaderMenu_SortMode_CanExecute, Column.GetId(), EColumnSortMode::Ascending), FIsActionChecked::CreateSP(this, &SNetStatsView::HeaderMenu_SortMode_IsChecked, Column.GetId(), EColumnSortMode::Ascending)); MenuBuilder.AddMenuEntry( LOCTEXT("ContextMenu_Header_Misc_Sort_SortAscending", "Sort Ascending"), LOCTEXT("ContextMenu_Header_Misc_Sort_SortAscending_Desc", "Sorts ascending"), FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.Misc.SortAscending"), Action_SortAscending, NAME_None, EUserInterfaceActionType::RadioButton); FUIAction Action_SortDescending( FExecuteAction::CreateSP(this, &SNetStatsView::HeaderMenu_SortMode_Execute, Column.GetId(), EColumnSortMode::Descending), FCanExecuteAction::CreateSP(this, &SNetStatsView::HeaderMenu_SortMode_CanExecute, Column.GetId(), EColumnSortMode::Descending), FIsActionChecked::CreateSP(this, &SNetStatsView::HeaderMenu_SortMode_IsChecked, Column.GetId(), EColumnSortMode::Descending)); MenuBuilder.AddMenuEntry( LOCTEXT("ContextMenu_Header_Misc_Sort_SortDescending", "Sort Descending"), LOCTEXT("ContextMenu_Header_Misc_Sort_SortDescending_Desc", "Sorts descending"), FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.Misc.SortDescending"), Action_SortDescending, NAME_None, EUserInterfaceActionType::RadioButton); bIsMenuVisible = true; MenuBuilder.EndSection(); } // if (Column.CanBeFiltered()) //{ // MenuBuilder.BeginSection("FilterMode", LOCTEXT("ContextMenu_Header_Misc_Filter_FilterMode", "Filter Mode")); // bIsMenuVisible = true; // MenuBuilder.EndSection(); // } } /* TODO: - Show top ten - Show top bottom - Filter by list (avg, median, 10%, 90%, etc.) - Text box for filtering for each column instead of one text box used for filtering - Grouping button for flat view modes (show at most X groups, show all groups for names) */ return bIsMenuVisible ? MenuBuilder.MakeWidget() : (TSharedRef)SNullWidget::NullWidget; } //////////////////////////////////////////////////////////////////////////////////////////////////// void SNetStatsView::InsightsManager_OnSessionChanged() { TSharedPtr NewSession = FInsightsManager::Get()->GetSession(); if (NewSession != Session) { Session = NewSession; Reset(); } else { UpdateTree(); } } //////////////////////////////////////////////////////////////////////////////////////////////////// void SNetStatsView::UpdateTree() { FStopwatch Stopwatch; Stopwatch.Start(); CreateGroups(); Stopwatch.Update(); const double Time1 = Stopwatch.GetAccumulatedTime(); SortTreeNodes(); Stopwatch.Update(); const double Time2 = Stopwatch.GetAccumulatedTime(); ApplyFiltering(); Stopwatch.Stop(); const double TotalTime = Stopwatch.GetAccumulatedTime(); if (TotalTime > 0.1) { UE_LOG(NetworkingProfiler, Log, TEXT("[NetStats] Tree view updated in %.3fs (%d events) --> G:%.3fs + S:%.3fs + F:%.3fs"), TotalTime, NetEventNodes.Num(), Time1, Time2 - Time1, TotalTime - Time2); } } //////////////////////////////////////////////////////////////////////////////////////////////////// void SNetStatsView::ApplyFiltering() { FilteredGroupNodes.Reset(); // Apply filter to all groups and its children. const int32 NumGroups = GroupNodes.Num(); for (int32 GroupIndex = 0; GroupIndex < NumGroups; ++GroupIndex) { FNetEventNodePtr& GroupPtr = GroupNodes[GroupIndex]; GroupPtr->ClearFilteredChildren(); const bool bIsGroupVisible = Filters->PassesAllFilters(GroupPtr); const TArray& GroupChildren = GroupPtr->GetChildren(); const int32 NumChildren = GroupChildren.Num(); int32 NumVisibleChildren = 0; for (int32 Cx = 0; Cx < NumChildren; ++Cx) { // Add a child. const FNetEventNodePtr& NodePtr = StaticCastSharedPtr(GroupChildren[Cx]); const bool bIsChildVisible = (!bFilterOutZeroCountEvents || NodePtr->GetAggregatedStats().InstanceCount > 0) && bNetEventTypeIsVisible[static_cast(NodePtr->GetType())] && Filters->PassesAllFilters(NodePtr); if (bIsChildVisible) { GroupPtr->AddFilteredChild(NodePtr); NumVisibleChildren++; } } if (bIsGroupVisible || NumVisibleChildren > 0) { // Add a group. FilteredGroupNodes.Add(GroupPtr); GroupPtr->SetExpansion(true); } else { GroupPtr->SetExpansion(false); } } // Only expand net event nodes if we have a text filter. const bool bNonEmptyTextFilter = !TextFilter->GetRawFilterText().IsEmpty(); if (bNonEmptyTextFilter) { if (!bExpansionSaved) { ExpandedNodes.Empty(); TreeView->GetExpandedItems(ExpandedNodes); bExpansionSaved = true; } for (int32 Fx = 0; Fx < FilteredGroupNodes.Num(); Fx++) { const FNetEventNodePtr& GroupPtr = FilteredGroupNodes[Fx]; TreeView->SetItemExpansion(GroupPtr, GroupPtr->IsExpanded()); } } else { if (bExpansionSaved) { // Restore previously expanded nodes when the text filter is disabled. TreeView->ClearExpandedItems(); for (auto It = ExpandedNodes.CreateConstIterator(); It; ++It) { TreeView->SetItemExpansion(*It, true); } bExpansionSaved = false; } } // Request tree refresh TreeView->RequestTreeRefresh(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void SNetStatsView::HandleItemToStringArray(const FNetEventNodePtr& FNetEventNodePtr, TArray& OutSearchStrings) const { OutSearchStrings.Add(FNetEventNodePtr->GetName().GetPlainNameString()); } //////////////////////////////////////////////////////////////////////////////////////////////////// TSharedRef SNetStatsView::GetToggleButtonForNetEventType(const ENetEventNodeType NodeType) { return SNew(SCheckBox) .Style(FEditorStyle::Get(), "ToggleButtonCheckbox") .HAlign(HAlign_Center) .Padding(2.0f) .OnCheckStateChanged(this, &SNetStatsView::FilterByNetEventType_OnCheckStateChanged, NodeType) .IsChecked(this, &SNetStatsView::FilterByNetEventType_IsChecked, NodeType) .ToolTipText(NetEventNodeTypeHelper::ToDescription(NodeType)) [SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) [SNew(SImage) .Image(NetEventNodeTypeHelper::GetIconForNetEventNodeType(NodeType))] + SHorizontalBox::Slot() .Padding(2.0f, 0.0f, 0.0f, 0.0f) .VAlign(VAlign_Center) [SNew(STextBlock) .Text(NetEventNodeTypeHelper::ToText(NodeType)) .TextStyle(FEditorStyle::Get(), TEXT("Profiler.Caption"))]]; } //////////////////////////////////////////////////////////////////////////////////////////////////// void SNetStatsView::FilterOutZeroCountEvents_OnCheckStateChanged(ECheckBoxState NewState) { bFilterOutZeroCountEvents = (NewState == ECheckBoxState::Checked); ApplyFiltering(); } //////////////////////////////////////////////////////////////////////////////////////////////////// ECheckBoxState SNetStatsView::FilterOutZeroCountEvents_IsChecked() const { return bFilterOutZeroCountEvents ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } //////////////////////////////////////////////////////////////////////////////////////////////////// void SNetStatsView::FilterByNetEventType_OnCheckStateChanged(ECheckBoxState NewState, const ENetEventNodeType InStatType) { bNetEventTypeIsVisible[static_cast(InStatType)] = (NewState == ECheckBoxState::Checked); ApplyFiltering(); } //////////////////////////////////////////////////////////////////////////////////////////////////// ECheckBoxState SNetStatsView::FilterByNetEventType_IsChecked(const ENetEventNodeType InStatType) const { return bNetEventTypeIsVisible[static_cast(InStatType)] ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } //////////////////////////////////////////////////////////////////////////////////////////////////// // TreeView //////////////////////////////////////////////////////////////////////////////////////////////////// void SNetStatsView::TreeView_Refresh() { if (TreeView.IsValid()) { TreeView->RequestTreeRefresh(); } } //////////////////////////////////////////////////////////////////////////////////////////////////// void SNetStatsView::TreeView_OnSelectionChanged(FNetEventNodePtr SelectedItem, ESelectInfo::Type SelectInfo) { if (SelectInfo != ESelectInfo::Direct) { TArray SelectedItems = TreeView->GetSelectedItems(); if (SelectedItems.Num() == 1 && !SelectedItems[0]->IsGroup()) { // TODO: FNetworkingProfilerManager::Get()->SetSelectedNetEvent(SelectedItems[0]->GetEventTypeIndex()); } } } //////////////////////////////////////////////////////////////////////////////////////////////////// void SNetStatsView::TreeView_OnGetChildren(FNetEventNodePtr InParent, TArray& OutChildren) { const TArray& Children = InParent->GetFilteredChildren(); OutChildren.Reset(Children.Num()); for (const Insights::FBaseTreeNodePtr& Child: Children) { OutChildren.Add(StaticCastSharedPtr(Child)); } } //////////////////////////////////////////////////////////////////////////////////////////////////// void SNetStatsView::TreeView_OnMouseButtonDoubleClick(FNetEventNodePtr NetEventNodePtr) { if (NetEventNodePtr->IsGroup()) { const bool bIsGroupExpanded = TreeView->IsItemExpanded(NetEventNodePtr); TreeView->SetItemExpansion(NetEventNodePtr, !bIsGroupExpanded); } else { TSharedPtr PacketContentView = ProfilerWindow.IsValid() ? ProfilerWindow->GetPacketContentView() : nullptr; if (PacketContentView.IsValid()) { const uint32 EventTypeIndex = NetEventNodePtr->GetEventTypeIndex(); const uint32 FilterEventTypeIndex = PacketContentView->GetFilterEventTypeIndex(); if (EventTypeIndex == FilterEventTypeIndex && PacketContentView->IsFilterByEventTypeEnabled()) { PacketContentView->DisableFilterEventType(); } else { PacketContentView->EnableFilterEventType(EventTypeIndex); // PacketContentView->FindFirstEvent(); } } } } //////////////////////////////////////////////////////////////////////////////////////////////////// // Tree View's Table Row //////////////////////////////////////////////////////////////////////////////////////////////////// TSharedRef SNetStatsView::TreeView_OnGenerateRow(FNetEventNodePtr NetEventNodePtr, const TSharedRef& OwnerTable) { TSharedRef TableRow = SNew(SNetStatsTableRow, OwnerTable) .OnShouldBeEnabled(this, &SNetStatsView::TableRow_ShouldBeEnabled) .OnIsColumnVisible(this, &SNetStatsView::IsColumnVisible) .OnSetHoveredCell(this, &SNetStatsView::TableRow_SetHoveredCell) .OnGetColumnOutlineHAlignmentDelegate(this, &SNetStatsView::TableRow_GetColumnOutlineHAlignment) .HighlightText(this, &SNetStatsView::TableRow_GetHighlightText) .HighlightedNodeName(this, &SNetStatsView::TableRow_GetHighlightedNodeName) .TablePtr(Table) .NetEventNodePtr(NetEventNodePtr); return TableRow; } //////////////////////////////////////////////////////////////////////////////////////////////////// bool SNetStatsView::TableRow_ShouldBeEnabled(FNetEventNodePtr NodePtr) const { return true; } //////////////////////////////////////////////////////////////////////////////////////////////////// void SNetStatsView::TableRow_SetHoveredCell(TSharedPtr InTablePtr, TSharedPtr InColumnPtr, FNetEventNodePtr InNodePtr) { HoveredColumnId = InColumnPtr ? InColumnPtr->GetId() : FName(); const bool bIsAnyMenusVisible = FSlateApplication::Get().AnyMenusVisible(); if (!HasMouseCapture() && !bIsAnyMenusVisible) { HoveredNodePtr = InNodePtr; } } //////////////////////////////////////////////////////////////////////////////////////////////////// EHorizontalAlignment SNetStatsView::TableRow_GetColumnOutlineHAlignment(const FName ColumnId) const { const TIndirectArray& Columns = TreeViewHeaderRow->GetColumns(); const int32 LastColumnIdx = Columns.Num() - 1; // First column if (Columns[0].ColumnId == ColumnId) { return HAlign_Left; } // Last column else if (Columns[LastColumnIdx].ColumnId == ColumnId) { return HAlign_Right; } // Middle columns { return HAlign_Center; } } //////////////////////////////////////////////////////////////////////////////////////////////////// FText SNetStatsView::TableRow_GetHighlightText() const { return SearchBox->GetText(); } //////////////////////////////////////////////////////////////////////////////////////////////////// FName SNetStatsView::TableRow_GetHighlightedNodeName() const { return HighlightedNodeName; } //////////////////////////////////////////////////////////////////////////////////////////////////// // SearchBox //////////////////////////////////////////////////////////////////////////////////////////////////// void SNetStatsView::SearchBox_OnTextChanged(const FText& InFilterText) { TextFilter->SetRawFilterText(InFilterText); SearchBox->SetError(TextFilter->GetFilterErrorText()); ApplyFiltering(); } //////////////////////////////////////////////////////////////////////////////////////////////////// bool SNetStatsView::SearchBox_IsEnabled() const { return NetEventNodes.Num() > 0; } //////////////////////////////////////////////////////////////////////////////////////////////////// // Grouping //////////////////////////////////////////////////////////////////////////////////////////////////// void SNetStatsView::CreateGroups() { if (GroupingMode == ENetEventGroupingMode::Flat) { GroupNodes.Reset(); const FName GroupName(TEXT("All")); FNetEventNodePtr GroupPtr = MakeShared(GroupName); GroupNodes.Add(GroupPtr); for (const FNetEventNodePtr& NodePtr: NetEventNodes) { GroupPtr->AddChildAndSetGroupPtr(NodePtr); } TreeView->SetItemExpansion(GroupPtr, true); } // Creates one group for each stat type. else if (GroupingMode == ENetEventGroupingMode::ByType) { TMap GroupNodeSet; for (const FNetEventNodePtr& NodePtr: NetEventNodes) { const ENetEventNodeType NodeType = NodePtr->GetType(); FNetEventNodePtr GroupPtr = GroupNodeSet.FindRef(NodeType); if (!GroupPtr) { const FName GroupName = *NetEventNodeTypeHelper::ToText(NodeType).ToString(); GroupPtr = GroupNodeSet.Add(NodeType, MakeShared(GroupName)); } GroupPtr->AddChildAndSetGroupPtr(NodePtr); TreeView->SetItemExpansion(GroupPtr, true); } GroupNodeSet.KeySort([](const ENetEventNodeType& A, const ENetEventNodeType& B) { return A < B; }); // sort groups by type GroupNodeSet.GenerateValueArray(GroupNodes); } // Creates one group for one letter. else if (GroupingMode == ENetEventGroupingMode::ByName) { TMap GroupNodeSet; for (const FNetEventNodePtr& NodePtr: NetEventNodes) { FString FirstLetterStr(NodePtr->GetName().GetPlainNameString().Left(1).ToUpper()); const TCHAR FirstLetter = FirstLetterStr[0]; FNetEventNodePtr GroupPtr = GroupNodeSet.FindRef(FirstLetter); if (!GroupPtr) { const FName GroupName(FirstLetterStr); GroupPtr = GroupNodeSet.Add(FirstLetter, MakeShared(GroupName)); } GroupPtr->AddChildAndSetGroupPtr(NodePtr); } GroupNodeSet.KeySort([](const TCHAR& A, const TCHAR& B) { return A < B; }); // sort groups alphabetically GroupNodeSet.GenerateValueArray(GroupNodes); } // Creates one group for each level. else if (GroupingMode == ENetEventGroupingMode::ByLevel) { TMap GroupNodeSet; for (const FNetEventNodePtr& NodePtr: NetEventNodes) { const uint32 Level = NodePtr->GetLevel(); FNetEventNodePtr GroupPtr = GroupNodeSet.FindRef(Level); if (!GroupPtr) { const FName GroupName(*FString::Printf(TEXT("Level %d"), Level)); GroupPtr = GroupNodeSet.Add(Level, MakeShared(GroupName)); } GroupPtr->AddChildAndSetGroupPtr(NodePtr); TreeView->SetItemExpansion(GroupPtr, true); } GroupNodeSet.KeySort([](const uint32& A, const uint32& B) { return A < B; }); // sort groups by level GroupNodeSet.GenerateValueArray(GroupNodes); } } //////////////////////////////////////////////////////////////////////////////////////////////////// void SNetStatsView::CreateGroupByOptionsSources() { GroupByOptionsSource.Reset(3); // Must be added in order of elements in the ENetEventGroupingMode. GroupByOptionsSource.Add(MakeShared(ENetEventGroupingMode::Flat)); GroupByOptionsSource.Add(MakeShared(ENetEventGroupingMode::ByName)); // GroupByOptionsSource.Add(MakeShared(ENetEventGroupingMode::ByType)); GroupByOptionsSource.Add(MakeShared(ENetEventGroupingMode::ByLevel)); ENetEventGroupingModePtr* GroupingModePtrPtr = GroupByOptionsSource.FindByPredicate([&](const ENetEventGroupingModePtr InGroupingModePtr) { return *InGroupingModePtr == GroupingMode; }); if (GroupingModePtrPtr != nullptr) { GroupByComboBox->SetSelectedItem(*GroupingModePtrPtr); } GroupByComboBox->RefreshOptions(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void SNetStatsView::GroupBy_OnSelectionChanged(TSharedPtr NewGroupingMode, ESelectInfo::Type SelectInfo) { if (SelectInfo != ESelectInfo::Direct) { GroupingMode = *NewGroupingMode; CreateGroups(); SortTreeNodes(); ApplyFiltering(); } } //////////////////////////////////////////////////////////////////////////////////////////////////// TSharedRef SNetStatsView::GroupBy_OnGenerateWidget(TSharedPtr InGroupingMode) const { return SNew(STextBlock) .Text(NetEventNodeGroupingHelper::ToText(*InGroupingMode)) .ToolTipText(NetEventNodeGroupingHelper::ToDescription(*InGroupingMode)); } //////////////////////////////////////////////////////////////////////////////////////////////////// FText SNetStatsView::GroupBy_GetSelectedText() const { return NetEventNodeGroupingHelper::ToText(GroupingMode); } //////////////////////////////////////////////////////////////////////////////////////////////////// FText SNetStatsView::GroupBy_GetSelectedTooltipText() const { return NetEventNodeGroupingHelper::ToDescription(GroupingMode); } //////////////////////////////////////////////////////////////////////////////////////////////////// // Sorting //////////////////////////////////////////////////////////////////////////////////////////////////// const FName SNetStatsView::GetDefaultColumnBeingSorted() { return FNetStatsViewColumns::TotalInclusiveSizeColumnID; } //////////////////////////////////////////////////////////////////////////////////////////////////// const EColumnSortMode::Type SNetStatsView::GetDefaultColumnSortMode() { return EColumnSortMode::Descending; } //////////////////////////////////////////////////////////////////////////////////////////////////// void SNetStatsView::CreateSortings() { AvailableSorters.Reset(); CurrentSorter = nullptr; for (const TSharedRef& ColumnRef: Table->GetColumns()) { if (ColumnRef->CanBeSorted()) { TSharedPtr SorterPtr = ColumnRef->GetValueSorter(); if (ensure(SorterPtr.IsValid())) { AvailableSorters.Add(SorterPtr); } } } UpdateCurrentSortingByColumn(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void SNetStatsView::UpdateCurrentSortingByColumn() { TSharedPtr ColumnPtr = Table->FindColumn(ColumnBeingSorted); CurrentSorter = ColumnPtr.IsValid() ? ColumnPtr->GetValueSorter() : nullptr; } //////////////////////////////////////////////////////////////////////////////////////////////////// void SNetStatsView::SortTreeNodes() { if (CurrentSorter.IsValid()) { // Sort groups (always by name). TArray SortedGroupNodes; for (const FNetEventNodePtr& NodePtr: GroupNodes) { SortedGroupNodes.Add(NodePtr); } TSharedPtr Sorter = CurrentSorter; Insights::ESortMode SortMode = (ColumnSortMode == EColumnSortMode::Type::Descending) ? Insights::ESortMode::Descending : Insights::ESortMode::Ascending; if (CurrentSorter->GetName() != FName(TEXT("ByName"))) { Sorter = MakeShared(Table->GetColumns()[0]); SortMode = Insights::ESortMode::Ascending; } Sorter->Sort(SortedGroupNodes, SortMode); GroupNodes.Reset(); for (const Insights::FBaseTreeNodePtr& NodePtr: SortedGroupNodes) { GroupNodes.Add(StaticCastSharedPtr(NodePtr)); } // Sort nodes in each group. for (FNetEventNodePtr& Root: GroupNodes) { SortTreeNodesRec(*Root, *CurrentSorter); } } } //////////////////////////////////////////////////////////////////////////////////////////////////// void SNetStatsView::SortTreeNodesRec(FNetEventNode& Node, const Insights::ITableCellValueSorter& Sorter) { if (ColumnSortMode == EColumnSortMode::Type::Descending) { Node.SortChildrenDescending(Sorter); } else // if (ColumnSortMode == EColumnSortMode::Type::Ascending) { Node.SortChildrenAscending(Sorter); } for (Insights::FBaseTreeNodePtr ChildPtr: Node.GetChildren()) { // if (ChildPtr->IsGroup()) if (ChildPtr->GetChildren().Num() > 0) { SortTreeNodesRec(*StaticCastSharedPtr(ChildPtr), Sorter); } } } //////////////////////////////////////////////////////////////////////////////////////////////////// EColumnSortMode::Type SNetStatsView::GetSortModeForColumn(const FName ColumnId) const { if (ColumnBeingSorted != ColumnId) { return EColumnSortMode::None; } return ColumnSortMode; } //////////////////////////////////////////////////////////////////////////////////////////////////// void SNetStatsView::SetSortModeForColumn(const FName& ColumnId, const EColumnSortMode::Type SortMode) { ColumnBeingSorted = ColumnId; ColumnSortMode = SortMode; UpdateCurrentSortingByColumn(); SortTreeNodes(); ApplyFiltering(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void SNetStatsView::OnSortModeChanged(const EColumnSortPriority::Type SortPriority, const FName& ColumnId, const EColumnSortMode::Type SortMode) { SetSortModeForColumn(ColumnId, SortMode); TreeView_Refresh(); } //////////////////////////////////////////////////////////////////////////////////////////////////// // SortMode action (HeaderMenu) //////////////////////////////////////////////////////////////////////////////////////////////////// bool SNetStatsView::HeaderMenu_SortMode_IsChecked(const FName ColumnId, const EColumnSortMode::Type InSortMode) { return ColumnBeingSorted == ColumnId && ColumnSortMode == InSortMode; } //////////////////////////////////////////////////////////////////////////////////////////////////// bool SNetStatsView::HeaderMenu_SortMode_CanExecute(const FName ColumnId, const EColumnSortMode::Type InSortMode) const { const Insights::FTableColumn& Column = *Table->FindColumnChecked(ColumnId); return Column.CanBeSorted(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void SNetStatsView::HeaderMenu_SortMode_Execute(const FName ColumnId, const EColumnSortMode::Type InSortMode) { SetSortModeForColumn(ColumnId, InSortMode); TreeView_Refresh(); } //////////////////////////////////////////////////////////////////////////////////////////////////// // SortMode action (ContextMenu) //////////////////////////////////////////////////////////////////////////////////////////////////// bool SNetStatsView::ContextMenu_SortMode_IsChecked(const EColumnSortMode::Type InSortMode) { return ColumnSortMode == InSortMode; } //////////////////////////////////////////////////////////////////////////////////////////////////// bool SNetStatsView::ContextMenu_SortMode_CanExecute(const EColumnSortMode::Type InSortMode) const { return true; // ColumnSortMode != InSortMode; } //////////////////////////////////////////////////////////////////////////////////////////////////// void SNetStatsView::ContextMenu_SortMode_Execute(const EColumnSortMode::Type InSortMode) { SetSortModeForColumn(ColumnBeingSorted, InSortMode); TreeView_Refresh(); } //////////////////////////////////////////////////////////////////////////////////////////////////// // SortByColumn action (ContextMenu) //////////////////////////////////////////////////////////////////////////////////////////////////// bool SNetStatsView::ContextMenu_SortByColumn_IsChecked(const FName ColumnId) { return ColumnId == ColumnBeingSorted; } //////////////////////////////////////////////////////////////////////////////////////////////////// bool SNetStatsView::ContextMenu_SortByColumn_CanExecute(const FName ColumnId) const { return true; // ColumnId != ColumnBeingSorted; } //////////////////////////////////////////////////////////////////////////////////////////////////// void SNetStatsView::ContextMenu_SortByColumn_Execute(const FName ColumnId) { SetSortModeForColumn(ColumnId, EColumnSortMode::Descending); TreeView_Refresh(); } //////////////////////////////////////////////////////////////////////////////////////////////////// // ShowColumn action //////////////////////////////////////////////////////////////////////////////////////////////////// bool SNetStatsView::CanShowColumn(const FName ColumnId) const { return true; } //////////////////////////////////////////////////////////////////////////////////////////////////// void SNetStatsView::ShowColumn(const FName ColumnId) { Insights::FTableColumn& Column = *Table->FindColumnChecked(ColumnId); Column.Show(); SHeaderRow::FColumn::FArguments ColumnArgs; ColumnArgs .ColumnId(Column.GetId()) .DefaultLabel(Column.GetShortName()) .HAlignHeader(HAlign_Fill) .VAlignHeader(VAlign_Fill) .HeaderContentPadding(FMargin(2.0f)) .HAlignCell(HAlign_Fill) .VAlignCell(VAlign_Fill) .SortMode(this, &SNetStatsView::GetSortModeForColumn, Column.GetId()) .OnSort(this, &SNetStatsView::OnSortModeChanged) .ManualWidth(Column.GetInitialWidth()) .FixedWidth(Column.IsFixedWidth() ? Column.GetInitialWidth() : TOptional()) .HeaderContent() [SNew(SBox) .ToolTip(SNetStatsViewTooltip::GetColumnTooltip(Column)) .HAlign(Column.GetHorizontalAlignment()) .VAlign(VAlign_Center) [SNew(STextBlock) .Text(this, &SNetStatsView::GetColumnHeaderText, Column.GetId())]] .MenuContent() [TreeViewHeaderRow_GenerateColumnMenu(Column)]; int32 ColumnIndex = 0; const int32 NewColumnPosition = Table->GetColumnPositionIndex(ColumnId); const int32 NumColumns = TreeViewHeaderRow->GetColumns().Num(); for (; ColumnIndex < NumColumns; ColumnIndex++) { const SHeaderRow::FColumn& CurrentColumn = TreeViewHeaderRow->GetColumns()[ColumnIndex]; const int32 CurrentColumnPosition = Table->GetColumnPositionIndex(CurrentColumn.ColumnId); if (NewColumnPosition < CurrentColumnPosition) { break; } } TreeViewHeaderRow->InsertColumn(ColumnArgs, ColumnIndex); } //////////////////////////////////////////////////////////////////////////////////////////////////// // HideColumn action //////////////////////////////////////////////////////////////////////////////////////////////////// bool SNetStatsView::CanHideColumn(const FName ColumnId) const { const Insights::FTableColumn& Column = *Table->FindColumnChecked(ColumnId); return Column.CanBeHidden(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void SNetStatsView::HideColumn(const FName ColumnId) { Insights::FTableColumn& Column = *Table->FindColumnChecked(ColumnId); Column.Hide(); TreeViewHeaderRow->RemoveColumn(ColumnId); } //////////////////////////////////////////////////////////////////////////////////////////////////// // ToggleColumn action //////////////////////////////////////////////////////////////////////////////////////////////////// bool SNetStatsView::IsColumnVisible(const FName ColumnId) { const Insights::FTableColumn& Column = *Table->FindColumnChecked(ColumnId); return Column.IsVisible(); } //////////////////////////////////////////////////////////////////////////////////////////////////// bool SNetStatsView::CanToggleColumnVisibility(const FName ColumnId) const { const Insights::FTableColumn& Column = *Table->FindColumnChecked(ColumnId); return !Column.IsVisible() || Column.CanBeHidden(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void SNetStatsView::ToggleColumnVisibility(const FName ColumnId) { const Insights::FTableColumn& Column = *Table->FindColumnChecked(ColumnId); if (Column.IsVisible()) { HideColumn(ColumnId); } else { ShowColumn(ColumnId); } } //////////////////////////////////////////////////////////////////////////////////////////////////// // "Show All Columns" action (ContextMenu) //////////////////////////////////////////////////////////////////////////////////////////////////// bool SNetStatsView::ContextMenu_ShowAllColumns_CanExecute() const { return true; } //////////////////////////////////////////////////////////////////////////////////////////////////// void SNetStatsView::ContextMenu_ShowAllColumns_Execute() { ColumnBeingSorted = GetDefaultColumnBeingSorted(); ColumnSortMode = GetDefaultColumnSortMode(); UpdateCurrentSortingByColumn(); for (const TSharedRef& ColumnRef: Table->GetColumns()) { const Insights::FTableColumn& Column = *ColumnRef; if (!Column.IsVisible()) { ShowColumn(Column.GetId()); } } } //////////////////////////////////////////////////////////////////////////////////////////////////// // "Show Min/Max/Median Columns" action (ContextMenu) //////////////////////////////////////////////////////////////////////////////////////////////////// bool SNetStatsView::ContextMenu_ShowMinMaxMedColumns_CanExecute() const { return true; } //////////////////////////////////////////////////////////////////////////////////////////////////// void SNetStatsView::ContextMenu_ShowMinMaxMedColumns_Execute() { TSet Preset = { FNetStatsViewColumns::NameColumnID, // FNetStatsViewColumns::MetaGroupNameColumnID, // FNetStatsViewColumns::TypeColumnID, FNetStatsViewColumns::InstanceCountColumnID, FNetStatsViewColumns::TotalInclusiveSizeColumnID, FNetStatsViewColumns::MaxInclusiveSizeColumnID, FNetStatsViewColumns::AverageInclusiveSizeColumnID, FNetStatsViewColumns::TotalExclusiveSizeColumnID, FNetStatsViewColumns::MaxExclusiveSizeColumnID, // FNetStatsViewColumns::AverageExclusiveSizeColumnID, }; ColumnBeingSorted = FNetStatsViewColumns::TotalInclusiveSizeColumnID; ColumnSortMode = EColumnSortMode::Descending; UpdateCurrentSortingByColumn(); for (const TSharedRef& ColumnRef: Table->GetColumns()) { const Insights::FTableColumn& Column = *ColumnRef; const bool bShouldBeVisible = Preset.Contains(Column.GetId()); if (bShouldBeVisible && !Column.IsVisible()) { ShowColumn(Column.GetId()); } else if (!bShouldBeVisible && Column.IsVisible()) { HideColumn(Column.GetId()); } } } //////////////////////////////////////////////////////////////////////////////////////////////////// // ResetColumns action (ContextMenu) //////////////////////////////////////////////////////////////////////////////////////////////////// bool SNetStatsView::ContextMenu_ResetColumns_CanExecute() const { return true; } //////////////////////////////////////////////////////////////////////////////////////////////////// void SNetStatsView::ContextMenu_ResetColumns_Execute() { ColumnBeingSorted = GetDefaultColumnBeingSorted(); ColumnSortMode = GetDefaultColumnSortMode(); UpdateCurrentSortingByColumn(); for (const TSharedRef& ColumnRef: Table->GetColumns()) { const Insights::FTableColumn& Column = *ColumnRef; if (Column.ShouldBeVisible() && !Column.IsVisible()) { ShowColumn(Column.GetId()); } else if (!Column.ShouldBeVisible() && Column.IsVisible()) { HideColumn(Column.GetId()); } } } //////////////////////////////////////////////////////////////////////////////////////////////////// void SNetStatsView::Reset() { GameInstanceIndex = 0; ConnectionIndex = 0; ConnectionMode = Trace::ENetProfilerConnectionMode::Outgoing; StatsPacketStartIndex = 0; StatsPacketEndIndex = 0; StatsStartPosition = 0; StatsEndPosition = 0; RebuildTree(true); } //////////////////////////////////////////////////////////////////////////////////////////////////// void SNetStatsView::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) { // Check if we need to update the lists of net events, but not too often. const uint64 Time = FPlatformTime::Cycles64(); if (Time > NextTimestamp) { RebuildTree(false); const uint64 WaitTime = static_cast(0.5 / FPlatformTime::GetSecondsPerCycle64()); // 500ms NextTimestamp = Time + WaitTime; } } //////////////////////////////////////////////////////////////////////////////////////////////////// void SNetStatsView::RebuildTree(bool bResync) { FStopwatch SyncStopwatch; FStopwatch Stopwatch; Stopwatch.Start(); if (bResync) { NetEventNodes.Empty(); } const uint32 PreviousNodeCount = NetEventNodes.Num(); SyncStopwatch.Start(); if (Session.IsValid()) { Trace::FAnalysisSessionReadScope SessionReadScope(*Session.Get()); const Trace::INetProfilerProvider& NetProfilerProvider = Trace::ReadNetProfilerProvider(*Session.Get()); NetProfilerProvider.ReadEventTypes([this, &bResync, &NetProfilerProvider](const Trace::FNetProfilerEventType* NetEvents, uint64 InNetEventCount) { const uint32 NetEventCount = static_cast(InNetEventCount); if (NetEventCount != NetEventNodes.Num()) { NetEventNodes.Empty(NetEventCount); for (uint32 Index = 0; Index < NetEventCount; ++Index) { const Trace::FNetProfilerEventType& NetEvent = NetEvents[Index]; ensure(NetEvent.EventTypeIndex == Index); const TCHAR* NamePtr = nullptr; NetProfilerProvider.ReadName(NetEvent.NameIndex, [&NamePtr](const Trace::FNetProfilerName& InName) { NamePtr = InName.Name; }); const FName Name(NamePtr); const ENetEventNodeType Type = ENetEventNodeType::NetEvent; FNetEventNodePtr NetEventNodePtr = MakeShared(NetEvent.EventTypeIndex, Name, Type, NetEvent.Level); NetEventNodes.Add(NetEventNodePtr); } ensure(NetEventNodes.Num() == NetEventCount); } }); } SyncStopwatch.Stop(); if (bResync || NetEventNodes.Num() != PreviousNodeCount) { UpdateTree(); UpdateStatsInternal(); // Save selection. TArray SelectedItems; TreeView->GetSelectedItems(SelectedItems); TreeView->RebuildList(); // Restore selection. if (SelectedItems.Num() > 0) { TreeView->ClearSelection(); for (FNetEventNodePtr& NodePtr: SelectedItems) { NodePtr = GetNetEventNode(NodePtr->GetEventTypeIndex()); } SelectedItems.RemoveAll([](const FNetEventNodePtr& NodePtr) { return !NodePtr.IsValid(); }); if (SelectedItems.Num() > 0) { TreeView->SetItemSelection(SelectedItems, true); TreeView->RequestScrollIntoView(SelectedItems.Last()); } } } Stopwatch.Stop(); const double TotalTime = Stopwatch.GetAccumulatedTime(); if (TotalTime > 0.01) { const double SyncTime = SyncStopwatch.GetAccumulatedTime(); UE_LOG(NetworkingProfiler, Log, TEXT("[NetStats] Tree view rebuilt in %.3fs (%.3fs + %.3fs) --> %d net events (%d added)"), TotalTime, SyncTime, TotalTime - SyncTime, NetEventNodes.Num(), NetEventNodes.Num() - PreviousNodeCount); } } //////////////////////////////////////////////////////////////////////////////////////////////////// void SNetStatsView::ResetStats() { GameInstanceIndex = 0; ConnectionIndex = 0; ConnectionMode = Trace::ENetProfilerConnectionMode::Outgoing; StatsPacketStartIndex = 0; StatsPacketEndIndex = 0; StatsStartPosition = 0; StatsEndPosition = 0; UpdateStatsInternal(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void SNetStatsView::UpdateStats(uint32 InGameInstanceIndex, uint32 InConnectionIndex, Trace::ENetProfilerConnectionMode InConnectionMode, uint32 InStatsPacketStartIndex, uint32 InStatsPacketEndIndex, uint32 InStatsStartPosition, uint32 InStatsEndPosition) { GameInstanceIndex = InGameInstanceIndex; ConnectionIndex = InConnectionIndex; ConnectionMode = InConnectionMode; StatsPacketStartIndex = InStatsPacketStartIndex; StatsPacketEndIndex = InStatsPacketEndIndex; StatsStartPosition = InStatsStartPosition; StatsEndPosition = InStatsEndPosition; UpdateStatsInternal(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void SNetStatsView::UpdateStatsInternal() { if (StatsPacketStartIndex >= StatsPacketEndIndex || // no packet selected? (StatsPacketEndIndex - StatsPacketStartIndex == 1 && StatsStartPosition >= StatsEndPosition)) // single packet, but invalid bit range? { // keep previous aggregated stats return; } FStopwatch AggregationStopwatch; FStopwatch Stopwatch; Stopwatch.Start(); for (const FNetEventNodePtr& NetEventNodePtr: NetEventNodes) { NetEventNodePtr->ResetAggregatedStats(); } if (Session.IsValid()) { TUniquePtr> AggregationResultTable; AggregationStopwatch.Start(); { Trace::FAnalysisSessionReadScope SessionReadScope(*Session.Get()); const Trace::INetProfilerProvider& NetProfilerProvider = Trace::ReadNetProfilerProvider(*Session.Get()); // CreateAggregation requires [PacketStartIndex, PacketEndIndex] as inclusive interval and [StartPos, EndPos) as exclusive interval. AggregationResultTable.Reset(NetProfilerProvider.CreateAggregation(ConnectionIndex, ConnectionMode, StatsPacketStartIndex, StatsPacketEndIndex - 1, StatsStartPosition, StatsEndPosition)); } AggregationStopwatch.Stop(); if (AggregationResultTable.IsValid()) { TUniquePtr> TableReader(AggregationResultTable->CreateReader()); while (TableReader->IsValid()) { const Trace::FNetProfilerAggregatedStats* Row = TableReader->GetCurrentRow(); FNetEventNodePtr NetEventNodePtr = GetNetEventNode(Row->EventTypeIndex); if (NetEventNodePtr) { NetEventNodePtr->SetAggregatedStats(*Row); TSharedPtr TableRowPtr = TreeView->WidgetFromItem(NetEventNodePtr); if (TableRowPtr.IsValid()) { TSharedPtr RowPtr = StaticCastSharedPtr(TableRowPtr); RowPtr->InvalidateContent(); } } TableReader->NextRow(); } } } UpdateTree(); const TArray SelectedNodes = TreeView->GetSelectedItems(); if (SelectedNodes.Num() > 0) { TreeView->RequestScrollIntoView(SelectedNodes[0]); } Stopwatch.Stop(); const double TotalTime = Stopwatch.GetAccumulatedTime(); const double AggregationTime = AggregationStopwatch.GetAccumulatedTime(); UE_LOG(NetworkingProfiler, Log, TEXT("[NetStats] Aggregated stats updated in %.4fs (%.4fs + %.4fs)"), TotalTime, AggregationTime, TotalTime - AggregationTime); } //////////////////////////////////////////////////////////////////////////////////////////////////// FNetEventNodePtr SNetStatsView::GetNetEventNode(uint32 EventTypeIndex) const { return (EventTypeIndex < (uint32)NetEventNodes.Num()) ? NetEventNodes[EventTypeIndex] : nullptr; } //////////////////////////////////////////////////////////////////////////////////////////////////// void SNetStatsView::SelectNetEventNode(uint32 EventTypeIndex) { FNetEventNodePtr NodePtr = GetNetEventNode(EventTypeIndex); if (NodePtr) { TreeView->SetSelection(NodePtr); TreeView->RequestScrollIntoView(NodePtr); } } //////////////////////////////////////////////////////////////////////////////////////////////////// #undef LOCTEXT_NAMESPACE