// Copyright Epic Games, Inc. All Rights Reserved. #include "Widgets/SEventGraph.h" #if STATS #include "Widgets/Layout/SSplitter.h" #include "Containers/MapBuilder.h" #include "Widgets/SOverlay.h" #include "SlateOptMacros.h" #include "Framework/Application/SlateApplication.h" #include "Widgets/Layout/SSeparator.h" #include "Widgets/Images/SImage.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "Widgets/Input/SButton.h" #include "Widgets/Input/SComboBox.h" #include "Widgets/Input/SComboButton.h" #include "Widgets/Input/SCheckBox.h" #include "Styling/AppStyle.h" #include "Widgets/StatDragDropOp.h" #include "Widgets/SEventGraphTooltip.h" #include "Widgets/Input/SSearchBox.h" #include "HAL/PlatformApplicationMisc.h" #include "ProfilerStyle.h" #define LOCTEXT_NAMESPACE "SEventGraph" namespace EEventGraphViewModes { FText ToName( const Type EventGraphViewMode ) { switch( EventGraphViewMode ) { case Hierarchical: return LOCTEXT("ViewMode_Name_Hierarchical", "Hierarchical"); case FlatInclusive: return LOCTEXT("ViewMode_Name_FlatInclusive", "Inclusive"); case FlatInclusiveCoalesced: return LOCTEXT("ViewMode_Name_FlatInclusiveCoalesced", "Inclusive"); case FlatExclusive: return LOCTEXT("ViewMode_Name_FlatExclusive", "Exclusive"); case FlatExclusiveCoalesced: return LOCTEXT("ViewMode_Name_FlatExclusiveCoalesced", "Exclusive" ); case ClassAggregate: return LOCTEXT("ViewMode_Name_ClassAggregate", "ClassAggregate"); default: return LOCTEXT("InvalidOrMax", "InvalidOrMax"); } } FText ToDescription( const Type EventGraphViewMode ) { switch( EventGraphViewMode ) { case Hierarchical: return LOCTEXT("ViewMode_Desc_Hierarchical", "Hierarchical tree view of the events"); case FlatInclusive: return LOCTEXT("ViewMode_Desc_Flat", "Flat list of the events, sorted by the inclusive time"); case FlatInclusiveCoalesced: return LOCTEXT("ViewMode_Desc_FlatCoalesced", "Flat list of the events coalesced by the event name, sorted by the inclusive time"); case FlatExclusive: return LOCTEXT("ViewMode_Desc_FlatExclusive", "Flat list of the events, sorted by the exclusive time"); case FlatExclusiveCoalesced: return LOCTEXT("ViewMode_Desc_FlatExclusiveCoalesced", "Flat list of the events coalesced by the event name, sorted by the exclusive time"); case ClassAggregate: return LOCTEXT("ViewMode_Desc_ClassAggregate", "ClassAggregate @TBD"); default: return LOCTEXT("InvalidOrMax", "InvalidOrMax"); } } FName ToBrushName( const Type EventGraphViewMode ) { switch( EventGraphViewMode ) { case Hierarchical: return TEXT("Profiler.EventGraph.HierarchicalIcon"); case FlatInclusive: return TEXT("Profiler.EventGraph.FlatIcon"); case FlatInclusiveCoalesced: return TEXT("Profiler.EventGraph.FlatCoalescedIcon"); case FlatExclusive: return TEXT("Profiler.EventGraph.FlatIcon"); case FlatExclusiveCoalesced: return TEXT("Profiler.EventGraph.FlatCoalescedIcon"); default: return NAME_None; } } } struct FEventGraphColumns { /** Default constructor. */ FEventGraphColumns() { // Make event property is initialized. FEventGraphSample::InitializePropertyManagement(); Collection[(uint32)EEventPropertyIndex::StatName] = FEventGraphColumn ( EEventPropertyIndex::StatName, TEXT( "name" ), LOCTEXT( "EventNameColumnTitle", "Event Name" ), LOCTEXT( "EventNameColumnDesc", "Name of the event" ), false, true, true, false, false, HAlign_Left, 0.0f ); Collection[(uint32)EEventPropertyIndex::InclusiveTimeMS] = FEventGraphColumn ( EEventPropertyIndex::InclusiveTimeMS, TEXT( "inc" ), LOCTEXT( "InclusiveTimeMSTitle", "Inc Time (MS)" ), LOCTEXT( "InclusiveTimeMSDesc", "Duration of the sample and its children, in milliseconds" ), false, true, true, true, true, HAlign_Right, 72.0f ); Collection[(uint32)EEventPropertyIndex::InclusiveTimePct] = FEventGraphColumn ( EEventPropertyIndex::InclusiveTimePct, TEXT( "inc%" ), LOCTEXT( "InclusiveTimePercentageTitle", "Inc Time (%)" ), LOCTEXT( "InclusiveTimePercentageDesc", "Duration of the sample and its children as percent of the caller" ), true, true, true, false, false, HAlign_Right, 72.0f ); Collection[(uint32)EEventPropertyIndex::ExclusiveTimeMS] = FEventGraphColumn ( EEventPropertyIndex::ExclusiveTimeMS, TEXT( "exc" ), LOCTEXT( "ExclusiveTimeMSTitle", "Exc Time (MS)" ), LOCTEXT( "ExclusiveTimeMSDesc", "Exclusive time of this event, in milliseconds" ), false, true, true, true, false, HAlign_Right, 72.0f ); Collection[(uint32)EEventPropertyIndex::ExclusiveTimePct] = FEventGraphColumn ( EEventPropertyIndex::ExclusiveTimePct, TEXT( "exc%" ), LOCTEXT( "ExclusiveTimePercentageTitle", "Exc Time (%)" ), LOCTEXT( "ExclusiveTimePercentageDesc", "Exclusive time of this event as percent of this call's inclusive time" ), true, true, true, false, false, HAlign_Right, 72.0f ); Collection[(uint32)EEventPropertyIndex::NumCallsPerFrame] = FEventGraphColumn ( EEventPropertyIndex::NumCallsPerFrame, TEXT( "calls" ), LOCTEXT( "CallsPerFrameTitle", "Calls" ), LOCTEXT( "CallsPerFrameDesc", "Number of times this event was called" ), false, true, true, true, false, HAlign_Right, 48.0f ); // Fake column used as a default column for NAME_None Collection[(uint32)EEventPropertyIndex::None] = FEventGraphColumn ( EEventPropertyIndex::None, TEXT( "None" ), LOCTEXT( "None", "None" ), LOCTEXT( "None", "None" ), false, false, false, false, false, HAlign_Left, 0.0f ); ColumnNameToIndexMapping = TMapBuilder() .Add( TEXT( "StatName" ), &Collection[(uint32)EEventPropertyIndex::StatName] ) .Add( TEXT( "InclusiveTimeMS" ), &Collection[(uint32)EEventPropertyIndex::InclusiveTimeMS] ) .Add( TEXT( "InclusiveTimePct" ), &Collection[(uint32)EEventPropertyIndex::InclusiveTimePct] ) .Add( TEXT( "ExclusiveTimeMS" ), &Collection[(uint32)EEventPropertyIndex::ExclusiveTimeMS] ) .Add( TEXT( "ExclusiveTimePct" ), &Collection[(uint32)EEventPropertyIndex::ExclusiveTimePct] ) .Add( TEXT( "NumCallsPerFrame" ), &Collection[(uint32)EEventPropertyIndex::NumCallsPerFrame] ) .Add( NAME_None, &Collection[(uint32)EEventPropertyIndex::None] ) ; } static constexpr uint32 NumColumns = (uint32)EEventPropertyIndex::None + 1 ; /** Contains basic information about columns used in the event graph widget. Names should be localized. */ FEventGraphColumn Collection[NumColumns]; // Generated from a XLSX file. TMap ColumnNameToIndexMapping; static const FEventGraphColumns& Get() { static FEventGraphColumns Instance; return Instance; } }; /*----------------------------------------------------------------------------- SEventTreeItem -----------------------------------------------------------------------------*/ DECLARE_DELEGATE_TwoParams( FSetHoveredTableCell, const FName /*ColumnID*/, const FEventGraphSamplePtr /*SamplePtr*/ ); DECLARE_DELEGATE_RetVal_OneParam( bool, FIsColumnVisibleDelegate, const FName /*ColumnID*/ ); DECLARE_DELEGATE_RetVal_OneParam( EHorizontalAlignment, FGetColumnOutlineHAlignmentDelegate, const FName /*ColumnID*/ ); class SEventGraphTableCell : public SCompoundWidget { public: SLATE_BEGIN_ARGS( SEventGraphTableCell ){} SLATE_EVENT( FSetHoveredTableCell, OnSetHoveredTableCell ) SLATE_ARGUMENT( FEventGraphSamplePtr, EventPtr ) SLATE_ARGUMENT( FName, ColumnID ) SLATE_ARGUMENT( bool, IsEventNameColumn ) SLATE_END_ARGS() /** * Construct this widget. * * @param InArgs - the declaration data for this widget. */ void Construct( const FArguments& InArgs, const TSharedRef& TableRow, const TWeakPtr& InOwnerEventGraph ) { SetHoveredTableCellDelegate = InArgs._OnSetHoveredTableCell; EventPtr = InArgs._EventPtr; OwnerEventGraph = InOwnerEventGraph; ColumnID = InArgs._ColumnID; ChildSlot [ GenerateWidgetForColumnID( ColumnID, InArgs._IsEventNameColumn, TableRow ) ]; } protected: BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION TSharedRef GenerateWidgetForColumnID( const FName& InColumnID, const bool bIsEventNameColumn, const TSharedRef& TableRow ) { const FEventGraphColumn& Column = *FEventGraphColumns::Get().ColumnNameToIndexMapping.FindChecked( InColumnID ); if( bIsEventNameColumn ) { return SNew(SHorizontalBox) +SHorizontalBox::Slot() .AutoWidth() .HAlign(HAlign_Right) .VAlign(VAlign_Center) [ SNew( SExpanderArrow, TableRow ) ] +SHorizontalBox::Slot() .AutoWidth() .HAlign(HAlign_Center) .VAlign(VAlign_Center) [ SNew( SImage ) .Visibility( this, &SEventGraphTableCell::GetHotPathIconVisibility ) .Image(FProfilerStyle::Get().GetBrush(TEXT("Profiler.EventGraph.HotPathSmall")) ) ] +SHorizontalBox::Slot() .AutoWidth() .VAlign( VAlign_Center ) .HAlign( Column.HorizontalAlignment ) .Padding( FMargin( 2.0f, 0.0f ) ) [ SNew( STextBlock ) .Text( FText::FromName(EventPtr->_StatName) ) .TextStyle(FProfilerStyle::Get(), TEXT("Profiler.Tooltip") ) .ColorAndOpacity( this, &SEventGraphTableCell::GetColorAndOpacity ) .ShadowColorAndOpacity( this, &SEventGraphTableCell::GetShadowColorAndOpacity ) ] +SHorizontalBox::Slot() .AutoWidth() .HAlign(HAlign_Center) .VAlign(VAlign_Center) [ SNew( SButton ) .ButtonStyle( FAppStyle::Get(), TEXT("HoverHintOnly") ) .ContentPadding( 0.0f ) .IsFocusable( false ) .OnClicked( this, &SEventGraphTableCell::ExpandCulledEvents_OnClicked ) [ SNew( SImage ) .Visibility( this, &SEventGraphTableCell::GetCulledEventsIconVisibility ) .Image(FProfilerStyle::Get().GetBrush("Profiler.EventGraph.HasCulledEventsSmall") ) .ToolTipText( LOCTEXT("HasCulledEvents_TT","This event contains culled children, if you want to see all children, please disable culling or use function details, or press this icon") ) ] ] +SHorizontalBox::Slot() .AutoWidth() .HAlign(HAlign_Center) .VAlign(VAlign_Center) [ SNew( SImage ) .Visibility( this, &SEventGraphTableCell::GetHintIconVisibility ) .Image(FProfilerStyle::Get().GetBrush("Profiler.Tooltip.HintIcon10") ) .ToolTip( SEventGraphTooltip::GetTableCellTooltip( EventPtr ) ) ]; } else { const FString FormattedValue = EventPtr->GetFormattedValue( Column.Index ); return SNew(SHorizontalBox) +SHorizontalBox::Slot() .FillWidth( 1.0f ) .VAlign( VAlign_Center ) .HAlign( Column.HorizontalAlignment ) .Padding( FMargin( 2.0f, 0.0f ) ) [ SNew( STextBlock ) .Text( FText::FromString(FormattedValue) ) .TextStyle(FProfilerStyle::Get(), TEXT("Profiler.Tooltip") ) .ColorAndOpacity( this, &SEventGraphTableCell::GetColorAndOpacity ) .ShadowColorAndOpacity( this, &SEventGraphTableCell::GetShadowColorAndOpacity ) ]; } } END_SLATE_FUNCTION_BUILD_OPTIMIZATION /** * The system will use this event to notify a widget that the cursor has entered it. This event is NOT bubbled. * * @param MyGeometry The Geometry of the widget receiving the event * @param MouseEvent Information about the input event */ virtual void OnMouseEnter( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) override { SCompoundWidget::OnMouseEnter( MyGeometry, MouseEvent ); SetHoveredTableCellDelegate.ExecuteIfBound( ColumnID, EventPtr ); } /** * The system will use this event to notify a widget that the cursor has left it. This event is NOT bubbled. * * @param MouseEvent Information about the input event */ virtual void OnMouseLeave( const FPointerEvent& MouseEvent ) override { SCompoundWidget::OnMouseLeave( MouseEvent ); SetHoveredTableCellDelegate.ExecuteIfBound( NAME_None, nullptr ); } /** * Called during drag and drop when the drag enters a widget. * * Enter/Leave events in slate are meant as lightweight notifications. * So we do not want to capture mouse or set focus in response to these. * However, OnDragEnter must also support external APIs (e.g. OLE Drag/Drop) * Those require that we let them know whether we can handle the content * being dragged OnDragEnter. * * The concession is to return a can_handled/cannot_handle * boolean rather than a full FReply. * * @param MyGeometry The geometry of the widget receiving the event. * @param DragDropEvent The drag and drop event. * * @return A reply that indicated whether the contents of the DragDropEvent can potentially be processed by this widget. */ virtual void OnDragEnter( const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent ) override { SCompoundWidget::OnDragEnter( MyGeometry, DragDropEvent ); SetHoveredTableCellDelegate.ExecuteIfBound( ColumnID, EventPtr ); } /** * Called during drag and drop when the drag leaves a widget. * * @param DragDropEvent The drag and drop event. */ virtual void OnDragLeave( const FDragDropEvent& DragDropEvent ) override { SCompoundWidget::OnDragLeave( DragDropEvent ); SetHoveredTableCellDelegate.ExecuteIfBound( NAME_None, nullptr ); } EVisibility GetHotPathIconVisibility() const { const bool bIsHotPathIconVisible = EventPtr->_bIsHotPath; return bIsHotPathIconVisible ? EVisibility::Visible : EVisibility::Collapsed; } EVisibility GetHintIconVisibility() const { return IsHovered() ? EVisibility::Visible : EVisibility::Hidden; } EVisibility GetCulledEventsIconVisibility() const { return EventPtr->HasCulledChildren() ? EVisibility::Visible : EVisibility::Collapsed; } FSlateColor GetColorAndOpacity() const { const FLinearColor TextColor = EventPtr->_bIsFiltered ? FLinearColor(1.0f,1.0f,1.0f,0.5f) : FLinearColor::White; return TextColor; } FLinearColor GetShadowColorAndOpacity() const { const FLinearColor ShadowColor = EventPtr->_bIsFiltered ? FLinearColor(0.f,0.f,0.f,0.25f) : FLinearColor(0.f,0.f,0.f,0.5f); return ShadowColor; } FReply ExpandCulledEvents_OnClicked() { OwnerEventGraph.Pin()->ExpandCulledEvents( EventPtr ); return FReply::Handled(); } protected: FSetHoveredTableCell SetHoveredTableCellDelegate; /** A shared pointer to the event graph sample. */ FEventGraphSamplePtr EventPtr; /** The event graph that owns this event graph cell. */ TWeakPtr< IEventGraph > OwnerEventGraph; /** The ID of the column where this event graph belongs. */ FName ColumnID; }; /** Widget that represents a table row in the event graph widget. Generates widgets for each column on demand. */ class SEventGraphTableRow : public SMultiColumnTableRow< FEventGraphSamplePtr > { public: SLATE_BEGIN_ARGS( SEventGraphTableRow ){} SLATE_EVENT( FIsColumnVisibleDelegate, OnIsColumnVisible ) SLATE_EVENT( FSetHoveredTableCell, OnSetHoveredTableCell ) SLATE_EVENT( FGetColumnOutlineHAlignmentDelegate, OnGetColumnOutlineHAlignmentDelegate ) SLATE_ATTRIBUTE( FName, HighlightedEventName ) SLATE_ARGUMENT( FEventGraphSamplePtr, EventPtr ) SLATE_END_ARGS() /** * Construct this widget. Called by the SNew() Slate macro. * * @param InArgs - Declaration used by the SNew() macro to construct this widget. * @param InOwnerTableView - The owner table into which this row is being placed. */ void Construct( const FArguments& InArgs, const TSharedRef& InOwnerTableView, const TSharedRef& InOwnerEventGraph ) { IsColumnVisibleDelegate = InArgs._OnIsColumnVisible; SetHoveredTableCellDelegate = InArgs._OnSetHoveredTableCell; GetColumnOutlineHAlignmentDelegate = InArgs._OnGetColumnOutlineHAlignmentDelegate; HighlightedEventName = InArgs._HighlightedEventName; EventPtr = InArgs._EventPtr; OwnerEventGraph = InOwnerEventGraph; SMultiColumnTableRow< FEventGraphSamplePtr >::Construct( SMultiColumnTableRow< FEventGraphSamplePtr >::FArguments(), InOwnerTableView ); } /** * Users of SMultiColumnTableRow would usually some piece of data associated with it. * The type of this data is ItemType; it's the stuff that your TableView (i.e. List or Tree) is visualizing. * The ColumnID tells you which column of the TableView we need to make a widget for. * Make a widget and return it. * * @param ColumnID A unique ID for a column in this TableView; see SHeaderRow::FColumn for more info. * * @return a widget to represent the contents of a cell in this row of a TableView. */ BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION virtual TSharedRef GenerateWidgetForColumn( const FName& ColumnID ) override { return SNew(SOverlay) .Visibility( EVisibility::SelfHitTestInvisible ) +SOverlay::Slot() .Padding( 0.0f ) [ SNew( SImage ) .Image(FProfilerStyle::Get().GetBrush("Brushes.White25") ) .ColorAndOpacity( this, &SEventGraphTableRow::GetBackgroundColorAndOpacity ) ] +SOverlay::Slot() .Padding( 0.0f ) [ SNew( SImage ) .Image( this, &SEventGraphTableRow::GetOutlineBrush, ColumnID ) .ColorAndOpacity( this, &SEventGraphTableRow::GetOutlineColorAndOpacity ) ] +SOverlay::Slot() [ SNew( SEventGraphTableCell, SharedThis(this), OwnerEventGraph ) .Visibility( this, &SEventGraphTableRow::IsColumnVisible, ColumnID ) .ColumnID( ColumnID ) .IsEventNameColumn( ColumnID == FEventGraphColumns::Get().Collection[(uint32)EEventPropertyIndex::StatName].ID ) .EventPtr( EventPtr ) .OnSetHoveredTableCell( this, &SEventGraphTableRow::OnSetHoveredTableCell ) ]; } END_SLATE_FUNCTION_BUILD_OPTIMIZATION /** * Called when Slate detects that a widget started to be dragged. * Usage: * A widget can ask Slate to detect a drag. * OnMouseDown() reply with FReply::Handled().DetectDrag( SharedThis(this) ). * Slate will either send an OnDragDetected() event or do nothing. * If the user releases a mouse button or leaves the widget before * a drag is triggered (maybe user started at the very edge) then no event will be * sent. * * @param InMyGeometry Widget geometry * @param InMouseEvent MouseMove that triggered the drag * */ virtual FReply OnDragDetected( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) override { if( MouseEvent.IsMouseButtonDown( EKeys::LeftMouseButton ) ) { return FReply::Handled().BeginDragDrop( FStatIDDragDropOp::NewSingle( EventPtr->_StatID, EventPtr->_StatName.GetPlainNameString() ) ); } return SMultiColumnTableRow< FEventGraphSamplePtr >::OnDragDetected(MyGeometry,MouseEvent); } protected: FSlateColor GetBackgroundColorAndOpacity() const { const FLinearColor ThreadColor(5.0f,0.0f,0.0f,1.0f); const FLinearColor DefaultColor(0.0f,0.0f,0.0f,0.0f); const float Alpha = static_cast(EventPtr->_FramePct) * 0.01f; const FLinearColor BackgroundColorAndOpacity = FMath::Lerp(DefaultColor,ThreadColor,Alpha); return BackgroundColorAndOpacity; } FSlateColor GetOutlineColorAndOpacity() const { const FLinearColor NoColor(0.0f,0.0f,0.0f,0.0f); const bool bShouldBeHighlighted = EventPtr->_StatName == HighlightedEventName.Get(); const FLinearColor OutlineColorAndOpacity = bShouldBeHighlighted ? FLinearColor(FColorList::SlateBlue) : NoColor; return OutlineColorAndOpacity; } const FSlateBrush* GetOutlineBrush( const FName ColumnID ) const { EHorizontalAlignment Result = HAlign_Center; if( IsColumnVisibleDelegate.IsBound() ) { Result = GetColumnOutlineHAlignmentDelegate.Execute( ColumnID ); } const FSlateBrush* Brush = nullptr; if( Result == HAlign_Left ) { Brush = FProfilerStyle::Get().GetBrush("Profiler.EventGraph.Border.L"); } else if( Result == HAlign_Right ) { Brush = FProfilerStyle::Get().GetBrush("Profiler.EventGraph.Border.R"); } else { Brush = FProfilerStyle::Get().GetBrush("Profiler.EventGraph.Border.TB"); } return Brush; } EVisibility IsColumnVisible( const FName ColumnID ) const { EVisibility Result = EVisibility::Collapsed; if( IsColumnVisibleDelegate.IsBound() ) { Result = IsColumnVisibleDelegate.Execute( ColumnID ) ? EVisibility::Visible : EVisibility::Collapsed; } return Result; } void OnSetHoveredTableCell( const FName InColumnID, const FEventGraphSamplePtr InSamplePtr ) { SetHoveredTableCellDelegate.ExecuteIfBound( InColumnID, InSamplePtr ); } protected: FIsColumnVisibleDelegate IsColumnVisibleDelegate; FSetHoveredTableCell SetHoveredTableCellDelegate; FGetColumnOutlineHAlignmentDelegate GetColumnOutlineHAlignmentDelegate; /** Name of the event that should be drawn as highlighted. */ TAttribute HighlightedEventName; /** A shared pointer to the event graph sample. */ FEventGraphSamplePtr EventPtr; /** The event graph that owns this event graph row. */ TWeakPtr< IEventGraph > OwnerEventGraph; }; /*----------------------------------------------------------------------------- SEventTree -----------------------------------------------------------------------------*/ SEventGraph::SEventGraph() : CurrentStateIndex( 0 ) {} SEventGraph::~SEventGraph() { // Remove ourselves from the profiler manager. if( FProfilerManager::Get().IsValid() ) { FProfilerManager::Get()->OnViewModeChanged().RemoveAll( this ); } } /*----------------------------------------------------------------------------- Event graph construction related functions -----------------------------------------------------------------------------*/ BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION void SEventGraph::Construct( const FArguments& InArgs ) { static TArray StaticEventArray; SAssignNew(ExternalScrollbar, SScrollBar) .AlwaysShowScrollbar(true); ChildSlot [ SNew(SSplitter) .Orientation(Orient_Vertical) + SSplitter::Slot() .Value(0.5f) [ SNew(SVerticalBox) + SVerticalBox::Slot() .AutoHeight() [ SNew(SBorder) .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) .Padding(2.0f) [ SNew(SVerticalBox) + SVerticalBox::Slot() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() [ GetWidgetForEventGraphTypes() ] + SHorizontalBox::Slot() .AutoWidth() .Padding(2.0f, 0.0f, 0.0f, 0.0f) [ GetWidgetForEventGraphViewModes() ] + SHorizontalBox::Slot() .FillWidth(1.0f) .Padding(2.0f, 0.0f, 0.0f, 0.0f) [ GetWidgetBoxForOptions() ] ] + SVerticalBox::Slot() .Padding(0.0f, 2.0f, 0.0f, 0.0f) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .FillWidth(1.0f) [ GetWidgetForThreadFilter() ] ] ] ] // Function details view ( @see VS2012 profiler ) + SVerticalBox::Slot() .FillHeight(1.0f) .Padding(0.0f, 2.0f, 0.0f, 0.0f) [ SAssignNew(FunctionDetailsBox,SBox) .HeightOverride(224.0f) [ SNew(SBorder) .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) .Padding(2.0f) .Clipping(EWidgetClipping::ClipToBounds) [ SNew(SHorizontalBox) // Calling Functions + SHorizontalBox::Slot() .FillWidth(1.0f) .Padding(2.0f) [ GetVerticalBoxForFunctionDetails(VerticalBox_TopCalling, LOCTEXT("FunctionDetails_CallingFunctions","Calling Functions")) ] // Current Function + SHorizontalBox::Slot() .FillWidth(1.0f) .Padding(2.0f) [ GetVerticalBoxForCurrentFunction() ] // Called Functions + SHorizontalBox::Slot() .FillWidth(1.0f) .Padding(2.0f) [ GetVerticalBoxForFunctionDetails( VerticalBox_TopCalled, LOCTEXT("FunctionDetails_CalledFunctions","Called Functions") ) ] ] ] ] ] + SSplitter::Slot() .Value(0.5f) [ SNew(SBorder) .BorderImage( FAppStyle::GetBrush("ToolPanel.GroupBorder") ) .Padding(0.0f) [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .FillWidth(1.0f) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() [ SAssignNew(TreeView_Base, STreeView) .ExternalScrollbar(ExternalScrollbar) .SelectionMode(ESelectionMode::Multi) .TreeItemsSource(&StaticEventArray) .OnGetChildren(this, &SEventGraph::EventGraph_OnGetChildren) .OnGenerateRow(this, &SEventGraph::EventGraph_OnGenerateRow) .OnSelectionChanged(this, &SEventGraph::EventGraph_OnSelectionChanged) .OnContextMenuOpening(FOnContextMenuOpening::CreateSP(this, &SEventGraph::EventGraph_GetMenuContent)) .HeaderRow ( SAssignNew(TreeViewHeaderRow,SHeaderRow) .Visibility(EVisibility::Visible) ) ] ] + SHorizontalBox::Slot() .AutoWidth() [ SNew(SBox) .WidthOverride(FOptionalSize(16.0f)) [ ExternalScrollbar.ToSharedRef() ] ] ] ] ]; InitializeAndShowHeaderColumns(); BindCommands(); FProfilerManager::Get()->OnViewModeChanged().AddSP( this, &SEventGraph::ProfilerManager_OnViewModeChanged ); } TSharedRef SEventGraph::GetToggleButtonForEventGraphType( const EEventGraphTypes::Type EventGraphType, const FName BrushName ) { TSharedRef ButtonContent = SNew(SHorizontalBox); if( BrushName != NAME_None ) { ButtonContent->AddSlot() .AutoWidth() .HAlign( HAlign_Center ) .VAlign( VAlign_Center ) [ SNew(SImage) .Image(FProfilerStyle::Get().GetBrush( BrushName ) ) ]; } ButtonContent->AddSlot() .HAlign( HAlign_Center ) .VAlign( VAlign_Center ) [ SNew( STextBlock ) .Text( FText::FromString(EEventGraphTypes::ToName( EventGraphType )) ) .ColorAndOpacity(FSlateColor::UseForeground()) ]; return SNew( SCheckBox ) .Style(FProfilerStyle::Get(), "ToggleButtonCheckbox") .IsEnabled( this, &SEventGraph::EventGraphType_IsEnabled, EventGraphType ) .HAlign( HAlign_Center ) .Padding( 2.0f ) .OnCheckStateChanged( this, &SEventGraph::EventGraphType_OnCheckStateChanged, EventGraphType ) .IsChecked( this, &SEventGraph::EventGraphType_IsChecked, EventGraphType ) .ToolTipText( FText::FromString(EEventGraphTypes::ToDescription( EventGraphType )) ) [ ButtonContent ]; } TSharedRef SEventGraph::GetToggleButtonForEventGraphViewMode( const EEventGraphViewModes::Type EventGraphViewMode ) { return SNew( SCheckBox ) .Style(FProfilerStyle::Get(), "ToggleButtonCheckbox") .IsEnabled( this, &SEventGraph::EventGraph_IsEnabled ) .HAlign( HAlign_Center ) .Padding( 2.0f ) .OnCheckStateChanged( this, &SEventGraph::EventGraphViewMode_OnCheckStateChanged, EventGraphViewMode ) .IsChecked( this, &SEventGraph::EventGraphViewMode_IsChecked, EventGraphViewMode ) .ToolTipText( EEventGraphViewModes::ToDescription( EventGraphViewMode ) ) .Visibility( this, &SEventGraph::EventGraphViewMode_GetVisibility, EventGraphViewMode ) [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .AutoWidth() .HAlign( HAlign_Center ) .VAlign( VAlign_Center ) [ SNew(SImage) .Image(FProfilerStyle::Get().GetBrush( EEventGraphViewModes::ToBrushName( EventGraphViewMode ) ) ) ] +SHorizontalBox::Slot() .HAlign( HAlign_Center ) .VAlign( VAlign_Center ) [ SNew( STextBlock ) .Text( EEventGraphViewModes::ToName( EventGraphViewMode ) ) .ColorAndOpacity(FSlateColor::UseForeground()) ] ]; } TSharedRef SEventGraph::GetWidgetForEventGraphTypes() { return SNew( SBorder ) .BorderImage(FProfilerStyle::Get().GetBrush("Profiler.Group.16") ) .Padding(FMargin(2.0f, 0.0)) [ SNew( SHorizontalBox ) // EventGraph - Type +SHorizontalBox::Slot() .HAlign( HAlign_Center ) .VAlign( VAlign_Center ) .AutoWidth() .Padding( 2.0f ) [ SNew(STextBlock) .Text( LOCTEXT("Toolbar_Type","Type") ) .TextStyle(FProfilerStyle::Get(), TEXT("Profiler.CaptionBold") ) ] // One-frame +SHorizontalBox::Slot() .HAlign( HAlign_Center ) .VAlign( VAlign_Fill ) .AutoWidth() .Padding( 2.0f ) [ GetToggleButtonForEventGraphType( EEventGraphTypes::OneFrame, NAME_None ) ] // Avg +SHorizontalBox::Slot() .HAlign( HAlign_Center ) .VAlign( VAlign_Fill ) .AutoWidth() .Padding( 2.0f ) [ GetToggleButtonForEventGraphType( EEventGraphTypes::Average, TEXT("Profiler.EventGraph.AverageIcon") ) ] // Max +SHorizontalBox::Slot() .HAlign( HAlign_Center ) .VAlign( VAlign_Fill ) .AutoWidth() .Padding( 2.0f ) [ GetToggleButtonForEventGraphType( EEventGraphTypes::Maximum, TEXT("Profiler.EventGraph.MaximumIcon") ) ] ]; } TSharedRef SEventGraph::GetWidgetForEventGraphViewModes() { return SNew( SBorder ) .BorderImage(FProfilerStyle::Get().GetBrush("Profiler.Group.16") ) .Padding(FMargin(2.0f, 0.0)) [ SNew( SHorizontalBox ) // View mode - Type +SHorizontalBox::Slot() .HAlign( HAlign_Center ) .VAlign( VAlign_Center ) .AutoWidth() .Padding( 2.0f ) [ SNew(STextBlock) .Text( LOCTEXT("Toolbar_ViewMode","View mode") ) .TextStyle(FProfilerStyle::Get(), TEXT("Profiler.CaptionBold") ) ] // View mode - Hierarchical +SHorizontalBox::Slot() .HAlign( HAlign_Center ) .VAlign( VAlign_Fill ) .AutoWidth() .Padding( 2.0f ) [ GetToggleButtonForEventGraphViewMode( EEventGraphViewModes::Hierarchical ) ] // View mode - Flat (Inclusive) +SHorizontalBox::Slot() .HAlign( HAlign_Center ) .VAlign( VAlign_Fill ) .AutoWidth() .Padding( 2.0f ) [ GetToggleButtonForEventGraphViewMode( EEventGraphViewModes::FlatInclusive ) ] // View mode - Flat Coalesced (Inclusive) +SHorizontalBox::Slot() .AutoWidth() .HAlign( HAlign_Center ) .VAlign( VAlign_Fill ) .Padding( 2.0f ) [ GetToggleButtonForEventGraphViewMode( EEventGraphViewModes::FlatInclusiveCoalesced ) ] // View mode - Flat (Exclusive) +SHorizontalBox::Slot() .HAlign( HAlign_Center ) .VAlign( VAlign_Fill ) .AutoWidth() .Padding( 1.0f ) [ GetToggleButtonForEventGraphViewMode( EEventGraphViewModes::FlatExclusive ) ] // View mode - Flat Coalesced (Exclusive) +SHorizontalBox::Slot() .HAlign( HAlign_Center ) .VAlign( VAlign_Fill ) .AutoWidth() .Padding( 2.0f ) [ GetToggleButtonForEventGraphViewMode( EEventGraphViewModes::FlatExclusiveCoalesced ) ] ]; } TSharedRef SEventGraph::GetWidgetForThreadFilter() { return SNew(SBorder) .BorderImage(FProfilerStyle::Get().GetBrush("Profiler.Group.16")) .Padding(FMargin(2.0f, 0.0)) [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .HAlign( HAlign_Center ) .VAlign( VAlign_Center ) .AutoWidth() .Padding( 2.0f ) [ SNew ( STextBlock ) .Text( LOCTEXT( "Toolbar_Thread", "Thread" ) ) .TextStyle(FProfilerStyle::Get(), TEXT( "Profiler.CaptionBold" ) ) ] +SHorizontalBox::Slot() .HAlign( HAlign_Center ) .VAlign( VAlign_Fill ) .AutoWidth() .Padding( 2.0f ) [ SAssignNew( ThreadFilterComboBox, SComboBox> ) .ContentPadding( FMargin( 6.0f, 2.0f ) ) .OptionsSource( &ThreadNamesForCombo ) .OnSelectionChanged( this, &SEventGraph::OnThreadFilterChanged ) .OnGenerateWidget( this, &SEventGraph::OnGenerateWidgetForThreadFilter ) [ SNew( STextBlock ) .Text( this, &SEventGraph::GenerateTextForThreadFilter, FName( TEXT( "SelectedThreadName" ) ) ) ] ] ]; } void SEventGraph::FillThreadFilterOptions() { ThreadNamesForCombo.Empty(); // Allow None as an option ThreadNamesForCombo.Add(MakeShareable(new FName())); if ( EventGraphStatesHistory.Num() == 0 ) { return; } FEventGraphSamplePtr Root = GetCurrentState()->GetRoot(); if ( !Root.IsValid() ) { return; } // Add a thread filter entry for each root child for ( const FEventGraphSamplePtr& Child : Root->GetChildren() ) { ThreadNamesForCombo.Add( MakeShareable( new FName( Child->_ThreadName ) ) ); } // Sort the thread names alphabetically ThreadNamesForCombo.Sort([]( const TSharedPtr Lhs, const TSharedPtr Rhs ) { return Lhs->IsNone() || ( !Rhs->IsNone() && Lhs->LexicalLess(*Rhs) ); }); // Refresh the combo box if ( ThreadFilterComboBox.IsValid() ) { ThreadFilterComboBox->RefreshOptions(); } } FText SEventGraph::GenerateTextForThreadFilter( FName ThreadName ) const { static const FName SelectedThreadName( TEXT( "SelectedThreadName" ) ); if ( ThreadName == SelectedThreadName ) { if ( EventGraphStatesHistory.Num() > 0 ) { ThreadName = GetCurrentState()->ThreadFilter; } else { ThreadName = NAME_None; } } return FText::FromName( ThreadName ); } void SEventGraph::OnThreadFilterChanged( TSharedPtr NewThread, ESelectInfo::Type SelectionType ) { if ( NewThread.IsValid() ) { GetCurrentState()->ThreadFilter = *NewThread; RestoreEventGraphStateFrom( GetCurrentState() ); GetCurrentState()->GetRoot()->SetBooleanStateForAllChildren(true); } } TSharedRef SEventGraph::OnGenerateWidgetForThreadFilter( TSharedPtr ThreadName ) const { return SNew( STextBlock ) .Text( GenerateTextForThreadFilter( ThreadName.IsValid() ? *ThreadName : NAME_None ) ); } TSharedRef SEventGraph::GetWidgetBoxForOptions() { return SNew( SBorder ) .BorderImage(FProfilerStyle::Get().GetBrush("Profiler.Group.16") ) .Padding( 0.0f ) [ SNew(SHorizontalBox) // History Back +SHorizontalBox::Slot() .AutoWidth() .Padding( 1.0f ) [ SNew( SButton ) .OnClicked( this, &SEventGraph::HistoryBack_OnClicked ) .IsEnabled( this, &SEventGraph::HistoryBack_IsEnabled ) .ToolTipText( this, &SEventGraph::HistoryBack_GetToolTipText ) .HAlign( HAlign_Center ) .VAlign( VAlign_Center ) .ContentPadding( 2.0f ) [ SNew(SImage) .Image(FProfilerStyle::Get().GetBrush("Profiler.EventGraph.HistoryBack") ) ] ] // History Forward +SHorizontalBox::Slot() .AutoWidth() .Padding( 1.0f ) [ SNew( SButton ) .OnClicked( this, &SEventGraph::HistoryForward_OnClicked ) .IsEnabled( this, &SEventGraph::HistoryForward_IsEnabled ) .ToolTipText( this, &SEventGraph::HistoryForward_GetToolTipText ) .HAlign( HAlign_Center ) .VAlign( VAlign_Center ) .ContentPadding( 2.0f ) [ SNew(SImage) .Image(FProfilerStyle::Get().GetBrush("Profiler.EventGraph.HistoryForward") ) ] ] // History List +SHorizontalBox::Slot() .AutoWidth() .Padding( 1.0f ) [ SNew( SComboButton ) .IsEnabled( this, &SEventGraph::HistoryList_IsEnabled ) .ContentPadding( 0.0f ) .OnGetMenuContent( this, &SEventGraph::HistoryList_GetMenuContent ) ] // Expand hot path +SHorizontalBox::Slot() .AutoWidth() .Padding( 1.0f ) [ SNew( SButton ) .IsEnabled( this, &SEventGraph::ContextMenu_ExpandHotPath_CanExecute ) .ToolTipText( LOCTEXT("ContextMenu_Header_Expand_ExpandHotPath_Desc", "Expands hot path for the selected events, based on the inclusive time, also enables descending sorting by inclusive time") ) .HAlign( HAlign_Center ) .VAlign( VAlign_Center ) .OnClicked( this, &SEventGraph::ExpandHotPath_OnClicked ) [ SNew( SImage ) .Image(FProfilerStyle::Get().GetBrush(TEXT("Profiler.EventGraph.ExpandHotPath16")) ) ] ] // Highlight hot path +SHorizontalBox::Slot() .AutoWidth() .HAlign( HAlign_Center ) .VAlign( VAlign_Fill ) .Padding( 1.0f ) [ SNew( SCheckBox ) .Visibility( EVisibility::Collapsed ) .IsEnabled( false ) .OnCheckStateChanged( this, &SEventGraph::HighlightHotPath_OnCheckStateChanged ) [ SNew( STextBlock ) .Text( LOCTEXT("HighlightHotPathCheckboxLabel", "HP") ) ] ] // Configuration +SHorizontalBox::Slot() .AutoWidth() .HAlign( HAlign_Center ) .VAlign( VAlign_Fill ) .Padding( 1.0f ) [ SNew( SButton ) .Visibility( EVisibility::Collapsed ) .IsEnabled( false ) .HAlign( HAlign_Center ) .VAlign( VAlign_Center ) .Text( LOCTEXT("ConfigurationButtonLabel", "CF") ) ] // Export +SHorizontalBox::Slot() .AutoWidth() .HAlign( HAlign_Center ) .VAlign( VAlign_Fill ) .Padding( 1.0f ) [ SNew( SButton ) .Visibility( EVisibility::Collapsed ) .IsEnabled( false ) .HAlign( HAlign_Center ) .VAlign( VAlign_Center ) .Text( LOCTEXT("ExportButtonLabel", "EX") ) ] // Search box +SHorizontalBox::Slot() .FillWidth( 1.0f ) .HAlign( HAlign_Fill ) .VAlign( VAlign_Center ) .Padding( 1.0f ) [ SAssignNew( FilteringSearchBox, SSearchBox ) .HintText( LOCTEXT("FilteringSearchBox_HintText", "Search or filter event(s)") ) .OnTextChanged( this, &SEventGraph::FilteringSearchBox_OnTextChanged ) .OnTextCommitted( this, &SEventGraph::FilteringSearchBox_OnTextCommitted ) .IsEnabled( this, &SEventGraph::FilteringSearchBox_IsEnabled ) .ToolTipText( LOCTEXT("FilteringSearchBox_TT", "Type here to search or filter events") ) ] // Aggressive filtering + SHorizontalBox::Slot() .AutoWidth() .HAlign(HAlign_Center) .VAlign(VAlign_Fill) .Padding(1.0f) [ SNew(SCheckBox) .Visibility(EVisibility::Visible) .IsEnabled(true) .OnCheckStateChanged(this, &SEventGraph::OnAggressiveFilteringToggled) [ SNew(STextBlock) .Text( LOCTEXT("AggressiveFilteringLabel", "AF") ) .ToolTipText(LOCTEXT("AggressiveFiltering_TT", "Toggle aggressive filtering")) ] ] // Filtering help +SHorizontalBox::Slot() .AutoWidth() .HAlign( HAlign_Center ) .VAlign( VAlign_Fill ) .Padding( 1.0f ) [ SNew( SButton ) .Visibility( EVisibility::Collapsed ) .IsEnabled( false ) .HAlign( HAlign_Center ) .VAlign( VAlign_Center ) .Text( LOCTEXT("FilteringHelpButtonLabel", "?") ) ] ]; } TSharedRef SEventGraph::GetVerticalBoxForFunctionDetails( TSharedPtr& out_VerticalBoxTopFuncions, const FText& Caption ) { return SNew(SVerticalBox) +SVerticalBox::Slot() .AutoHeight() .HAlign( HAlign_Center ) .VAlign( VAlign_Center ) .Padding( 2.0f ) [ SNew(STextBlock) .Text( Caption ) .TextStyle(FProfilerStyle::Get(), TEXT("Profiler.CaptionBold") ) ] +SVerticalBox::Slot() .AutoHeight() .HAlign( HAlign_Fill ) .VAlign( VAlign_Center ) .Padding( 2.0f ) [ SNew(SSeparator) .Orientation( Orient_Horizontal ) ] +SVerticalBox::Slot() .FillHeight( 1.0f ) .HAlign( HAlign_Fill ) .VAlign( VAlign_Fill ) .Padding( 0.0f ) [ SAssignNew(out_VerticalBoxTopFuncions,SVerticalBox) ]; } TSharedRef SEventGraph::GetVerticalBoxForCurrentFunction() { return SNew(SVerticalBox) +SVerticalBox::Slot() .AutoHeight() .HAlign( HAlign_Center ) .VAlign( VAlign_Center ) .Padding( 2.0f ) [ SNew(STextBlock) .Text( LOCTEXT("FunctionDetails_CurrentFunction","Current Function") ) .TextStyle(FProfilerStyle::Get(), TEXT("Profiler.CaptionBold") ) ] +SVerticalBox::Slot() .AutoHeight() .HAlign( HAlign_Fill ) .VAlign( VAlign_Center ) .Padding( 2.0f ) [ SNew(SSeparator) .Orientation( Orient_Horizontal ) ] +SVerticalBox::Slot() .AutoHeight() .HAlign( HAlign_Center ) .VAlign( VAlign_Center ) .Padding( 2.0f ) .Expose( CurrentFunctionDescSlot ); } END_SLATE_FUNCTION_BUILD_OPTIMIZATION /*----------------------------------------------------------------------------- ... -----------------------------------------------------------------------------*/ TSharedRef< ITableRow > SEventGraph::EventGraph_OnGenerateRow( FEventGraphSamplePtr EventPtr, const TSharedRef& OwnerTable ) { TSharedRef< ITableRow > ReturnRow = SNew(SEventGraphTableRow, OwnerTable, SharedThis(this)) .OnIsColumnVisible( this, &SEventGraph::EventGraphTableRow_IsColumnVisible ) .OnSetHoveredTableCell( this, &SEventGraph::EventGraphTableRow_SetHoveredTableCell ) .OnGetColumnOutlineHAlignmentDelegate( this, &SEventGraph::EventGraphRow_GetColumnOutlineHAlignment ) .HighlightedEventName( this, &SEventGraph::EventGraphRow_GetHighlightedEventName ) .EventPtr( EventPtr ) ; return ReturnRow; } void SEventGraph::EventGraph_OnSelectionChanged( FEventGraphSamplePtr SelectedItem, ESelectInfo::Type SelectInfo ) { if( SelectInfo != ESelectInfo::Direct ) { UpdateFunctionDetails(); } } bool SEventGraph::EventGraphTableRow_IsColumnVisible( const FName ColumnID ) const { bool bResult = false; const FEventGraphColumn& ColumnPtr = TreeViewHeaderColumns.FindChecked(ColumnID); return ColumnPtr.bIsVisible; } void SEventGraph::EventGraphTableRow_SetHoveredTableCell( const FName ColumnID, const FEventGraphSamplePtr EventPtr ) { HoveredColumnID = ColumnID; const bool bIsAnyMenusVisible = FSlateApplication::Get().AnyMenusVisible(); if( !HasMouseCapture() && !bIsAnyMenusVisible ) { HoveredSamplePtr = EventPtr; } #if DEBUG_PROFILER_PERFORMANCE UE_LOG( Profiler, Log, TEXT("%s -> %s"), *HoveredColumnID.GetPlainNameString(), EventPtr.IsValid() ? *EventPtr->_StatName.GetPlainNameString() : TEXT("nullptr") ); #endif // DEBUG_PROFILER_PERFORMANCE } FName SEventGraph::EventGraphRow_GetHighlightedEventName() const { return HighlightedEventName; } EHorizontalAlignment SEventGraph::EventGraphRow_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; } } void SEventGraph::EventGraph_OnGetChildren( FEventGraphSamplePtr InParent, TArray< FEventGraphSamplePtr >& out_Children ) { if( GetCurrentStateViewMode() == EEventGraphViewModes::Hierarchical ) { out_Children = InParent->GetNotCulledChildren(); } } void SEventGraph::TreeView_SetItemsExpansion_Recurrent( TArray& InEventPtrs, const bool bShouldExpand ) { for( int32 EventIndex = 0; EventIndex < InEventPtrs.Num(); EventIndex++ ) { const FEventGraphSamplePtr EventPtr = InEventPtrs[EventIndex]; TreeView_Base->SetItemExpansion( EventPtr, bShouldExpand ); TreeView_SetItemsExpansion_Recurrent( EventPtr->GetChildren(), bShouldExpand ); } } void SEventGraph::SetSortModeForColumn( const FName& ColumnID, const EColumnSortMode::Type SortMode ) { ColumnBeingSorted = ColumnID; ColumnSortMode = SortMode; SortEvents(); } /*----------------------------------------------------------------------------- ShowSelectedEventsInViewMode -----------------------------------------------------------------------------*/ void SEventGraph::ShowSelectedEventsInViewMode_Execute( EEventGraphViewModes::Type NewViewMode ) { const TArray SelectedEvents = TreeView_Base->GetSelectedItems(); ShowEventsInViewMode( SelectedEvents, NewViewMode ); } bool SEventGraph::ShowSelectedEventsInViewMode_CanExecute( EEventGraphViewModes::Type NewViewMode ) const { return TreeView_Base->GetNumItemsSelected() > 0; } ECheckBoxState SEventGraph::ShowSelectedEventsInViewMode_GetCheckState( EEventGraphViewModes::Type NewViewMode ) const { return GetCurrentStateViewMode() == NewViewMode ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } static EEventCompareOps::Type EColumnSortModeToEventCompareOp( const EColumnSortMode::Type ColumnSortMode ) { if( ColumnSortMode == EColumnSortMode::Descending ) { return EEventCompareOps::Greater; } else if( ColumnSortMode == EColumnSortMode::Ascending ) { return EEventCompareOps::Less; } check( 0 ); return EEventCompareOps::InvalidOrMax; } void SEventGraph::SortEvents() { PROFILER_SCOPE_LOG_TIME( TEXT( "SEventGraph::SortEvents" ), nullptr ); if( ColumnBeingSorted != NAME_None ) { const FEventGraphColumn& Column = *FEventGraphColumns::Get().ColumnNameToIndexMapping.FindChecked( ColumnBeingSorted ); if( GetCurrentStateViewMode() == EEventGraphViewModes::Hierarchical || GetCurrentStateViewMode() == EEventGraphViewModes::FlatInclusive || GetCurrentStateViewMode() == EEventGraphViewModes::FlatInclusiveCoalesced || GetCurrentStateViewMode() == EEventGraphViewModes::FlatExclusive || GetCurrentStateViewMode() == EEventGraphViewModes::FlatExclusiveCoalesced ) { FEventArraySorter::Sort( GetCurrentState()->GetRealRoot()->GetChildren(), Column.ID, EColumnSortModeToEventCompareOp( ColumnSortMode ) ); FEventArraySorter::Sort( Events_FlatCoalesced, Column.ID, EColumnSortModeToEventCompareOp( ColumnSortMode ) ); FEventArraySorter::Sort( Events_Flat, Column.ID, EColumnSortModeToEventCompareOp( ColumnSortMode ) ); // Update not culled children. GetCurrentState()->GetRoot()->SetBooleanStateForAllChildren(true); } } } void SEventGraph::FilteringSearchBox_OnTextChanged( const FText& InFilterText ) { } static bool RecursiveShowUnfilteredItems(TSharedPtr< STreeView >& TreeView, TArray& Nodes) { bool bExpandedAnyChildren = false; for (FEventGraphSamplePtr& Node : Nodes) { const bool bChildIsExpanded = RecursiveShowUnfilteredItems(TreeView, Node->GetChildren()); const bool bThisWantsExpanded = !Node->PropertyValueAsBool(EEventPropertyIndex::bIsFiltered); const bool bExpandThis = bChildIsExpanded || bThisWantsExpanded; bExpandedAnyChildren |= bExpandThis; TreeView->SetItemExpansion(Node, bExpandThis); } return bExpandedAnyChildren; } void SEventGraph::FilteringSearchBox_OnTextCommitted(const FText& NewText, ETextCommit::Type CommitType) { PROFILER_SCOPE_LOG_TIME(TEXT("SEventGraph::FilterOutByText_Execute"), nullptr); SaveCurrentEventGraphState(); FEventGraphState* Op = GetCurrentState()->CreateCopyWithTextFiltering(NewText.ToString()); CurrentStateIndex = EventGraphStatesHistory.Insert(MakeShareable(Op), CurrentStateIndex + 1); RestoreEventGraphStateFrom(GetCurrentState()); // Auto-expand to view the unfiltered items if (GetCurrentStateViewMode() == EEventGraphViewModes::Hierarchical) { RecursiveShowUnfilteredItems(TreeView_Base, GetCurrentState()->GetRoot()->GetChildren()); TreeView_Refresh(); } } bool SEventGraph::FilteringSearchBox_IsEnabled() const { return true; } void SEventGraph::OnAggressiveFilteringToggled(ECheckBoxState InState) { GetCurrentState()->SetAggressiveFiltering( InState == ECheckBoxState::Checked ? true : false ); RestoreEventGraphStateFrom(GetCurrentState()); // Auto-expand to view the unfiltered items if (GetCurrentStateViewMode() == EEventGraphViewModes::Hierarchical) { RecursiveShowUnfilteredItems(TreeView_Base, GetCurrentState()->GetRoot()->GetChildren()); TreeView_Refresh(); } } TSharedPtr SEventGraph::EventGraph_GetMenuContent() const { const FEventGraphColumn& Column = *FEventGraphColumns::Get().ColumnNameToIndexMapping.FindChecked( HoveredColumnID ); const TArray SelectedEvents = TreeView_Base->GetSelectedItems(); const int NumSelectedEvents = SelectedEvents.Num(); FEventGraphSamplePtr SelectedEvent = NumSelectedEvents ? SelectedEvents[0] : nullptr; FText SelectionStr; FText PropertyName; FText PropertyValue; if( NumSelectedEvents == 0 ) { SelectionStr = LOCTEXT( "NothingSelected", "Nothing selected" ); } else if( NumSelectedEvents == 1 ) { SelectionStr = FText::FromString( SelectedEvent->_StatName.GetPlainNameString() ); PropertyName = Column.ShortName; PropertyValue = FText::FromString( SelectedEvent->GetFormattedValue(Column.Index) ); } 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 events"), FSlateIcon(FProfilerStyle::Get().GetStyleSetName(), "@missing.icon"), DummyUIAction, NAME_None, EUserInterfaceActionType::Button ); } MenuBuilder.EndSection(); // Root menu MenuBuilder.BeginSection("Root/Culling/Filtering", LOCTEXT("ContextMenu_Header_Root", "Root") ); { MenuBuilder.AddMenuEntry ( LOCTEXT("ContextMenu_Root_Set", "Set Root"), LOCTEXT("ContextMenu_Root_Set_Desc", "Sets the root to the selected event(s) and switches to hierarchical view"), FSlateIcon(FProfilerStyle::Get().GetStyleSetName(), "Profiler.EventGraph.SetRoot"), SetRoot_Custom(), NAME_None, EUserInterfaceActionType::Button ); FUIAction Action_AggregateForSelection; // MenuBuilder.AddMenuEntry // ( // LOCTEXT("ContextMenu_Root_Reset", "Reset To Default"), // LOCTEXT("ContextMenu_Root_Reset_Desc", "Resets the root options and removes from the history"), // TEXT("Profiler.Misc.ResetToDefault"), ResetRoot_Custom(), NAME_None, EUserInterfaceActionType::Button // ); } //MenuBuilder.EndSection(); // Culling menu //MenuBuilder.BeginSection("Culling", LOCTEXT("ContextMenu_Culling","Culling") ); { FText CullingDesc; if( !Column.bCanBeCulled ) { CullingDesc = LOCTEXT("ContextMenu_Culling_DescErrCol","Culling not available, please select a different column"); } else if( NumSelectedEvents == 1 ) { CullingDesc = FText::Format( LOCTEXT("ContextMenu_Culling_DescFmt","Cull events to '{0}' based on '{1}'"), PropertyValue, PropertyName ); } else { CullingDesc = LOCTEXT("ContextMenu_Culling_DescErrEve","Culling not available, please select one event"); } MenuBuilder.AddMenuEntry ( CullingDesc, LOCTEXT("ContextMenu_Culling_Desc_TT","Culls the event graph based on the property value of the selected event"), FSlateIcon(FProfilerStyle::Get().GetStyleSetName(), "Profiler.EventGraph.CullEvents"), CullByProperty_Custom(SelectedEvent,HoveredColumnID,false), NAME_None, EUserInterfaceActionType::Button ); // Filtering menu FText FilteringDesc; if( !Column.bCanBeFiltered ) { FilteringDesc = LOCTEXT("ContextMenu_Filtering_DescErrCol","Filtering not available, please select a different column"); } else if( NumSelectedEvents == 1 ) { FilteringDesc = FText::Format( LOCTEXT("ContextMenu_Filtering_DescFmt","Filter events to '{0}' based on '{1}'"), PropertyValue, PropertyName ); } else { FilteringDesc = LOCTEXT("ContextMenu_Filtering_DescErrEve","Filtering not available, please select one event"); } MenuBuilder.AddMenuEntry ( FilteringDesc, LOCTEXT("ContextMenu_Filtering_Desc_TT","Filters the event graph based on the property value of the selected event"), FSlateIcon(FProfilerStyle::Get().GetStyleSetName(), "Profiler.EventGraph.FilterEvents"), FilterOutByProperty_Custom(SelectedEvent,HoveredColumnID,false), NAME_None, EUserInterfaceActionType::Button ); MenuBuilder.AddMenuSeparator(); MenuBuilder.AddMenuEntry ( LOCTEXT("ContextMenu_ClearHistory", "Reset to default"), LOCTEXT("ContextMenu_ClearHistory_Reset_Desc", "For the selected event graph resets root/culling/filter to the default state and clears the history"), FSlateIcon(FProfilerStyle::Get().GetStyleSetName(), "Profiler.Misc.ResetToDefault"), ClearHistory_Custom(), NAME_None, EUserInterfaceActionType::Button ); } MenuBuilder.EndSection(); MenuBuilder.BeginSection("Expand", LOCTEXT("ContextMenu_Header_Expand", "Expand") ); { MenuBuilder.AddMenuEntry ( LOCTEXT("ContextMenu_Header_Expand_ExpandAll", "Expand All"), LOCTEXT("ContextMenu_Header_Expand_ExpandAll_Desc", "Expands all events"), FSlateIcon(FProfilerStyle::Get().GetStyleSetName(), "Profiler.EventGraph.ExpandAll"), SetExpansionForEvents_Custom( ESelectedEventTypes::AllEvents, true ), NAME_None, EUserInterfaceActionType::Button ); MenuBuilder.AddMenuEntry ( LOCTEXT("ContextMenu_Header_Expand_CollapseAll", "Collapse All"), LOCTEXT("ContextMenu_Header_Expand_CollapseAll_Desc", "Collapses all events"), FSlateIcon(FProfilerStyle::Get().GetStyleSetName(), "Profiler.EventGraph.CollapseAll"), SetExpansionForEvents_Custom( ESelectedEventTypes::AllEvents, false ), NAME_None, EUserInterfaceActionType::Button ); MenuBuilder.AddMenuEntry ( LOCTEXT("ContextMenu_Header_Expand_ExpandSelection", "Expand Selection"), LOCTEXT("ContextMenu_Header_Expand_ExpandSelection_Desc", "Expands selected events"), FSlateIcon(FProfilerStyle::Get().GetStyleSetName(), "Profiler.EventGraph.ExpandSelection"), SetExpansionForEvents_Custom( ESelectedEventTypes::SelectedEvents, true ), NAME_None, EUserInterfaceActionType::Button ); MenuBuilder.AddMenuEntry ( LOCTEXT("ContextMenu_Header_Expand_CollapseSelection", "Collapse Selection"), LOCTEXT("ContextMenu_Header_Expand_CollapseSelection_Desc", "Collapses selected events"), FSlateIcon(FProfilerStyle::Get().GetStyleSetName(), "Profiler.EventGraph.CollapseSelection"), SetExpansionForEvents_Custom( ESelectedEventTypes::SelectedEvents, false ), NAME_None, EUserInterfaceActionType::Button ); MenuBuilder.AddMenuEntry ( LOCTEXT("ContextMenu_Header_Expand_ExpandThread", "Expand Thread"), LOCTEXT("ContextMenu_Header_Expand_ExpandThread_Desc", "Expands selected threads"), FSlateIcon(FProfilerStyle::Get().GetStyleSetName(), "Profiler.EventGraph.ExpandThread"), SetExpansionForEvents_Custom( ESelectedEventTypes::SelectedThreadEvents, true ), NAME_None, EUserInterfaceActionType::Button ); MenuBuilder.AddMenuEntry ( LOCTEXT("ContextMenu_Header_Expand_CollapseThread", "Collapse Thread"), LOCTEXT("ContextMenu_Header_Expand_CollapseThread_Desc", "Collapses selected threads"), FSlateIcon(FProfilerStyle::Get().GetStyleSetName(), "Profiler.EventGraph.CollapseThread"), SetExpansionForEvents_Custom( ESelectedEventTypes::SelectedThreadEvents, false ), NAME_None, EUserInterfaceActionType::Button ); //----------------------------------------------------------------------------- FUIAction Action_ExpandHotPath ( FExecuteAction::CreateSP( const_cast(this), &SEventGraph::ContextMenu_ExpandHotPath_Execute ), FCanExecuteAction::CreateSP( this, &SEventGraph::ContextMenu_ExpandHotPath_CanExecute ) ); MenuBuilder.AddMenuEntry ( LOCTEXT("ContextMenu_Header_Expand_ExpandHotPath", "Expand Hot Path"), LOCTEXT("ContextMenu_Header_Expand_ExpandHotPath_Desc", "Expands hot path for the selected events, based on the inclusive time, also enables descending sorting by inclusive time"), FSlateIcon(FProfilerStyle::Get().GetStyleSetName(), "Profiler.EventGraph.ExpandHotPath"), Action_ExpandHotPath, NAME_None, EUserInterfaceActionType::Button ); } MenuBuilder.EndSection(); MenuBuilder.BeginSection("Navigation", LOCTEXT("ContextMenu_Header_Navigation", "Navigation") ); { MenuBuilder.AddMenuEntry ( LOCTEXT("ContextMenu_Header_Navigation_ShowInHierarchicalView", "Show In Hierarchical View"), LOCTEXT("ContextMenu_Header_Navigation_ShowInHierarchicalView_Desc", "Switches to hierarchical view and expands selected events"), FSlateIcon(FProfilerStyle::Get().GetStyleSetName(), EEventGraphViewModes::ToBrushName(EEventGraphViewModes::Hierarchical)), ShowSelectedEventsInViewMode_Custom(EEventGraphViewModes::Hierarchical), NAME_None, EUserInterfaceActionType::Check ); //----------------------------------------------------------------------------- MenuBuilder.AddMenuEntry ( LOCTEXT("ContextMenu_Header_Navigation_ShowInFlatView", "Show In FlatInclusive View"), LOCTEXT("ContextMenu_Header_Navigation_ShowInFlatView_Desc", "Switches to flat view, also enables descending sorting by inclusive time"), FSlateIcon(FProfilerStyle::Get().GetStyleSetName(), EEventGraphViewModes::ToBrushName(EEventGraphViewModes::FlatExclusive)), ShowSelectedEventsInViewMode_Custom(EEventGraphViewModes::FlatExclusive), NAME_None, EUserInterfaceActionType::Check ); if( FProfilerManager::GetSettings().bShowCoalescedViewModesInEventGraph ) { MenuBuilder.AddMenuEntry ( LOCTEXT("ContextMenu_Header_Navigation_ShowInFlatCoalesced", "Show In FlatInclusive Coalesced"), LOCTEXT("ContextMenu_Header_Navigation_ShowInFlatCoalesced_Desc", "Switches to flat coalesced, also enables descending sorting by inclusive time"), FSlateIcon(FProfilerStyle::Get().GetStyleSetName(), EEventGraphViewModes::ToBrushName(EEventGraphViewModes::FlatInclusiveCoalesced)), ShowSelectedEventsInViewMode_Custom(EEventGraphViewModes::FlatInclusiveCoalesced), NAME_None, EUserInterfaceActionType::Check ); } //----------------------------------------------------------------------------- MenuBuilder.AddMenuEntry ( LOCTEXT("ContextMenu_Header_Navigation_ShowInFlatExclusiveView", "Show In Flat Exclusive View"), LOCTEXT("ContextMenu_Header_Navigation_ShowInFlatExclusiveView_Desc", "Switches to flat exclusive view, also enables ascending sorting by exclusive time"), FSlateIcon(FProfilerStyle::Get().GetStyleSetName(), EEventGraphViewModes::ToBrushName(EEventGraphViewModes::FlatExclusive)), ShowSelectedEventsInViewMode_Custom(EEventGraphViewModes::FlatExclusive), NAME_None, EUserInterfaceActionType::Check ); if( FProfilerManager::GetSettings().bShowCoalescedViewModesInEventGraph ) { MenuBuilder.AddMenuEntry ( LOCTEXT("ContextMenu_Header_Navigation_ShowInFlatExclusiveCoalescedView", "Show In Flat Exclusive Coalesced View"), LOCTEXT("ContextMenu_Header_Navigation_ShowInFlatExclusiveCoalescedView_Desc", "Switches to flat exclusive coalesced view, also enables ascending sorting by exclusive time enabled"), FSlateIcon(FProfilerStyle::Get().GetStyleSetName(), EEventGraphViewModes::ToBrushName(EEventGraphViewModes::FlatExclusiveCoalesced)), ShowSelectedEventsInViewMode_Custom(EEventGraphViewModes::FlatExclusiveCoalesced), NAME_None, EUserInterfaceActionType::Check ); } //----------------------------------------------------------------------------- FUIAction Action_ShowInClassAggregate; FUIAction Action_ShowInGraphPanel; // Highlight all occurrences based on object's name/class FUIAction Action_HighlightBasedOnClass; } MenuBuilder.EndSection(); /* Event graph coloring based on inclusive time as a gradient from black to red? */ MenuBuilder.BeginSection("Misc", LOCTEXT("ContextMenu_Header_Misc", "Miscellaneous") ); { FUIAction Action_CopySelectedToClipboard ( FExecuteAction::CreateSP( const_cast(this), &SEventGraph::ContextMenu_CopySelectedToClipboard_Execute ), FCanExecuteAction::CreateSP( this, &SEventGraph::ContextMenu_CopySelectedToClipboard_CanExecute ) ); MenuBuilder.AddMenuEntry ( LOCTEXT("ContextMenu_Header_Misc_CopySelectedToClipboard", "Copy To Clipboard"), LOCTEXT("ContextMenu_Header_Misc_CopySelectedToClipboard_Desc", "Copies selection to clipboard"), FSlateIcon(FProfilerStyle::Get().GetStyleSetName(), "Profiler.Misc.CopyToClipboard"), Action_CopySelectedToClipboard, NAME_None, EUserInterfaceActionType::Button ); FUIAction Action_SaveSelectedToFile; FUIAction Action_SelectStack ( FExecuteAction::CreateSP( const_cast(this), &SEventGraph::ContextMenu_SelectStack_Execute ), FCanExecuteAction::CreateSP( this, &SEventGraph::ContextMenu_SelectStack_CanExecute ) ); MenuBuilder.AddMenuEntry ( LOCTEXT("ContextMenu_Header_Misc_SelectStack", "Select Stack"), LOCTEXT("ContextMenu_Header_Misc_SelectStack_Desc", "Selects all events in the stack"), FSlateIcon(FProfilerStyle::Get().GetStyleSetName(), "Profiler.EventGraph.SelectStack"), Action_SelectStack, NAME_None, EUserInterfaceActionType::Button ); MenuBuilder.AddSubMenu ( LOCTEXT("ContextMenu_Header_Misc_Sort", "Sort By"), LOCTEXT("ContextMenu_Header_Misc_Sort_Desc", "Sort by column"), FNewMenuDelegate::CreateSP( const_cast(this), &SEventGraph::EventGraph_BuildSortByMenu ), false, FSlateIcon(FProfilerStyle::Get().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( const_cast(this), &SEventGraph::EventGraph_BuildViewColumnMenu ), false, FSlateIcon(FProfilerStyle::Get().GetStyleSetName(), "Profiler.EventGraph.ViewColumn") ); FUIAction Action_ResetColumns ( FExecuteAction::CreateSP( const_cast(this), &SEventGraph::ContextMenu_ResetColumns_Execute ), FCanExecuteAction::CreateSP( this, &SEventGraph::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(FProfilerStyle::Get().GetStyleSetName(), "Profiler.EventGraph.ResetColumn"), Action_ResetColumns, NAME_None, EUserInterfaceActionType::Button ); } MenuBuilder.EndSection(); return MenuBuilder.MakeWidget(); } void SEventGraph::EventGraph_BuildSortByMenu( FMenuBuilder& MenuBuilder ) { // TODO: Refactor later @see TSharedPtr SCascadePreviewViewportToolBar::GenerateViewMenu() const MenuBuilder.BeginSection("ColumnName", LOCTEXT("ContextMenu_Header_Misc_ColumnName","Column Name")); for( auto It = TreeViewHeaderColumns.CreateConstIterator(); It; ++It ) { const FEventGraphColumn& Column = It.Value(); if( Column.bIsVisible && Column.bCanBeSorted ) { FUIAction Action_SortByColumn ( FExecuteAction::CreateSP( this, &SEventGraph::ContextMenu_SortByColumn_Execute, Column.ID ), FCanExecuteAction::CreateSP( this, &SEventGraph::ContextMenu_SortByColumn_CanExecute, Column.ID ), FIsActionChecked::CreateSP( this, &SEventGraph::ContextMenu_SortByColumn_IsChecked, Column.ID ) ); MenuBuilder.AddMenuEntry ( Column.ShortName, Column.Description, 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, &SEventGraph::ContextMenu_SortMode_Execute, EColumnSortMode::Ascending ), FCanExecuteAction::CreateSP( this, &SEventGraph::ContextMenu_SortMode_CanExecute, EColumnSortMode::Ascending ), FIsActionChecked::CreateSP( this, &SEventGraph::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(FProfilerStyle::Get().GetStyleSetName(), "Profiler.Misc.SortAscending"), Action_SortAscending, NAME_None, EUserInterfaceActionType::RadioButton ); FUIAction Action_SortDescending ( FExecuteAction::CreateSP( this, &SEventGraph::ContextMenu_SortMode_Execute, EColumnSortMode::Descending ), FCanExecuteAction::CreateSP( this, &SEventGraph::ContextMenu_SortMode_CanExecute, EColumnSortMode::Descending ), FIsActionChecked::CreateSP( this, &SEventGraph::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(FProfilerStyle::Get().GetStyleSetName(), "Profiler.Misc.SortDescending"), Action_SortDescending, NAME_None, EUserInterfaceActionType::RadioButton ); } MenuBuilder.EndSection(); } void SEventGraph::EventGraph_BuildViewColumnMenu( FMenuBuilder& MenuBuilder ) { MenuBuilder.BeginSection("ViewColumn", LOCTEXT("ContextMenu_Header_Columns_View", "View Column") ); for( auto It = TreeViewHeaderColumns.CreateConstIterator(); It; ++It ) { const FEventGraphColumn& Column = It.Value(); FUIAction Action_ToggleColumn ( FExecuteAction::CreateSP( this, &SEventGraph::ContextMenu_ToggleColumn_Execute, Column.ID ), FCanExecuteAction::CreateSP( this, &SEventGraph::ContextMenu_ToggleColumn_CanExecute, Column.ID ), FIsActionChecked::CreateSP( this, &SEventGraph::ContextMenu_ToggleColumn_IsChecked, Column.ID ) ); MenuBuilder.AddMenuEntry ( Column.ShortName, Column.Description, FSlateIcon(), Action_ToggleColumn, NAME_None, EUserInterfaceActionType::ToggleButton ); } MenuBuilder.EndSection(); } void SEventGraph::EventGraphViewMode_OnCheckStateChanged( ECheckBoxState NewRadioState, const EEventGraphViewModes::Type InViewMode ) { if( NewRadioState == ECheckBoxState::Checked && GetCurrentStateViewMode() != InViewMode) { const TArray< FEventGraphSamplePtr > SelectedEvents = TreeView_Base->GetSelectedItems(); ShowEventsInViewMode( SelectedEvents, InViewMode ); } } ECheckBoxState SEventGraph::EventGraphViewMode_IsChecked( const EEventGraphViewModes::Type InViewMode ) const { return (GetCurrentStateViewMode() == InViewMode) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } void SEventGraph::EventGraphType_OnCheckStateChanged( ECheckBoxState NewRadioState, const EEventGraphTypes::Type NewEventGraphType ) { const uint32 NumFrames = GetCurrentState()->GetNumFrames(); if( NewRadioState == ECheckBoxState::Checked && GetCurrentStateEventGraphType() != NewEventGraphType ) { FEventGraphStateRef EventGraphState = GetCurrentState(); GetHierarchicalExpandedEvents( EventGraphState->ExpandedEvents ); GetHierarchicalSelectedEvents( EventGraphState->SelectedEvents ); EventGraphState->UpdateToNewEventGraphType( NewEventGraphType ); SetEventGraphFromStateInternal( EventGraphState ); } } ECheckBoxState SEventGraph::EventGraphType_IsChecked( const EEventGraphTypes::Type InEventGraphType ) const { if( IsEventGraphStatesHistoryValid() ) { const uint32 NumFrames = GetCurrentState()->GetNumFrames(); if( NumFrames == 1 ) { return GetCurrentStateEventGraphType() == InEventGraphType ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } else if( NumFrames > 1 ) { return GetCurrentStateEventGraphType() == InEventGraphType ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } } return ECheckBoxState::Unchecked; } bool SEventGraph::EventGraphType_IsEnabled( const EEventGraphTypes::Type InEventGraphType ) const { if( IsEventGraphStatesHistoryValid() ) { const uint32 NumFrames = GetCurrentState()->GetNumFrames(); if( InEventGraphType == EEventGraphTypes::OneFrame ) { return NumFrames == 1 ? true : false; } else { return NumFrames > 1 ? true : false; } } return false; } void SEventGraph::SetTreeItemsForViewMode( const EEventGraphViewModes::Type NewViewMode, EEventGraphTypes::Type NewEventGraphType ) { GetCurrentState()->ViewMode = NewViewMode; GetCurrentState()->EventGraphType = NewEventGraphType; GetCurrentState()->ApplyCulling(); GetCurrentState()->ApplyFiltering(); if( GetCurrentStateViewMode() == EEventGraphViewModes::Hierarchical ) { TreeView_Base->SetTreeItemsSource( &GetCurrentState()->GetRoot()->GetChildren() ); } else if( GetCurrentStateViewMode() == EEventGraphViewModes::FlatInclusive || GetCurrentStateViewMode() == EEventGraphViewModes::FlatExclusive ) { TreeView_Base->SetTreeItemsSource( &Events_Flat ); } else if( GetCurrentStateViewMode() == EEventGraphViewModes::FlatInclusiveCoalesced || GetCurrentStateViewMode() == EEventGraphViewModes::FlatExclusiveCoalesced ) { TreeView_Base->SetTreeItemsSource( &Events_FlatCoalesced ); } } FReply SEventGraph::ExpandHotPath_OnClicked() { ContextMenu_ExpandHotPath_Execute(); return FReply::Handled(); } void SEventGraph::HighlightHotPath_OnCheckStateChanged( ECheckBoxState InState ) { } void SEventGraph::TreeView_Refresh() { if( TreeView_Base.IsValid() ) { TreeView_Base->RequestTreeRefresh(); } } void SEventGraph::TreeViewHeaderRow_CreateColumnArgs( const uint32 ColumnIndex ) { const FEventGraphColumn Column = FEventGraphColumns::Get().Collection[ColumnIndex]; SHeaderRow::FColumn::FArguments ColumnArgs; ColumnArgs .ColumnId( Column.ID ) .DefaultLabel( Column.ShortName ) .SortMode( EColumnSortMode::None ) .HAlignHeader( HAlign_Fill ) .VAlignHeader( VAlign_Fill ) .HeaderContentPadding( TOptional( 2.0f ) ) .HAlignCell( HAlign_Fill ) .VAlignCell( VAlign_Fill ) .SortMode( this, &SEventGraph::TreeViewHeaderRow_GetSortModeForColumn, Column.ID ) .OnSort( this, &SEventGraph::TreeViewHeaderRow_OnSortModeChanged ) .FixedWidth( Column.FixedColumnWidth > 0.0f ? Column.FixedColumnWidth : TOptional() ) .HeaderContent() [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .HAlign( Column.HorizontalAlignment ) .VAlign( VAlign_Center ) [ SNew(STextBlock) .Text( Column.ShortName ) .ToolTipText( Column.Description ) ] ] .MenuContent() [ TreeViewHeaderRow_GenerateColumnMenu( Column ) ]; TreeViewHeaderColumnArgs.Add( Column.ID, ColumnArgs ); TreeViewHeaderColumns.Add( Column.ID, Column ); } void SEventGraph::InitializeAndShowHeaderColumns() { ColumnSortMode = EColumnSortMode::Descending; ColumnBeingSorted = FEventGraphColumns::Get().Collection[(uint32)EEventPropertyIndex::InclusiveTimeMS].ID; for (uint32 ColumnIndex = 0; ColumnIndex < FEventGraphColumns::Get().NumColumns; ColumnIndex++) { TreeViewHeaderRow_CreateColumnArgs( ColumnIndex ); } for( auto It = TreeViewHeaderColumns.CreateConstIterator(); It; ++It ) { const FEventGraphColumn& Column = It.Value(); if( Column.bIsVisible ) { TreeViewHeaderRow_ShowColumn( Column.ID ); } } } void SEventGraph::TreeViewHeaderRow_OnSortModeChanged( const EColumnSortPriority::Type SortPriority, const FName& ColumnID, const EColumnSortMode::Type SortMode ) { SetSortModeForColumn( ColumnID, SortMode ); TreeView_Refresh(); } EColumnSortMode::Type SEventGraph::TreeViewHeaderRow_GetSortModeForColumn( const FName ColumnID ) const { if( ColumnBeingSorted != ColumnID ) { return EColumnSortMode::None; } return ColumnSortMode; } void SEventGraph::HeaderMenu_HideColumn_Execute( const FName ColumnID ) { FEventGraphColumn& Column = TreeViewHeaderColumns.FindChecked(ColumnID); Column.bIsVisible = false; TreeViewHeaderRow->RemoveColumn( ColumnID ); } void SEventGraph::TreeViewHeaderRow_ShowColumn( const FName ColumnID ) { FEventGraphColumn& Column = TreeViewHeaderColumns.FindChecked( ColumnID ); Column.bIsVisible = true; SHeaderRow::FColumn::FArguments& ColumnArgs = TreeViewHeaderColumnArgs.FindChecked( ColumnID ); const int32 NumColumns = TreeViewHeaderRow->GetColumns().Num(); const int32 ColumnIndex = FMath::Max( 0, FMath::Min( (int32)Column.Index, NumColumns ) ); TreeViewHeaderRow->InsertColumn( ColumnArgs, ColumnIndex ); } bool SEventGraph::HeaderMenu_HideColumn_CanExecute( const FName ColumnID ) const { const FEventGraphColumn& Column = TreeViewHeaderColumns.FindChecked(ColumnID); return Column.bCanBeHidden; } TSharedRef< SWidget > SEventGraph::TreeViewHeaderRow_GenerateColumnMenu( const FEventGraphColumn& Column ) { bool bIsMenuVisible = false; const bool bShouldCloseWindowAfterMenuSelection = true; FMenuBuilder MenuBuilder( bShouldCloseWindowAfterMenuSelection, NULL ); { if( Column.bCanBeHidden ) { MenuBuilder.BeginSection("Column", LOCTEXT("TreeViewHeaderRow_Header_Column", "Column") ); FUIAction Action_HideColumn ( FExecuteAction::CreateSP( this, &SEventGraph::HeaderMenu_HideColumn_Execute, Column.ID ), FCanExecuteAction::CreateSP( this, &SEventGraph::HeaderMenu_HideColumn_CanExecute, Column.ID ) ); 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.bCanBeSorted ) { MenuBuilder.BeginSection("SortMode", LOCTEXT("ContextMenu_Header_Misc_Sort_SortMode", "Sort Mode") ); FUIAction Action_SortAscending ( FExecuteAction::CreateSP( this, &SEventGraph::HeaderMenu_SortMode_Execute, Column.ID, EColumnSortMode::Ascending ), FCanExecuteAction::CreateSP( this, &SEventGraph::HeaderMenu_SortMode_CanExecute, Column.ID, EColumnSortMode::Ascending ), FIsActionChecked::CreateSP( this, &SEventGraph::HeaderMenu_SortMode_IsChecked, Column.ID, EColumnSortMode::Ascending ) ); MenuBuilder.AddMenuEntry ( LOCTEXT("ContextMenu_Header_Misc_Sort_SortAscending", "Sort Ascending"), LOCTEXT("ContextMenu_Header_Misc_Sort_SortAscending_Desc", "Sorts ascending"), FSlateIcon(FProfilerStyle::Get().GetStyleSetName(), "Profiler.Misc.SortAscending"), Action_SortAscending, NAME_None, EUserInterfaceActionType::RadioButton ); FUIAction Action_SortDescending ( FExecuteAction::CreateSP( this, &SEventGraph::HeaderMenu_SortMode_Execute, Column.ID, EColumnSortMode::Descending ), FCanExecuteAction::CreateSP( this, &SEventGraph::HeaderMenu_SortMode_CanExecute, Column.ID, EColumnSortMode::Descending ), FIsActionChecked::CreateSP( this, &SEventGraph::HeaderMenu_SortMode_IsChecked, Column.ID, EColumnSortMode::Descending ) ); MenuBuilder.AddMenuEntry ( LOCTEXT("ContextMenu_Header_Misc_Sort_SortDescending", "Sort Descending"), LOCTEXT("ContextMenu_Header_Misc_Sort_SortDescending_Desc", "Sorts descending"), FSlateIcon(FProfilerStyle::Get().GetStyleSetName(), "Profiler.Misc.SortDescending"), Action_SortDescending, NAME_None, EUserInterfaceActionType::RadioButton ); bIsMenuVisible = true; MenuBuilder.EndSection(); } if( Column.bCanBeFiltered ) { MenuBuilder.BeginSection("FilterMode", LOCTEXT("ContextMenu_Header_Misc_Filter_FilterMode", "Filter Mode") ); bIsMenuVisible = true; MenuBuilder.EndSection(); } if( Column.bCanBeCulled ) { MenuBuilder.BeginSection("CullMode", LOCTEXT("ContextMenu_Header_Misc_Cull_CullMode", "Cull Mode") ); bIsMenuVisible = true; MenuBuilder.EndSection(); } } /* HEADER COLUMN 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 SEventGraph::ContextMenu_ExecuteDummy( const FName ActionName ) { #if DEBUG_PROFILER_PERFORMANCE UE_LOG( Profiler, Log, TEXT("SEventGraph::ContextMenu_ExecuteDummy -> %s"), *ActionName.ToString() ); #endif // DEBUG_PROFILER_PERFORMANCE } bool SEventGraph::ContextMenu_CanExecuteDummy( const FName ActionName ) const { #if DEBUG_PROFILER_PERFORMANCE UE_LOG( Profiler, Log, TEXT("SEventGraph::ContextMenu_CanExecuteDummy -> %s"), *ActionName.ToString() ); #endif // DEBUG_PROFILER_PERFORMANCE return false; } bool SEventGraph::ContextMenu_IsCheckedDummy( const FName ActionName ) const { #if DEBUG_PROFILER_PERFORMANCE UE_LOG( Profiler, Log, TEXT("SEventGraph::ContextMenu_IsCheckedDummy -> %s"), *ActionName.ToString() ); #endif // DEBUG_PROFILER_PERFORMANCE return false; } /*----------------------------------------------------------------------------- UI ACTIONS -----------------------------------------------------------------------------*/ void SEventGraph::ContextMenu_ExpandHotPath_Execute() { TArray SelectedItems = TreeView_Base->GetSelectedItems(); FEventGraphSamplePtr EventPtr = SelectedItems[0]; ColumnSortMode = EColumnSortMode::Descending; ColumnBeingSorted = FEventGraphColumns::Get().Collection[(uint32)EEventPropertyIndex::InclusiveTimeMS].ID; SortEvents(); // Clear hot path TreeView_Base->ClearExpandedItems(); GetCurrentState()->GetRoot()->SetBooleanStateForAllChildren(false); FEventGraphSamplePtr LastHotEvent; for( FEventGraphSamplePtr HotEvent = EventPtr; HotEvent.IsValid(); HotEvent = HotEvent->GetChildren().Num() > 0 ? HotEvent->GetChildren()[0] : NULL ) { HotEvent->PropertyValueAsBool(EEventPropertyIndex::bIsHotPath) = true; HotEvent->_bIsHotPath = true; LastHotEvent = HotEvent; } // Expand all events from the bottom to the top most event. TArray StackToExpand; LastHotEvent->GetStack( StackToExpand ); for( int EventIndex = StackToExpand.Num()-1; EventIndex >= 0; EventIndex-- ) { TreeView_Base->SetItemExpansion( StackToExpand[EventIndex], true ); } TreeView_Refresh(); } bool SEventGraph::ContextMenu_ExpandHotPath_CanExecute() const { return GetCurrentStateViewMode() == EEventGraphViewModes::Hierarchical && TreeView_Base->GetNumItemsSelected() == 1; } void SEventGraph::ContextMenu_CopySelectedToClipboard_Execute() { TArray SelectedEvents = TreeView_Base->GetSelectedItems(); FString Result; // Prepare header. for (uint32 ColumnIndex = 0; ColumnIndex < FEventGraphColumns::Get().NumColumns; ColumnIndex++) { const FEventGraphColumn& Column = FEventGraphColumns::Get().Collection[ColumnIndex]; Result += FString::Printf( TEXT("\"%s\","), *Column.ShortName.ToString() ); } Result += LINE_TERMINATOR; // Prepare selected samples. for( int32 EventIndex = 0; EventIndex < SelectedEvents.Num(); EventIndex++ ) { const FEventGraphSamplePtr EventPtr = SelectedEvents[EventIndex]; for (uint32 ColumnIndex = 0; ColumnIndex < FEventGraphColumns::Get().NumColumns; ColumnIndex++) { const FEventGraphColumn& Column = FEventGraphColumns::Get().Collection[ColumnIndex]; if( Column.Index != EEventPropertyIndex::None ) { const FString FormattedValue = EventPtr->GetFormattedValue( Column.Index ); Result += FString::Printf( TEXT("\"%s\","), *FormattedValue ); } } Result += LINE_TERMINATOR; } if( Result.Len() ) { FPlatformApplicationMisc::ClipboardCopy( *Result ); } } bool SEventGraph::ContextMenu_CopySelectedToClipboard_CanExecute() const { const int32 NumSelectedItems = TreeView_Base->GetNumItemsSelected(); return NumSelectedItems > 0; } void SEventGraph::ContextMenu_SelectStack_Execute() { TArray SelectedEvents = TreeView_Base->GetSelectedItems(); TArray ArrayStack; SelectedEvents[0]->GetStack( ArrayStack ); for( int32 Nx = 0; Nx < ArrayStack.Num(); ++Nx ) { TreeView_Base->SetItemSelection( ArrayStack[Nx], true, ESelectInfo::Direct ); } } bool SEventGraph::ContextMenu_SelectStack_CanExecute() const { bool bResult = false; TArray SelectedEvents = TreeView_Base->GetSelectedItems(); if( SelectedEvents.Num() == 1 ) { FEventGraphSamplePtr StackEvent = SelectedEvents[0]; bResult = StackEvent->GetParent().IsValid() && !StackEvent->GetParent()->IsRoot(); } return bResult; } void SEventGraph::ContextMenu_SortByColumn_Execute( const FName ColumnID ) { SetSortModeForColumn( ColumnID, EColumnSortMode::Descending ); TreeView_Refresh(); } bool SEventGraph::ContextMenu_SortByColumn_CanExecute( const FName ColumnID ) const { return ColumnID != ColumnBeingSorted; } bool SEventGraph::ContextMenu_SortByColumn_IsChecked( const FName ColumnID ) { return ColumnID == ColumnBeingSorted; } void SEventGraph::ContextMenu_SortMode_Execute( const EColumnSortMode::Type InSortMode ) { SetSortModeForColumn( ColumnBeingSorted, InSortMode ); TreeView_Refresh(); } bool SEventGraph::ContextMenu_SortMode_CanExecute( const EColumnSortMode::Type InSortMode ) const { return ColumnSortMode != InSortMode; } bool SEventGraph::ContextMenu_SortMode_IsChecked( const EColumnSortMode::Type InSortMode ) { return ColumnSortMode == InSortMode; } void SEventGraph::ContextMenu_ResetColumns_Execute() { ColumnSortMode = EColumnSortMode::Descending; ColumnBeingSorted = FEventGraphColumns::Get().Collection[(uint32)EEventPropertyIndex::InclusiveTimeMS].ID; for( uint32 ColumnIndex = 0; ColumnIndex < FEventGraphColumns::Get().NumColumns; ColumnIndex++ ) { const FEventGraphColumn& DefaultColumn = FEventGraphColumns::Get().Collection[ColumnIndex]; const FEventGraphColumn& CurrentColumn = TreeViewHeaderColumns.FindChecked( DefaultColumn.ID ); if( DefaultColumn.bIsVisible && !CurrentColumn.bIsVisible ) { TreeViewHeaderRow_ShowColumn( DefaultColumn.ID ); } else if( !DefaultColumn.bIsVisible && CurrentColumn.bIsVisible ) { HeaderMenu_HideColumn_Execute( DefaultColumn.ID ); } } } bool SEventGraph::ContextMenu_ResetColumns_CanExecute() const { return true; } void SEventGraph::ContextMenu_ToggleColumn_Execute( const FName ColumnID ) { FEventGraphColumn& Column = TreeViewHeaderColumns.FindChecked(ColumnID); if( Column.bIsVisible ) { HeaderMenu_HideColumn_Execute( ColumnID ); } else { TreeViewHeaderRow_ShowColumn( ColumnID ); } } bool SEventGraph::ContextMenu_ToggleColumn_CanExecute( const FName ColumnID ) const { const FEventGraphColumn& Column = TreeViewHeaderColumns.FindChecked(ColumnID); return Column.bCanBeHidden; } bool SEventGraph::ContextMenu_ToggleColumn_IsChecked( const FName ColumnID ) { const FEventGraphColumn& Column = TreeViewHeaderColumns.FindChecked(ColumnID); return Column.bIsVisible; } void SEventGraph::HeaderMenu_SortMode_Execute( const FName ColumnID, const EColumnSortMode::Type InSortMode ) { SetSortModeForColumn( ColumnID, InSortMode ); TreeView_Refresh(); } bool SEventGraph::HeaderMenu_SortMode_CanExecute( const FName ColumnID, const EColumnSortMode::Type InSortMode ) const { const FEventGraphColumn& Column = TreeViewHeaderColumns.FindChecked(ColumnID); const bool bIsValid = Column.bCanBeSorted; bool bCanExecute = ColumnBeingSorted != ColumnID ? true : ColumnSortMode != InSortMode; bCanExecute = bCanExecute && bIsValid; return bCanExecute; } bool SEventGraph::HeaderMenu_SortMode_IsChecked( const FName ColumnID, const EColumnSortMode::Type InSortMode ) { return ColumnBeingSorted == ColumnID && ColumnSortMode == InSortMode; } /*----------------------------------------------------------------------------- CreateEvents_... -----------------------------------------------------------------------------*/ void SEventGraph::CreateEvents() { // #YRX_Profiler 2015-12-17 Fix recursive calls accounting. // Linear GetCurrentState()->GetRoot()->GetLinearEvents( Events_Flat, true ); // Linear Coalesced by Name // Event name -> Events TMap< FName, TArray > FlatIncCoalescedEvents; const int32 NumLinearSamples = Events_Flat.Num(); Events_FlatCoalesced.Reset( NumLinearSamples ); HierarchicalToFlatCoalesced.Reset(); for( int32 LinearEventIndex = 0; LinearEventIndex < NumLinearSamples; LinearEventIndex++ ) { const FEventGraphSamplePtr EventPtr = Events_Flat[LinearEventIndex]; FlatIncCoalescedEvents.FindOrAdd( EventPtr->_StatName ).Add( EventPtr->DuplicateSimplePtr() ); HierarchicalToFlatCoalesced.Add( EventPtr->_StatName, EventPtr ); } // Should ignore recursion! for( auto It = FlatIncCoalescedEvents.CreateConstIterator(); It; ++It ) { const TArray& InclusiveCoalescedEvents = It.Value(); FEventGraphSamplePtr CoalescedEvent = InclusiveCoalescedEvents[0]; for( int32 EventIndex = 1; EventIndex < InclusiveCoalescedEvents.Num(); EventIndex++ ) { CoalescedEvent->Combine( InclusiveCoalescedEvents[EventIndex] ); } Events_FlatCoalesced.Add( CoalescedEvent ); } // CoalesceEventsByColumnName/ID } void SEventGraph::ShowEventsInViewMode( const TArray& EventsToSynchronize, const EEventGraphViewModes::Type NewViewMode ) { FEventGraphStateRef EventGraphState = GetCurrentState(); GetHierarchicalSelectedEvents( EventGraphState->SelectedEvents, &EventsToSynchronize ); GetHierarchicalExpandedEvents( EventGraphState->ExpandedEvents ); SetTreeItemsForViewMode( NewViewMode, GetCurrentStateEventGraphType() ); SetHierarchicalSelectedEvents( EventGraphState->SelectedEvents ); SetHierarchicalExpandedEvents( EventGraphState->ExpandedEvents ); EEventPropertyIndex ColumnIndex = FEventGraphColumns::Get().ColumnNameToIndexMapping.FindChecked( ColumnBeingSorted )->Index; if( NewViewMode == EEventGraphViewModes::FlatInclusive || NewViewMode == EEventGraphViewModes::FlatInclusiveCoalesced || NewViewMode == EEventGraphViewModes::Hierarchical ) { ColumnIndex = EEventPropertyIndex::InclusiveTimeMS; SetSortModeForColumn( FEventGraphColumns::Get().Collection[(uint32)ColumnIndex].ID, EColumnSortMode::Descending ); } else if( NewViewMode == EEventGraphViewModes::FlatExclusive || NewViewMode == EEventGraphViewModes::FlatExclusiveCoalesced ) { ColumnIndex = EEventPropertyIndex::ExclusiveTimeMS; SetSortModeForColumn( FEventGraphColumns::Get().Collection[(uint32)ColumnIndex].ID, EColumnSortMode::Descending ); } ScrollToTheSlowestSelectedEvent( ColumnIndex ); if( NewViewMode == EEventGraphViewModes::Hierarchical ) { TArray SelectedEvents = TreeView_Base->GetSelectedItems(); for( int32 Nx = 0; Nx < SelectedEvents.Num(); ++Nx ) { // Find stack for the specified event and expand that stack. FEventGraphSamplePtr EventToExpand = SelectedEvents[Nx]; TArray StackToExpand; EventToExpand->GetStack( StackToExpand ); for( int32 Index = 0; Index < StackToExpand.Num(); ++Index ) { TreeView_Base->SetItemExpansion( StackToExpand[Index], true ); } } } TreeView_Refresh(); } void SEventGraph::ScrollToTheSlowestSelectedEvent( EEventPropertyIndex ColumnIndex ) { TArray SelectedEvents = TreeView_Base->GetSelectedItems(); if( SelectedEvents.Num() > 0 ) { // Sort events by the inclusive or the exclusive time, depends on the view mode. const FEventGraphColumn& Column = FEventGraphColumns::Get().Collection[(uint32)ColumnIndex]; FEventArraySorter::Sort( SelectedEvents, Column.ID, EEventCompareOps::Greater ); // Scroll to the the slowest item. TreeView_Base->RequestScrollIntoView( SelectedEvents[0] ); } } /*----------------------------------------------------------------------------- Get/Set HierarchicalSelectedEvents -----------------------------------------------------------------------------*/ void SEventGraph::GetHierarchicalSelectedEvents( TArray< FEventGraphSamplePtr >& out_HierarchicalSelectedEvents, const TArray* SelectedEvents /*= NULL*/ ) const { out_HierarchicalSelectedEvents.Reset(); TArray ViewSelectedEvents; if( SelectedEvents != NULL ) { ViewSelectedEvents = *SelectedEvents; } else { ViewSelectedEvents = TreeView_Base->GetSelectedItems(); } if( GetCurrentStateViewMode() == EEventGraphViewModes::FlatInclusiveCoalesced || GetCurrentStateViewMode() == EEventGraphViewModes::FlatExclusiveCoalesced ) { for( int32 Nx = 0; Nx < ViewSelectedEvents.Num(); ++Nx ) { HierarchicalToFlatCoalesced.MultiFind( ViewSelectedEvents[Nx]->_StatName, out_HierarchicalSelectedEvents ); } } else { out_HierarchicalSelectedEvents = ViewSelectedEvents; } } void SEventGraph::SetHierarchicalSelectedEvents( const TArray& HierarchicalSelectedEvents ) { struct FCoalescedEventMatcher { FCoalescedEventMatcher( const FEventGraphSamplePtr& InEventPtr ) : EventPtr( InEventPtr ) {} bool operator()(const FEventGraphSamplePtr& Other) const { return EventPtr->_StatName == Other->_StatName; } protected: const FEventGraphSamplePtr& EventPtr; }; TArray SelectedEvents; if( GetCurrentStateViewMode() == EEventGraphViewModes::FlatInclusiveCoalesced || GetCurrentStateViewMode() == EEventGraphViewModes::FlatExclusiveCoalesced ) { for( int32 Nx = 0; Nx < HierarchicalSelectedEvents.Num(); ++Nx ) { const int32 Index = Events_FlatCoalesced.IndexOfByPredicate(FCoalescedEventMatcher(HierarchicalSelectedEvents[Nx])); if( Index != INDEX_NONE ) { SelectedEvents.AddUnique( Events_FlatCoalesced[Index] ); } } } else { SelectedEvents = HierarchicalSelectedEvents; } TreeView_Base->ClearSelection(); for( int32 Nx = 0; Nx < SelectedEvents.Num(); ++Nx ) { TreeView_Base->SetItemSelection( SelectedEvents[Nx], true ); } } /*----------------------------------------------------------------------------- Get/Set HierarchicalExpandedEvents -----------------------------------------------------------------------------*/ void SEventGraph::GetHierarchicalExpandedEvents( TSet& out_HierarchicalExpandedEvents ) const { if( GetCurrentStateViewMode() == EEventGraphViewModes::Hierarchical ) { out_HierarchicalExpandedEvents.Empty(); TreeView_Base->GetExpandedItems( out_HierarchicalExpandedEvents ); } } void SEventGraph::SetHierarchicalExpandedEvents( const TSet& HierarchicalExpandedEvents ) { if( GetCurrentStateViewMode() == EEventGraphViewModes::Hierarchical ) { TreeView_Base->ClearExpandedItems(); for( auto It = HierarchicalExpandedEvents.CreateConstIterator(); It; ++It ) { TreeView_Base->SetItemExpansion( *It, true ); } } } /*----------------------------------------------------------------------------- Function details -----------------------------------------------------------------------------*/ FReply SEventGraph::CallingCalledFunctionButton_OnClicked( FEventGraphSamplePtr EventPtr ) { if( !EventPtr->_bIsCulled ) { UpdateFunctionDetailsForEvent( EventPtr ); TArray Events; Events.Add( EventPtr ); ShowEventsInViewMode( Events, GetCurrentStateViewMode() ); } return FReply::Handled(); } void SEventGraph::DisableFunctionDetails() { (*CurrentFunctionDescSlot ) [ SNew(STextBlock) .WrapTextAt( 128.0f ) .Text( LOCTEXT("FunctionDetails_SelectOneEvent","Function details view works only if you select one event. Please select an individual event to proceed.") ) .TextStyle(FProfilerStyle::Get(), TEXT("Profiler.Tooltip") ) ]; VerticalBox_TopCalling->ClearChildren(); VerticalBox_TopCalled->ClearChildren(); HighlightedEventName = NAME_None; } void SEventGraph::UpdateFunctionDetailsForEvent( FEventGraphSamplePtr SelectedEvent ) { GenerateCallerCalleeGraph( SelectedEvent ); (*CurrentFunctionDescSlot) [ GetContentForEvent(SelectedEvent,1.0f,true) ]; RecreateWidgetsForTopEvents( VerticalBox_TopCalling, TopCallingFunctionEvents ); RecreateWidgetsForTopEvents( VerticalBox_TopCalled, TopCalledFunctionEvents ); HighlightedEventName = SelectedEvent->_StatName; } void SEventGraph::UpdateFunctionDetails() { TArray SelectedItems = TreeView_Base->GetSelectedItems(); if( SelectedItems.Num() == 1 ) { UpdateFunctionDetailsForEvent( SelectedItems[0] ); } else { DisableFunctionDetails(); } } /*----------------------------------------------------------------------------- Function details -----------------------------------------------------------------------------*/ void SEventGraph::GenerateCallerCalleeGraph( FEventGraphSamplePtr SelectedEvent ) { TArray< FEventGraphSamplePtr > EventsByName; HierarchicalToFlatCoalesced.MultiFind( SelectedEvent->_StatName, EventsByName ); // Parent TSet< FEventGraphSamplePtr > CallingFunctionEventSet; for( int32 Nx = 0; Nx < EventsByName.Num(); ++Nx ) { FEventGraphSamplePtr ParentPtr = EventsByName[Nx]->GetParent(); if( ParentPtr.IsValid() && !ParentPtr->IsRoot() ) { CallingFunctionEventSet.Add( ParentPtr ); } } GenerateTopEvents( CallingFunctionEventSet, TopCallingFunctionEvents ); CalculateEventWeights( TopCallingFunctionEvents ); // Children TSet< FEventGraphSamplePtr > CalledFunctionEventSet; for( int32 Nx = 0; Nx < EventsByName.Num(); ++Nx ) { CalledFunctionEventSet.Append( EventsByName[Nx]->GetChildren() ); } GenerateTopEvents( CalledFunctionEventSet, TopCalledFunctionEvents ); CalculateEventWeights( TopCalledFunctionEvents ); } void SEventGraph::GenerateTopEvents( const TSet< FEventGraphSamplePtr >& EventPtrSet, TArray& out_Results ) { const int NumTopEvents = 5; TArray EventPtrArray = EventPtrSet.Array(); // Calculate total time. double TotalTimeMS = 0.0f; for( int32 Nx = 0; Nx < EventPtrArray.Num(); ++Nx ) { TotalTimeMS += EventPtrArray[Nx]->_InclusiveTimeMS; } // Sort events by the inclusive time. const FEventGraphColumn& Column = FEventGraphColumns::Get().Collection[(uint32)EEventPropertyIndex::InclusiveTimeMS]; FEventArraySorter::Sort( EventPtrArray, Column.ID, EEventCompareOps::Greater ); // Calculate total time for the top events. double Top5TimeMS = 0.0f; for( int32 Nx = 0; Nx < EventPtrArray.Num() && Nx < NumTopEvents; ++Nx ) { Top5TimeMS += EventPtrArray[Nx]->_InclusiveTimeMS; } // Calculate values for top events. out_Results.Reset(); for( int32 Nx = 0; Nx < EventPtrArray.Num() && Nx < NumTopEvents; ++Nx ) { const FEventGraphSamplePtr EventPtr = EventPtrArray[Nx]; const float IncTimeToTotalPct = static_cast(EventPtr->_InclusiveTimeMS / TotalTimeMS); const float HeightPct = static_cast(EventPtr->_InclusiveTimeMS / Top5TimeMS); out_Results.Add( FEventPtrAndMisc(EventPtr,IncTimeToTotalPct,HeightPct) ); } } void SEventGraph::CalculateEventWeights( TArray& Events ) { // TODO: This value was calculated by hand // and gives reasonable results for scaling button in the function details. // Maximum number of visible buttons is 5, 5 buttons require 100px, where 20px for each button // The height of the area is 190px so 190px/20px = ~9 const float MaxButtons = 9.0f; const float TotalHeightPct = MaxButtons / 5.0f; const float MinHeightPct = TotalHeightPct / MaxButtons; // Update min height pct for buttons where the ratio is too low float CurrentHeightPct = 0.0f; for( int32 Tx = 0; Tx < Events.Num(); ++Tx ) { FEventPtrAndMisc& EventPtr = Events[Tx]; EventPtr.HeightPct = FMath::Max( EventPtr.HeightPct, MinHeightPct ); CurrentHeightPct += EventPtr.HeightPct; } // Update height pct to fit all button into visible area. const float FitHeightPct = TotalHeightPct / CurrentHeightPct; for( int32 Tx = 0; Tx < Events.Num(); ++Tx ) { FEventPtrAndMisc& EventPtr = Events[Tx]; EventPtr.HeightPct *= FitHeightPct; } } void SEventGraph::RecreateWidgetsForTopEvents( const TSharedPtr& DestVerticalBox, const TArray& TopEvents ) { DestVerticalBox->ClearChildren(); for( int32 Nx = 0; Nx < TopEvents.Num(); ++Nx ) { const FEventPtrAndMisc& EventPtrAndPct = TopEvents[Nx]; DestVerticalBox->AddSlot() .FillHeight( EventPtrAndPct.HeightPct ) .Padding( 1.0f ) [ SNew(SButton) .HAlign( HAlign_Left ) .VAlign( VAlign_Center ) .TextStyle(FProfilerStyle::Get(), "Profiler.Tooltip" ) .ContentPadding( FMargin(4.0f, 1.0f) ) .OnClicked( this, &SEventGraph::CallingCalledFunctionButton_OnClicked, EventPtrAndPct.EventPtr ) [ GetContentForEvent( EventPtrAndPct.EventPtr, EventPtrAndPct.IncTimeToTotalPct, false ) ] ]; } } FString SEventGraph::GetEventDescription( FEventGraphSamplePtr EventPtr, float Pct, const bool bSimple ) { const bool bIgnoreEventName = EventPtr->_ThreadName == EventPtr->_StatName; const FString ThreadName = EventPtr->_ThreadName.GetPlainNameString().LeftChop( 9 );// TODO: Fix this const FString EventName = FProfilerHelper::ShortenName(EventPtr->_StatName.GetPlainNameString(),28); const FString ThreadAndEventName = bIgnoreEventName ? ThreadName : FString::Printf( TEXT("%s:%s"), *ThreadName, *EventName ); const FString Caption = FString::Printf ( TEXT("%s, %.1f%% (%s)"), *ThreadAndEventName, Pct*100.0f, *EventPtr->GetFormattedValue(EEventPropertyIndex::InclusiveTimeMS) ); return bSimple ? ThreadAndEventName : Caption; } TSharedRef SEventGraph::GetContentForEvent( FEventGraphSamplePtr EventPtr, float Pct, const bool bSimple ) { TSharedRef Content = SNew(SHorizontalBox); Content->AddSlot() .AutoWidth() .HAlign(HAlign_Center) .VAlign(VAlign_Center) [ SNew(STextBlock) .Text( FText::FromString(GetEventDescription(EventPtr,Pct,bSimple)) ) .TextStyle(FProfilerStyle::Get(), bSimple ? TEXT("Profiler.Tooltip") : TEXT("Profiler.EventGraph.DarkText") ) ]; if( EventPtr->_bIsCulled ) { Content->AddSlot() .AutoWidth() .HAlign(HAlign_Center) .VAlign(VAlign_Center) [ SNew( SImage ) .Image(FProfilerStyle::Get().GetBrush("Profiler.EventGraph.CulledEvent") ) .ToolTipText( LOCTEXT("Misc_EventCulled","Event is culled") ) ]; } if( EventPtr->_bIsFiltered ) { Content->AddSlot() .AutoWidth() .HAlign(HAlign_Center) .VAlign(VAlign_Center) [ SNew( SImage ) .Image(FProfilerStyle::Get().GetBrush("Profiler.EventGraph.FilteredEvent") ) .ToolTipText( LOCTEXT("Misc_EventFiltered","Event is filtered") ) ]; } Content->AddSlot() .AutoWidth() .HAlign(HAlign_Center) .VAlign(VAlign_Center) [ SNew( SImage ) .Image(FProfilerStyle::Get().GetBrush("Profiler.Tooltip.HintIcon10") ) .ToolTip( SEventGraphTooltip::GetTableCellTooltip( EventPtr ) ) ]; return Content; } /*----------------------------------------------------------------------------- History management -----------------------------------------------------------------------------*/ void SEventGraph::SetNewEventGraphState( const FEventGraphDataRef AverageEventGraph, const FEventGraphDataRef MaximumEventGraph, bool bInitial ) { PROFILER_SCOPE_LOG_TIME( TEXT( "SEventGraph::UpdateEventGraph" ), nullptr ); // Store current operation. SaveCurrentEventGraphState(); FEventGraphState* Op = new FEventGraphState( AverageEventGraph->DuplicateAsRef(), MaximumEventGraph->DuplicateAsRef() ); CurrentStateIndex = EventGraphStatesHistory.Add( MakeShareable(Op) ); RestoreEventGraphStateFrom( GetCurrentState(), bInitial ); FillThreadFilterOptions(); } FReply SEventGraph::HistoryBack_OnClicked() { SwitchToEventGraphState( CurrentStateIndex-1 ); return FReply::Handled(); } bool SEventGraph::HistoryBack_IsEnabled() const { return EventGraphStatesHistory.Num() > 1 && CurrentStateIndex > 0; } FText SEventGraph::HistoryBack_GetToolTipText() const { // TODO: Add a nicer custom tooltip. if( HistoryBack_IsEnabled() ) { return FText::Format( LOCTEXT("HistoryBack_Tooltip", "Back to {0}"), EventGraphStatesHistory[CurrentStateIndex-1]->GetFullDescription() ); } return FText::GetEmpty(); } FReply SEventGraph::HistoryForward_OnClicked() { SwitchToEventGraphState( CurrentStateIndex+1 ); return FReply::Handled(); } bool SEventGraph::HistoryForward_IsEnabled() const { return EventGraphStatesHistory.Num() > 1 && CurrentStateIndex < EventGraphStatesHistory.Num()-1; } FText SEventGraph::HistoryForward_GetToolTipText() const { // TODO: Add a nicer custom tooltip. if( HistoryForward_IsEnabled() ) { return FText::Format( LOCTEXT("HistoryForward_Tooltip", "Forward to {0}"), EventGraphStatesHistory[CurrentStateIndex+1]->GetFullDescription() ); } return FText::GetEmpty(); } bool SEventGraph::EventGraph_IsEnabled() const { return EventGraphStatesHistory.Num() > 0; } bool SEventGraph::HistoryList_IsEnabled() const { return EventGraphStatesHistory.Num() > 1; } TSharedRef SEventGraph::HistoryList_GetMenuContent() const { FMenuBuilder MenuBuilder(true, NULL); for( int32 StateIndex = 0; StateIndex < EventGraphStatesHistory.Num(); ++StateIndex ) { const FEventGraphStateRef StateRef = EventGraphStatesHistory[StateIndex]; MenuBuilder.AddMenuEntry ( StateRef->GetFullDescription(), FText(), FSlateIcon(), HistoryList_GoTo_Custom( StateIndex ), NAME_None, EUserInterfaceActionType::RadioButton ); } return MenuBuilder.MakeWidget(); } void SEventGraph::HistoryList_GoTo_Execute( int32 StateIndex ) { if( StateIndex != CurrentStateIndex ) { SwitchToEventGraphState( StateIndex ); } } void SEventGraph::SaveCurrentEventGraphState() { if( EventGraphStatesHistory.Num() > 0 ) { FEventGraphStateRef EventGraphState = GetCurrentState(); GetHierarchicalExpandedEvents( EventGraphState->ExpandedEvents ); GetHierarchicalSelectedEvents( EventGraphState->SelectedEvents ); } } void SEventGraph::SetEventGraphFromStateInternal( const FEventGraphStateRef& EventGraphState ) { EventGraphState->ApplyCulling(); EventGraphState->ApplyFiltering(); if( EventGraphState->ExpandedCulledEvents.Num() > 0 ) { const int32 NumChildren = EventGraphState->ExpandedCulledEvents.Num(); for( int32 Nx = 0; Nx < NumChildren; ++Nx ) { const FEventGraphSamplePtr& EventPtr = EventGraphState->ExpandedCulledEvents[Nx]; //EventPtr->PropertyValueAsBool( EEventPropertyIndex::bIsCulled) = false; EventPtr->_bIsCulled = false; EventPtr->RequestNotCulledChildrenUpdate(); } } CreateEvents(); SetTreeItemsForViewMode( EventGraphState->ViewMode, EventGraphState->EventGraphType ); SetHierarchicalSelectedEvents( EventGraphState->SelectedEvents ); SetHierarchicalExpandedEvents( EventGraphState->ExpandedEvents ); SortEvents(); ScrollToTheSlowestSelectedEvent( EEventPropertyIndex::InclusiveTimeMS ); UpdateFunctionDetails(); TreeView_Refresh(); } void SEventGraph::RestoreEventGraphStateFrom( const FEventGraphStateRef EventGraphState, const bool bRestoredFromHistoryEvent /*= true*/ ) { SetEventGraphFromStateInternal( EventGraphState ); if( bRestoredFromHistoryEvent ) { // Broadcast that a new graph event has been set. EventGraphRestoredFromHistoryEvent.Broadcast( EventGraphState->GetEventGraph()->GetFrameStartIndex(), EventGraphState->GetEventGraph()->GetFrameEndIndex() ); } } void SEventGraph::SwitchToEventGraphState( int32 StateIndex ) { SaveCurrentEventGraphState(); CurrentStateIndex = StateIndex; RestoreEventGraphStateFrom( GetCurrentState() ); } /*----------------------------------------------------------------------------- UI Actions -----------------------------------------------------------------------------*/ void SEventGraph::BindCommands() { Map_SelectAllFrames_Global(); } void SEventGraph::SetRoot_Execute() { TArray SelectedLeafs; GetHierarchicalSelectedEvents( SelectedLeafs ); TMap< FEventGraphSamplePtr, TSet > StacksForSelectedLeafs; // Grab stack for all selected events. for( int32 Nx = 0; Nx < SelectedLeafs.Num(); ++Nx ) { const FEventGraphSamplePtr SelectedLeaf = SelectedLeafs[Nx]; TArray ArrayStack; SelectedLeaf->GetStack( ArrayStack ); TSet SetStack; SetStack.Append( ArrayStack ); StacksForSelectedLeafs.Add( SelectedLeaf, SetStack ); } // Remove duplicated stacks. // Not super efficient, but should be ok for now. for( auto OuterIt = StacksForSelectedLeafs.CreateIterator(); OuterIt; ++OuterIt ) { const TSet& OuterStack = OuterIt.Value(); const FEventGraphSamplePtr OuterLeafPtr = OuterIt.Key(); for( auto InnerIt = StacksForSelectedLeafs.CreateIterator(); InnerIt; ++InnerIt ) { const TSet& InnerStack = InnerIt.Value(); const FEventGraphSamplePtr InnerLeafPtr = InnerIt.Key(); // The same roots, so ignore. if( InnerLeafPtr == OuterLeafPtr ) { continue; } const bool bRemoveInner = InnerStack.Contains( OuterLeafPtr ); const bool bRemoveOuter = OuterStack.Contains( InnerLeafPtr ); if( bRemoveOuter ) { OuterIt.RemoveCurrent(); break; } else if( bRemoveInner ) { InnerIt.RemoveCurrent(); } } } TArray UniqueLeafs; StacksForSelectedLeafs.GenerateKeyArray( UniqueLeafs ); // Store current operation. SaveCurrentEventGraphState(); FEventGraphState* Op = GetCurrentState()->CreateCopyWithNewRoot( UniqueLeafs ); CurrentStateIndex = EventGraphStatesHistory.Insert( MakeShareable(Op), CurrentStateIndex+1 ); RestoreEventGraphStateFrom( GetCurrentState() ); } bool SEventGraph::SetRoot_CanExecute() const { const int32 NumItemsSelected = TreeView_Base->GetNumItemsSelected(); return NumItemsSelected > 0 && NumItemsSelected < 16; } void SEventGraph::ClearHistory_Execute() { // Remove all history from the currently visible event graph, but leave the default state. const FEventGraphStateRef EventGraphState = GetCurrentState(); for( int32 Nx = 0; Nx < EventGraphStatesHistory.Num(); ++Nx ) { const FEventGraphStateRef It = EventGraphStatesHistory[Nx]; if( It->MaximumEventGraph == EventGraphState->MaximumEventGraph && It->HistoryType != EEventHistoryTypes::NewEventGraph ) { EventGraphStatesHistory.RemoveAt( Nx, EAllowShrinking::No); Nx--; } } // Find new index of the current state. for( int32 Nx = 0; Nx < EventGraphStatesHistory.Num(); ++Nx ) { const FEventGraphStateRef It = EventGraphStatesHistory[Nx]; if( It->MaximumEventGraph == EventGraphState->MaximumEventGraph ) { CurrentStateIndex = Nx; break; } } RestoreEventGraphStateFrom( GetCurrentState() ); } bool SEventGraph::ClearHistory_CanExecute() const { bool bCanExecute = false; const FEventGraphStateRef EventGraphState = GetCurrentState(); for( int32 Nx = 0; Nx < EventGraphStatesHistory.Num(); ++Nx ) { const FEventGraphStateRef It = EventGraphStatesHistory[Nx]; if( It->MaximumEventGraph == EventGraphState->MaximumEventGraph && It->HistoryType != EEventHistoryTypes::NewEventGraph ) { bCanExecute = true; } } return bCanExecute; } void SEventGraph::FilterOutByProperty_Execute( const FEventGraphSamplePtr EventPtr, const FName PropertyName, const bool bReset ) { PROFILER_SCOPE_LOG_TIME( TEXT( "SEventGraph::FilterOutByProperty_Execute" ), nullptr ); // Store current operation. SaveCurrentEventGraphState(); FEventGraphState* Op = GetCurrentState()->CreateCopyWithFiltering( PropertyName, EventPtr ); CurrentStateIndex = EventGraphStatesHistory.Insert( MakeShareable(Op), CurrentStateIndex+1 ); RestoreEventGraphStateFrom( GetCurrentState() ); } bool SEventGraph::FilterOutByProperty_CanExecute( const FEventGraphSamplePtr EventPtr, const FName PropertyName, const bool bReset ) const { const FEventGraphColumn& Column = TreeViewHeaderColumns.FindChecked(PropertyName); if( bReset ) { return false; } else { return EventPtr.IsValid() && Column.bCanBeFiltered; } } void SEventGraph::CullByProperty_Execute( const FEventGraphSamplePtr EventPtr, const FName PropertyName, const bool bReset ) { PROFILER_SCOPE_LOG_TIME( TEXT( "SEventGraph::CullByProperty_Execute" ), nullptr ); // Store current operation. SaveCurrentEventGraphState(); FEventGraphState* Op = GetCurrentState()->CreateCopyWithCulling( PropertyName, EventPtr ); CurrentStateIndex = EventGraphStatesHistory.Insert( MakeShareable(Op), CurrentStateIndex+1 ); RestoreEventGraphStateFrom( GetCurrentState() ); } bool SEventGraph::CullByProperty_CanExecute( const FEventGraphSamplePtr EventPtr, const FName PropertyName, const bool bReset ) const { const FEventGraphColumn& Column = TreeViewHeaderColumns.FindChecked(PropertyName); if( bReset ) { return false; } else { return EventPtr.IsValid() && Column.bCanBeCulled; } } void SEventGraph::GetEventsForChangingExpansion( TArray& out_Events, const ESelectedEventTypes::Type SelectedEventType ) { if( SelectedEventType == ESelectedEventTypes::AllEvents ) { out_Events = GetCurrentState()->GetRealRoot()->GetChildren(); } else if( SelectedEventType == ESelectedEventTypes::SelectedEvents ) { out_Events = TreeView_Base->GetSelectedItems(); } else if( SelectedEventType == ESelectedEventTypes::SelectedThreadEvents ) { TArray SelectedItems = TreeView_Base->GetSelectedItems(); TSet ThreadEventSet; for( int32 EventIndex = 0; EventIndex < SelectedItems.Num(); EventIndex++ ) { const FEventGraphSamplePtr ThreadEvent = SelectedItems[EventIndex]->GetOutermost(); ThreadEventSet.Add( ThreadEvent ); } out_Events = ThreadEventSet.Array(); } } void SEventGraph::SetExpansionForEvents_Execute( const ESelectedEventTypes::Type SelectedEventType, bool bShouldExpand ) { TArray Events; GetEventsForChangingExpansion( Events, SelectedEventType ); TreeView_SetItemsExpansion_Recurrent( Events, bShouldExpand ); } bool SEventGraph::SetExpansionForEvents_CanExecute( const ESelectedEventTypes::Type SelectedEventType, bool bShouldExpand ) const { const int32 NumSelectedItems = TreeView_Base->GetNumItemsSelected(); if( SelectedEventType == ESelectedEventTypes::AllEvents ) { return GetCurrentStateViewMode() == EEventGraphViewModes::Hierarchical; } else if( SelectedEventType == ESelectedEventTypes::SelectedEvents ) { return GetCurrentStateViewMode() == EEventGraphViewModes::Hierarchical && NumSelectedItems > 0; } else if( SelectedEventType == ESelectedEventTypes::SelectedThreadEvents ) { return GetCurrentStateViewMode() == EEventGraphViewModes::Hierarchical && NumSelectedItems > 0; } return false; } void SEventGraph::Map_SelectAllFrames_Global() { TSharedRef ProfilerCommandList = FProfilerManager::Get()->GetCommandList(); const FProfilerCommands& ProfilerCommands = FProfilerManager::GetCommands(); const FProfilerActionManager& ProfilerActionManager = FProfilerManager::GetActionManager(); // Assumes only one instance of the event graph, this may change in future. const FUIAction* UIAction = ProfilerCommandList->GetActionForCommand( ProfilerCommands.EventGraph_SelectAllFrames ); if( !UIAction ) { ProfilerCommandList->MapAction( ProfilerCommands.EventGraph_SelectAllFrames, SelectAllFrames_Custom() ); } else { // Replace with the new UI Action. const FUIAction NewUIAction = SelectAllFrames_Custom(); new((void*)UIAction) FUIAction(NewUIAction); } } /*----------------------------------------------------------------------------- Settings -----------------------------------------------------------------------------*/ EVisibility SEventGraph::EventGraphViewMode_GetVisibility( const EEventGraphViewModes::Type ViewMode ) const { if( ViewMode == EEventGraphViewModes::FlatInclusiveCoalesced || ViewMode == EEventGraphViewModes::FlatExclusiveCoalesced ) { const EVisibility Vis = FProfilerManager::GetSettings().bShowCoalescedViewModesInEventGraph ? EVisibility::Visible : EVisibility::Collapsed; if (Vis == EVisibility::Collapsed && GetCurrentStateViewMode() == ViewMode) { // If view mode is not available event graph will switch to the hierarchical view mode. SEventGraph* MutableThis = const_cast< SEventGraph* >( this ); MutableThis->EventGraphViewMode_OnCheckStateChanged( ECheckBoxState::Checked, EEventGraphViewModes::Hierarchical ); } return Vis; } else { return EVisibility::Visible; } } void SEventGraph::ExpandCulledEvents( FEventGraphSamplePtr EventPtr ) { // Update not culled children. EventPtr->SetBooleanStateForAllChildren(false); EventPtr->SetBooleanStateForAllChildren(true); struct FAddExcludedCulledEvents { FORCEINLINE void operator()( FEventGraphSample* InEventPtr, TArray& out_ExpandedCulledEvents ) { out_ExpandedCulledEvents.Add( InEventPtr->AsShared() ); } }; EventPtr->ExecuteOperationForAllChildren( FAddExcludedCulledEvents(), GetCurrentState()->ExpandedCulledEvents ); CreateEvents(); TreeView_Refresh(); } void SEventGraph::SelectAllFrames_Execute() { SwitchToEventGraphState( 0 ); } bool SEventGraph::SelectAllFrames_CanExecute() const { return IsEventGraphStatesHistoryValid(); } void SEventGraph::ProfilerManager_OnViewModeChanged( EProfilerViewMode NewViewMode ) { // if( NewViewMode == EProfilerViewMode::LineIndexBased ) // { // FunctionDetailsBox->SetVisibility( EVisibility::Visible ); // FunctionDetailsBox->SetEnabled( true ); // } // else if( NewViewMode == EProfilerViewMode::ThreadViewTimeBased ) // { // FunctionDetailsBox->SetVisibility( EVisibility::Collapsed ); // FunctionDetailsBox->SetEnabled( false ); // } } #undef LOCTEXT_NAMESPACE /*----------------------------------------------------------------------------- EventGraphState -----------------------------------------------------------------------------*/ #define LOCTEXT_NAMESPACE "FEventGraphState" FText SEventGraph::FEventGraphState::GetFullDescription() const { FTextBuilder Builder; FFormatNamedArguments Args; Args.Add(TEXT("FrameStartIndex"), GetEventGraph()->GetFrameStartIndex()); Args.Add(TEXT("FrameEndIndex"), GetEventGraph()->GetFrameEndIndex()); Args.Add(TEXT("NumberOfFrames"), GetNumFrames()); Builder.AppendLineFormat(LOCTEXT("FullDesc", "Event graph with range ({FrameStartIndex},{FrameEndIndex}) contains {NumberOfFrames} frame(s)"), Args); Builder.Indent(); if( IsRooted() ) { Builder.AppendLine(GetRootedDesc()); } if( IsCulled() ) { Builder.AppendLine(GetCullingDesc()); } if( IsFiltered() ) { Builder.AppendLine(GetFilteringDesc()); } return Builder.ToText(); } FText SEventGraph::FEventGraphState::GetRootedDesc() const { const int32 NumFakeRoots = FakeRoot->GetChildren().Num(); if (NumFakeRoots == 1) { FFormatNamedArguments Args; Args.Add(TEXT("StatName"), FText::FromName(FakeRoot->GetChildren()[0]->_StatName)); return FText::Format(LOCTEXT("RootedDesc_SingleChild", "Rooted: {StatName}"), Args); } return LOCTEXT("RootedDesc_MultipleChildren", "Rooted: Multiple"); } FText SEventGraph::FEventGraphState::GetCullingDesc() const { FFormatNamedArguments Args; Args.Add(TEXT("CulledPropertyName"), FText::FromName(CullPropertyName)); Args.Add(TEXT("EventName"), FText::FromString(CullEventPtr->GetFormattedValue(FEventGraphSample::GetEventPropertyByName(CullPropertyName).Index))); return FText::Format(LOCTEXT("CulledDesc", "Culled: {CulledPropertyName} {EventName}"), Args); } FText SEventGraph::FEventGraphState::GetFilteringDesc() const { FFormatNamedArguments Args; Args.Add(TEXT("FilterPropertyName"), FText::FromName(FilterPropertyName)); Args.Add(TEXT("EventName"), FText::FromString(FilterEventPtr->GetFormattedValue(FEventGraphSample::GetEventPropertyByName(FilterPropertyName).Index))); return FText::Format(LOCTEXT("FilteredDesc", "Filtered: {FilterPropertyName} {EventName}"), Args); } FText SEventGraph::FEventGraphState::GetHistoryDesc() const { FText Result = LOCTEXT("DefaultDesc", "Default state"); if( HistoryType == EEventHistoryTypes::Rooted ) { Result = GetRootedDesc(); } else if( HistoryType == EEventHistoryTypes::Culled ) { Result = GetCullingDesc(); } else if( HistoryType == EEventHistoryTypes::Filtered ) { Result = GetFilteringDesc(); } return Result; } static void CreateOneToOneMapping_EventGraphSample ( const FEventGraphSamplePtr LocalEvent, const FEventGraphSamplePtr SourceEvent, TMap< FEventGraphSamplePtr, FEventGraphSamplePtr >& out_MaximumToAverageMapping, TMap< FEventGraphSamplePtr, FEventGraphSamplePtr >& out_AverageToMaximumMapping ) { out_MaximumToAverageMapping.Add( LocalEvent, SourceEvent ); out_AverageToMaximumMapping.Add( SourceEvent, LocalEvent ); check( LocalEvent->GetChildren().Num() == SourceEvent->GetChildren().Num() ); for( int32 Index = 0; Index < LocalEvent->GetChildren().Num(); ++Index ) { CreateOneToOneMapping_EventGraphSample( LocalEvent->GetChildren()[Index], SourceEvent->GetChildren()[Index], out_MaximumToAverageMapping, out_AverageToMaximumMapping ); } } void SEventGraph::FEventGraphState::CreateOneToOneMapping() { CreateOneToOneMapping_EventGraphSample( MaximumEventGraph->GetRoot(), AverageEventGraph->GetRoot(), MaximumToAverageMapping, AverageToMaximumMapping ); } #undef LOCTEXT_NAMESPACE #endif // STATS