// Copyright Epic Games, Inc. All Rights Reserved. #include "NiagaraMessages.h" #include "NiagaraEditorUtilities.h" #include "NiagaraMessageManager.h" #include "NiagaraMessageUtilities.h" #include "NiagaraScriptSource.h" #include "NiagaraNodeFunctionCall.h" #include "NiagaraNodeEmitter.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(NiagaraMessages) #define LOCTEXT_NAMESPACE "NiagaraMessages" const FName FNiagaraMessageTopics::CompilerTopicName = "Compiler"; const FName FNiagaraMessageTopics::ObjectTopicName = "Object"; FText INiagaraMessage::GenerateMessageTitle() const { return FText::GetEmpty(); } FNiagaraMessageCompileEvent::FNiagaraMessageCompileEvent( const FNiagaraCompileEvent& InCompileEvent , TArray& InContextScriptNamesAndAssetPaths , TOptional& InOwningScriptNameAndUsageText , TOptional& InCompiledScriptNameAndAssetPath , const TArray& InAssociatedObjectKeys ) : INiagaraMessage(InAssociatedObjectKeys) , CompileEvent(InCompileEvent) , ContextScriptNamesAndAssetPaths(InContextScriptNamesAndAssetPaths) , OwningScriptNameAndUsageText(InOwningScriptNameAndUsageText) , CompiledScriptNameAndAssetPath(InCompiledScriptNameAndAssetPath) { } TSharedRef FNiagaraMessageCompileEvent::GenerateTokenizedMessage() const { EMessageSeverity::Type MessageSeverity = EMessageSeverity::Info; switch (CompileEvent.Severity) { case FNiagaraCompileEventSeverity::Error: MessageSeverity = EMessageSeverity::Error; break; case FNiagaraCompileEventSeverity::Warning: MessageSeverity = EMessageSeverity::Warning; break; case FNiagaraCompileEventSeverity::Display: MessageSeverity = EMessageSeverity::Info; break; // log is still treated like an info case FNiagaraCompileEventSeverity::Log: MessageSeverity = EMessageSeverity::Info; break; default: ensureMsgf(false, TEXT("Compile event severity type not handled!")); MessageSeverity = EMessageSeverity::Info; break; } TSharedRef Message = FTokenizedMessage::Create(MessageSeverity); //Add message from compile event at start of message if (CompiledScriptNameAndAssetPath.IsSet()) { //Compile event is from a script asset if (ContextScriptNamesAndAssetPaths.Num() == 0) { if (CompileEvent.PinGuid != FGuid()) { Message->AddToken(FNiagaraCompileEventToken::Create(CompiledScriptNameAndAssetPath.GetValue().ScriptAssetPathString, FText::FromString(CompileEvent.Message), CompileEvent.NodeGuid, CompileEvent.PinGuid)); } else if (CompileEvent.NodeGuid != FGuid()) { Message->AddToken(FNiagaraCompileEventToken::Create(CompiledScriptNameAndAssetPath.GetValue().ScriptAssetPathString, FText::FromString(CompileEvent.Message), CompileEvent.NodeGuid)); } else { Message->AddToken(FTextToken::Create(FText::FromString(CompileEvent.Message))); } } else { if (CompileEvent.PinGuid != FGuid()) { Message->AddToken(FNiagaraCompileEventToken::Create(ContextScriptNamesAndAssetPaths.Last().ScriptAssetPathString, FText::FromString(CompileEvent.Message), CompileEvent.NodeGuid, CompileEvent.PinGuid)); } else if (CompileEvent.NodeGuid != FGuid()) { Message->AddToken(FNiagaraCompileEventToken::Create(ContextScriptNamesAndAssetPaths.Last().ScriptAssetPathString, FText::FromString(CompileEvent.Message), CompileEvent.NodeGuid)); } else { Message->AddToken(FNiagaraCompileEventToken::Create(ContextScriptNamesAndAssetPaths.Last().ScriptAssetPathString, FText::FromString(CompileEvent.Message))); } } } else { //Compile event is from an emitter or system asset if (ContextScriptNamesAndAssetPaths.Num() == 0) { Message->AddToken(FTextToken::Create(FText::FromString(CompileEvent.Message))); } else if (CompileEvent.PinGuid != FGuid()) { Message->AddToken(FNiagaraCompileEventToken::Create(ContextScriptNamesAndAssetPaths.Last().ScriptAssetPathString, FText::FromString(CompileEvent.Message), CompileEvent.NodeGuid, CompileEvent.PinGuid)); } else if (CompileEvent.NodeGuid != FGuid()) { Message->AddToken(FNiagaraCompileEventToken::Create(ContextScriptNamesAndAssetPaths.Last().ScriptAssetPathString, FText::FromString(CompileEvent.Message), CompileEvent.NodeGuid)); } else { Message->AddToken(FNiagaraCompileEventToken::Create(ContextScriptNamesAndAssetPaths.Last().ScriptAssetPathString, FText::FromString(CompileEvent.Message))); } } //Now add the owning script name and usage if it is set if (OwningScriptNameAndUsageText.IsSet()) { Message->AddToken(FTextToken::Create(OwningScriptNameAndUsageText.GetValue())); } //Finally add the context stack of the scripts that were passed through to get to the originating graph for (const FNiagaraScriptNameAndAssetPath& ScriptNameAndPath : ContextScriptNamesAndAssetPaths) { Message->AddToken(FNiagaraCompileEventToken::Create(ScriptNameAndPath.ScriptAssetPathString, FText::FromString(*ScriptNameAndPath.ScriptNameString))); } return Message; } FText FNiagaraMessageCompileEvent::GenerateMessageText() const { if (OwningScriptNameAndUsageText.IsSet()) { return FText::Format(LOCTEXT("MessageAndOwnerFormat", "{0}{1}"), FText::FromString(CompileEvent.Message), OwningScriptNameAndUsageText.GetValue()); } else { return FText::FromString(CompileEvent.Message); } } FText FNiagaraMessageCompileEvent::GenerateMessageTitle() const { EStackIssueSeverity StackIssueSeverity; switch (CompileEvent.Severity) { case FNiagaraCompileEventSeverity::Error: StackIssueSeverity = EStackIssueSeverity::Error; break; case FNiagaraCompileEventSeverity::Warning: StackIssueSeverity = EStackIssueSeverity::Warning; break; case FNiagaraCompileEventSeverity::Display: StackIssueSeverity = EStackIssueSeverity::Info; break; default: StackIssueSeverity = EStackIssueSeverity::Info; break; } if (!CompileEvent.ShortDescription.IsEmpty()) { return FText::FromString(CompileEvent.ShortDescription); } else { return FNiagaraMessageUtilities::GetShortDescriptionFromSeverity(StackIssueSeverity); } } void FNiagaraMessageCompileEvent::GenerateLinks(TArray& OutLinkDisplayNames, TArray& OutLinkNavigationActions) const { // TODO: Do this without instantiating these tokens. if ((CompiledScriptNameAndAssetPath.IsSet() || ContextScriptNamesAndAssetPaths.Num() > 0) && (CompileEvent.NodeGuid.IsValid() || CompileEvent.PinGuid.IsValid())) { FString AssetPath = CompiledScriptNameAndAssetPath.IsSet() ? CompiledScriptNameAndAssetPath.GetValue().ScriptAssetPathString : ContextScriptNamesAndAssetPaths.Last().ScriptAssetPathString; TSharedPtr NavigateToSourceToken; if (CompileEvent.PinGuid.IsValid()) { NavigateToSourceToken = FNiagaraCompileEventToken::Create(AssetPath, FText::FromString(CompileEvent.Message), CompileEvent.NodeGuid, CompileEvent.PinGuid); } else if (CompileEvent.NodeGuid.IsValid()) { NavigateToSourceToken = FNiagaraCompileEventToken::Create(AssetPath, FText::FromString(CompileEvent.Message), CompileEvent.NodeGuid); } if (NavigateToSourceToken.IsValid()) { OutLinkDisplayNames.Add(LOCTEXT("NavigateToMessageSource", "Navigate to message source")); OutLinkNavigationActions.Add(FSimpleDelegate::CreateLambda([NavigateToSourceToken]() { NavigateToSourceToken->GetOnMessageTokenActivated().Execute(NavigateToSourceToken.ToSharedRef()); })); } } for (const FNiagaraScriptNameAndAssetPath& ScriptNameAndPath : ContextScriptNamesAndAssetPaths) { OutLinkDisplayNames.Add(FText::Format(LOCTEXT("NavigateToAssetFormat", "Navigate to asset: {0}"), FText::FromString(*ScriptNameAndPath.ScriptNameString))); TSharedRef NavigateToAssetToken = FNiagaraCompileEventToken::Create(ScriptNameAndPath.ScriptAssetPathString, FText::FromString(*ScriptNameAndPath.ScriptNameString)); OutLinkNavigationActions.Add(FSimpleDelegate::CreateLambda([NavigateToAssetToken]() { NavigateToAssetToken->GetOnMessageTokenActivated().Execute(NavigateToAssetToken); })); } } FNiagaraMessageJobCompileEvent::FNiagaraMessageJobCompileEvent( const FNiagaraCompileEvent& InCompileEvent , const TWeakObjectPtr& InOriginatingScriptWeakObjPtr , FGuid InCompiledScriptVersion , const TOptional& InOwningScriptNameString , const TOptional& InSourceScriptAssetPath ) : CompileEvent(InCompileEvent) , OriginatingScriptWeakObjPtr(InOriginatingScriptWeakObjPtr) , CompiledScriptVersion(InCompiledScriptVersion) , OwningScriptNameString(InOwningScriptNameString) , SourceScriptAssetPath(InSourceScriptAssetPath) { } TSharedRef FNiagaraMessageJobCompileEvent::GenerateNiagaraMessage() const { TArray ContextStackGuids = CompileEvent.StackGuids; if (OriginatingScriptWeakObjPtr.IsValid()) { const UNiagaraScriptSourceBase* FunctionScriptSourceBase = OriginatingScriptWeakObjPtr->GetSource(CompiledScriptVersion); checkf(FunctionScriptSourceBase->IsA(), TEXT("Script source for function call node is not assigned or is not of type UNiagaraScriptSource!")) const UNiagaraScriptSource* FunctionScriptSource = Cast(FunctionScriptSourceBase); checkf(FunctionScriptSource, TEXT("Script source base was somehow not a derived type!")); const UNiagaraGraph* ScriptGraph = FunctionScriptSource->NodeGraph; checkf(ScriptGraph, TEXT("Function Script does not have a UNiagaraGraph!")); TArray ContextScriptNamesAndPaths; TOptional CompiledScriptNameAndPath; TOptional OwningScriptNameAndUsageText; TOptional ScriptNameAndPathsGetterFailureReason; TOptional OwningScriptNameStringCopy = OwningScriptNameString; TArray ContextObjectKeys; bool bSuccessfullyFoundScripts = RecursiveGetScriptNamesAndAssetPathsFromContextStack(ContextStackGuids, CompileEvent.NodeGuid, ScriptGraph, ContextScriptNamesAndPaths, OwningScriptNameStringCopy, ScriptNameAndPathsGetterFailureReason, ContextObjectKeys); if (bSuccessfullyFoundScripts == false) { //If we can't find the scripts in the context stack of the compile event, return a message with the error and ask for a recompile. return MakeShared(ScriptNameAndPathsGetterFailureReason.GetValue(), EMessageSeverity::Error, FNiagaraMessageTopics::CompilerTopicName); } const ENiagaraScriptUsage ScriptUsage = OriginatingScriptWeakObjPtr->GetUsage(); if (OwningScriptNameStringCopy.IsSet()) { FString ScriptAndUsage = OwningScriptNameStringCopy.GetValue(); const TOptional ScriptUsageInStackString = FNiagaraMessageManager::GetStringForScriptUsageInStack(ScriptUsage); if (ScriptUsageInStackString.IsSet()) { ScriptAndUsage = ScriptAndUsage + FString(", ") + ScriptUsageInStackString.GetValue() + FString(", "); } OwningScriptNameAndUsageText = TOptional(FText::FromString(ScriptAndUsage)); } if (SourceScriptAssetPath.IsSet()) { //If this compile event is from a script, set the compiled script name and asset path so the user can navigate to errors locally. CompiledScriptNameAndPath = TOptional(FNiagaraScriptNameAndAssetPath(OriginatingScriptWeakObjPtr->GetName(), SourceScriptAssetPath.GetValue())); } return MakeShared(CompileEvent, ContextScriptNamesAndPaths, OwningScriptNameAndUsageText, CompiledScriptNameAndPath, ContextObjectKeys); } //The originating script weak ptr is no longer valid, send an error message asking for recompile. FText MessageText = LOCTEXT("CompileEventMessageJobFail", "Cached info for compile event is out of date, recompile to get full info. Event: {0}"); //Add in the message of the compile event for visibility on which compile events do not have info. MessageText = FText::Format(MessageText, FText::FromString(CompileEvent.Message)); return MakeShared(MessageText, EMessageSeverity::Error, FNiagaraMessageTopics::CompilerTopicName); } bool FNiagaraMessageJobCompileEvent::RecursiveGetScriptNamesAndAssetPathsFromContextStack( TArray& InContextStackNodeGuids , FGuid NodeGuid , const UNiagaraGraph* InGraphToSearch , TArray& OutContextScriptNamesAndAssetPaths , TOptional& OutEmitterName , TOptional& OutFailureReason , TArray& OutContextNodeObjectKeys ) const { checkf(InGraphToSearch, TEXT("Failed to get a node graph to search!")); if (NodeGuid.IsValid()) { TObjectPtr const* EventNodePtr = InGraphToSearch->Nodes.FindByPredicate([NodeGuid](UEdGraphNode* Node) { return Node->NodeGuid == NodeGuid; }); if (EventNodePtr != nullptr) { OutContextNodeObjectKeys.AddUnique(FObjectKey(*EventNodePtr)); } } if (InContextStackNodeGuids.Num() == 0) { //StackGuids arr has been cleared out which means we have walked the entire context stack. return true; } // Search in the current graph for a node with a GUID that matches a GUID in the list of Function Call and Emitter node GUIDs that define the context stack for a compile event auto FindNodeInGraphWithContextStackGuid = [&InGraphToSearch, &InContextStackNodeGuids]()->TOptional { for (UEdGraphNode* GraphNode : InGraphToSearch->Nodes) { for (int i = 0; i < InContextStackNodeGuids.Num(); i++) { if (GraphNode->NodeGuid == InContextStackNodeGuids[i]) { InContextStackNodeGuids.RemoveAt(i); return GraphNode; } } } return TOptional(); }; TOptional ContextNode = FindNodeInGraphWithContextStackGuid(); if (ContextNode.IsSet()) { // found a node in the current graph that has a GUID in the context list OutContextNodeObjectKeys.Add(FObjectKey(ContextNode.GetValue())); UNiagaraNodeFunctionCall* FunctionCallNode = Cast(ContextNode.GetValue()); if (FunctionCallNode) { // node is a function call node, now get the Niagara Script assigned to this node, add a message token and recursively call into the graph of that script. UNiagaraScript* FunctionCallNodeAssignedScript = FunctionCallNode->FunctionScript; if (FunctionCallNodeAssignedScript == nullptr) { FText FailureReason = LOCTEXT("GenerateCompileEventMessage_FunctionCallNodeScriptNotFound", "Script for Function Call Node \"{0}\" not found!"); OutFailureReason = FText::Format(FailureReason, FText::FromString(FunctionCallNode->GetFunctionName())); return false; } UNiagaraScriptSourceBase* FunctionScriptSourceBase = FunctionCallNode->GetFunctionScriptSource(); if (FunctionScriptSourceBase == nullptr) { FText FailureReason = LOCTEXT("GenerateCompileEventMessage_FunctionCallNodeScriptSourceBaseNotFound", "Source Script for Function Call Node \"{0}\" not found!"); OutFailureReason = FText::Format(FailureReason, FText::FromString(FunctionCallNode->GetFunctionName())); return false; } UNiagaraScriptSource* FunctionScriptSource = Cast(FunctionScriptSourceBase); checkf(FunctionScriptSource, TEXT("Script source base was somehow not a derived type!")); UNiagaraGraph* FunctionScriptGraph = FunctionScriptSource->NodeGraph; if (FunctionScriptGraph == nullptr) { FText FailureReason = LOCTEXT("GenerateCompileEventMessage_FunctionCallNodeGraphNotFound", "Graph for Function Call Node \"{0}\" not found!"); OutFailureReason = FText::Format(FailureReason, FText::FromString(FunctionCallNode->GetFunctionName())); return false; } OutContextScriptNamesAndAssetPaths.Add(FNiagaraScriptNameAndAssetPath(FunctionCallNodeAssignedScript->GetName(), FunctionCallNodeAssignedScript->GetPathName())); return RecursiveGetScriptNamesAndAssetPathsFromContextStack(InContextStackNodeGuids, NodeGuid, FunctionScriptGraph, OutContextScriptNamesAndAssetPaths, OutEmitterName, OutFailureReason, OutContextNodeObjectKeys); } UNiagaraNodeEmitter* EmitterNode = Cast(ContextNode.GetValue()); if (EmitterNode) { // node is an emitter node, now get the Emitter name, add a message token and recursively call into the graph of that emitter. UNiagaraScriptSource* EmitterScriptSource = EmitterNode->GetScriptSource(); checkf(EmitterScriptSource, TEXT("Emitter Node does not have a Script Source!")); UNiagaraGraph* EmitterScriptGraph = EmitterScriptSource->NodeGraph; checkf(EmitterScriptGraph, TEXT("Emitter Script Source does not have a UNiagaraGraph!")); OutEmitterName = EmitterNode->GetEmitterUniqueName(); return RecursiveGetScriptNamesAndAssetPathsFromContextStack(InContextStackNodeGuids, NodeGuid, EmitterScriptGraph, OutContextScriptNamesAndAssetPaths, OutEmitterName, OutFailureReason, OutContextNodeObjectKeys); } checkf(false, TEXT("Matching node is not a function call or emitter node!")); } FText FailureReason = LOCTEXT("CompileEventMessageGenerator_CouldNotFindMatchingNodeGUID", "Failed to walk the entire context stack, is this compile event out of date ? Event: '{0}'"); OutFailureReason = FText::Format(FailureReason, FText::FromString(CompileEvent.Message)); return false; } FNiagaraStackMessage::FNiagaraStackMessage() { } FNiagaraStackMessage::FNiagaraStackMessage(const FText& InMessageText, const FText& InShortDescription, ENiagaraMessageSeverity InSeverity, bool bInAllowDismissal, FGuid InGuid) { MessageText = InMessageText; ShortDescription = InShortDescription; MessageSeverity = InSeverity; bAllowDismissal = bInAllowDismissal; Guid = InGuid; } TSharedRef FNiagaraMessageText::GenerateTokenizedMessage() const { return FTokenizedMessage::Create(MessageSeverity, MessageText); } FText FNiagaraMessageText::GenerateMessageText() const { return MessageText; } FText FNiagaraMessageText::GenerateMessageTitle() const { return ShortDescription; } const uint32 INiagaraMessage::GetMessageTopicBitflag() const { if (MessageTopicBitflag == 0) { MessageTopicBitflag = FNiagaraMessageManager::Get()->GetMessageTopicBitflag(GetMessageTopic()); } return MessageTopicBitflag; } void UNiagaraMessageDataText::Init(const FText& InMessageText, const ENiagaraMessageSeverity InMessageSeverity, const FName& InTopicName) { MessageText = InMessageText; MessageSeverity = InMessageSeverity; TopicName = InTopicName; } void UNiagaraMessageDataText::Init(const FText& InMessageText, const FText& InShortDescription, const ENiagaraMessageSeverity InMessageSeverity, const FName& InTopicName) { MessageText = InMessageText; ShortDescription = InShortDescription; MessageSeverity = InMessageSeverity; TopicName = InTopicName; } TSharedRef UNiagaraMessageDataText::GenerateNiagaraMessage(const FGenerateNiagaraMessageInfo& InGenerateInfo) const { const TArray& Links = InGenerateInfo.GetLinks(); if (Links.Num() > 0) { return MakeShared(MessageText, ShortDescription, EMessageSeverity::Type(MessageSeverity), TopicName, InGenerateInfo.GetDismissHandler(), Links, InGenerateInfo.GetAssociatedObjectKeys()); } return MakeShared(MessageText, ShortDescription, EMessageSeverity::Type(MessageSeverity), TopicName, InGenerateInfo.GetDismissHandler(), InGenerateInfo.GetAssociatedObjectKeys()); } TSharedRef FNiagaraMessageTextWithLinks::GenerateTokenizedMessage() const { TSharedRef TokenizedMessage = FNiagaraMessageText::GenerateTokenizedMessage(); for (const FLinkNameAndDelegate& LinkNameAndDelegate : Links) { TSharedRef LinkActionToken = FActionToken::Create(LinkNameAndDelegate.LinkNameText, FText(), FOnActionTokenExecuted(LinkNameAndDelegate.LinkDelegate)); TokenizedMessage->AddToken(LinkActionToken); } return TokenizedMessage; } void FNiagaraMessageTextWithLinks::GenerateLinks(TArray& OutLinkDisplayNames, TArray& OutLinkNavigationActions) const { for (const FLinkNameAndDelegate& LinkInfo : Links) { OutLinkDisplayNames.Add(LinkInfo.LinkNameText); OutLinkNavigationActions.Add(LinkInfo.LinkDelegate); } } #undef LOCTEXT_NAMESPACE //NiagaraMessages