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

445 lines
17 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Stats/Stats.h"
#include "UObject/Object.h"
#include "EdGraph/EdGraphNode.h"
#include "EdGraph/EdGraphPin.h"
#include "UObject/ObjectKey.h"
#if WITH_EDITOR
#include "Logging/TokenizedMessage.h"
#include "Misc/CompilationResult.h"
#include "EdGraphToken.h"
#endif
class FTokenizedMessage;
class IMessageLogListing;
#if WITH_EDITOR
/** This class maps from final objects to their original source object, across cloning, autoexpansion, etc... */
class UNREALED_API FBacktrackMap
{
protected:
// Maps from transient object created during compiling to original 'source code' object
TMap<UObject const*, UObject*> SourceBacktrackMap;
// Maps from transient pins created during compiling to original 'source pin' object
TMap<UEdGraphPin*, UEdGraphPin*> PinSourceBacktrackMap;
public:
/** Update the source backtrack map to note that NewObject was most closely generated/caused by the SourceObject */
void NotifyIntermediateObjectCreation(UObject* NewObject, UObject* SourceObject);
/** Update the pin source backtrack map to note that NewPin was most closely generated/caused by the SourcePin */
void NotifyIntermediatePinCreation(UEdGraphPin* NewPin, UEdGraphPin* SourcePin);
/** Returns the true source object for the passed in object */
UObject* FindSourceObject(UObject* PossiblyDuplicatedObject);
UObject const* FindSourceObject(UObject const* PossiblyDuplicatedObject) const;
UEdGraphPin* FindSourcePin(UEdGraphPin* PossiblyDuplicatedPin);
UEdGraphPin const* FindSourcePin(UEdGraphPin const* PossiblyDuplicatedPin) const;
};
/** This class represents a log of compiler output lines (errors, warnings, and information notes), each of which can be a rich tokenized message */
class UNREALED_API FCompilerResultsLog
{
// Compiler event
struct FCompilerEvent
{
FString Name;
uint32 Counter;
double StartTime;
double FinishTime;
TSharedPtr<FCompilerEvent> ParentEventScope;
TArray<TSharedRef<FCompilerEvent>> ChildEvents;
FCompilerEvent(TSharedPtr<FCompilerEvent> InParentEventScope = nullptr)
: Name(TEXT("")), Counter(0), StartTime(0.0), FinishTime(0.0), ParentEventScope(InParentEventScope)
{
}
void Start(const TCHAR* InName)
{
Name = InName;
StartTime = FPlatformTime::Seconds();
}
void Finish()
{
FinishTime = FPlatformTime::Seconds();
}
};
// Current compiler event scope
TSharedPtr<FCompilerEvent> CurrentEventScope;
public:
// List of all tokenized messages
TArray<TSharedRef<FTokenizedMessage>> Messages;
// Number of error messages
int32 NumErrors;
// Number of warnings
int32 NumWarnings;
// Should we be silent?
bool bSilentMode;
// Should we log only Info messages, or all messages?
bool bLogInfoOnly;
// Should nodes mentioned in messages be annotated for display with that message?
bool bAnnotateMentionedNodes;
// Should detailed results be appended to the final summary log?
bool bLogDetailedResults;
// Minimum event time (ms) for inclusion into the final summary log
int EventDisplayThresholdMs;
/** Tracks nodes that produced errors/warnings */
TSet<TWeakObjectPtr<UEdGraphNode>> AnnotatedNodes;
protected:
// Maps from transient object created during compiling to original 'source code' object
FBacktrackMap SourceBacktrackMap;
// Name of the source object being compiled
FString SourcePath;
// Map to track intermediate tunnel nodes back to the intermediate expansion tunnel instance.
TMap<TWeakObjectPtr<const UEdGraphNode>, TWeakObjectPtr<const UEdGraphNode>> IntermediateTunnelNodeToTunnelInstanceMap;
// Map to track intermediate nodes back to the source macro instance nodes
TMap<TWeakObjectPtr<const UEdGraphNode>, TWeakObjectPtr<UEdGraphNode>> FullMacroBacktrackMap;
public:
FCompilerResultsLog(bool bIsCompatibleWithEvents = true);
virtual ~FCompilerResultsLog();
/** Register this log with the MessageLog module */
static void Register();
/** Unregister this log from the MessageLog module */
static void Unregister();
/** Accessor for the LogName, so it can be opened elsewhere */
static FName GetLogName() { return Name; }
/** Set the source name for the final log summary */
void SetSourcePath(const FString& InSourcePath)
{
SourcePath = InSourcePath;
}
/**
* Write an error in to the compiler log.
* Note: @@ will be replaced by node or pin links for nodes/pins passed via varargs
*/
template <typename... Args>
TSharedRef<FTokenizedMessage> Error(const TCHAR* Format, Args... args)
{
++NumErrors;
TSharedRef<FTokenizedMessage> Line = FTokenizedMessage::Create(EMessageSeverity::Error);
InternalLogMessage(NAME_None, Format, Line, args...);
return Line;
}
/**
* Write a warning in to the compiler log.
* Note: @@ will be replaced by node or pin links for nodes/pins passed via varargs
*/
template <typename... Args>
TSharedRef<FTokenizedMessage> Warning(const TCHAR* Format, Args... args)
{
++NumWarnings;
TSharedRef<FTokenizedMessage> Line = FTokenizedMessage::Create(EMessageSeverity::Warning);
InternalLogMessage(NAME_None, Format, Line, args...);
return Line;
}
/**
* Write a warning in to the compiler log.
* Note: @@ will be replaced by node or pin links for nodes/pins passed via varargs
*/
template <typename... Args>
void Warning(FName ID, const TCHAR* Format, Args... args)
{
if (!IsMessageEnabled(ID))
{
return;
}
++NumWarnings;
TSharedRef<FTokenizedMessage> Line = FTokenizedMessage::Create(EMessageSeverity::Warning);
InternalLogMessage(ID, Format, Line, args...);
return;
}
/**
* Write a note in to the compiler log.
* Note: @@ will be replaced by node or pin links for nodes/pins passed via varargs
*/
template <typename... Args>
TSharedRef<FTokenizedMessage> Note(const TCHAR* Format, Args... args)
{
TSharedRef<FTokenizedMessage> Line = FTokenizedMessage::Create(EMessageSeverity::Info);
InternalLogMessage(NAME_None, Format, Line, args...);
return Line;
}
/**
* Store a potential error for a given node in the compiler log. All messages for the node can be committed to the log later by calling CommitPotentialMessages
* Note: @@ will be replaced by node or pin links for nodes/pins passed via varargs
*/
template <typename... Args>
TSharedRef<FTokenizedMessage> StorePotentialError(const UEdGraphNode* Source, const TCHAR* Format, Args... args)
{
TSharedRef<FTokenizedMessage> Line = FTokenizedMessage::Create(EMessageSeverity::Error);
TArray<UEdGraphNode*> SourceNodes;
Tokenize(Format, *Line, SourceNodes, args...);
PotentialMessages.FindOrAdd(Source).Add(Line);
return Line;
}
/**
* Store a potential warning for a given node in the compiler log. All messages for the node can be committed to the log later by calling CommitPotentialMessages
* Note: @@ will be replaced by node or pin links for nodes/pins passed via varargs
*/
template <typename... Args>
TSharedRef<FTokenizedMessage> StorePotentialWarning(const UEdGraphNode* Source, const TCHAR* Format, Args... args)
{
TSharedRef<FTokenizedMessage> Line = FTokenizedMessage::Create(EMessageSeverity::Warning);
TArray<UEdGraphNode*> SourceNodes;
Tokenize(Format, *Line, SourceNodes, args...);
PotentialMessages.FindOrAdd(Source).Add(Line);
return Line;
}
/**
* Store a potential note for a given node in the compiler log. All messages for the node can be committed to the log later by calling CommitPotentialMessages
* Note: @@ will be replaced by node or pin links for nodes/pins passed via varargs
*/
template <typename... Args>
TSharedRef<FTokenizedMessage> StorePotentialNote(const UEdGraphNode* Source, const TCHAR* Format, Args... args)
{
TSharedRef<FTokenizedMessage> Line = FTokenizedMessage::Create(EMessageSeverity::Info);
TArray<UEdGraphNode*> SourceNodes;
Tokenize(Format, *Line, SourceNodes, args...);
PotentialMessages.FindOrAdd(Source).Add(Line);
return Line;
}
/**
* Store an already tokenized message.
*/
void AddTokenizedMessage(TSharedRef<FTokenizedMessage> InMessage)
{
switch (InMessage->GetSeverity())
{
case EMessageSeverity::Error:
case EMessageSeverity::CriticalError:
++NumErrors;
break;
case EMessageSeverity::Warning:
case EMessageSeverity::PerformanceWarning:
++NumWarnings;
break;
}
Messages.Add(InMessage);
}
/**
* Commit all stored potential messages for a given node. Returns true if any messages were written.
*/
bool CommitPotentialMessages(UEdGraphNode* Source);
/** Update the source backtrack map to note that NewObject was most closely generated/caused by the SourceObject */
void NotifyIntermediateObjectCreation(UObject* NewObject, UObject* SourceObject);
void NotifyIntermediatePinCreation(UEdGraphPin* NewObject, UEdGraphPin* SourceObject);
/** Update the expansion map to note that Node was expanded from OuterTunnelInstance, both the node and tunnel instance should be intermediate nodes */
void NotifyIntermediateTunnelNode(const UEdGraphNode* Node, const UEdGraphNode* OuterTunnelInstance);
/** Update the map that tracks nodes created by macro instance nodes */
void NotifyIntermediateMacroNode(UEdGraphNode* SourceNode, const UEdGraphNode* IntermediateNode);
/** Returns the true source object for the passed in object */
UObject* FindSourceObject(UObject* PossiblyDuplicatedObject);
UObject const* FindSourceObject(UObject const* PossiblyDuplicatedObject) const;
UObject* FindSourceMacroInstance(const UEdGraphNode* IntermediateNode) const;
/** Returns the intermediate tunnel instance that generated the node */
const UEdGraphNode* GetIntermediateTunnelInstance(const UEdGraphNode* IntermediateNode) const;
/** Returns a int32 used to uniquely identify an action for the latent action manager */
int32 CalculateStableIdentifierForLatentActionManager(const UEdGraphNode* Node);
/** Returns the true source object for the passed in object; does type checking on the result */
template <typename T>
T* FindSourceObjectTypeChecked(UObject* PossiblyDuplicatedObject)
{
return CastChecked<T>(FindSourceObject(PossiblyDuplicatedObject));
}
template <typename T>
T const* FindSourceObjectTypeChecked(UObject const* PossiblyDuplicatedObject) const
{
return CastChecked<T const>(FindSourceObject(PossiblyDuplicatedObject));
}
UEdGraphPin* FindSourcePin(UEdGraphPin* PossiblyDuplicatedPin);
const UEdGraphPin* FindSourcePin(const UEdGraphPin* PossiblyDuplicatedPin) const;
void Append(FCompilerResultsLog const& Other);
/** Begin a new compiler event */
void BeginEvent(const TCHAR* InName);
/** End the current compiler event */
void EndEvent();
/** Access the current event target log */
static FCompilerResultsLog* GetEventTarget()
{
return CurrentEventTarget;
}
/** Get the message log listing for this blueprint */
static TSharedRef<IMessageLogListing> GetBlueprintMessageLog(UBlueprint* InBlueprint);
protected:
/** Helper method to add a child event to the given parent event scope */
void AddChildEvent(TSharedPtr<FCompilerEvent>& ParentEventScope, TSharedRef<FCompilerEvent>& ChildEventScope);
void InternalLogMessage(FName MessageID, const TSharedRef<FTokenizedMessage>& Message, const TArray<UEdGraphNode*>& SourceNodes);
void Tokenize(const TCHAR* Text, FTokenizedMessage& OutMessage, TArray<UEdGraphNode*>& OutSourceNode)
{
OutMessage.AddToken(FTextToken::Create(FText::FromString(Text)));
}
template <typename T, typename... Args>
void Tokenize(const TCHAR* Format, FTokenizedMessage& OutMessage, TArray<UEdGraphNode*>& OutSourceNode, T First, Args... Rest)
{
// read to next "@@":
if (const TCHAR* DelimiterStr = FCString::Strstr(Format, TEXT("@@")))
{
OutMessage.AddToken(FTextToken::Create(FText::FromString(FString(DelimiterStr - Format, Format))));
FEdGraphToken::Create(First, this, OutMessage, OutSourceNode);
const TCHAR* NextChunk = DelimiterStr + FCString::Strlen(TEXT("@@"));
if (*NextChunk)
{
Tokenize(NextChunk, OutMessage, OutSourceNode, Rest...);
}
}
else
{
Tokenize(Format, OutMessage, OutSourceNode);
}
}
template <typename... Args>
void InternalLogMessage(FName MessageID, const TCHAR* Format, const TSharedRef<FTokenizedMessage>& Message, Args... args)
{
// Convention for SourceNode established by the original version of the compiler results log
// was to annotate the error on the first node we can find. I am preserving that behavior
// for this type safe, variadic version:
TArray<UEdGraphNode*> SourceNodes;
Tokenize(Format, *Message, SourceNodes, args...);
InternalLogMessage(MessageID, Message, SourceNodes);
}
/** Links the UEdGraphNode with the LogLine: */
void AnnotateNode(const TArray<UEdGraphNode*>& Nodes, TSharedRef<FTokenizedMessage> LogLine);
/** Internal method to append the final compiler results summary to the MessageLog */
void InternalLogSummary();
/** Internal helper method to recursively append event details into the MessageLog */
void InternalLogEvent(const FCompilerEvent& InEvent, int32 InDepth = 0);
/** Returns true if the user has requested this compiler message be suppressed */
bool IsMessageEnabled(FName ID);
private:
/** Map of stored potential messages indexed by a node. Can be committed to the results log by calling CommitPotentialMessages for that node. */
TMap<FObjectKey, TArray<TSharedRef<FTokenizedMessage>>> PotentialMessages;
/** Parses a compiler log dump to generate tokenized output */
static TArray<TSharedRef<FTokenizedMessage>> ParseCompilerLogDump(const FString& LogDump);
/** Goes to an error given a Message Token */
static void OnGotoError(const class TSharedRef<IMessageToken>& Token);
/** Callback function for binding the global compiler dump to open the static compiler log */
static void GetGlobalModuleCompilerDump(const FString& LogDump, ECompilationResult::Type CompilationResult, bool bShowLog);
/** Searches a token list for referenced UEdGraphNodes, used to update the nodes when a log is committed */
static void GetNodesFromTokens(const TArray<TSharedRef<IMessageToken>>& MessageTokens, TArray<UEdGraphNode*>& OutOwnerNodes);
/** The log's name, for easy re-use */
static const FName Name;
/** The log target for compile events */
static FCompilerResultsLog* CurrentEventTarget;
/** Handle to the registered GetGlobalModuleCompilerDump delegate. */
static FDelegateHandle GetGlobalModuleCompilerDumpDelegateHandle;
};
/** This class will begin a new compile event on construction, and automatically end it when the instance goes out of scope */
class UNREALED_API FScopedCompilerEvent
{
public:
/** Constructor; automatically begins a new event */
FScopedCompilerEvent(const TCHAR* InName)
{
FCompilerResultsLog* ResultsLog = FCompilerResultsLog::GetEventTarget();
if (ResultsLog != nullptr)
{
ResultsLog->BeginEvent(InName);
}
}
/** Destructor; automatically ends the event */
~FScopedCompilerEvent()
{
FCompilerResultsLog* ResultsLog = FCompilerResultsLog::GetEventTarget();
if (ResultsLog != nullptr)
{
ResultsLog->EndEvent();
}
}
};
/** Scope wrapper for the blueprint message log. Ensures we dont leak logs that we dont need (i.e. those that have no messages) */
class UNREALED_API FScopedBlueprintMessageLog
{
public:
FScopedBlueprintMessageLog(UBlueprint* InBlueprint);
~FScopedBlueprintMessageLog();
public:
/** The listing we wrap */
TSharedRef<IMessageLogListing> Log;
/** The generated name of the log */
FName LogName;
};
#if STATS
#define BP_SCOPED_COMPILER_EVENT_STAT(Stat) \
SCOPE_CYCLE_COUNTER(Stat); \
FScopedCompilerEvent PREPROCESSOR_JOIN(ScopedCompilerEvent,__LINE__)(GET_STATDESCRIPTION(Stat))
#else
#define BP_SCOPED_COMPILER_EVENT_STAT(Stat) \
FScopedCompilerEvent PREPROCESSOR_JOIN(ScopedCompilerEvent,__LINE__)(ANSI_TO_TCHAR(#Stat))
#endif
#endif // #if WITH_EDITOR