// Copyright Epic Games, Inc. All Rights Reserved. #include "NiagaraNodeFunctionCall.h" #include "Editor.h" #include "AssetRegistry/AssetRegistryModule.h" #include "EdGraphSchema_Niagara.h" #include "NiagaraComponent.h" #include "NiagaraConstants.h" #include "NiagaraCustomVersion.h" #include "Subsystems/AssetEditorSubsystem.h" #include "NiagaraEditorUtilities.h" #include "NiagaraGraph.h" #include "NiagaraHlslTranslator.h" #include "NiagaraMessages.h" #include "NiagaraNodeInput.h" #include "NiagaraNodeOutput.h" #include "NiagaraNodeParameterMapGet.h" #include "NiagaraNodeParameterMapSet.h" #include "NiagaraScript.h" #include "NiagaraScriptSource.h" #include "NiagaraScriptVariable.h" #include "Widgets/SNiagaraGraphNodeFunctionCallWithSpecifiers.h" #include "Misc/SecureHash.h" #include "Modules/ModuleManager.h" #include "UObject/UnrealType.h" #include "ViewModels/Stack/NiagaraParameterHandle.h" #include "ViewModels/Stack/NiagaraStackGraphUtilities.h" #include "NiagaraNodeStaticSwitch.h" #include "NiagaraSettings.h" #include "ScopedTransaction.h" #include "SourceCodeNavigation.h" #include "UnrealEdGlobals.h" #include "Editor/UnrealEdEngine.h" #include "Preferences/UnrealEdOptions.h" #include "Widgets/Input/SComboButton.h" #include "ToolMenus.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(NiagaraNodeFunctionCall) #define LOCTEXT_NAMESPACE "NiagaraNodeFunctionCall" void UNiagaraNodeFunctionCall::PostLoad() { Super::PostLoad(); if (FunctionScript) { const int32 NiagaraCustomVersion = GetLinkerCustomVersion(FNiagaraCustomVersion::GUID); FunctionScript->ConditionalPostLoad(); if (NiagaraCustomVersion < FNiagaraCustomVersion::ModuleVersioning) { SelectedScriptVersion = FGuid(); InvalidScriptVersionReference = FGuid(); PreviousScriptVersion = FGuid(); } FixupFunctionScriptVersion(); // We need to make sure that the variables that could potentially be used in AllocateDefaultPins have been properly // loaded. Otherwise, we could be out of date. UNiagaraScriptSource* Source = GetFunctionScriptSource(); if (Source) { Source->ConditionalPostLoad(); if (UNiagaraGraph* Graph = Source->NodeGraph) { Graph->ConditionalPostLoad(); // Fix up autogenerated default values if necessary. if (NiagaraCustomVersion < FNiagaraCustomVersion::EnabledAutogeneratedDefaultValuesForFunctionCallNodes) { FPinCollectorArray InputPins; GetInputPins(InputPins); TArray InputNodes; UNiagaraGraph::FFindInputNodeOptions Options; Options.bSort = true; Options.bFilterDuplicates = true; Graph->FindInputNodes(InputNodes, Options); for (UEdGraphPin* InputPin : InputPins) { auto FindInputNodeByName = [&](UNiagaraNodeInput* InputNode) { return InputNode->Input.GetName().ToString() == InputPin->PinName.ToString(); }; UNiagaraNodeInput** MatchingInputNodePtr = InputNodes.FindByPredicate(FindInputNodeByName); if (MatchingInputNodePtr != nullptr) { UNiagaraNodeInput* MatchingInputNode = *MatchingInputNodePtr; SetPinAutoGeneratedDefaultValue(*InputPin, *MatchingInputNode); // If the default value wasn't set, update it with the new autogenerated default. if (InputPin->DefaultValue.IsEmpty()) { InputPin->DefaultValue = InputPin->AutogeneratedDefaultValue; } } } } } } if (NiagaraCustomVersion < FNiagaraCustomVersion::StaticSwitchFunctionPinsUsePersistentGuids) { UpdateStaticSwitchPinsWithPersistentGuids(); } if (NiagaraCustomVersion < FNiagaraCustomVersion::RepopulateFunctionCallNodePinNameBindings) { FNiagaraStackGraphUtilities::PopulateFunctionCallNameBindings(*this); } } else { //Perform some fix up due to a bug with dynamic pins and add pins. //Rebuild the dynamic pins and ensure we have add pins where needed. const int32 NiagaraVer = GetLinkerCustomVersion(FNiagaraCustomVersion::GUID); if (NiagaraVer < FNiagaraCustomVersion::DynamicPinNodeFixup && AllowDynamicPins()) { bool bFoundInputAdd = false; bool bFoundOutputAdd = false; for (const UEdGraphPin* Pin : Pins) { if (IsAddPin(Pin)) { if (Pin->Direction == EGPD_Input) { bFoundInputAdd = true; } else { bFoundOutputAdd = true; } } } if (!bFoundInputAdd) { CreateAddPin(EGPD_Input); } if (!bFoundOutputAdd) { CreateAddPin(EGPD_Output); } } } // Allow data interfaces an opportunity to intercept changes if (Signature.IsValid() && Signature.bMemberFunction) { if ((Signature.Inputs.Num() > 0) && Signature.Inputs[0].GetType().IsDataInterface()) { UNiagaraDataInterface* CDO = CastChecked(Signature.Inputs[0].GetType().GetClass()->GetDefaultObject()); FNiagaraFunctionSignature CopyForComparison = Signature; if (CDO->UpgradeFunctionCall(Signature)) { FunctionDisplayName.Empty(); if (Signature != CopyForComparison) { ReallocatePins(); } } } } UpdatePinTooltips(); // Clean up invalid old references to propagated parameters CleanupPropagatedSwitchValues(); if (FunctionDisplayName.IsEmpty()) { ComputeNodeName(); } if (MessageKeyToMessageMap_DEPRECATED.IsEmpty() == false) { for (auto KeyMessagePair : MessageKeyToMessageMap_DEPRECATED) { MessageStore.AddMessage(KeyMessagePair.Key, KeyMessagePair.Value); } MessageKeyToMessageMap_DEPRECATED.Empty(); } } void UNiagaraNodeFunctionCall::UpgradeDIFunctionCalls() { // We no longer use this upgrade path, but leaving here for convience in case we need to use this route again for other things #if 0 UClass* InterfaceClass = nullptr; UNiagaraDataInterface* InterfaceCDO = nullptr; if (Signature.IsValid() && FunctionScript == nullptr) { if (Signature.Inputs.Num() > 0) { if (Signature.Inputs[0].GetType().IsDataInterface()) { InterfaceClass = Signature.Inputs[0].GetType().GetClass(); InterfaceCDO = Cast(InterfaceClass->GetDefaultObject()); } } } FString UpgradeNote; if (!UpgradeNote.IsEmpty()) { UE_LOG(LogNiagaraEditor, Log, TEXT("Upgradeing Niagara Data Interface fuction call node. This may cause unnessessary recompiles. Please resave these assets if this occurs. Or use fx.UpgradeAllNiagaraAssets.")); UE_LOG(LogNiagaraEditor, Log, TEXT("Node: %s"), *GetFullName()); if (InterfaceCDO) { UE_LOG(LogNiagaraEditor, Log, TEXT("Interface: %s"), *InterfaceCDO->GetFullName()); } UE_LOG(LogNiagaraEditor, Log, TEXT("Function: %s"), *Signature.GetName()); UE_LOG(LogNiagaraEditor, Log, TEXT("Upgrade Note: %s"),* UpgradeNote); } #endif } TSharedPtr UNiagaraNodeFunctionCall::CreateVisualWidget() { if (!FunctionScript && FunctionSpecifiers.Num() == 0) { FunctionSpecifiers = Signature.FunctionSpecifiers; } if (FunctionSpecifiers.Num() == 0) { return Super::CreateVisualWidget(); } else { return SNew(SNiagaraGraphNodeFunctionCallWithSpecifiers, this); } } void UNiagaraNodeFunctionCall::AddOrphanedStaticSwitchPinForDataRetention(FNiagaraVariableBase StaticSwitchVariable, const FString& StaticSwitchPinDefault) { UEdGraphPin* NewPin = AddStaticSwitchInputPin(StaticSwitchVariable); NewPin->DefaultValue = StaticSwitchPinDefault; NewPin->bOrphanedPin = true; } void UNiagaraNodeFunctionCall::RemoveAllDynamicPins() { FScopedTransaction RemovePinTransaction(LOCTEXT("RemoveAllDynamicPinsTransaction", "Remove all dynamic pins")); if(AllowDynamicPins()) { FPinCollectorArray PinArray; GetInputPins(PinArray); for(UEdGraphPin* Pin : PinArray) { if(CanModifyPin(Pin) && IsBaseSignatureOfDataInterfaceFunction(Pin) == false) { RemoveDynamicPin(Pin); } } GetOutputPins(PinArray); for(UEdGraphPin* Pin : PinArray) { if(CanModifyPin(Pin) && IsBaseSignatureOfDataInterfaceFunction(Pin) == false) { RemoveDynamicPin(Pin); } } } } UClass* UNiagaraNodeFunctionCall::GetDIClass()const { if(Signature.IsValid()) { if (Signature.Inputs.Num() > 0) { if (Signature.Inputs[0].GetType().IsDataInterface() && GetValidateDataInterfaces()) { return Signature.Inputs[0].GetType().GetClass(); } } } return nullptr; } void UNiagaraNodeFunctionCall::SetFunctionSpecifier(FName Key, FName Value) { Modify(); FunctionSpecifiers.FindOrAdd(Key) = Value; MarkNodeRequiresSynchronization(__FUNCTION__, true); } UEdGraphPin* UNiagaraNodeFunctionCall::AddStaticSwitchInputPin(FNiagaraVariable Input) { UNiagaraGraph* Graph = GetCalledGraph(); const UEdGraphSchema_Niagara* Schema = Graph->GetNiagaraSchema(); UEdGraphPin* NewPin = CreatePin(EGPD_Input, Schema->TypeDefinitionToPinType(Input.GetType()), Input.GetName()); NewPin->bNotConnectable = true; NewPin->bDefaultValueIsIgnored = FindPropagatedVariable(Input) != nullptr; NewPin->SetSavePinIfOrphaned(true); FGuid PinPersistentGuid; TOptional SwitchDefaultValue = Graph->GetStaticSwitchDefaultValue(Input); TOptional ScriptVariableGuid = Graph->GetScriptVariableGuid(Input); if (SwitchDefaultValue.IsSet() && ScriptVariableGuid.IsSet()) { checkf(Input.GetType().GetSize() == sizeof(FNiagaraInt32), TEXT("Incorrectly sized Input variable for UNiagaraNodeFunctionCall::AddStaticSwitchInputPin(). Size: %d (should be %d). Type: %s. Name: %s. Node: %s. ScriptVariableGuid: %s"), Input.GetType().GetSize(), sizeof(FNiagaraInt32), *Input.GetType().GetName(), *Input.GetName().ToString(), *GetFullName(), *ScriptVariableGuid->ToString()); Input.AllocateData(); Input.SetValue( {*SwitchDefaultValue} ); PinPersistentGuid = *ScriptVariableGuid; } else { FNiagaraEditorUtilities::ResetVariableToDefaultValue(Input); PinPersistentGuid = FGuid(); } NewPin->PersistentGuid = PinPersistentGuid; FString PinDefaultValue; if (Schema->TryGetPinDefaultValueFromNiagaraVariable(Input, PinDefaultValue)) { NewPin->AutogeneratedDefaultValue = PinDefaultValue; NewPin->DefaultValue = PinDefaultValue; } return NewPin; } bool UNiagaraNodeFunctionCall::CanModifyPin(const UEdGraphPin* Pin) const { if(IsAddPin(Pin)) { return false; } if(Signature.IsValid() && (Signature.VariadicInput() || Signature.VariadicOutput())) { if(IsBaseSignatureOfDataInterfaceFunction(Pin) == false) { return true; } } return false; } void UNiagaraNodeFunctionCall::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) { if (PropertyChangedEvent.Property != nullptr) { ReallocatePins(); } Super::PostEditChangeProperty(PropertyChangedEvent); MarkNodeRequiresSynchronization(__FUNCTION__, true); //GetGraph()->NotifyGraphChanged(); } void UNiagaraNodeFunctionCall::AllocateDefaultPins() { if (FunctionScriptAssetObjectPath != NAME_None && FunctionScript == nullptr) { FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); FAssetData ScriptAssetData = AssetRegistryModule.Get().GetAssetByObjectPath(FSoftObjectPath(FunctionScriptAssetObjectPath.ToString())); if (ScriptAssetData.IsValid()) { FunctionScript = Cast(ScriptAssetData.GetAsset()); if (FunctionScript && FunctionScript->IsVersioningEnabled()) { SelectedScriptVersion = FunctionScript->GetExposedVersion().VersionGuid; } } } const UEdGraphSchema_Niagara* Schema = CastChecked(GetSchema()); if (HasValidScriptAndGraph()) { UNiagaraGraph* Graph = GetCalledGraph(); //These pins must be refreshed and kept in the correct order for the function TArray Inputs; TArray Outputs; Graph->GetParameters(Inputs, Outputs); TArray InputNodes; UNiagaraGraph::FFindInputNodeOptions Options; Options.bSort = true; Options.bFilterDuplicates = true; Graph->FindInputNodes(InputNodes, Options); AdvancedPinDisplay = ENodeAdvancedPins::NoPins; for (UNiagaraNodeInput* InputNode : InputNodes) { if (InputNode->IsExposed()) { UEdGraphPin* NewPin = CreatePin(EGPD_Input, Schema->TypeDefinitionToPinType(InputNode->Input.GetType()), InputNode->Input.GetName()); //An inline pin default only makes sense if we are required. //Non exposed or optional inputs will used their own function input nodes defaults when not directly provided by a link. //Special class types cannot have an inline default. NewPin->bDefaultValueIsIgnored = !(InputNode->IsRequired() && InputNode->Input.GetType().GetClass() == nullptr); SetPinAutoGeneratedDefaultValue(*NewPin, *InputNode); NewPin->DefaultValue = NewPin->AutogeneratedDefaultValue; //TODO: Some visual indication of Auto bound pins. //I tried just linking to null but // FNiagaraVariable AutoBoundVar; // ENiagaraInputNodeUsage AutBoundUsage = ENiagaraInputNodeUsage::Undefined; // bool bCanAutoBind = FindAutoBoundInput(InputNode->AutoBindOptions, NewPin, AutoBoundVar, AutBoundUsage); // if (bCanAutoBind) // { // // } if (InputNode->IsHidden()) { NewPin->bAdvancedView = true; AdvancedPinDisplay = ENodeAdvancedPins::Hidden; } else { NewPin->bAdvancedView = false; } } } TArray SwitchNodeInputs = Graph->FindStaticSwitchInputs(); for (FNiagaraVariable& Input : SwitchNodeInputs) { if (Input.IsValid()) { AddStaticSwitchInputPin(Input); } else { UE_LOG(LogNiagaraEditor, Warning, TEXT("Static switch input %s on function call node %s is invalid"), *Input.GetName().ToString(), *GetPathName()); } } for (FNiagaraVariable& Output : Outputs) { UEdGraphPin* NewPin = CreatePin(EGPD_Output, Schema->TypeDefinitionToPinType(Output.GetType()), Output.GetName()); NewPin->bDefaultValueIsIgnored = true; } // Make sure to note that we've synchronized with the external version. CachedChangeId = Graph->GetChangeID(); } else { if (Signature.bRequiresExecPin) { UEdGraphPin* NewPin = CreatePin(EGPD_Input, Schema->TypeDefinitionToPinType(FNiagaraTypeDefinition::GetParameterMapDef()), TEXT("")); NewPin->bDefaultValueIsIgnored = true; } for (FNiagaraVariable& Input : Signature.Inputs) { UEdGraphPin* NewPin = CreatePin(EGPD_Input, Schema->TypeDefinitionToPinType(Input.GetType()), Input.GetName()); NewPin->bDefaultValueIsIgnored = false; NewPin->PinToolTip = Signature.InputDescriptions.Contains(Input) ? Signature.InputDescriptions[Input].ToString() : FString(); FString PinDefaultValue; if (Schema->TryGetPinDefaultValueFromNiagaraVariable(Input, PinDefaultValue)) { NewPin->AutogeneratedDefaultValue = PinDefaultValue; NewPin->DefaultValue = PinDefaultValue; } } if (Signature.bRequiresExecPin) { UEdGraphPin* NewPin = CreatePin(EGPD_Output, Schema->TypeDefinitionToPinType(FNiagaraTypeDefinition::GetParameterMapDef()), TEXT("")); NewPin->bDefaultValueIsIgnored = true; } for (FNiagaraVariableBase& Output : Signature.Outputs) { UEdGraphPin* NewPin = CreatePin(EGPD_Output, Schema->TypeDefinitionToPinType(Output.GetType()), Output.GetName()); NewPin->bDefaultValueIsIgnored = true; NewPin->PinToolTip = Signature.OutputDescriptions.Contains(Output) ? Signature.OutputDescriptions[Output].ToString() : FString(); } if (AllowDynamicPins()) { if(Signature.IsValid() && (Signature.VariadicInput() || Signature.VariadicOutput())) { if(Signature.VariadicInput()) { CreateAddPin(EGPD_Input); } if(Signature.VariadicOutput()) { CreateAddPin(EGPD_Output); } } else { CreateAddPin(EGPD_Input); CreateAddPin(EGPD_Output); } } // We don't reference an external function, so set an invalid id. CachedChangeId = FGuid(); } if (FunctionDisplayName.IsEmpty()) { ComputeNodeName(); } UpdateNodeErrorMessage(); } // Returns true if this node is deprecated bool UNiagaraNodeFunctionCall::IsDeprecated() const { FVersionedNiagaraScriptData* ScriptData = GetScriptData(); if (ScriptData) { return ScriptData->bDeprecated; } return false; } void UNiagaraNodeFunctionCall::UpdateInputNameBinding(const FGuid& BoundVariableGuid, const FName& BoundName) { if (!BoundVariableGuid.IsValid() || BoundName.IsNone()) { return; } BoundPinNames.Add(BoundVariableGuid, BoundName); } FText UNiagaraNodeFunctionCall::GetNodeTitle(ENodeTitleType::Type TitleType) const { FString DetectedName = FunctionScript ? FunctionScript->GetName() : Signature.GetNameString(); if (DetectedName.IsEmpty()) { return FText::FromString(TEXT("Missing ( Was\"") + FunctionDisplayName + TEXT("\")")); } else { return FText::FromString(FName::NameToDisplayString(FunctionDisplayName, false)); } } FText UNiagaraNodeFunctionCall::GetTooltipText()const { if (FunctionScript != nullptr) { return FunctionScript->GetDescription(SelectedScriptVersion); } else if (Signature.IsValid()) { return Signature.Description; } else { return LOCTEXT("NiagaraFuncCallUnknownSignatureTooltip", "Unknown function call"); } } FLinearColor UNiagaraNodeFunctionCall::GetNodeTitleColor() const { return UEdGraphSchema_Niagara::NodeTitleColor_FunctionCall; } bool UNiagaraNodeFunctionCall::CanAddToGraph(UNiagaraGraph* TargetGraph, FString& OutErrorMsg) const { if (Super::CanAddToGraph(TargetGraph, OutErrorMsg) == false) { return false; } UPackage* TargetPackage = TargetGraph->GetOutermost(); TArray FunctionGraphs; UNiagaraScript* SpawningFunctionScript = FunctionScript; // We probably haven't loaded the script yet. Let's do so now so that we can trace its lineage. if (FunctionScriptAssetObjectPath != NAME_None && FunctionScript == nullptr) { FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); FAssetData ScriptAssetData = AssetRegistryModule.Get().GetAssetByObjectPath(FSoftObjectPath(FunctionScriptAssetObjectPath.ToString())); if (ScriptAssetData.IsValid()) { SpawningFunctionScript = Cast(ScriptAssetData.GetAsset()); } } // Now we need to get the graphs referenced by the script that we are about to spawn in. if (SpawningFunctionScript && SpawningFunctionScript->GetSource(SelectedScriptVersion)) { UNiagaraScriptSource* Source = Cast(SpawningFunctionScript->GetSource(SelectedScriptVersion)); if (Source) { UNiagaraGraph* FunctionGraph = Source->NodeGraph; if (FunctionGraph) { FunctionGraph->GetAllReferencedGraphs(FunctionGraphs); } } } // Iterate over each graph referenced by this spawning function call and see if any of them reference the graph that we are about to be spawned into. If // a match is found, then adding us would introduce a cycle and we need to abort the add. for (const UNiagaraGraph* Graph : FunctionGraphs) { UPackage* FunctionPackage = Graph->GetOutermost(); if (FunctionPackage != nullptr && TargetPackage != nullptr && FunctionPackage == TargetPackage) { OutErrorMsg = LOCTEXT("NiagaraFuncCallCannotAddToGraph", "Cannot add to graph because the Function Call used by this node would lead to a cycle.").ToString(); return false; } } return true; } UNiagaraGraph* UNiagaraNodeFunctionCall::GetCalledGraph() const { if (FunctionScript) { if (UNiagaraScriptSource* Source = GetFunctionScriptSource()) { UNiagaraGraph* FunctionGraph = Source->NodeGraph; return FunctionGraph; } } return nullptr; } ENiagaraScriptUsage UNiagaraNodeFunctionCall::GetCalledUsage() const { if (FunctionScript) { return FunctionScript->GetUsage(); } return ENiagaraScriptUsage::Function; } UNiagaraScriptSource* UNiagaraNodeFunctionCall::GetFunctionScriptSource() const { if (FunctionScript == nullptr) { return nullptr; } UNiagaraScriptSourceBase* SourceBase = FunctionScript->GetSource(SelectedScriptVersion); return SourceBase ? CastChecked(SourceBase) : nullptr; } FVersionedNiagaraScriptData* UNiagaraNodeFunctionCall::GetScriptData() const { return FunctionScript ? FunctionScript->GetScriptData(SelectedScriptVersion) : nullptr; } static FText GetFormattedDeprecationMessage(const FVersionedNiagaraScriptData* ScriptData, const FString& FunctionDisplayName) { FFormatNamedArguments Args; Args.Add(TEXT("NodeName"), FText::FromString(FunctionDisplayName)); if (ScriptData->DeprecationRecommendation != nullptr) { Args.Add(TEXT("Recommendation"), FText::FromString(ScriptData->DeprecationRecommendation.GetPathName())); } if (ScriptData->DeprecationMessage.IsEmptyOrWhitespace() == false) { Args.Add(TEXT("Message"), ScriptData->DeprecationMessage); } FText FormatString = LOCTEXT("DeprecationErrorFmtUnknown", "Function call \"{NodeName}\" is deprecated. No recommendation was provided."); if (ScriptData->DeprecationRecommendation != nullptr && ScriptData->DeprecationMessage.IsEmptyOrWhitespace() == false) { FormatString = LOCTEXT("DeprecationErrorFmtMessageAndRecommendation", "Function call \"{NodeName}\" is deprecated. Reason:\n{Message}.\nPlease use {Recommendation} instead."); } else if (ScriptData->DeprecationRecommendation != nullptr) { FormatString = LOCTEXT("DeprecationErrorFmtRecommendation", "Function call \"{NodeName}\" is deprecated. Please use {Recommendation} instead."); } else if (ScriptData->DeprecationMessage.IsEmptyOrWhitespace() == false) { FormatString = LOCTEXT("DeprecationErrorFmtMessage", "Function call \"{NodeName}\" is deprecated. Reason:\n{Message} "); } return FText::Format(FormatString, Args); } void UNiagaraNodeFunctionCall::Compile(FTranslator* Translator, TArray& Outputs) const { // this is dirty, but we'll continue doing this for now because it's all going to go away with Digested graphs UNiagaraNodeFunctionCall* MutableThis = const_cast(this); TArray Inputs; bool bError = false; const UEdGraphSchema_Niagara* Schema = CastChecked(GetSchema()); FVersionedNiagaraScriptData* ScriptData = GetScriptData(); if (ScriptData && HasValidScriptAndGraph()) { if (InvalidScriptVersionReference.IsValid()) { FText WarningMessage = FText::Format(LOCTEXT("InvalidScriptVersionReferenceMessage", "Function call \"{0}\" references an invalid script version ({1}). Compiling with the exposed version as fallback, but please check the node and set a valid version."), FText::FromString(FunctionDisplayName), FText::FromString(InvalidScriptVersionReference.ToString())); Translator->Warning(WarningMessage, this, nullptr); } if (ScriptData->bDeprecated && IsNodeEnabled()) { FText DeprecationMessage = GetFormattedDeprecationMessage(ScriptData, FunctionDisplayName); Translator->Warning(DeprecationMessage, this, nullptr); } FPinCollectorArray CallerInputPins; GetInputPins(CallerInputPins); UNiagaraScriptSource* Source = GetFunctionScriptSource(); UNiagaraGraph* FunctionGraph = Source->NodeGraph; TArray FunctionInputNodes; UNiagaraGraph::FFindInputNodeOptions Options; Options.bSort = true; Options.bFilterDuplicates = true; FunctionGraph->FindInputNodes(FunctionInputNodes, Options); // We check which module inputs are not used so we can later remove them from the compilation of the // parameter map that sets the input values for our function. This is mainly done to prevent data interfaces being // initialized as parameter when they are not used in the function or module. TSet HiddenPinNames; for (UEdGraphPin* Pin : FNiagaraStackGraphUtilities::GetUnusedFunctionInputPins(*this, FCompileConstantResolver(Translator, DebugState))) { HiddenPinNames.Add(Pin->PinName); } Translator->EnterFunctionCallNode(HiddenPinNames); for (UNiagaraNodeInput* FunctionInputNode : FunctionInputNodes) { //Finds the matching Pin in the caller. UEdGraphPin** PinPtr = CallerInputPins.FindByPredicate([&](UEdGraphPin* InPin) { return Schema->PinToNiagaraVariable(InPin).IsEquivalent(FunctionInputNode->Input); }); if (!PinPtr) { if (FunctionInputNode->IsExposed()) { //Couldn't find the matching pin for an exposed input. Probably a stale function call node that needs to be refreshed. Translator->Error(LOCTEXT("StaleFunctionCallError", "Function call is stale and needs to be refreshed."), this, nullptr); bError = true; } else if (FunctionInputNode->ExposureOptions.bRequired == true) { // Not exposed, but required. This means we should just add as a constant. Inputs.Add(Translator->GetConstant(FunctionInputNode->Input)); continue; } Inputs.Add(INDEX_NONE); continue; } UEdGraphPin* CallerPin = *PinPtr; UEdGraphPin* CallerLinkedTo = CallerPin->LinkedTo.Num() > 0 ? UNiagaraNode::TraceOutputPin(CallerPin->LinkedTo[0]) : nullptr; FNiagaraVariable PinVar = Schema->PinToNiagaraVariable(CallerPin); if (!CallerLinkedTo) { //if (Translator->CanReadAttributes()) { //Try to auto bind if we're not linked to by the caller. FNiagaraVariable AutoBoundVar; ENiagaraInputNodeUsage AutBoundUsage = ENiagaraInputNodeUsage::Undefined; if (FindAutoBoundInput(FunctionInputNode, CallerPin, AutoBoundVar, AutBoundUsage)) { UNiagaraGraph* CallerGraph = MutableThis->GetNiagaraGraph(); UNiagaraNodeInput* NewNode = NewObject(CallerGraph); NewNode->Input = PinVar; NewNode->Usage = AutBoundUsage; NewNode->AllocateDefaultPins(); CallerLinkedTo = NewNode->GetOutputPin(0); CallerPin->BreakAllPinLinks(); CallerPin->MakeLinkTo(CallerLinkedTo); } } } if (CallerLinkedTo) { //Param is provided by the caller. Typical case. Inputs.Add(Translator->CompileInputPin(CallerPin)); continue; } else { if (FunctionInputNode->IsRequired()) { if (CallerPin->bDefaultValueIsIgnored) { //This pin can't use a default and it is required so flag an error. Translator->Error(FText::Format(LOCTEXT("RequiredInputUnboundErrorFmt", "Required input {0} was not bound and could not be automatically bound."), CallerPin->GetDisplayName()), this, CallerPin); bError = true; //We weren't linked to anything and we couldn't auto bind so tell the compiler this input isn't provided and it should use it's local default. Inputs.Add(INDEX_NONE); } else { //We also compile the pin anyway if it is required as we'll be attempting to use it's inline default. Inputs.Add(Translator->CompileInputPin(CallerPin)); } } else { //We optional, weren't linked to anything and we couldn't auto bind so tell the compiler this input isn't provided and it should use it's local default. Inputs.Add(INDEX_NONE); } } } FCompileConstantResolver ConstantResolver(Translator, DebugState); FNiagaraEditorUtilities::SetStaticSwitchConstants(GetCalledGraph(), CallerInputPins, ConstantResolver); Translator->ExitFunctionCallNode(); } else if (MutableThis->Signature.IsValid()) { UClass* DIClass = GetDIClass(); if (DIClass) { if (UNiagaraDataInterface* DataInterfaceCDO = Cast(DIClass->GetDefaultObject())) { TArray ValidationErrors; DataInterfaceCDO->ValidateFunction(Signature, ValidationErrors); bError = ValidationErrors.Num() > 0; for (FText& ValidationError : ValidationErrors) { Translator->Error(ValidationError, this, nullptr); } if (bError) { return; } } } Translator->EnterFunctionCallNode(TSet()); MutableThis->Signature.FunctionSpecifiers = FunctionSpecifiers; bError = CompileInputPins(Translator, Inputs); Translator->ExitFunctionCallNode(); } else { Translator->Error(FText::Format(LOCTEXT("UnknownFunction", "Unknown Function Call! Missing Script or Data Interface Signature. Stack Name: {0}"), FText::FromString(GetFunctionName())), this, nullptr); bError = true; } if (!bError) { Translator->FunctionCall(this, Inputs, Outputs); } } UObject* UNiagaraNodeFunctionCall::GetReferencedAsset() const { if (FunctionScript && FunctionScript->GetOutermost() != GetOutermost()) { return FunctionScript; } else { return nullptr; } } void UNiagaraNodeFunctionCall::OpenReferencedAsset() const { if (FunctionScript && FunctionScript->GetOutermost() != GetOutermost()) { if (FunctionScript->IsVersioningEnabled()) { FunctionScript->VersionToOpenInEditor = SelectedScriptVersion; } #if WITH_EDITOR if ( GEditor ) { GEditor->GetEditorSubsystem()->OpenEditorForAsset(FunctionScript); } #endif } else if (GUnrealEd && GUnrealEd->GetUnrealEdOptions()->IsCPPAllowed()) { if (!Signature.SourceFile.IsEmpty() && FPaths::FileExists(Signature.SourceFile)) { FSourceCodeNavigation::OpenSourceFile(Signature.SourceFile, Signature.SourceLine); } else if (Signature.Inputs.Num() > 0) { // if the signature doesn't have the source info stored we try to find the source file // by looking at the data interface on the input pin as a fallback UClass* Class = Signature.Inputs[0].GetType().GetClass(); if (Class && Class->IsChildOf(UNiagaraDataInterface::StaticClass())) { FString ClassSourcePath; if(FSourceCodeNavigation::FindClassSourcePath(Class, ClassSourcePath)) { const FString AbsoluteSourcePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*ClassSourcePath); FSourceCodeNavigation::OpenSourceFile(AbsoluteSourcePath); } } } } } void UNiagaraNodeFunctionCall::UpdateNodeErrorMessage() { FVersionedNiagaraScriptData* ScriptData = GetScriptData(); if (ScriptData) { if (ScriptData->bDeprecated) { UEdGraphNode::bHasCompilerMessage = true; ErrorType = EMessageSeverity::Warning; UEdGraphNode::ErrorMsg = GetFormattedDeprecationMessage(ScriptData, FunctionDisplayName).ToString(); } else if (ScriptData->bExperimental) { UEdGraphNode::bHasCompilerMessage = true; UEdGraphNode::ErrorType = EMessageSeverity::Info; if (ScriptData->ExperimentalMessage.IsEmptyOrWhitespace()) { UEdGraphNode::NodeUpgradeMessage = LOCTEXT("FunctionExperimental", "This function is marked as experimental, use with care!"); } else { FFormatNamedArguments Args; Args.Add(TEXT("Message"), ScriptData->ExperimentalMessage); UEdGraphNode::NodeUpgradeMessage = FText::Format(LOCTEXT("FunctionExperimentalReason", "This function is marked as experimental, reason:\n{Message}."), Args); } } else { UEdGraphNode::bHasCompilerMessage = false; UEdGraphNode::ErrorMsg = FString(); } } else if (Signature.IsValid()) { if (Signature.bSoftDeprecatedFunction) { UEdGraphNode::bHasCompilerMessage = true; UEdGraphNode::ErrorType = EMessageSeverity::Info; UEdGraphNode::NodeUpgradeMessage = LOCTEXT("FunctionDeprecatedSoftly", "There is a newer version of this function, consider switching over to it."); } else if (Signature.bExperimental) { UEdGraphNode::bHasCompilerMessage = true; UEdGraphNode::ErrorType = EMessageSeverity::Info; if (Signature.ExperimentalMessage.IsEmptyOrWhitespace()) { UEdGraphNode::NodeUpgradeMessage = LOCTEXT("FunctionExperimental", "This function is marked as experimental, use with care!"); } else { FFormatNamedArguments Args; Args.Add(TEXT("Message"), Signature.ExperimentalMessage); UEdGraphNode::NodeUpgradeMessage = FText::Format(LOCTEXT("FunctionExperimentalReason", "This function is marked as experimental, reason:\n{Message}."), Args); } } } } TArray UNiagaraNodeFunctionCall::GetBoundPinGuidsByName(FName InputName) const { TArray Result; for (auto Entry : BoundPinNames) { if (Entry.Value == InputName) { Result.Add(Entry.Key); } } return Result; } void UNiagaraNodeFunctionCall::FixupFunctionScriptVersion() { if (!FunctionScript) { return; } FunctionScript->CheckVersionDataAvailable(); if (FunctionScript->IsVersioningEnabled()) { if (!SelectedScriptVersion.IsValid()) { // When we did not reference a versioned script before and do now, reference the first version (instead of the exposed version as new function calls do) SelectedScriptVersion = FunctionScript->GetAllAvailableVersions()[0].VersionGuid; } else if (!FunctionScript->GetAllAvailableVersions().ContainsByPredicate([this](const FNiagaraAssetVersion& Version) { return SelectedScriptVersion == Version.VersionGuid; })) { // The version we are referencing does not exist any more, maybe it was deleted. // So we fall back to the exposed version, but save the old guid to generate a warning. InvalidScriptVersionReference = SelectedScriptVersion; SelectedScriptVersion = FunctionScript->GetExposedVersion().VersionGuid; } } else { SelectedScriptVersion = FGuid(); InvalidScriptVersionReference = FGuid(); PreviousScriptVersion = FGuid(); } } void UNiagaraNodeFunctionCall::UpdatePinTooltips() { if (!Signature.IsValid()) { return; } // if it's a DI function we grab the newest tooltips first if ((Signature.Inputs.Num() > 0) && Signature.Inputs[0].GetType().IsDataInterface()) { UNiagaraDataInterface* CDO = CastChecked(Signature.Inputs[0].GetType().GetClass()->GetDefaultObject()); TArray AllSignatures; CDO->GetFunctionSignatures(AllSignatures); for (const FNiagaraFunctionSignature& DISignature : AllSignatures) { if (Signature.Name != DISignature.Name) { continue; } Signature.Description = DISignature.Description; Signature.InputDescriptions = DISignature.InputDescriptions; Signature.OutputDescriptions = DISignature.OutputDescriptions; } } // update input pin tooltips TArray InputPins; GetInputPins(InputPins); if (Signature.Inputs.Num() == InputPins.Num()) { for (int i = 0; i < InputPins.Num(); i++) { FNiagaraVariable& Input = Signature.Inputs[i]; InputPins[i]->PinToolTip = Signature.InputDescriptions.Contains(Input) ? Signature.InputDescriptions[Input].ToString() : FString(); } } // update output pin tooltips TArray OutputPins; GetOutputPins(OutputPins); if (Signature.Outputs.Num() == OutputPins.Num()) { for (int i = 0; i < OutputPins.Num(); i++) { FNiagaraVariableBase& Output = Signature.Outputs[i]; OutputPins[i]->PinToolTip = Signature.OutputDescriptions.Contains(Output) ? Signature.OutputDescriptions[Output].ToString() : FString(); } } } void UNiagaraNodeFunctionCall::UpdateStaticSwitchPinsWithPersistentGuids() { UNiagaraGraph* CalledGraph = GetCalledGraph(); if (CalledGraph != nullptr) { TArray InputPins; GetInputPins(InputPins); TArray StaticSwitchVariables = CalledGraph->FindStaticSwitchInputs(); for (UEdGraphPin* InputPin : InputPins) { FNiagaraVariable InputVariable = GetDefault()->PinToNiagaraVariable(InputPin); if (StaticSwitchVariables.Contains(InputVariable)) { TOptional VariableGuid = CalledGraph->GetScriptVariableGuid(InputVariable); if (VariableGuid.IsSet()) { InputPin->PersistentGuid = *VariableGuid; } } } } } bool UNiagaraNodeFunctionCall::RefreshFromExternalChanges() { bool bReload = false; if (FunctionScript) { FixupFunctionScriptVersion(); UNiagaraScriptSource* Source = GetFunctionScriptSource(); // cooked niagara can be missing source, but we shouldn't need to reload if already cooked for editor if (!GetOutermost()->bIsCookedForEditor) { if (ensureMsgf(Source != nullptr, TEXT("No source found for FunctionScript %s in RefreshFromExternalChanges for %s"), *GetPathNameSafe(FunctionScript), *GetPathNameSafe(this))) { bReload = CachedChangeId != Source->NodeGraph->GetChangeID(); } } } else if (Signature.IsValid()) { bReload = true; } UpdateNodeErrorMessage(); // Go over the static switch parameters to set their propagation status on the pins UNiagaraGraph* CalledGraph = GetCalledGraph(); if (CalledGraph) { CleanupPropagatedSwitchValues(); FPinCollectorArray InputPins; GetInputPins(InputPins); for (FNiagaraVariable InputVar : CalledGraph->FindStaticSwitchInputs()) { for (UEdGraphPin* Pin : InputPins) { if (InputVar.GetName().IsEqual(Pin->GetFName())) { Pin->bDefaultValueIsIgnored = FindPropagatedVariable(InputVar) != nullptr; break; } } } } if (bReload) { // TODO - Leverage code in reallocate pins to determine if any pins have changed... ReallocatePins(false); FNiagaraStackGraphUtilities::SynchronizeReferencingMapPinsWithFunctionCall(*this); FNiagaraStackGraphUtilities::FixDynamicInputNodeOutputPinsFromExternalChanges(*this); return true; } else { return false; } } void UNiagaraNodeFunctionCall::GatherExternalDependencyData(ENiagaraScriptUsage InUsage, const FGuid& InUsageId, FNiagaraScriptHashCollector& HashCollector) const { if (HasValidScriptAndGraph()) { // We don't know which graph type we're referencing, so we try them all... may need to replace this with something faster in the future. UNiagaraGraph* FunctionGraph = GetCalledGraph(); FunctionGraph->RebuildCachedCompileIds(); for (int32 i = (int32)ENiagaraScriptUsage::Function; i <= (int32)ENiagaraScriptUsage::DynamicInput; i++) { FGuid FoundGuid = FunctionGraph->GetBaseId((ENiagaraScriptUsage)i, FGuid(0, 0, 0, 0)); FNiagaraCompileHash FoundCompileHash = FunctionGraph->GetCompileDataHash((ENiagaraScriptUsage)i, FGuid(0, 0, 0, 0)); if (FoundGuid.IsValid() && FoundCompileHash.IsValid()) { HashCollector.AddHash(FoundCompileHash, FunctionGraph->GetPathName()); FunctionGraph->GatherExternalDependencyData((ENiagaraScriptUsage)i, FGuid(0, 0, 0, 0), HashCollector); } } } } void UNiagaraNodeFunctionCall::UpdateCompileHashForNode(FSHA1& HashState) const { Super::UpdateCompileHashForNode(HashState); HashState.UpdateWithString(*GetFunctionName(), GetFunctionName().Len()); } void UNiagaraNodeFunctionCall::UpdateReferencedStaticsHashForNode(FSHA1& HashState) const { // Assume that we only care about static switch pins and their current values FPinCollectorArray InputPins; GetInputPins(InputPins); for (int32 PinIndex = 0; PinIndex < InputPins.Num(); ++PinIndex) { if (!InputPins[PinIndex]) { continue; } if (InputPins[PinIndex]->PersistentGuid.IsValid() && InputPins[PinIndex]->LinkedTo.Num() == 0 && InputPins[PinIndex]->bNotConnectable == true) { HashState.Update((const uint8*)&InputPins[PinIndex]->PersistentGuid, sizeof(InputPins[PinIndex]->PersistentGuid)); HashState.UpdateWithString(*InputPins[PinIndex]->DefaultValue, InputPins[PinIndex]->DefaultValue.Len()); } } } void UNiagaraNodeFunctionCall::GetNodeContextMenuActions(class UToolMenu* Menu, class UGraphNodeContextMenuContext* Context) const { Super::GetNodeContextMenuActions(Menu, Context); if ( FunctionScript != nullptr || Signature.Inputs.Num() == 0 ) { return; } if ( Signature.Inputs[0].GetType().IsDataInterface() == false ) { return; } if ( UClass* DIClass = Cast(Signature.Inputs[0].GetType().GetClass()) ) { INiagaraDataInterfaceNodeActionProvider::GetNodeContextMenuActions(DIClass, Menu, Context, Signature); } } TSharedRef UNiagaraNodeFunctionCall::CreateTitleRightWidget() { if ( FunctionScript != nullptr || Signature.Inputs.Num() == 0 ) { return SNullWidget::NullWidget; } if ( Signature.Inputs[0].GetType().IsDataInterface() == false ) { return SNullWidget::NullWidget; } if ( UClass* DIClass = Cast(Signature.Inputs[0].GetType().GetClass()) ) { FString MenuBaseName = "GraphEditor.Niagara.InlinePrimaryActions."; FString MenuName = MenuBaseName.Append(DIClass->GetName()); if(UToolMenus::Get()->IsMenuRegistered(FName(*MenuName)) == false) { UToolMenu* InlinePrimaryMenu = UToolMenus::Get()->RegisterMenu(FName(*MenuName)); InlinePrimaryMenu->AddDynamicSection("GetInlineNodeContextActions", FNewToolMenuDelegate::CreateLambda([DIClass, this](UToolMenu* InMenu) { // it's important to pass in the menu from the lambda parameter instead of using the original menu INiagaraDataInterfaceNodeActionProvider::GetInlineNodeContextMenuActions(DIClass, InMenu); })); } TSharedPtr ButtonContentWidget = nullptr; INiagaraDataInterfaceNodeActionProvider::FInlineMenuDisplayOptions DisplayOptions = INiagaraDataInterfaceNodeActionProvider::GetInlineMenuDisplayOptions(DIClass, this); if(DisplayOptions.bDisplayInline == false) { return SNullWidget::NullWidget; } if(DisplayOptions.DisplayBrush != nullptr) { ButtonContentWidget = SNew(SImage) .Image(DisplayOptions.DisplayBrush); } else { ButtonContentWidget = SNew(STextBlock) .Text(DisplayOptions.DisplayName.IsEmpty() ? LOCTEXT("NodeInlineOptionsMenuLabel", "Options") : DisplayOptions.DisplayName); } TSharedRef Result = SNew(SComboButton) .ComboButtonStyle(&FAppStyle::GetWidgetStyle("SimpleComboButton")) .OnGetMenuContent(FOnGetContent::CreateLambda([this, MenuName, DIClass]() { UGraphNodeContextMenuContext* ContextObject = NewObject(); ContextObject->Init(this->GetGraph(), this, nullptr, false); FToolMenuContext Context(ContextObject); // we generate the menu instead of using FindMenu as this will cause the 'instance' of this menu to be created, with dynamic sections being generated. UToolMenu* GeneratedMenu = UToolMenus::Get()->GenerateMenu(FName(*MenuName), Context); return UToolMenus::Get()->GenerateWidget(GeneratedMenu); })) .ButtonContent() [ ButtonContentWidget.ToSharedRef() ]; if(DisplayOptions.TooltipText.IsEmpty() == false) { Result->SetToolTipText(DisplayOptions.TooltipText); } return Result; } return SNullWidget::NullWidget; } void UNiagaraNodeFunctionCall::CollectAddPinActions(FNiagaraMenuActionCollector& Collector, UEdGraphPin* AddPin)const { Super::CollectAddPinActions(Collector, AddPin); if (FunctionScript == nullptr && Signature.Inputs.Num() > 0) { UClass* DIClass = Cast(Signature.Inputs[0].GetType().GetClass()); if (DIClass) { INiagaraDataInterfaceNodeActionProvider::CollectAddPinActions(DIClass, Collector, AddPin); } } } bool UNiagaraNodeFunctionCall::HasValidScriptAndGraph() const { return FunctionScript != nullptr && GetCalledGraph() != nullptr; } void UNiagaraNodeFunctionCall::BuildParameterMapHistory(FNiagaraParameterMapHistoryBuilder& OutHistory, bool bRecursive /*= true*/, bool bFilterForCompilation /*= true*/) const { const UEdGraphSchema_Niagara* Schema = CastChecked(GetSchema()); auto FindParameterMapPinIndex = [&]() { FPinCollectorArray InputPins; GetInputPins(InputPins); for (int32 PinIndex = 0; PinIndex < InputPins.Num(); ++PinIndex) { if (Schema->PinToTypeDefinition(InputPins[PinIndex]) == FNiagaraTypeDefinition::GetParameterMapDef()) { return PinIndex; } } return int32(INDEX_NONE); }; // when dealing with function scripts it's not sufficient to assume that InputPin[0] is the ParamMap pin, and so we // need to search through the inputs to find the right one const int32 ParamMapPinIndex = FindParameterMapPinIndex(); const bool bHasParamMapPin = ParamMapPinIndex != INDEX_NONE; const UEdGraphPin* ParamMapPin = bHasParamMapPin ? GetInputPin(ParamMapPinIndex) : nullptr; if (bHasParamMapPin && ParamMapPin->LinkedTo.Num() == 0) { // Looks like this function call is not yet hooked up. Skip it to prevent cascading errors in the compilation return; } Super::BuildParameterMapHistory(OutHistory, bRecursive, bFilterForCompilation); if (!IsNodeEnabled() && OutHistory.GetIgnoreDisabled()) { RouteParameterMapAroundMe(OutHistory, bRecursive); return; } const bool ValidScriptAndGraph = HasValidScriptAndGraph(); if (ValidScriptAndGraph == false && Signature.IsValid() == false) { RouteParameterMapAroundMe(OutHistory, bRecursive); return; } if (ValidScriptAndGraph) { UNiagaraGraph* FunctionGraph = GetCalledGraph(); UNiagaraNodeOutput* OutputNode = FunctionGraph->FindOutputNode(ENiagaraScriptUsage::Function); if (OutputNode == nullptr) { OutputNode = FunctionGraph->FindOutputNode(ENiagaraScriptUsage::Module); } if (OutputNode == nullptr) { OutputNode = FunctionGraph->FindOutputNode(ENiagaraScriptUsage::DynamicInput); } FPinCollectorArray InputPins; GetInputPins(InputPins); // Because the pin traversals above the the Super::BuildParameterMapHistory don't traverse into the input pins if they aren't linked. // This leaves static variables that aren't linked to anything basically ignored. The code below fills in that gap. // We don't do it in the base code because many input pins are traversed only if the owning node wants them to be. // OutputNode is another example of manually registering the input pins. for (UEdGraphPin* Pin : InputPins) { // Make sure to register pin constants if (Pin->LinkedTo.Num() == 0 && Schema->IsPinStatic(Pin) && Pin->DefaultValue.Len() != 0) { OutHistory.RegisterConstantFromInputPin(Pin, Pin->DefaultValue); } } FCompileConstantResolver FunctionResolver = OutHistory.ConstantResolver->WithDebugState(DebugState); if (OutHistory.HasCurrentUsageContext()) { // if we traverse a full emitter graph the usage might change during the traversal, so we need to update the constant resolver FunctionResolver = FunctionResolver.WithUsage(OutHistory.GetCurrentUsageContext()); } FNiagaraEditorUtilities::SetStaticSwitchConstants(FunctionGraph, InputPins, FunctionResolver); int32 ParamMapIdx = INDEX_NONE; uint32 NodeIdx = INDEX_NONE; if (bHasParamMapPin && bRecursive) { ParamMapIdx = OutHistory.TraceParameterMapOutputPin((ParamMapPin->LinkedTo[0])); } OutHistory.EnterFunction(GetFunctionName(), FunctionGraph, this); if (ParamMapIdx != INDEX_NONE) { NodeIdx = OutHistory.BeginNodeVisitation(ParamMapIdx, this); } const bool DoDepthTraversal = OutHistory.ShouldProcessDepthTraversal(FunctionGraph); // check if we should be recursing deeper into the graph if (DoDepthTraversal) { ++OutHistory.CurrentGraphDepth; OutputNode->BuildParameterMapHistory(OutHistory, true, bFilterForCompilation); --OutHistory.CurrentGraphDepth; } // Since we're about to lose the pin calling context, we finish up the function call parameter map pin wiring // here when we have the calling context and the child context still available to us... FPinCollectorArray OutputPins; GetOutputPins(OutputPins); TArray, TInlineAllocator<16> > MatchedPairs; TArray, TInlineAllocator<16> > MatchedConstants; TArray > OutputMatched; TArray> OutputVariables; const int32 OutputPinCount = OutputPins.Num(); OutputMatched.AddDefaulted(OutputPinCount); OutputVariables.Reserve(OutputPinCount); for (int32 OutputIt = 0; OutputIt < OutputPinCount; ++OutputIt) { OutputVariables.Emplace(Schema->PinToNiagaraVariable(OutputPins[OutputIt])); } // Find the matches of names and types of the sub-graph output pins and this function call nodes' outputs. for (UEdGraphPin* ChildOutputNodePin : OutputNode->GetAllPins()) { if (!ChildOutputNodePin) { continue; } const FNiagaraVariableBase VarChild = Schema->PinToNiagaraVariable(ChildOutputNodePin); if (ChildOutputNodePin->LinkedTo.Num() > 0 && VarChild.GetType() == FNiagaraTypeDefinition::GetParameterMapDef()) { for (int32 i = 0; i < OutputPins.Num(); i++) { if (OutputVariables[i].IsEquivalent(VarChild)) { TPair& Pair = MatchedPairs.AddZeroed_GetRef(); Pair.Key = OutputPins[i]; Pair.Value = OutHistory.TraceParameterMapOutputPin(ChildOutputNodePin->LinkedTo[0]); OutputMatched[i] = true; } } } else if (VarChild.GetType().IsStatic()) { for (int32 i = 0; i < OutputPins.Num(); i++) { if (!OutputMatched[i] && OutputVariables[i].IsEquivalent(VarChild)) { TPair& Pair = MatchedConstants.AddZeroed_GetRef(); Pair.Key = OutputPins[i]; Pair.Value = OutHistory.GetConstantFromInputPin(ChildOutputNodePin); OutputMatched[i] = true; } } } } if (ParamMapIdx != INDEX_NONE) { OutHistory.EndNodeVisitation(ParamMapIdx, NodeIdx); } OutHistory.ExitFunction(this); if (DoDepthTraversal) { for (int32 i = 0; i < MatchedPairs.Num(); i++) { OutHistory.RegisterParameterMapPin(MatchedPairs[i].Value, MatchedPairs[i].Key); } for (int32 i = 0; i < MatchedConstants.Num(); i++) { OutHistory.RegisterConstantPin(MatchedConstants[i].Value, MatchedConstants[i].Key); } } else { for (int32 OutputPinIt = 0; OutputPinIt < OutputPinCount; ++OutputPinIt) { if (OutputVariables[OutputPinIt].GetType() == FNiagaraTypeDefinition::GetParameterMapDef()) { OutHistory.RegisterParameterMapPin(ParamMapIdx, OutputPins[OutputPinIt]); break; } } } } else if (Signature.bRequiresExecPin) { RouteParameterMapAroundMe(OutHistory, bRecursive); } } void UNiagaraNodeFunctionCall::ChangeScriptVersion(FGuid NewScriptVersion, const FNiagaraScriptVersionUpgradeContext& UpgradeContext, bool bShowNotesInStack, bool bDeferOverridePinUpdate) { bool bPreviousVersionValid = !InvalidScriptVersionReference.IsValid(); if (NewScriptVersion == SelectedScriptVersion && bPreviousVersionValid) { return; } Modify(); PythonUpgradeScriptWarnings.Empty(); if (bPreviousVersionValid && FunctionScript && !UpgradeContext.bSkipPythonScript) { // run python update scripts TArray UpgradeVersionData; FVersionedNiagaraScriptData* PreviousData = FunctionScript->GetScriptData(SelectedScriptVersion); FVersionedNiagaraScriptData* NewData = FunctionScript->GetScriptData(NewScriptVersion); for (const FNiagaraAssetVersion& Version : FunctionScript->GetAllAvailableVersions()) { if (PreviousData->Version <= Version && Version <= NewData->Version) { UpgradeVersionData.Add(FunctionScript->GetScriptData(Version.VersionGuid)); } } FNiagaraEditorUtilities::RunPythonUpgradeScripts(this, UpgradeVersionData, UpgradeContext, PythonUpgradeScriptWarnings); } InvalidScriptVersionReference = FGuid(); PreviousScriptVersion = bShowNotesInStack ? SelectedScriptVersion : NewScriptVersion; SelectedScriptVersion = NewScriptVersion; if (!bDeferOverridePinUpdate) { UpdateOverridePins(UpgradeContext); } MarkNodeRequiresSynchronization(__FUNCTION__, true); } void UNiagaraNodeFunctionCall::UpdateOverridePins(const FNiagaraScriptVersionUpgradeContext& UpgradeContext) { if (FunctionScript) { // Automatically remove old inputs so it does not show a bunch of warnings to the user FPinCollectorArray InputPins; GetInputPins(InputPins); for (UEdGraphPin* Pin : InputPins) { if (Pin->bOrphanedPin) { RemovePin(Pin); } } UNiagaraNodeParameterMapSet* OverrideNode = FNiagaraStackGraphUtilities::GetStackFunctionOverrideNode(*this); if (OverrideNode != nullptr) { FPinCollectorArray OverridePins; OverrideNode->Modify(); OverrideNode->GetInputPins(OverridePins); if (!OverridePins.IsEmpty()) { TMap FunctionInputNames; TArray ModuleInputVariables; FNiagaraStackGraphUtilities::GetStackFunctionInputs(*this, ModuleInputVariables, UpgradeContext.ConstantResolver, FNiagaraStackGraphUtilities::ENiagaraGetStackFunctionInputPinsOptions::ModuleInputsOnly); for (const FNiagaraVariable& InputVariable : ModuleInputVariables) { FunctionInputNames.Add(FNiagaraParameterHandle(InputVariable.GetName()).GetName(), InputVariable.GetType()); } for (UEdGraphPin* OverridePin : OverridePins) { if (!FNiagaraStackGraphUtilities::IsOverridePinForFunction(*OverridePin, *this)) { continue; } FName InputName = FNiagaraParameterHandle(OverridePin->PinName).GetName(); FNiagaraTypeDefinition* InputType = FunctionInputNames.Find(InputName); FNiagaraTypeDefinition OverridePinType = UEdGraphSchema_Niagara::PinTypeToTypeDefinition(OverridePin->PinType); if (InputType != nullptr && *InputType != OverridePinType) { // looks like the type of the module input changed - we'll change the override pin type as well OverridePin->Modify(); OverridePin->PinType = UEdGraphSchema_Niagara::TypeDefinitionToPinType(*InputType); } else if (InputType == nullptr) { // the input doesn't exist any more, delete the override pin TArray> RemovedDataObjects; FNiagaraStackGraphUtilities::RemoveNodesForStackFunctionInputOverridePin(*OverridePin, RemovedDataObjects); OverrideNode->RemovePin(OverridePin); } } } } } } UEdGraphPin* UNiagaraNodeFunctionCall::FindParameterMapDefaultValuePin(const FName VariableName, ENiagaraScriptUsage InParentUsage, FCompileConstantResolver ConstantResolver) const { if (FunctionScript) { UNiagaraScriptSource* ScriptSource = GetFunctionScriptSource(); if (ScriptSource != nullptr && ScriptSource->NodeGraph != nullptr) { // Set the static switch values so we traverse the correct node paths TArray InputPins; GetInputPins(InputPins); FNiagaraEditorUtilities::SetStaticSwitchConstants(ScriptSource->NodeGraph, InputPins, ConstantResolver.WithDebugState(DebugState)); return ScriptSource->NodeGraph->FindParameterMapDefaultValuePin(VariableName, FunctionScript->GetUsage(), InParentUsage); } } return nullptr; } void UNiagaraNodeFunctionCall::FindParameterMapDefaultValuePins(TConstArrayView VariableNames, ENiagaraScriptUsage InParentUsage, const FCompileConstantResolver& ConstantResolver, TArrayView DefaultPins) { if (FunctionScript) { UNiagaraScriptSource* ScriptSource = GetFunctionScriptSource(); if (ScriptSource != nullptr && ScriptSource->NodeGraph != nullptr) { // Set the static switch values so we traverse the correct node paths TArray InputPins; GetInputPins(InputPins); FNiagaraEditorUtilities::SetStaticSwitchConstants(ScriptSource->NodeGraph, InputPins, ConstantResolver.WithDebugState(DebugState)); ScriptSource->NodeGraph->MultiFindParameterMapDefaultValuePins(VariableNames, FunctionScript->GetUsage(), InParentUsage, DefaultPins); } } } UEdGraphPin* UNiagaraNodeFunctionCall::FindStaticSwitchInputPin(const FName& VariableName) const { UNiagaraGraph* CalledGraph = GetCalledGraph(); if (CalledGraph == nullptr) { return nullptr; } FPinCollectorArray InputPins; GetInputPins(InputPins); for (FNiagaraVariable InputVar : CalledGraph->FindStaticSwitchInputs()) { if (InputVar.GetName().IsEqual(VariableName)) { for (UEdGraphPin* Pin : InputPins) { if (VariableName.IsEqual(Pin->GetFName())) { return Pin; } } } } return nullptr; } bool UNiagaraNodeFunctionCall::ContainsDebugSwitch() const { UNiagaraGraph* CalledGraph = GetCalledGraph(); if (CalledGraph) { TArray Switches; CalledGraph->GetNodesOfClass(Switches); for (const auto& Switch : Switches) { if (Switch->IsDebugSwitch()) { return true; } } TArray FunctionCalls; CalledGraph->GetNodesOfClass(FunctionCalls); for (const auto& Function : FunctionCalls) { if (Function->ContainsDebugSwitch()) { return true; } } } return false; } void UNiagaraNodeFunctionCall::SuggestName(FString SuggestedName, bool bForceSuggestion) { ComputeNodeName(SuggestedName, bForceSuggestion); } UNiagaraNodeFunctionCall::FOnInputsChanged& UNiagaraNodeFunctionCall::OnInputsChanged() { return OnInputsChangedDelegate; } FNiagaraPropagatedVariable* UNiagaraNodeFunctionCall::FindPropagatedVariable(const FNiagaraVariable& Variable) { for (FNiagaraPropagatedVariable& Propagated : PropagatedStaticSwitchParameters) { if (Propagated.SwitchParameter == Variable) { return &Propagated; } } return nullptr; } void UNiagaraNodeFunctionCall::RemovePropagatedVariable(const FNiagaraVariable& Variable) { for (int i = 0; i < PropagatedStaticSwitchParameters.Num(); i++) { if (PropagatedStaticSwitchParameters[i].SwitchParameter == Variable) { PropagatedStaticSwitchParameters.RemoveAt(i); return; } } } ENiagaraNumericOutputTypeSelectionMode UNiagaraNodeFunctionCall::GetNumericOutputTypeSelectionMode() const { if (GetScriptData()) { return GetScriptData()->NumericOutputTypeSelectionMode; } return ENiagaraNumericOutputTypeSelectionMode::None; } void UNiagaraNodeFunctionCall::AutowireNewNode(UEdGraphPin* FromPin) { UNiagaraNode::AutowireNewNode(FromPin); ComputeNodeName(); } void UNiagaraNodeFunctionCall::RefreshSignature() { if (Signature.IsValid() && Signature.bMemberFunction) { if ((Signature.Inputs.Num() > 0) && Signature.Inputs[0].GetType().IsDataInterface()) { UNiagaraDataInterface* CDO = CastChecked(Signature.Inputs[0].GetType().GetClass()->GetDefaultObject()); TArray BaseDIFuncs; CDO->GetFunctionSignatures(BaseDIFuncs); if (FNiagaraFunctionSignature* BaseSig = BaseDIFuncs.FindByPredicate([&](const FNiagaraFunctionSignature& CheckSig) { return Signature.Name == CheckSig.Name; })) { //Revert signature to base and re-add dynamic pins as new inputs and outputs. Signature = *BaseSig; FPinCollectorArray FoundPins; GetInputPins(FoundPins); for (int32 i = 0; i < FoundPins.Num(); ++i) { UEdGraphPin* InputPin = FoundPins[i]; FNiagaraVariable InputVariable = UEdGraphSchema_Niagara::PinToNiagaraVariable(InputPin); if(!BaseSig->Inputs.Contains(InputVariable) && IsAddPin(InputPin) == false && IsExecPin(InputPin) == false) { Signature.AddInput(InputVariable, FText::FromString(InputPin->PinToolTip)); } } GetOutputPins(FoundPins); for (int32 i = 0; i < FoundPins.Num(); ++i) { UEdGraphPin* OutputPin = FoundPins[i]; FNiagaraVariableBase InputVariable = UEdGraphSchema_Niagara::PinToNiagaraVariable(OutputPin); if(!BaseSig->Outputs.Contains(InputVariable) && IsAddPin(OutputPin) == false && IsExecPin(OutputPin) == false) { Signature.AddOutput(UEdGraphSchema_Niagara::PinToNiagaraVariable(OutputPin), FText::FromString(OutputPin->PinToolTip)); } } return; } } } Signature = FNiagaraFunctionSignature(); } bool UNiagaraNodeFunctionCall::IsBaseSignatureOfDataInterfaceFunction(const UEdGraphPin* Pin) const { if ((Signature.Inputs.Num() > 0) && Signature.Inputs[0].GetType().IsDataInterface()) { UNiagaraDataInterface* CDO = CastChecked(Signature.Inputs[0].GetType().GetClass()->GetDefaultObject()); TArray BaseDIFuncs; CDO->GetFunctionSignatures(BaseDIFuncs); if (FNiagaraFunctionSignature* BaseSig = BaseDIFuncs.FindByPredicate([&](const FNiagaraFunctionSignature& CheckSig) { return Signature.Name == CheckSig.Name; })) { FPinCollectorArray FoundPins; TArray InputOrOutputVariables; if(BaseSig->bRequiresExecPin && IsExecPin(Pin)) { return true; } if(Pin->Direction == EGPD_Input) { GetInputPins(FoundPins); InputOrOutputVariables = BaseSig->GetInputs(); } else { GetOutputPins(FoundPins); InputOrOutputVariables = BaseSig->Outputs; } FNiagaraVariableBase Variable = UEdGraphSchema_Niagara::PinToNiagaraVariable(Pin); if(InputOrOutputVariables.Contains(Variable)) { return true; } } } return false; } void UNiagaraNodeFunctionCall::ComputeNodeName(FString SuggestedName, bool bForceSuggestion) { FString FunctionName = FunctionScript ? FunctionScript->GetName() : Signature.GetNameString(); FName ProposedName; if (SuggestedName.IsEmpty() == false) { // If we have a suggested name and and either there is no function name, or it is a permutation of the function name // it can be used as the proposed name. if (bForceSuggestion || FunctionName.IsEmpty() || SuggestedName == FunctionName || (SuggestedName.StartsWith(FunctionName) && SuggestedName.RightChop(FunctionName.Len()).IsNumeric())) { ProposedName = *SuggestedName; } } if(ProposedName == NAME_None) { const FString CurrentName = FunctionDisplayName; if (FunctionName.IsEmpty() == false) { ProposedName = *FunctionName; } else { ProposedName = *CurrentName; } } UNiagaraGraph* Graph = GetNiagaraGraph(); TArray Nodes; Graph->GetNodesOfClass(Nodes); TSet Names; for (UNiagaraNodeFunctionCall* Node : Nodes) { CA_ASSUME(Node != nullptr); if (Node != this) { Names.Add(*Node->GetFunctionName()); } } FString NewName = FNiagaraUtilities::GetUniqueName(ProposedName, Names).ToString(); if (!FunctionDisplayName.Equals(NewName)) { FunctionDisplayName = NewName; } } void UNiagaraNodeFunctionCall::SetPinAutoGeneratedDefaultValue(UEdGraphPin& FunctionInputPin, UNiagaraNodeInput& FunctionScriptInputNode) { if (FunctionInputPin.bDefaultValueIsIgnored == false) { FPinCollectorArray InputPins; FunctionScriptInputNode.GetInputPins(InputPins); if (InputPins.Num() == 1 && InputPins[0]->bDefaultValueIsIgnored == false) { // If the function graph's input node had an input pin, and that pin's default wasn't ignored, use that value. FunctionInputPin.AutogeneratedDefaultValue = InputPins[0]->DefaultValue; } else { const UEdGraphSchema_Niagara* Schema = CastChecked(GetSchema()); FString PinDefaultValue; if (Schema->TryGetPinDefaultValueFromNiagaraVariable(FunctionScriptInputNode.Input, PinDefaultValue)) { FunctionInputPin.AutogeneratedDefaultValue = PinDefaultValue; } } } } void UNiagaraNodeFunctionCall::CleanupPropagatedSwitchValues() { for (int i = PropagatedStaticSwitchParameters.Num() - 1; i >= 0; i--) { FNiagaraPropagatedVariable& Propagated = PropagatedStaticSwitchParameters[i]; if (Propagated.SwitchParameter.GetName().IsNone() || !IsValidPropagatedVariable(Propagated.SwitchParameter)) { PropagatedStaticSwitchParameters.RemoveAt(i); } } } bool UNiagaraNodeFunctionCall::IsValidPropagatedVariable(const FNiagaraVariable& Variable) const { UNiagaraGraph* Graph = GetCalledGraph(); if (!Graph) { return false; } for (const FNiagaraVariable& Var : Graph->FindStaticSwitchInputs(false)) { if (Var == Variable) { return true; } } return false; } bool UNiagaraNodeFunctionCall::FindAutoBoundInput(const UNiagaraNodeInput* InputNode, const UEdGraphPin* PinToAutoBind, FNiagaraVariable& OutFoundVar, ENiagaraInputNodeUsage& OutNodeUsage) const { check(InputNode); if (PinToAutoBind->LinkedTo.Num() > 0 || !InputNode->CanAutoBind()) { return false; } ensureMsgf(InputNode->IsExposed() == true, TEXT("AutoBind inputs should be exposed for Function(%s) Pin(%s)"), *FunctionDisplayName, *PinToAutoBind->GetName()); const UEdGraphSchema_Niagara* Schema = CastChecked(GetSchema()); FNiagaraVariable PinVar = Schema->PinToNiagaraVariable(PinToAutoBind); //See if we can auto bind this pin to something in the caller script. const UNiagaraGraph* CallerGraph = GetNiagaraGraph(); check(CallerGraph); UNiagaraNodeOutput* CallerOutputNodeSpawn = CallerGraph->FindOutputNode(ENiagaraScriptUsage::ParticleSpawnScript); UNiagaraNodeOutput* CallerOutputNodeUpdate = CallerGraph->FindOutputNode(ENiagaraScriptUsage::ParticleUpdateScript); //First, let's see if we're an attribute of this emitter. Only valid if we're a module call off the primary script. if (CallerOutputNodeSpawn || CallerOutputNodeUpdate) { UNiagaraNodeOutput* CallerOutputNode = CallerOutputNodeSpawn != nullptr ? CallerOutputNodeSpawn : CallerOutputNodeUpdate; check(CallerOutputNode); { FNiagaraVariable* AttrVarPtr = CallerOutputNode->Outputs.FindByPredicate([&](const FNiagaraVariable& Attr) { return PinVar.IsEquivalent(Attr); }); if (AttrVarPtr) { OutFoundVar = *AttrVarPtr; OutNodeUsage = ENiagaraInputNodeUsage::Attribute; return true; } } } //Next, lets see if we are a system constant. //Do we need a smarter (possibly contextual) handling of system constants? const TArray& SysConstants = FNiagaraConstants::GetEngineConstants(); if (SysConstants.Contains(PinVar)) { OutFoundVar = PinVar; OutNodeUsage = ENiagaraInputNodeUsage::SystemConstant; return true; } //Unable to auto bind. return false; } #undef LOCTEXT_NAMESPACE