Files
2025-05-18 13:04:45 +08:00

1286 lines
44 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#if STATS
#include "SlateFwd.h"
#include "Layout/Visibility.h"
#include "Widgets/DeclarativeSyntaxSupport.h"
#include "Styling/SlateTypes.h"
#include "Framework/Commands/UIAction.h"
#include "ProfilerDataSource.h"
#include "ProfilerManager.h"
#include "Input/Reply.h"
#include "Widgets/SWidget.h"
#include "Widgets/SCompoundWidget.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/Views/SHeaderRow.h"
#include "Widgets/Views/STableViewBase.h"
#include "Widgets/Views/STableRow.h"
#include "Widgets/Views/STreeView.h"
class FEventGraphColumn;
class FMenuBuilder;
/** Type definition for shared pointers to instances of FEventGraphColumn. */
typedef TSharedPtr<class FEventGraphColumn> FEventGraphColumnPtr;
/** Type definition for shared references to instances of FEventGraphColumn. */
typedef TSharedRef<class FEventGraphColumn> FEventGraphColumnRef;
template <typename OptionType>
class SComboBox;
/** Enumerates event graph view modes. */
namespace EEventGraphViewModes
{
enum Type
{
/** Hierarchical list of the events. */
Hierarchical,
/** Flat list of the events based on the inclusive time, sorted by the inclusive time. */
FlatInclusive,
/** Flat list of the events based on the inclusive time coalesced by the event name, sorted by the inclusive time. */
FlatInclusiveCoalesced,
/** Flat list of the events based on the exclusive time, sorted by the exclusive time. */
FlatExclusive,
/** Flat list of the events based on the exclusive time coalesced by the event name, sorted by the exclusive time. */
FlatExclusiveCoalesced,
/** For the specified class shows an aggregated hierarchy @TBD. */
ClassAggregate,
/** Invalid enum type, may be used as a number of enumerations. */
InvalidOrMax,
};
/**
* @param EventGraphViewMode - The value to get the string for.
*
* @return string representation of the specified EEventGraphViewModes value.
*/
FText ToName( const EEventGraphViewModes::Type EventGraphViewMode );
/**
* @param EventGraphViewMode - The value to get the string for.
*
* @return string representation with more detailed explanation of the specified EEventGraphViewModes value.
*/
FText ToDescription( const EEventGraphViewModes::Type EventGraphViewMode );
FName ToBrushName( const Type EventGraphViewMode );
}
/** Holds information about a column in the event graph widget. */
class FEventGraphColumn
{
friend struct FEventGraphColumns;
protected:
/** Default constructor, only used in FEventGraphColumns */
FEventGraphColumn()
{}
/** Initialization constructor, only used in FEventGraphColumns. */
FEventGraphColumn
(
const EEventPropertyIndex InIndex,
const FName InSearchID,
FText InShortName,
FText InDescription,
const bool bInCanBeHidden,
const bool bInIsVisible,
const bool bInCanBeSorted,
const bool bInCanBeFiltered,
const bool bInCanBeCulled,
const EHorizontalAlignment InHorizontalAlignment,
const float InFixedColumnWidth
)
: Index( InIndex )
, ID( FEventGraphSample::GetEventPropertyByIndex(InIndex).Name )
, SearchID( InSearchID )
, ShortName( MoveTemp(InShortName) )
, Description( MoveTemp(InDescription) )
, bCanBeHidden( bInCanBeHidden )
, bIsVisible( bInIsVisible )
, bCanBeSorted( bInCanBeSorted )
, bCanBeFiltered( bInCanBeFiltered )
, bCanBeCulled( bInCanBeCulled )
, HorizontalAlignment( InHorizontalAlignment )
, FixedColumnWidth( InFixedColumnWidth )
{}
public:
/** Index of the event's property, also means the index of the column @see EEventPropertyIndex. */
EEventPropertyIndex Index;
/** Name of the column, name of the property. */
FName ID;
/** Name of the column used by the searching system. */
FName SearchID;
/** Short name of the column, displayed in the event graph header. */
FText ShortName;
/** Long name of the column, displayed in the column tooltip. */
FText Description;
/** Whether this column can be hidden. */
bool bCanBeHidden;
/** Is this column visible?. */
bool bIsVisible;
/** Whether this column cab be used for sorting. */
bool bCanBeSorted;
/** Where this column can be used to filtering displayed results. */
bool bCanBeFiltered;
/** Where this column can be used to culling displayed results. */
bool bCanBeCulled;
/** Horizontal alignment of the content in this column. */
EHorizontalAlignment HorizontalAlignment;
/** If greater than 0.0f, this column has fixed width and cannot be resized. */
float FixedColumnWidth;
};
/*-----------------------------------------------------------------------------
Declarations
-----------------------------------------------------------------------------*/
/** Interface for the event graph. */
class IEventGraph
{
public:
virtual void ExpandCulledEvents( FEventGraphSamplePtr EventPtr ) = 0;
};
/** A custom event graph widget used to visualize profiling data. */
class SEventGraph : public SCompoundWidget, public IEventGraph
{
struct ESelectedEventTypes
{
enum Type
{
AllEvents,
SelectedEvents,
SelectedThreadEvents,
};
};
public:
SLATE_BEGIN_ARGS( SEventGraph ){}
SLATE_END_ARGS()
/** Default constructor. */
SEventGraph();
/** Destructor. */
~SEventGraph();
/**
* Construct this widget.
*
* @param InArgs - the declaration data for this widget.
*/
void Construct( const FArguments& InArgs );
protected:
virtual void ExpandCulledEvents( FEventGraphSamplePtr EventPtr );
protected:
/*-----------------------------------------------------------------------------
Helper for constructing sub-widgets of this event graph widget.
-----------------------------------------------------------------------------*/
TSharedRef<SWidget> GetWidgetForEventGraphTypes();
TSharedRef<SWidget> GetWidgetForEventGraphViewModes();
TSharedRef<SWidget> GetWidgetBoxForOptions();
TSharedRef<SWidget> GetToggleButtonForEventGraphType( const EEventGraphTypes::Type EventGraphType, const FName BrushName );
TSharedRef<SWidget> GetToggleButtonForEventGraphViewMode( const EEventGraphViewModes::Type EventGraphViewMode );
/*-----------------------------------------------------------------------------
Settings
-----------------------------------------------------------------------------*/
EVisibility EventGraphViewMode_GetVisibility( const EEventGraphViewModes::Type ViewMode ) const;
/*-----------------------------------------------------------------------------
Events
-----------------------------------------------------------------------------*/
public:
/**
* The event to execute when the event graph state has been restored from the history.
*
* @param FrameStartIndex - <describe FrameStartIndex>
* @param FrameEndIndex - <describe FrameEndIndex>
*
*/
DECLARE_EVENT_TwoParams( SEventGraph, FEventGraphRestoredFromHistoryEvent, uint32 /*FrameStartIndex*/, uint32 /*FrameEndIndex*/ );
FEventGraphRestoredFromHistoryEvent& OnEventGraphRestoredFromHistory()
{
return EventGraphRestoredFromHistoryEvent;
}
protected:
void ProfilerManager_OnViewModeChanged( EProfilerViewMode NewViewMode );
protected:
/** The event to execute when the event graph has been restored from the history. */
FEventGraphRestoredFromHistoryEvent EventGraphRestoredFromHistoryEvent;
/** Called by STreeView to generate a table row for the specified item. */
TSharedRef< ITableRow > EventGraph_OnGenerateRow( FEventGraphSamplePtr EventPtr, const TSharedRef< STableViewBase >& OwnerTable );
/**
* Called by STreeView to retrieves the children for the specified parent item.
* @param InParent - The parent node to retrieve the children from.
* @param out_Children - List of children for the parent node.
*/
void EventGraph_OnGetChildren( FEventGraphSamplePtr InParent, TArray< FEventGraphSamplePtr >& out_Children );
void EventGraph_OnSelectionChanged( FEventGraphSamplePtr SelectedItem, ESelectInfo::Type SelectInfo );
void EventGraph_OnExpansionChanged( FEventGraphSamplePtr SelectedItem, bool bIsExpanded );
void TreeView_SetItemsExpansion_Recurrent( TArray<FEventGraphSamplePtr>& InEventGraphSamples, const bool bShouldExpand );
void SetHierarchicalSelectedEvents( const TArray<FEventGraphSamplePtr>& HierarchicalSelectedEvents );
void GetHierarchicalSelectedEvents( TArray< FEventGraphSamplePtr >& out_HierarchicalSelectedEvents, const TArray<FEventGraphSamplePtr>* SelectedEvents = NULL ) const;
void SetHierarchicalExpandedEvents( const TSet<FEventGraphSamplePtr>& HierarchicalExpandedEvents );
void GetHierarchicalExpandedEvents( TSet<FEventGraphSamplePtr>& out_HierarchicalExpandedEvents ) const;
void ShowEventsInViewMode( const TArray<FEventGraphSamplePtr>& EventsToSynchronize, const EEventGraphViewModes::Type NewViewMode );
void ScrollToTheSlowestSelectedEvent( EEventPropertyIndex ColumnIndex );
void CreateEvents();
void SortEvents();
void TreeView_Refresh();
void SetTreeItemsForViewMode( const EEventGraphViewModes::Type NewViewMode, EEventGraphTypes::Type NewEventGraphType );
void EventGraphViewMode_OnCheckStateChanged( ECheckBoxState NewRadioState, const EEventGraphViewModes::Type InViewMode );
ECheckBoxState EventGraphViewMode_IsChecked( const EEventGraphViewModes::Type InViewMode ) const;
void EventGraphType_OnCheckStateChanged( ECheckBoxState NewRadioState, const EEventGraphTypes::Type NewEventGraphType );
ECheckBoxState EventGraphType_IsChecked( const EEventGraphTypes::Type InEventGraphType ) const;
bool EventGraphType_IsEnabled( const EEventGraphTypes::Type InEventGraphType ) const;
void FilteringSearchBox_OnTextChanged( const FText& InFilterText );
void FilteringSearchBox_OnTextCommitted( const FText& NewText, ETextCommit::Type CommitType );
bool FilteringSearchBox_IsEnabled() const;
void OnAggressiveFilteringToggled(ECheckBoxState InState);
TSharedPtr<SWidget> EventGraph_GetMenuContent() const;
void EventGraph_BuildSortByMenu( FMenuBuilder& MenuBuilder );
void EventGraph_BuildViewColumnMenu( FMenuBuilder& MenuBuilder );
FReply ExpandHotPath_OnClicked();
void HighlightHotPath_OnCheckStateChanged( ECheckBoxState InState );
void InitializeAndShowHeaderColumns();
void TreeViewHeaderRow_OnSortModeChanged( const EColumnSortPriority::Type SortPriority, const FName& ColumnID, const EColumnSortMode::Type SortMode );
void SetSortModeForColumn( const FName& ColumnID, EColumnSortMode::Type SortMode );
EColumnSortMode::Type TreeViewHeaderRow_GetSortModeForColumn( const FName ColumnID ) const;
TSharedRef<SWidget> TreeViewHeaderRow_GenerateColumnMenu( const FEventGraphColumn& Column );
bool EventGraphTableRow_IsColumnVisible( const FName ColumnID ) const;
void EventGraphTableRow_SetHoveredTableCell( const FName ColumnID, const FEventGraphSamplePtr EventPtr );
FName EventGraphRow_GetHighlightedEventName() const;
EHorizontalAlignment EventGraphRow_GetColumnOutlineHAlignment( const FName ColumnID ) const;
void TreeViewHeaderRow_ShowColumn( const FName ColumnID );
void TreeViewHeaderRow_CreateColumnArgs( const uint32 ColumnIndex );
/** Binds our UI commands to delegates. */
void BindCommands();
/*-----------------------------------------------------------------------------
SelectAllFrames
-----------------------------------------------------------------------------*/
public:
/** Maps UI command info SelectAllFrames with the specified UI command list. */
void Map_SelectAllFrames_Global();
/** Add comment here */
const FUIAction SelectAllFrames_Custom() const
{
FUIAction UIAction;
UIAction.ExecuteAction = FExecuteAction::CreateSP( const_cast<SEventGraph*>(this), &SEventGraph::SelectAllFrames_Execute );
UIAction.CanExecuteAction = FCanExecuteAction::CreateSP( this, &SEventGraph::SelectAllFrames_CanExecute );
return UIAction;
}
protected:
/** Handles FExecuteAction for SelectAllFrames. */
void SelectAllFrames_Execute();
/** Handles FCanExecuteAction for SelectAllFrames. */
bool SelectAllFrames_CanExecute() const;
// Dummy implementation, used to verify that all actions have been properly implemented.
void ContextMenu_ExecuteDummy( const FName ActionName );
bool ContextMenu_CanExecuteDummy( const FName ActionName ) const;
bool ContextMenu_IsCheckedDummy( const FName ActionName ) const;
// Action_ExpandHotPath
void ContextMenu_ExpandHotPath_Execute();
bool ContextMenu_ExpandHotPath_CanExecute() const;
// Action_CopySelectedToClipboard
void ContextMenu_CopySelectedToClipboard_Execute();
bool ContextMenu_CopySelectedToClipboard_CanExecute() const;
// Action_SelectStack
void ContextMenu_SelectStack_Execute();
bool ContextMenu_SelectStack_CanExecute() const;
// Action_SortByColumn
void ContextMenu_SortByColumn_Execute( const FName ColumnID );
bool ContextMenu_SortByColumn_CanExecute( const FName ColumnID ) const;
bool ContextMenu_SortByColumn_IsChecked( const FName ColumnID );
// Action_ToggleColumn
void ContextMenu_ToggleColumn_Execute( const FName ColumnID );
bool ContextMenu_ToggleColumn_CanExecute( const FName ColumnID ) const;
bool ContextMenu_ToggleColumn_IsChecked( const FName ColumnID );
// Action_SortMode
void ContextMenu_SortMode_Execute( const EColumnSortMode::Type InSortMode );
bool ContextMenu_SortMode_CanExecute( const EColumnSortMode::Type InSortMode ) const;
bool ContextMenu_SortMode_IsChecked( const EColumnSortMode::Type InSortMode );
// Action_ResetColumns
void ContextMenu_ResetColumns_Execute();
bool ContextMenu_ResetColumns_CanExecute() const;
// Header Action_SortMode
void HeaderMenu_SortMode_Execute( const FName ColumnID, const EColumnSortMode::Type InSortMode );
bool HeaderMenu_SortMode_CanExecute( const FName ColumnID, const EColumnSortMode::Type InSortMode ) const;
bool HeaderMenu_SortMode_IsChecked( const FName ColumnID, const EColumnSortMode::Type InSortMode );
// Header Action_HideColumn
void HeaderMenu_HideColumn_Execute( const FName ColumnID );
bool HeaderMenu_HideColumn_CanExecute( const FName ColumnID ) const;
/*-----------------------------------------------------------------------------
SetRoot
-----------------------------------------------------------------------------*/
public:
/** Add comment here */
const FUIAction SetRoot_Custom() const
{
FUIAction UIAction;
UIAction.ExecuteAction = FExecuteAction::CreateSP( const_cast<SEventGraph*>(this), &SEventGraph::SetRoot_Execute );
UIAction.CanExecuteAction = FCanExecuteAction::CreateSP( this, &SEventGraph::SetRoot_CanExecute );
return UIAction;
}
protected:
/** Handles FExecuteAction for SetRoot. */
void SetRoot_Execute();
/** Handles FCanExecuteAction for SetRoot. */
bool SetRoot_CanExecute() const;
/*-----------------------------------------------------------------------------
ClearHistory
-----------------------------------------------------------------------------*/
public:
/** Add comment here */
const FUIAction ClearHistory_Custom() const
{
FUIAction UIAction;
UIAction.ExecuteAction = FExecuteAction::CreateSP( const_cast<SEventGraph*>(this), &SEventGraph::ClearHistory_Execute );
UIAction.CanExecuteAction = FCanExecuteAction::CreateSP( this, &SEventGraph::ClearHistory_CanExecute );
return UIAction;
}
protected:
/** Handles FExecuteAction for ClearHistory. */
void ClearHistory_Execute();
/** Handles FCanExecuteAction for ClearHistory. */
bool ClearHistory_CanExecute() const;
/*-----------------------------------------------------------------------------
ShowSelectedEventsInViewMode
-----------------------------------------------------------------------------*/
public:
/** Add comment here */
const FUIAction ShowSelectedEventsInViewMode_Custom( EEventGraphViewModes::Type NewViewMode ) const
{
FUIAction UIAction;
UIAction.ExecuteAction = FExecuteAction::CreateSP( const_cast<SEventGraph*>(this), &SEventGraph::ShowSelectedEventsInViewMode_Execute, NewViewMode );
UIAction.CanExecuteAction = FCanExecuteAction::CreateSP( this, &SEventGraph::ShowSelectedEventsInViewMode_CanExecute, NewViewMode );
UIAction.GetActionCheckState = FGetActionCheckState::CreateSP( this, &SEventGraph::ShowSelectedEventsInViewMode_GetCheckState, NewViewMode );
return UIAction;
}
protected:
/** Handles FExecuteAction for ShowSelectedEventsInViewMode. */
void ShowSelectedEventsInViewMode_Execute(EEventGraphViewModes::Type NewViewMode);
/** Handles FCanExecuteAction for ShowSelectedEventsInViewMode. */
bool ShowSelectedEventsInViewMode_CanExecute(EEventGraphViewModes::Type NewViewMode) const;
/** Handles FGetActionCheckState for ShowSelectedEventsInViewMode. */
ECheckBoxState ShowSelectedEventsInViewMode_GetCheckState(EEventGraphViewModes::Type NewViewMode) const;
/*-----------------------------------------------------------------------------
FilterOutByProperty
-----------------------------------------------------------------------------*/
public:
/** Add comment here */
const FUIAction FilterOutByProperty_Custom( const FEventGraphSamplePtr EventPtr, const FName PropertyName, const bool bReset ) const
{
FUIAction UIAction;
UIAction.ExecuteAction = FExecuteAction::CreateSP( const_cast<SEventGraph*>(this), &SEventGraph::FilterOutByProperty_Execute, EventPtr, PropertyName, bReset );
UIAction.CanExecuteAction = FCanExecuteAction::CreateSP( this, &SEventGraph::FilterOutByProperty_CanExecute, EventPtr, PropertyName, bReset );
return UIAction;
}
protected:
/** Handles FExecuteAction for FilterOutByProperty. */
void FilterOutByProperty_Execute( const FEventGraphSamplePtr EventPtr, const FName PropertyName, const bool bReset );
/** Handles FCanExecuteAction for FilterOutByProperty. */
bool FilterOutByProperty_CanExecute( const FEventGraphSamplePtr EventPtr, const FName PropertyName, const bool bReset ) const;
/*-----------------------------------------------------------------------------
CullByProperty
-----------------------------------------------------------------------------*/
public:
/** Add comment here */
const FUIAction CullByProperty_Custom( const FEventGraphSamplePtr EventPtr, const FName PropertyName, const bool bReset ) const
{
FUIAction UIAction;
UIAction.ExecuteAction = FExecuteAction::CreateSP( const_cast<SEventGraph*>(this), &SEventGraph::CullByProperty_Execute, EventPtr, PropertyName, bReset );
UIAction.CanExecuteAction = FCanExecuteAction::CreateSP( this, &SEventGraph::CullByProperty_CanExecute, EventPtr, PropertyName, bReset );
return UIAction;
}
protected:
/** Handles FExecuteAction for CullByProperty. */
void CullByProperty_Execute( const FEventGraphSamplePtr EventPtr, const FName PropertyName, const bool bReset );
/** Handles FCanExecuteAction for CullByProperty. */
bool CullByProperty_CanExecute( const FEventGraphSamplePtr EventPtr, const FName PropertyName, const bool bReset ) const;
/*-----------------------------------------------------------------------------
HistoryList_GoTo
-----------------------------------------------------------------------------*/
public:
/** Add comment here */
const FUIAction HistoryList_GoTo_Custom( int32 StateIndex ) const
{
FUIAction UIAction;
UIAction.ExecuteAction = FExecuteAction::CreateSP( const_cast<SEventGraph*>(this), &SEventGraph::HistoryList_GoTo_Execute, StateIndex );
UIAction.CanExecuteAction = FCanExecuteAction();
UIAction.GetActionCheckState = FGetActionCheckState::CreateSP( this, &SEventGraph::HistoryList_GoTo_GetCheckState, StateIndex );
return UIAction;
}
protected:
void HistoryList_GoTo_ExecuteRadioState( ECheckBoxState NewRadioState, int32 StateIndex )
{
if( NewRadioState == ECheckBoxState::Checked )
{
HistoryList_GoTo_Execute( StateIndex );
}
}
/** Handles FExecuteAction for HistoryList_GoTo. */
void HistoryList_GoTo_Execute( int32 StateIndex );
/** Handles FGetActionCheckState for HistoryList_GoTo. */
ECheckBoxState HistoryList_GoTo_GetCheckState( int32 StateIndex ) const
{
return StateIndex == CurrentStateIndex ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
}
/*-----------------------------------------------------------------------------
SetExpansionForEvents
-----------------------------------------------------------------------------*/
public:
/** Add comment here */
const FUIAction SetExpansionForEvents_Custom( const ESelectedEventTypes::Type SelectedEventType, bool bShouldExpand ) const
{
FUIAction UIAction;
UIAction.ExecuteAction = FExecuteAction::CreateSP( const_cast<SEventGraph*>(this), &SEventGraph::SetExpansionForEvents_Execute, SelectedEventType, bShouldExpand );
UIAction.CanExecuteAction = FCanExecuteAction::CreateSP( this, &SEventGraph::SetExpansionForEvents_CanExecute, SelectedEventType, bShouldExpand );
return UIAction;
}
protected:
void GetEventsForChangingExpansion( TArray<FEventGraphSamplePtr>& out_Events, const ESelectedEventTypes::Type SelectedEventType );
/** Handles FExecuteAction for SetExpansionForEvents. */
void SetExpansionForEvents_Execute( const ESelectedEventTypes::Type SelectedEventType, bool bShouldExpand );
/** Handles FCanExecuteAction for SetExpansionForEvents. */
bool SetExpansionForEvents_CanExecute( const ESelectedEventTypes::Type SelectedEventType, bool bShouldExpand ) const;
protected:
/** All events coalesced by the event name, stored as FName -> FEventGraphSamplePtr. */
TMultiMap< FName, FEventGraphSamplePtr > HierarchicalToFlatCoalesced;
/** An array of samples to be displayed in this widget. */
TArray<FEventGraphSamplePtr> Events_Flat;
TArray<FEventGraphSamplePtr> Events_FlatCoalesced;
/** How we sort the event graph?. */
EColumnSortMode::Type ColumnSortMode;
/** Name of the column currently being sorted, NAME_None if sorting is disabled. */
FName ColumnBeingSorted;
typedef TSharedPtr< STreeView<FEventGraphSamplePtr> > FTreeViewOfEventGraphSamples;
/** Holds the tree view widget which display event graph samples. */
FTreeViewOfEventGraphSamples TreeView_Base;
/** External scrollbar used to synchronize tree view position. */
TSharedPtr<SScrollBar> ExternalScrollbar;
TSharedPtr<SBox> FunctionDetailsBox;
/** Holds the tree view header row widget which display all columns in the tree view. */
TSharedPtr< SHeaderRow > TreeViewHeaderRow;
/** The search box widget used to filter items displayed in this widget. */
TSharedPtr< SSearchBox > FilteringSearchBox;
/** Column metadata used to initialize column arguments, stored as PropertyName -> FEventGraphColumn. */
TMap< FName, FEventGraphColumn > TreeViewHeaderColumns;
/** Column arguments used to initialize a new header column in the tree view, stored as column name to column arguments mapping. */
TMap< FName, SHeaderRow::FColumn::FArguments > TreeViewHeaderColumnArgs;
/** Name of the column currently being hovered by the mouse. */
FName HoveredColumnID;
/** A shared pointer to the event currently being hovered by the mouse. */
FEventGraphSamplePtr HoveredSamplePtr;
/*-----------------------------------------------------------------------------
History management
-----------------------------------------------------------------------------*/
protected:
struct EEventHistoryTypes
{
enum Type
{
NewEventGraph,
Rooted,
Culled,
Filtered,
};
};
class FEventGraphState
{
struct FCulledTag{};
struct FFilteredTag{};
public:
/** New event graph state constructor. */
FEventGraphState(FEventGraphDataRef InAverageEventGraph, FEventGraphDataRef InMaximumEventGraph)
: AverageEventGraph(InAverageEventGraph)
, MaximumEventGraph(InMaximumEventGraph)
, FakeRoot(FEventGraphSample::CreateNamedEvent(FEventGraphConsts::FakeRoot))
, CullPropertyName(NAME_None)
, CullEventPtr(nullptr)
, FilterPropertyName(NAME_None)
, FilterEventPtr(nullptr)
, CreationTime(FPlatformTime::Seconds())
, HistoryType(EEventHistoryTypes::NewEventGraph)
, ViewMode(EEventGraphViewModes::Hierarchical)
, EventGraphType(InAverageEventGraph->GetNumFrames() == 1 ? EEventGraphTypes::OneFrame : EEventGraphTypes::Average)
, bAggressiveFiltering(false)
{
CreateOneToOneMapping();
}
FEventGraphState* CreateCopyWithNewRoot(const TArray<FEventGraphSamplePtr>& UniqueLeafs)
{
return new FEventGraphState(*this, UniqueLeafs);
}
FEventGraphState* CreateCopyWithCulling(const FName InCullPropertyName, const FEventGraphSamplePtr InCullEventPtr)
{
return new FEventGraphState(*this, InCullPropertyName, InCullEventPtr, FCulledTag());
}
FEventGraphState* CreateCopyWithFiltering(const FName InFilterPropertyName, const FEventGraphSamplePtr InFilterEventPtr)
{
return new FEventGraphState(*this, InFilterPropertyName, InFilterEventPtr, FFilteredTag());
}
FEventGraphState* CreateCopyWithTextFiltering(const FString& InFilterText)
{
FEventGraphState* Result = new FEventGraphState(*this, NAME_None, nullptr, FFilteredTag());
InFilterText.ParseIntoArray(Result->TextBasedFilterStringTokens, TEXT(" "));
return Result;
}
void SetAggressiveFiltering(bool bInAggressiveFiltering)
{
bAggressiveFiltering = bInAggressiveFiltering;
}
protected:
/** Copy constructor for setting new root. */
FEventGraphState(const FEventGraphState& EventGraphState, const TArray<FEventGraphSamplePtr>& UniqueLeafs)
: AverageEventGraph(EventGraphState.AverageEventGraph)
, MaximumEventGraph(EventGraphState.MaximumEventGraph)
, AverageToMaximumMapping(EventGraphState.AverageToMaximumMapping)
, MaximumToAverageMapping(EventGraphState.MaximumToAverageMapping)
, ExpandedEvents(EventGraphState.ExpandedEvents)
, SelectedEvents(EventGraphState.SelectedEvents)
, FakeRoot(FEventGraphSample::CreateNamedEvent(FEventGraphConsts::FakeRoot))
, CullPropertyName(EventGraphState.CullPropertyName)
, CullEventPtr(EventGraphState.CullEventPtr)
, ExpandedCulledEvents(EventGraphState.ExpandedCulledEvents)
, FilterPropertyName(EventGraphState.FilterPropertyName)
, FilterEventPtr(EventGraphState.FilterEventPtr)
, CreationTime(FPlatformTime::Seconds())
, HistoryType(EEventHistoryTypes::Rooted)
, ViewMode(EventGraphState.ViewMode)
, EventGraphType(EventGraphState.EventGraphType)
, bAggressiveFiltering(EventGraphState.bAggressiveFiltering)
{
// Set new root.
SetNewRoot(UniqueLeafs);
}
/** Copy constructor for culling. */
FEventGraphState(const FEventGraphState& EventGraphState, const FName InCullPropertyName, const FEventGraphSamplePtr InCullEventPtr, FCulledTag)
: AverageEventGraph(EventGraphState.AverageEventGraph)
, MaximumEventGraph(EventGraphState.MaximumEventGraph)
, AverageToMaximumMapping(EventGraphState.AverageToMaximumMapping)
, MaximumToAverageMapping(EventGraphState.MaximumToAverageMapping)
, ExpandedEvents(EventGraphState.ExpandedEvents)
, SelectedEvents(EventGraphState.SelectedEvents)
, FakeRoot(FEventGraphSample::CreateNamedEvent(FEventGraphConsts::FakeRoot))
, CullPropertyName(InCullPropertyName)
, CullEventPtr(InCullEventPtr)
, ExpandedCulledEvents()
, FilterPropertyName(EventGraphState.FilterPropertyName)
, FilterEventPtr(EventGraphState.FilterEventPtr)
, CreationTime(FPlatformTime::Seconds())
, HistoryType(EEventHistoryTypes::Culled)
, ViewMode(EventGraphState.ViewMode)
, EventGraphType(EventGraphState.EventGraphType)
, bAggressiveFiltering(EventGraphState.bAggressiveFiltering)
{
// Copy fake root.
SetNewRoot(EventGraphState.FakeRoot->GetChildren());
}
/** Copy constructor for filtering. */
FEventGraphState(const FEventGraphState& EventGraphState, const FName InFilterPropertyName, const FEventGraphSamplePtr InFilterEventPtr, FFilteredTag)
: AverageEventGraph(EventGraphState.AverageEventGraph)
, MaximumEventGraph(EventGraphState.MaximumEventGraph)
, AverageToMaximumMapping(EventGraphState.AverageToMaximumMapping)
, MaximumToAverageMapping(EventGraphState.MaximumToAverageMapping)
, ExpandedEvents(EventGraphState.ExpandedEvents)
, SelectedEvents(EventGraphState.SelectedEvents)
, FakeRoot(FEventGraphSample::CreateNamedEvent(FEventGraphConsts::FakeRoot))
, CullPropertyName(EventGraphState.CullPropertyName)
, CullEventPtr(EventGraphState.CullEventPtr)
, ExpandedCulledEvents(EventGraphState.ExpandedCulledEvents)
, FilterPropertyName(InFilterPropertyName)
, FilterEventPtr(InFilterEventPtr)
, CreationTime(FPlatformTime::Seconds())
, HistoryType(EEventHistoryTypes::Filtered)
, ViewMode(EventGraphState.ViewMode)
, EventGraphType(EventGraphState.EventGraphType)
, bAggressiveFiltering(EventGraphState.bAggressiveFiltering)
{
// Copy fake root.
SetNewRoot(EventGraphState.FakeRoot->GetChildren());
}
public:
const FEventGraphDataRef AverageEventGraph;
const FEventGraphDataRef MaximumEventGraph;
TMap<FEventGraphSamplePtr, FEventGraphSamplePtr> AverageToMaximumMapping;
TMap<FEventGraphSamplePtr, FEventGraphSamplePtr> MaximumToAverageMapping;
/** Only for hierarchical events, states for coalesced events are generated on demand. */
TSet<FEventGraphSamplePtr> ExpandedEvents;
TArray<FEventGraphSamplePtr> SelectedEvents;
/** Fake root event used to limit the event graph to the specified events and its children. */
FEventGraphSamplePtr FakeRoot;
/** Name of the property used to cull the event graph. */
FName CullPropertyName;
/** Value of the property used to cull the event graph. */
FEventGraphSamplePtr CullEventPtr;
/** Events that were culled, but later the user decided to expand them. */
TArray<FEventGraphSamplePtr> ExpandedCulledEvents;
/** Name of the property used to filter out the event graph. */
FName FilterPropertyName;
/** Value of the property used to filter out the event graph. */
FEventGraphSamplePtr FilterEventPtr;
/** Text substrings to match for text-based filtering (AND - all must be present) */
TArray<FString> TextBasedFilterStringTokens;
const double CreationTime;
EEventHistoryTypes::Type HistoryType;
/** Event graph view mode. */
EEventGraphViewModes::Type ViewMode;
/** Event graph type. */
EEventGraphTypes::Type EventGraphType;
/** Whether aggressive filtering is currently on */
bool bAggressiveFiltering;
/** Event Filter by thread name */
FName ThreadFilter;
void CreateOneToOneMapping();
const bool IsCulled() const
{
return CullPropertyName != NAME_None;
}
const bool IsFiltered() const
{
return FilterPropertyName != NAME_None;
}
const bool IsRooted() const
{
return FakeRoot->GetChildren().Num() > 0;
}
/**
* @return the number of frames used to create this event graph data state.
*/
const uint32 GetNumFrames() const
{
return AverageEventGraph->GetNumFrames();
}
FText GetFullDescription() const;
FText GetRootedDesc() const;
FText GetCullingDesc() const;
FText GetFilteringDesc() const;
FText GetHistoryDesc() const;
const FEventGraphDataRef& GetEventGraph() const
{
return EventGraphType == EEventGraphTypes::Average ? AverageEventGraph : MaximumEventGraph;
}
FEventGraphSamplePtr GetRoot() const
{
return IsRooted() ? FakeRoot : GetEventGraph()->GetRoot();
}
FEventGraphSamplePtr GetRealRoot() const
{
return GetEventGraph()->GetRoot();
}
void SetNewRoot(const TArray<FEventGraphSamplePtr>& NewRootEvents)
{
for (int32 Nx = 0; Nx < NewRootEvents.Num(); ++Nx)
{
FakeRoot->AddChildPtr(NewRootEvents[Nx]);
}
}
void SetNewRootFiltered(const TArray<FEventGraphSamplePtr>& NewRootEvents)
{
for (int32 Nx = 0; Nx < NewRootEvents.Num(); ++Nx)
{
const FEventGraphSamplePtr &RootEvent = NewRootEvents[Nx];
bool bAddRoot = !IsChildFiltered(RootEvent);
if (bAddRoot)
{
FakeRoot->AddChildPtr(RootEvent);
}
}
}
bool IsChildFiltered(const FEventGraphSamplePtr &ChildEvent)
{
bool bFiltered = ChildEvent->PropertyValueAsBool(EEventPropertyIndex::bIsFiltered);
for (int32 Cx = 0; Cx < ChildEvent->GetChildren().Num(); Cx++)
{
bFiltered |= IsChildFiltered(ChildEvent->GetChildren()[Cx]);
}
return bFiltered;
}
void ApplyCulling()
{
if (IsCulled())
{
// Apply culling.
FEventArrayBooleanOp::ExecuteOperation
(
GetRoot(), EEventPropertyIndex::bIsCulled,
CullEventPtr, FEventGraphSample::GetEventPropertyByName(CullPropertyName).Index,
EEventCompareOps::Less
);
// Update not culled children.
GetRoot()->SetBooleanStateForAllChildren<EEventPropertyIndex::bNeedNotCulledChildrenUpdate>(true);
}
else
{
// Reset culling.
GetRoot()->SetBooleanStateForAllChildren<EEventPropertyIndex::bIsCulled>(false);
}
}
private:
static bool PassesTokenFilter(const TArray<FString>& FilterTokens, const FString& TestString)
{
for (const FString& Token : FilterTokens)
{
if (TestString.Contains(Token))
{
return true;
}
}
return false;
}
// Sets the filter and optionally culled properties, returning true if any child passed the filter
bool ApplyTextBasedFilterInternal(TArray< FEventGraphSamplePtr >& Nodes, bool bCullAsWell)
{
bool bAnyPasses = false;
for (FEventGraphSamplePtr& Node : Nodes)
{
const bool bChildPassesFilter = ApplyTextBasedFilterInternal(Node->GetChildren(), bCullAsWell);
const bool bThisPassesFilter = PassesTokenFilter(TextBasedFilterStringTokens, Node->_StatName.ToString());
bAnyPasses = bAnyPasses || bThisPassesFilter || bChildPassesFilter;
Node->PropertyValueAsBool(EEventPropertyIndex::bIsFiltered) = !bThisPassesFilter;
if (bCullAsWell)
{
const bool bChildSavesFromCull = (ViewMode == EEventGraphViewModes::Hierarchical) && bChildPassesFilter;
bool& bCullState = Node->PropertyValueAsBool(EEventPropertyIndex::bIsCulled);
bCullState = (bCullState || !bChildPassesFilter) && !bChildSavesFromCull;
}
}
return bAnyPasses;
}
/* Aggressive filtering - rebuild the stats graph based on the text filter,
* removing all items that don't pass the filter and combining identically
* named stats into single rows
*/
void RebuildForFilter(TArray< FEventGraphSamplePtr >& Nodes)
{
FakeRoot->GetChildren().Empty();
for (auto Node : Nodes)
{
bool NodePass = NodePassesFilter(Node);
bool Pass = RebuildChildrenWhoPassFilter(Node);
if (Pass || NodePass)
{
FakeRoot->GetChildren().Add(Node);
Node->SetParent(FakeRoot);
}
}
for (auto Node : FakeRoot->GetChildren())
{
CombineLikeNamed(Node->GetChildren());
Node->RecalcTimes();
}
}
/* Iterates through Nodes, reparents children of nodes with identical names
* and updates times to reflect the reparenting; used by aggressive filtering to consolidate as much
* of the data as possible in a single tree row and a single number
*/
void CombineLikeNamed(TArray< FEventGraphSamplePtr >& Nodes)
{
TArray<FEventGraphSamplePtr> KillNodes;
for (int32 IdxA = 0; IdxA < Nodes.Num(); IdxA++)
{
TArray<FEventGraphSamplePtr>& AChildren = Nodes[IdxA]->GetChildren();
FString ANodeName = Nodes[IdxA]->_StatName.ToString();
for (int32 IdxB = IdxA + 1; IdxB < Nodes.Num(); IdxB++)
{
TArray<FEventGraphSamplePtr>& BChildren = Nodes[IdxB]->GetChildren();
FString BNodeName = Nodes[IdxB]->_StatName.ToString();
if (IdxA != IdxB && (&AChildren != &BChildren) && ANodeName == BNodeName)
{
AChildren.Append(BChildren);
for (FEventGraphSamplePtr Child : AChildren)
{
Child->SetParent(Nodes[IdxA]);
}
Nodes[IdxA]->RecalcTimes();
BChildren.Empty();
KillNodes.Add(Nodes[IdxB]);
}
}
CombineLikeNamed(Nodes[IdxA]->GetChildren());
}
// build a new node array without the ones we want to get rid of
TArray<FEventGraphSamplePtr> NewNodes;
for (int32 Idx = 0; Idx < Nodes.Num(); Idx++)
{
bool bFound = false;
for (int32 KillIdx = 0; KillIdx < KillNodes.Num(); KillIdx++)
{
if (Nodes[Idx] == KillNodes[KillIdx])
{
bFound = true;
break;
}
}
if (bFound == false)
{
NewNodes.Add(Nodes[Idx]);
}
}
Nodes = NewNodes;
}
/* Removes all children of Node that don't pass any of the tokens in the text filter, recursively;
* if a node matches, all of its children will be considered to pass as well
*/
bool RebuildChildrenWhoPassFilter(FEventGraphSamplePtr& Node)
{
TArray<FEventGraphSamplePtr> PassingChildren;
bool bAnyChildPasses = false;
for (auto Child : Node->GetChildren())
{
bool& bFilteredState = Node->PropertyValueAsBool(EEventPropertyIndex::bIsFiltered);
bool& bCulledState = Node->PropertyValueAsBool(EEventPropertyIndex::bIsCulled);
bCulledState = false;
if (NodePassesFilter(Child))
{
bFilteredState = false;
PassingChildren.Add(Child);
bAnyChildPasses = true;
FilterAllChildren(Child);
}
else
{
bool bChildPasses = RebuildChildrenWhoPassFilter(Child);
if (bChildPasses)
{
PassingChildren.Append(Child->GetChildren());
Child->GetChildren().Empty();
}
bAnyChildPasses |= bChildPasses;
}
}
Node->GetChildren() = PassingChildren;
return bAnyChildPasses;
}
/* Check if Node matches any token out of TextBasedFilterStringTokens
*/
bool NodePassesFilter(FEventGraphSamplePtr& Node)
{
return PassesTokenFilter(TextBasedFilterStringTokens, Node->_StatName.ToString());
}
/* Set all children of Node to filtered status
*/
void FilterAllChildren(FEventGraphSamplePtr &Node)
{
for (auto Child : Node->GetChildren())
{
Child->PropertyValueAsBool(EEventPropertyIndex::bIsFiltered) = true;
FilterAllChildren(Child);
}
}
public:
void ApplyFiltering()
{
// aggressive filtering?
if (bAggressiveFiltering && TextBasedFilterStringTokens.Num() > 0)
{
// Rebuild the event graph in-place
RebuildForFilter(GetEventGraph()->GetRoot()->GetChildren());
}
else
{
FakeRoot->GetChildren().Empty();
if (TextBasedFilterStringTokens.Num() > 0)
{
// Apply text substring filtering (and optionally culling).
const bool bAlsoCull = true;
ApplyTextBasedFilterInternal(GetRoot()->GetChildren(), bAlsoCull);
}
else if (IsFiltered())
{
// Apply filtering.
FEventArrayBooleanOp::ExecuteOperation
(
GetRoot(), EEventPropertyIndex::bIsFiltered,
FilterEventPtr, FEventGraphSample::GetEventPropertyByName(FilterPropertyName).Index,
EEventCompareOps::Less
);
}
else if (!ThreadFilter.IsNone())
{
// Filter by event Thread name
struct FFilterByThreadName
{
FFilterByThreadName(const FName InThreadName) : ThreadName(InThreadName) {}
void operator()(FEventGraphSample* InEventPtr)
{
InEventPtr->_bIsFiltered = InEventPtr->GetThread() != nullptr && InEventPtr->_ThreadName != ThreadName;
InEventPtr->_bIsCulled = InEventPtr->GetThread() != nullptr && InEventPtr->_ThreadName != ThreadName;
}
FName ThreadName;
};
GetRoot()->ExecuteOperationForAllChildren(FFilterByThreadName(ThreadFilter));
}
else
{
// Reset filtering.
GetRoot()->SetBooleanStateForAllChildren<EEventPropertyIndex::bIsFiltered>(false);
}
}
}
/** Hacky method to update this saved state so it can be used with the new event graph type, mostly temporary. */
void UpdateToNewEventGraphType( const EEventGraphTypes::Type NewEventGraphType )
{
if( EventGraphType != NewEventGraphType )
{
const TMap<FEventGraphSamplePtr,FEventGraphSamplePtr>& OneToOneMapping = NewEventGraphType == EEventGraphTypes::Maximum ? AverageToMaximumMapping : MaximumToAverageMapping;
// Copy selected events.
TArray<FEventGraphSamplePtr> NewSelectedEvents;
for( int32 Nx = 0; Nx < SelectedEvents.Num(); ++Nx )
{
const FEventGraphSamplePtr& EventRef = OneToOneMapping.FindRef( SelectedEvents[Nx] );
NewSelectedEvents.Add( EventRef );
}
// Cope expanded events.
TSet<FEventGraphSamplePtr> NewExpandedEvents;
for( auto It = ExpandedEvents.CreateConstIterator(); It; ++It )
{
const FEventGraphSamplePtr& EventRef = OneToOneMapping.FindRef( *It );
NewExpandedEvents.Add( EventRef );
}
// Copy fake root's children.
FEventGraphSamplePtr NewFakeRoot = FEventGraphSample::CreateNamedEvent( FEventGraphConsts::FakeRoot );
TArray<FEventGraphSamplePtr>& Children = FakeRoot->GetChildren();
for( int32 Nx = 0; Nx < Children.Num(); ++Nx )
{
const FEventGraphSamplePtr& EventRef = OneToOneMapping.FindRef( Children[Nx] );
NewFakeRoot->AddChildPtr( EventRef );
}
// Switch to new data.
Exchange( SelectedEvents, NewSelectedEvents );
Exchange( ExpandedEvents, NewExpandedEvents );
FakeRoot = NewFakeRoot;
EventGraphType = NewEventGraphType;
}
}
};
/** Type definition for shared pointers to instances of FEventGraphState. */
typedef TSharedPtr<class FEventGraphState> FEventGraphStatePtr;
/** Type definition for shared references to instances of FEventGraphState. */
typedef TSharedRef<class FEventGraphState> FEventGraphStateRef;
FEventGraphStateRef GetCurrentState() const
{
return EventGraphStatesHistory[CurrentStateIndex];
}
/**
* @return the current event graph view mode.
*/
EEventGraphViewModes::Type GetCurrentStateViewMode() const
{
if( IsEventGraphStatesHistoryValid() )
{
return GetCurrentState()->ViewMode;
}
return EEventGraphViewModes::InvalidOrMax;
}
/**
* @return the current event graph type.
*/
EEventGraphTypes::Type GetCurrentStateEventGraphType() const
{
return GetCurrentState()->EventGraphType;
}
public:
/** Updates top level of the event graph, but only if there is no any other selection. */
void SetNewEventGraphState( const FEventGraphDataRef AverageEventGraph, const FEventGraphDataRef MaximumEventGraph, bool bInitial );
protected:
void SaveCurrentEventGraphState();
void RestoreEventGraphStateFrom( const FEventGraphStateRef EventGraphState, const bool bRestoredFromHistoryEvent = true );
void SwitchToEventGraphState( int32 StateIndex );
void SetEventGraphFromStateInternal( const FEventGraphStateRef& EventGraphState );
bool EventGraph_IsEnabled() const;
FReply HistoryBack_OnClicked();
bool HistoryBack_IsEnabled() const;
FText HistoryBack_GetToolTipText() const;
bool HistoryForward_IsEnabled() const;
FReply HistoryForward_OnClicked();
FText HistoryForward_GetToolTipText() const;
bool HistoryList_IsEnabled() const;
TSharedRef<SWidget> HistoryList_GetMenuContent() const;
bool IsEventGraphStatesHistoryValid() const
{
return EventGraphStatesHistory.Num() > 0;
}
/** Array of all operations that have been done in this event graph. */
TArray<FEventGraphStateRef> EventGraphStatesHistory;
/** The current operation index. */
int32 CurrentStateIndex;
/*-----------------------------------------------------------------------------
Function details
-----------------------------------------------------------------------------*/
protected:
struct FEventPtrAndMisc
{
FEventPtrAndMisc( FEventGraphSamplePtr InEventPtr, float InIncTimeToTotalPct, float InHeightPct )
: EventPtr( InEventPtr )
, IncTimeToTotalPct( InIncTimeToTotalPct )
, HeightPct( InHeightPct )
{}
FEventGraphSamplePtr EventPtr;
float IncTimeToTotalPct;
float HeightPct;
};
TSharedRef<SVerticalBox> GetVerticalBoxForFunctionDetails( TSharedPtr<SVerticalBox>& out_VerticalBoxTopFuncions, const FText& Caption );
TSharedRef<SVerticalBox> GetVerticalBoxForCurrentFunction();
void UpdateFunctionDetailsForEvent( FEventGraphSamplePtr SelectedEvent );
void DisableFunctionDetails();
void UpdateFunctionDetails();
void RecreateWidgetsForTopEvents( const TSharedPtr<SVerticalBox>& DestVerticalBox, const TArray<FEventPtrAndMisc>& TopEvents );
void GenerateCallerCalleeGraph( FEventGraphSamplePtr SelectedEvent );
void GenerateTopEvents( const TSet< FEventGraphSamplePtr >& EventPtrSet, TArray<FEventPtrAndMisc>& out_Results );
void CalculateEventWeights( TArray<FEventPtrAndMisc>& Events );
FString GetEventDescription( FEventGraphSamplePtr EventPtr, float Pct, const bool bSimple );
TSharedRef<SHorizontalBox> GetContentForEvent( FEventGraphSamplePtr EventPtr, float Pct, const bool bSimple );
FReply CallingCalledFunctionButton_OnClicked( FEventGraphSamplePtr EventPtr );
TSharedPtr<SVerticalBox> VerticalBox_TopCalled;
TSharedPtr<SVerticalBox> VerticalBox_TopCalling;
TSharedPtr<SVerticalBox> VerticalBox_CurrentFunction;
SVerticalBox::FSlot* CurrentFunctionDescSlot;
TArray<FEventPtrAndMisc> TopCallingFunctionEvents;
TArray<FEventPtrAndMisc> TopCalledFunctionEvents;
/** Name of the event that should be drawn as highlighted. */
FName HighlightedEventName;
/** ThreadName filter methods and data */
void FillThreadFilterOptions();
FText GenerateTextForThreadFilter( FName ThreadName ) const;
void OnThreadFilterChanged( TSharedPtr<FName> NewThread, ESelectInfo::Type SelectionType );
TSharedRef<SWidget> GetWidgetForThreadFilter();
TSharedRef<SWidget> OnGenerateWidgetForThreadFilter( TSharedPtr<FName> ThreadName ) const;
TSharedPtr<SComboBox<TSharedPtr<FName>>> ThreadFilterComboBox;
TArray<TSharedPtr<FName>> ThreadNamesForCombo;
};
#endif // STATS