EM_Task/UnrealEd/Public/Kismet2/KismetDebugUtilities.h
Boshuang Zhao 5144a49c9b add
2026-02-13 16:18:33 +08:00

280 lines
11 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "UObject/Object.h"
#include "UObject/Class.h"
#include "Engine/Selection.h"
static_assert(DO_BLUEPRINT_GUARD, "KismetDebugUtilities assumes BP exception tracking is enabled");
class UBlueprint;
class UBreakpoint;
template <typename ElementType>
class TSimpleRingBuffer;
DECLARE_LOG_CATEGORY_EXTERN(LogBlueprintDebug, Log, All);
//////////////////////////////////////////////////////////////////////////
// FKismetTraceSample
struct FKismetTraceSample
{
TWeakObjectPtr<class UObject> Context;
TWeakObjectPtr<class UFunction> Function;
int32 Offset;
double ObservationTime;
};
//////////////////////////////////////////////////////////////////////////
// FDebugInfo
struct FDebugInfo
{
FText DisplayName;
FText Value;
FText Type;
TArray<FDebugInfo> Children;
};
//////////////////////////////////////////////////////////////////////////
// FObjectsBeingDebuggedIterator
// Helper class to iterate over all objects that should be visible in the debugger
struct UNREALED_API FObjectsBeingDebuggedIterator
{
public:
FObjectsBeingDebuggedIterator();
/** @name Element access */
//@{
UObject* operator*() const;
UObject* operator->() const;
//@}
/** Advances iterator to the next element in the container. */
FObjectsBeingDebuggedIterator& operator++();
/** conversion to "bool" returning true if the iterator has not reached the last element. */
FORCEINLINE explicit operator bool() const
{
return IsValid();
}
/** inverse of the "bool" operator */
FORCEINLINE bool operator!() const
{
return !(bool)*this;
}
private:
FSelectionIterator SelectedActorsIter;
int32 LevelScriptActorIndex;
private:
void FindNextLevelScriptActor();
bool IsValid() const;
UWorld* GetWorld() const;
};
//////////////////////////////////////////////////////////////////////////
// FObjectsBeingDebuggedIterator
// Helper class to iterate over all objects that should be visible in the debugger
struct UNREALED_API FBlueprintObjectsBeingDebuggedIterator
{
public:
FBlueprintObjectsBeingDebuggedIterator(UBlueprint* InBlueprint);
/** @name Element access */
//@{
UObject* operator*() const;
UObject* operator->() const;
//@}
/** Advances iterator to the next element in the container. */
FBlueprintObjectsBeingDebuggedIterator& operator++();
/** conversion to "bool" returning true if the iterator has not reached the last element. */
FORCEINLINE explicit operator bool() const
{
return IsValid();
}
/** inverse of the "bool" operator */
FORCEINLINE bool operator!() const
{
return !(bool)*this;
}
private:
UBlueprint* Blueprint;
private:
bool IsValid() const;
};
//////////////////////////////////////////////////////////////////////////
// FKismetDebugUtilities
class UNREALED_API FKismetDebugUtilities
{
public:
// Delegate for when pins are added or removed from the watchlist
DECLARE_MULTICAST_DELEGATE_OneParam(FOnWatchedPinsListChanged, class UBlueprint*);
static void OnScriptException(const UObject* ActiveObject, const FFrame& StackFrame, const FBlueprintExceptionInfo& Info);
/** Returns the current instruction; if a PIE/SIE session is started but paused; otherwise NULL */
static class UEdGraphNode* GetCurrentInstruction();
/** Returns the most recent hit breakpoint; if a PIE/SIE session is started but paused; otherwise NULL */
static class UEdGraphNode* GetMostRecentBreakpointHit();
/** Request an attempt to single-step to the next node */
static void RequestSingleStepIn();
/** Request an attempt to step over to the next node in this graph or the calling graph */
static void RequestStepOver();
/** Request an attempt to step out of the current graph */
static void RequestStepOut();
/** Called on terminatation of the current script execution so we can reset any break conditions */
static void EndOfScriptExecution(const FBlueprintContextTracker& BlueprintContext);
// The maximum number of trace samples to gather before overwriting old ones
enum
{
MAX_TRACE_STACK_SAMPLES = 1024
};
// Get the trace stack
static const TSimpleRingBuffer<FKismetTraceSample>& GetTraceStack();
// Find the node that resulted in code at the specified location in the Object, or NULL if there was a problem (e.g., no debugging information was generated)
static class UEdGraphNode* FindSourceNodeForCodeLocation(const UObject* Object, UFunction* Function, int32 DebugOpcodeOffset, bool bAllowImpreciseHit = false);
// Return proper class for breakpoint
static UClass* FindClassForNode(const UObject* Object, UFunction* Function);
// Notify the debugger of the start of the game frame
static void NotifyDebuggerOfStartOfGameFrame(UWorld* CurrentWorld);
// Notify the debugger of the end of the game frame
static void NotifyDebuggerOfEndOfGameFrame(UWorld* CurrentWorld);
// Whether or not we are single stepping
static bool IsSingleStepping();
// Breakpoint utils
/** Is the node a valid breakpoint target? (i.e., the node is impure and ended up generating code) */
static bool IsBreakpointValid(UBreakpoint* Breakpoint);
/** Set the node that the breakpoint should focus on */
static void SetBreakpointLocation(UBreakpoint* Breakpoint, UEdGraphNode* NewNode);
/** Set or clear the enabled flag for the breakpoint */
static void SetBreakpointEnabled(UBreakpoint* Breakpoint, bool bIsEnabled);
/** Sets this breakpoint up as a single-step breakpoint (will disable or delete itself after one go if the breakpoint wasn't already enabled) */
static void SetBreakpointEnabledForSingleStep(UBreakpoint* Breakpoint, bool bDeleteAfterStep);
/** Reapplies the breakpoint (used after recompiling to ensure it is set if needed) */
static void ReapplyBreakpoint(UBreakpoint* Breakpoint);
/** Start the process of deleting this breakpoint */
static void StartDeletingBreakpoint(UBreakpoint* Breakpoint, UBlueprint* OwnerBlueprint);
/** Update the internal state of the breakpoint when it got hit */
static void UpdateBreakpointStateWhenHit(UBreakpoint* Breakpoint, UBlueprint* OwnerBlueprint);
/** Returns the installation site(s); don't cache these pointers! */
static void GetBreakpointInstallationSites(UBreakpoint* Breakpoint, TArray<uint8*>& InstallSites);
/** Install/uninstall the breakpoint into/from the script code for the generated class that contains the node */
static void SetBreakpointInternal(UBreakpoint* Breakpoint, bool bShouldBeEnabled);
/** Returns the set of valid macro source node breakpoint location(s) for the given macro instance node. The set may be empty. */
static void GetValidBreakpointLocations(const class UK2Node_MacroInstance* MacroInstanceNode, TArray<const UEdGraphNode*>& BreakpointLocations);
// Blueprint utils
// Looks thru the debugging data for any class variables associated with the pin
static class FProperty* FindClassPropertyForPin(UBlueprint* Blueprint, const UEdGraphPin* Pin);
// Looks thru the debugging data for any class variables associated with the node (e.g., temporary variables or timelines)
static class FProperty* FindClassPropertyForNode(UBlueprint* Blueprint, const UEdGraphNode* Node);
// Is there debugging data available for this blueprint?
static bool HasDebuggingData(const UBlueprint* Blueprint);
/** Returns the breakpoint associated with a node, or NULL */
static UBreakpoint* FindBreakpointForNode(UBlueprint* Blueprint, const UEdGraphNode* Node, bool bCheckSubLocations = false);
/** Deletes all breakpoints in this blueprint */
static void ClearBreakpoints(UBlueprint* Blueprint);
// Notifies listeners when a watched pin is added or removed
static FOnWatchedPinsListChanged WatchedPinsListChangedEvent;
static bool CanWatchPin(const UBlueprint* Blueprint, const UEdGraphPin* Pin);
static bool IsPinBeingWatched(const UBlueprint* Blueprint, const UEdGraphPin* Pin);
static void TogglePinWatch(UBlueprint* Blueprint, const UEdGraphPin* Pin);
static void RemovePinWatch(UBlueprint* Blueprint, const UEdGraphPin* Pin);
static void ClearPinWatches(UBlueprint* Blueprint);
enum EWatchTextResult
{
// The property was valid and the value has been returned as a string
EWTR_Valid,
// The property is a local of a function that is not on the current stack
EWTR_NotInScope,
// There is no debug object selected
EWTR_NoDebugObject,
// There is no property related to the pin
EWTR_NoProperty
};
// Gets the watched tooltip for a specified site
static EWatchTextResult GetWatchText(FString& OutWatchText, UBlueprint* Blueprint, UObject* ActiveObject, const UEdGraphPin* WatchPin);
// Gets the debug info for a specified site
static EWatchTextResult GetDebugInfo(FDebugInfo& OutDebugInfo, UBlueprint* Blueprint, UObject* ActiveObject, const UEdGraphPin* WatchPin);
//@TODO: Pretty lame way to handle this messaging, ideally the entire Info object gets pushed into the editor when intraframe debugging is triggered!
// This doesn't work properly if there is more than one blueprint editor open at once either (one will consume it, the others will be left in the cold)
static FText GetAndClearLastExceptionMessage();
protected:
static void CheckBreakConditions(UEdGraphNode* NodeStoppedAt, bool bHitBreakpoint, int32 BreakpointOffset, bool& InOutBreakExecution);
static void AttemptToBreakExecution(UBlueprint* BlueprintObj, const UObject* ActiveObject, const FFrame& StackFrame, const FBlueprintExceptionInfo& Info, UEdGraphNode* NodeStoppedAt, int32 DebugOpcodeOffset);
static void GetDebugInfo_InContainer(int32 Index, FDebugInfo& DebugInfo, FProperty* Property, const void* Data);
static void GetDebugInfoInternal(FDebugInfo& DebugInfo, FProperty* Property, const void* PropertyValue);
/**
* @brief Helper function for converting between blueprint and debuggable data
* output params are only valid if the return result is EWatchTextResult::EWTR_Valid
*
* @param Blueprint Active blueprint that is being debugged
* @param ActiveObject Instance of the object that is being debugged
* @param WatchPin The pin where this debug breakpoint is from
* @param OutProperty Property of interest
* @param OutData Populated with the property address of interest
* @param OutDelta Populated with the same thing as OutData
* @param OutParent Populated with the active object
* @param SeenObjects Used to track what objects have been traversed to find the OutProperty address
* @return EWTR_Valid if the debug data could be found, otherwise an appropriate error code
*/
static EWatchTextResult FindDebuggingData(UBlueprint* Blueprint, UObject* ActiveObject, const UEdGraphPin* WatchPin, FProperty*& OutProperty, void*& OutData, void*& OutDelta, UObject*& OutParent, TArray<UObject*>& SeenObjects, bool* OutbShouldUseContainerOffset = nullptr);
private:
FKismetDebugUtilities() {}
};
//////////////////////////////////////////////////////////////////////////