// Copyright Epic Games, Inc. All Rights Reserved. #include "NiagaraStackFunctionInputBinder.h" #include "NiagaraScript.h" #include "NiagaraEmitterHandle.h" #include "NiagaraScriptSource.h" #include "NiagaraGraph.h" #include "NiagaraNodeFunctionCall.h" #include "NiagaraNodeOutput.h" #include "EdGraph/EdGraphPin.h" #define LOCTEXT_NAMESPACE "StackFunctionInputBinder" FNiagaraStackFunctionInputBinder::FNiagaraStackFunctionInputBinder() : DefaultPin(nullptr) , OverridePin(nullptr) { } bool FNiagaraStackFunctionInputBinder::TryBind( UNiagaraScript* InScript, TArray InDependentScripts, FCompileConstantResolver InConstantResolver, FString InOwningEmitterUniqueName, UNiagaraNodeFunctionCall* InFunctionCallNode, FName InMetaDataKey, FString InMetaDataValue, TOptional InInputType, bool bInIsRequired, FText& OutErrorMessage) { UNiagaraGraph* FunctionGraph = InFunctionCallNode->GetCalledGraph(); FInputMatchesPredicate InputMatches; InputMatches.BindLambda([=](FNiagaraVariable InputVariable) { TOptional InputMetadata = FunctionGraph->GetMetaData(InputVariable); if (InputMetadata.IsSet()) { FString* ValueString = InputMetadata->PropertyMetaData.Find(InMetaDataKey); if (ValueString != nullptr && *ValueString == InMetaDataValue) { return true; } } return false; }); FText InputMatchDescription = FText::Format(LOCTEXT("MetadataMatchDescriptionFormat", "metadata key = '{0}' and value = '{1}'"), FText::FromName(InMetaDataKey), FText::FromString(InMetaDataValue)); return TryBindInternal(InScript, InDependentScripts, InConstantResolver, InOwningEmitterUniqueName, InFunctionCallNode, InputMatches, InputMatchDescription, InInputType, bInIsRequired, OutErrorMessage); } bool FNiagaraStackFunctionInputBinder::TryBind( UNiagaraScript* InScript, TArray InDependentScripts, FCompileConstantResolver InConstantResolver, FString InOwningEmitterUniqueName, UNiagaraNodeFunctionCall* InFunctionCallNode, FName InInputName, TOptional InInputType, bool bInIsRequired, FText& OutErrorMessage) { FName QualifiedInputName = *(TEXT("Module.") + InInputName.ToString()); FInputMatchesPredicate InputMatches = FInputMatchesPredicate::CreateLambda([=](FNiagaraVariable InputVariable) { return InputVariable.GetName() == InInputName || InputVariable.GetName() == QualifiedInputName; }); FText InputMatchDescription = FText::Format(LOCTEXT("NameMatchDescriptionFormat", "name = '{0}'"), FText::FromName(InInputName)); return TryBindInternal(InScript, InDependentScripts, InConstantResolver, InOwningEmitterUniqueName, InFunctionCallNode, InputMatches, InputMatchDescription, InInputType, bInIsRequired, OutErrorMessage); } bool FNiagaraStackFunctionInputBinder::TryBindInternal( UNiagaraScript* InScript, TArray InDependentScripts, FCompileConstantResolver InConstantResolver, FString InOwningEmitterUniqueName, UNiagaraNodeFunctionCall* InFunctionCallNode, FInputMatchesPredicate InputMatchesCallback, FText InputMatchDescription, TOptional InInputType, bool bInIsRequired, FText& OutErrorMessage) { Script = InScript; for (UNiagaraScript* DependentScript : InDependentScripts) { DependentScripts.Add(TWeakObjectPtr(DependentScript)); } FunctionCallNode = InFunctionCallNode; TArray InputVariables; TSet HiddenInputVariables; FNiagaraStackGraphUtilities::GetStackFunctionInputs(*FunctionCallNode, InputVariables, HiddenInputVariables, InConstantResolver, FNiagaraStackGraphUtilities::ENiagaraGetStackFunctionInputPinsOptions::ModuleInputsOnly); bool bInputFound = false; for (const FNiagaraVariable& InputVariable : InputVariables) { if (HiddenInputVariables.Contains(InputVariable)) { continue; } if(InputMatchesCallback.Execute(InputVariable)) { InputName = InputVariable.GetName(); if(InInputType.IsSet() && InputVariable.GetType() != InInputType.GetValue()) { OutErrorMessage = FText::Format(LOCTEXT("TypeErrorFormat", "Input type {0} didn't match the required type {1}."), InputVariable.GetType().GetNameText(), InputType.GetNameText()); Reset(); return false; } InputType = InputVariable.GetType(); TOptional InputMetadata = InFunctionCallNode->GetNiagaraGraph()->GetMetaData(InputVariable); InputVariableGuid = InputMetadata.IsSet() ? InputMetadata->GetVariableGuid() : FGuid(); AliasedParameterHandle = FNiagaraParameterHandle::CreateAliasedModuleParameterHandle(FNiagaraParameterHandle(InputVariable.GetName()), FunctionCallNode.Get()); RefreshGraphPins(); if (OverridePin != nullptr && OverridePin->LinkedTo.Num() > 0) { OutErrorMessage = LOCTEXT("InputOverriddenError", "Input is overridden in the stack and doesn't support setting a local value."); Reset(); return false; } if (DefaultPin == nullptr) { OutErrorMessage = LOCTEXT("MiddingDefaultError", "Could not find the default pin for this input in the source graph, it may be hidden by a static switch."); Reset(); return false; } if (FNiagaraStackGraphUtilities::IsRapidIterationType(InputType)) { RapidIterationParameter = FNiagaraStackGraphUtilities::CreateRapidIterationParameter( InOwningEmitterUniqueName, Script->GetUsage(), AliasedParameterHandle.GetParameterHandleString(), InputVariable.GetType()); } bInputFound = true; break; } } // For the EditCondition and VisibleCondition, we want to be able to bind to static switch input variables UNiagaraGraph* CalledGraph = FunctionCallNode->GetCalledGraph(); if (!bInputFound && CalledGraph) { for (FNiagaraVariable InputVar : CalledGraph->FindStaticSwitchInputs()) { if (InputMatchesCallback.Execute(InputVar) && (!InInputType.IsSet() || InputVar.GetType() == InInputType.GetValue())) { InputName = InputVar.GetName(); InputType = InputVar.GetType(); AliasedParameterHandle = FNiagaraParameterHandle::CreateAliasedModuleParameterHandle(FNiagaraParameterHandle(InputVar.GetName()), FunctionCallNode.Get()); RefreshGraphPins(); bInputFound = true; break; } } } if (bInputFound == false) { Reset(); if (bInIsRequired) { OutErrorMessage = FText::Format(LOCTEXT("RequiredButNotFoundErrorFormat", "No input found with {0}"), InputMatchDescription); return false; } } return true; } bool FNiagaraStackFunctionInputBinder::IsValid() const { if (Script.IsValid()) { if (ValidScriptGraphChangeIdForOverridePin != Script->GetSource(FunctionCallNode->SelectedScriptVersion)->GetChangeID() || ValidScriptGraphChangeIdForDefaultPin != GetChangeIdFromFunctionScript()) { RefreshGraphPins(); } return (OverridePin == nullptr || OverridePin->LinkedTo.Num() == 0) && DefaultPin != nullptr; } return false; } void FNiagaraStackFunctionInputBinder::Reset() { Script.Reset(); DependentScripts.Empty(); FunctionCallNode.Reset(); InputType = FNiagaraTypeDefinition(); InputName = NAME_None; AliasedParameterHandle = FNiagaraParameterHandle(); ValidScriptGraphChangeIdForOverridePin = FGuid(); ValidScriptGraphChangeIdForDefaultPin = FGuid(); RapidIterationParameter = FNiagaraVariable(); DefaultPin = nullptr; OverridePin = nullptr; } FName FNiagaraStackFunctionInputBinder::GetInputName() const { return InputName; } FNiagaraTypeDefinition FNiagaraStackFunctionInputBinder::GetInputType() const { return InputType; } UNiagaraNodeFunctionCall* FNiagaraStackFunctionInputBinder::GetFunctionCallNode() const { return FunctionCallNode.Get(); } TArray FNiagaraStackFunctionInputBinder::GetData() const { checkf(Script.IsValid(), TEXT("Bound script is not valid")); if (ValidScriptGraphChangeIdForOverridePin != Script->GetSource(FunctionCallNode->SelectedScriptVersion)->GetChangeID() || ValidScriptGraphChangeIdForDefaultPin != GetChangeIdFromFunctionScript()) { RefreshGraphPins(); if (!OverridePin || OverridePin->LinkedTo.Num() == 0) { UE_LOG(LogNiagaraEditor, Warning, TEXT("FNiagaraStackFunctionInputBinder - OverridePin is no longer valid because the function was overriden")); } } // We copy the data to an array here rather than return a pointer to the data because values stored in pins are // backed by string so their value data allocated on the stack via conversion and so can't be returned by pointer. TArray Data; Data.AddUninitialized(InputType.GetSize()); if (RapidIterationParameter.IsValid() && Script->RapidIterationParameters.IndexOf(RapidIterationParameter) != -1) { Script->RapidIterationParameters.CopyParameterData(RapidIterationParameter, Data.GetData()); } else { const UEdGraphSchema_Niagara* Schema = GetDefault(); UEdGraphPin* ValuePin = OverridePin != nullptr ? OverridePin : DefaultPin; FMemory::Memcpy(Data.GetData(), Schema->PinToNiagaraVariable(ValuePin, true).GetData(), InputType.GetSize()); } return Data; } void FNiagaraStackFunctionInputBinder::SetData(const uint8* InValue, int32 InSize) { checkf(InSize == InputType.GetSize(), TEXT("Set value size doesn't match bound value size")); if (FMemory::Memcmp(GetData().GetData(), InValue, InSize) != 0) { if ( RapidIterationParameter.IsValid() ) { Script->Modify(); Script->RapidIterationParameters.SetParameterData(InValue, RapidIterationParameter, true); for (TWeakObjectPtr DependentScript : DependentScripts) { checkf(DependentScript.IsValid(), TEXT("Bound dependent script is no longer valid")); DependentScript->Modify(); DependentScript->RapidIterationParameters.SetParameterData(InValue, RapidIterationParameter, true); } } // Static variables are RI parameters and will therefore not force a recompile unless we take this path // Not taking this path will result in the Script StaticVariablesWritten being out of date if ( !RapidIterationParameter.IsValid() || InputType.IsStatic() ) { checkf(FunctionCallNode.IsValid(), TEXT("Bound function call is no longer valid")); FNiagaraVariable TempVariable(InputType, NAME_None); TempVariable.SetData(InValue); FString PinDefaultValue; const UEdGraphSchema_Niagara* Schema = GetDefault(); bool bSuccess = Schema->TryGetPinDefaultValueFromNiagaraVariable(TempVariable, PinDefaultValue); checkf(bSuccess, TEXT("Default value not supported for type %s"), *InputType.GetName()); if (OverridePin == nullptr) { OverridePin = &FNiagaraStackGraphUtilities::GetOrCreateStackFunctionInputOverridePin(*FunctionCallNode, AliasedParameterHandle, InputType, InputVariableGuid, FGuid()); } OverridePin->Modify(); OverridePin->DefaultValue = PinDefaultValue; Cast(OverridePin->GetOwningNode())->MarkNodeRequiresSynchronization(TEXT("OverridePin Default Value Changed"), true); ValidScriptGraphChangeIdForOverridePin = Script->GetSource(FunctionCallNode->SelectedScriptVersion)->GetChangeID(); ValidScriptGraphChangeIdForDefaultPin = GetChangeIdFromFunctionScript(); } } } FGuid FNiagaraStackFunctionInputBinder::GetChangeIdFromFunctionScript() const { if (FunctionCallNode->FunctionScript != nullptr && FunctionCallNode->GetFunctionScriptSource() != nullptr) { return FunctionCallNode->GetFunctionScriptSource()->GetChangeID(); } return FGuid(); } void FNiagaraStackFunctionInputBinder::RefreshGraphPins() const { if (FunctionCallNode.IsValid() == false || FunctionCallNode->FunctionScript == nullptr || FunctionCallNode->GetFunctionScriptSource() == nullptr) { OverridePin = nullptr; DefaultPin = nullptr; return; } OverridePin = FNiagaraStackGraphUtilities::GetStackFunctionInputOverridePin(*FunctionCallNode, AliasedParameterHandle); DefaultPin = FunctionCallNode->FindParameterMapDefaultValuePin(InputName, Script->GetUsage(), FCompileConstantResolver()); // if we bind to a static switch value the default value pin cannot be found via the parameter map if (DefaultPin == nullptr) { DefaultPin = FunctionCallNode->FindStaticSwitchInputPin(InputName); } ValidScriptGraphChangeIdForOverridePin = Script->GetSource(FunctionCallNode->SelectedScriptVersion)->GetChangeID(); ValidScriptGraphChangeIdForDefaultPin = GetChangeIdFromFunctionScript(); } #undef LOCTEXT_NAMESPACE