// Copyright Epic Games, Inc. All Rights Reserved. #include "VisualLogTrack.h" #include "IRewindDebugger.h" #include "Modules/ModuleManager.h" #include "PropertyEditorModule.h" #include "VisualLoggerProvider.h" #include "UObject/Package.h" #define LOCTEXT_NAMESPACE "VisualLogTrack" namespace RewindDebugger { static TArray> CategoryNameAsTextMappings; FText& GetCategoryNameAsText(FName InCategory) { TPair* CachedEntry = CategoryNameAsTextMappings.FindByPredicate([InCategory](const TPair& Pair) { return Pair.Key == InCategory; }); if (CachedEntry == nullptr) { CachedEntry = &CategoryNameAsTextMappings.Add_GetRef({ InCategory, FText::FromName(InCategory) }); } return CachedEntry->Value; } UVLogDetailsObject* FVisualLogCategoryTrack::InitializeDetailsObject() { UVLogDetailsObject* DetailsObject = NewObject(); DetailsObject->SetFlags(RF_Standalone); DetailsObjectWeakPtr = MakeWeakObjectPtr(DetailsObject); DetailsView->SetObject(DetailsObject); return DetailsObject; } FVisualLogCategoryTrack::FVisualLogCategoryTrack(uint64 InObjectId, const FName& InCategory) : ObjectId(InObjectId), Category(InCategory) { TrackName = GetCategoryNameAsText(Category); EventData = MakeShared(); Icon = FSlateIcon("EditorStyle", "Sequencer.Tracks.Event", "Sequencer.Tracks.Event"); FPropertyEditorModule& PropertyEditorModule = FModuleManager::GetModuleChecked("PropertyEditor"); FDetailsViewArgs DetailsViewArgs; DetailsViewArgs.NameAreaSettings = FDetailsViewArgs::HideNameArea; DetailsView = PropertyEditorModule.CreateDetailView(DetailsViewArgs); InitializeDetailsObject(); } FVisualLogCategoryTrack::~FVisualLogCategoryTrack() { if (UVLogDetailsObject* DetailsObject = DetailsObjectWeakPtr.Get()) { DetailsObject->ClearFlags(RF_Standalone); } } TSharedPtr FVisualLogCategoryTrack::GetEventData() const { if (!EventData.IsValid()) { EventData = MakeShared(); } EventUpdateRequested++; return EventData; } static FLinearColor MakeNotifyColor(uint32 InSeed, bool bInLine = false) { FRandomStream Stream(InSeed); const uint8 Hue = (uint8)(Stream.FRand() * 255.0f); const uint8 SatVal = bInLine ? 196 : 128; return FLinearColor::MakeFromHSV8(Hue, SatVal, SatVal); } TSharedPtr FVisualLogCategoryTrack::GetDetailsViewInternal() { return DetailsView; } bool FVisualLogCategoryTrack::UpdateInternal() { IRewindDebugger* RewindDebugger = IRewindDebugger::Instance(); TRange TraceTimeRange = RewindDebugger->GetCurrentTraceRange(); double StartTime = TraceTimeRange.GetLowerBoundValue(); double EndTime = TraceTimeRange.GetUpperBoundValue(); const TraceServices::IAnalysisSession* AnalysisSession = RewindDebugger->GetAnalysisSession(); if (const FVisualLoggerProvider* VisLogProvider = AnalysisSession->ReadProvider(FVisualLoggerProvider::ProviderName)) { if (EventUpdateRequested > 10) { TRACE_CPUPROFILER_EVENT_SCOPE(FVisualLogTrack::UpdateEventPointsInternal); EventUpdateRequested = 0; EventData->Points.SetNum(0, EAllowShrinking::No); EventData->Windows.SetNum(0); TraceServices::FAnalysisSessionReadScope SessionReadScope(*AnalysisSession); VisLogProvider->ReadVisualLogEntryTimeline(ObjectId, [this, StartTime, EndTime, VisLogProvider, AnalysisSession](const FVisualLoggerProvider::VisualLogEntryTimeline& InTimeline) { InTimeline.EnumerateEvents(StartTime, EndTime, [this, StartTime, EndTime, VisLogProvider, AnalysisSession](double InStartTime, double InEndTime, uint32 InDepth, const FVisualLogEntry& InMessage) { for (const FVisualLogShapeElement& Element : InMessage.ElementsToDraw) { if (Element.Category == Category) { EventData->Points.Add({ InMessage.TimeStamp,GetCategoryNameAsText(Element.Category), FText::FromString(Element.Description),Element.GetFColor() }); } } for (const FVisualLogLine& Line : InMessage.LogLines) { if (Line.Category == Category) { // No Description from log lines. EventData->Points.Add({ InMessage.TimeStamp, GetCategoryNameAsText(Line.Category), FText::GetEmpty(), Line.Color }); } } return TraceServices::EEventEnumerate::Continue; }); }); } double CurrentScrubTime = IRewindDebugger::Instance()->CurrentTraceTime(); if (PreviousScrubTime != CurrentScrubTime) { PreviousScrubTime = CurrentScrubTime; UVLogDetailsObject* DetailsObject = DetailsObjectWeakPtr.Get(); if (DetailsObject == nullptr) { // this should not happen unless the object was garbage collected (which should not happen since it's marked as Standalone) DetailsObject = InitializeDetailsObject(); } DetailsObject->VisualLogDetails.SetNum(0, EAllowShrinking::No); const TraceServices::IFrameProvider& FramesProvider = TraceServices::ReadFrameProvider(*AnalysisSession); TraceServices::FAnalysisSessionReadScope SessionReadScope(*AnalysisSession); TraceServices::FFrame MarkerFrame; if (FramesProvider.GetFrameFromTime(ETraceFrameType::TraceFrameType_Game, CurrentScrubTime, MarkerFrame)) { VisLogProvider->ReadVisualLogEntryTimeline(ObjectId, [this, DetailsObject, &MarkerFrame, EndTime, VisLogProvider, AnalysisSession](const FVisualLoggerProvider::VisualLogEntryTimeline& InTimeline) { InTimeline.EnumerateEvents(MarkerFrame.StartTime, MarkerFrame.EndTime, [this, DetailsObject, VisLogProvider, AnalysisSession](double InStartTime, double InEndTime, uint32 InDepth, const FVisualLogEntry& InMessage) { for (const FVisualLogShapeElement& Element : InMessage.ElementsToDraw) { if (Element.Category == Category) { DetailsObject->VisualLogDetails.Add({ Element.Category, Element.Description }); } } for (const FVisualLogLine& Line : InMessage.LogLines) { if (Line.Category == Category) { DetailsObject->VisualLogDetails.Add({ Line.Category, Line.Line }); } } return TraceServices::EEventEnumerate::Continue; }); }); } } } bool bChanged = false; return bChanged; } TSharedPtr FVisualLogTrack::GetDetailsViewInternal() { return nullptr; } TSharedPtr FVisualLogCategoryTrack::GetTimelineViewInternal() { return SNew(SEventTimelineView) .ViewRange_Lambda([]() { return IRewindDebugger::Instance()->GetCurrentViewRange(); }) .EventData_Raw(this, &FVisualLogCategoryTrack::GetEventData); } static const FName VisualLogName("Visual Logging"); FName FVisualLogTrackCreator::GetTargetTypeNameInternal() const { static const FName AnimInstanceName("Object"); return AnimInstanceName; } FName FVisualLogTrackCreator::GetNameInternal() const { return VisualLogName; } void FVisualLogTrackCreator::GetTrackTypesInternal(TArray& Types) const { Types.Add({ VisualLogName, LOCTEXT("Visual Logging", "Visual Logging") }); } TSharedPtr FVisualLogTrackCreator::CreateTrackInternal(const FObjectId& InObjectId) const { return MakeShared(InObjectId.GetMainId()); } FVisualLogTrack::FVisualLogTrack(uint64 InObjectId) : ObjectId(InObjectId) { Icon = FSlateIcon("EditorStyle", "Sequencer.Tracks.Event", "Sequencer.Tracks.Event"); } bool FVisualLogTrack::UpdateInternal() { TRACE_CPUPROFILER_EVENT_SCOPE(FVisualLogTrack::UpdateInternal); IRewindDebugger* RewindDebugger = IRewindDebugger::Instance(); TRange TraceTimeRange = RewindDebugger->GetCurrentTraceRange(); double StartTime = TraceTimeRange.GetLowerBoundValue(); double EndTime = TraceTimeRange.GetUpperBoundValue(); const TraceServices::IAnalysisSession* AnalysisSession = RewindDebugger->GetAnalysisSession(); const FVisualLoggerProvider* VisLogProvider = AnalysisSession->ReadProvider(FVisualLoggerProvider::ProviderName); bool bChanged = false; if (VisLogProvider) { TArray UniqueTrackIds; TraceServices::FAnalysisSessionReadScope SessionReadScope(*AnalysisSession); VisLogProvider->ReadVisualLogEntryTimeline(ObjectId, [this, StartTime, EndTime, VisLogProvider, AnalysisSession, &UniqueTrackIds](const FVisualLoggerProvider::VisualLogEntryTimeline& InTimeline) { InTimeline.EnumerateEvents(StartTime, EndTime, [this, StartTime, EndTime, VisLogProvider, AnalysisSession, &UniqueTrackIds](double InStartTime, double InEndTime, uint32 InDepth, const FVisualLogEntry& InMessage) { if (InMessage.LogLines.Num() > 0) { for (const FVisualLogLine& Line : InMessage.LogLines) { UniqueTrackIds.AddUnique(Line.Category); } } for (const FVisualLogShapeElement& Element : InMessage.ElementsToDraw) { UniqueTrackIds.AddUnique(Element.Category); } return TraceServices::EEventEnumerate::Continue; }); }); UniqueTrackIds.StableSort([](const FName& A, const FName& B) { return A.ToString() < B.ToString(); }); const int32 TrackCount = UniqueTrackIds.Num(); if (Children.Num() != TrackCount) bChanged = true; Children.SetNum(UniqueTrackIds.Num()); for (int i = 0; i < TrackCount; i++) { if (!Children[i].IsValid() || !(Children[i].Get()->GetName() == UniqueTrackIds[i])) { Children[i] = MakeShared(ObjectId, UniqueTrackIds[i]); bChanged = true; } if (Children[i]->Update()) { bChanged = true; } } } return bChanged; } void FVisualLogTrack::IterateSubTracksInternal(TFunction SubTrack)> IteratorFunction) { for (TSharedPtr& Track : Children) { IteratorFunction(Track); } }; bool FVisualLogTrackCreator::HasDebugInfoInternal(const FObjectId& InObjectId) const { TRACE_CPUPROFILER_EVENT_SCOPE(FVisualLogTrack::HasDebugInfoInternal); const TraceServices::IAnalysisSession* AnalysisSession = IRewindDebugger::Instance()->GetAnalysisSession(); TraceServices::FAnalysisSessionReadScope SessionReadScope(*AnalysisSession); bool bHasData = false; if (const FVisualLoggerProvider* VLogProvider = AnalysisSession->ReadProvider(FVisualLoggerProvider::ProviderName)) { VLogProvider->ReadVisualLogEntryTimeline(InObjectId.GetMainId(), [&bHasData](const FVisualLoggerProvider::VisualLogEntryTimeline& InTimeline) { bHasData = true; }); } return bHasData; } } #undef LOCTEXT_NAMESPACE