// 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(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()->SavePersistentData(); GetMutableDefault()->OnSettingChanged().RemoveAll(this); #endif if (LastUsedWorld.IsValid()) { for (TActorIterator 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& ConstructUnderMajorTab, const TSharedPtr& 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()->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(); 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()->bEnableGraphsVisualization; bEnableGraphsVisualization = !bEnableGraphsVisualization; }), FCanExecuteAction::CreateLambda([this]()->bool{return FVisualLoggerGraphsDatabase::Get().ContainsHistogramGraphs(); }), FIsActionChecked::CreateLambda([]()->bool{return ULogVisualizerSessionSettings::StaticClass()->GetDefaultObject()->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 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 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()); 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("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(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& LayoutToSave) { // save any layout here } void SVisualLogger::FillWindowMenu(FMenuBuilder& MenuBuilder, const TSharedPtr TabManager) { if (!TabManager.IsValid()) { return; } TabManager->PopulateLocalTabSpawnerMenu(MenuBuilder); } TSharedRef SVisualLogger::HandleTabManagerSpawnTab(const FSpawnTabArgs& Args, FName TabIdentifier) const { TSharedPtr 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(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()->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()->bUsePlayersOnlyForPause) { const TIndirectArray& 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()->bUsePlayersOnlyForPause) { const TIndirectArray& 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 Guard(bAutoScrollingToLastItem, true); FLogVisualizer::Get().GotoLastItemAnyRow(); } } bool SVisualLogger::HandleAutoScrollIsChecked() const { return bAutoScrollToLastItem; } void SVisualLogger::HandleLoadCommandExecute() { FArchive Ar; TArray RecordedLogs; TArray 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 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 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 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(FVisualLoggerEditorInterface::Get()->GetHelperActor(LastUsedWorld.Get()))) { HelperActor->ResetRendering(); } const TMap& 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 It(LastUsedWorld.Get(), AVisualLoggerFilterVolume::StaticClass()); It; ++It) { AVisualLoggerFilterVolume* Volume = Cast(*It); FilterVolumesInLastUsedWorld.Emplace(Volume); } } void SVisualLogger::ProcessFilterVolumes() { FilterBoxes.Reset(FilterVolumesInLastUsedWorld.Num()); for (TWeakObjectPtr& WeakVolume : FilterVolumesInLastUsedWorld) { if (const AVisualLoggerFilterVolume* Volume = WeakVolume.Get()) { FilterBoxes.Add(Volume->GetBounds().GetBox()); } } } void SVisualLogger::OnLevelActorAdded(AActor* Actor) { if (AVisualLoggerFilterVolume* Volume = Cast(Actor)) { FilterVolumesInLastUsedWorld.AddUnique(Volume); ProcessFilterVolumes(); OnFiltersChanged(); } } void SVisualLogger::OnLevelActorDeleted(AActor* Actor) { if (AVisualLoggerFilterVolume* Volume = Cast(Actor)) { FilterVolumesInLastUsedWorld.Remove(Volume); ProcessFilterVolumes(); OnFiltersChanged(); } } void SVisualLogger::OnActorMoved(AActor* Actor) { if (AVisualLoggerFilterVolume* Volume = Cast(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()->bResetDataWithNewSession)) { ResetData(); } CollectFilterVolumes(); ProcessFilterVolumes(); AVisualLoggerRenderingActor* HelperActor = Cast(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& 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& 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& 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 > AllFutures; for (auto Iterator = FVisualLoggerDatabase::Get().GetRowIterator(); Iterator; ++Iterator) { FVisualLoggerDBRow* DBRow = &(*Iterator); AllFutures.Add( Async(EAsyncExecution::TaskGraph, [this, DBRow]() { const TArray& Entries = DBRow->GetItems(); for (int32 Index = 0; Index < Entries.Num(); ++Index) { UpdateVisibilityForEntry(*DBRow, Index); } } )); } bool bAllFuturesReady = false; do { bAllFuturesReady = true; for (TFuture& 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& 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& 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 > AllFutures; for (auto Iterator = FVisualLoggerDatabase::Get().GetRowIterator(); Iterator; ++Iterator) { FVisualLoggerDBRow* DBRow = &(*Iterator); AllFutures.Add( Async(EAsyncExecution::TaskGraph, [this, DBRow]() { const TArray& Entries = DBRow->GetItems(); for (int32 Index = 0; Index < Entries.Num(); ++Index) { UpdateVisibilityForEntry(*DBRow, Index); } } ) ); } bool bAllFuturesReady = false; do { bAllFuturesReady = true; for (TFuture& 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 Guard(bAutoScrollingToLastItem, true); FLogVisualizer::Get().GotoLastItemAnyRow(); } } void SVisualLogger::UpdateVisibilityForEntry(const FVisualLoggerDBRow& DBRow, int32 ItemIndex) { const ULogVisualizerSettings* Settings = GetDefault(); const FVisualLogDevice::FVisualLogEntryItem& CurrentEntry = DBRow.GetItems()[ItemIndex]; const FString SearchString = FVisualLoggerFilters::Get().GetSearchString(); TArray 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 SelectedItem, int64 UserData, FName TagName) { const TMap& 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 &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& Items = DBRow.GetItems(); if (Items.IsValidIndex(ClosestItem) && Items[ClosestItem].Entry.TimeStamp <= NewScrubPosition) { DBRow.MoveTo(ClosestItem); } } const TMap& 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& 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