Files
UnrealEngine/Engine/Source/Developer/LogVisualizer/Private/SVisualLogger.cpp
2025-05-18 13:04:45 +08:00

1266 lines
42 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SVisualLogger.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "HAL/FileManager.h"
#include "Misc/CommandLine.h"
#include "Modules/ModuleManager.h"
#include "Async/Future.h"
#include "Async/Async.h"
#include "Debug/DebugDrawService.h"
#include "AI/NavigationSystemBase.h"
#include "Engine/Engine.h"
#include "EngineUtils.h"
#include "VisualLogger/VisualLogger.h"
#include "LogVisualizerSettings.h"
#include "LogVisualizerSessionSettings.h"
#include "VisualLoggerDatabase.h"
#include "LogVisualizerStyle.h"
#include "VisualLoggerCommands.h"
#include "Widgets/Docking/SDockTab.h"
#include "SVisualLoggerToolbar.h"
#include "SVisualLoggerFilters.h"
#include "SVisualLoggerView.h"
#include "SVisualLoggerLogsList.h"
#include "SVisualLoggerStatusView.h"
#include "SVisualLoggerTab.h"
#include "VisualLoggerTimeSliderController.h"
#include "VisualLoggerRenderingActor.h"
#include "VisualLoggerCanvasRenderer.h"
#include "DesktopPlatformModule.h"
#include "VisualLoggerCameraController.h"
#include "Framework/Application/SlateApplication.h"
#if WITH_EDITOR
#include "Editor/EditorEngine.h"
#include "Editor.h"
#endif
#include "GameDelegates.h"
#include "ISettingsModule.h"
#include "VisualLogger/VisualLoggerBinaryFileDevice.h"
#include "VisualLogger/VisualLoggerFilterVolume.h"
#define LOCTEXT_NAMESPACE "SVisualLogger"
DEFINE_LOG_CATEGORY_STATIC(LogVisualLogger, Log, All);
/* Local constants
*****************************************************************************/
static const FName ToolbarTabId("Toolbar");
static const FName FiltersTabId("Filters");
static const FName MainViewTabId("MainView");
static const FName LogsListTabId("LogsList");
static const FName StatusViewTabId("StatusView");
namespace LogVisualizer
{
static const FString LogFileDescription = LOCTEXT("FileTypeDescription", "Visual Log File").ToString();
static const FString LoadFileTypes = FString::Printf(TEXT("%s (*.bvlog;*.%s)|*.bvlog;*.%s"), *LogFileDescription, VISLOG_FILENAME_EXT, VISLOG_FILENAME_EXT);
static const FString SaveFileTypes = FString::Printf(TEXT("%s (*.%s)|*.%s"), *LogFileDescription, VISLOG_FILENAME_EXT, VISLOG_FILENAME_EXT);
}
/* SMessagingDebugger constructors
*****************************************************************************/
namespace
{
static UWorld* GetWorldForGivenObject(const UObject* Object)
{
UWorld* World = GEngine->GetWorldFromContextObject(Object, EGetWorldErrorMode::ReturnNull);
#if WITH_EDITOR
UEditorEngine* EEngine = Cast<UEditorEngine>(GEngine);
if (GIsEditor && EEngine != nullptr && World == nullptr)
{
// lets use PlayWorld during PIE/Simulate and regular world from editor otherwise, to draw debug information
World = EEngine->PlayWorld != nullptr ? ToRawPtr(EEngine->PlayWorld) : EEngine->GetEditorWorldContext().World();
}
#endif
if (!GIsEditor && World == nullptr)
{
World = GEngine->GetWorld();
}
return World;
}
}
SVisualLogger::SVisualLogger()
: SCompoundWidget(), CommandList(MakeShareable(new FUICommandList))
{
bPausedLogger = false;
bGotHistogramData = false;
bAutoScrollToLastItem = false;
bAutoScrollingToLastItem = false;
class FVisualLoggerDevice : public FVisualLogDevice
{
public:
FVisualLoggerDevice(SVisualLogger& InVisualLogger) : VisualLoggerWidget(InVisualLogger) {}
virtual ~FVisualLoggerDevice(){}
virtual void Serialize(const UObject* InLogOwner, const FName& InOwnerName, const FName& InOwnerDisplayName, const FName& InOwnerClassName, const FVisualLogEntry& InLogEntry) override
{
VisualLoggerWidget.OnNewLogEntry(FVisualLogDevice::FVisualLogEntryItem(InOwnerName, InOwnerDisplayName, InOwnerClassName, InLogEntry));
}
SVisualLogger& VisualLoggerWidget;
};
InternalDevice = MakeShareable(new FVisualLoggerDevice(*this));
FVisualLogger::Get().AddDevice(InternalDevice.Get());
}
SVisualLogger::~SVisualLogger()
{
#if WITH_EDITOR
FWorldDelegates::OnPostWorldInitialization.Remove(PostWorldInitializationHandle);
FGameDelegates::Get().GetEndPlayMapDelegate().RemoveAll(this);
#endif
TabManager->CloseAllAreas();
TabManager->UnregisterAllTabSpawners();
GEngine->OnWorldAdded().RemoveAll(this);
FVisualLogger::Get().RemoveDevice(InternalDevice.Get());
InternalDevice.Reset();
#if WITH_EDITOR
GetMutableDefault<ULogVisualizerSettings>()->SavePersistentData();
GetMutableDefault<ULogVisualizerSettings>()->OnSettingChanged().RemoveAll(this);
#endif
if (LastUsedWorld.IsValid())
{
for (TActorIterator<AVisualLoggerRenderingActor> It(LastUsedWorld.Get()); It; ++It)
{
LastUsedWorld->DestroyActor(*It);
}
}
UDebugDrawService::Unregister(DrawOnCanvasDelegateHandle);
VisualLoggerCanvasRenderer.Reset();
FVisualLoggerDatabase::Get().GetEvents().OnRowSelectionChanged.RemoveAll(this);
FVisualLoggerDatabase::Get().GetEvents().OnNewItem.RemoveAll(this);
FVisualLoggerDatabase::Get().GetEvents().OnItemSelectionChanged.RemoveAll(this);
FLogVisualizer::Get().GetEvents().OnFiltersChanged.RemoveAll(this);
FLogVisualizer::Get().GetEvents().OnLogLineSelectionChanged.Unbind();
FLogVisualizer::Get().GetEvents().OnKeyboardEvent.Unbind();
GEngine->OnLevelActorAdded().RemoveAll(this);
GEngine->OnLevelActorDeleted().RemoveAll(this);
GEngine->OnActorMoved().RemoveAll(this);
FVisualLoggerDatabase::Get().Reset();
}
/* SMessagingDebugger interface
*****************************************************************************/
void SVisualLogger::Construct(const FArguments& InArgs, const TSharedRef<SDockTab>& ConstructUnderMajorTab, const TSharedPtr<SWindow>& ConstructUnderWindow)
{
bPausedLogger = false;
bGotHistogramData = false;
bAutoScrollToLastItem = false;
bAutoScrollingToLastItem = false;
FLogVisualizer::Get().SetCurrentVisualizer(SharedThis(this));
//////////////////////////////////////////////////////////////////////////
// Visual Logger Events
FLogVisualizer::Get().GetEvents().OnFiltersChanged.AddRaw(this, &SVisualLogger::OnFiltersChanged);
FLogVisualizer::Get().GetEvents().OnLogLineSelectionChanged = FOnLogLineSelectionChanged::CreateSP(this, &SVisualLogger::OnLogLineSelectionChanged);
FLogVisualizer::Get().GetEvents().OnKeyboardEvent = FOnKeyboardEvent::CreateSP(this, &SVisualLogger::OnKeyboardRedirection);
FLogVisualizer::Get().GetTimeSliderController().Get()->GetTimeSliderArgs().OnScrubPositionChanged = FVisualLoggerTimeSliderArgs::FOnScrubPositionChanged::CreateSP(this, &SVisualLogger::OnScrubPositionChanged);
FVisualLoggerDatabase::Get().GetEvents().OnRowSelectionChanged.AddRaw(this, &SVisualLogger::OnObjectSelectionChanged);
FVisualLoggerDatabase::Get().GetEvents().OnNewItem.AddRaw(this, &SVisualLogger::OnNewItemHandler);
FVisualLoggerDatabase::Get().GetEvents().OnItemSelectionChanged.AddRaw(this, &SVisualLogger::OnItemsSelectionChanged);
LastUsedWorld = FVisualLoggerEditorInterface::Get()->GetWorld();
CollectFilterVolumes();
ProcessFilterVolumes();
#if WITH_EDITOR
PostWorldInitializationHandle = FWorldDelegates::OnPostWorldInitialization.AddLambda([this](UWorld*, const UWorld::InitializationValues)
{
UWorld* World = FVisualLoggerEditorInterface::Get()->GetWorld();
if (World != nullptr && World != LastUsedWorld)
{
OnNewWorld(World);
}
});
// Note that we use EndPlay delegate instead of FEditorDelegates::EndPie since we want
// the teardown of the PlayWorld to be completed so FVisualLoggerEditorInterface will return
// us the Editor world (if any) as the new world.
FGameDelegates::Get().GetEndPlayMapDelegate().AddLambda([this]()
{
UWorld* World = FVisualLoggerEditorInterface::Get()->GetWorld();
if (World != nullptr && World != LastUsedWorld)
{
OnNewWorld(World);
}
});
GetMutableDefault<ULogVisualizerSettings>()->OnSettingChanged().AddRaw(this, &SVisualLogger::OnSettingsChanged);
#endif
GEngine->OnWorldAdded().AddRaw(this, &SVisualLogger::OnNewWorld);
GEngine->OnLevelActorAdded().AddRaw(this, &SVisualLogger::OnLevelActorAdded);
GEngine->OnLevelActorDeleted().AddRaw(this, &SVisualLogger::OnLevelActorDeleted);
GEngine->OnActorMoved().AddRaw(this, &SVisualLogger::OnActorMoved);
//////////////////////////////////////////////////////////////////////////
// Command Action Lists
const FVisualLoggerCommands& Commands = FVisualLoggerCommands::Get();
FUICommandList& ActionList = *CommandList;
ULogVisualizerSettings* Settings = GetMutableDefault<ULogVisualizerSettings>();
Settings->ConfigureVisLog();
Settings->LoadPersistentData();
ActionList.MapAction(Commands.StartRecording, FExecuteAction::CreateSP(this, &SVisualLogger::HandleStartRecordingCommandExecute), FCanExecuteAction::CreateSP(this, &SVisualLogger::HandleStartRecordingCommandCanExecute), FIsActionChecked(), FIsActionButtonVisible::CreateSP(this, &SVisualLogger::HandleStartRecordingCommandIsVisible));
ActionList.MapAction(Commands.StopRecording, FExecuteAction::CreateSP(this, &SVisualLogger::HandleStopRecordingCommandExecute), FCanExecuteAction::CreateSP(this, &SVisualLogger::HandleStopRecordingCommandCanExecute), FIsActionChecked(), FIsActionButtonVisible::CreateSP(this, &SVisualLogger::HandleStopRecordingCommandIsVisible));
ActionList.MapAction(Commands.Pause, FExecuteAction::CreateSP(this, &SVisualLogger::HandlePauseCommandExecute), FCanExecuteAction::CreateSP(this, &SVisualLogger::HandlePauseCommandCanExecute), FIsActionChecked(), FIsActionButtonVisible::CreateSP(this, &SVisualLogger::HandlePauseCommandIsVisible));
ActionList.MapAction(Commands.Resume, FExecuteAction::CreateSP(this, &SVisualLogger::HandleResumeCommandExecute), FCanExecuteAction::CreateSP(this, &SVisualLogger::HandleResumeCommandCanExecute), FIsActionChecked(), FIsActionButtonVisible::CreateSP(this, &SVisualLogger::HandleResumeCommandIsVisible));
ActionList.MapAction(Commands.LoadFromVLog, FExecuteAction::CreateSP(this, &SVisualLogger::HandleLoadCommandExecute), FCanExecuteAction::CreateSP(this, &SVisualLogger::HandleLoadCommandCanExecute), FIsActionChecked(), FIsActionButtonVisible::CreateSP(this, &SVisualLogger::HandleLoadCommandCanExecute));
ActionList.MapAction(Commands.SaveToVLog, FExecuteAction::CreateSP(this, &SVisualLogger::HandleSaveCommandExecute), FCanExecuteAction::CreateSP(this, &SVisualLogger::HandleSaveCommandCanExecute), FIsActionChecked(), FIsActionButtonVisible::CreateSP(this, &SVisualLogger::HandleSaveCommandCanExecute));
ActionList.MapAction(Commands.SaveAllToVLog, FExecuteAction::CreateSP(this, &SVisualLogger::HandleSaveAllCommandExecute), FCanExecuteAction::CreateSP(this, &SVisualLogger::HandleSaveCommandCanExecute), FIsActionChecked(), FIsActionButtonVisible::CreateSP(this, &SVisualLogger::HandleSaveCommandCanExecute));
ActionList.MapAction(Commands.FreeCamera,
FExecuteAction::CreateSP(this, &SVisualLogger::HandleCameraCommandExecute),
FCanExecuteAction::CreateSP(this, &SVisualLogger::HandleCameraCommandCanExecute),
FIsActionChecked::CreateSP(this, &SVisualLogger::HandleCameraCommandIsChecked),
FIsActionButtonVisible::CreateSP(this, &SVisualLogger::HandleCameraCommandCanExecute));
ActionList.MapAction(Commands.ToggleGraphs,
FExecuteAction::CreateLambda([](){bool& bEnableGraphsVisualization = ULogVisualizerSessionSettings::StaticClass()->GetDefaultObject<ULogVisualizerSessionSettings>()->bEnableGraphsVisualization; bEnableGraphsVisualization = !bEnableGraphsVisualization; }),
FCanExecuteAction::CreateLambda([this]()->bool{return FVisualLoggerGraphsDatabase::Get().ContainsHistogramGraphs(); }),
FIsActionChecked::CreateLambda([]()->bool{return ULogVisualizerSessionSettings::StaticClass()->GetDefaultObject<ULogVisualizerSessionSettings>()->bEnableGraphsVisualization; }),
FIsActionButtonVisible::CreateLambda([this]()->bool{return FVisualLoggerGraphsDatabase::Get().ContainsHistogramGraphs(); }));
ActionList.MapAction(Commands.ResetData,
FExecuteAction::CreateSP(this, &SVisualLogger::ResetData),
FCanExecuteAction::CreateSP(this, &SVisualLogger::HandleSaveCommandCanExecute),
FIsActionChecked(),
FIsActionButtonVisible::CreateSP(this, &SVisualLogger::HandleSaveCommandCanExecute));
ActionList.MapAction(Commands.Refresh,
FExecuteAction::CreateSP(this, &SVisualLogger::HandleRefreshCommandExecute),
FCanExecuteAction::CreateSP(this, &SVisualLogger::HandleRefreshCommandCanExecute),
FIsActionChecked(),
FIsActionButtonVisible::CreateSP(this, &SVisualLogger::HandleRefreshCommandCanExecute));
ActionList.MapAction(Commands.AutoScroll,
FExecuteAction::CreateSP(this, &SVisualLogger::HandleAutoScrollCommandExecute),
FCanExecuteAction(),
FIsActionChecked::CreateSP(this, &SVisualLogger::HandleAutoScrollIsChecked),
FIsActionButtonVisible());
// Tab Spawners
TabManager = FGlobalTabmanager::Get()->NewTabManager(ConstructUnderMajorTab);
TSharedRef<FWorkspaceItem> AppMenuGroup = TabManager->AddLocalWorkspaceMenuCategory(LOCTEXT("VisualLoggerGroupName", "Visual Logger"));
TabManager->RegisterTabSpawner(ToolbarTabId, FOnSpawnTab::CreateSP(this, &SVisualLogger::HandleTabManagerSpawnTab, ToolbarTabId))
.SetDisplayName(LOCTEXT("ToolbarTabTitle", "Toolbar"))
.SetGroup(AppMenuGroup)
.SetIcon(FSlateIcon(FLogVisualizerStyle::Get().GetStyleSetName(), "ToolbarTabIcon"));
TabManager->RegisterTabSpawner(FiltersTabId, FOnSpawnTab::CreateSP(this, &SVisualLogger::HandleTabManagerSpawnTab, FiltersTabId))
.SetDisplayName(LOCTEXT("FiltersTabTitle", "Filters"))
.SetGroup(AppMenuGroup)
.SetIcon(FSlateIcon(FLogVisualizerStyle::Get().GetStyleSetName(), "FiltersTabIcon"));
TabManager->RegisterTabSpawner(MainViewTabId, FOnSpawnTab::CreateSP(this, &SVisualLogger::HandleTabManagerSpawnTab, MainViewTabId))
.SetDisplayName(LOCTEXT("MainViewTabTitle", "MainView"))
.SetGroup(AppMenuGroup)
.SetIcon(FSlateIcon(FLogVisualizerStyle::Get().GetStyleSetName(), "MainViewTabIcon"));
TabManager->RegisterTabSpawner(LogsListTabId, FOnSpawnTab::CreateSP(this, &SVisualLogger::HandleTabManagerSpawnTab, LogsListTabId))
.SetDisplayName(LOCTEXT("LogsListTabTitle", "LogsList"))
.SetGroup(AppMenuGroup)
.SetIcon(FSlateIcon(FLogVisualizerStyle::Get().GetStyleSetName(), "LogsListTabIcon"));
TabManager->RegisterTabSpawner(StatusViewTabId, FOnSpawnTab::CreateSP(this, &SVisualLogger::HandleTabManagerSpawnTab, StatusViewTabId))
.SetDisplayName(LOCTEXT("StatusViewTabTitle", "StatusView"))
.SetGroup(AppMenuGroup)
.SetIcon(FSlateIcon(FLogVisualizerStyle::Get().GetStyleSetName(), "StatusViewTabIcon"));
// Default Layout
const TSharedRef<FTabManager::FLayout> Layout = FTabManager::NewLayout("VisualLoggerLayout_v1.0")
->AddArea
(
FTabManager::NewPrimaryArea()
->SetOrientation(Orient_Vertical)
->Split
(
FTabManager::NewStack()
->AddTab(ToolbarTabId, ETabState::OpenedTab)
->SetHideTabWell(true)
)
->Split
(
FTabManager::NewStack()
->AddTab(FiltersTabId, ETabState::OpenedTab)
->SetHideTabWell(true)
)
->Split
(
FTabManager::NewStack()
->AddTab(MainViewTabId, ETabState::OpenedTab)
->SetHideTabWell(true)
)
->Split
(
FTabManager::NewSplitter()
->SetOrientation(Orient_Horizontal)
->SetSizeCoefficient(0.6f)
->Split
(
FTabManager::NewStack()
->AddTab(StatusViewTabId, ETabState::OpenedTab)
->SetHideTabWell(true)
->SetSizeCoefficient(0.3f)
)
->Split
(
FTabManager::NewStack()
->AddTab(LogsListTabId, ETabState::OpenedTab)
->SetHideTabWell(true)
->SetSizeCoefficient(0.7f)
)
)
);
TabManager->SetOnPersistLayout(FTabManager::FOnPersistLayout::CreateSP(this, &SVisualLogger::HandleTabManagerPersistLayout));
// Window Menu
FMenuBarBuilder MenuBarBuilder = FMenuBarBuilder(TSharedPtr<FUICommandList>());
MenuBarBuilder.AddPullDownMenu(
LOCTEXT("WindowMenuLabel", "Window"),
FText::GetEmpty(),
FNewMenuDelegate::CreateStatic(&SVisualLogger::FillWindowMenu, TabManager),
"Window"
);
MenuBarBuilder.AddMenuEntry(
LOCTEXT("SettingsMenuLabel", "Settings"),
FText::GetEmpty(),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateLambda(
[this](){
ISettingsModule* SettingsModule = FModuleManager::GetModulePtr<ISettingsModule>("Settings");
if (SettingsModule != nullptr)
{
SettingsModule->ShowViewer("Editor", "Advanced", "VisualLogger");
}
}
)),
"Settings"
);
ChildSlot
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
MenuBarBuilder.MakeWidget()
]
+ SVerticalBox::Slot()
.FillHeight(1.0f)
[
TabManager->RestoreFrom(Layout, ConstructUnderWindow).ToSharedRef()
]
];
VisualLoggerCanvasRenderer = MakeShareable(new FVisualLoggerCanvasRenderer());
DrawOnCanvasDelegateHandle = UDebugDrawService::Register(TEXT("VisLog"), FDebugDrawDelegate::CreateRaw(VisualLoggerCanvasRenderer.Get(), &FVisualLoggerCanvasRenderer::DrawOnCanvas));
Cast<AVisualLoggerRenderingActor>(FVisualLoggerEditorInterface::Get()->GetHelperActor(LastUsedWorld.Get()));
}
void SVisualLogger::OnNewLogEntry(const FVisualLogDevice::FVisualLogEntryItem& Entry)
{
if (bPausedLogger)
{
OnPauseCacheForEntries.Add(Entry);
return;
}
FVisualLoggerDatabase::Get().AddItem(Entry);
}
void SVisualLogger::HandleMajorTabPersistVisualState()
{
// save any settings here
}
void SVisualLogger::HandleTabManagerPersistLayout(const TSharedRef<FTabManager::FLayout>& LayoutToSave)
{
// save any layout here
}
void SVisualLogger::FillWindowMenu(FMenuBuilder& MenuBuilder, const TSharedPtr<FTabManager> TabManager)
{
if (!TabManager.IsValid())
{
return;
}
TabManager->PopulateLocalTabSpawnerMenu(MenuBuilder);
}
TSharedRef<SDockTab> SVisualLogger::HandleTabManagerSpawnTab(const FSpawnTabArgs& Args, FName TabIdentifier) const
{
TSharedPtr<SWidget> TabWidget = SNullWidget::NullWidget;
bool AutoSizeTab = false;
if (TabIdentifier == ToolbarTabId)
{
TabWidget = SNew(SVisualLoggerToolbar, CommandList);
AutoSizeTab = true;
}
else if (TabIdentifier == FiltersTabId)
{
TabWidget = SAssignNew(VisualLoggerFilters, SVisualLoggerFilters, CommandList);
AutoSizeTab = true;
}
else if (TabIdentifier == MainViewTabId)
{
TabWidget = SAssignNew(MainView, SVisualLoggerView, CommandList).OnFiltersSearchChanged(const_cast<SVisualLogger*>(this), &SVisualLogger::OnFiltersSearchChanged);
AutoSizeTab = false;
}
else if (TabIdentifier == LogsListTabId)
{
TabWidget = SAssignNew(LogsList, SVisualLoggerLogsList, CommandList);
AutoSizeTab = false;
}
else if (TabIdentifier == StatusViewTabId)
{
TabWidget = SAssignNew(StatusView, SVisualLoggerStatusView, CommandList);
AutoSizeTab = false;
}
check(TabWidget.IsValid());
return SNew(SVisualLoggerTab)
.ShouldAutosize(AutoSizeTab)
.TabRole(ETabRole::DocumentTab)
[
TabWidget.ToSharedRef()
];
}
bool SVisualLogger::HandleStartRecordingCommandCanExecute() const
{
return !FVisualLogger::Get().IsRecording();
}
void SVisualLogger::HandleStartRecordingCommandExecute()
{
FVisualLogger::Get().SetIsRecording(true);
}
bool SVisualLogger::HandleStartRecordingCommandIsVisible() const
{
return !FVisualLogger::Get().IsRecording();
}
bool SVisualLogger::HandleStopRecordingCommandCanExecute() const
{
return FVisualLogger::Get().IsRecording();
}
void SVisualLogger::HandleStopRecordingCommandExecute()
{
UWorld* World = FLogVisualizer::Get().GetWorld();
if (FParse::Param(FCommandLine::Get(), TEXT("LogNavOctree")) == true && GetDefault<ULogVisualizerSettings>()->bLogNavOctreeOnStop)
{
FVisualLogger::NavigationDataDump(World, LogNavigation, ELogVerbosity::Log, FBox());
}
FVisualLogger::Get().SetIsRecording(false);
if (AVisualLoggerCameraController::IsEnabled(World))
{
AVisualLoggerCameraController::DisableCamera(World);
}
if (bPausedLogger)
{
HandleResumeCommandExecute();
}
}
bool SVisualLogger::HandleStopRecordingCommandIsVisible() const
{
return FVisualLogger::Get().IsRecording();
}
bool SVisualLogger::HandlePauseCommandCanExecute() const
{
return !bPausedLogger;
}
void SVisualLogger::HandlePauseCommandExecute()
{
if (GetDefault<ULogVisualizerSettings>()->bUsePlayersOnlyForPause)
{
const TIndirectArray<FWorldContext>& WorldContexts = GEngine->GetWorldContexts();
for (const FWorldContext& Context : WorldContexts)
{
if (Context.World() != nullptr)
{
Context.World()->bPlayersOnlyPending = true;
}
}
}
bPausedLogger = true;
}
bool SVisualLogger::HandlePauseCommandIsVisible() const
{
return HandlePauseCommandCanExecute();
}
bool SVisualLogger::HandleResumeCommandCanExecute() const
{
return bPausedLogger;
}
void SVisualLogger::HandleResumeCommandExecute()
{
if (GetDefault<ULogVisualizerSettings>()->bUsePlayersOnlyForPause)
{
const TIndirectArray<FWorldContext>& WorldContexts = GEngine->GetWorldContexts();
for (const FWorldContext& Context : WorldContexts)
{
if (Context.World() != nullptr)
{
Context.World()->bPlayersOnly = false;
Context.World()->bPlayersOnlyPending = false;
}
}
}
bPausedLogger = false;
for (const auto& CurrentEntry : OnPauseCacheForEntries)
{
OnNewLogEntry(CurrentEntry);
}
OnPauseCacheForEntries.Reset();
}
bool SVisualLogger::HandleResumeCommandIsVisible() const
{
return HandleResumeCommandCanExecute();
}
bool SVisualLogger::HandleCameraCommandIsChecked() const
{
UWorld* World = FLogVisualizer::Get().GetWorld();
return World && AVisualLoggerCameraController::IsEnabled(World);
}
bool SVisualLogger::HandleCameraCommandCanExecute() const
{
UWorld* World = FLogVisualizer::Get().GetWorld();
return FVisualLogger::Get().IsRecording() && World && (World->bPlayersOnly || World->bPlayersOnlyPending) && World->IsPlayInEditor() && (GEditor && !GEditor->bIsSimulatingInEditor);
}
void SVisualLogger::HandleCameraCommandExecute()
{
UWorld* World = FLogVisualizer::Get().GetWorld();
if (AVisualLoggerCameraController::IsEnabled(World))
{
AVisualLoggerCameraController::DisableCamera(World);
}
else
{
// switch debug cam on
CameraController = AVisualLoggerCameraController::EnableCamera(World);
}
}
bool SVisualLogger::HandleRefreshCommandCanExecute() const
{
UWorld* World = FLogVisualizer::Get().GetWorld();
return FVisualLogger::Get().IsRecording() && World && World->IsEditorWorld();
}
void SVisualLogger::HandleRefreshCommandExecute()
{
FVisualLogger::Get().Flush();
}
bool SVisualLogger::HandleLoadCommandCanExecute() const
{
return true;
}
void SVisualLogger::HandleAutoScrollCommandExecute()
{
bAutoScrollToLastItem = !bAutoScrollToLastItem;
if (bAutoScrollToLastItem)
{
TGuardValue<bool> Guard(bAutoScrollingToLastItem, true);
FLogVisualizer::Get().GotoLastItemAnyRow();
}
}
bool SVisualLogger::HandleAutoScrollIsChecked() const
{
return bAutoScrollToLastItem;
}
void SVisualLogger::HandleLoadCommandExecute()
{
FArchive Ar;
TArray<FVisualLogDevice::FVisualLogEntryItem> RecordedLogs;
TArray<FString> OpenFilenames;
IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get();
bool bOpened = false;
if (DesktopPlatform)
{
const FString DefaultBrowsePath = FString::Printf(TEXT("%slogs/"), *FPaths::ProjectSavedDir());
bOpened = DesktopPlatform->OpenFileDialog(
FSlateApplication::Get().FindBestParentWindowHandleForDialogs(AsShared()),
LOCTEXT("OpenProjectBrowseTitle", "Open Project").ToString(),
DefaultBrowsePath,
TEXT(""),
LogVisualizer::LoadFileTypes,
EFileDialogFlags::None,
OpenFilenames
);
}
if (bOpened && OpenFilenames.Num() > 0)
{
OnNewWorld(GetWorldForGivenObject(nullptr));
for (int FilenameIndex = 0; FilenameIndex < OpenFilenames.Num(); ++FilenameIndex)
{
FString CurrentFileName = OpenFilenames[FilenameIndex];
const bool bIsBinaryFile = CurrentFileName.Find(TEXT(".bvlog")) != INDEX_NONE;
if (bIsBinaryFile)
{
FArchive* FileAr = IFileManager::Get().CreateFileReader(*CurrentFileName);
FVisualLoggerHelpers::Serialize(*FileAr, RecordedLogs);
FileAr->Close();
delete FileAr;
FileAr = NULL;
for (FVisualLogDevice::FVisualLogEntryItem& CurrentItem : RecordedLogs)
{
OnNewLogEntry(CurrentItem);
}
}
}
}
}
bool SVisualLogger::HandleSaveCommandCanExecute() const
{
return FVisualLoggerDatabase::Get().NumberOfRows() > 0;
}
void SVisualLogger::HandleSaveAllCommandExecute()
{
HandleSaveCommand(true);
}
void SVisualLogger::HandleSaveCommandExecute()
{
HandleSaveCommand(false);
}
void SVisualLogger::HandleSaveCommand(bool bSaveAllData)
{
TArray<FName> SelectedRows;
if (!bSaveAllData)
{
SelectedRows = FVisualLoggerDatabase::Get().GetSelectedRows();
}
else
{
for (auto Iter(FVisualLoggerDatabase::Get().GetConstRowIterator()); Iter; ++Iter)
{
SelectedRows.Add((*Iter).GetOwnerName());
}
}
if (SelectedRows.Num())
{
// Prompt the user for the filenames
TArray<FString> SaveFilenames;
IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get();
bool bSaved = false;
if (DesktopPlatform)
{
const FString DefaultBrowsePath = FString::Printf(TEXT("%slogs/"), *FPaths::ProjectSavedDir());
bSaved = DesktopPlatform->SaveFileDialog(
FSlateApplication::Get().FindBestParentWindowHandleForDialogs(AsShared()),
LOCTEXT("NewProjectBrowseTitle", "Choose a project location").ToString(),
DefaultBrowsePath,
TEXT(""),
LogVisualizer::SaveFileTypes,
EFileDialogFlags::None,
SaveFilenames
);
}
if (bSaved)
{
if (SaveFilenames.Num() > 0 && SaveFilenames[0].IsEmpty() == false)
{
TArray<FVisualLogDevice::FVisualLogEntryItem> FrameCache;
for (const FName& CurrentName : SelectedRows)
{
FVisualLoggerDBRow& DataRow = FVisualLoggerDatabase::Get().GetRowByName(CurrentName);
FrameCache.Append(DataRow.GetItems());
}
if (FrameCache.Num())
{
FArchive* FileArchive = IFileManager::Get().CreateFileWriter(*SaveFilenames[0]);
if (ensure(FileArchive))
{
FVisualLoggerHelpers::Serialize(*FileArchive, FrameCache);
FileArchive->Close();
delete FileArchive;
FileArchive = NULL;
}
else
{
UE_LOG(LogVisualLogger, Error, TEXT("Failed to create file \"%s\""), *SaveFilenames[0]);
}
}
}
}
}
}
void SVisualLogger::ResetData()
{
bGotHistogramData = false;
OnPauseCacheForEntries.Reset();
FLogVisualizer::Get().Reset();
FVisualLoggerDatabase::Get().Reset();
FVisualLoggerFilters::Get().Reset();
if (MainView.IsValid())
{
MainView->ResetData();
}
if (LogsList.IsValid())
{
LogsList->ResetData();
}
if (StatusView.IsValid())
{
StatusView->ResetData();
}
if (VisualLoggerCanvasRenderer.IsValid())
{
VisualLoggerCanvasRenderer->ResetData();
}
if (AVisualLoggerRenderingActor* HelperActor = Cast<AVisualLoggerRenderingActor>(FVisualLoggerEditorInterface::Get()->GetHelperActor(LastUsedWorld.Get())))
{
HelperActor->ResetRendering();
}
const TMap<FName, FVisualLogExtensionInterface*>& AllExtensions = FVisualLogger::Get().GetAllExtensions();
for (auto Iterator = AllExtensions.CreateConstIterator(); Iterator; ++Iterator)
{
FVisualLogExtensionInterface* Extension = (*Iterator).Value;
if (Extension != NULL)
{
Extension->ResetData(FVisualLoggerEditorInterface::Get());
}
}
FLogVisualizer::Get().GetEvents().OnLogLineSelectionChanged = FOnLogLineSelectionChanged::CreateSP(this, &SVisualLogger::OnLogLineSelectionChanged);
FLogVisualizer::Get().GetEvents().OnKeyboardEvent = FOnKeyboardEvent::CreateSP(this, &SVisualLogger::OnKeyboardRedirection);
FLogVisualizer::Get().GetTimeSliderController().Get()->GetTimeSliderArgs().OnScrubPositionChanged = FVisualLoggerTimeSliderArgs::FOnScrubPositionChanged::CreateSP(this, &SVisualLogger::OnScrubPositionChanged);
}
void SVisualLogger::CollectFilterVolumes()
{
FilterVolumesInLastUsedWorld.Reset();
for (TActorIterator<AActor> It(LastUsedWorld.Get(), AVisualLoggerFilterVolume::StaticClass()); It; ++It)
{
AVisualLoggerFilterVolume* Volume = Cast<AVisualLoggerFilterVolume>(*It);
FilterVolumesInLastUsedWorld.Emplace(Volume);
}
}
void SVisualLogger::ProcessFilterVolumes()
{
FilterBoxes.Reset(FilterVolumesInLastUsedWorld.Num());
for (TWeakObjectPtr<const AVisualLoggerFilterVolume>& WeakVolume : FilterVolumesInLastUsedWorld)
{
if (const AVisualLoggerFilterVolume* Volume = WeakVolume.Get())
{
FilterBoxes.Add(Volume->GetBounds().GetBox());
}
}
}
void SVisualLogger::OnLevelActorAdded(AActor* Actor)
{
if (AVisualLoggerFilterVolume* Volume = Cast<AVisualLoggerFilterVolume>(Actor))
{
FilterVolumesInLastUsedWorld.AddUnique(Volume);
ProcessFilterVolumes();
OnFiltersChanged();
}
}
void SVisualLogger::OnLevelActorDeleted(AActor* Actor)
{
if (AVisualLoggerFilterVolume* Volume = Cast<AVisualLoggerFilterVolume>(Actor))
{
FilterVolumesInLastUsedWorld.Remove(Volume);
ProcessFilterVolumes();
OnFiltersChanged();
}
}
void SVisualLogger::OnActorMoved(AActor* Actor)
{
if (AVisualLoggerFilterVolume* Volume = Cast<AVisualLoggerFilterVolume>(Actor))
{
ProcessFilterVolumes();
OnFiltersChanged();
}
}
void SVisualLogger::OnSettingsChanged(FName PropertyName)
{
if (PropertyName == GET_MEMBER_NAME_CHECKED(ULogVisualizerSettings, bUseFilterVolumes))
{
// Note that we don't need to collect & process the volumes since their bookkeeping
// is not conditional to the flag, only the filtering.
OnFiltersChanged();
}
else if (PropertyName == GET_MEMBER_NAME_CHECKED(ULogVisualizerSettings, bSearchInsideLogs))
{
OnFiltersChanged();
}
}
void SVisualLogger::OnNewWorld(UWorld* NewWorld)
{
LastUsedWorld = NewWorld;
// Only reset data when activating a new game world, not when returning to the Editor world (i.e. after PIE)
if (!IsValid(NewWorld) ||
(NewWorld->IsGameWorld() && GetDefault<ULogVisualizerSettings>()->bResetDataWithNewSession))
{
ResetData();
}
CollectFilterVolumes();
ProcessFilterVolumes();
AVisualLoggerRenderingActor* HelperActor = Cast<AVisualLoggerRenderingActor>(FVisualLoggerEditorInterface::Get()->GetHelperActor(LastUsedWorld.Get()));
if (ensure(HelperActor != nullptr))
{
// reset data and simulate row/item selection to recreate rendering proxy with correct data
HelperActor->ResetRendering();
const TArray<FName>& SelectedRows = FVisualLoggerDatabase::Get().GetSelectedRows();
HelperActor->ObjectSelectionChanged(SelectedRows);
for (auto& RowName : SelectedRows)
{
FVisualLoggerDBRow& DBRow = FVisualLoggerDatabase::Get().GetRowByName(RowName);
HelperActor->OnItemSelectionChanged(DBRow, DBRow.GetCurrentItemIndex());
}
}
}
void SVisualLogger::OnObjectSelectionChanged(const TArray<FName>& RowNames)
{
const double ScrubTime = FLogVisualizer::Get().GetTimeSliderController().Get()->GetTimeSliderArgs().ScrubPosition.Get();
for (auto RowName : RowNames)
{
FVisualLoggerDBRow& DBRow = FVisualLoggerDatabase::Get().GetRowByName(RowName);
if (DBRow.GetCurrentItemIndex() == INDEX_NONE)
{
DBRow.MoveTo(DBRow.GetClosestItem(ScrubTime, ScrubTime));
}
}
}
void SVisualLogger::OnItemsSelectionChanged(const FVisualLoggerDBRow& ChangedRow, int32 SelectedItemIndex)
{
const TMap<FName, FVisualLogExtensionInterface*>& AllExtensions = FVisualLogger::Get().GetAllExtensions();
for (auto Iterator = AllExtensions.CreateConstIterator(); Iterator; ++Iterator)
{
FVisualLogExtensionInterface* Extension = (*Iterator).Value;
if (Extension != NULL)
{
Extension->OnItemsSelectionChanged(FVisualLoggerEditorInterface::Get());
}
}
}
void SVisualLogger::OnFiltersChanged()
{
const uint32 StartCycles = FPlatformTime::Cycles();
TArray<TFuture<void> > AllFutures;
for (auto Iterator = FVisualLoggerDatabase::Get().GetRowIterator(); Iterator; ++Iterator)
{
FVisualLoggerDBRow* DBRow = &(*Iterator);
AllFutures.Add(
Async(EAsyncExecution::TaskGraph, [this, DBRow]()
{
const TArray<FVisualLogDevice::FVisualLogEntryItem>& Entries = DBRow->GetItems();
for (int32 Index = 0; Index < Entries.Num(); ++Index)
{
UpdateVisibilityForEntry(*DBRow, Index);
}
}
));
}
bool bAllFuturesReady = false;
do
{
bAllFuturesReady = true;
for (TFuture<void>& CurrentFuture : AllFutures)
{
bAllFuturesReady &= CurrentFuture.IsReady();
if (bAllFuturesReady == false)
{
break;
}
}
if (bAllFuturesReady == false)
{
FPlatformProcess::Sleep(0.01);
}
} while (bAllFuturesReady != true);
for (auto Iterator = FVisualLoggerDatabase::Get().GetRowIterator(); Iterator; ++Iterator)
{
FVisualLoggerDBRow& DBRow = *Iterator;
FVisualLoggerDatabase::Get().SetRowVisibility(DBRow.GetOwnerName(), DBRow.IsRowVisible());
}
const int32 BlockingCycles = int32(FPlatformTime::Cycles() - StartCycles);
{
const TArray<FName>& SelectedRows = FVisualLoggerDatabase::Get().GetSelectedRows();
const double ScrubTime = FLogVisualizer::Get().GetTimeSliderController()->GetTimeSliderArgs().ScrubPosition.Get();
{
for (auto RowName : SelectedRows)
{
auto& DBRow = FVisualLoggerDatabase::Get().GetRowByName(RowName);
const int32 ClosestItem = DBRow.GetClosestItem(ScrubTime, ScrubTime);
const TArray<FVisualLogDevice::FVisualLogEntryItem>& Items = DBRow.GetItems();
if (Items.IsValidIndex(ClosestItem) && Items[ClosestItem].Entry.TimeStamp <= ScrubTime)
{
DBRow.MoveTo(ClosestItem);
}
}
}
}
UE_LOG(LogVisualLogger, Display, TEXT("SVisualLogger::OnFiltersChanged: %5.2fms"), FPlatformTime::ToMilliseconds(BlockingCycles));
}
void SVisualLogger::OnFiltersSearchChanged(const FText& Filter)
{
const uint32 StartCycles = FPlatformTime::Cycles();
FVisualLoggerFilters::Get().SetSearchString(Filter.ToString());
TArray<TFuture<void> > AllFutures;
for (auto Iterator = FVisualLoggerDatabase::Get().GetRowIterator(); Iterator; ++Iterator)
{
FVisualLoggerDBRow* DBRow = &(*Iterator);
AllFutures.Add(
Async(EAsyncExecution::TaskGraph, [this, DBRow]()
{
const TArray<FVisualLogDevice::FVisualLogEntryItem>& Entries = DBRow->GetItems();
for (int32 Index = 0; Index < Entries.Num(); ++Index)
{
UpdateVisibilityForEntry(*DBRow, Index);
}
}
)
);
}
bool bAllFuturesReady = false;
do
{
bAllFuturesReady = true;
for (TFuture<void>& CurrentFuture : AllFutures)
{
bAllFuturesReady &= CurrentFuture.IsReady();
if (bAllFuturesReady == false)
{
break;
}
}
if (bAllFuturesReady == false)
{
FPlatformProcess::Sleep(0.01);
}
} while (bAllFuturesReady != true);
for (auto Iterator = FVisualLoggerDatabase::Get().GetRowIterator(); Iterator; ++Iterator)
{
FVisualLoggerDBRow& DBRow = *Iterator;
FVisualLoggerDatabase::Get().SetRowVisibility(DBRow.GetOwnerName(), DBRow.IsRowVisible());
}
if (LogsList.IsValid())
{
// it depends on rows visibility so it have to be called here, manually after changes to rows visibilities
LogsList->OnFiltersSearchChanged(Filter);
}
if (VisualLoggerCanvasRenderer.IsValid())
{
VisualLoggerCanvasRenderer->DirtyCachedData();
}
const int32 BlockingCycles = int32(FPlatformTime::Cycles() - StartCycles);
UE_LOG(LogVisualLogger, Display, TEXT("SVisualLogger::OnFiltersSearchChanged: %5.2fms"), FPlatformTime::ToMilliseconds(BlockingCycles));
}
void SVisualLogger::OnNewItemHandler(const FVisualLoggerDBRow& DBRow, int32 ItemIndex)
{
UpdateVisibilityForEntry(DBRow, ItemIndex);
// A new item can be hidden by the category filters by UpdateVisibilityForEntry.
// In such case, we might need to update row visibility/
// Note that this is not called within UpdateVisibilityForEntry since it can be called by async tasks.
FVisualLoggerDatabase::Get().SetRowVisibility(DBRow.GetOwnerName(), DBRow.IsRowVisible());
if (bAutoScrollToLastItem)
{
TGuardValue<bool> Guard(bAutoScrollingToLastItem, true);
FLogVisualizer::Get().GotoLastItemAnyRow();
}
}
void SVisualLogger::UpdateVisibilityForEntry(const FVisualLoggerDBRow& DBRow, int32 ItemIndex)
{
const ULogVisualizerSettings* Settings = GetDefault<ULogVisualizerSettings>();
const FVisualLogDevice::FVisualLogEntryItem& CurrentEntry = DBRow.GetItems()[ItemIndex];
const FString SearchString = FVisualLoggerFilters::Get().GetSearchString();
TArray<FVisualLoggerCategoryVerbosityPair> OutCategories;
FVisualLoggerHelpers::GetCategories(CurrentEntry.Entry, OutCategories);
bool bPassingFilter = false;
for (FVisualLoggerCategoryVerbosityPair& CategoryPair : OutCategories)
{
if (FVisualLoggerFilters::Get().ShouldDisplayCategory(CategoryPair.CategoryName, CategoryPair.Verbosity))
{
bPassingFilter = true;
break;
}
}
if (bPassingFilter && Settings->bUseFilterVolumes && FilterBoxes.Num() > 0 && CurrentEntry.Entry.bIsLocationValid)
{
bool bIsInsideAnyFilterVolume = false;
for (const FBox& Box : FilterBoxes)
{
if (Box.IsInside(CurrentEntry.Entry.Location))
{
bIsInsideAnyFilterVolume = true;
break;
}
}
bPassingFilter &= bIsInsideAnyFilterVolume;
}
if (bPassingFilter && Settings->bSearchInsideLogs && SearchString.Len() > 0)
{
bPassingFilter = false;
for (const FVisualLogLine& CurrentLine : CurrentEntry.Entry.LogLines)
{
if (CurrentLine.Line.Find(SearchString) != INDEX_NONE || CurrentLine.Category.ToString().Find(SearchString) != INDEX_NONE)
{
bPassingFilter = true;
break;
}
}
if (!bPassingFilter)
{
for (const FVisualLogEvent& CurrentEvent : CurrentEntry.Entry.Events)
{
if (CurrentEvent.Name.Find(SearchString) != INDEX_NONE)
{
bPassingFilter = true;
break;
}
}
}
}
FVisualLoggerDatabase::Get().GetRowByName(DBRow.GetOwnerName()).SetItemVisibility(ItemIndex, bPassingFilter);
}
void SVisualLogger::OnLogLineSelectionChanged(TSharedPtr<struct FLogEntryItem> SelectedItem, int64 UserData, FName TagName)
{
const TMap<FName, FVisualLogExtensionInterface*>& AllExtensions = FVisualLogger::Get().GetAllExtensions();
for (auto Iterator = AllExtensions.CreateConstIterator(); Iterator; ++Iterator)
{
FVisualLogExtensionInterface* Extension = (*Iterator).Value;
if (Extension != NULL)
{
Extension->OnLogLineSelectionChanged(FVisualLoggerEditorInterface::Get(), SelectedItem, UserData);
}
}
}
void SVisualLogger::OnScrubPositionChanged(double NewScrubPosition, bool bScrubbing)
{
const TArray<FName> &SelectedRows = FVisualLoggerDatabase::Get().GetSelectedRows();
const double ScrubTime = FLogVisualizer::Get().GetTimeSliderController()->GetTimeSliderArgs().ScrubPosition.Get();
for (auto RowName : SelectedRows)
{
auto& DBRow = FVisualLoggerDatabase::Get().GetRowByName(RowName);
const int32 ClosestItem = SelectedRows.Num() > 1 ? DBRow.GetClosestItem(NewScrubPosition, ScrubTime) : DBRow.GetClosestItem(NewScrubPosition);
const TArray<FVisualLogDevice::FVisualLogEntryItem>& Items = DBRow.GetItems();
if (Items.IsValidIndex(ClosestItem) && Items[ClosestItem].Entry.TimeStamp <= NewScrubPosition)
{
DBRow.MoveTo(ClosestItem);
}
}
const TMap<FName, FVisualLogExtensionInterface*>& AllExtensions = FVisualLogger::Get().GetAllExtensions();
for (auto Iterator = AllExtensions.CreateConstIterator(); Iterator; ++Iterator)
{
FVisualLogExtensionInterface* Extension = (*Iterator).Value;
if (Extension != NULL)
{
Extension->OnScrubPositionChanged(FVisualLoggerEditorInterface::Get(), NewScrubPosition, bScrubbing);
}
}
// Cancel auto-scrolling on scrub (except if it comes from within and the actual bAutoScroll behavior, which is guarded by bAutoScrollingToLastItem)
if (!bAutoScrollingToLastItem)
{
bAutoScrollToLastItem = false;
}
}
FReply SVisualLogger::OnKeyboardRedirection(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent)
{
FReply ReturnValue = FReply::Unhandled();
const TArray<FName>& SelectedRows = FVisualLoggerDatabase::Get().GetSelectedRows();
if (SelectedRows.Num() == 0)
{
return ReturnValue;
}
// find time to move by
const FKey Key = InKeyEvent.GetKey();
if (Key == EKeys::Left || Key == EKeys::Right)
{
const double ScrubTime = FLogVisualizer::Get().GetTimeSliderController()->GetTimeSliderArgs().ScrubPosition.Get();
double NewTimeToSet = ScrubTime;
double BestTimeDifference = MAX_dbl;
const int32 MoveDist = InKeyEvent.IsLeftControlDown() ? InKeyEvent.IsLeftShiftDown() ? 20 : 10 : 1;
for (auto RowName : SelectedRows)
{
const FVisualLoggerDBRow& DBRow = FVisualLoggerDatabase::Get().GetRowByName(RowName);
const int32 CurrentItemIndex = DBRow.GetCurrentItemIndex();
if (CurrentItemIndex == INDEX_NONE)
{
continue;
}
if (Key == EKeys::Right)
{
double TimeDifference = DBRow.GetCurrentItem().Entry.TimeStamp - ScrubTime;
if (TimeDifference > 0 && FMath::Abs(TimeDifference) < FMath::Abs(BestTimeDifference))
{
BestTimeDifference = TimeDifference;
NewTimeToSet = DBRow.GetCurrentItem().Entry.TimeStamp;
}
const int32 NextItemIndex = FLogVisualizer::Get().GetNextItem(RowName, MoveDist);
TimeDifference = DBRow.GetItems()[NextItemIndex].Entry.TimeStamp - ScrubTime;
if (TimeDifference > 0 && FMath::Abs(TimeDifference) < FMath::Abs(BestTimeDifference))
{
BestTimeDifference = TimeDifference;
NewTimeToSet = DBRow.GetItems()[NextItemIndex].Entry.TimeStamp;
}
}
else if (Key == EKeys::Left)
{
double TimeDifference = DBRow.GetCurrentItem().Entry.TimeStamp - ScrubTime;
if (TimeDifference < 0 && FMath::Abs(TimeDifference) < FMath::Abs(BestTimeDifference))
{
BestTimeDifference = TimeDifference;
NewTimeToSet = DBRow.GetCurrentItem().Entry.TimeStamp;
}
const int32 PrevItemIndex = FLogVisualizer::Get().GetPreviousItem(RowName, MoveDist);
TimeDifference = DBRow.GetItems()[PrevItemIndex].Entry.TimeStamp - ScrubTime;
if (TimeDifference < 0 && FMath::Abs(TimeDifference) > 0 && FMath::Abs(TimeDifference) < FMath::Abs(BestTimeDifference))
{
BestTimeDifference = TimeDifference;
NewTimeToSet = DBRow.GetItems()[PrevItemIndex].Entry.TimeStamp;
}
}
}
FLogVisualizer::Get().GetTimeSliderController()->CommitScrubPosition(NewTimeToSet, false);
ReturnValue = FReply::Handled();
}
FName OwnerName = SelectedRows[SelectedRows.Num() - 1];
const FVisualLoggerDBRow& DBRow = FVisualLoggerDatabase::Get().GetRowByName(OwnerName);
if (DBRow.GetCurrentItemIndex() != INDEX_NONE)
{
if (Key == EKeys::Home)
{
FLogVisualizer::Get().GotoFirstItem(OwnerName);
ReturnValue = FReply::Handled();
}
else if (Key == EKeys::End)
{
FLogVisualizer::Get().GotoLastItem(OwnerName);
ReturnValue = FReply::Handled();
}
else if (Key == EKeys::Enter)
{
FLogVisualizer::Get().UpdateCameraPosition(OwnerName, DBRow.GetCurrentItemIndex());
ReturnValue = FReply::Handled();
}
}
return ReturnValue;
}
#undef LOCTEXT_NAMESPACE