Files
UnrealEngine/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraStackFunctionInputBinder.cpp
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

337 lines
12 KiB
C++

// 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<UNiagaraScript*> InDependentScripts,
FCompileConstantResolver InConstantResolver,
FString InOwningEmitterUniqueName,
UNiagaraNodeFunctionCall* InFunctionCallNode,
FName InMetaDataKey,
FString InMetaDataValue,
TOptional<FNiagaraTypeDefinition> InInputType,
bool bInIsRequired,
FText& OutErrorMessage)
{
UNiagaraGraph* FunctionGraph = InFunctionCallNode->GetCalledGraph();
FInputMatchesPredicate InputMatches;
InputMatches.BindLambda([=](FNiagaraVariable InputVariable)
{
TOptional<FNiagaraVariableMetaData> 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<UNiagaraScript*> InDependentScripts,
FCompileConstantResolver InConstantResolver,
FString InOwningEmitterUniqueName,
UNiagaraNodeFunctionCall* InFunctionCallNode,
FName InInputName,
TOptional<FNiagaraTypeDefinition> 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<UNiagaraScript*> InDependentScripts,
FCompileConstantResolver InConstantResolver,
FString InOwningEmitterUniqueName,
UNiagaraNodeFunctionCall* InFunctionCallNode,
FInputMatchesPredicate InputMatchesCallback,
FText InputMatchDescription,
TOptional<FNiagaraTypeDefinition> InInputType,
bool bInIsRequired,
FText& OutErrorMessage)
{
Script = InScript;
for (UNiagaraScript* DependentScript : InDependentScripts)
{
DependentScripts.Add(TWeakObjectPtr<UNiagaraScript>(DependentScript));
}
FunctionCallNode = InFunctionCallNode;
TArray<FNiagaraVariable> InputVariables;
TSet<FNiagaraVariable> 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<FNiagaraVariableMetaData> 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<uint8> 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<uint8> 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<UEdGraphSchema_Niagara>();
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<UNiagaraScript> 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<UEdGraphSchema_Niagara>();
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<UNiagaraNode>(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