2014-12-07 19:09:38 -05:00
|
|
|
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
|
2014-03-14 14:13:41 -04:00
|
|
|
|
2014-10-14 22:50:06 -04:00
|
|
|
#include "SlateBasics.h"
|
2014-03-14 14:13:41 -04:00
|
|
|
#include "TaskGraphInterfaces.h"
|
|
|
|
|
#include "VisualizerEvents.h"
|
|
|
|
|
#include "SEventsTree.h"
|
2014-10-14 22:50:06 -04:00
|
|
|
#include "SSearchBox.h"
|
2014-03-14 14:13:41 -04:00
|
|
|
|
|
|
|
|
#define LOCTEXT_NAMESPACE "SEventsTree"
|
|
|
|
|
|
|
|
|
|
FName SEventsTree::NAME_NameColumn = FName(*NSLOCTEXT("TaskGraph", "ColumnName", "Name").ToString());
|
|
|
|
|
FName SEventsTree::NAME_DurationColumn = FName( *NSLOCTEXT("TaskGraph", "ColumnDuration", "Duration").ToString() );
|
|
|
|
|
|
|
|
|
|
void SEventItem::Construct( const FArguments& InArgs, const TSharedRef<STableViewBase>& InOwnerTableView )
|
|
|
|
|
{
|
|
|
|
|
EventName = InArgs._EventName;
|
|
|
|
|
EventDuration = InArgs._EventDuration;
|
|
|
|
|
|
|
|
|
|
SMultiColumnTableRow< TSharedPtr< FVisualizerEvent > >::Construct( FSuperRowType::FArguments(), InOwnerTableView );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
|
|
|
|
TSharedRef< SWidget > SEventItem::GenerateWidgetForColumn( const FName& ColumnName )
|
|
|
|
|
{
|
|
|
|
|
if( ColumnName == SEventsTree::NAME_NameColumn )
|
|
|
|
|
{
|
|
|
|
|
return
|
|
|
|
|
SNew(SHorizontalBox)
|
|
|
|
|
+SHorizontalBox::Slot()
|
|
|
|
|
.AutoWidth()
|
|
|
|
|
[
|
|
|
|
|
SNew( SExpanderArrow, SharedThis(this) )
|
|
|
|
|
]
|
|
|
|
|
+SHorizontalBox::Slot()
|
|
|
|
|
.AutoWidth()
|
|
|
|
|
[
|
|
|
|
|
SNew( STextBlock )
|
2015-01-07 09:52:40 -05:00
|
|
|
.Text( FText::FromString(EventName) )
|
2014-03-14 14:13:41 -04:00
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
else if( ColumnName == SEventsTree::NAME_DurationColumn )
|
|
|
|
|
{
|
|
|
|
|
return SNew( STextBlock )
|
|
|
|
|
.Text( this, &SEventItem::GetDurationText );
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return
|
|
|
|
|
SNew( STextBlock )
|
2015-01-07 09:52:40 -05:00
|
|
|
. Text(FText::Format( LOCTEXT("UnsupportedColumnFmt", "Unsupported Column: {0}"), FText::FromName(ColumnName) ) );
|
2014-03-14 14:13:41 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
|
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
|
|
|
|
void SEventsTree::Construct( const FArguments& InArgs )
|
|
|
|
|
{
|
|
|
|
|
DurationUnits = EVisualizerTimeUnits::Milliseconds;
|
|
|
|
|
ViewMode = EVisualizerViewMode::Hierarchical;
|
|
|
|
|
bSuppressSelectionChangedEvent = false;
|
|
|
|
|
|
|
|
|
|
OnEventSelectionChangedDelegate = InArgs._OnEventSelectionChanged;
|
|
|
|
|
ProfileData = InArgs._ProfileData.Get();
|
|
|
|
|
|
|
|
|
|
// Duration column drop down menu
|
|
|
|
|
const bool bInShouldCloseWindowAfterMenuSelection = true;
|
|
|
|
|
FMenuBuilder MenuBuilder( bInShouldCloseWindowAfterMenuSelection, NULL );
|
|
|
|
|
{
|
|
|
|
|
FUIAction Action( FExecuteAction::CreateSP( this, &SEventsTree::SetDurationUnits, EVisualizerTimeUnits::Microseconds ),
|
|
|
|
|
FCanExecuteAction(),
|
|
|
|
|
FIsActionChecked::CreateSP( this, &SEventsTree::CheckDurationUnits, EVisualizerTimeUnits::Microseconds ) );
|
|
|
|
|
|
|
|
|
|
MenuBuilder.AddMenuEntry( LOCTEXT("Microseconds", "Microseconds"), FText(), FSlateIcon(), Action, NAME_None, EUserInterfaceActionType::Check );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
FUIAction Action( FExecuteAction::CreateSP( this, &SEventsTree::SetDurationUnits, EVisualizerTimeUnits::Milliseconds ),
|
|
|
|
|
FCanExecuteAction(),
|
|
|
|
|
FIsActionChecked::CreateSP( this, &SEventsTree::CheckDurationUnits, EVisualizerTimeUnits::Milliseconds ) );
|
|
|
|
|
|
|
|
|
|
MenuBuilder.AddMenuEntry( LOCTEXT("Milliseconds", "Milliseconds"), FText(), FSlateIcon(), Action, NAME_None, EUserInterfaceActionType::Check );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
FUIAction Action( FExecuteAction::CreateSP( this, &SEventsTree::SetDurationUnits, EVisualizerTimeUnits::Seconds ),
|
|
|
|
|
FCanExecuteAction(),
|
|
|
|
|
FIsActionChecked::CreateSP( this, &SEventsTree::CheckDurationUnits, EVisualizerTimeUnits::Seconds ) );
|
|
|
|
|
|
|
|
|
|
MenuBuilder.AddMenuEntry( LOCTEXT("Seconds", "Seconds"), FText(), FSlateIcon(), Action, NAME_None, EUserInterfaceActionType::Check );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Name column drop down menu
|
|
|
|
|
FMenuBuilder NameMenuBuilder( bInShouldCloseWindowAfterMenuSelection, NULL );
|
|
|
|
|
{
|
|
|
|
|
FUIAction Action( FExecuteAction::CreateSP( this, &SEventsTree::SetViewMode, EVisualizerViewMode::Hierarchical ),
|
|
|
|
|
FCanExecuteAction(),
|
|
|
|
|
FIsActionChecked::CreateSP( this, &SEventsTree::CheckViewMode, EVisualizerViewMode::Hierarchical ) );
|
|
|
|
|
|
|
|
|
|
NameMenuBuilder.AddMenuEntry( LOCTEXT("Hierarchical", "Hierarchical"), FText(), FSlateIcon(), Action, NAME_None, EUserInterfaceActionType::Check );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
FUIAction Action( FExecuteAction::CreateSP( this, &SEventsTree::SetViewMode, EVisualizerViewMode::Flat ),
|
|
|
|
|
FCanExecuteAction(),
|
|
|
|
|
FIsActionChecked::CreateSP( this, &SEventsTree::CheckViewMode, EVisualizerViewMode::Flat ) );
|
|
|
|
|
|
|
|
|
|
NameMenuBuilder.AddMenuEntry( LOCTEXT("Flat", "Flat"), FText(), FSlateIcon(), Action, NAME_None, EUserInterfaceActionType::Check );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
FUIAction Action( FExecuteAction::CreateSP( this, &SEventsTree::SetViewMode, EVisualizerViewMode::Coalesced ),
|
|
|
|
|
FCanExecuteAction(),
|
|
|
|
|
FIsActionChecked::CreateSP( this, &SEventsTree::CheckViewMode, EVisualizerViewMode::Coalesced ) );
|
|
|
|
|
|
|
|
|
|
NameMenuBuilder.AddMenuEntry( LOCTEXT("Coalesced", "Coalesced"), FText(), FSlateIcon(), Action, NAME_None, EUserInterfaceActionType::Check );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
FUIAction Action( FExecuteAction::CreateSP( this, &SEventsTree::SetViewMode, EVisualizerViewMode::FlatCoalesced ),
|
|
|
|
|
FCanExecuteAction(),
|
|
|
|
|
FIsActionChecked::CreateSP( this, &SEventsTree::CheckViewMode, EVisualizerViewMode::FlatCoalesced ) );
|
|
|
|
|
|
|
|
|
|
NameMenuBuilder.AddMenuEntry( LOCTEXT("FlatCoalesced", "Flat Coalesced"), FText(), FSlateIcon(), Action, NAME_None, EUserInterfaceActionType::Check );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this->ChildSlot
|
|
|
|
|
[
|
|
|
|
|
SNew( SVerticalBox )
|
2015-04-01 06:38:28 -04:00
|
|
|
+SVerticalBox::Slot().AutoHeight().Padding( 1.0f, 0.0f, 1.0f, 2.0f )
|
2014-03-14 14:13:41 -04:00
|
|
|
[
|
|
|
|
|
SNew( SSearchBox )
|
|
|
|
|
.ToolTipText( NSLOCTEXT("TaskGraph", "FilterSearchHint", "Type here to search events.") )
|
|
|
|
|
.OnTextChanged( this, &SEventsTree::OnFilterTextChanged )
|
|
|
|
|
.OnTextCommitted( this, &SEventsTree::OnFilterTextCommitted )
|
|
|
|
|
]
|
|
|
|
|
+SVerticalBox::Slot().Padding( 2 ).FillHeight( 1 ).VAlign( VAlign_Fill )
|
|
|
|
|
[
|
|
|
|
|
SNew( SHorizontalBox )
|
|
|
|
|
+SHorizontalBox::Slot().Padding( 2 ).FillWidth( 1 ).HAlign( HAlign_Fill )
|
|
|
|
|
[
|
|
|
|
|
// List of all events for the selected thread
|
|
|
|
|
SAssignNew( EventsListView, STreeView< TSharedPtr< FVisualizerEvent > > )
|
|
|
|
|
// List view items are this tall
|
2015-04-01 06:38:28 -04:00
|
|
|
.ItemHeight( 12 )
|
2014-03-14 14:13:41 -04:00
|
|
|
// Tell the list view where to get its source data
|
|
|
|
|
.TreeItemsSource( &SelectedEventsView )
|
|
|
|
|
// When the list view needs to generate a widget for some data item, use this method
|
|
|
|
|
.OnGenerateRow( this, &SEventsTree::OnGenerateWidgetForEventsList )
|
|
|
|
|
// Given some DataItem, this is how we find out if it has any children and what they are.
|
|
|
|
|
.OnGetChildren( this, &SEventsTree::OnGetChildrenForEventsList )
|
|
|
|
|
// Selection mode
|
|
|
|
|
.SelectionMode( ESelectionMode::Single )
|
|
|
|
|
// Selection callback
|
|
|
|
|
.OnSelectionChanged( this, &SEventsTree::OnEventSelectionChanged )
|
|
|
|
|
.HeaderRow
|
|
|
|
|
(
|
|
|
|
|
SNew( SHeaderRow )
|
|
|
|
|
+ SHeaderRow::Column( NAME_NameColumn )
|
|
|
|
|
.DefaultLabel( NSLOCTEXT("TaskGraph", "ColumnName", "Name") )
|
|
|
|
|
.SortMode( TAttribute< EColumnSortMode::Type >::Create( TAttribute< EColumnSortMode::Type >::FGetter::CreateSP( this, &SEventsTree::GetColumnSortMode, NAME_NameColumn ) ) )
|
|
|
|
|
.OnSort( FOnSortModeChanged::CreateSP( this, &SEventsTree::OnColumnSortModeChanged ) )
|
2015-04-01 06:38:28 -04:00
|
|
|
.FillWidth( 1.0f )
|
2014-03-14 14:13:41 -04:00
|
|
|
.MenuContent()
|
|
|
|
|
[
|
|
|
|
|
NameMenuBuilder.MakeWidget()
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
+ SHeaderRow::Column( NAME_DurationColumn )
|
2014-11-26 12:46:05 -05:00
|
|
|
.DefaultLabel( this, &SEventsTree::GetDurationColumnTitle )
|
2014-03-14 14:13:41 -04:00
|
|
|
.SortMode( TAttribute< EColumnSortMode::Type >::Create( TAttribute< EColumnSortMode::Type >::FGetter::CreateSP( this, &SEventsTree::GetColumnSortMode, NAME_DurationColumn ) ) )
|
|
|
|
|
.OnSort( FOnSortModeChanged::CreateSP( this, &SEventsTree::OnColumnSortModeChanged ) )
|
2015-04-01 06:38:28 -04:00
|
|
|
.FixedWidth( 128.0f )
|
2014-03-14 14:13:41 -04:00
|
|
|
.MenuContent()
|
|
|
|
|
[
|
|
|
|
|
MenuBuilder.MakeWidget()
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
]
|
|
|
|
|
]
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
|
|
|
|
|
|
|
|
|
EColumnSortMode::Type SEventsTree::GetColumnSortMode( const FName ColumnId )
|
|
|
|
|
{
|
|
|
|
|
if ( SortByColumn != ColumnId )
|
|
|
|
|
{
|
|
|
|
|
return EColumnSortMode::None;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return SortMode;
|
|
|
|
|
}
|
|
|
|
|
|
2014-11-26 12:46:05 -05:00
|
|
|
FText SEventsTree::GetDurationColumnTitle() const
|
2014-03-14 14:13:41 -04:00
|
|
|
{
|
|
|
|
|
static const FText Units[] = { NSLOCTEXT("TaskGraph", "microseconds", "microseconds"), NSLOCTEXT("TaskGraph", "milliseconds", "ms"), NSLOCTEXT("TaskGraph", "seconds", "s") };
|
|
|
|
|
|
2014-11-26 12:46:05 -05:00
|
|
|
return FText::Format( NSLOCTEXT("TaskGraph", "ColumnDurationValue", "Duration ({0})"), Units[ DurationUnits ] );
|
2014-03-14 14:13:41 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ClearEventsSelection( TArray< TSharedPtr< FVisualizerEvent > >& Events )
|
|
|
|
|
{
|
|
|
|
|
for( int32 Index = 0; Index < Events.Num(); ++Index )
|
|
|
|
|
{
|
|
|
|
|
TSharedPtr< FVisualizerEvent > CurrentEvent = Events[ Index ];
|
|
|
|
|
CurrentEvent->IsSelected = false;
|
|
|
|
|
|
|
|
|
|
// Clear recursively
|
|
|
|
|
if( CurrentEvent->Children.Num() )
|
|
|
|
|
{
|
|
|
|
|
ClearEventsSelection( CurrentEvent->Children );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SEventsTree::OnEventSelectionChanged( TSharedPtr< FVisualizerEvent > Selection, ESelectInfo::Type /*SelectInfo*/ )
|
|
|
|
|
{
|
|
|
|
|
if( Selection.IsValid() )
|
|
|
|
|
{
|
|
|
|
|
ClearEventsSelection( SelectedEventsView );
|
|
|
|
|
|
|
|
|
|
Selection->IsSelected = true;
|
|
|
|
|
|
|
|
|
|
// Mirror the selection in the source events list
|
|
|
|
|
TSharedPtr< FVisualizerEvent > MappedSelection = ViewToEventsMap.FindChecked( Selection );
|
|
|
|
|
MappedSelection->IsSelected = true;
|
|
|
|
|
|
|
|
|
|
if( !bSuppressSelectionChangedEvent )
|
|
|
|
|
{
|
|
|
|
|
OnEventSelectionChangedDelegate.ExecuteIfBound( MappedSelection );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if( !bSuppressSelectionChangedEvent )
|
|
|
|
|
{
|
|
|
|
|
OnEventSelectionChangedDelegate.ExecuteIfBound( Selection );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TSharedRef< ITableRow > SEventsTree::OnGenerateWidgetForEventsList( TSharedPtr< FVisualizerEvent > InItem, const TSharedRef<STableViewBase>& OwnerTable )
|
|
|
|
|
{
|
|
|
|
|
return SNew( SEventItem, OwnerTable )
|
|
|
|
|
.EventName( InItem->EventName )
|
|
|
|
|
.EventDuration( this, &SEventsTree::GetEventDuration, InItem->DurationMs );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
double SEventsTree::GetEventDuration( double InDurationMs ) const
|
|
|
|
|
{
|
|
|
|
|
switch( DurationUnits )
|
|
|
|
|
{
|
|
|
|
|
case EVisualizerTimeUnits::Microseconds:
|
|
|
|
|
return InDurationMs * 1000.0;
|
|
|
|
|
case EVisualizerTimeUnits::Milliseconds:
|
|
|
|
|
return InDurationMs;
|
|
|
|
|
case EVisualizerTimeUnits::Seconds:
|
|
|
|
|
return InDurationMs * 0.001;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return InDurationMs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SEventsTree::SetDurationUnits( EVisualizerTimeUnits::Type InUnits )
|
|
|
|
|
{
|
|
|
|
|
DurationUnits = InUnits;
|
|
|
|
|
EventsListView->RequestTreeRefresh();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SEventsTree::SetViewMode( EVisualizerViewMode::Type InMode )
|
|
|
|
|
{
|
|
|
|
|
ViewMode = InMode;
|
|
|
|
|
|
|
|
|
|
CreateSelectedEventsView();
|
|
|
|
|
SortEventsList();
|
|
|
|
|
|
|
|
|
|
EventsListView->RequestTreeRefresh();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SEventsTree::OnGetChildrenForEventsList( TSharedPtr<FVisualizerEvent> InItem, TArray<TSharedPtr<FVisualizerEvent> >& OutChildren )
|
|
|
|
|
{
|
|
|
|
|
OutChildren = InItem->Children;
|
|
|
|
|
}
|
|
|
|
|
|
Asset View now supports secondary sorting
#UDN: Feature suggestion; Content browser can sort by a second column
#branch UE4
#change
Added two new images for secondary sort ascending and descending.
(Updated styles where needed).
Added new enum EColumnSortPriority: currently only lists Primary and Secondary, but can easily accommodate more*.
(FOnSortModeChanged was modified to also have the priority as a param, fixedup existing usage).
SHeaderRow now also has a SortPriority attribute to specify the SortMode order
(Defaults to Primary if unused).
Modified TUniquePtr so that assigning one from another worked correctly.
(Reviewed by Steve Robb).
SAssetView is the only table that has been modified, so far, to take advantage of the secondary sort. SetMajorityAssetType has been updated to correctly filter out all those sorts which are no longer relevant and bump the priority of the remaining sorts to fill in the ægapsÆ made by non-longer-relevant ones.
FAssetViewSortManager has been overhauled to take SortPriority into consideration when sortingÃ
Firstly, duplicate comparison structs were removed in favour of single structs which have æascendingÆ as a paramà any remaining duplicate code was removed in favour of an inherited usage. The base struct has an array of æfurther methodsÆ to employ in the result of a tie. Should a tie occur the ænextÆ comparison is done, and so on until itÆs not a tie or we run out of comparisons to perform.*
The manager defaults to having no secondary sort, so it relies on the interface thatÆs using it to provide it.
Whenever a column is assign the code makes sure that itÆs not already assigned to another column and corrects the order to take this into account.
Fixed a bug in FCompareFAssetItemByTagNumericalAscending comparing A with A (instead of B).
Various optimizations to the sort to descrease times
*The only places æSecondaryÆ is referred to in code is in GetSortingBrush (so it can display the correct icon for the sort), and OnTitleClicked (so it can set to correct sort based on input). The sorting code itself has no concept as to a secondary sort, it can support any number of sorting levels so if in future a tertiary (or more) is added, it is only these two function which should need updating as the sort will automatically accommodate it.
reviewed by Thomas.Sarkanen, Bob.Tellez
[CL 2119201 by Andrew Brown in Main branch]
2014-06-27 04:30:08 -04:00
|
|
|
void SEventsTree::OnColumnSortModeChanged( const EColumnSortPriority::Type SortPriority, const FName& ColumnId, const EColumnSortMode::Type InSortMode )
|
2014-03-14 14:13:41 -04:00
|
|
|
{
|
|
|
|
|
SortByColumn = ColumnId;
|
|
|
|
|
SortMode = InSortMode;
|
|
|
|
|
|
|
|
|
|
SortEventsList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SEventsTree::SortEventsList( TArray< TSharedPtr< FVisualizerEvent > >& Events )
|
|
|
|
|
{
|
|
|
|
|
// Sort taking the current settings into account
|
|
|
|
|
if( SortByColumn == NAME_NameColumn )
|
|
|
|
|
{
|
|
|
|
|
if( SortMode == EColumnSortMode::Ascending )
|
|
|
|
|
{
|
|
|
|
|
struct FCompareEventsByName
|
|
|
|
|
{
|
|
|
|
|
FORCEINLINE bool operator()( const TSharedPtr<FVisualizerEvent> A, const TSharedPtr<FVisualizerEvent> B ) const { return A->EventName < B->EventName; }
|
|
|
|
|
};
|
|
|
|
|
Events.Sort( FCompareEventsByName() );
|
|
|
|
|
}
|
|
|
|
|
else if( SortMode == EColumnSortMode::Descending )
|
|
|
|
|
{
|
|
|
|
|
struct FCompareEventsByNameDescending
|
|
|
|
|
{
|
|
|
|
|
FORCEINLINE bool operator()( const TSharedPtr<FVisualizerEvent> A, const TSharedPtr<FVisualizerEvent> B ) const { return B->EventName < A->EventName; }
|
|
|
|
|
};
|
|
|
|
|
Events.Sort( FCompareEventsByNameDescending() );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if( SortByColumn == NAME_DurationColumn )
|
|
|
|
|
{
|
|
|
|
|
if( SortMode == EColumnSortMode::Ascending )
|
|
|
|
|
{
|
|
|
|
|
struct FCompareEventsByDuration
|
|
|
|
|
{
|
|
|
|
|
FORCEINLINE bool operator()( const TSharedPtr<FVisualizerEvent> A, const TSharedPtr<FVisualizerEvent> B ) const { return A->DurationMs < B->DurationMs; }
|
|
|
|
|
};
|
|
|
|
|
Events.Sort( FCompareEventsByDuration() );
|
|
|
|
|
}
|
|
|
|
|
else if( SortMode == EColumnSortMode::Descending )
|
|
|
|
|
{
|
|
|
|
|
struct FCompareEventsByDurationDescending
|
|
|
|
|
{
|
|
|
|
|
FORCEINLINE bool operator()( const TSharedPtr<FVisualizerEvent> A, const TSharedPtr<FVisualizerEvent> B ) const { return B->DurationMs < A->DurationMs; }
|
|
|
|
|
};
|
|
|
|
|
Events.Sort( FCompareEventsByDurationDescending() );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Sort recursively
|
|
|
|
|
for( int32 Index = 0; Index < Events.Num(); Index++ )
|
|
|
|
|
{
|
|
|
|
|
if( Events[ Index ]->Children.Num() )
|
|
|
|
|
{
|
|
|
|
|
SortEventsList( Events[ Index ]->Children );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SEventsTree::SortEventsList( void )
|
|
|
|
|
{
|
|
|
|
|
SortEventsList( SelectedEventsView );
|
|
|
|
|
|
|
|
|
|
EventsListView->RequestTreeRefresh();
|
|
|
|
|
|
|
|
|
|
RestoreEventSelection( SelectedEventsView );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool SEventsTree::RestoreEventSelection( TArray< TSharedPtr< FVisualizerEvent > >& Events )
|
|
|
|
|
{
|
|
|
|
|
// Search for the selected event
|
|
|
|
|
for( int32 Index = 0; Index < Events.Num(); ++Index )
|
|
|
|
|
{
|
|
|
|
|
if( Events[ Index ]->IsSelected )
|
|
|
|
|
{
|
|
|
|
|
// Select it in the tree view widget
|
|
|
|
|
EventsListView->ClearSelection();
|
|
|
|
|
EventsListView->RequestTreeRefresh();
|
|
|
|
|
EventsListView->SetSelection( Events[ Index ] );
|
|
|
|
|
EventsListView->RequestScrollIntoView( Events[ Index ] );
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Search recursively
|
|
|
|
|
if( RestoreEventSelection( Events[ Index ]->Children ) )
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SEventsTree::HandleBarGraphSelectionChanged( TSharedPtr< FVisualizerEvent > Selection )
|
|
|
|
|
{
|
|
|
|
|
if ( Selection.IsValid() )
|
|
|
|
|
{
|
|
|
|
|
for( int32 EventIndex = 0; EventIndex < SelectedEvents.Num(); EventIndex++ )
|
|
|
|
|
{
|
|
|
|
|
TSharedPtr<FVisualizerEvent> Event = SelectedEvents[ EventIndex ];
|
|
|
|
|
if( Selection->EventName == Event->EventName )
|
|
|
|
|
{
|
|
|
|
|
HandleBarEventSelectionChanged( SelectedThread->Category, Event );
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SEventsTree::HandleBarGraphExpansionChanged( TSharedPtr< FVisualizerEvent > Selection )
|
|
|
|
|
{
|
|
|
|
|
if ( Selection.IsValid() )
|
|
|
|
|
{
|
|
|
|
|
// We don't want to trigger selection changed event when the selection change is actually coming from the bar graph
|
|
|
|
|
bSuppressSelectionChangedEvent = true;
|
|
|
|
|
|
|
|
|
|
SelectedThread = Selection;
|
|
|
|
|
SelectedEvents = Selection.Get()->Children;
|
|
|
|
|
CreateSelectedEventsView();
|
|
|
|
|
SortEventsList();
|
|
|
|
|
|
|
|
|
|
bSuppressSelectionChangedEvent = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SEventsTree::HandleBarEventSelectionChanged( int32 Thread, TSharedPtr<FVisualizerEvent> Selection )
|
|
|
|
|
{
|
|
|
|
|
const TSharedPtr< FVisualizerEvent >* MappedViewSelection = ViewToEventsMap.FindKey( Selection );
|
|
|
|
|
if( MappedViewSelection )
|
|
|
|
|
{
|
|
|
|
|
TSharedPtr< FVisualizerEvent > ViewSelection = *MappedViewSelection;
|
|
|
|
|
|
|
|
|
|
// Clear the current selection
|
|
|
|
|
EventsListView->ClearSelection();
|
|
|
|
|
EventsListView->RequestTreeRefresh();
|
|
|
|
|
|
|
|
|
|
// Expand all parents so that it's visible
|
|
|
|
|
for( TSharedPtr< FVisualizerEvent > ParentEvent = ViewSelection->ParentEvent; ParentEvent.IsValid(); ParentEvent = ParentEvent->ParentEvent )
|
|
|
|
|
{
|
|
|
|
|
EventsListView->SetItemExpansion( ParentEvent, true );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
EventsListView->SetSelection( ViewSelection );
|
|
|
|
|
|
|
|
|
|
EventsListView->RequestScrollIntoView( ViewSelection );
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
EventsListView->ClearSelection();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FString SEventsTree::GetTabTitle() const
|
|
|
|
|
{
|
|
|
|
|
if ( SelectedThread.IsValid() )
|
|
|
|
|
{
|
|
|
|
|
return SelectedThread->EventName;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return NSLOCTEXT("TaskGraph", "EventsVisualizerName", "Empty Events List").ToString();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int32 SEventsTree::CountEvents( TArray< TSharedPtr< FVisualizerEvent > >& Events )
|
|
|
|
|
{
|
|
|
|
|
int32 Count = Events.Num();
|
|
|
|
|
|
|
|
|
|
for( int32 Index = 0; Index < Events.Num(); Index++ )
|
|
|
|
|
{
|
|
|
|
|
Count += CountEvents( Events[ Index ]->Children );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Count;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SEventsTree::CreateSelectedEventsView()
|
|
|
|
|
{
|
|
|
|
|
const int32 EventsCount = CountEvents( SelectedEvents );
|
|
|
|
|
ViewToEventsMap.Empty( EventsCount );
|
|
|
|
|
SelectedEventsView.Empty( SelectedEvents.Num() );
|
|
|
|
|
|
|
|
|
|
// Create the selected events copy based on the current view mode
|
|
|
|
|
if( ViewMode == EVisualizerViewMode::Hierarchical )
|
|
|
|
|
{
|
|
|
|
|
SelectedEventsView.Empty( SelectedEvents.Num() );
|
|
|
|
|
|
|
|
|
|
for( int32 Index = 0; Index < SelectedEvents.Num(); Index++ )
|
|
|
|
|
{
|
|
|
|
|
if( FilterEvent( SelectedEvents[ Index ] ) )
|
|
|
|
|
{
|
|
|
|
|
TSharedPtr< FVisualizerEvent > EventCopy( CreateSelectedEventsViewRecursively( SelectedEvents[ Index ] ) );
|
|
|
|
|
if( EventCopy.IsValid() )
|
|
|
|
|
{
|
|
|
|
|
SelectedEventsView.Add( EventCopy );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if( ViewMode == EVisualizerViewMode::Flat )
|
|
|
|
|
{
|
|
|
|
|
SelectedEventsView.Empty( EventsCount );
|
|
|
|
|
|
|
|
|
|
for( int32 Index = 0; Index < SelectedEvents.Num(); Index++ )
|
|
|
|
|
{
|
|
|
|
|
CreateSelectedEventsViewRecursivelyAndFlatten( SelectedEvents[ Index ] );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if( ViewMode == EVisualizerViewMode::Coalesced )
|
|
|
|
|
{
|
|
|
|
|
SelectedEventsView.Empty( SelectedEvents.Num() );
|
|
|
|
|
CreateSelectedEventsViewRecursivelyCoalesced( SelectedEvents, SelectedEventsView, TSharedPtr< FVisualizerEvent >() );
|
|
|
|
|
}
|
|
|
|
|
else if( ViewMode == EVisualizerViewMode::FlatCoalesced )
|
|
|
|
|
{
|
|
|
|
|
SelectedEventsView.Empty( EventsCount );
|
|
|
|
|
CreateSelectedEventsViewRecursivelyFlatCoalesced( SelectedEvents );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TSharedPtr< FVisualizerEvent > SEventsTree::CreateSelectedEventsViewRecursively( TSharedPtr< FVisualizerEvent > SourceEvent )
|
|
|
|
|
{
|
|
|
|
|
TSharedPtr< FVisualizerEvent > EventCopy( new FVisualizerEvent( *SourceEvent ) );
|
|
|
|
|
|
|
|
|
|
EventCopy->Children.Empty( SourceEvent->Children.Num() );
|
|
|
|
|
for( int32 ChildIndex = 0; ChildIndex < SourceEvent->Children.Num(); ChildIndex++ )
|
|
|
|
|
{
|
|
|
|
|
if( FilterEvent( SourceEvent->Children[ ChildIndex ] ) )
|
|
|
|
|
{
|
|
|
|
|
TSharedPtr< FVisualizerEvent > ChildCopy = CreateSelectedEventsViewRecursively( SourceEvent->Children[ ChildIndex ] );
|
|
|
|
|
if( ChildCopy.IsValid() )
|
|
|
|
|
{
|
|
|
|
|
ChildCopy->ParentEvent = EventCopy;
|
|
|
|
|
EventCopy->Children.Add( ChildCopy );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add this event because it's a leaf or has valid children
|
|
|
|
|
if( EventCopy->Children.Num() > 0 || SourceEvent->Children.Num() == 0 )
|
|
|
|
|
{
|
|
|
|
|
ViewToEventsMap.Add( EventCopy, SourceEvent );
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
EventCopy.Reset();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return EventCopy;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SEventsTree::CreateSelectedEventsViewRecursivelyAndFlatten( TSharedPtr< FVisualizerEvent > SourceEvent )
|
|
|
|
|
{
|
|
|
|
|
// Collect only children and store them directly into SelectedEventsView
|
|
|
|
|
if ( FilterEvent( SourceEvent ) && SourceEvent->Children.Num() == 0 )
|
|
|
|
|
{
|
|
|
|
|
TSharedPtr< FVisualizerEvent > EventCopy( new FVisualizerEvent( *SourceEvent ) );
|
|
|
|
|
EventCopy->ParentEvent.Reset();
|
|
|
|
|
ViewToEventsMap.Add( EventCopy, SourceEvent );
|
|
|
|
|
SelectedEventsView.Add( EventCopy );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for( int32 ChildIndex = 0; ChildIndex < SourceEvent->Children.Num(); ChildIndex++ )
|
|
|
|
|
{
|
|
|
|
|
CreateSelectedEventsViewRecursivelyAndFlatten( SourceEvent->Children[ ChildIndex ] );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SEventsTree::CreateSelectedEventsViewRecursivelyCoalesced( TArray< TSharedPtr< FVisualizerEvent > >& SourceEvents, TArray< TSharedPtr< FVisualizerEvent > >& CopiedEvents, TSharedPtr< FVisualizerEvent > InParent )
|
|
|
|
|
{
|
|
|
|
|
for( int32 SourceIndex = 0; SourceIndex < SourceEvents.Num(); SourceIndex++ )
|
|
|
|
|
{
|
|
|
|
|
TSharedPtr< FVisualizerEvent > SourceEvent = SourceEvents[ SourceIndex ];
|
|
|
|
|
if( FilterEvent( SourceEvent ) )
|
|
|
|
|
{
|
|
|
|
|
if( SourceEvent->Children.Num() == 0 )
|
|
|
|
|
{
|
|
|
|
|
// Check if a child with the same name has already been added to the copied event
|
|
|
|
|
bool bEventExists = false;
|
|
|
|
|
for( int32 CopiedEventIndex = 0; CopiedEventIndex < CopiedEvents.Num(); CopiedEventIndex++ )
|
|
|
|
|
{
|
|
|
|
|
if( CopiedEvents[ CopiedEventIndex ]->EventName == SourceEvent->EventName )
|
|
|
|
|
{
|
|
|
|
|
bEventExists = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if( !bEventExists )
|
|
|
|
|
{
|
|
|
|
|
TSharedPtr< FVisualizerEvent > EventCopy( new FVisualizerEvent( *SourceEvent ) );
|
|
|
|
|
EventCopy->ParentEvent = InParent;
|
|
|
|
|
|
|
|
|
|
ViewToEventsMap.Add( EventCopy, SourceEvent );
|
|
|
|
|
CopiedEvents.Add( EventCopy );
|
|
|
|
|
|
|
|
|
|
// Find other children with the same name and add their time to the copied one
|
|
|
|
|
for( int32 OtherIndex = SourceIndex + 1; OtherIndex < SourceEvents.Num(); OtherIndex++ )
|
|
|
|
|
{
|
|
|
|
|
TSharedPtr< FVisualizerEvent > OtherEvent = SourceEvents[ OtherIndex ];
|
|
|
|
|
if( OtherEvent->Children.Num() == 0 && OtherEvent->EventName == SourceEvent->EventName )
|
|
|
|
|
{
|
|
|
|
|
EventCopy->DurationMs += OtherEvent->DurationMs;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
TSharedPtr< FVisualizerEvent > EventCopy( new FVisualizerEvent( *SourceEvent ) );
|
|
|
|
|
EventCopy->Children.Empty( SourceEvent->Children.Num() );
|
|
|
|
|
EventCopy->ParentEvent = InParent;
|
|
|
|
|
|
|
|
|
|
CreateSelectedEventsViewRecursivelyCoalesced( SourceEvent->Children, EventCopy->Children, EventCopy );
|
|
|
|
|
|
|
|
|
|
// Only add this event if its children haven't been filtered
|
|
|
|
|
if( EventCopy->Children.Num() )
|
|
|
|
|
{
|
|
|
|
|
ViewToEventsMap.Add( EventCopy, SourceEvent );
|
|
|
|
|
CopiedEvents.Add( EventCopy );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SEventsTree::CreateSelectedEventsViewRecursivelyFlatCoalesced( TArray< TSharedPtr< FVisualizerEvent > >& SourceEvents )
|
|
|
|
|
{
|
|
|
|
|
for( int32 SourceIndex = 0; SourceIndex < SourceEvents.Num(); SourceIndex++ )
|
|
|
|
|
{
|
|
|
|
|
TSharedPtr< FVisualizerEvent > SourceEvent = SourceEvents[ SourceIndex ];
|
|
|
|
|
if( FilterEvent( SourceEvent ) )
|
|
|
|
|
{
|
|
|
|
|
if( SourceEvent->Children.Num() == 0 )
|
|
|
|
|
{
|
|
|
|
|
// Check if a child with the same name has already been added to the copied event
|
|
|
|
|
bool bEventExists = false;
|
|
|
|
|
for( int32 CopiedEventIndex = 0; CopiedEventIndex < SelectedEventsView.Num(); CopiedEventIndex++ )
|
|
|
|
|
{
|
|
|
|
|
if( SelectedEventsView[ CopiedEventIndex ]->EventName == SourceEvent->EventName )
|
|
|
|
|
{
|
|
|
|
|
bEventExists = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if( !bEventExists )
|
|
|
|
|
{
|
|
|
|
|
TSharedPtr< FVisualizerEvent > EventCopy( new FVisualizerEvent( *SourceEvent ) );
|
|
|
|
|
EventCopy->ParentEvent.Reset();
|
|
|
|
|
|
|
|
|
|
ViewToEventsMap.Add( EventCopy, SourceEvent );
|
|
|
|
|
SelectedEventsView.Add( EventCopy );
|
|
|
|
|
|
|
|
|
|
// Find other children with the same name and add their time to the copied one
|
|
|
|
|
for( int32 OtherIndex = SourceIndex + 1; OtherIndex < SourceEvents.Num(); OtherIndex++ )
|
|
|
|
|
{
|
|
|
|
|
TSharedPtr< FVisualizerEvent > OtherEvent = SourceEvents[ OtherIndex ];
|
|
|
|
|
if( OtherEvent->Children.Num() == 0 && OtherEvent->EventName == SourceEvent->EventName )
|
|
|
|
|
{
|
|
|
|
|
EventCopy->DurationMs += OtherEvent->DurationMs;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
CreateSelectedEventsViewRecursivelyFlatCoalesced( SourceEvent->Children );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SEventsTree::OnFilterTextChanged( const FText& InFilterText )
|
|
|
|
|
{
|
|
|
|
|
FilterText = InFilterText.ToString();
|
|
|
|
|
CreateSelectedEventsView();
|
|
|
|
|
EventsListView->RequestTreeRefresh();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SEventsTree::OnFilterTextCommitted( const FText& InFilterText, ETextCommit::Type /*CommitInfo*/ )
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#undef LOCTEXT_NAMESPACE
|