// Copyright Epic Games, Inc. All Rights Reserved. #if WITH_STATETREE_DEBUGGER #include "Debugger/StateTreeTrace.h" #include "Debugger/StateTreeDebugger.h" #include "Exporters/Exporter.h" #include "ObjectTrace.h" #include "Serialization/BufferArchive.h" #include "StateTree.h" #include "StateTreeExecutionTypes.h" #include "UObject/Package.h" #include "Trace/Trace.inl" #if WITH_EDITOR #include "Editor.h" #endif // WITH_EDITOR UE_TRACE_CHANNEL_DEFINE(StateTreeDebugChannel) UE_TRACE_EVENT_BEGIN(StateTreeDebugger, WorldTimestampEvent) UE_TRACE_EVENT_FIELD(double, WorldTime) UE_TRACE_EVENT_END() UE_TRACE_EVENT_BEGIN(StateTreeDebugger, InstanceEvent) UE_TRACE_EVENT_FIELD(uint64, Cycle) UE_TRACE_EVENT_FIELD(UE::Trace::WideString, TreeName) UE_TRACE_EVENT_FIELD(UE::Trace::WideString, TreePath) UE_TRACE_EVENT_FIELD(uint32, CompiledDataHash) UE_TRACE_EVENT_FIELD(uint32, InstanceId) UE_TRACE_EVENT_FIELD(uint32, InstanceSerial) UE_TRACE_EVENT_FIELD(UE::Trace::WideString, InstanceName) UE_TRACE_EVENT_FIELD(uint8, EventType) UE_TRACE_EVENT_END() UE_TRACE_EVENT_BEGIN(StateTreeDebugger, LogEvent) UE_TRACE_EVENT_FIELD(uint64, Cycle) UE_TRACE_EVENT_FIELD(uint32, InstanceId) UE_TRACE_EVENT_FIELD(uint32, InstanceSerial) UE_TRACE_EVENT_FIELD(uint16, Phase) UE_TRACE_EVENT_FIELD(UE::Trace::WideString, Message) UE_TRACE_EVENT_END() UE_TRACE_EVENT_BEGIN(StateTreeDebugger, StateEvent) UE_TRACE_EVENT_FIELD(uint64, Cycle) UE_TRACE_EVENT_FIELD(uint32, InstanceId) UE_TRACE_EVENT_FIELD(uint32, InstanceSerial) UE_TRACE_EVENT_FIELD(uint16, Phase) UE_TRACE_EVENT_FIELD(uint16, StateIndex) UE_TRACE_EVENT_FIELD(uint8, EventType) UE_TRACE_EVENT_FIELD(uint8, SelectionBehavior) UE_TRACE_EVENT_END() UE_TRACE_EVENT_BEGIN(StateTreeDebugger, TaskEvent) UE_TRACE_EVENT_FIELD(uint64, Cycle) UE_TRACE_EVENT_FIELD(uint32, InstanceId) UE_TRACE_EVENT_FIELD(uint32, InstanceSerial) UE_TRACE_EVENT_FIELD(uint16, Phase) UE_TRACE_EVENT_FIELD(uint16, NodeIndex) UE_TRACE_EVENT_FIELD(uint8[], DataView) UE_TRACE_EVENT_FIELD(uint8, EventType) UE_TRACE_EVENT_FIELD(uint8, Status) UE_TRACE_EVENT_END() UE_TRACE_EVENT_BEGIN(StateTreeDebugger, TransitionEvent) UE_TRACE_EVENT_FIELD(uint64, Cycle) UE_TRACE_EVENT_FIELD(uint32, InstanceId) UE_TRACE_EVENT_FIELD(uint32, InstanceSerial) UE_TRACE_EVENT_FIELD(uint16, Phase) UE_TRACE_EVENT_FIELD(uint16, TransitionIndex) UE_TRACE_EVENT_FIELD(uint8, EventType) UE_TRACE_EVENT_END() UE_TRACE_EVENT_BEGIN(StateTreeDebugger, ConditionEvent) UE_TRACE_EVENT_FIELD(uint64, Cycle) UE_TRACE_EVENT_FIELD(uint32, InstanceId) UE_TRACE_EVENT_FIELD(uint32, InstanceSerial) UE_TRACE_EVENT_FIELD(uint16, Phase) UE_TRACE_EVENT_FIELD(uint16, NodeIndex) UE_TRACE_EVENT_FIELD(uint8[], DataView) UE_TRACE_EVENT_FIELD(uint8, EventType) UE_TRACE_EVENT_END() UE_TRACE_EVENT_BEGIN(StateTreeDebugger, ActiveStatesEvent) UE_TRACE_EVENT_FIELD(uint64, Cycle) UE_TRACE_EVENT_FIELD(uint32, InstanceId) UE_TRACE_EVENT_FIELD(uint32, InstanceSerial) UE_TRACE_EVENT_FIELD(uint16, Phase) UE_TRACE_EVENT_FIELD(uint16[], ActiveStates) UE_TRACE_EVENT_END() namespace UE::StateTreeTrace { FDelegateHandle GOnWorldTickStartDelegateHandle; #if WITH_EDITOR FDelegateHandle GOnPIEStartDelegateHandle; #endif // WITH_EDITOR double GRecordingWorldTime = -1; double GTracedRecordingWorldTime = -1; void TraceTimeMapping() { if (GTracedRecordingWorldTime != GRecordingWorldTime) { GTracedRecordingWorldTime = GRecordingWorldTime; UE_TRACE_LOG(StateTreeDebugger, WorldTimestampEvent, StateTreeDebugChannel) << WorldTimestampEvent.WorldTime(GRecordingWorldTime); } } void SerializeDataViewToArchive(FBufferArchive Ar, const FStateTreeDataView DataView) { if (const UScriptStruct* ScriptStruct = Cast(DataView.GetStruct())) { TRACE_CPUPROFILER_EVENT_SCOPE(UE::StateTree::ExportStructAsText) FString StructPath = ScriptStruct->GetPathName(); FString TextValue; ScriptStruct->ExportText(TextValue, DataView.GetMemory(), DataView.GetMemory(), /*OwnerObject*/nullptr, PPF_None, /*ExportRootScope*/nullptr); Ar << StructPath; Ar << TextValue; } else if (const UClass* Class = Cast(DataView.GetStruct())) { TRACE_CPUPROFILER_EVENT_SCOPE(UE::StateTree::ExportObjectAsText) FString StructPath = Class->GetPathName(); FStringOutputDevice OutputDevice; UObject* Object = DataView.GetMutablePtr(); // Not using on scope FExportObjectInnerContext since it is very costly to build. // Passing a null context will make the export use an already built thread local context. UExporter::ExportToOutputDevice(nullptr, Object, /*Exporter*/nullptr, OutputDevice, TEXT("copy"), 0, PPF_ExportsNotFullyQualified | PPF_Copy | PPF_Delimited, false, Object->GetOuter()); Ar << StructPath; Ar << OutputDevice; } } void RegisterGlobalDelegates() { #if WITH_EDITOR GOnPIEStartDelegateHandle = FEditorDelegates::BeginPIE.AddLambda([&LastRecordingWorldTime=GTracedRecordingWorldTime](const bool bIsSimulating) { LastRecordingWorldTime = -1; }); #endif // WITH_EDITOR GOnWorldTickStartDelegateHandle = FWorldDelegates::OnWorldTickStart.AddLambda([&WorldTime=GRecordingWorldTime](const UWorld* TickedWorld, ELevelTick TickType, float DeltaTime) { #if OBJECT_TRACE_ENABLED WorldTime = FObjectTrace::GetWorldElapsedTime(TickedWorld); #endif// OBJECT_TRACE_ENABLED }); } void UnregisterGlobalDelegates() { #if WITH_EDITOR FEditorDelegates::BeginPIE.Remove(GOnPIEStartDelegateHandle); #endif // WITH_EDITOR FWorldDelegates::OnWorldTickStart.Remove(GOnWorldTickStartDelegateHandle); GOnWorldTickStartDelegateHandle.Reset(); } void OutputInstanceLifetimeEvent( const FStateTreeInstanceDebugId InstanceId, const UStateTree* StateTree, const TCHAR* InstanceName, const EStateTreeTraceInstanceEventType EventType ) { if (StateTree == nullptr) { return; } const FString ObjectName = StateTree->GetName(); const FString ObjectPackageName = StateTree->GetPackage()->GetName(); TraceTimeMapping(); UE_TRACE_LOG(StateTreeDebugger, InstanceEvent, StateTreeDebugChannel) << InstanceEvent.Cycle(FPlatformTime::Cycles64()) << InstanceEvent.TreeName(*ObjectName, ObjectName.Len()) << InstanceEvent.TreePath(*ObjectPackageName, ObjectPackageName.Len()) << InstanceEvent.CompiledDataHash(StateTree->LastCompiledEditorDataHash) << InstanceEvent.InstanceId(InstanceId.Id) << InstanceEvent.InstanceSerial(InstanceId.SerialNumber) << InstanceEvent.InstanceName(InstanceName) << InstanceEvent.EventType(static_cast>(EventType)); } void OutputLogEventTrace( const FStateTreeInstanceDebugId InstanceId, const EStateTreeUpdatePhase Phase, const TCHAR* Fmt, ... ) { static TCHAR TraceStaticBuffer[8192]; GET_VARARGS(TraceStaticBuffer, UE_ARRAY_COUNT(TraceStaticBuffer), UE_ARRAY_COUNT(TraceStaticBuffer) - 1, Fmt, Fmt); TraceTimeMapping(); UE_TRACE_LOG(StateTreeDebugger, LogEvent, StateTreeDebugChannel) << LogEvent.Cycle(FPlatformTime::Cycles64()) << LogEvent.InstanceId(InstanceId.Id) << LogEvent.InstanceSerial(InstanceId.SerialNumber) << LogEvent.Phase(static_cast>(Phase)) << LogEvent.Message(TraceStaticBuffer); } void OutputStateEventTrace( const FStateTreeInstanceDebugId InstanceId, const EStateTreeUpdatePhase Phase, const FStateTreeStateHandle StateHandle, const EStateTreeTraceNodeEventType EventType, const EStateTreeStateSelectionBehavior SelectionBehavior ) { TraceTimeMapping(); UE_TRACE_LOG(StateTreeDebugger, StateEvent, StateTreeDebugChannel) << StateEvent.Cycle(FPlatformTime::Cycles64()) << StateEvent.InstanceId(InstanceId.Id) << StateEvent.InstanceSerial(InstanceId.SerialNumber) << StateEvent.Phase(static_cast>(Phase)) << StateEvent.StateIndex(StateHandle.Index) << StateEvent.EventType(static_cast>(EventType)) << StateEvent.SelectionBehavior(static_cast>(SelectionBehavior)); } void OutputTaskEventTrace( const FStateTreeInstanceDebugId InstanceId, const EStateTreeUpdatePhase Phase, const FStateTreeIndex16 TaskIdx, const FStateTreeDataView DataView, const EStateTreeTraceNodeEventType EventType, const EStateTreeRunStatus Status ) { FBufferArchive Archive; SerializeDataViewToArchive(Archive, DataView); TraceTimeMapping(); UE_TRACE_LOG(StateTreeDebugger, TaskEvent, StateTreeDebugChannel) << TaskEvent.Cycle(FPlatformTime::Cycles64()) << TaskEvent.InstanceId(InstanceId.Id) << TaskEvent.InstanceSerial(InstanceId.SerialNumber) << TaskEvent.Phase(static_cast>(Phase)) << TaskEvent.NodeIndex(TaskIdx.Get()) << TaskEvent.DataView(Archive.GetData(), Archive.Num()) << TaskEvent.EventType(static_cast>(EventType)) << TaskEvent.Status(static_cast>(Status)); } void OutputTransitionEventTrace( const FStateTreeInstanceDebugId InstanceId, const EStateTreeUpdatePhase Phase, const FStateTreeIndex16 TransitionIdx, const EStateTreeTraceNodeEventType EventType ) { FBufferArchive Archive; Archive << EventType; TraceTimeMapping(); UE_TRACE_LOG(StateTreeDebugger, TransitionEvent, StateTreeDebugChannel) << TransitionEvent.Cycle(FPlatformTime::Cycles64()) << TransitionEvent.InstanceId(InstanceId.Id) << TransitionEvent.InstanceSerial(InstanceId.SerialNumber) << TransitionEvent.Phase(static_cast>(Phase)) << TransitionEvent.TransitionIndex(TransitionIdx.Get()) << TransitionEvent.EventType(static_cast>(EventType)); } void OutputConditionEventTrace( const FStateTreeInstanceDebugId InstanceId, const EStateTreeUpdatePhase Phase, const FStateTreeIndex16 ConditionIdx, const FStateTreeDataView DataView, const EStateTreeTraceNodeEventType EventType ) { FBufferArchive Archive; SerializeDataViewToArchive(Archive, DataView); TraceTimeMapping(); UE_TRACE_LOG(StateTreeDebugger, ConditionEvent, StateTreeDebugChannel) << ConditionEvent.Cycle(FPlatformTime::Cycles64()) << ConditionEvent.InstanceId(InstanceId.Id) << ConditionEvent.InstanceSerial(InstanceId.SerialNumber) << ConditionEvent.Phase(static_cast>(Phase)) << ConditionEvent.NodeIndex(ConditionIdx.Get()) << ConditionEvent.DataView(Archive.GetData(), Archive.Num()) << ConditionEvent.EventType(static_cast>(EventType)); } void OutputActiveStatesEventTrace( const FStateTreeInstanceDebugId InstanceId, const EStateTreeUpdatePhase Phase, const FStateTreeActiveStates& ActiveStates ) { TArray> StatesIndices; for (int32 i = 0; i < ActiveStates.Num(); i++) { StatesIndices.Add(ActiveStates[i].Index); } TraceTimeMapping(); UE_TRACE_LOG(StateTreeDebugger, ActiveStatesEvent, StateTreeDebugChannel) << ActiveStatesEvent.Cycle(FPlatformTime::Cycles64()) << ActiveStatesEvent.InstanceId(InstanceId.Id) << ActiveStatesEvent.InstanceSerial(InstanceId.SerialNumber) << ActiveStatesEvent.Phase(static_cast>(Phase)) << ActiveStatesEvent.ActiveStates(StatesIndices.GetData(), StatesIndices.Num()); } } // UE::StateTreeTrace #endif // WITH_STATETREE_DEBUGGER