You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
NOTE: Old tutorials not deprecated (yet), but widget highlights in old tutorials will stop working with this change! Added new Blueprintable UEditorTutorial object. Added suite of widgets and details customizations to display tutorials. New system is available on command line switch -NewTutorials. Slate changes: Tag names are now stored in SWidgets, rather than simply being discarded. Removed STutorialWrapper in favour of using Tags. Added Tags to more multibox widgets, so virtually all can now be picked. Added SWindow::HasOverlay so we dont attempt to add overlays to widows that cannot have them. [CL 2244216 by Thomas Sarkanen in Main branch]
2561 lines
75 KiB
C++
2561 lines
75 KiB
C++
// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "LogVisualizerPCH.h"
|
|
#include "SLogBar.h"
|
|
#include "Debug/LogVisualizerCameraController.h"
|
|
#include "Debug/ReporterGraph.h"
|
|
#include "MainFrame.h"
|
|
#include "DesktopPlatformModule.h"
|
|
#include "Json.h"
|
|
#include "Editor/UnrealEd/Classes/Editor/EditorEngine.h"
|
|
|
|
#if WITH_EDITOR
|
|
# include "Editor/UnrealEd/Public/EditorComponents.h"
|
|
# include "Editor/UnrealEd/Public/EditorReimportHandler.h"
|
|
# include "Editor/UnrealEd/Public/TexAlignTools.h"
|
|
# include "Editor/UnrealEd/Public/TickableEditorObject.h"
|
|
# include "UnrealEdClasses.h"
|
|
# include "Editor/UnrealEd/Public/Editor.h"
|
|
# include "Editor/UnrealEd/Public/EditorViewportClient.h"
|
|
#endif
|
|
|
|
#include "GameplayDebuggingComponent.h"
|
|
|
|
#include "SFilterList.h"
|
|
|
|
#if ENABLE_VISUAL_LOG
|
|
|
|
#define LOCTEXT_NAMESPACE "SLogVisualizer"
|
|
|
|
const FName SLogVisualizer::NAME_LogName = TEXT("LogName");
|
|
const FName SLogVisualizer::NAME_StartTime = TEXT("StartTime");
|
|
const FName SLogVisualizer::NAME_EndTime = TEXT("EndTime");
|
|
const FName SLogVisualizer::NAME_LogTimeSpan = TEXT("LogTimeSpan");
|
|
|
|
namespace LogVisualizer
|
|
{
|
|
static const FString LogFileExtensionPure = TEXT("vlog");
|
|
static const FString LogFileDescription = LOCTEXT("FileTypeDescription", "Visual Log File").ToString();
|
|
static const FString LogFileExtension = FString::Printf(TEXT("*.%s"), *LogFileExtensionPure);
|
|
static const FString FileTypes = FString::Printf( TEXT("%s (%s)|%s"), *LogFileDescription, *LogFileExtension, *LogFileExtension );
|
|
}
|
|
|
|
FColor SLogVisualizer::ColorPalette[] = {
|
|
FColor(0xff00A480),
|
|
FColorList::Aquamarine,
|
|
FColorList::Cyan,
|
|
FColorList::Brown,
|
|
FColorList::Green,
|
|
FColorList::Orange,
|
|
FColorList::Magenta,
|
|
FColorList::BrightGold,
|
|
FColorList::NeonBlue,
|
|
FColorList::MediumSlateBlue,
|
|
FColorList::SpicyPink,
|
|
FColorList::SpringGreen,
|
|
FColorList::SteelBlue,
|
|
FColorList::SummerSky,
|
|
FColorList::Violet,
|
|
FColorList::VioletRed,
|
|
FColorList::YellowGreen,
|
|
FColor(0xff62E200),
|
|
FColor(0xff1F7B67),
|
|
FColor(0xff62AA2A),
|
|
FColor(0xff70227E),
|
|
FColor(0xff006B53),
|
|
FColor(0xff409300),
|
|
FColor(0xff5D016D),
|
|
FColor(0xff34D2AF),
|
|
FColor(0xff8BF13C),
|
|
FColor(0xffBC38D3),
|
|
FColor(0xff5ED2B8),
|
|
FColor(0xffA6F16C),
|
|
FColor(0xffC262D3),
|
|
FColor(0xff0F4FA8),
|
|
FColor(0xff00AE68),
|
|
FColor(0xffDC0055),
|
|
FColor(0xff284C7E),
|
|
FColor(0xff21825B),
|
|
FColor(0xffA52959),
|
|
FColor(0xff05316D),
|
|
FColor(0xff007143),
|
|
FColor(0xff8F0037),
|
|
FColor(0xff4380D3),
|
|
FColor(0xff36D695),
|
|
FColor(0xffEE3B80),
|
|
FColor(0xff6996D3),
|
|
FColor(0xff60D6A7),
|
|
FColor(0xffEE6B9E)
|
|
};
|
|
|
|
template <typename ItemType>
|
|
class SLogListView : public SListView<ItemType>
|
|
{
|
|
public:
|
|
virtual FReply OnMouseWheel(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override
|
|
{
|
|
if (!MouseEvent.IsLeftShiftDown())
|
|
{
|
|
return SListView<ItemType>::OnMouseWheel(MyGeometry, MouseEvent);
|
|
};
|
|
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
void RefreshList()
|
|
{
|
|
const TArray<ItemType>& ItemsSourceRef = (*this->ItemsSource);
|
|
|
|
for (int32 Index = 0; Index < ItemsSourceRef.Num(); ++Index)
|
|
{
|
|
TSharedPtr< SLogsTableRow > TableRow = StaticCastSharedPtr< SLogsTableRow >(this->WidgetGenerator.GetWidgetForItem(ItemsSourceRef[Index]));
|
|
if (TableRow.IsValid())
|
|
{
|
|
TableRow->UpdateEntries();
|
|
}
|
|
}
|
|
|
|
}
|
|
};
|
|
|
|
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
|
void SLogVisualizer::Construct(const FArguments& InArgs, FLogVisualizer* InLogVisualizer)
|
|
{
|
|
LogVisualizer = InLogVisualizer;
|
|
SortBy = ELogsSortMode::ByName;
|
|
LogEntryIndex = INDEX_NONE;
|
|
SelectedLogIndex = INDEX_NONE;
|
|
LogsStartTime = FLT_MAX;
|
|
LogsEndTime = -FLT_MAX;
|
|
ScrollbarOffset = 0.f;
|
|
ZoomSliderValue = 0.f;
|
|
LastBarsOffset = 0.f;
|
|
MinZoom = 1.0f;
|
|
MaxZoom = 20.0f;
|
|
CurrentViewedTime = 0.f;
|
|
bDrawLogEntriesPath = true;
|
|
bIgnoreTrivialLogs = true;
|
|
HistogramPreviewWindow = 50;
|
|
bShowHistogramLabelsOutside = false;
|
|
bStickToLastData = false;
|
|
bOffsetDataSet = false;
|
|
bHistogramGraphsFilter = true;
|
|
|
|
UsedCategories.Empty();
|
|
|
|
ChildSlot
|
|
[
|
|
SNew(SBorder)
|
|
.BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder"))
|
|
[
|
|
SNew(SVerticalBox)
|
|
|
|
// Toolbar
|
|
+SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
SNew( SOverlay )
|
|
+ SOverlay::Slot()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
// Record button
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(1)
|
|
.AspectRatio()
|
|
[
|
|
SNew(SButton)
|
|
.OnClicked(this, &SLogVisualizer::OnRecordButtonClicked)
|
|
.Content()
|
|
[
|
|
SNew(SImage)
|
|
.Image(this, &SLogVisualizer::GetRecordButtonBrush)
|
|
]
|
|
]
|
|
// 'Pause' toggle button
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(1)
|
|
.AspectRatio()
|
|
[
|
|
SNew(SCheckBox)
|
|
.Style(FEditorStyle::Get(), "ToggleButtonCheckbox")
|
|
.OnCheckStateChanged(this, &SLogVisualizer::OnPauseChanged)
|
|
.IsChecked(this, &SLogVisualizer::GetPauseState)
|
|
.Content()
|
|
[
|
|
SNew(SImage)
|
|
.Image(FEditorStyle::GetBrush("LogVisualizer.Pause"))
|
|
]
|
|
]
|
|
// 'Camera' toggle button
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(1)
|
|
.AspectRatio()
|
|
[
|
|
SNew(SCheckBox)
|
|
.Style(FEditorStyle::Get(), "ToggleButtonCheckbox")
|
|
.OnCheckStateChanged(this, &SLogVisualizer::OnToggleCamera)
|
|
.IsChecked(this, &SLogVisualizer::GetToggleCameraState)
|
|
.Content()
|
|
[
|
|
SNew(SImage)
|
|
.Image(FEditorStyle::GetBrush("LogVisualizer.Camera"))
|
|
]
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.MaxWidth(3)
|
|
.Padding(1)
|
|
.AspectRatio()
|
|
[
|
|
SNew(SSeparator)
|
|
.Orientation(Orient_Vertical)
|
|
]
|
|
// 'Save' function
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(1)
|
|
.AspectRatio()
|
|
[
|
|
SNew(SButton)
|
|
.OnClicked(this, &SLogVisualizer::OnSave)
|
|
.Content()
|
|
[
|
|
SNew(SImage)
|
|
.Image(FEditorStyle::GetBrush("LogVisualizer.Save"))
|
|
]
|
|
]
|
|
// 'Load' function
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(1)
|
|
.AspectRatio()
|
|
[
|
|
SNew(SButton)
|
|
.OnClicked(this, &SLogVisualizer::OnLoad)
|
|
.Content()
|
|
[
|
|
SNew(SImage)
|
|
.Image(FEditorStyle::GetBrush("LogVisualizer.Load"))
|
|
]
|
|
]
|
|
// 'Remove' function
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(1)
|
|
.AspectRatio()
|
|
[
|
|
SNew(SButton)
|
|
.OnClicked(this, &SLogVisualizer::OnRemove)
|
|
.Content()
|
|
[
|
|
SNew(SImage)
|
|
.Image(FEditorStyle::GetBrush("LogVisualizer.Remove"))
|
|
]
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.MaxWidth(3)
|
|
.Padding(1)
|
|
.AspectRatio()
|
|
[
|
|
SNew(SSeparator)
|
|
.Orientation(Orient_Vertical)
|
|
]
|
|
]
|
|
|
|
+ SOverlay::Slot()
|
|
.HAlign(HAlign_Right)
|
|
.Padding(4)
|
|
[
|
|
SNew(SComboButton)
|
|
.ComboButtonStyle(FEditorStyle::Get(), "ContentBrowser.Filters.Style")
|
|
.ForegroundColor(FLinearColor::White)
|
|
.ContentPadding(0)
|
|
.ToolTipText(LOCTEXT("SettingsToolTip", "Log Visualizer settings."))
|
|
.HasDownArrow(true)
|
|
.ContentPadding(FMargin(1, 0))
|
|
.ButtonContent()
|
|
[
|
|
SNew(STextBlock)
|
|
.TextStyle(FEditorStyle::Get(), "ContentBrowser.Filters.Text")
|
|
.Text(LOCTEXT("Settings", "Settings"))
|
|
]
|
|
.MenuContent()
|
|
[
|
|
SNew(SVerticalBox)
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
SNew(SCheckBox)
|
|
.OnCheckStateChanged(this, &SLogVisualizer::OnDrawLogEntriesPathChanged)
|
|
.IsChecked(this, &SLogVisualizer::GetDrawLogEntriesPathState)
|
|
.Content()
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("VisLogDrawLogsPath", "Draw Log\'s path"))
|
|
.ToolTipText(LOCTEXT("VisLogDrawLogsPathTooltip", "Toggle whether path of composed of log entries\' locations"))
|
|
]
|
|
]
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
SNew(SCheckBox)
|
|
.OnCheckStateChanged(this, &SLogVisualizer::OnIgnoreTrivialLogs)
|
|
.IsChecked(this, &SLogVisualizer::GetIgnoreTrivialLogs)
|
|
.Content()
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("VisLogIgnoreTrivialLogs", "Ignore trivial logs"))
|
|
.ToolTipText(LOCTEXT("VisLogIgnoreTrivialLogsTooltip", "Whether to show trivial logs, i.e. the ones with only one entry."))
|
|
]
|
|
]
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
SNew(SCheckBox)
|
|
.OnCheckStateChanged(this, &SLogVisualizer::OnChangeHistogramLabelLocation)
|
|
.IsChecked(this, &SLogVisualizer::GetHistogramLabelLocation)
|
|
.Content()
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("VisLogHistogramLabelLocation", "Set histogram labels outside graph"))
|
|
.ToolTipText(LOCTEXT("VisLogHistogramLabelLocationTooltip", "Whether to show histogram labels inside graph or outside."))
|
|
]
|
|
]
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
SNew(SCheckBox)
|
|
.OnCheckStateChanged(this, &SLogVisualizer::OnStickToLastData)
|
|
.IsChecked(this, &SLogVisualizer::GetStickToLastData)
|
|
.Content()
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("VisLogStickToLastData", "Stick to recent data"))
|
|
.ToolTipText(LOCTEXT("VisLogStickToLastDataTooltip", "Whether to show the recent data or not."))
|
|
]
|
|
]
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
SNew(SCheckBox)
|
|
.OnCheckStateChanged(this, &SLogVisualizer::OnOffsetDataSets)
|
|
.IsChecked(this, &SLogVisualizer::GetOffsetDataSets)
|
|
.Content()
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("VisLogOffsetDataSets", "Offset data sets"))
|
|
.ToolTipText(LOCTEXT("VisLogOffsetDataSetsTooltip", "Offset data sets on graphs to make it easier to read"))
|
|
]
|
|
]
|
|
]
|
|
]
|
|
]
|
|
|
|
// Filters
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
SAssignNew(FilterListPtr, SLogFilterList)
|
|
.OnFilterChanged(this, &SLogVisualizer::OnLogCategoryFiltersChanged)
|
|
.Tag(TEXT("CategoryFilters"))
|
|
/*.OnGetContextMenu(this, &SLogVisualizer::GetFilterContextMenu)*/
|
|
/*.FrontendFilters(FrontendFilters)*/
|
|
]
|
|
|
|
+SVerticalBox::Slot()
|
|
.FillHeight(5)
|
|
[
|
|
SNew(SSplitter)
|
|
.Orientation(Orient_Vertical)
|
|
|
|
+SSplitter::Slot()
|
|
[
|
|
SNew(SBorder)
|
|
.BorderImage(FEditorStyle::GetBrush("Menu.Background"))
|
|
.Padding(1.0)
|
|
[
|
|
SAssignNew(LogsListWidget, SLogListView< TSharedPtr<FLogsListItem> >)
|
|
.ItemHeight(20)
|
|
// Called when the user double-clicks with LMB on an item in the list
|
|
.OnMouseButtonDoubleClick(this, &SLogVisualizer::OnListDoubleClick)
|
|
.ListItemsSource(&LogsList)
|
|
.SelectionMode(ESelectionMode::Multi)
|
|
.OnGenerateRow(this, &SLogVisualizer::LogsListGenerateRow)
|
|
.OnSelectionChanged(this, &SLogVisualizer::LogsListSelectionChanged)
|
|
.HeaderRow(
|
|
SNew(SHeaderRow)
|
|
// ID
|
|
+SHeaderRow::Column(NAME_LogName)
|
|
.SortMode(this, &SLogVisualizer::GetLogsSortMode)
|
|
.OnSort(this, &SLogVisualizer::OnSortByChanged)
|
|
.HAlignCell(HAlign_Left)
|
|
.FillWidth(0.25f)
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.HAlign(HAlign_Left)
|
|
.Padding(0.0, 2.0)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("VisLogName", "Log Subject"))
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(5,0)
|
|
[
|
|
SAssignNew(LogNameFilterBox, SEditableTextBox)
|
|
.SelectAllTextWhenFocused(true)
|
|
.OnTextCommitted(this, &SLogVisualizer::FilterTextCommitted)
|
|
.MinDesiredWidth(90.f)
|
|
.RevertTextOnEscape(true)
|
|
]
|
|
]
|
|
+SHeaderRow::Column(NAME_LogTimeSpan)
|
|
.VAlignCell(VAlign_Center)
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.HAlign(HAlign_Center)
|
|
.Padding(0.0, 2.0)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("VisLogFilterName", "Quick Filter"))
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(5, 0)
|
|
[
|
|
SAssignNew(QuickFilterBox, SEditableTextBox)
|
|
.SelectAllTextWhenFocused(true)
|
|
.OnTextChanged(this, &SLogVisualizer::OnQuickFilterTextChanged, ETextCommit::Default)
|
|
.MinDesiredWidth(170.f)
|
|
.RevertTextOnEscape(true)
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
[
|
|
SNew(SCheckBox)
|
|
.OnCheckStateChanged(this, &SLogVisualizer::OnHistogramGraphsFilter)
|
|
.IsChecked(this, &SLogVisualizer::GetHistogramGraphsFilter)
|
|
.Content()
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("VisLogHistogramGraphsFilter", "histogram graphs filter"))
|
|
.ToolTipText(LOCTEXT("VisLogHistogramGraphsFilterTooltip", "Whether to filter histogram graphs or regular categories."))
|
|
]
|
|
]
|
|
]
|
|
)
|
|
]
|
|
]
|
|
+SSplitter::Slot()
|
|
[
|
|
SNew(SBorder)
|
|
.BorderImage(FEditorStyle::GetBrush("Menu.Background"))
|
|
.Padding(1.0)
|
|
[
|
|
SNew(SVerticalBox)
|
|
|
|
+SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.MaxHeight(60)
|
|
[
|
|
SAssignNew( Timeline, STimeline )
|
|
.MinValue(0.0f)
|
|
.MaxValue(100.0f)
|
|
.FixedLabelSpacing(100.f)
|
|
]
|
|
|
|
+SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding( 2 )
|
|
.VAlign( VAlign_Fill )
|
|
[
|
|
SAssignNew( ScrollBar, SScrollBar )
|
|
.Orientation( Orient_Horizontal )
|
|
.OnUserScrolled( this, &SLogVisualizer::OnZoomScrolled )
|
|
]
|
|
|
|
+SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding( 2 )
|
|
[
|
|
SAssignNew(ZoomSlider, SSlider)
|
|
.Value( this, &SLogVisualizer::GetZoomValue )
|
|
.OnValueChanged( this, &SLogVisualizer::OnSetZoomValue )
|
|
]
|
|
|
|
+SVerticalBox::Slot()
|
|
.Padding(2)
|
|
.FillHeight(3)
|
|
//.VAlign(VAlign_Fill)
|
|
[
|
|
SNew(SSplitter)
|
|
|
|
+SSplitter::Slot()
|
|
.Value(1)
|
|
[
|
|
SNew( SBorder )
|
|
.Padding(1)
|
|
.BorderImage( FEditorStyle::GetBrush( "ToolBar.Background" ) )
|
|
[
|
|
SAssignNew(StatusItemsView, STreeView<TSharedPtr<FLogStatusItem> >)
|
|
.ItemHeight(40.0f)
|
|
.TreeItemsSource(&StatusItems)
|
|
.OnGenerateRow(this, &SLogVisualizer::HandleGenerateLogStatus)
|
|
.OnGetChildren(this, &SLogVisualizer::OnLogStatusGetChildren)
|
|
.SelectionMode(ESelectionMode::None)
|
|
]
|
|
]
|
|
+SSplitter::Slot()
|
|
.Value(3)
|
|
[
|
|
SNew( SBorder )
|
|
.Padding(1)
|
|
.BorderImage( FEditorStyle::GetBrush( "ToolBar.Background" ) )
|
|
[
|
|
SAssignNew(LogsLinesWidget, SListView<TSharedPtr<FLogEntryItem> >)
|
|
.ItemHeight(20)
|
|
.ListItemsSource(&LogEntryLines)
|
|
.SelectionMode(ESelectionMode::Multi)
|
|
.OnGenerateRow(this, &SLogVisualizer::LogEntryLinesGenerateRow)
|
|
]
|
|
]
|
|
]
|
|
]
|
|
]
|
|
]
|
|
+SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
// Status area
|
|
SNew(STextBlock)
|
|
.Text(this, &SLogVisualizer::GetStatusText)
|
|
]
|
|
]
|
|
];
|
|
|
|
LogVisualizer->OnLogAdded().AddSP(this, &SLogVisualizer::OnLogAdded);
|
|
|
|
TArray<TSharedPtr<FActorsVisLog> >& Logs = LogVisualizer->Logs;
|
|
TSharedPtr<FActorsVisLog>* SharedLog = Logs.GetTypedData();
|
|
for (int32 LogIndex = 0; LogIndex < Logs.Num(); ++LogIndex, ++SharedLog)
|
|
{
|
|
if (SharedLog->IsValid())
|
|
{
|
|
AddOrUpdateLog(LogIndex, SharedLog->Get());
|
|
}
|
|
}
|
|
|
|
if (LogsList.Num() == 0)
|
|
{
|
|
Timeline->SetVisibility(EVisibility::Hidden);
|
|
ScrollBar->SetVisibility(EVisibility::Hidden);
|
|
ZoomSlider->SetVisibility(EVisibility::Hidden);
|
|
}
|
|
|
|
DoFullUpdate();
|
|
|
|
LastBrowsePath = FPaths::GameLogDir();
|
|
|
|
DrawingOnCanvasDelegate = FDebugDrawDelegate::CreateSP(this, &SLogVisualizer::DrawOnCanvas);
|
|
UDebugDrawService::Register(TEXT("VisLog"), DrawingOnCanvasDelegate);
|
|
UGameplayDebuggingComponent::OnDebuggingTargetChangedDelegate.AddSP(this, &SLogVisualizer::SelectionChanged);
|
|
}
|
|
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
|
|
|
SLogVisualizer::~SLogVisualizer()
|
|
{
|
|
UGameplayDebuggingComponent::OnDebuggingTargetChangedDelegate.RemoveAll(this);
|
|
LogVisualizer->OnLogAdded().RemoveAll(this);
|
|
UDebugDrawService::Unregister(DrawingOnCanvasDelegate);
|
|
InvalidateCanvas();
|
|
}
|
|
|
|
void SLogVisualizer::OnListDoubleClick(TSharedPtr<FLogsListItem> LogListItem)
|
|
{
|
|
#if WITH_EDITOR
|
|
FVector Orgin, Extent;
|
|
|
|
bool bFoundActor = false;
|
|
if (LogVisualizer->Logs.IsValidIndex(LogListItem->LogIndex))
|
|
{
|
|
TSharedPtr<FActorsVisLog>& Log = LogVisualizer->Logs[LogListItem->LogIndex];
|
|
for (FActorIterator It(GetWorld()); It; ++It)
|
|
{
|
|
AActor* Actor = Cast<AActor>(*It);
|
|
if (Actor->GetFName() == Log->Name)
|
|
{
|
|
Actor->GetActorBounds(false, Orgin, Extent);
|
|
bFoundActor = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!bFoundActor)
|
|
{
|
|
Extent = FVector(150, 150, 150);
|
|
}
|
|
|
|
if (LogVisualizer->Logs.IsValidIndex(LogListItem->LogIndex))
|
|
{
|
|
TSharedPtr<FActorsVisLog>& Log = LogVisualizer->Logs[LogListItem->LogIndex];
|
|
if (Log.IsValid() && Log->Entries.IsValidIndex(LogEntryIndex))
|
|
{
|
|
Orgin = Log->Entries[LogEntryIndex]->Location;
|
|
}
|
|
}
|
|
|
|
|
|
UEditorEngine *EEngine = Cast<UEditorEngine>(GEngine);
|
|
if (GIsEditor && EEngine != NULL)
|
|
{
|
|
for (auto ViewportClient : EEngine->AllViewportClients)
|
|
{
|
|
ViewportClient->FocusViewportOnBox(FBox::BuildAABB(Orgin, Extent));
|
|
}
|
|
}
|
|
else if (ALogVisualizerCameraController::IsEnabled(GetWorld()) && CameraController.IsValid() && CameraController->GetSpectatorPawn())
|
|
{
|
|
ULocalPlayer* LocalPlayer = Cast<ULocalPlayer>(CameraController->Player);
|
|
if (LocalPlayer && LocalPlayer->ViewportClient && LocalPlayer->ViewportClient->Viewport)
|
|
{
|
|
FViewport* Viewport = LocalPlayer->ViewportClient->Viewport;
|
|
|
|
FBox BoundingBox = FBox::BuildAABB(Orgin, Extent);
|
|
const FVector Position = BoundingBox.GetCenter();
|
|
float Radius = BoundingBox.GetExtent().Size();
|
|
|
|
FViewportCameraTransform ViewTransform;
|
|
ViewTransform.TransitionToLocation(Position, true);
|
|
|
|
float NewOrthoZoom;
|
|
const float AspectRatio = 1.777777f;
|
|
uint32 MinAxisSize = (AspectRatio > 1.0f) ? Viewport->GetSizeXY().Y : Viewport->GetSizeXY().X;
|
|
float Zoom = Radius / (MinAxisSize / 2);
|
|
|
|
NewOrthoZoom = Zoom * (Viewport->GetSizeXY().X*15.0f);
|
|
NewOrthoZoom = FMath::Clamp<float>(NewOrthoZoom, 250, MAX_FLT);
|
|
ViewTransform.SetOrthoZoom(NewOrthoZoom);
|
|
|
|
CameraController->GetSpectatorPawn()->TeleportTo(ViewTransform.GetLocation(), ViewTransform.GetRotation(), false, true);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
int32 SLogVisualizer::GetCurrentVisibleLogEntryIndex(const TArray<TSharedPtr<FVisLogEntry> >& InVisibleEntries)
|
|
{
|
|
if (LogVisualizer->Logs.IsValidIndex(SelectedLogIndex))
|
|
{
|
|
TSharedPtr<FActorsVisLog> Log = LogVisualizer->Logs[SelectedLogIndex];
|
|
if (Log.IsValid() && Log->Entries.IsValidIndex(LogEntryIndex))
|
|
{
|
|
for (int32 Index = 0; Index < InVisibleEntries.Num(); ++Index)
|
|
{
|
|
if (InVisibleEntries[Index] == Log->Entries[LogEntryIndex])
|
|
{
|
|
return Index;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return INDEX_NONE;
|
|
}
|
|
|
|
void SLogVisualizer::UpdateVisibleEntriesCache(const TSharedPtr<FActorsVisLog>& Log, int32 Index)
|
|
{
|
|
int32 CurrentIndex = INDEX_NONE;
|
|
int32 LogListNum = LogsList.Num();
|
|
for (int32 LogIndex = 0; LogIndex < LogListNum; ++LogIndex)
|
|
{
|
|
if (LogsList[LogIndex]->LogIndex == Index)
|
|
{
|
|
CurrentIndex = LogIndex;
|
|
break;
|
|
}
|
|
}
|
|
|
|
float LastTimestamp = -1;
|
|
if (CurrentIndex != INDEX_NONE)
|
|
{
|
|
LastTimestamp = LogsList[CurrentIndex]->LastEndTimestamp;
|
|
}
|
|
|
|
const int32 NumLogs = OutEntriesCached.Num();
|
|
int32 CachedLogIndex = INDEX_NONE;
|
|
for (int32 EntriesIndex = 0; EntriesIndex < NumLogs; ++EntriesIndex)
|
|
{
|
|
if (OutEntriesCached[EntriesIndex].Log == Log)
|
|
{
|
|
CachedLogIndex = EntriesIndex;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (CachedLogIndex == INDEX_NONE)
|
|
{
|
|
FCachedEntries CachedLog;
|
|
CachedLog.Log = Log;
|
|
CachedLogIndex = OutEntriesCached.Add(CachedLog);
|
|
}
|
|
|
|
if (FilterListPtr.IsValid())
|
|
{
|
|
for (int32 EntryIndex = 0; EntryIndex < Log->Entries.Num(); ++EntryIndex)
|
|
{
|
|
const TSharedPtr<FVisLogEntry> CurrentEntry = Log->Entries[EntryIndex];
|
|
if (CurrentEntry->TimeStamp <= LastTimestamp)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
bool bAddedEntry = false;
|
|
if (!bAddedEntry)
|
|
{
|
|
for (int32 LogLineIndex = 0; LogLineIndex < CurrentEntry->LogLines.Num(); ++LogLineIndex)
|
|
{
|
|
const FVisLogEntry::FLogLine &CurrentLine = CurrentEntry->LogLines[LogLineIndex];
|
|
const FString CurrentCategory = CurrentLine.Category.ToString();
|
|
if (FilterListPtr->IsFilterEnabled(CurrentCategory, CurrentLine.Verbosity))
|
|
{
|
|
OutEntriesCached[CachedLogIndex].CachedEntries.Add(CurrentEntry);
|
|
bAddedEntry = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bAddedEntry)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (int32 ElementIndex = 0; ElementIndex < CurrentEntry->ElementsToDraw.Num(); ++ElementIndex)
|
|
{
|
|
const FString CurrentCategory = CurrentEntry->ElementsToDraw[ElementIndex].Category.ToString();
|
|
FVisLogEntry::FElementToDraw &CurrentElement = CurrentEntry->ElementsToDraw[ElementIndex];
|
|
if (CurrentElement.Category != NAME_None && (FilterListPtr->IsFilterEnabled(CurrentCategory, CurrentElement.Verbosity)))
|
|
{
|
|
OutEntriesCached[CachedLogIndex].CachedEntries.AddUnique(CurrentEntry);
|
|
bAddedEntry = true;
|
|
break;
|
|
}
|
|
}
|
|
if (bAddedEntry)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (int32 SampleIndex = 0; SampleIndex < CurrentEntry->HistogramSamples.Num(); ++SampleIndex)
|
|
{
|
|
const FVisLogEntry::FHistogramSample &CurrentSample = CurrentEntry->HistogramSamples[SampleIndex];
|
|
const FName CurrentCategory = CurrentSample.Category;
|
|
const FName CurrentDataName = CurrentSample.DataName;
|
|
|
|
const bool bCurrentCategoryPassed = FilterListPtr->IsFilterEnabled(CurrentCategory.ToString(), ELogVerbosity::All);
|
|
const bool bCurrentDataNamePassed = FilterListPtr->IsFilterEnabled(CurrentDataName.ToString(), ELogVerbosity::All);
|
|
|
|
if (CurrentCategory != NAME_None && (bCurrentCategoryPassed && bCurrentDataNamePassed))
|
|
{
|
|
OutEntriesCached[CachedLogIndex].CachedEntries.AddUnique(CurrentEntry);
|
|
bAddedEntry = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// if there is no LogFilter widget - show all
|
|
OutEntriesCached[CachedLogIndex].CachedEntries = Log->Entries;
|
|
}
|
|
LogsList[CurrentIndex]->LastEndTimestamp = LogsList[CurrentIndex]->EndTimestamp;
|
|
}
|
|
|
|
void SLogVisualizer::GetVisibleEntries(const TSharedPtr<FActorsVisLog>& Log, TArray<TSharedPtr<FVisLogEntry> >& OutEntries)
|
|
{
|
|
const int32 NumLogs = OutEntriesCached.Num();
|
|
int32 CachedLogIndex = INDEX_NONE;
|
|
for (int32 Index = 0; Index < NumLogs; ++Index)
|
|
{
|
|
if (OutEntriesCached[Index].Log == Log)
|
|
{
|
|
CachedLogIndex = Index;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (CachedLogIndex == INDEX_NONE)
|
|
{
|
|
FCachedEntries CachedLog;
|
|
CachedLog.Log = Log;
|
|
CachedLogIndex = OutEntriesCached.Add(CachedLog);
|
|
}
|
|
|
|
if (OutEntriesCached[CachedLogIndex].CachedEntries.Num() == 0)
|
|
{
|
|
// generate entries based on current filters
|
|
OutEntriesCached[CachedLogIndex].CachedEntries.Reset();
|
|
if (FilterListPtr.IsValid())
|
|
{
|
|
for (int32 i = 0; i < Log->Entries.Num(); ++i)
|
|
{
|
|
bool bAddedEntry = false;
|
|
|
|
const TSharedPtr<FVisLogEntry> CurrentEntry = Log->Entries[i];
|
|
if (!bAddedEntry)
|
|
{
|
|
for (int32 j = 0; j < CurrentEntry->LogLines.Num(); ++j)
|
|
{
|
|
const FVisLogEntry::FLogLine &CurrentLine = CurrentEntry->LogLines[j];
|
|
const FString CurrentCategory = CurrentLine.Category.ToString();
|
|
if (FilterListPtr->IsFilterEnabled(CurrentCategory, CurrentLine.Verbosity))
|
|
{
|
|
OutEntriesCached[CachedLogIndex].CachedEntries.Add(CurrentEntry);
|
|
bAddedEntry = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bAddedEntry)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (int32 j = 0; j < CurrentEntry->ElementsToDraw.Num(); ++j)
|
|
{
|
|
const FString CurrentCategory = CurrentEntry->ElementsToDraw[j].Category.ToString();
|
|
FVisLogEntry::FElementToDraw &CurrentElement = CurrentEntry->ElementsToDraw[j];
|
|
if (CurrentElement.Category != NAME_None && (FilterListPtr->IsFilterEnabled(CurrentCategory, CurrentElement.Verbosity)))
|
|
{
|
|
OutEntriesCached[CachedLogIndex].CachedEntries.AddUnique(CurrentEntry);
|
|
bAddedEntry = true;
|
|
break;
|
|
}
|
|
}
|
|
if (bAddedEntry)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (int32 SampleIndex = 0; SampleIndex < CurrentEntry->HistogramSamples.Num(); ++SampleIndex)
|
|
{
|
|
const FVisLogEntry::FHistogramSample &CurrentSample = CurrentEntry->HistogramSamples[SampleIndex];
|
|
const FName CurrentCategory = CurrentSample.Category;
|
|
const FName CurrentDataName = CurrentSample.DataName;
|
|
|
|
const bool bCurrentCategoryPassed = FilterListPtr->IsFilterEnabled(CurrentCategory.ToString(), ELogVerbosity::All);
|
|
const bool bCurrentDataNamePassed = FilterListPtr->IsFilterEnabled(CurrentDataName.ToString(), ELogVerbosity::All);
|
|
|
|
if (CurrentCategory != NAME_None && (bCurrentCategoryPassed && bCurrentDataNamePassed))
|
|
{
|
|
OutEntriesCached[CachedLogIndex].CachedEntries.AddUnique(CurrentEntry);
|
|
bAddedEntry = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
// if there is no LogFilter widget - show all
|
|
OutEntriesCached[CachedLogIndex].CachedEntries = Log->Entries;
|
|
}
|
|
}
|
|
|
|
OutEntries.Reset();
|
|
if (QuickFilterText.Len() > 0)
|
|
{
|
|
// filter our data using quick filter string
|
|
const int32 NumEntries = OutEntriesCached[CachedLogIndex].CachedEntries.Num();
|
|
for (int32 Index = 0; Index < NumEntries; ++Index)
|
|
{
|
|
TSharedPtr<FVisLogEntry> LogEntry = OutEntriesCached[CachedLogIndex].CachedEntries[Index];
|
|
if (!LogEntry.IsValid())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
bool bAddedEntry = false;
|
|
if (!bAddedEntry)
|
|
{
|
|
for (int32 LineIndex = 0; LineIndex < LogEntry->LogLines.Num(); ++LineIndex)
|
|
{
|
|
FString CurrentCategory = LogEntry->LogLines[LineIndex].Category.ToString();
|
|
if (bHistogramGraphsFilter || CurrentCategory.Find(QuickFilterText) != INDEX_NONE)
|
|
{
|
|
OutEntries.AddUnique(LogEntry);
|
|
bAddedEntry = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bAddedEntry)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (int32 ElementIndex = 0; ElementIndex < LogEntry->ElementsToDraw.Num(); ++ElementIndex)
|
|
{
|
|
FString CurrentCategory = LogEntry->ElementsToDraw[ElementIndex].Category.ToString();
|
|
if (bHistogramGraphsFilter || CurrentCategory.Find(QuickFilterText) != INDEX_NONE)
|
|
{
|
|
OutEntries.AddUnique(LogEntry);
|
|
bAddedEntry = true;
|
|
break;
|
|
}
|
|
}
|
|
if (bAddedEntry)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (int32 SampleIndex = 0; SampleIndex < LogEntry->HistogramSamples.Num(); ++SampleIndex)
|
|
{
|
|
const FName CurrentCategory = LogEntry->HistogramSamples[SampleIndex].Category;
|
|
const FName CurrentGraphName = LogEntry->HistogramSamples[SampleIndex].GraphName;
|
|
const FName CurrentDataName = LogEntry->HistogramSamples[SampleIndex].DataName;
|
|
|
|
const bool bCurrentCategoryPassed = bHistogramGraphsFilter || (QuickFilterText.Len() == 0 || CurrentCategory.ToString().Find(QuickFilterText) != INDEX_NONE);
|
|
const bool bCurrentDataNamePassed = !bHistogramGraphsFilter || (QuickFilterText.Len() == 0 || CurrentDataName.ToString().Find(QuickFilterText) != INDEX_NONE);
|
|
|
|
if (bCurrentCategoryPassed && bCurrentDataNamePassed)
|
|
{
|
|
OutEntries.AddUnique(LogEntry);
|
|
bAddedEntry = true;
|
|
break;
|
|
}
|
|
}
|
|
} //for (int32 Index = 0; Index < NumEntries; ++Index)
|
|
} //if (QuickFilterText.Len() > 0)
|
|
else
|
|
{
|
|
OutEntries = OutEntriesCached[CachedLogIndex].CachedEntries;
|
|
}
|
|
}
|
|
|
|
void SLogVisualizer::OnLogCategoryFiltersChanged()
|
|
{
|
|
LogsList.Reset();
|
|
OutEntriesCached.Reset();
|
|
RebuildFilteredList();
|
|
|
|
if (LogVisualizer && LogVisualizer->Logs.IsValidIndex(SelectedLogIndex))
|
|
{
|
|
TSharedPtr<FActorsVisLog> Log = LogVisualizer->Logs[SelectedLogIndex];
|
|
if (Log.IsValid() && Log->Entries.IsValidIndex(LogEntryIndex))
|
|
{
|
|
ShowEntry(Log->Entries[LogEntryIndex].Get());
|
|
}
|
|
}
|
|
InvalidateCanvas();
|
|
}
|
|
|
|
UWorld* SLogVisualizer::GetWorld() const
|
|
{
|
|
// TODO: This needs to be an internalized reference
|
|
UEditorEngine *EEngine = Cast<UEditorEngine>(GEngine);
|
|
if (GIsEditor && EEngine != NULL)
|
|
{
|
|
// lets use PlayWorld during PIE/Simulate and regular world from editor otherwise, to draw debug information
|
|
return EEngine->PlayWorld != NULL ? EEngine->PlayWorld : EEngine->GetEditorWorldContext().World();
|
|
|
|
}
|
|
else if (!GIsEditor)
|
|
{
|
|
return LogVisualizer->GetWorld();
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void SLogVisualizer::Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime )
|
|
{
|
|
SCompoundWidget::Tick(AllottedGeometry, InCurrentTime, InDeltaTime);
|
|
|
|
TimeTillNextUpdate -= InDeltaTime;
|
|
|
|
UWorld* World = GetWorld();
|
|
if (World && !World->bPlayersOnly && TimeTillNextUpdate < 0 && LogVisualizer->IsRecording())
|
|
{
|
|
DoTickUpdate();
|
|
//DoFullUpdate();
|
|
}
|
|
|
|
if (bStickToLastData && World && !World->bPlayersOnly && LogVisualizer->IsRecording() && World->IsGameWorld() && LogVisualizer && LogVisualizer->Logs.IsValidIndex(SelectedLogIndex))
|
|
{
|
|
TSharedPtr<FActorsVisLog> Log = LogVisualizer->Logs[SelectedLogIndex];
|
|
if (Log->Entries.Num() > 0 && LogEntryIndex != Log->Entries.Num() - 1)
|
|
{
|
|
LogEntryIndex = Log->Entries.Num() - 1;
|
|
ShowEntry(Log->Entries[LogEntryIndex].Get());
|
|
}
|
|
}
|
|
}
|
|
|
|
FReply SLogVisualizer::OnMouseWheel( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
|
|
{
|
|
if (MouseEvent.IsLeftControlDown())
|
|
{
|
|
OnSetZoomValue(FMath::Clamp(ZoomSliderValue + MouseEvent.GetWheelDelta() * 0.05f, 0.f, 1.f));
|
|
return FReply::Handled();
|
|
}
|
|
|
|
if (MouseEvent.IsLeftShiftDown())
|
|
{
|
|
OnSetHistogramWindowValue(MouseEvent.GetWheelDelta());
|
|
return FReply::Handled();
|
|
}
|
|
|
|
return SCompoundWidget::OnMouseWheel(MyGeometry, MouseEvent);
|
|
}
|
|
|
|
FReply SLogVisualizer::OnKeyDown( const FGeometry& MyGeometry, const FKeyboardEvent& InKeyboardEvent )
|
|
{
|
|
const FKey Key = InKeyboardEvent.GetKey();
|
|
if (Key == EKeys::Left || Key == EKeys::Right)
|
|
{
|
|
int32 MoveBy = Key == EKeys::Left ? -1 : 1;
|
|
if (InKeyboardEvent.IsLeftControlDown())
|
|
{
|
|
MoveBy *= 10;
|
|
}
|
|
|
|
IncrementCurrentLogIndex(MoveBy);
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
return SCompoundWidget::OnKeyDown(MyGeometry, InKeyboardEvent);
|
|
}
|
|
|
|
TSharedRef< SWidget > SLogVisualizer::MakeMainMenu()
|
|
{
|
|
FMenuBarBuilder MenuBuilder( NULL );
|
|
{
|
|
// File
|
|
MenuBuilder.AddPullDownMenu(
|
|
NSLOCTEXT("LogVisualizer", "FileMenu", "File"),
|
|
NSLOCTEXT("LogVisualizer", "FileMenu_ToolTip", "Open the file menu"),
|
|
FNewMenuDelegate::CreateSP( this, &SLogVisualizer::OpenSavedSession ) );
|
|
|
|
// Help
|
|
MenuBuilder.AddPullDownMenu(
|
|
NSLOCTEXT("LogVisualizer", "HelpMenu", "Help"),
|
|
NSLOCTEXT("LogVisualizer", "HelpMenu_ToolTip", "Open the help menu"),
|
|
FNewMenuDelegate::CreateSP( this, &SLogVisualizer::FillHelpMenu ) );
|
|
}
|
|
|
|
// Create the menu bar
|
|
TSharedRef< SWidget > MenuBarWidget = MenuBuilder.MakeWidget();
|
|
|
|
return MenuBarWidget;
|
|
}
|
|
|
|
void SLogVisualizer::FillHelpMenu(FMenuBuilder& MenuBuilder)
|
|
{
|
|
}
|
|
|
|
void SLogVisualizer::OpenSavedSession(FMenuBuilder& MenuBuilder)
|
|
{
|
|
}
|
|
|
|
//----------------------------------------------------------------------//
|
|
// non-slate
|
|
//----------------------------------------------------------------------//
|
|
|
|
void SLogVisualizer::SelectionChanged(AActor* DebuggedActor, bool bIsBeingDebuggedNow)
|
|
{
|
|
if (DebuggedActor != NULL && bIsBeingDebuggedNow)
|
|
{
|
|
SelectActor(DebuggedActor);
|
|
}
|
|
}
|
|
|
|
void SLogVisualizer::IncrementCurrentLogIndex(int32 IncrementBy)
|
|
{
|
|
if (!LogVisualizer->Logs.IsValidIndex(SelectedLogIndex))
|
|
{
|
|
return;
|
|
}
|
|
|
|
TSharedPtr<FActorsVisLog> Log = LogVisualizer->Logs[SelectedLogIndex];
|
|
check(Log.IsValid());
|
|
|
|
int32 NewEntryIndex = FMath::Clamp(LogEntryIndex + IncrementBy, 0, Log->Entries.Num() - 1);
|
|
|
|
if (FilterListPtr.IsValid())
|
|
{
|
|
while (NewEntryIndex >= 0 && NewEntryIndex < Log->Entries.Num())
|
|
{
|
|
bool bShouldShow = false;
|
|
for (int32 LineIndex = 0; LineIndex < Log->Entries[NewEntryIndex]->LogLines.Num(); ++LineIndex)
|
|
{
|
|
if (FilterListPtr->IsFilterEnabled(Log->Entries[NewEntryIndex]->LogLines[LineIndex].Category.ToString(), Log->Entries[NewEntryIndex]->LogLines[LineIndex].Verbosity))
|
|
{
|
|
bShouldShow = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!bShouldShow)
|
|
{
|
|
for (int32 LineIndex = 0; LineIndex < Log->Entries[NewEntryIndex]->ElementsToDraw.Num(); ++LineIndex)
|
|
{
|
|
if (Log->Entries[NewEntryIndex]->ElementsToDraw[LineIndex].Category == NAME_None || FilterListPtr->IsFilterEnabled(Log->Entries[NewEntryIndex]->ElementsToDraw[LineIndex].Category.ToString(), Log->Entries[NewEntryIndex]->ElementsToDraw[LineIndex].Verbosity))
|
|
{
|
|
bShouldShow = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!bShouldShow)
|
|
{
|
|
for (int32 SampleIndex = 0; SampleIndex < Log->Entries[NewEntryIndex]->HistogramSamples.Num(); ++SampleIndex)
|
|
{
|
|
const FName CurrentCategory = Log->Entries[NewEntryIndex]->HistogramSamples[SampleIndex].Category;
|
|
const FName CurrentGraphName = Log->Entries[NewEntryIndex]->HistogramSamples[SampleIndex].GraphName;
|
|
const FName CurrentDataName = Log->Entries[NewEntryIndex]->HistogramSamples[SampleIndex].DataName;
|
|
if (CurrentCategory == NAME_None ||
|
|
(FilterListPtr->IsFilterEnabled(CurrentCategory.ToString(), ELogVerbosity::All) && FilterListPtr->IsFilterEnabled(CurrentGraphName.ToString(), CurrentDataName.ToString(), ELogVerbosity::All)))
|
|
{
|
|
bShouldShow = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bShouldShow)
|
|
{
|
|
break;
|
|
}
|
|
|
|
NewEntryIndex += (IncrementBy > 0 ? 1 : -1);
|
|
}
|
|
}
|
|
|
|
if (NewEntryIndex != LogEntryIndex && Log->Entries.IsValidIndex(NewEntryIndex))
|
|
{
|
|
LogEntryIndex = NewEntryIndex;
|
|
ShowEntry(Log->Entries[NewEntryIndex].Get());
|
|
}
|
|
}
|
|
|
|
void SLogVisualizer::AddOrUpdateLog(int32 LogIndex, const FActorsVisLog* Log)
|
|
{
|
|
if (Log->Entries.Num() == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (LogsList.Num() == 0)
|
|
{
|
|
Timeline->SetVisibility(EVisibility::Visible);
|
|
ScrollBar->SetVisibility(EVisibility::Visible);
|
|
ZoomSlider->SetVisibility(EVisibility::Visible);
|
|
}
|
|
|
|
const float StartTimestamp = Log->Entries[0]->TimeStamp;
|
|
const float EndTimestamp = Log->Entries[Log->Entries.Num() - 1]->TimeStamp;
|
|
|
|
|
|
int32 CurrentIndex = INDEX_NONE;
|
|
int32 LogListNum = LogsList.Num();
|
|
for (int32 ListIndex = 0; ListIndex < LogListNum; ++ListIndex)
|
|
{
|
|
if (LogsList[ListIndex]->LogIndex == LogIndex)
|
|
{
|
|
CurrentIndex = ListIndex;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// add used categories
|
|
for (int32 EntryIndex = 0; EntryIndex < Log->Entries.Num(); ++EntryIndex)
|
|
{
|
|
if (CurrentIndex != INDEX_NONE && Log->Entries[EntryIndex]->TimeStamp <= LogsList[CurrentIndex]->EndTimestamp)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (auto Iter(Log->Entries[EntryIndex]->LogLines.CreateConstIterator()); Iter; Iter++)
|
|
{
|
|
int32 Index = UsedCategories.Find(Iter->Category.ToString());
|
|
if (Index == INDEX_NONE)
|
|
{
|
|
Index = UsedCategories.Add(Iter->Category.ToString());
|
|
FilterListPtr->AddFilter(Iter->Category.ToString(), GetColorForUsedCategory(Index));
|
|
}
|
|
}
|
|
|
|
for (auto Iter(Log->Entries[EntryIndex]->ElementsToDraw.CreateConstIterator()); Iter; Iter++)
|
|
{
|
|
const FString CategoryAsString = Iter->Category != NAME_None ? Iter->Category.ToString() : TEXT("ShapeElement");
|
|
|
|
int32 Index = UsedCategories.Find(CategoryAsString);
|
|
if (Index == INDEX_NONE)
|
|
{
|
|
Index = UsedCategories.Add(CategoryAsString);
|
|
FilterListPtr->AddFilter(CategoryAsString, GetColorForUsedCategory(Index));
|
|
}
|
|
}
|
|
|
|
for (int32 SampleIndex = 0; SampleIndex < Log->Entries[EntryIndex]->HistogramSamples.Num(); ++SampleIndex)
|
|
{
|
|
const FString CategoryAsString = Log->Entries[EntryIndex]->HistogramSamples[SampleIndex].Category.ToString();
|
|
|
|
int32 Index = UsedCategories.Find(CategoryAsString);
|
|
if (Index == INDEX_NONE)
|
|
{
|
|
Index = UsedCategories.Add(CategoryAsString);
|
|
FilterListPtr->AddFilter(CategoryAsString, GetColorForUsedCategory(Index));
|
|
}
|
|
|
|
const FString GraphNameAsString = Log->Entries[EntryIndex]->HistogramSamples[SampleIndex].GraphName.ToString();
|
|
const FString DataNameAsString = Log->Entries[EntryIndex]->HistogramSamples[SampleIndex].DataName.ToString();
|
|
FilterListPtr->AddGraphFilter(GraphNameAsString, DataNameAsString, FColor::White);
|
|
}
|
|
}
|
|
|
|
if (CurrentIndex == INDEX_NONE)
|
|
{
|
|
LogsList.Add(MakeShareable(new FLogsListItem(Log->Name.ToString(), StartTimestamp, EndTimestamp, LogIndex)));
|
|
}
|
|
else
|
|
{
|
|
LogsList[CurrentIndex]->StartTimestamp = StartTimestamp;
|
|
LogsList[CurrentIndex]->LastEndTimestamp = LogsList[CurrentIndex]->EndTimestamp;
|
|
LogsList[CurrentIndex]->EndTimestamp= EndTimestamp;
|
|
}
|
|
}
|
|
|
|
void SLogVisualizer::DoFullUpdate()
|
|
{
|
|
TSharedPtr<FLogsListItem>* LogListItem = LogsList.GetTypedData();
|
|
for (int32 ItemIndex = 0; ItemIndex < LogsList.Num(); ++ItemIndex, ++LogListItem)
|
|
{
|
|
if (LogListItem->IsValid() && LogVisualizer->Logs.IsValidIndex((*LogListItem)->LogIndex))
|
|
{
|
|
TSharedPtr<FActorsVisLog>& Log = LogVisualizer->Logs[(*LogListItem)->LogIndex];
|
|
if (Log->Entries.Num() > 0)
|
|
{
|
|
LogsStartTime = FMath::Min(Log->Entries[0]->TimeStamp, LogsStartTime);
|
|
LogsEndTime = FMath::Max(Log->Entries[Log->Entries.Num() - 1]->TimeStamp, LogsEndTime);
|
|
}
|
|
}
|
|
}
|
|
|
|
Timeline->SetMinMaxValues(LogsStartTime, LogsEndTime);
|
|
// set zoom max so that single even on SBarLogs has desired size on maximum zoom
|
|
const float WidthPx = Timeline->GetDrawingGeometry().Size.X;
|
|
if (WidthPx > 0)
|
|
{
|
|
const float OldMaxZoom = MaxZoom;
|
|
const float PxPerTimeUnit = WidthPx * SLogBar::TimeUnit / (LogsEndTime - LogsStartTime);
|
|
MaxZoom = SLogBar::MaxUnitSizePx / PxPerTimeUnit;
|
|
if (MaxZoom < MinZoom)
|
|
{
|
|
MaxZoom = MinZoom;
|
|
}
|
|
|
|
ZoomSliderValue = MaxZoom * ZoomSliderValue / OldMaxZoom;
|
|
// update
|
|
}
|
|
|
|
LogsList.Reset();
|
|
OutEntriesCached.Reset();
|
|
RebuildFilteredList();
|
|
|
|
TimeTillNextUpdate = 1.f / FullUpdateFrequency;
|
|
InvalidateCanvas();
|
|
}
|
|
|
|
void SLogVisualizer::DoTickUpdate()
|
|
{
|
|
TSharedPtr<FLogsListItem>* LogListItem = LogsList.GetTypedData();
|
|
for (int32 ItemIndex = 0; ItemIndex < LogsList.Num(); ++ItemIndex, ++LogListItem)
|
|
{
|
|
if (LogListItem->IsValid() && LogVisualizer->Logs.IsValidIndex((*LogListItem)->LogIndex))
|
|
{
|
|
TSharedPtr<FActorsVisLog>& Log = LogVisualizer->Logs[(*LogListItem)->LogIndex];
|
|
if (Log->Entries.Num() > 0)
|
|
{
|
|
LogsStartTime = FMath::Min(Log->Entries[0]->TimeStamp, LogsStartTime);
|
|
LogsEndTime = FMath::Max(Log->Entries[Log->Entries.Num() - 1]->TimeStamp, LogsEndTime);
|
|
}
|
|
}
|
|
}
|
|
|
|
Timeline->SetMinMaxValues(LogsStartTime, LogsEndTime);
|
|
// set zoom max so that single even on SBarLogs has desired size on maximum zoom
|
|
const float WidthPx = Timeline->GetDrawingGeometry().Size.X;
|
|
if (WidthPx > 0)
|
|
{
|
|
const float OldMaxZoom = MaxZoom;
|
|
const float PxPerTimeUnit = WidthPx * SLogBar::TimeUnit / (LogsEndTime - LogsStartTime);
|
|
MaxZoom = SLogBar::MaxUnitSizePx / PxPerTimeUnit;
|
|
if (MaxZoom < MinZoom)
|
|
{
|
|
MaxZoom = MinZoom;
|
|
}
|
|
|
|
ZoomSliderValue = MaxZoom * ZoomSliderValue / OldMaxZoom;
|
|
// update
|
|
}
|
|
|
|
RebuildFilteredList();
|
|
|
|
TimeTillNextUpdate = 1.f / FullUpdateFrequency;
|
|
InvalidateCanvas();
|
|
}
|
|
|
|
void SLogVisualizer::OnLogAdded()
|
|
{
|
|
// take last log
|
|
const int32 NewLogIndex = LogVisualizer->Logs.Num()-1;
|
|
|
|
TSharedPtr<FLogsListItem> Item;
|
|
for (int32 Index = 0; Index < LogsList.Num(); ++Index)
|
|
{
|
|
Item = LogsList[Index];
|
|
TArray<TSharedPtr<FActorsVisLog> >& Logs = LogVisualizer->Logs;
|
|
if (Item->Name == Logs[NewLogIndex]->Name.ToString())
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!Item.IsValid())
|
|
{
|
|
AddOrUpdateLog(NewLogIndex, LogVisualizer->Logs[NewLogIndex].Get());
|
|
}
|
|
|
|
RequestFullUpdate();
|
|
}
|
|
|
|
TSharedRef<ITableRow> SLogVisualizer::LogsListGenerateRow(TSharedPtr<FLogsListItem> InItem, const TSharedRef<STableViewBase>& OwnerTable)
|
|
{
|
|
return SNew(SLogsTableRow, OwnerTable)
|
|
.Item(InItem)
|
|
.OwnerVisualizerWidget(SharedThis(this));
|
|
}
|
|
|
|
void SLogVisualizer::LogsListSelectionChanged(TSharedPtr<FLogsListItem> SelectedItem, ESelectInfo::Type SelectInfo)
|
|
{
|
|
//@todo find log entry closest to current time selection
|
|
//LogEntryIndex = INDEX_NONE;
|
|
const int32 NewLogIndex = SelectedItem.IsValid() ? SelectedItem->LogIndex : INDEX_NONE;
|
|
if (NewLogIndex != SelectedLogIndex && NewLogIndex != INDEX_NONE)
|
|
{
|
|
SelectedLogIndex = NewLogIndex;
|
|
|
|
if (LogVisualizer->Logs.IsValidIndex(NewLogIndex))
|
|
{
|
|
TSharedPtr<FActorsVisLog> Log = LogVisualizer->Logs[NewLogIndex];
|
|
LogEntryIndex = Log->Entries.Num() - 1;
|
|
}
|
|
}
|
|
|
|
if (LogVisualizer->Logs.IsValidIndex(SelectedLogIndex))
|
|
{
|
|
if (USelection* SelectedActors = GEditor->GetSelectedActors())
|
|
{
|
|
TSharedPtr<FActorsVisLog> Log = LogVisualizer->Logs[SelectedLogIndex];
|
|
|
|
if (UWorld* World = GetWorld())
|
|
{
|
|
for (FConstPawnIterator Iterator = World->GetPawnIterator(); Iterator; ++Iterator)
|
|
{
|
|
if (APawn* CurrentPawn = *Iterator)
|
|
{
|
|
if (AController* CurrentController = CurrentPawn->GetController())
|
|
{
|
|
if (CurrentController->GetName() == Log->Name.ToString())
|
|
{
|
|
SelectedActors->Select(CurrentPawn);
|
|
}
|
|
else
|
|
{
|
|
SelectedActors->Deselect(CurrentPawn);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//SetCurrentViewedTime(CurrentViewedTime, /*bForce=*/true);
|
|
|
|
LogsLinesWidget->RequestListRefresh();
|
|
InvalidateCanvas();
|
|
}
|
|
|
|
TSharedRef<ITableRow> SLogVisualizer::LogEntryLinesGenerateRow(TSharedPtr<FLogEntryItem> Item, const TSharedRef<STableViewBase>& OwnerTable)
|
|
{
|
|
return SNew( STableRow< TSharedPtr<FString> >, OwnerTable )
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(FMargin(5.0f, 0.0f))
|
|
[
|
|
SNew(STextBlock)
|
|
.ColorAndOpacity(FSlateColor(Item->CategoryColor))
|
|
.Text(Item->Category)
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(FMargin(5.0f, 0.0f))
|
|
[
|
|
SNew(STextBlock)
|
|
.ColorAndOpacity(FSlateColor(FLinearColor::Gray))
|
|
.Text(FString(TEXT("(")) + FString(FOutputDevice::VerbosityToString(Item->Verbosity)) + FString(TEXT(")")))
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(FMargin(5.0f, 0.0f))
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(Item->Line)
|
|
]
|
|
];
|
|
}
|
|
/*
|
|
void SLogVisualizer::LogEntryLineSelectionChanged(TSharedPtr<FLogsListItem> SelectedItem, ESelectInfo::Type SelectInfo)
|
|
{
|
|
|
|
}
|
|
*/
|
|
bool SLogVisualizer::ShouldListLog(const TSharedPtr<FActorsVisLog>& Log)
|
|
{
|
|
//// Check log name filter
|
|
if (!Log.IsValid() || (LogNameFilterString.Len() > 0 && !Log->Name.ToString().Contains(LogNameFilterString)))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
UWorld* World = GetWorld();
|
|
if (!World || World->IsGameWorld())
|
|
{
|
|
return !bIgnoreTrivialLogs || Log->Entries.Num() >= 2;
|
|
}
|
|
else
|
|
{
|
|
static TArray<TSharedPtr<FVisLogEntry> > OutEntries;
|
|
GetVisibleEntries(Log, OutEntries);
|
|
return !bIgnoreTrivialLogs || OutEntries.Num() >= 2;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void SLogVisualizer::UpdateFilterInfo()
|
|
{
|
|
// get filters
|
|
LogNameFilterString = LogNameFilterBox->GetText().ToString();
|
|
}
|
|
|
|
void SLogVisualizer::SetCurrentViewedTime(float NewTime, const bool bForce)
|
|
{
|
|
if (CurrentViewedTime == NewTime && bForce == false)
|
|
{
|
|
return;
|
|
}
|
|
|
|
CurrentViewedTime = NewTime;
|
|
InvalidateCanvas();
|
|
}
|
|
|
|
void SLogVisualizer::InvalidateCanvas()
|
|
{
|
|
#if WITH_EDITOR
|
|
UEditorEngine *EEngine = Cast<UEditorEngine>(GEngine);
|
|
if (GIsEditor && EEngine != NULL)
|
|
{
|
|
for (int32 Index = 0; Index < EEngine->AllViewportClients.Num(); Index++)
|
|
{
|
|
FEditorViewportClient* ViewportClient = EEngine->AllViewportClients[Index];
|
|
if (ViewportClient)
|
|
{
|
|
ViewportClient->Invalidate();
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void SLogVisualizer::RequestShowLogEntry(TSharedPtr<FLogsListItem> Item, TSharedPtr<FVisLogEntry> LogEntry)
|
|
{
|
|
ShowLogEntry(Item, LogEntry);
|
|
}
|
|
|
|
void SLogVisualizer::ShowLogEntry(TSharedPtr<FLogsListItem> Item, TSharedPtr<FVisLogEntry> LogEntry)
|
|
{
|
|
if(LogsListWidget->GetSelectedItems().Find(Item) == INDEX_NONE)
|
|
{
|
|
LogsListWidget->ClearSelection();
|
|
LogsListWidget->SetItemSelection(Item, true);
|
|
}
|
|
|
|
if (LogVisualizer->Logs.IsValidIndex(SelectedLogIndex))
|
|
{
|
|
TSharedPtr<FActorsVisLog> Log = LogVisualizer->Logs[SelectedLogIndex];
|
|
LogEntryIndex = Log->Entries.Find(LogEntry);
|
|
}
|
|
else
|
|
{
|
|
LogEntryIndex = INDEX_NONE;
|
|
}
|
|
|
|
ShowEntry(LogEntry.Get());
|
|
}
|
|
|
|
FLinearColor SLogVisualizer::GetColorForUsedCategory(int32 Index)
|
|
{
|
|
if (Index >= 0 && Index < sizeof(ColorPalette) / sizeof(ColorPalette[0]))
|
|
{
|
|
return ColorPalette[Index];
|
|
}
|
|
|
|
static bool bReateColorList = false;
|
|
static FColorList StaticColor;
|
|
if (!bReateColorList)
|
|
{
|
|
bReateColorList = true;
|
|
StaticColor.CreateColorMap();
|
|
}
|
|
return StaticColor.GetFColorByIndex(Index);
|
|
}
|
|
|
|
TSharedRef<ITableRow> SLogVisualizer::HandleGenerateLogStatus(TSharedPtr<FLogStatusItem> InItem, const TSharedRef<STableViewBase>& OwnerTable)
|
|
{
|
|
if (InItem->Children.Num() > 0)
|
|
{
|
|
return SNew(STableRow<TSharedPtr<FLogStatusItem> >, OwnerTable)
|
|
[
|
|
SNew(STextBlock).Text(InItem->ItemText)
|
|
];
|
|
}
|
|
|
|
FString TooltipText = FString::Printf(TEXT("%s: %s"), *InItem->ItemText, *InItem->ValueText);
|
|
return SNew(STableRow<TSharedPtr<FLogStatusItem> >, OwnerTable)
|
|
[
|
|
SNew(SBorder)
|
|
.BorderImage( FEditorStyle::GetBrush("NoBorder") )
|
|
.ToolTipText(TooltipText)
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
[
|
|
SNew(STextBlock).Text(InItem->ItemText).ColorAndOpacity(FColorList::Aquamarine)
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.Padding(4.0f, 0, 0, 0)
|
|
.AutoWidth()
|
|
[
|
|
SNew(STextBlock).Text(InItem->ValueText)
|
|
]
|
|
]
|
|
];
|
|
}
|
|
|
|
void SLogVisualizer::OnLogStatusGetChildren(TSharedPtr<FLogStatusItem> InItem, TArray< TSharedPtr<FLogStatusItem> >& OutItems)
|
|
{
|
|
OutItems = InItem->Children;
|
|
}
|
|
|
|
void SLogVisualizer::UpdateStatusItems(const FVisLogEntry* LogEntry)
|
|
{
|
|
TArray<FString> ExpandedCategories;
|
|
for (int32 ItemIndex = 0; ItemIndex < StatusItems.Num(); ItemIndex++)
|
|
{
|
|
const bool bIsExpanded = StatusItemsView->IsItemExpanded(StatusItems[ItemIndex]);
|
|
if (bIsExpanded)
|
|
{
|
|
ExpandedCategories.Add(StatusItems[ItemIndex]->ItemText);
|
|
}
|
|
}
|
|
|
|
StatusItems.Empty();
|
|
|
|
if (LogEntry)
|
|
{
|
|
FString TimestampDesc = FString::Printf(TEXT("%.2fs"), LogEntry->TimeStamp);
|
|
StatusItems.Add(MakeShareable(new FLogStatusItem(LOCTEXT("VisLogTimestamp","Time").ToString(), TimestampDesc)));
|
|
|
|
for (int32 CategoryIndex = 0; CategoryIndex < LogEntry->Status.Num(); CategoryIndex++)
|
|
{
|
|
if (LogEntry->Status[CategoryIndex].Data.Num() <= 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
TSharedRef<FLogStatusItem> StatusItem = MakeShareable(new FLogStatusItem(LogEntry->Status[CategoryIndex].Category));
|
|
for (int32 LineIndex = 0; LineIndex < LogEntry->Status[CategoryIndex].Data.Num(); LineIndex++)
|
|
{
|
|
FString KeyDesc, ValueDesc;
|
|
const bool bHasValue = LogEntry->Status[CategoryIndex].GetDesc(LineIndex, KeyDesc, ValueDesc);
|
|
if (bHasValue)
|
|
{
|
|
StatusItem->Children.Add(MakeShareable(new FLogStatusItem(KeyDesc, ValueDesc)));
|
|
}
|
|
}
|
|
|
|
StatusItems.Add(StatusItem);
|
|
}
|
|
}
|
|
|
|
StatusItemsView->RequestTreeRefresh();
|
|
|
|
for (int32 ItemIndex = 0; ItemIndex < StatusItems.Num(); ItemIndex++)
|
|
{
|
|
for (const FString& Category : ExpandedCategories)
|
|
{
|
|
if (StatusItems[ItemIndex]->ItemText == Category)
|
|
{
|
|
StatusItemsView->SetItemExpansion(StatusItems[ItemIndex], true);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SLogVisualizer::ShowEntry(const FVisLogEntry* LogEntry)
|
|
{
|
|
UpdateStatusItems(LogEntry);
|
|
LogEntryLines.Reset();
|
|
|
|
const FVisLogEntry::FLogLine* LogLine = LogEntry->LogLines.GetTypedData();
|
|
for (int LineIndex = 0; LineIndex < LogEntry->LogLines.Num(); ++LineIndex, ++LogLine)
|
|
{
|
|
bool bShowLine = true;
|
|
|
|
if (FilterListPtr.IsValid())
|
|
{
|
|
FString CurrentCategory = LogLine->Category.ToString();
|
|
bShowLine = FilterListPtr->IsFilterEnabled(CurrentCategory, LogLine->Verbosity) && (bHistogramGraphsFilter || (QuickFilterText.Len() == 0 || CurrentCategory.Find(QuickFilterText) != INDEX_NONE));
|
|
|
|
}
|
|
|
|
if (bShowLine)
|
|
{
|
|
FLogEntryItem EntryItem;
|
|
EntryItem.Category = LogLine->Category.ToString();
|
|
|
|
int32 Index = UsedCategories.Find(EntryItem.Category);
|
|
if (Index == INDEX_NONE)
|
|
{
|
|
Index = UsedCategories.Add(EntryItem.Category);
|
|
}
|
|
EntryItem.CategoryColor = GetColorForUsedCategory(Index);
|
|
|
|
EntryItem.Verbosity = LogLine->Verbosity;
|
|
EntryItem.Line = LogLine->Line;
|
|
|
|
LogEntryLines.Add(MakeShareable(new FLogEntryItem(EntryItem)));
|
|
}
|
|
}
|
|
|
|
SetCurrentViewedTime(LogEntry->TimeStamp);
|
|
|
|
LogsLinesWidget->RequestListRefresh();
|
|
}
|
|
|
|
int32 SLogVisualizer::FindIndexInLogsList(const int32 LogIndex) const
|
|
{
|
|
for (int32 Index = 0; Index < LogsList.Num(); ++Index)
|
|
{
|
|
if (LogsList[Index]->LogIndex == LogIndex)
|
|
{
|
|
return Index;
|
|
}
|
|
}
|
|
|
|
return INDEX_NONE;
|
|
}
|
|
|
|
void SLogVisualizer::RebuildFilteredList()
|
|
{
|
|
// store current selection
|
|
TArray< TSharedPtr<FLogsListItem> > ItemsToSelect = LogsListWidget->GetSelectedItems();
|
|
|
|
for (int32 LogIndex = 0; LogIndex < LogVisualizer->Logs.Num(); ++LogIndex)
|
|
{
|
|
TSharedPtr<FActorsVisLog> Log = LogVisualizer->Logs[LogIndex];
|
|
if (ShouldListLog(Log))
|
|
{
|
|
// Passed filter so add to filtered results (defer sorting until end)
|
|
AddOrUpdateLog(LogIndex, Log.Get());
|
|
UpdateVisibleEntriesCache(Log, LogIndex);
|
|
}
|
|
}
|
|
|
|
// When underlying array changes, refresh list
|
|
LogsListWidget->RequestListRefresh();
|
|
LogsListWidget->RefreshList();
|
|
|
|
// redo selection
|
|
if (ItemsToSelect.Num() > 0)
|
|
{
|
|
TSharedPtr<FLogsListItem>* Item = ItemsToSelect.GetTypedData();
|
|
for (int32 ItemIndex = 0; ItemIndex < ItemsToSelect.Num(); ++ItemIndex, ++Item)
|
|
{
|
|
const int32 IndexInList = FindIndexInLogsList((*Item)->LogIndex);
|
|
if (IndexInList != INDEX_NONE)
|
|
{
|
|
LogsListWidget->SetItemSelection(LogsList[IndexInList], true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
float SLogVisualizer::GetZoomValue() const
|
|
{
|
|
return ZoomSliderValue;
|
|
}
|
|
|
|
void SLogVisualizer::OnSetZoomValue( float NewValue )
|
|
{
|
|
const float PrevZoom = GetZoom();
|
|
const float PrevVisibleRange = 1.0f / PrevZoom;
|
|
|
|
ZoomSliderValue = NewValue;
|
|
const float Zoom = GetZoom();
|
|
|
|
const float MaxOffset = GetMaxScrollOffsetFraction();
|
|
const float MaxGraphOffset = GetMaxGraphOffset();
|
|
|
|
const float ViewedTimeSpan = (LogsEndTime - LogsStartTime) / Zoom;
|
|
const float ScrollOffsetFraction = FMath::Clamp((CurrentViewedTime - LogsStartTime - ViewedTimeSpan/2) / (LogsEndTime - LogsStartTime), 0.0f, MaxOffset);
|
|
|
|
const float WidthPx = Timeline->GetDrawingGeometry().Size.X;
|
|
const float GraphOffset = MaxOffset > 0 ? (ScrollOffsetFraction / MaxOffset) * MaxGraphOffset : 0.f;
|
|
|
|
ZoomChangedNotify.Broadcast(Zoom, -GraphOffset);
|
|
|
|
ScrollBar->SetState( ScrollOffsetFraction, 1.0f / Zoom );
|
|
|
|
Timeline->SetZoom( Zoom );
|
|
Timeline->SetOffset( -GraphOffset );
|
|
|
|
ScrollbarOffset = -GraphOffset;
|
|
InvalidateCanvas();
|
|
}
|
|
|
|
void SLogVisualizer::OnZoomScrolled(float InScrollOffsetFraction)
|
|
{
|
|
if( ZoomSliderValue > 0.0f )
|
|
{
|
|
const float MaxOffset = GetMaxScrollOffsetFraction();
|
|
const float MaxGraphOffset = GetMaxGraphOffset();
|
|
InScrollOffsetFraction = FMath::Clamp( InScrollOffsetFraction, 0.0f, MaxOffset );
|
|
float GraphOffset = -( InScrollOffsetFraction / MaxOffset ) * MaxGraphOffset;
|
|
|
|
ScrollBar->SetState( InScrollOffsetFraction, 1.0f / GetZoom() );
|
|
|
|
ZoomChangedNotify.Broadcast(GetZoom(), GraphOffset);
|
|
|
|
Timeline->SetOffset( GraphOffset );
|
|
|
|
ScrollbarOffset = GraphOffset;
|
|
InvalidateCanvas();
|
|
}
|
|
}
|
|
|
|
void SLogVisualizer::OnSetHistogramWindowValue(float NewValue)
|
|
{
|
|
HistogramPreviewWindow = FMath::Clamp(HistogramPreviewWindow + NewValue * 1.0f, 0.0f, 100.0f);
|
|
HistogramWindowChangedNotify.Broadcast(HistogramPreviewWindow);
|
|
InvalidateCanvas();
|
|
}
|
|
|
|
void SLogVisualizer::OnDrawLogEntriesPathChanged(ESlateCheckBoxState::Type NewState)
|
|
{
|
|
bDrawLogEntriesPath = (NewState == ESlateCheckBoxState::Checked);
|
|
InvalidateCanvas();
|
|
}
|
|
|
|
ESlateCheckBoxState::Type SLogVisualizer::GetDrawLogEntriesPathState() const
|
|
{
|
|
return bDrawLogEntriesPath ? ESlateCheckBoxState::Checked : ESlateCheckBoxState::Unchecked;
|
|
}
|
|
|
|
void SLogVisualizer::OnIgnoreTrivialLogs(ESlateCheckBoxState::Type NewState)
|
|
{
|
|
bIgnoreTrivialLogs = (NewState == ESlateCheckBoxState::Checked);
|
|
DoFullUpdate();
|
|
}
|
|
|
|
ESlateCheckBoxState::Type SLogVisualizer::GetIgnoreTrivialLogs() const
|
|
{
|
|
return bIgnoreTrivialLogs ? ESlateCheckBoxState::Checked : ESlateCheckBoxState::Unchecked;
|
|
}
|
|
|
|
void SLogVisualizer::OnChangeHistogramLabelLocation(ESlateCheckBoxState::Type NewState)
|
|
{
|
|
bShowHistogramLabelsOutside = (NewState == ESlateCheckBoxState::Checked);
|
|
InvalidateCanvas();
|
|
}
|
|
|
|
ESlateCheckBoxState::Type SLogVisualizer::GetHistogramLabelLocation() const
|
|
{
|
|
return bShowHistogramLabelsOutside ? ESlateCheckBoxState::Checked : ESlateCheckBoxState::Unchecked;
|
|
}
|
|
|
|
void SLogVisualizer::OnStickToLastData(ESlateCheckBoxState::Type NewState)
|
|
{
|
|
bStickToLastData = (NewState == ESlateCheckBoxState::Checked);
|
|
InvalidateCanvas();
|
|
}
|
|
|
|
ESlateCheckBoxState::Type SLogVisualizer::GetStickToLastData() const
|
|
{
|
|
return bStickToLastData ? ESlateCheckBoxState::Checked : ESlateCheckBoxState::Unchecked;
|
|
}
|
|
|
|
void SLogVisualizer::OnToggleCamera(ESlateCheckBoxState::Type NewState)
|
|
{
|
|
UWorld* World = GetWorld();
|
|
if (ALogVisualizerCameraController::IsEnabled(World))
|
|
{
|
|
ALogVisualizerCameraController::DisableCamera(World);
|
|
}
|
|
else
|
|
{
|
|
ALogVisualizerCameraController::EnableCamera(World);
|
|
}
|
|
InvalidateCanvas();
|
|
}
|
|
|
|
ESlateCheckBoxState::Type SLogVisualizer::GetToggleCameraState() const
|
|
{
|
|
return ALogVisualizerCameraController::IsEnabled(GetWorld())
|
|
? ESlateCheckBoxState::Checked : ESlateCheckBoxState::Unchecked;
|
|
}
|
|
|
|
void SLogVisualizer::OnOffsetDataSets(ESlateCheckBoxState::Type NewState)
|
|
{
|
|
bOffsetDataSet = (NewState == ESlateCheckBoxState::Checked);
|
|
InvalidateCanvas();
|
|
}
|
|
|
|
ESlateCheckBoxState::Type SLogVisualizer::GetOffsetDataSets() const
|
|
{
|
|
return bOffsetDataSet ? ESlateCheckBoxState::Checked : ESlateCheckBoxState::Unchecked;
|
|
}
|
|
|
|
void SLogVisualizer::OnHistogramGraphsFilter(ESlateCheckBoxState::Type NewState)
|
|
{
|
|
bHistogramGraphsFilter = (NewState == ESlateCheckBoxState::Checked);
|
|
QuickFilterText.Empty();
|
|
QuickFilterBox->SetText(FText::FromString(QuickFilterText));
|
|
|
|
LogsList.Reset();
|
|
OutEntriesCached.Reset();
|
|
RebuildFilteredList();
|
|
|
|
if (LogVisualizer && LogVisualizer->Logs.IsValidIndex(SelectedLogIndex))
|
|
{
|
|
TSharedPtr<FActorsVisLog> Log = LogVisualizer->Logs[SelectedLogIndex];
|
|
if (Log.IsValid() && Log->Entries.IsValidIndex(LogEntryIndex))
|
|
{
|
|
ShowEntry(Log->Entries[LogEntryIndex].Get());
|
|
}
|
|
}
|
|
}
|
|
|
|
ESlateCheckBoxState::Type SLogVisualizer::GetHistogramGraphsFilter() const
|
|
{
|
|
return bHistogramGraphsFilter ? ESlateCheckBoxState::Checked : ESlateCheckBoxState::Unchecked;
|
|
}
|
|
|
|
//----------------------------------------------------------------------//
|
|
// Drawing
|
|
//----------------------------------------------------------------------//
|
|
void SLogVisualizer::DrawOnCanvas(UCanvas* Canvas, APlayerController*)
|
|
{
|
|
UWorld* World = GetWorld();
|
|
if (World != NULL && LogVisualizer->Logs.IsValidIndex(SelectedLogIndex))
|
|
{
|
|
TSharedPtr<FActorsVisLog> Log = LogVisualizer->Logs[SelectedLogIndex];
|
|
const TArray<TSharedPtr<FVisLogEntry> >& Entries = Log->Entries;
|
|
|
|
if (bDrawLogEntriesPath)
|
|
{
|
|
const TSharedPtr<FVisLogEntry>* Entry = Entries.GetTypedData();
|
|
FVector Location = (*Entry)->Location;
|
|
++Entry;
|
|
|
|
for (int32 Index = 1; Index < Entries.Num(); ++Index, ++Entry)
|
|
{
|
|
const FVector CurrentLocation = (*Entry)->Location;
|
|
DrawDebugLine(World, Location, CurrentLocation, FColor(160, 160, 240));
|
|
Location = CurrentLocation;
|
|
}
|
|
}
|
|
|
|
if (Entries.IsValidIndex(LogEntryIndex))
|
|
{
|
|
// draw all additional data stored in current entry
|
|
const TSharedPtr<FVisLogEntry>& Entry = Entries[LogEntryIndex];
|
|
|
|
// mark current location
|
|
DrawDebugCone(World, Entry->Location, /*Direction*/FVector(0, 0, 1), /*Length*/200.f
|
|
, PI/64, PI/64, /*NumSides*/16, FColor::Red);
|
|
|
|
UFont* Font = GEngine->GetSmallFont();
|
|
FCanvasTextItem TextItem( FVector2D::ZeroVector, FText::GetEmpty(), Font, FLinearColor::White );
|
|
const FString TimeStampString = FString::Printf(TEXT("%.2f"), Entry->TimeStamp);
|
|
const FVector EntryScreenLoc = Canvas->Project(Entry->Location);
|
|
Canvas->SetDrawColor(FColor::Black);
|
|
Canvas->DrawText(Font, TimeStampString,EntryScreenLoc.X+1, EntryScreenLoc.Y+1);
|
|
Canvas->SetDrawColor(FColor::White);
|
|
Canvas->DrawText(Font, TimeStampString, EntryScreenLoc.X, EntryScreenLoc.Y);
|
|
|
|
//let's draw histogram data
|
|
|
|
struct FGraphLineData
|
|
{
|
|
FName DataName;
|
|
TArray<FVector2D> Samples;
|
|
};
|
|
typedef TMap<FName, FGraphLineData > FGraphLines;
|
|
|
|
struct FGraphData
|
|
{
|
|
FGraphData() : Min(FVector2D(FLT_MAX, FLT_MAX)), Max(FVector2D(FLT_MIN, FLT_MIN)) {}
|
|
|
|
FVector2D Min, Max;
|
|
TMap<FName, FGraphLineData> GraphLines;
|
|
};
|
|
|
|
TMap<FName, FGraphData> CollectedGraphs;
|
|
|
|
float MinTime, MaxTime;
|
|
Timeline->GetMinMaxValues(MinTime, MaxTime);
|
|
|
|
const float StartTime = Timeline->GetOffset() + MinTime;
|
|
const float EndTime = StartTime + (MaxTime - MinTime) / this->GetZoom();
|
|
const float WindowHalfWidth = (EndTime - StartTime) * HistogramPreviewWindow * 0.01 * 0.5;
|
|
const FVector2D TimeStampWindow(Entry->TimeStamp - WindowHalfWidth, Entry->TimeStamp + WindowHalfWidth);
|
|
|
|
int32 ColorIndex = 0;
|
|
for (int32 EntryIndex = 0; EntryIndex < Entries.Num(); ++EntryIndex)
|
|
{
|
|
const TSharedPtr<FVisLogEntry>& CurrentEntry = Entries[EntryIndex];
|
|
if (HistogramPreviewWindow <= 0)
|
|
{
|
|
if (CurrentEntry->TimeStamp > Entry->TimeStamp)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (CurrentEntry->TimeStamp < TimeStampWindow.X)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (CurrentEntry->TimeStamp > TimeStampWindow.Y)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
const int32 SamplesNum = CurrentEntry->HistogramSamples.Num();
|
|
for (int32 SampleIndex = 0; SampleIndex < SamplesNum; ++SampleIndex)
|
|
{
|
|
FVisLogEntry::FHistogramSample CurrentSample = CurrentEntry->HistogramSamples[SampleIndex];
|
|
|
|
const FName CurrentCategory = CurrentSample.Category;
|
|
const FName CurrentGraphName = CurrentSample.GraphName;
|
|
const FName CurrentDataName = CurrentSample.DataName;
|
|
|
|
const bool bIsValidByFilter = FilterListPtr->IsFilterEnabled(CurrentSample.GraphName.ToString(), CurrentSample.DataName.ToString(), ELogVerbosity::All);
|
|
const bool bCurrentDataNamePassed = !bHistogramGraphsFilter || (QuickFilterText.Len() == 0 || CurrentDataName.ToString().Find(QuickFilterText) != INDEX_NONE);
|
|
|
|
|
|
if (!FilterListPtr.IsValid() || (bIsValidByFilter /*&& bCurrentCategoryPassed*/ && bCurrentDataNamePassed))
|
|
{
|
|
FGraphData &GraphData = CollectedGraphs.FindOrAdd(CurrentSample.GraphName);
|
|
FGraphLineData &LineData = GraphData.GraphLines.FindOrAdd(CurrentSample.DataName);
|
|
LineData.DataName = CurrentSample.DataName;
|
|
LineData.Samples.Add( CurrentSample.SampleValue );
|
|
|
|
GraphData.Min.X = FMath::Min(GraphData.Min.X, CurrentSample.SampleValue.X);
|
|
GraphData.Min.Y = FMath::Min(GraphData.Min.Y, CurrentSample.SampleValue.Y);
|
|
|
|
GraphData.Max.X = FMath::Max(GraphData.Max.X, CurrentSample.SampleValue.X);
|
|
GraphData.Max.Y = FMath::Max(GraphData.Max.Y, CurrentSample.SampleValue.Y);
|
|
}
|
|
}
|
|
}
|
|
|
|
const float GoldenRatioConjugate = 0.618033988749895f;
|
|
int32 GraphIndex = 0;
|
|
if (CollectedGraphs.Num() > 0)
|
|
{
|
|
const int NumberOfGraphs = CollectedGraphs.Num();
|
|
const int32 NumberOfColumns = FMath::CeilToInt(FMath::Sqrt(NumberOfGraphs));
|
|
int32 NumberOfRows = FMath::FloorToInt(NumberOfGraphs / NumberOfColumns);
|
|
if (NumberOfGraphs - NumberOfRows * NumberOfColumns > 0)
|
|
{
|
|
NumberOfRows += 1;
|
|
}
|
|
|
|
const int32 MaxNumberOfGraphs = FMath::Max(NumberOfRows, NumberOfColumns);
|
|
const float GraphWidth = 0.8f / NumberOfColumns;
|
|
const float GraphHeight = 0.8f / NumberOfRows;
|
|
|
|
const float XGraphSpacing = 0.2f / (MaxNumberOfGraphs + 1);
|
|
const float YGraphSpacing = 0.2f / (MaxNumberOfGraphs + 1);
|
|
|
|
const float StartX = XGraphSpacing;
|
|
float StartY = 0.5 + (0.5 * NumberOfRows - 1) * (GraphHeight + YGraphSpacing);
|
|
|
|
float CurrentX = StartX;
|
|
float CurrentY = StartY;
|
|
int32 GraphIndex = 0;
|
|
int32 CurrentColumn = 0;
|
|
int32 CurrentRow = 0;
|
|
for (auto It(CollectedGraphs.CreateIterator()); It; ++It)
|
|
{
|
|
TWeakObjectPtr<UReporterGraph> HistogramGraph = Canvas->GetReporterGraph();
|
|
if (!HistogramGraph.IsValid())
|
|
{
|
|
break;
|
|
}
|
|
HistogramGraph->SetNumGraphLines(It->Value.GraphLines.Num());
|
|
int32 LineIndex = 0;
|
|
UFont* Font = GEngine->GetSmallFont();
|
|
int32 MaxStringSize = 0;
|
|
float Hue = 0;// StartGoldenRatio[GraphIndex++];
|
|
|
|
auto& CategoriesForGraph = UsedGraphCategories.FindOrAdd(It->Key.ToString());
|
|
|
|
It->Value.GraphLines.KeySort(TLess<FName>());
|
|
for (auto LinesIt(It->Value.GraphLines.CreateConstIterator()); LinesIt; ++LinesIt)
|
|
{
|
|
const FString DataName = LinesIt->Value.DataName.ToString();
|
|
int32 CategoryIndex = CategoriesForGraph.Find(DataName);
|
|
if (CategoryIndex == INDEX_NONE)
|
|
{
|
|
CategoryIndex = CategoriesForGraph.AddUnique(DataName);
|
|
}
|
|
Hue = CategoryIndex * GoldenRatioConjugate;
|
|
if (Hue > 1)
|
|
{
|
|
Hue -= FMath::FloorToFloat(Hue);
|
|
}
|
|
|
|
HistogramGraph->GetGraphLine(LineIndex)->Color = FLinearColor::FGetHSV(Hue * 255, 0, 244);
|
|
HistogramGraph->GetGraphLine(LineIndex)->LineName = DataName;
|
|
HistogramGraph->GetGraphLine(LineIndex)->Data.Append(LinesIt->Value.Samples);
|
|
|
|
int32 DummyY, CurrentX;
|
|
StringSize(Font, CurrentX, DummyY, *LinesIt->Value.DataName.ToString());
|
|
MaxStringSize = CurrentX > MaxStringSize ? CurrentX : MaxStringSize;
|
|
|
|
++LineIndex;
|
|
}
|
|
|
|
FVector2D GraphSpaceSize;
|
|
GraphSpaceSize.Y = GraphSpaceSize.X = 0.8f / CollectedGraphs.Num();
|
|
|
|
HistogramGraph->SetGraphScreenSize(CurrentX, CurrentX + GraphWidth, CurrentY, CurrentY + GraphHeight);
|
|
CurrentX += GraphWidth + XGraphSpacing;
|
|
HistogramGraph->SetAxesMinMax(FVector2D(TimeStampWindow.X, It->Value.Min.Y), FVector2D(TimeStampWindow.Y, It->Value.Max.Y));
|
|
|
|
HistogramGraph->SetNumThresholds(0);
|
|
HistogramGraph->SetStyles(EGraphAxisStyle::Grid, EGraphDataStyle::Lines);
|
|
HistogramGraph->SetBackgroundColor( FColor(0,0,0, 200) );
|
|
HistogramGraph->SetLegendPosition(bShowHistogramLabelsOutside ? ELegendPosition::Outside : ELegendPosition::Inside);
|
|
HistogramGraph->OffsetDataSets(bOffsetDataSet);
|
|
HistogramGraph->bVisible = true;
|
|
HistogramGraph->Draw(Canvas);
|
|
|
|
++GraphIndex;
|
|
|
|
if (++CurrentColumn >= NumberOfColumns)
|
|
{
|
|
CurrentColumn = 0;
|
|
CurrentRow++;
|
|
|
|
CurrentX = StartX;
|
|
CurrentY -= GraphHeight + YGraphSpacing;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
const FVisLogEntry::FElementToDraw* ElementToDraw = Entry->ElementsToDraw.GetTypedData();
|
|
const int32 ElementsCount = Entry->ElementsToDraw.Num();
|
|
|
|
for (int32 ElementIndex = 0; ElementIndex < ElementsCount; ++ElementIndex, ++ElementToDraw)
|
|
{
|
|
if (FilterListPtr.IsValid() && !FilterListPtr->IsFilterEnabled(ElementToDraw->Category.ToString(), ElementToDraw->Verbosity))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const FColor Color = ElementToDraw->GetFColor();
|
|
Canvas->SetDrawColor(Color);
|
|
|
|
switch(ElementToDraw->GetType())
|
|
{
|
|
case FVisLogEntry::FElementToDraw::SinglePoint:
|
|
{
|
|
const float Radius = float(ElementToDraw->Radius);
|
|
const bool bDrawLabel = ElementToDraw->Description.IsEmpty() == false;
|
|
const FVector* Location = ElementToDraw->Points.GetTypedData();
|
|
for (int32 Index = 0; Index < ElementToDraw->Points.Num(); ++Index, ++Location)
|
|
{
|
|
DrawDebugSphere(World, *Location, Radius, 16, Color);
|
|
if (bDrawLabel)
|
|
{
|
|
const FVector ScreenLoc = Canvas->Project(*Location);
|
|
Canvas->DrawText(Font, FString::Printf(TEXT("%s_%d"), *ElementToDraw->Description, Index), ScreenLoc.X, ScreenLoc.Y);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case FVisLogEntry::FElementToDraw::Segment:
|
|
{
|
|
const float Thickness = float(ElementToDraw->Thicknes);
|
|
const bool bDrawLabel = ElementToDraw->Description.IsEmpty() == false && ElementToDraw->Points.Num() > 2;
|
|
const FVector* Location = ElementToDraw->Points.GetTypedData();
|
|
for (int32 Index = 0; Index + 1 < ElementToDraw->Points.Num(); Index += 2, Location += 2)
|
|
{
|
|
DrawDebugLine(World, *Location, *(Location + 1), Color
|
|
, /*bPersistentLines*/false, /*LifeTime*/-1
|
|
, /*DepthPriority*/0, Thickness);
|
|
|
|
if (bDrawLabel)
|
|
{
|
|
const FString PrintString = FString::Printf(TEXT("%s_%d"), *ElementToDraw->Description, Index);
|
|
float TextXL, TextYL;
|
|
Canvas->StrLen(Font, PrintString, TextXL, TextYL);
|
|
const FVector ScreenLoc = Canvas->Project(*Location + (*(Location+1)-*Location)/2);
|
|
Canvas->DrawText(Font, *PrintString, ScreenLoc.X - TextXL/2.0f, ScreenLoc.Y - TextYL/2.0f);
|
|
}
|
|
}
|
|
if (ElementToDraw->Description.IsEmpty() == false)
|
|
{
|
|
float TextXL, TextYL;
|
|
Canvas->StrLen(Font, ElementToDraw->Description, TextXL, TextYL);
|
|
const FVector ScreenLoc = Canvas->Project(ElementToDraw->Points[0]
|
|
+ (ElementToDraw->Points[1] - ElementToDraw->Points[0])/2);
|
|
Canvas->DrawText(Font, *ElementToDraw->Description, ScreenLoc.X - TextXL/2.0f, ScreenLoc.Y - TextYL/2.0f);
|
|
}
|
|
}
|
|
break;
|
|
case FVisLogEntry::FElementToDraw::Path:
|
|
{
|
|
const float Thickness = float(ElementToDraw->Thicknes);
|
|
FVector Location = ElementToDraw->Points[0];
|
|
for (int32 Index = 1; Index < ElementToDraw->Points.Num(); ++Index)
|
|
{
|
|
const FVector CurrentLocation = ElementToDraw->Points[Index];
|
|
DrawDebugLine(World, Location, CurrentLocation, Color
|
|
, /*bPersistentLines*/false, /*LifeTime*/-1
|
|
, /*DepthPriority*/0, Thickness);
|
|
Location = CurrentLocation;
|
|
}
|
|
}
|
|
break;
|
|
case FVisLogEntry::FElementToDraw::Box:
|
|
{
|
|
const float Thickness = float(ElementToDraw->Thicknes);
|
|
const bool bDrawLabel = ElementToDraw->Description.IsEmpty() == false && ElementToDraw->Points.Num() > 2;
|
|
const FVector* BoxExtent = ElementToDraw->Points.GetTypedData();
|
|
for (int32 Index = 0; Index + 1 < ElementToDraw->Points.Num(); Index += 2, BoxExtent += 2)
|
|
{
|
|
FBox Box(*BoxExtent, *(BoxExtent + 1));
|
|
DrawDebugBox(World, Box.GetCenter(), Box.GetExtent(), Color
|
|
, /*bPersistentLines*/false, /*LifeTime*/-1
|
|
, /*DepthPriority*/0/*, Thickness*/);
|
|
|
|
if (bDrawLabel)
|
|
{
|
|
const FString PrintString = FString::Printf(TEXT("%s_%d"), *ElementToDraw->Description, Index);
|
|
float TextXL, TextYL;
|
|
Canvas->StrLen(Font, PrintString, TextXL, TextYL);
|
|
const FVector ScreenLoc = Canvas->Project(Box.GetCenter());
|
|
Canvas->DrawText(Font, *PrintString, ScreenLoc.X - TextXL/2.0f, ScreenLoc.Y - TextYL/2.0f);
|
|
}
|
|
}
|
|
if (ElementToDraw->Description.IsEmpty() == false)
|
|
{
|
|
float TextXL, TextYL;
|
|
Canvas->StrLen(Font, ElementToDraw->Description, TextXL, TextYL);
|
|
const FVector ScreenLoc = Canvas->Project(ElementToDraw->Points[0]
|
|
+ (ElementToDraw->Points[1] - ElementToDraw->Points[0])/2);
|
|
Canvas->DrawText(Font, *ElementToDraw->Description, ScreenLoc.X - TextXL/2.0f, ScreenLoc.Y - TextYL/2.0f);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
const FSlateBrush* SLogVisualizer::GetRecordButtonBrush() const
|
|
{
|
|
if(LogVisualizer->IsRecording())
|
|
{
|
|
// If recording, show stop button
|
|
return FEditorStyle::GetBrush("LogVisualizer.Stop");
|
|
}
|
|
else
|
|
{
|
|
// If stopped, show record button
|
|
return FEditorStyle::GetBrush("LogVisualizer.Record");
|
|
}
|
|
}
|
|
|
|
FString SLogVisualizer::GetStatusText() const
|
|
{
|
|
return TEXT("");
|
|
}
|
|
|
|
ESlateCheckBoxState::Type SLogVisualizer::GetPauseState() const
|
|
{
|
|
UWorld* World = GetWorld();
|
|
return (World != NULL && (World->bPlayersOnly || World->bPlayersOnlyPending)) ? ESlateCheckBoxState::Checked : ESlateCheckBoxState::Unchecked;
|
|
}
|
|
|
|
FReply SLogVisualizer::OnRecordButtonClicked()
|
|
{
|
|
// Toggle recording state
|
|
LogVisualizer->SetIsRecording(!LogVisualizer->IsRecording());
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
FReply SLogVisualizer::OnLoad()
|
|
{
|
|
TArray<FString> OpenFilenames;
|
|
IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get();
|
|
bool bOpened = false;
|
|
if ( DesktopPlatform )
|
|
{
|
|
void* ParentWindowWindowHandle = NULL;
|
|
|
|
IMainFrameModule& MainFrameModule = FModuleManager::LoadModuleChecked<IMainFrameModule>(TEXT("MainFrame"));
|
|
const TSharedPtr<SWindow>& MainFrameParentWindow = MainFrameModule.GetParentWindow();
|
|
if ( MainFrameParentWindow.IsValid() && MainFrameParentWindow->GetNativeWindow().IsValid() )
|
|
{
|
|
ParentWindowWindowHandle = MainFrameParentWindow->GetNativeWindow()->GetOSWindowHandle();
|
|
}
|
|
|
|
bOpened = DesktopPlatform->OpenFileDialog(
|
|
ParentWindowWindowHandle,
|
|
LOCTEXT("OpenProjectBrowseTitle", "Open Project").ToString(),
|
|
LastBrowsePath,
|
|
TEXT(""),
|
|
LogVisualizer::FileTypes,
|
|
EFileDialogFlags::None,
|
|
OpenFilenames
|
|
);
|
|
}
|
|
|
|
if ( bOpened )
|
|
{
|
|
if ( OpenFilenames.Num() > 0 )
|
|
{
|
|
LastBrowsePath = OpenFilenames[0];
|
|
LoadFiles(OpenFilenames);
|
|
}
|
|
}
|
|
|
|
DoFullUpdate();
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
FReply SLogVisualizer::OnSave()
|
|
{
|
|
// Prompt the user for the filenames
|
|
TArray<FString> SaveFilenames;
|
|
IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get();
|
|
bool bSaved = false;
|
|
if ( DesktopPlatform )
|
|
{
|
|
void* ParentWindowWindowHandle = NULL;
|
|
|
|
IMainFrameModule& MainFrameModule = FModuleManager::LoadModuleChecked<IMainFrameModule>(TEXT("MainFrame"));
|
|
const TSharedPtr<SWindow>& MainFrameParentWindow = MainFrameModule.GetParentWindow();
|
|
if ( MainFrameParentWindow.IsValid() && MainFrameParentWindow->GetNativeWindow().IsValid() )
|
|
{
|
|
ParentWindowWindowHandle = MainFrameParentWindow->GetNativeWindow()->GetOSWindowHandle();
|
|
}
|
|
|
|
bSaved = DesktopPlatform->SaveFileDialog(
|
|
ParentWindowWindowHandle,
|
|
LOCTEXT("NewProjectBrowseTitle", "Choose a project location").ToString(),
|
|
LastBrowsePath,
|
|
TEXT(""),
|
|
LogVisualizer::FileTypes,
|
|
EFileDialogFlags::None,
|
|
SaveFilenames
|
|
);
|
|
}
|
|
|
|
if ( bSaved )
|
|
{
|
|
if ( SaveFilenames.Num() > 0 )
|
|
{
|
|
LastBrowsePath = SaveFilenames[0];
|
|
SaveSelectedLogs(SaveFilenames[0]);
|
|
/*CurrentProjectFilePath = FPaths::GetPath(FPaths::GetPath(SaveFilenames[0]));
|
|
CurrentProjectFileName = FPaths::GetBaseFilename(SaveFilenames[0]);*/
|
|
}
|
|
}
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
FReply SLogVisualizer::OnRemove()
|
|
{
|
|
TArray< TSharedPtr<FLogsListItem> > ItemsToRemove = LogsListWidget->GetSelectedItems();
|
|
if (ItemsToRemove.Num() > 0)
|
|
{
|
|
TArray<int32> IndicesToRemove;
|
|
IndicesToRemove.AddUninitialized(ItemsToRemove.Num());
|
|
|
|
for (int32 ListItemIndex = 0; ListItemIndex < ItemsToRemove.Num(); ++ListItemIndex)
|
|
{
|
|
IndicesToRemove[ListItemIndex] = ItemsToRemove[ListItemIndex]->LogIndex;
|
|
}
|
|
|
|
IndicesToRemove.Sort();
|
|
|
|
int32 PrevIdx = -1;
|
|
for (int32 LogToRemove = IndicesToRemove.Num() - 1; LogToRemove >= 0; --LogToRemove)
|
|
{
|
|
if (IndicesToRemove[LogToRemove] == PrevIdx)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
LogVisualizer->Logs.RemoveAtSwap(IndicesToRemove[LogToRemove], 1, false);
|
|
PrevIdx = IndicesToRemove[LogToRemove];
|
|
|
|
const int32 IndexInList = FindIndexInLogsList(IndicesToRemove[LogToRemove]);
|
|
if (IndexInList != INDEX_NONE)
|
|
{
|
|
LogsList.RemoveAtSwap(IndexInList);
|
|
}
|
|
}
|
|
|
|
LogsListWidget->ClearSelection();
|
|
|
|
LogsList.Reset();
|
|
OutEntriesCached.Reset();
|
|
RebuildFilteredList();
|
|
}
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
void SLogVisualizer::OnPauseChanged(ESlateCheckBoxState::Type NewState)
|
|
{
|
|
UWorld* World = GetWorld();
|
|
if (World != NULL)
|
|
{
|
|
if (NewState != ESlateCheckBoxState::Checked)
|
|
{
|
|
World->bPlayersOnly = false;
|
|
World->bPlayersOnlyPending = false;
|
|
|
|
ALogVisualizerCameraController::DisableCamera(World);
|
|
}
|
|
else
|
|
{
|
|
World->bPlayersOnlyPending = true;
|
|
// switch debug cam on
|
|
CameraController = ALogVisualizerCameraController::EnableCamera(World);
|
|
if (CameraController.IsValid())
|
|
{
|
|
CameraController->OnActorSelected = ALogVisualizerCameraController::FActorSelectedDelegate::CreateSP(
|
|
this, &SLogVisualizer::CameraActorSelected
|
|
);
|
|
CameraController->OnIterateLogEntries = ALogVisualizerCameraController::FLogEntryIterationDelegate::CreateSP(
|
|
this, &SLogVisualizer::IncrementCurrentLogIndex
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SLogVisualizer::CameraActorSelected(AActor* SelectedActor)
|
|
{
|
|
// find log corresponding to this Actor
|
|
if (SelectedActor == NULL || LogVisualizer == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
SelectActor(SelectedActor);
|
|
}
|
|
|
|
void SLogVisualizer::SelectActor(AActor* SelectedActor)
|
|
{
|
|
const AActor* LogOwner = SelectedActor->GetVisualLogRedirection();
|
|
const int32 LogIndex = LogVisualizer->GetLogIndexForActor(LogOwner);
|
|
if (LogVisualizer->Logs.IsValidIndex(LogIndex))
|
|
{
|
|
SelectedLogIndex = LogIndex;
|
|
|
|
// find item pointing to given log index
|
|
for (int32 ItemIndex = 0; ItemIndex < LogsList.Num(); ++ItemIndex)
|
|
{
|
|
if (LogsList[ItemIndex]->LogIndex == LogIndex)
|
|
{
|
|
TSharedPtr<FActorsVisLog> Log = LogVisualizer->Logs[SelectedLogIndex];
|
|
ShowLogEntry(LogsList[ItemIndex], Log->Entries[Log->Entries.Num()-1]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SLogVisualizer::OnQuickFilterTextChanged(const FText& CommentText, ETextCommit::Type CommitInfo)
|
|
{
|
|
QuickFilterText = CommentText.ToString();
|
|
OnLogCategoryFiltersChanged();
|
|
}
|
|
|
|
void SLogVisualizer::FilterTextCommitted(const FText& CommentText, ETextCommit::Type CommitInfo)
|
|
{
|
|
UpdateFilterInfo();
|
|
DoFullUpdate();
|
|
}
|
|
|
|
FString SLogVisualizer::GetLogEntryStatusText() const
|
|
{
|
|
return TEXT("Pause game with Pause button\nand select log entry to start viewing\nlog's content");
|
|
}
|
|
|
|
void SLogVisualizer::OnSortByChanged(const EColumnSortPriority::Type SortPriority, const FName& ColumnName, const EColumnSortMode::Type NewSortMode)
|
|
{
|
|
SortBy = ELogsSortMode::ByName;
|
|
|
|
if (ColumnName == NAME_StartTime)
|
|
{
|
|
SortBy = ELogsSortMode::ByStartTime;
|
|
}
|
|
else if (ColumnName == NAME_EndTime)
|
|
{
|
|
SortBy = ELogsSortMode::ByEndTime;
|
|
}
|
|
|
|
LogsList.Reset();
|
|
OutEntriesCached.Reset();
|
|
RebuildFilteredList();
|
|
}
|
|
|
|
EColumnSortMode::Type SLogVisualizer::GetLogsSortMode() const
|
|
{
|
|
return (SortBy == ELogsSortMode::ByName) ? EColumnSortMode::Ascending : EColumnSortMode::None;
|
|
}
|
|
|
|
void SLogVisualizer::LoadFiles(TArray<FString>& OpenFilenames)
|
|
{
|
|
for (int FilenameIndex = 0; FilenameIndex < OpenFilenames.Num(); ++FilenameIndex)
|
|
{
|
|
FArchive* FileAr = IFileManager::Get().CreateFileReader(*(OpenFilenames[FilenameIndex]));
|
|
if (FileAr != NULL)
|
|
{
|
|
TSharedPtr<FJsonObject> Object;
|
|
TSharedRef<TJsonReader<UCS2CHAR> > Reader = TJsonReader<UCS2CHAR>::Create(FileAr);
|
|
|
|
if (FJsonSerializer::Deserialize(Reader, Object))
|
|
{
|
|
TArray< TSharedPtr<FJsonValue> > JsonLogs = Object->GetArrayField(VisualLogJson::TAG_LOGS);
|
|
for (int32 LogIndex = 0; LogIndex < JsonLogs.Num(); ++LogIndex)
|
|
{
|
|
TSharedPtr<FJsonObject> JsonLogObject = JsonLogs[LogIndex]->AsObject();
|
|
if (JsonLogObject.IsValid() != false)
|
|
{
|
|
if (JsonLogObject->HasTypedField<EJson::String>(VisualLogJson::TAG_NAME))
|
|
{
|
|
TSharedPtr<FActorsVisLog> NewLog = MakeShareable(new FActorsVisLog(JsonLogs[LogIndex]));
|
|
LogVisualizer->AddLoadedLog(NewLog);
|
|
}
|
|
}
|
|
}
|
|
bIgnoreTrivialLogs = false;
|
|
}
|
|
|
|
FileAr->Close();
|
|
}
|
|
}
|
|
|
|
if (OpenFilenames.Num() > 0)
|
|
{
|
|
LogsList.Reset();
|
|
OutEntriesCached.Reset();
|
|
RebuildFilteredList();
|
|
}
|
|
}
|
|
|
|
void SLogVisualizer::SaveSelectedLogs(FString& Filename)
|
|
{
|
|
TSharedPtr<FJsonObject> Object = MakeShareable(new FJsonObject);
|
|
|
|
TArray< TSharedPtr<FJsonValue> > EntriesArray;
|
|
TArray< TSharedPtr<FLogsListItem> > ItemsToSave = LogsListWidget->GetSelectedItems();
|
|
if (ItemsToSave.Num() == 0)
|
|
{
|
|
// store all
|
|
ItemsToSave = LogsList;
|
|
}
|
|
|
|
EntriesArray.Reserve(ItemsToSave.Num());
|
|
|
|
|
|
TSharedPtr<FLogsListItem>* LogListItem = ItemsToSave.GetTypedData();
|
|
for (int32 ItemIndex = 0; ItemIndex < ItemsToSave.Num(); ++ItemIndex, ++LogListItem)
|
|
{
|
|
if (LogListItem->IsValid() && LogVisualizer->Logs.IsValidIndex((*LogListItem)->LogIndex))
|
|
{
|
|
TSharedPtr<FActorsVisLog> Log = LogVisualizer->Logs[(*LogListItem)->LogIndex];
|
|
EntriesArray.Add(Log->ToJson());
|
|
}
|
|
}
|
|
|
|
if (EntriesArray.Num() > 0)
|
|
{
|
|
Object->SetArrayField(VisualLogJson::TAG_LOGS, EntriesArray);
|
|
|
|
FArchive* FileAr = IFileManager::Get().CreateFileWriter(*Filename);
|
|
if (FileAr != NULL)
|
|
{
|
|
TSharedRef<TJsonWriter<UCS2CHAR> > Writer = TJsonWriter<UCS2CHAR>::Create(FileAr);
|
|
FJsonSerializer::Serialize( Object.ToSharedRef(), Writer );
|
|
FileAr->Close();
|
|
}
|
|
}
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|
|
|
|
#endif //ENABLE_VISUAL_LOG
|