Files
ToonTanks/Plugins/VisualStudioTools/Source/VisualStudioBlueprintDebuggerHelper/Private/VisualStudioBlueprintDebuggerHelperModule.cpp
2025-07-14 00:29:30 -04:00

247 lines
7.2 KiB
C++

// Copyright 2022 (c) Microsoft. All rights reserved.
#include "VisualStudioBlueprintDebuggerHelperModule.h"
#include <Modules/ModuleManager.h>
#include <UObject/Script.h>
#include <UObject/Stack.h>
#include <UObject/Object.h>
#include <Templates/Casts.h>
#include <Kismet2/KismetDebugUtilities.h>
#include <Containers/Array.h>
#include <UObject/Class.h>
#include <Engine/BlueprintGeneratedClass.h>
#include <Engine/Blueprint.h>
#include <Runtime/Launch/Resources/Version.h>
#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 4
#include <Blueprint/BlueprintExceptionInfo.h>
#endif
#include <Internationalization/Text.h>
#include <HAL/Platform.h>
#include <EdGraph/EdGraphNode.h>
#include <EdGraph/EdGraphPin.h>
#include <Templates/SharedPointer.h>
#include <Templates/Tuple.h>
#include <CoreGlobals.h>
#include <map>
IMPLEMENT_MODULE(FVisualStudioBlueprintDebuggerHelper, VisualStudioBlueprintDebuggerHelper);
DEFINE_LOG_CATEGORY(LogVisualStudioBlueprintDebuggerHelper);
#if ENGINE_MAJOR_VERSION >= 5
#define FCustomBlueprintPropertyInfo TSharedPtr<FPropertyInstanceInfo>
#else
#define FCustomBlueprintPropertyInfo FDebugInfo
#endif
struct FVSNodePinRuntimeInformation
{
UEdGraphPin* Pin;
FCustomBlueprintPropertyInfo Property;
FVSNodePinRuntimeInformation(UEdGraphPin* InPin, FCustomBlueprintPropertyInfo InProperty)
: Pin(InPin)
, Property(InProperty)
{
}
};
struct FVSNodeData
{
FText NodeName;
TArray<TSharedPtr<FVSNodePinRuntimeInformation>> Properties;
int32 ScriptEntryTag;
const UEdGraphNode* Node;
};
struct FVSNodesRuntimeInformation
{
TArray<TSharedPtr<FVSNodeData>> Nodes;
};
struct FVSBlueprintRuntimeInformation
{
TArray<TTuple<UBlueprint*, TSharedPtr<FVSNodesRuntimeInformation>>> RunningBlueprints;
};
struct StackTraceHelper
{
int32 ScriptEntryTag;
FString NodeName;
};
// Keep exported so we can read it.
VISUALSTUDIOBLUEPRINTDEBUGGERHELPER_API FVSBlueprintRuntimeInformation BlueprintsRuntimeInformation;
VISUALSTUDIOBLUEPRINTDEBUGGERHELPER_API std::map<void*, StackTraceHelper> StackFrameInformation;
VISUALSTUDIOBLUEPRINTDEBUGGERHELPER_API const char* DebuggerHelperVersion = "1.0.0";
void FVisualStudioBlueprintDebuggerHelper::StartupModule()
{
CurrentScriptEntryTag = 0;
FBlueprintContextTracker::OnEnterScriptContext.AddRaw(
this,
&FVisualStudioBlueprintDebuggerHelper::OnEnterScriptContext);
FBlueprintContextTracker::OnExitScriptContext.AddRaw(
this,
&FVisualStudioBlueprintDebuggerHelper::OnExitScriptContext);
FBlueprintCoreDelegates::OnScriptException.AddRaw(
this,
&FVisualStudioBlueprintDebuggerHelper::OnScriptException);
}
void FVisualStudioBlueprintDebuggerHelper::ShutdownModule()
{
FBlueprintCoreDelegates::OnScriptException.RemoveAll(this);
FBlueprintContextTracker::OnExitScriptContext.RemoveAll(this);
FBlueprintContextTracker::OnEnterScriptContext.RemoveAll(this);
}
void FVisualStudioBlueprintDebuggerHelper::OnEnterScriptContext(
const struct FBlueprintContextTracker& Context,
const UObject* SourceObject,
const UFunction* Function)
{
if (!IsInGameThread())
{
return;
}
CurrentScriptEntryTag = Context.GetScriptEntryTag();
}
void FVisualStudioBlueprintDebuggerHelper::OnExitScriptContext(const struct FBlueprintContextTracker& Context)
{
if (!IsInGameThread())
{
return;
}
for (auto ItRunningBlueprints = BlueprintsRuntimeInformation.RunningBlueprints.CreateIterator(); ItRunningBlueprints; ++ItRunningBlueprints)
{
auto& RunningBlueprint = ItRunningBlueprints->Value;
for (auto ItNodeData = RunningBlueprint->Nodes.CreateIterator(); ItNodeData; ++ItNodeData)
{
if ((*ItNodeData)->ScriptEntryTag == Context.GetScriptEntryTag())
{
ItNodeData.RemoveCurrent();
}
}
if (!RunningBlueprint->Nodes.Num())
{
ItRunningBlueprints.RemoveCurrent();
}
}
for (auto ItStackFrameInfo = StackFrameInformation.begin(); ItStackFrameInfo != StackFrameInformation.end();)
{
if (ItStackFrameInfo->second.ScriptEntryTag == Context.GetScriptEntryTag())
{
ItStackFrameInfo = StackFrameInformation.erase(ItStackFrameInfo);
}
else
{
++ItStackFrameInfo;
}
}
CurrentScriptEntryTag--;
}
void FVisualStudioBlueprintDebuggerHelper::OnScriptException(
const UObject* Owner,
const struct FFrame& Stack,
const FBlueprintExceptionInfo& ExceptionInfo)
{
EBlueprintExceptionType::Type ExceptionType = ExceptionInfo.GetType();
if (ExceptionType != EBlueprintExceptionType::Type::Tracepoint &&
ExceptionType != EBlueprintExceptionType::Type::WireTracepoint &&
ExceptionType != EBlueprintExceptionType::Type::Breakpoint)
{
return;
}
UFunction* NodeFunction = Cast<UFunction>(Stack.Node);
if (!NodeFunction)
{
return;
}
UBlueprintGeneratedClass* BlueprintGeneratedClass = Cast<UBlueprintGeneratedClass>(NodeFunction->GetOuter());
if (!BlueprintGeneratedClass)
{
return;
}
UBlueprint* Blueprint = Cast<UBlueprint>(BlueprintGeneratedClass->ClassGeneratedBy);
if (!Blueprint)
{
return;
}
const int32 BreakpointOffset = Stack.Code - Stack.Node->Script.GetData() - 1;
const UEdGraphNode* NodeStoppedAt = FKismetDebugUtilities::FindSourceNodeForCodeLocation(Owner, Stack.Node, BreakpointOffset, /*bAllowImpreciseHit=*/ true);
if (!NodeStoppedAt)
{
return;
}
StackFrameInformation[NodeFunction] = { CurrentScriptEntryTag, FString::Printf(TEXT("%s::%s"), *Blueprint->GetFriendlyName(), *NodeStoppedAt->GetNodeTitle(ENodeTitleType::Type::ListView).ToString()) };
TTuple<UBlueprint*, TSharedPtr<FVSNodesRuntimeInformation>>* ExistingNodesRuntimeInformationTuple = BlueprintsRuntimeInformation.RunningBlueprints.FindByPredicate([&Blueprint](const TTuple<UBlueprint*, TSharedPtr<FVSNodesRuntimeInformation>>& Tuple) {
return Tuple.Key == Blueprint;
});
TSharedPtr<FVSNodesRuntimeInformation> NodesRuntimeInformation;
if (!ExistingNodesRuntimeInformationTuple)
{
NodesRuntimeInformation = MakeShared<FVSNodesRuntimeInformation>();
BlueprintsRuntimeInformation.RunningBlueprints.Add(MakeTuple(Blueprint, NodesRuntimeInformation));
}
else
{
NodesRuntimeInformation = ExistingNodesRuntimeInformationTuple->Value;
}
TSharedPtr<FVSNodeData> CurrentNodeData;
if (NodesRuntimeInformation->Nodes.Num() == 0 || NodeStoppedAt != NodesRuntimeInformation->Nodes.Top()->Node)
{
CurrentNodeData = MakeShared<FVSNodeData>();
CurrentNodeData->Node = NodeStoppedAt;
CurrentNodeData->NodeName = NodeStoppedAt->GetNodeTitle(ENodeTitleType::Type::ListView);
CurrentNodeData->ScriptEntryTag = CurrentScriptEntryTag;
NodesRuntimeInformation->Nodes.Push(CurrentNodeData);
}
else
{
CurrentNodeData = NodesRuntimeInformation->Nodes.Top();
}
FCustomBlueprintPropertyInfo PinInstanceInfo;
for (auto GraphPin : NodeStoppedAt->Pins)
{
FKismetDebugUtilities::EWatchTextResult DebugResult = FKismetDebugUtilities::GetDebugInfo(PinInstanceInfo, Blueprint, (UObject*)Owner, GraphPin);
if (DebugResult != FKismetDebugUtilities::EWTR_Valid)
{
continue;
}
TSharedPtr<FVSNodePinRuntimeInformation>* Existing = CurrentNodeData->Properties.FindByPredicate([&GraphPin](TSharedPtr<FVSNodePinRuntimeInformation>& PinInfo) {
return PinInfo->Pin == GraphPin;
});
if (!Existing)
{
CurrentNodeData->Properties.Add(MakeShared<FVSNodePinRuntimeInformation>(GraphPin, PinInstanceInfo));
}
else
{
(*Existing)->Property = PinInstanceInfo;
}
}
}