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

4860 lines
165 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "NiagaraGraphDigest.h"
#include "Containers/MapBuilder.h"
#include "Internationalization/Internationalization.h"
#include "INiagaraEditorTypeUtilities.h"
#include "NiagaraCompilationBridge.h"
#include "NiagaraCompilationPrivate.h"
#include "NiagaraDigestDatabase.h"
#include "NiagaraEditorModule.h"
#include "NiagaraEditorUtilities.h"
#include "NiagaraNodeAssignment.h"
#include "NiagaraNodeConvert.h"
#include "NiagaraNodeCustomHlsl.h"
#include "NiagaraNodeEmitter.h"
#include "NiagaraNodeIf.h"
#include "NiagaraNodeOp.h"
#include "NiagaraNodeOutput.h"
#include "NiagaraNodeOutputTag.h"
#include "NiagaraNodeParameterMapFor.h"
#include "NiagaraNodeParameterMapGet.h"
#include "NiagaraNodeParameterMapSet.h"
#include "NiagaraNodeReadDataSet.h"
#include "NiagaraNodeReroute.h"
#include "NiagaraNodeSelect.h"
#include "NiagaraNodeSimTargetSelector.h"
#include "NiagaraNodeStaticSwitch.h"
#include "NiagaraNodeWriteDataSet.h"
#include "NiagaraTraversalStateContext.h"
#include "NiagaraScriptSource.h"
#include "Algo/RemoveIf.h"
#define LOCTEXT_NAMESPACE "NiagaraCompiler"
#define NIAGARA_GRAPH_DIGEST_NODE_TYPE(name) NIAGARA_GRAPH_DIGEST_NODE_IMPLEMENT_BODY(name)
NIAGARA_GRAPH_DIGEST_NODE_TYPE_LIST;
#undef NIAGARA_GRAPH_DIGEST_NODE_TYPE
struct FNiagaraCompilationGraphCreateContext
{
FNiagaraCompilationGraphCreateContext(FNiagaraCompilationGraphDigested& InParentGraph, TArray<const FNiagaraCompilationGraphDigested*>& InDigestedChildGraphs, const FNiagaraGraphChangeIdBuilder& InChangeIdBuilder)
: ParentGraph(InParentGraph)
, DigestedChildGraphs(InDigestedChildGraphs)
, ChangeIdBuilder(InChangeIdBuilder)
{
}
FNiagaraCompilationGraphDigested& ParentGraph;
TArray<const FNiagaraCompilationGraphDigested*>& DigestedChildGraphs;
const FNiagaraGraphChangeIdBuilder& ChangeIdBuilder;
};
struct FNiagaraCompilationGraphDuplicateContext
{
FNiagaraCompilationGraphDuplicateContext(FNiagaraCompilationGraph& InTargetGraph, const FNiagaraCompilationGraph& InSourceGraph, const TArray<ENiagaraScriptUsage>& InScriptUsages, const FNiagaraCompilationCopyData* InCopyCompilationData, const FNiagaraCompilationBranchMap& InBranches)
: TargetGraph(InTargetGraph)
, SourceGraph(InSourceGraph)
, ScriptUsages(InScriptUsages)
, CopyCompilationData(InCopyCompilationData)
, Branches(InBranches)
{
}
FNiagaraCompilationGraph& TargetGraph;
const FNiagaraCompilationGraph& SourceGraph;
const TArray<ENiagaraScriptUsage>& ScriptUsages;
const FNiagaraCompilationCopyData* CopyCompilationData;
const FNiagaraCompilationBranchMap& Branches;
TMap<const FNiagaraCompilationNode*, FNiagaraCompilationNode*> DuplicatedNodeMap;
TArray<FNiagaraCompilationNodeFunctionCall*> FunctionsRequiresGraph;
};
struct FNiagaraCompilationGraphInstanceContext
{
FNiagaraCompilationGraphInstanceContext(const FNiagaraFixedConstantResolver& InConstantResolver, const FNiagaraCompilationGraph* InCompilationGraph, const FNiagaraPrecompileData* InPrecompileData)
: ConstantResolver(InConstantResolver)
, PrecompileData(InPrecompileData)
{
bDisableDebugSwitches = InPrecompileData ? InPrecompileData->bDisableDebugSwitches : false;
TraversalContext.BeginContext(InCompilationGraph, ConstantResolver);
}
FNiagaraCompilationGraphInstanceContext() = delete;
const FNiagaraFixedConstantResolver& ConstantResolver;
const FNiagaraPrecompileData* PrecompileData;
FNiagaraTraversalStateContext TraversalContext;
FGraphTraversalHandle GraphTraversalHandle;
TArray<const FNiagaraCompilationNodeFunctionCall*> FunctionStack;
bool bForceNumericResolution = false;
bool bDisableDebugSwitches = false;
void EnterFunction(const FNiagaraCompilationNodeFunctionCall* InCallingNode)
{
TraversalContext.PushFunction(InCallingNode, ConstantResolver);
GraphTraversalHandle.PushNode(InCallingNode);
FunctionStack.Push(InCallingNode);
}
void LeaveFunction(const FNiagaraCompilationNodeFunctionCall* FunctionBeingLeft)
{
const FNiagaraCompilationNodeFunctionCall* CurrentFunction = FunctionStack.Top();
check(CurrentFunction == FunctionBeingLeft);
TraversalContext.PopFunction(CurrentFunction);
GraphTraversalHandle.PopNode();
FunctionStack.Pop();
}
const FNiagaraCompilationNodeFunctionCall* GetCurrentFunctionNode() const
{
return FunctionStack.Top();
}
};
namespace NiagaraCompilationImpl
{
DECLARE_DELEGATE_RetVal_TwoParams(TUniquePtr<FNiagaraCompilationNode>, FCreateNodeDelegate, const UEdGraphNode*, FNiagaraCompilationGraphCreateContext&);
DECLARE_DELEGATE_RetVal_TwoParams(TUniquePtr<FNiagaraCompilationNode>, FDuplicateNodeDelegate, const FNiagaraCompilationNode*, FNiagaraCompilationGraphDuplicateContext&);
struct FNodeHelper
{
FNodeHelper(FNiagaraCompilationNode::ENodeType InNodeType)
: NodeType(InNodeType)
{}
FNodeHelper() = delete;
FCreateNodeDelegate CreateNodeDelegate;
FDuplicateNodeDelegate DuplicateNodeDelegate;
const FNiagaraCompilationNode::ENodeType NodeType;
};
template<typename NodeClass, typename CompilationNodeClass>
FNodeHelper CreateNodeHelper(FNiagaraCompilationNode::ENodeType NodeType)
{
FNodeHelper Helper(NodeType);
Helper.CreateNodeDelegate.BindLambda
(
[](const UEdGraphNode* SourceNode, FNiagaraCompilationGraphCreateContext& NodeContext) -> TUniquePtr<FNiagaraCompilationNode>
{
if (const NodeClass* TypedSourceNode = static_cast<const NodeClass*>(SourceNode))
{
return MakeUnique<CompilationNodeClass>(TypedSourceNode, NodeContext);
}
return TUniquePtr<FNiagaraCompilationNode>();
}
);
Helper.DuplicateNodeDelegate.BindLambda
(
[](const FNiagaraCompilationNode* SourceNode, FNiagaraCompilationGraphDuplicateContext& NodeContext) -> TUniquePtr<FNiagaraCompilationNode>
{
if (const CompilationNodeClass* TypedSourceNode = static_cast<const CompilationNodeClass*>(SourceNode))
{
return MakeUnique<CompilationNodeClass>(*TypedSourceNode, NodeContext);
}
return TUniquePtr<FNiagaraCompilationNode>();
}
);
return Helper;
}
using FCreateNodeMap = TMap<UClass*, FNodeHelper>;
#define MAKE_COMPILATION_ENUM_NAME(name) FNiagaraCompilationNode::ENodeType::name
#define MAKE_SOURCE_NODE_CLASS_NAME(name) UNiagaraNode##name
#define MAKE_COMPILATION_CLASS_NAME(name) FNiagaraCompilationNode##name
#define NIAGARA_GRAPH_DIGEST_NODE_TYPE(name) \
.Add(MAKE_SOURCE_NODE_CLASS_NAME(name)::StaticClass(), CreateNodeHelper<MAKE_SOURCE_NODE_CLASS_NAME(name), MAKE_COMPILATION_CLASS_NAME(name)>(MAKE_COMPILATION_ENUM_NAME(name)))
const FCreateNodeMap& GetCreateNodeMap()
{
static FCreateNodeMap NodeMap;
static FRWLock MapLock;
{
FReadScopeLock Lock(MapLock);
if (!NodeMap.IsEmpty())
{
return NodeMap;
}
}
{
FWriteScopeLock Lock(MapLock);
if (NodeMap.IsEmpty())
{
NodeMap = TMapBuilder<UClass*, FNodeHelper>()
NIAGARA_GRAPH_DIGEST_NODE_TYPE_LIST;
}
}
return NodeMap;
}
#undef NIAGARA_GRAPH_DIGEST_NODE_TYPE
TUniquePtr<FNiagaraCompilationNode> CreateNode(const UEdGraphNode* SourceNode, FNiagaraCompilationGraphCreateContext& NodeContext)
{
if (const FNodeHelper* NodeHelper = GetCreateNodeMap().Find(SourceNode->GetClass()))
{
if (NodeHelper->CreateNodeDelegate.IsBound())
{
return NodeHelper->CreateNodeDelegate.Execute(SourceNode, NodeContext);
}
}
return nullptr;
}
TUniquePtr<FNiagaraCompilationNode> DuplicateNode(const FNiagaraCompilationNode* SourceNode, FNiagaraCompilationGraphDuplicateContext& NodeContext)
{
if (const FNodeHelper* NodeHelper = GetCreateNodeMap().Find(SourceNode->GetSourceNodeClass()))
{
if (NodeHelper->DuplicateNodeDelegate.IsBound())
{
return NodeHelper->DuplicateNodeDelegate.Execute(SourceNode, NodeContext);
}
}
return nullptr;
}
const UEdGraphPin* FindInputExecutionPin(const UNiagaraNode* Node)
{
if (Node)
{
for (const UEdGraphPin* Pin : Node->Pins)
{
if (Pin->Direction == EEdGraphPinDirection::EGPD_Input)
{
if (Node->IsParameterMapPin(Pin))
{
return Pin;
}
}
}
}
return nullptr;
}
const UEdGraphPin* TraceThroughIgnoredNodes(const UEdGraphPin* Pin)
{
check(Pin);
check(Pin->Direction == EEdGraphPinDirection::EGPD_Output);
while (Pin)
{
const UEdGraphNode* OwningNode = Pin->GetOwningNode();
const UEdGraphPin* InputPin = nullptr;
if (!OwningNode->IsNodeEnabled())
{
if (const UNiagaraNode* NiagaraNode = Cast<const UNiagaraNode>(OwningNode))
{
if (NiagaraNode->IsParameterMapPin(Pin))
{
InputPin = FindInputExecutionPin(NiagaraNode);
}
else
{
return nullptr;
}
}
}
else if (const UNiagaraNodeReroute* RerouteNode = Cast<const UNiagaraNodeReroute>(OwningNode))
{
InputPin = RerouteNode->GetInputPin(0);
}
else
{
break;
}
Pin = (InputPin && !InputPin->LinkedTo.IsEmpty()) ? InputPin->LinkedTo[0] : nullptr;
}
return Pin;
}
const FNiagaraCompilationOutputPin* TraceOutputPin(TNiagaraParameterMapHistoryBuilder<FNiagaraCompilationDigestBridge>& Builder, const FNiagaraCompilationOutputPin* Pin, bool bFilterForCompilation)
{
if (!bFilterForCompilation)
{
return Pin;
}
// we ignore reroute nodes because those have already been removed from the compilation copies
// what is left is static switches
if (const FNiagaraCompilationNodeStaticSwitch* StaticSwitchNode = Pin->OwningNode->AsType<FNiagaraCompilationNodeStaticSwitch>())
{
return StaticSwitchNode->TraceOutputPin(Builder, Pin, bFilterForCompilation);
}
return Pin;
}
TArray<const FNiagaraCompilationNode*> CollectConnectedNodes(TConstArrayView<const FNiagaraCompilationNode*> RootNodes, int32 MaxNodeCount, const FNiagaraCompilationBranchMap& BranchMap)
{
TSet<const FNiagaraCompilationNode*> VisitedNodeSet;
VisitedNodeSet.Reserve(MaxNodeCount);
VisitedNodeSet.Append(RootNodes);
TArray<const FNiagaraCompilationNode*> NodesToProcess;
NodesToProcess.Reserve(MaxNodeCount);
NodesToProcess.Append(RootNodes);
while (!NodesToProcess.IsEmpty())
{
const FNiagaraCompilationNode* CurrentNode = NodesToProcess.Pop();
for (const FNiagaraCompilationInputPin& InputPin : CurrentNode->InputPins)
{
if (const FNiagaraCompilationOutputPin* OutputPin = InputPin.TraceBranchMap(BranchMap)->LinkedTo)
{
bool bAlreadyAdded;
VisitedNodeSet.Add(OutputPin->OwningNode, &bAlreadyAdded);
if (!bAlreadyAdded)
{
NodesToProcess.Add(OutputPin->OwningNode);
}
}
}
}
return VisitedNodeSet.Array();
}
TArray<const FNiagaraCompilationNode*> TopologicalSort(TConstArrayView<const FNiagaraCompilationNode*> Nodes, const FNiagaraCompilationBranchMap& BranchMap)
{
TSet<const FNiagaraCompilationNode*> VisitedNodeSet;
TArray<const FNiagaraCompilationNode*> SortedNodes;
SortedNodes.Reserve(Nodes.Num());
using FProcessingNode = TTuple<const FNiagaraCompilationNode*, bool /*IsParent*/>;
TArray<FProcessingNode> NodesToProcess;
for (const FNiagaraCompilationNode* Node : Nodes)
{
if (!VisitedNodeSet.Contains(Node))
{
NodesToProcess.Emplace(Node, false);
}
while (!NodesToProcess.IsEmpty())
{
FProcessingNode CurrentNode = NodesToProcess.Pop();
if (CurrentNode.Value)
{
SortedNodes.Add(CurrentNode.Key);
}
else if (!VisitedNodeSet.Contains(CurrentNode.Key))
{
VisitedNodeSet.Add(CurrentNode.Key);
NodesToProcess.Emplace(CurrentNode.Key, true);
for (const FNiagaraCompilationInputPin& InputPin : CurrentNode.Key->InputPins)
{
if (const FNiagaraCompilationOutputPin* OutputPin = InputPin.TraceBranchMap(BranchMap)->LinkedTo)
{
const FNiagaraCompilationNode* TargetNode = OutputPin->OwningNode;
if (!VisitedNodeSet.Contains(TargetNode))
{
NodesToProcess.Emplace(TargetNode, false);
}
}
}
}
}
}
return MoveTemp(SortedNodes);
}
template<typename PinType>
PinType* FindPinByName(TArrayView<PinType> Pins, FName Name)
{
return Pins.FindByPredicate([Name](const PinType& Pin) -> bool
{
return Pin.Variable.GetName() == Name;
});
}
template<typename PinType>
int32 GetPinIndexById(TConstArrayView<PinType> Pins, const FGuid& PinId)
{
return Pins.IndexOfByPredicate([&PinId](const FNiagaraCompilationPin& Pin) -> bool
{
return Pin.UniquePinId == PinId;
});
}
template<typename PinType>
int32 GetPinIndexByPersistentId(TConstArrayView<PinType> Pins, const FGuid& PersistentId)
{
return Pins.IndexOfByPredicate([&PersistentId](const FNiagaraCompilationPin& Pin) -> bool
{
return Pin.PersistentGuid == PersistentId;
});
}
bool GetStaticSwitchValueFromPin(FNiagaraCompilationGraphInstanceContext& Context, const FNiagaraCompilationOutputPin& OutputPin, int32& StaticSwitchValue)
{
if (!Context.PrecompileData)
{
return false;
}
bool bResultFound = false;
FGraphTraversalHandle GraphTraversalHandle(Context.GraphTraversalHandle);
GraphTraversalHandle.PushPin(&OutputPin);
// find it in the compiled data
if (const FString* StaticValue = Context.PrecompileData->PinToConstantValues.Find(GraphTraversalHandle))
{
FNiagaraEditorModule& NiagaraEditorModule = FModuleManager::GetModuleChecked<FNiagaraEditorModule>("NiagaraEditor");
FNiagaraVariable VarWithValue(OutputPin.Variable);
FNiagaraVariable SearchVar(OutputPin.Variable.GetType(), FName(*StaticValue));
int32 StaticVarSearchIdx = Context.PrecompileData->StaticVariables.Find(SearchVar);
if (StaticVarSearchIdx == INDEX_NONE)
{
TSharedPtr<INiagaraEditorTypeUtilities, ESPMode::ThreadSafe> TypeEditorUtilities = NiagaraEditorModule.GetTypeUtilities(OutputPin.Variable.GetType());
if (TypeEditorUtilities.IsValid() && TypeEditorUtilities->CanHandlePinDefaults())
{
TypeEditorUtilities->SetValueFromPinDefaultString(*StaticValue, VarWithValue);
}
}
else
{
VarWithValue = Context.PrecompileData->StaticVariables[StaticVarSearchIdx];
}
if (VarWithValue.IsDataAllocated())
{
if (VarWithValue.GetType().IsSameBaseDefinition(FNiagaraTypeDefinition::GetBoolDef()))
{
StaticSwitchValue = VarWithValue.GetValue<bool>();
bResultFound = true;
}
else if (VarWithValue.GetType().IsSameBaseDefinition(FNiagaraTypeDefinition::GetIntDef()) || VarWithValue.GetType().IsEnum())
{
StaticSwitchValue = VarWithValue.GetValue<int32>();
bResultFound = true;
}
else
{
check(false);
}
}
}
return bResultFound;
}
}; // NiagaraCompilationImpl
void FNiagaraGraphChangeIdBuilder::ParseReferencedGraphs(const UNiagaraGraph* Graph)
{
TSet<const UNiagaraGraph*> CurrentGraphChain;
RecursiveBuildGraphChangeId(Graph, CurrentGraphChain);
}
FGuid FNiagaraGraphChangeIdBuilder::FindChangeId(const UNiagaraGraph* Graph) const
{
const FGuid* ChangeId = ChangeIdMap.Find(Graph);
if (ensure(ChangeId))
{
return *ChangeId;
}
return FGuid();
}
FGuid FNiagaraGraphChangeIdBuilder::RecursiveBuildGraphChangeId(const UNiagaraGraph* Graph, TSet<const UNiagaraGraph*>& CurrentGraphChain)
{
if (!Graph)
{
return FGuid();
}
// see if the graph has already been processed
if (const FGuid* ExistingChangeId = ChangeIdMap.Find(Graph))
{
return *ExistingChangeId;
}
FGuid ChangeId = Graph->GetChangeID();
// check for a cycle in the graph chain we're currently processing
const bool bCycleInGraphChain = CurrentGraphChain.Contains(Graph);
if (bCycleInGraphChain)
{
ensure(false);
return ChangeId;
}
CurrentGraphChain.Add(Graph);
for (const UEdGraphNode* Node : Graph->Nodes)
{
const UNiagaraGraph* SubGraph = nullptr;
if (const UNiagaraNodeFunctionCall* FunctionCallNode = Cast<const UNiagaraNodeFunctionCall>(Node))
{
SubGraph = FunctionCallNode->GetCalledGraph();
}
else if (const UNiagaraNodeEmitter* EmitterNode = Cast<const UNiagaraNodeEmitter>(Node))
{
SubGraph = EmitterNode->GetCalledGraph();
}
if (SubGraph)
{
ChangeId = FGuid::Combine(ChangeId, SubGraph->GetChangeID());
ChangeId = FGuid::Combine(ChangeId, RecursiveBuildGraphChangeId(SubGraph, CurrentGraphChain));
}
}
ChangeIdMap.Add(Graph, ChangeId);
return ChangeId;
}
void FNiagaraCompilationGraphDigested::Digest(const UNiagaraGraph* InGraph, const FNiagaraGraphChangeIdBuilder& ChangeIdBuilder)
{
using namespace NiagaraCompilationImpl;
TArray<UClass*> IgnoredClassTypes =
{
UNiagaraNodeReroute::StaticClass(),
UEdGraphNode_Comment::StaticClass(),
};
SourceGraph = InGraph;
Nodes.Reserve(InGraph->Nodes.Num());
TMap<const UEdGraphNode*, int32> NodeIndexMap;
FNiagaraCompilationGraphCreateContext NodeContext(*this, ChildGraphs, ChangeIdBuilder);
for (const UEdGraphNode* SourceNode : InGraph->Nodes)
{
if (!SourceNode->IsNodeEnabled())
{
continue;
}
if (TUniquePtr<FNiagaraCompilationNode> CompilationNode = CreateNode(SourceNode, NodeContext))
{
NodeIndexMap.Add(SourceNode, Nodes.Num());
Nodes.Emplace(MoveTemp(CompilationNode));
}
else
{
check(IgnoredClassTypes.Contains(SourceNode->GetClass()));
}
}
// after all the nodes & pins have been created we can go in and make the connections between the nodes
for (TUniquePtr<FNiagaraCompilationNode>& CompilationNode : Nodes)
{
for (FNiagaraCompilationInputPin& InputPin : CompilationNode->InputPins)
{
const UEdGraphPin* SourceInputPin = CompilationNode->SourceNode->Pins[InputPin.SourcePinIndex];
const UEdGraphPin* SourceLinkedPin = nullptr;
// apparently some content exists where we'll have multiple copies of a connection added. Find the first non-null LinkedPin
for (const UEdGraphPin* CurrentLinkedPin : SourceInputPin->LinkedTo)
{
if (SourceLinkedPin == nullptr)
{
SourceLinkedPin = CurrentLinkedPin;
break;
}
}
if (SourceLinkedPin)
{
if (const UEdGraphPin* LinkedPin = TraceThroughIgnoredNodes(SourceLinkedPin))
{
const UEdGraphNode* LinkedSourceNode = LinkedPin->GetOwningNode();
const int32* LinkedNodeIndexPtr = NodeIndexMap.Find(LinkedSourceNode);
// There are situations where a pin is connected to a node that doesn't exist within the UNiagaraGraph::Nodes array.
// This corruption seems connected to auto-generated input nodes for function calls. For now we're going to ignore those
// connections
const int32 LinkedNodeIndex = (LinkedNodeIndexPtr && Nodes.IsValidIndex(*LinkedNodeIndexPtr)) ? *LinkedNodeIndexPtr : INDEX_NONE;
if (LinkedNodeIndex != INDEX_NONE)
{
TUniquePtr<FNiagaraCompilationNode>& LinkedNode = Nodes[LinkedNodeIndex];
const int32 LinkedSourcePinIndex = LinkedNode->SourceNode->Pins.IndexOfByKey(LinkedPin);
if (LinkedSourcePinIndex != INDEX_NONE)
{
FNiagaraCompilationOutputPin* LinkedOutputPin = LinkedNode->OutputPins.FindByPredicate([LinkedSourcePinIndex](const FNiagaraCompilationOutputPin& OutputPin)
{
return OutputPin.SourcePinIndex == LinkedSourcePinIndex;
});
InputPin.LinkedTo = LinkedOutputPin;
// and create the reverse link
if (LinkedOutputPin)
{
LinkedOutputPin->LinkedTo.AddUnique(&InputPin);
}
}
}
}
}
}
}
UPackage* TransientPackage = GetTransientPackage();
// todo - why do we need a duplicate of the CDO?
auto RegisterTransientCDO = [this](UPackage* Owner, UClass* DataInterfaceClass) -> void
{
if (!CachedDataInterfaceCDODuplicates.Contains(DataInterfaceClass))
{
UNiagaraDataInterface* DataInterfaceCDO = DataInterfaceClass->GetDefaultObject<UNiagaraDataInterface>();
UNiagaraDataInterface* DuplicatedCDO = DuplicateObject<UNiagaraDataInterface>(DataInterfaceCDO, Owner);
CachedDataInterfaceCDODuplicates.Add(DataInterfaceClass, DuplicatedCDO);
}
};
using FScriptMap = TMap<FNiagaraVariable, TObjectPtr<UNiagaraScriptVariable>>;
const FScriptMap& GraphScriptVariables = InGraph->GetAllMetaData();
ScriptVariableData.Reserve(GraphScriptVariables.Num());
for (const FScriptMap::ElementType& GraphScriptVariable : GraphScriptVariables)
{
if (GraphScriptVariable.Value)
{
ScriptVariableData.AddDefaulted_GetRef().InitFrom(*GraphScriptVariable.Value, false);
const FNiagaraVariable& Variable = GraphScriptVariable.Key;
const FNiagaraTypeDefinition& VariableType = Variable.GetType();
if (VariableType.IsDataInterface())
{
RegisterTransientCDO(TransientPackage, VariableType.GetClass());
}
if (VariableType.IsStatic())
{
bContainsStaticVariables = true;
}
}
}
// additionally we're going to go through the mapgets in the node and see if we might have missed any
// datainterface variables. There are cases in top level graphs where seemingly orphaned mapget nodes
// can reference a data interface variable but it isn't represented in the script variables or in input
// nodes.
for (TUniquePtr<FNiagaraCompilationNode>& CompilationNode : Nodes)
{
if (const FNiagaraCompilationNodeParameterMapGet* MapGet = CompilationNode->AsType<FNiagaraCompilationNodeParameterMapGet>())
{
for (const FNiagaraCompilationOutputPin& OutputPin : MapGet->OutputPins)
{
if (OutputPin.Variable.IsDataInterface())
{
RegisterTransientCDO(TransientPackage, OutputPin.Variable.GetType().GetClass());
}
}
}
}
if (const UNiagaraScript* SourceScript = InGraph->GetTypedOuter<const UNiagaraScript>())
{
SourceScriptName = SourceScript->GetName();
SourceScriptFullName = SourceScript->GetFullName();
}
// go through all the data interfaces that have been registered and make sure we populate CachedDataInterfaceCDODuplicates
// along with moving things over to transient duplicates
for (const FDataInterfaceDuplicateMap::ElementType& DataInterfaceIt : CachedDataInterfaceDuplicates)
{
if (UNiagaraDataInterface* DataInterface = DataInterfaceIt.Value.Get())
{
if (UClass* DataInterfaceClass = DataInterface->GetClass())
{
RegisterTransientCDO(TransientPackage, DataInterfaceClass);
}
}
}
}
UNiagaraDataInterface* FNiagaraCompilationGraphDigested::DigestDataInterface(UNiagaraDataInterface* SourceDataInterface)
{
check(IsInGameThread());
TObjectPtr<UNiagaraDataInterface>& ExistingDuplicate = CachedDataInterfaceDuplicates.FindOrAdd(SourceDataInterface);
if (!ExistingDuplicate)
{
ExistingDuplicate = DuplicateObject<UNiagaraDataInterface>(SourceDataInterface, GetTransientPackage());
}
return ExistingDuplicate;
}
void FNiagaraCompilationGraphDigested::RegisterObjectAsset(FName VariableName, UObject* SourceObjectAsset)
{
const UObject* ExistingObjectAsset = CachedNamedObjectAssets.FindRef(VariableName);
if (ExistingObjectAsset)
{
check(ExistingObjectAsset == SourceObjectAsset);
}
else
{
CachedNamedObjectAssets.Add(VariableName, SourceObjectAsset);
}
}
void FNiagaraCompilationGraphDigested::CollectReferencedDataInterfaceCDO(FDataInterfaceCDOMap& Interfaces) const
{
// add our collected DIs then iterate over all the child graphs
for (FDataInterfaceCDOMap::TConstIterator It = CachedDataInterfaceCDODuplicates.CreateConstIterator(); It; ++It)
{
Interfaces.FindOrAdd(It.Key(), It.Value());
}
for (const FNiagaraCompilationGraphDigested* ChildGraph : ChildGraphs)
{
ChildGraph->CollectReferencedDataInterfaceCDO(Interfaces);
}
}
void FNiagaraCompilationGraph::FindOutputNodes(TArray<const FNiagaraCompilationNodeOutput*>& OutputNodes) const
{
OutputNodes.Reserve(OutputNodeIndices.Num());
for (int32 OutputNodeIndex : OutputNodeIndices)
{
if (const FNiagaraCompilationNodeOutput* OutputNode = Nodes[OutputNodeIndex]->AsType<FNiagaraCompilationNodeOutput>())
{
OutputNodes.Add(OutputNode);
}
}
}
void FNiagaraCompilationGraph::FindOutputNodes(ENiagaraScriptUsage TargetUsageType, TArray<const FNiagaraCompilationNodeOutput*>& OutputNodes) const
{
OutputNodes.Reserve(OutputNodeIndices.Num());
for (int32 OutputNodeIndex : OutputNodeIndices)
{
if (const FNiagaraCompilationNodeOutput* OutputNode = Nodes[OutputNodeIndex]->AsType<FNiagaraCompilationNodeOutput>())
{
if (OutputNode->Usage == TargetUsageType)
{
OutputNodes.Add(OutputNode);
}
}
}
}
const FNiagaraCompilationNodeOutput* FNiagaraCompilationGraph::FindOutputNode(ENiagaraScriptUsage Usage, const FGuid& UsageId) const
{
for (int32 OutputNodeIndex : OutputNodeIndices)
{
const FNiagaraCompilationNodeOutput& OutputNode = Nodes[OutputNodeIndex]->AsTypeRef<FNiagaraCompilationNodeOutput>();
if (OutputNode.Usage == Usage && OutputNode.UsageId == UsageId)
{
return &OutputNode;
}
}
return nullptr;
}
const FNiagaraCompilationNodeOutput* FNiagaraCompilationGraph::FindEquivalentOutputNode(ENiagaraScriptUsage Usage, const FGuid& UsageId) const
{
for (int32 OutputNodeIndex : OutputNodeIndices)
{
const FNiagaraCompilationNodeOutput& OutputNode = Nodes[OutputNodeIndex]->AsTypeRef<FNiagaraCompilationNodeOutput>();
if (UNiagaraScript::IsEquivalentUsage(OutputNode.Usage, Usage) && OutputNode.UsageId == UsageId)
{
return &OutputNode;
}
}
return nullptr;
}
void FNiagaraCompilationGraph::FindInputNodes(TArray<const FNiagaraCompilationNodeInput*>& OutInputNodes, UNiagaraGraph::FFindInputNodeOptions Options) const
{
using namespace NiagaraCompilationImpl;
TArray<const FNiagaraCompilationNodeInput*> InputNodes;
if (!Options.bFilterByScriptUsage)
{
for (int32 InputNodeIndex : InputNodeIndices)
{
const FNiagaraCompilationNodeInput* InputNode = Nodes[InputNodeIndex]->AsType<FNiagaraCompilationNodeInput>();
if (ensure(InputNode))
{
if ((InputNode->Usage == ENiagaraInputNodeUsage::Parameter && Options.bIncludeParameters) ||
(InputNode->Usage == ENiagaraInputNodeUsage::Attribute && Options.bIncludeAttributes) ||
(InputNode->Usage == ENiagaraInputNodeUsage::SystemConstant && Options.bIncludeSystemConstants) ||
(InputNode->Usage == ENiagaraInputNodeUsage::TranslatorConstant && Options.bIncludeTranslatorConstants))
{
InputNodes.Add(InputNode);
}
}
}
}
else
{
const TArray<const FNiagaraCompilationNode*> OutputNodes = FindOutputNodesByUsage({Options.TargetScriptUsage});
TArray<const FNiagaraCompilationNode*> ConnectedNodes = CollectConnectedNodes(OutputNodes, Nodes.Num(), FNiagaraCompilationBranchMap());
for (int32 InputNodeIndex : InputNodeIndices)
{
const FNiagaraCompilationNodeInput* InputNode = Nodes[InputNodeIndex]->AsType<FNiagaraCompilationNodeInput>();
if (ensure(InputNode))
{
if ((InputNode->Usage == ENiagaraInputNodeUsage::Parameter && Options.bIncludeParameters) ||
(InputNode->Usage == ENiagaraInputNodeUsage::Attribute && Options.bIncludeAttributes) ||
(InputNode->Usage == ENiagaraInputNodeUsage::SystemConstant && Options.bIncludeSystemConstants))
{
if (ConnectedNodes.Contains(InputNode))
{
InputNodes.Add(InputNode);
}
}
}
}
}
if (Options.bFilterDuplicates)
{
for (const FNiagaraCompilationNodeInput* InputNode : InputNodes)
{
auto NodeMatches = [=](const FNiagaraCompilationNodeInput* UniqueInputNode)
{
if (InputNode->Usage == ENiagaraInputNodeUsage::Parameter)
{
return UniqueInputNode->InputVariable.IsEquivalent(InputNode->InputVariable, false);
}
else
{
return UniqueInputNode->InputVariable.IsEquivalent(InputNode->InputVariable);
}
};
if (OutInputNodes.ContainsByPredicate(NodeMatches) == false)
{
OutInputNodes.Add(InputNode);
}
}
}
else
{
OutInputNodes.Append(InputNodes);
}
if (Options.bSort)
{
Algo::Sort(OutInputNodes, [](const FNiagaraCompilationNodeInput* Lhs, const FNiagaraCompilationNodeInput* Rhs) -> bool
{
if (Lhs->CallSortPriority < Rhs->CallSortPriority)
{
return true;
}
else if (Lhs->CallSortPriority > Rhs->CallSortPriority)
{
return false;
}
//If equal priority, sort lexicographically.
return Lhs->InputVariable.GetName().LexicalLess(Rhs->InputVariable.GetName());
});
}
}
FString FNiagaraCompilationGraph::GetFunctionAliasByContext(const FNiagaraDigestFunctionAliasContext& FunctionAliasContext) const
{
FString FunctionAlias;
TSet<UClass*> SkipNodeTypes;
for (const TUniquePtr<FNiagaraCompilationNode>& Node : Nodes)
{
if (const FNiagaraCompilationNode* NiagaraNode = Node.Get())
{
if (SkipNodeTypes.Contains(NiagaraNode->GetSourceNodeClass()))
{
continue;
}
bool OncePerNodeType = false;
NiagaraNode->AppendFunctionAliasForContext(FunctionAliasContext, FunctionAlias, OncePerNodeType);
if (OncePerNodeType)
{
SkipNodeTypes.Add(NiagaraNode->GetSourceNodeClass());
}
}
}
for (const FNiagaraCompilationPin* Pin : FunctionAliasContext.StaticSwitchValues)
{
FunctionAlias += TEXT("_") + FNiagaraHlslTranslator::GetSanitizedFunctionNameSuffix(Pin->PinName.ToString())
+ TEXT("_") + FNiagaraHlslTranslator::GetSanitizedFunctionNameSuffix(Pin->DefaultValue);
}
return FunctionAlias;
}
bool FNiagaraCompilationGraph::HasParametersOfType(const FNiagaraTypeDefinition& Type) const
{
for (int32 InputNodeIndex : InputNodeIndices)
{
const FNiagaraCompilationNodeInput& InputNode = Nodes[InputNodeIndex]->AsTypeRef<FNiagaraCompilationNodeInput>();
if (InputNode.InputVariable.GetType() == Type)
{
return true;
}
}
for (int32 OutputNodeIndex : OutputNodeIndices)
{
const FNiagaraCompilationNodeOutput& OutputNode = Nodes[OutputNodeIndex]->AsTypeRef<FNiagaraCompilationNodeOutput>();
for (const FNiagaraVariable& OutputVariable : OutputNode.Outputs)
{
if (OutputVariable.GetType() == Type)
{
return true;
}
}
}
return false;
}
const FNiagaraScriptVariableData* FNiagaraCompilationGraph::GetScriptVariableData(const FNiagaraVariableBase& Variable) const
{
return ScriptVariableData.FindByPredicate([&Variable](const FNiagaraScriptVariableData& VariableData) -> bool
{
return Variable.IsEquivalent(VariableData.Variable, false /*bAllowAssignableTypes*/);
});
}
TOptional<ENiagaraDefaultMode> FNiagaraCompilationGraph::GetDefaultMode(const FNiagaraVariableBase& Variable, FNiagaraScriptVariableBinding& Binding) const
{
if (const FNiagaraScriptVariableData* ScriptVariable = GetScriptVariableData(Variable))
{
Binding = ScriptVariable->DefaultBinding;
return ScriptVariable->DefaultMode;
}
return TOptional<ENiagaraDefaultMode>();
}
TOptional<FNiagaraVariableMetaData> FNiagaraCompilationGraph::GetMetaData(const FNiagaraVariableBase& Variable) const
{
if (const FNiagaraScriptVariableData* ScriptVariable = GetScriptVariableData(Variable))
{
return ScriptVariable->Metadata;
}
return TOptional<FNiagaraVariableMetaData>();
}
TArray<const FNiagaraCompilationNode*> FNiagaraCompilationGraph::FindOutputNodesByUsage(TConstArrayView<ENiagaraScriptUsage> Usages) const
{
TArray<const FNiagaraCompilationNode*> OutputNodesByUsage;
for (int32 OutputNodeIndex : OutputNodeIndices)
{
const FNiagaraCompilationNodeOutput& OutputNode = Nodes[OutputNodeIndex]->AsTypeRef<FNiagaraCompilationNodeOutput>();
if (Usages.ContainsByPredicate([&OutputNode](ENiagaraScriptUsage CompileUsage) { return UNiagaraScript::IsEquivalentUsage(CompileUsage, OutputNode.Usage); }))
{
OutputNodesByUsage.Add(&OutputNode);
}
}
return OutputNodesByUsage;
}
TSharedPtr<FNiagaraCompilationGraphInstanced, ESPMode::ThreadSafe> FNiagaraCompilationGraphDigested::InstantiateSubGraph(
const TArray<ENiagaraScriptUsage>& Usages,
const FNiagaraCompilationCopyData* CopyCompilationData,
const FNiagaraCompilationBranchMap& Branches,
TArray<FNiagaraCompilationNodeFunctionCall*>& PendingInstantiations) const
{
using namespace NiagaraCompilationImpl;
TSharedPtr<FNiagaraCompilationGraphInstanced, ESPMode::ThreadSafe> SubGraph = MakeShared<FNiagaraCompilationGraphInstanced, ESPMode::ThreadSafe>();
SubGraph->InstantiationSourceGraph = AsShared().ToSharedPtr();
TArray<const FNiagaraCompilationNode*> OutputNodesByUsage = FindOutputNodesByUsage(Usages);
const int32 InstantiatedOutputNodeCount = OutputNodesByUsage.Num();
TArray<const FNiagaraCompilationNode*> SortedNodes = TopologicalSort(CollectConnectedNodes(OutputNodesByUsage, Nodes.Num(), Branches), Branches);
FNiagaraCompilationGraphDuplicateContext DuplicateNodeContext(*SubGraph, *this, Usages, CopyCompilationData, Branches);
const int32 SubGraphNodeCount = SortedNodes.Num();
SubGraph->Nodes.Reserve(SubGraphNodeCount);
SubGraph->OutputNodeIndices.Reserve(InstantiatedOutputNodeCount);
SubGraph->InputNodeIndices.Reserve(InputNodeIndices.Num());
for (const FNiagaraCompilationNode* SourceNode : SortedNodes)
{
TUniquePtr<FNiagaraCompilationNode> TargetNode = DuplicateNode(SourceNode, DuplicateNodeContext);
DuplicateNodeContext.DuplicatedNodeMap.Add(SourceNode, TargetNode.Get());
SubGraph->Nodes.Emplace(MoveTemp(TargetNode));
}
// copy over all the properties for the subgraph (this will contain data that could have been culled based on the
// output nodes that we're filtering by above
SubGraph->VariableBinding = VariableBinding;
SubGraph->ScriptVariableData = ScriptVariableData;
SubGraph->SourceScriptName = SourceScriptName;
SubGraph->SourceScriptFullName = SourceScriptFullName;
SubGraph->bContainsStaticVariables = bContainsStaticVariables;
while (!DuplicateNodeContext.FunctionsRequiresGraph.IsEmpty())
{
PendingInstantiations.Add(DuplicateNodeContext.FunctionsRequiresGraph.Pop());
}
// the StaticSwitchInputs will be missing the results from static switch nodes if they are being
// bypassed in the compilation copy. So we just copy the missing variables from the source graph
for (const FNiagaraVariableBase& SwitchInput : StaticSwitchInputs)
{
SubGraph->StaticSwitchInputs.AddUnique(SwitchInput);
}
return SubGraph;
}
void FNiagaraCompilationGraphInstanced::ValidateRefinement() const
{
// validate that now that we've refined the graph we have no more generic numerics and also ensure
// that there are no more static switches connected
for (const TUniquePtr<FNiagaraCompilationNode>& Node : Nodes)
{
switch (Node->NodeType)
{
case FNiagaraCompilationNode::ENodeType::StaticSwitch:
{
// only switches handled by the compiler are allowed to make it to this stage
const bool bSetByCompiler = Node->AsType<FNiagaraCompilationNodeStaticSwitch>()->bSetByCompiler;
if (!bSetByCompiler)
{
UE_LOG(LogNiagaraEditor, Warning, TEXT("Encountered StaticSwitch that should have been resolved. Graph will likely fail to compile. Task: %s Node %s"),
*SourceScriptFullName, *Node->NodeName);
}
}
break;
// it's possible that we could eliminate these during instantiation as well, but currently
// the information used is stored on the translator (because we can use the same
// instantiated graphs for different usages)
//case FNiagaraCompilationNode::ENodeType::UsageSelector:
//case FNiagaraCompilationNode::ENodeType::SimTargetSelector:
// check(false);
//break;
}
for (const FNiagaraCompilationInputPin& InputPin : Node->InputPins)
{
ensureMsgf(InputPin.Variable.GetType() != FNiagaraTypeDefinition::GetGenericNumericDef() || !InputPin.LinkedTo,
TEXT("Failed during ValidateRefinement for compilation task - %s. Connected InputPin[%s.%s] is still a generic."),
*SourceScriptFullName, *InputPin.OwningNode->NodeName, *InputPin.PinName.ToString());
}
for (const FNiagaraCompilationOutputPin& OutputPin : Node->OutputPins)
{
ensureMsgf(OutputPin.Variable.GetType() != FNiagaraTypeDefinition::GetGenericNumericDef() || OutputPin.LinkedTo.IsEmpty(),
TEXT("Failed during ValidateRefinement for compilation task - %s. Connected OutputPin[%s.%s] is still a generic."),
*SourceScriptFullName, *OutputPin.OwningNode->NodeName, *OutputPin.PinName.ToString());
}
}
}
void FNiagaraCompilationGraphInstanced::Refine(FNiagaraCompilationGraphInstanceContext& InstantiationContext, const FNiagaraCompilationNodeFunctionCall* CallingNode)
{
if (CallingNode)
{
PatchGenericNumericsFromCaller(InstantiationContext);
}
ResolveNumerics(InstantiationContext);
if (CallingNode)
{
// go through the nodes and apply any changes necessary based on the CallingNode
for (TUniquePtr<FNiagaraCompilationNode>& CompilationNode : Nodes)
{
if (FNiagaraCompilationNodeFunctionCall* FunctionCallNode = CompilationNode->AsType<FNiagaraCompilationNodeFunctionCall>())
{
InheritDebugState(InstantiationContext, *FunctionCallNode);
PropagateDefaultValues(InstantiationContext, *FunctionCallNode);
}
}
}
StripUnconnectedPins(InstantiationContext);
// validate that now that we've refined the graph we have no more generic numerics and also ensure
// that there are no more static switches connected
ValidateRefinement();
}
void FNiagaraCompilationGraphInstanced::PatchGenericNumericsFromCaller(FNiagaraCompilationGraphInstanceContext& Context)
{
static const FNiagaraTypeDefinition& GenericTypeDef = FNiagaraTypeDefinition::GetGenericNumericDef();
const FNiagaraCompilationNodeFunctionCall* CallingNode = Context.GetCurrentFunctionNode();
for (int32 InputNodeIndex : InputNodeIndices)
{
FNiagaraCompilationNodeInput& InputNode = Nodes[InputNodeIndex]->AsTypeRef<FNiagaraCompilationNodeInput>();
if (InputNode.InputVariable.GetType() == GenericTypeDef)
{
const FNiagaraCompilationInputPin* CallerInputPin = CallingNode->InputPins.FindByPredicate([&InputNode](const FNiagaraCompilationInputPin& InputPin) -> bool
{
return InputPin.PinName == InputNode.InputVariable.GetName();
});
if (CallerInputPin)
{
InputNode.InputVariable.SetType(CallerInputPin->Variable.GetType());
InputNode.OutputPins[0].Variable.SetType(CallerInputPin->Variable.GetType());
}
}
}
for (int32 OutputNodeIndex : OutputNodeIndices)
{
FNiagaraCompilationNodeOutput& OutputNode = Nodes[OutputNodeIndex]->AsTypeRef<FNiagaraCompilationNodeOutput>();
const int32 InputPinCount = OutputNode.InputPins.Num();
for (int32 InputPinIt = 0; InputPinIt < InputPinCount; ++InputPinIt)
{
FNiagaraVariable& OutputVariable = OutputNode.Outputs[InputPinIt];
if (OutputVariable.GetType() == GenericTypeDef)
{
const FNiagaraCompilationOutputPin* CallerOutputPin = CallingNode->OutputPins.FindByPredicate([&OutputVariable](const FNiagaraCompilationOutputPin& OutputPin) -> bool
{
return OutputPin.PinName == OutputVariable.GetName();
});
if (CallerOutputPin)
{
OutputVariable.SetType(CallerOutputPin->Variable.GetType());
OutputNode.InputPins[InputPinIt].Variable.SetType(CallerOutputPin->Variable.GetType());
}
}
}
}
}
void FNiagaraCompilationGraphInstanced::ResolveNumerics(FNiagaraCompilationGraphInstanceContext& Context)
{
const FNiagaraTypeDefinition& GenericTypeDef = FNiagaraTypeDefinition::GetGenericNumericDef();
const FNiagaraTypeDefinition& PlaceholderTypeDef = FNiagaraTypeDefinition::GetFloatDef();
// if we're forcing generics to be converted we apply that to the inputs first
if (Context.bForceNumericResolution)
{
for (int32 InputNodeIndex : InputNodeIndices)
{
FNiagaraCompilationNodeInput& InputNode = Nodes[InputNodeIndex]->AsTypeRef<FNiagaraCompilationNodeInput>();
for (FNiagaraCompilationOutputPin& OutputPin : InputNode.OutputPins)
{
if (OutputPin.Variable.GetType() == GenericTypeDef)
{
OutputPin.Variable.SetType(PlaceholderTypeDef);
}
}
if (InputNode.InputVariable.GetType() == GenericTypeDef)
{
InputNode.InputVariable.SetType(PlaceholderTypeDef);
}
}
}
// process the graph
for (TUniquePtr<FNiagaraCompilationNode>& Node : Nodes)
{
Node->ResolveNumerics();
}
// if we're forcing generics to be converted we finish off by making sure the outputs are converted
if (Context.bForceNumericResolution)
{
for (int32 OutputNodeIndex : OutputNodeIndices)
{
FNiagaraCompilationNodeOutput& OutputNode = Nodes[OutputNodeIndex]->AsTypeRef<FNiagaraCompilationNodeOutput>();
const int32 InputPinCount = OutputNode.InputPins.Num();
for (int32 InputPinIt = 0; InputPinIt < InputPinCount; ++InputPinIt)
{
if ((OutputNode.InputPins[InputPinIt].Variable.GetType() != GenericTypeDef)
&& (OutputNode.Outputs[InputPinIt].GetType() == GenericTypeDef))
{
OutputNode.Outputs[InputPinIt].SetType(PlaceholderTypeDef);
}
}
}
}
}
void FNiagaraCompilationGraphInstanced::StripUnconnectedPins(FNiagaraCompilationGraphInstanceContext& Context)
{
// through instantiation some pins may become disconnected (for example because of static switches being evaluated). For some nodes,
// like MapGet, this can be problematic because the culled paths may lead to some pins being in a weird state.
// If the output of a MapGet is unconnected, then it's default input pin can also be disconnected.
TArray<FNiagaraCompilationNodeParameterMapGet*, TInlineAllocator<32>> MapGetNodes;
for (TUniquePtr<FNiagaraCompilationNode>& Node : Nodes)
{
if (FNiagaraCompilationNodeParameterMapGet* MapGetNode = Node->AsType<FNiagaraCompilationNodeParameterMapGet>())
{
MapGetNodes.Add(MapGetNode);
}
}
bool bVisitMapGetNodes = !MapGetNodes.IsEmpty();
while (bVisitMapGetNodes)
{
bVisitMapGetNodes = false;
for (FNiagaraCompilationNodeParameterMapGet* MapGetNode : MapGetNodes)
{
const int32 OutputPinCount = MapGetNode->OutputPins.Num();
for (int32 OutputPinIt = 0; OutputPinIt < OutputPinCount; ++OutputPinIt)
{
const FNiagaraCompilationOutputPin& OutputPin = MapGetNode->OutputPins[OutputPinIt];
if (OutputPin.LinkedTo.IsEmpty())
{
if (MapGetNode->DefaultInputPinIndices.IsValidIndex(OutputPinIt))
{
const int32 InputPinIndex = MapGetNode->DefaultInputPinIndices[OutputPinIt];
if (MapGetNode->InputPins.IsValidIndex(InputPinIndex))
{
// if we do change something in the graph then revisit our map nodes to see if there
// may be more connections that can be removed
if (MapGetNode->InputPins[InputPinIndex].Disconnect())
{
bVisitMapGetNodes = true;
}
}
}
}
}
}
}
}
void FNiagaraCompilationGraphInstanced::InheritDebugState(FNiagaraCompilationGraphInstanceContext& Context, FNiagaraCompilationNodeFunctionCall& FunctionCallNode)
{
FunctionCallNode.DebugState = FunctionCallNode.bInheritDebugState ? Context.ConstantResolver.GetDebugState() : FunctionCallNode.DebugState;
}
void FNiagaraCompilationGraphInstanced::PropagateDefaultValues(FNiagaraCompilationGraphInstanceContext& Context, FNiagaraCompilationNodeFunctionCall& FunctionCallNode)
{
using namespace NiagaraCompilationImpl;
const FNiagaraCompilationNodeFunctionCall* CallingNode = Context.GetCurrentFunctionNode();
for (const FNiagaraCompilationNodeFunctionCall::FTaggedVariable& PropagatedVariable : FunctionCallNode.PropagatedStaticSwitchParameters)
{
const FName FunctionPinName = PropagatedVariable.Key.GetName();
const FName CallerPinName = PropagatedVariable.Value;
FNiagaraCompilationInputPin* FunctionInputPin = FindPinByName<FNiagaraCompilationInputPin>(FunctionCallNode.InputPins, FunctionPinName);
if (FunctionInputPin)
{
// reset the default value, to be replaced by the calling function's input, if one is found
FunctionInputPin->DefaultValue = FString();
if (const FNiagaraCompilationInputPin* CallerInputPin = FindPinByName<const FNiagaraCompilationInputPin>(CallingNode->InputPins, CallerPinName))
{
FunctionInputPin->DefaultValue = CallerInputPin->DefaultValue;
}
}
}
}
TSharedPtr<FNiagaraCompilationGraphInstanced, ESPMode::ThreadSafe> FNiagaraCompilationGraphDigested::Instantiate(const FNiagaraPrecompileData* PrecompileData, const FNiagaraCompilationCopyData* CopyCompilationData, const TArray<ENiagaraScriptUsage>& Usages, const FNiagaraFixedConstantResolver& ConstantResolver) const
{
// in order to manage the traversal state context and to keep a lit on the potential for crazy recursion we're
// going to track the dependent graphs that require instantiation in this quasi tree structure
struct FChildrenStackEntry
{
FChildrenStackEntry(int32 InParentFunctionIndex)
: ParentFunctionIndex(InParentFunctionIndex)
{
}
const int32 ParentFunctionIndex;
TArray<FNiagaraCompilationNodeFunctionCall*> Functions;
int32 CurrentFunctionIndex = 0;
};
TArray<FChildrenStackEntry> FunctionsToInstantiate;
TSharedPtr<FNiagaraCompilationGraphInstanced, ESPMode::ThreadSafe> InstantiatedGraph = InstantiateSubGraph(Usages, CopyCompilationData, FNiagaraCompilationBranchMap(), FunctionsToInstantiate.Emplace_GetRef(INDEX_NONE).Functions);
// initialize the traversal context with the data that was pulled from the parameter map history done during the
// precompile
FNiagaraCompilationGraphInstanceContext InstantiationContext(ConstantResolver, InstantiatedGraph.Get(), PrecompileData);
if (ensure(InstantiatedGraph))
{
InstantiatedGraph->ResolveNumerics(InstantiationContext);
}
int32 TotalGraphCount = 1;
int32 TotalNodeCount = Nodes.Num();
int32 TotalCulledNodeCount = 0;
int32 CurrentFunctionSetIndex = 0;
while (CurrentFunctionSetIndex != INDEX_NONE)
{
FChildrenStackEntry& CurrentStack = FunctionsToInstantiate[CurrentFunctionSetIndex];
if (CurrentStack.Functions.IsValidIndex(CurrentStack.CurrentFunctionIndex))
{
const int32 NextFunctionSetIndex = FunctionsToInstantiate.Num();
FNiagaraCompilationNodeFunctionCall* FunctionToInstantiate = CurrentStack.Functions[CurrentStack.CurrentFunctionIndex];
FChildrenStackEntry& ChildStack = FunctionsToInstantiate.Emplace_GetRef(CurrentFunctionSetIndex);
InstantiationContext.EnterFunction(FunctionToInstantiate);
FNiagaraCompilationBranchMap Branches;
EvaluateStaticBranches(InstantiationContext, Branches);
FNiagaraCompilationGraphDigested* CalledDigestedGraph = FunctionToInstantiate->CalledGraph->AsDigested();
if (ensure(CalledDigestedGraph))
{
TSharedPtr<FNiagaraCompilationGraphInstanced, ESPMode::ThreadSafe> CalledInstantiatedGraph =
CalledDigestedGraph->InstantiateSubGraph({ FunctionToInstantiate->CalledScriptUsage }, CopyCompilationData, Branches, ChildStack.Functions);
CalledInstantiatedGraph->Refine(InstantiationContext, FunctionToInstantiate);
++TotalGraphCount;
TotalNodeCount += CalledInstantiatedGraph->Nodes.Num();
TotalCulledNodeCount += CalledDigestedGraph->Nodes.Num() - CalledInstantiatedGraph->Nodes.Num();
// now replace the called graph with the instantiated version
FunctionToInstantiate->CalledGraph = CalledInstantiatedGraph;
}
CurrentFunctionSetIndex = NextFunctionSetIndex;
}
else
{
CurrentFunctionSetIndex = CurrentStack.ParentFunctionIndex;
if (CurrentFunctionSetIndex != INDEX_NONE)
{
if (ensure(!InstantiationContext.FunctionStack.IsEmpty()))
{
// get the parent graph from the InstanatiationContext so that we can aggregate any relevant
// information up the chain
const int32 ParentFunctionIndex = InstantiationContext.FunctionStack.Num() - 2;
const int32 ChildFunctionIndex = InstantiationContext.FunctionStack.Num() - 1;
ensure(InstantiationContext.FunctionStack.IsValidIndex(ChildFunctionIndex));
FNiagaraCompilationGraphInstanced* ParentGraph = InstantiationContext.FunctionStack.IsValidIndex(ParentFunctionIndex)
? InstantiationContext.FunctionStack[ParentFunctionIndex]->CalledGraph->AsInstanced()
: InstantiatedGraph.Get();
FNiagaraCompilationGraphInstanced* ChildGraph = InstantiationContext.FunctionStack[ChildFunctionIndex]->CalledGraph->AsInstanced();
if (ensure(ParentGraph && ChildGraph))
{
ParentGraph->AggregateChildGraph(ChildGraph);
}
}
FChildrenStackEntry& ParentEntry = FunctionsToInstantiate[CurrentFunctionSetIndex];
InstantiationContext.LeaveFunction(ParentEntry.Functions[ParentEntry.CurrentFunctionIndex]);
++ParentEntry.CurrentFunctionIndex;
}
}
}
return InstantiatedGraph;
}
void FNiagaraCompilationGraph::EvaluateStaticBranches(FNiagaraCompilationGraphInstanceContext& Context, FNiagaraCompilationBranchMap& Branches) const
{
// build a list of connected nodes in topological order for the current graph
// start at the back (outputs) and work to the front, evaluating each node
// for the following node types we need to evaluate their state for the instantiated graph:
// static switches
// UsageSelectors (explicitly, not all UsageSelectors, because that also includes Select nodes)
// SimTarget
TSet<const FNiagaraCompilationNode*> VisitedNodes;
VisitedNodes.Reserve(Nodes.Num());
const FNiagaraCompilationNodeFunctionCall* CallingNode = Context.GetCurrentFunctionNode();
TArray<const FNiagaraCompilationNode*> NodesToProcess = CallingNode->CalledGraph->GetOutputNodes();
while (!NodesToProcess.IsEmpty())
{
const FNiagaraCompilationNode* NodeToProcess = NodesToProcess.Pop();
TArray<const FNiagaraCompilationInputPin*> ValidInputPins = NodeToProcess->EvaluateBranches(Context, Branches);
for (const FNiagaraCompilationInputPin* InputPin : ValidInputPins)
{
if (InputPin->LinkedTo)
{
bool bAlreadyVisited;
VisitedNodes.Add(InputPin->LinkedTo->OwningNode, &bAlreadyVisited);
if (!bAlreadyVisited)
{
NodesToProcess.Push(InputPin->LinkedTo->OwningNode);
}
}
}
}
}
void FNiagaraCompilationGraph::NodeTraversal(
bool bRecursive,
bool bOrdered,
TFunctionRef<bool(const FNiagaraCompilationNodeOutput&)> RootNodeFilter,
TFunctionRef<bool(const FNiagaraCompilationNode&)> NodeOperation) const
{
using namespace NiagaraCompilationImpl;
for (int32 OutputNodeIndex : OutputNodeIndices)
{
const FNiagaraCompilationNodeOutput* OutputNode = Nodes[OutputNodeIndex]->AsType<FNiagaraCompilationNodeOutput>();
if (RootNodeFilter(*OutputNode))
{
FNiagaraCompilationBranchMap BranchMap;
TArray<const FNiagaraCompilationNode*> NodesToProcess = CollectConnectedNodes( { OutputNode }, Nodes.Num(), BranchMap );
if (bOrdered)
{
NodesToProcess = TopologicalSort(NodesToProcess, BranchMap);
}
for (const FNiagaraCompilationNode* NodeToProcess : NodesToProcess)
{
if (!NodeOperation(*NodeToProcess))
{
break;
}
if (bRecursive)
{
if (const FNiagaraCompilationNodeFunctionCall* FunctionCallNode = NodeToProcess->AsType<FNiagaraCompilationNodeFunctionCall>())
{
if (const FNiagaraCompilationGraph* ChildGraph = FunctionCallNode->CalledGraph.Get())
{
auto ChildGraphRootNodeFilter = [FunctionCallNode](const FNiagaraCompilationNodeOutput& OutputNode) -> bool
{
return (OutputNode.Usage == FunctionCallNode->CalledScriptUsage);
};
ChildGraph->NodeTraversal(bRecursive, bOrdered, ChildGraphRootNodeFilter, NodeOperation);
}
}
}
}
}
}
}
void FNiagaraCompilationGraphInstanced::AggregateChildGraph(const FNiagaraCompilationGraphInstanced* ChildGraph)
{
if (ChildGraph->bContainsStaticVariables)
{
bContainsStaticVariables = true;
}
}
void FNiagaraCompilationGraph::CollectReachableNodes(const FNiagaraCompilationNodeOutput* OutputNode, TArray<const FNiagaraCompilationNode*>& ReachableNodes) const
{
ReachableNodes = NiagaraCompilationImpl::CollectConnectedNodes( {OutputNode}, Nodes.Num(), FNiagaraCompilationBranchMap() );
}
void FNiagaraCompilationGraph::BuildTraversal(const FNiagaraCompilationNode* RootNode, TArray<const FNiagaraCompilationNode*>& OrderedNodes) const
{
BuildTraversal(RootNode, FNiagaraCompilationBranchMap(), OrderedNodes);
}
void FNiagaraCompilationGraph::BuildTraversal(const FNiagaraCompilationNode* RootNode, const FNiagaraCompilationBranchMap& Branches, TArray<const FNiagaraCompilationNode*>& OrderedNodes) const
{
using namespace NiagaraCompilationImpl;
OrderedNodes = TopologicalSort(CollectConnectedNodes({ RootNode }, Nodes.Num(), Branches), Branches);
}
void FNiagaraCompilationGraphDigested::AddReferencedObjects(FReferenceCollector& Collector)
{
Collector.AddReferencedObjects(CachedDataInterfaceDuplicates);
Collector.AddReferencedObjects(CachedDataInterfaceCDODuplicates);
Collector.AddReferencedObjects(CachedNamedObjectAssets);
}
FString FNiagaraCompilationGraphDigested::GetReferencerName() const
{
return TEXT("FNiagaraCompilationGraphDigested");
}
TArray<const FNiagaraCompilationNode*> FNiagaraCompilationGraph::GetOutputNodes() const
{
TArray<const FNiagaraCompilationNode*> OutputNodes;
OutputNodes.Reserve(OutputNodeIndices.Num());
Algo::Transform(OutputNodeIndices, OutputNodes, [this](int32 OutputNodeIndex) -> const FNiagaraCompilationNode*
{
return Nodes[OutputNodeIndex].Get();
});
return OutputNodes;
}
FNiagaraCompilationPin::FNiagaraCompilationPin(const UEdGraphPin* InPin)
: Direction(InPin->Direction)
{
PinName = InPin->PinName;
DefaultValue = InPin->DefaultValue;
PersistentGuid = InPin->PersistentGuid;
PinType = InPin->PinType;
UniquePinId = InPin->PinId;
bHidden = InPin->bHidden;
}
FNiagaraCompilationPin::FNiagaraCompilationPin(const FNiagaraCompilationPin& SourcePin, const FNiagaraCompilationNode* InOwningNode, FNiagaraCompilationGraphDuplicateContext& Context)
: Variable(SourcePin.Variable)
, SourcePinIndex(SourcePin.SourcePinIndex)
, PinName(SourcePin.PinName)
, DefaultValue(SourcePin.DefaultValue)
, PersistentGuid(SourcePin.PersistentGuid)
, PinType(SourcePin.PinType)
, UniquePinId(SourcePin.UniquePinId)
, bHidden(SourcePin.bHidden)
, OwningNode(InOwningNode)
, Direction(SourcePin.Direction)
{
}
UEdGraphPin* FNiagaraCompilationPin::GetSourcePin() const
{
return OwningNode->SourceNode->Pins[SourcePinIndex];
}
bool FNiagaraCompilationPin::IsWildcard() const
{
return PinType.PinCategory == UEdGraphSchema_Niagara::PinCategoryType && PinType.PinSubCategoryObject == FNiagaraTypeDefinition::GetWildcardStruct();
}
FNiagaraCompilationInputPin::FNiagaraCompilationInputPin(const UEdGraphPin* InPin)
: FNiagaraCompilationPin(InPin)
{
check(InPin->Direction == EEdGraphPinDirection::EGPD_Input);
bDefaultValueIsIgnored = InPin->bDefaultValueIsIgnored;
bool bForceValue = false;
if (!bDefaultValueIsIgnored)
{
if ((InPin->PinType.PinCategory == UEdGraphSchema_Niagara::PinCategoryType)
|| (InPin->PinType.PinCategory == UEdGraphSchema_Niagara::PinCategoryStaticType)
|| (InPin->PinType.PinCategory == UEdGraphSchema_Niagara::PinCategoryEnum)
|| (InPin->PinType.PinCategory == UEdGraphSchema_Niagara::PinCategoryStaticEnum))
{
bForceValue = InPin->LinkedTo.IsEmpty();
}
}
FNameBuilder PinNameBuilder(PinName);
const ENiagaraStructConversion StructConversion = PinNameBuilder.ToView().StartsWith(PARAM_MAP_USER_STR)
? ENiagaraStructConversion::UserFacing
: ENiagaraStructConversion::Simulation;
Variable = UEdGraphSchema_Niagara::PinToNiagaraVariable(InPin, bForceValue, StructConversion);
}
FNiagaraCompilationInputPin::FNiagaraCompilationInputPin(const FNiagaraCompilationInputPin& SourceInputPin, const FNiagaraCompilationNode* InOwningNode, FNiagaraCompilationGraphDuplicateContext& Context)
: FNiagaraCompilationPin(SourceInputPin, InOwningNode, Context)
, LinkedTo(nullptr)
, bDefaultValueIsIgnored(SourceInputPin.bDefaultValueIsIgnored)
{
const FNiagaraCompilationInputPin* TracedInputPin = SourceInputPin.TraceBranchMap(Context.Branches);
if (const FNiagaraCompilationOutputPin* OutputPin = TracedInputPin->LinkedTo)
{
const FNiagaraCompilationNode* SourceNode = OutputPin->OwningNode;
if (ensure(SourceNode))
{
FNiagaraCompilationNode* ConnectedNode = Context.DuplicatedNodeMap.FindRef(SourceNode);
if (ensure(ConnectedNode))
{
// find the pin index in the array
const int32 OutputPinIndex = UE_PTRDIFF_TO_INT32(OutputPin - SourceNode->OutputPins.GetData());
ensure(SourceNode->OutputPins.IsValidIndex(OutputPinIndex));
LinkedTo = &ConnectedNode->OutputPins[OutputPinIndex];
ConnectedNode->OutputPins[OutputPinIndex].LinkedTo.Add(this);
}
}
}
// if we're not connected to anything we need to also check to see if that's because our link has
// been terminated by a static branch. If so we may need to update our default value based on the contents
// of the static switch
else if (TracedInputPin != &SourceInputPin)
{
const FNiagaraTypeDefinition& GenericNumericDef = FNiagaraTypeDefinition::GetGenericNumericDef();
if (Variable.GetType() == GenericNumericDef)
{
const FNiagaraTypeDefinition& SourceTypeDef = TracedInputPin->Variable.GetType();
if (SourceTypeDef != GenericNumericDef)
{
PinType = TracedInputPin->PinType;
Variable.SetType(SourceTypeDef);
}
}
if (ensure(FNiagaraTypeDefinition::TypesAreAssignable(Variable.GetType(), TracedInputPin->Variable.GetType())))
{
if (TracedInputPin->Variable.IsDataAllocated())
{
Variable.AllocateData();
Variable.SetData(TracedInputPin->Variable.GetData());
}
}
}
}
const FNiagaraCompilationInputPin* FNiagaraCompilationInputPin::TraceBranchMap(const FNiagaraCompilationBranchMap& BranchMap) const
{
const FNiagaraCompilationInputPin* CurrentInputPin = this;
while (CurrentInputPin->LinkedTo)
{
if (const FNiagaraCompilationInputPin* NextInputPin = BranchMap.FindRef(CurrentInputPin->LinkedTo))
{
CurrentInputPin = NextInputPin;
}
else
{
break;
}
}
return CurrentInputPin;
}
bool FNiagaraCompilationInputPin::Disconnect()
{
if (LinkedTo)
{
FNiagaraCompilationOutputPin* MutableOutputPin = const_cast<FNiagaraCompilationOutputPin*>(LinkedTo);
LinkedTo = nullptr;
return MutableOutputPin->LinkedTo.RemoveSingle(this) > 0;
}
return false;
}
FNiagaraCompilationOutputPin::FNiagaraCompilationOutputPin(const UEdGraphPin* InPin)
: FNiagaraCompilationPin(InPin)
{
check(InPin->Direction == EEdGraphPinDirection::EGPD_Output);
FNameBuilder PinNameBuilder(InPin->PinName);
const ENiagaraStructConversion StructConversion = PinNameBuilder.ToView().StartsWith(PARAM_MAP_USER_STR)
? ENiagaraStructConversion::UserFacing
: ENiagaraStructConversion::Simulation;
Variable = UEdGraphSchema_Niagara::PinToNiagaraVariable(InPin, false, StructConversion);
}
FNiagaraCompilationOutputPin::FNiagaraCompilationOutputPin(const FNiagaraCompilationOutputPin& SourceOutputPin, const FNiagaraCompilationNode* InOwningNode, FNiagaraCompilationGraphDuplicateContext& Context)
: FNiagaraCompilationPin(SourceOutputPin, InOwningNode, Context)
{
// OutputPins are updated for duplication by the input pins, but we can reserve space for the pins
LinkedTo.Reserve(SourceOutputPin.LinkedTo.Num());
}
FNiagaraCompilationNode::FNiagaraCompilationNode(ENodeType InNodeType, const UEdGraphNode* InNode, FNiagaraCompilationGraphCreateContext& Context)
: NodeType(InNodeType)
{
auto IsAddPin = [](const UEdGraphPin* Pin) -> bool
{
return Pin->PinType.PinCategory == UEdGraphSchema_Niagara::PinCategoryMisc &&
Pin->PinType.PinSubCategory == UNiagaraNodeWithDynamicPins::AddPinSubCategory;
};
if (InNode)
{
NodeName = InNode->GetName();
FullName = InNode->GetFullName();
FullTitle = InNode->GetNodeTitle(ENodeTitleType::FullTitle).ToString();
NodeGuid = InNode->NodeGuid;
NodeEnabled = InNode->IsNodeEnabled();
SourceNode = InNode;
OwningGraph = &Context.ParentGraph;
const UEdGraph* SourceOwningGraph = Context.ParentGraph.SourceGraph.Get();
const int32 PinCount = SourceNode->Pins.Num();
for (int32 PinIt = 0; PinIt < PinCount; ++PinIt)
{
const UEdGraphPin* Pin = SourceNode->Pins[PinIt];
// skip add & orphaned pins
if (IsAddPin(Pin) || Pin->bOrphanedPin)
{
continue;
}
if (Pin->bOrphanedPin)
{
// todo error reporting
UE_LOG(LogNiagaraEditor, Warning, TEXT("Node pin is no longer valid. This pin must be disconnected or reset to default so it can be removed."));
continue;
}
// in the case of the pin being connected to a node that is not in the graph's Node array we just skip the pin
bool bConnectedToInvalidNode = false;
for (const UEdGraphPin* LinkedPin : Pin->LinkedTo)
{
if (!SourceOwningGraph->Nodes.Contains(LinkedPin->GetOwningNode()))
{
bConnectedToInvalidNode = true;
break;
}
}
if (bConnectedToInvalidNode)
{
continue;
}
FNiagaraCompilationPin* CompilationPin = nullptr;
if (Pin->Direction == EEdGraphPinDirection::EGPD_Input)
{
CompilationPin = &InputPins.Emplace_GetRef(Pin);
}
else
{
CompilationPin = &OutputPins.Emplace_GetRef(Pin);
}
CompilationPin->SourcePinIndex = PinIt;
CompilationPin->OwningNode = this;
}
if (const UNiagaraNode* NiagaraNode = Cast<const UNiagaraNode>(InNode))
{
NumericSelectionMode = NiagaraNode->GetNumericOutputTypeSelectionMode();
}
else
{
NumericSelectionMode = ENiagaraNumericOutputTypeSelectionMode::None;
}
}
}
FNiagaraCompilationNode::FNiagaraCompilationNode(const FNiagaraCompilationNode& InNode, FNiagaraCompilationGraphDuplicateContext& Context)
: NodeType(InNode.NodeType)
, NodeName(InNode.NodeName)
, FullName(InNode.FullName)
, FullTitle(InNode.FullTitle)
, NodeGuid(InNode.NodeGuid)
, NodeEnabled(InNode.NodeEnabled)
, NumericSelectionMode(InNode.NumericSelectionMode)
, OwningGraph(&Context.TargetGraph)
, SourceNode(InNode.SourceNode)
{
InputPins.Reserve(InNode.InputPins.Num());
for (const FNiagaraCompilationInputPin& SourceInputPin : InNode.InputPins)
{
InputPins.Emplace(SourceInputPin, this, Context);
}
OutputPins.Reserve(InNode.OutputPins.Num());
for (const FNiagaraCompilationOutputPin& SourceOutputPin : InNode.OutputPins)
{
OutputPins.Emplace(SourceOutputPin, this, Context);
}
}
void FNiagaraCompilationNode::BuildParameterMapHistory(FParameterMapHistoryBuilder& Builder, bool bRecursive, bool bFilterForCompilation) const
{
if (bRecursive)
{
Builder.VisitInputPins(this, bFilterForCompilation);
}
}
void FNiagaraCompilationNode::Compile(FTranslator* Translator, TArray<int32>& Outputs) const
{
Translator->Error(FText::FromString("Unimplemented Node!"), this, nullptr);
}
void FNiagaraCompilationNode::ResolveNumericPins(TConstArrayView<int32> InputPinIndices, TConstArrayView<int32> OutputPinIndices)
{
for (int32 InputPinIndex : InputPinIndices)
{
FNiagaraCompilationInputPin& InputPin = InputPins[InputPinIndex];
if (InputPin.PinType.PinCategory == UEdGraphSchema_Niagara::PinCategoryType ||
InputPin.PinType.PinCategory == UEdGraphSchema_Niagara::PinCategoryStaticType)
{
// If the input pin is the generic numeric type set it to the type of the linked output pin which should have been processed already.
if (InputPin.Variable.GetType() == FNiagaraTypeDefinition::GetGenericNumericDef() && InputPin.LinkedTo)
{
const FNiagaraCompilationOutputPin* LinkedOutputPin = InputPin.LinkedTo;
const FNiagaraTypeDefinition& LinkedOutputPinType = LinkedOutputPin->Variable.GetType();
if (LinkedOutputPinType.IsValid())
{
// Only update the input pin type if the linked pin type is valid.
InputPin.PinType = LinkedOutputPin->PinType;
InputPin.Variable.SetType(LinkedOutputPinType);
}
}
}
}
if (NumericSelectionMode == ENiagaraNumericOutputTypeSelectionMode::None)
{
return;
}
const FNiagaraTypeDefinition& GenericTypeDef = FNiagaraTypeDefinition::GetGenericNumericDef();
TArray<FNiagaraCompilationOutputPin*, TInlineAllocator<16>> PinsToResolve;
for (int32 OutputPinIndex : OutputPinIndices)
{
if (OutputPins[OutputPinIndex].Variable.GetType() == GenericTypeDef)
{
PinsToResolve.Add(&OutputPins[OutputPinIndex]);
}
}
if (!PinsToResolve.IsEmpty())
{
TArray<FNiagaraTypeDefinition, TInlineAllocator<16>> ConcreteInputTypes;
for (int32 InputPinIndex : InputPinIndices)
{
if (InputPins[InputPinIndex].Variable.GetType() != GenericTypeDef)
{
ConcreteInputTypes.Add(InputPins[InputPinIndex].Variable.GetType());
}
}
if (!ConcreteInputTypes.IsEmpty())
{
FNiagaraTypeDefinition ResolvedType = GenericTypeDef;
if (NumericSelectionMode == ENiagaraNumericOutputTypeSelectionMode::Custom)
{
ResolvedType = ResolveCustomNumericType(ConcreteInputTypes);
}
else
{
ResolvedType = FNiagaraTypeDefinition::GetNumericOutputType(ConcreteInputTypes, NumericSelectionMode);
}
if (ResolvedType != GenericTypeDef)
{
for (FNiagaraCompilationOutputPin* PinToResolve : PinsToResolve)
{
PinToResolve->Variable.SetType(ResolvedType);
}
}
}
}
}
void FNiagaraCompilationNode::ResolveNumerics()
{
TArray<int32, TInlineAllocator<16>> InputPinIndices;
TArray<int32, TInlineAllocator<16>> OutputPinIndices;
const int32 InputPinCount = InputPins.Num();
const int32 OutputPinCount = OutputPins.Num();
InputPinIndices.Reserve(InputPinCount);
OutputPinIndices.Reserve(OutputPinCount);
for (int32 InputPinIt = 0; InputPinIt < InputPinCount; ++InputPinIt)
{
InputPinIndices.Add(InputPinIt);
}
for (int32 OutputPinIt = 0; OutputPinIt < OutputPinCount; ++OutputPinIt)
{
OutputPinIndices.Add(OutputPinIt);
}
ResolveNumericPins(InputPinIndices, OutputPinIndices);
}
FNiagaraTypeDefinition FNiagaraCompilationNode::ResolveCustomNumericType(TConstArrayView<FNiagaraTypeDefinition> ConcreteInputTypes) const
{
checkf(false, TEXT("Not implemented for node type"));
return FNiagaraTypeDefinition::GetFloatDef();
}
bool FNiagaraCompilationNode::CompileInputPins(FTranslator* Translator, TArray<int32>& OutInputResults) const
{
bool bError = false;
OutInputResults.Reserve(InputPins.Num());
for (const FNiagaraCompilationInputPin& InputPin : InputPins)
{
int32 Result = Translator->CompileInputPin(&InputPin);
if (Result == INDEX_NONE)
{
bError = true;
Translator->Error(FText::Format(LOCTEXT("CompileInputPinErrorFormat", "Error compiling Pin"), FText::FromName(InputPin.PinName)), this, &InputPin);
}
OutInputResults.Add(Result);
}
return bError;
}
bool FNiagaraCompilationNode::ConditionalRouteParameterMapAroundMe(FParameterMapHistoryBuilder& Builder) const
{
if (!NodeEnabled && Builder.GetIgnoreDisabled())
{
RouteParameterMapAroundMe(Builder);
return true;
}
return false;
}
void FNiagaraCompilationNode::RouteParameterMapAroundMe(FParameterMapHistoryBuilder& Builder) const
{
const FNiagaraTypeDefinition ParameterMapDef = FNiagaraTypeDefinition::GetParameterMapDef();
auto FindParameterMapDef = [&ParameterMapDef](const FNiagaraCompilationPin& Pin) -> bool
{
return Pin.Variable.GetType() == ParameterMapDef;
};
const FNiagaraCompilationInputPin* InputPin = InputPins.FindByPredicate(FindParameterMapDef);
const FNiagaraCompilationOutputPin* OutputPin = OutputPins.FindByPredicate(FindParameterMapDef);
if (InputPin && OutputPin && InputPin->LinkedTo)
{
const int32 PMIdx = Builder.TraceParameterMapOutputPin(InputPin->LinkedTo);
Builder.RegisterParameterMapPin(PMIdx, OutputPin);
}
}
void FNiagaraCompilationNode::RegisterPassthroughPin(FParameterMapHistoryBuilder& Builder, const FNiagaraCompilationInputPin* InputPin, const FNiagaraCompilationOutputPin* OutputPin, bool bFilterForCompilation, bool bVisitInputPin) const
{
if (bVisitInputPin)
{
Builder.VisitInputPin(InputPin, bFilterForCompilation);
}
FNiagaraTypeDefinition InDef = InputPin->Variable.GetType();
FNiagaraTypeDefinition OutDef = OutputPin->Variable.GetType();
if (InDef == FNiagaraTypeDefinition::GetParameterMapDef() && OutDef == FNiagaraTypeDefinition::GetParameterMapDef() && InputPin->LinkedTo)
{
int32 PMIdx = Builder.TraceParameterMapOutputPin(InputPin->LinkedTo);
Builder.RegisterParameterMapPin(PMIdx, OutputPin);
}
else if (InDef.IsStatic() && InputPin->LinkedTo)
{
int32 ConstantIdx = Builder.GetConstantFromOutputPin(InputPin->LinkedTo);
Builder.RegisterConstantPin(ConstantIdx, InputPin);
if (OutDef == InDef)
{
Builder.RegisterConstantPin(ConstantIdx, OutputPin);
}
}
else if (InDef.IsStatic() && !InputPin->LinkedTo)
{
FString CachedDefaultValue;
if (!Builder.TraversalStateContext->GetFunctionDefaultValue(NodeGuid, InputPin->PinName, CachedDefaultValue))
{
CachedDefaultValue = InputPin->DefaultValue;
}
int32 ConstantIdx = Builder.AddOrGetConstantFromValue(CachedDefaultValue);
Builder.RegisterConstantPin(ConstantIdx, InputPin);
if (OutDef == InDef)
{
Builder.RegisterConstantPin(ConstantIdx, OutputPin);
}
}
}
FString FNiagaraCompilationNode::GetTypeName() const
{
check(false);
return TEXT("<Unknown>");
}
int32 FNiagaraCompilationNode::GetInputPinIndexById(const FGuid& InId) const
{
return NiagaraCompilationImpl::GetPinIndexById<FNiagaraCompilationInputPin>(InputPins, InId);
}
int32 FNiagaraCompilationNode::GetInputPinIndexByPersistentId(const FGuid& InId) const
{
return NiagaraCompilationImpl::GetPinIndexByPersistentId<FNiagaraCompilationInputPin>(InputPins, InId);
}
int32 FNiagaraCompilationNode::GetOutputPinIndexById(const FGuid& InId) const
{
return NiagaraCompilationImpl::GetPinIndexById<FNiagaraCompilationOutputPin>(OutputPins, InId);
}
const FNiagaraCompilationInputPin* FNiagaraCompilationNode::GetInputExecPin() const
{
for (const FNiagaraCompilationInputPin& InputPin : InputPins)
{
if (InputPin.Variable.GetType() == FNiagaraTypeDefinition::GetParameterMapDef())
{
return &InputPin;
}
}
return nullptr;
}
const FNiagaraCompilationOutputPin* FNiagaraCompilationNode::GetOutputExecPin() const
{
for (const FNiagaraCompilationOutputPin& OutputPin : OutputPins)
{
if (OutputPin.Variable.GetType() == FNiagaraTypeDefinition::GetParameterMapDef())
{
return &OutputPin;
}
}
return nullptr;
}
TArray<const FNiagaraCompilationInputPin*> FNiagaraCompilationNode::EvaluateBranches(FNiagaraCompilationGraphInstanceContext& Context, FNiagaraCompilationBranchMap& Branches) const
{
TArray<const FNiagaraCompilationInputPin*> ValidPins;
ValidPins.Reserve(InputPins.Num());
Algo::Transform(InputPins, ValidPins, [this](const FNiagaraCompilationInputPin& InputPin) -> const FNiagaraCompilationInputPin*
{
return &InputPin;
});
return ValidPins;
}
FNiagaraCompilationNodeAssignment::FNiagaraCompilationNodeAssignment(const UNiagaraNodeAssignment* InNode, FNiagaraCompilationGraphCreateContext& Context)
: FNiagaraCompilationNodeFunctionCall(InNode, Context, ENodeType::Assignment)
{
}
FNiagaraCompilationNodeAssignment::FNiagaraCompilationNodeAssignment(const FNiagaraCompilationNodeAssignment& InNode, FNiagaraCompilationGraphDuplicateContext& Context)
: FNiagaraCompilationNodeFunctionCall(InNode, Context)
{
}
FNiagaraCompilationNodeEmitter::FNiagaraCompilationNodeEmitter(const UNiagaraNodeEmitter* InNode, FNiagaraCompilationGraphCreateContext& Context)
: FNiagaraCompilationNode(ENodeType::Emitter, InNode, Context)
{
EmitterID = InNode->GetEmitterID();
EmitterHandleID = InNode->GetEmitterHandleId();
EmitterUniqueName = InNode->GetEmitterUniqueName();
if (const UNiagaraGraph* DependentGraph = InNode->GetCalledGraph())
{
CalledGraph = FNiagaraDigestDatabase::Get().CreateGraphDigest(DependentGraph, Context.ChangeIdBuilder);
if (const FNiagaraCompilationGraphDigested* DigestedGraph = CalledGraph->AsDigested())
{
Context.DigestedChildGraphs.AddUnique(DigestedGraph);
}
}
Usage = InNode->GetUsage();
EmitterName = InNode->GetName();
EmitterPathName = InNode->GetPathName();
EmitterHandleIdString = EmitterHandleID.ToString(EGuidFormats::Digits);
}
FNiagaraCompilationNodeEmitter::FNiagaraCompilationNodeEmitter(const FNiagaraCompilationNodeEmitter& InNode, FNiagaraCompilationGraphDuplicateContext& Context)
: FNiagaraCompilationNode(InNode, Context)
, EmitterID(InNode.EmitterID)
, EmitterHandleID(InNode.EmitterHandleID)
, EmitterUniqueName(InNode.EmitterUniqueName)
, EmitterName(InNode.EmitterName)
, EmitterPathName(InNode.EmitterPathName)
, EmitterHandleIdString(InNode.EmitterHandleIdString)
, Usage(InNode.Usage)
{
// we need to replace the CalledGraph here with the graph that has been instantiated already
const FNiagaraCompilationCopyData::FSharedCompilationCopy* EmitterCopy = Context.CopyCompilationData->EmitterData.FindByPredicate([this](const FNiagaraCompilationCopyData::FSharedCompilationCopy& EmitterCopy) -> bool
{
return EmitterCopy->EmitterUniqueName == EmitterUniqueName;
});
// because toggling the enabled state of an emitter doesn't necessarily change the emitter node in the graph
// and so the EmitterCopy may be null in this case. If that's true, we'll mark the node as being disabled.
if (EmitterCopy)
{
CalledGraph = (*EmitterCopy)->InstantiatedGraph;
}
else
{
NodeEnabled = false;
}
}
void FNiagaraCompilationNodeEmitter::BuildParameterMapHistory(FParameterMapHistoryBuilder& Builder, bool bRecursive, bool bFilterForCompilation) const
{
FNiagaraCompilationNode::BuildParameterMapHistory(Builder, bRecursive, bFilterForCompilation);
if (ConditionalRouteParameterMapAroundMe(Builder))
{
return;
}
if (Builder.ExclusiveEmitterHandle.IsSet() && Builder.ExclusiveEmitterHandle != EmitterHandleID)
{
RouteParameterMapAroundMe(Builder);
return;
}
const FNiagaraFixedConstantResolver* ChildConstantResolver = Builder.ConstantResolver->FindChildResolver(EmitterHandleID);
if (!ChildConstantResolver)
{
// if no child resolver was found for the specified emitter, that means that the emitter is likely not enabled and so we can proceed without
// processing it
RouteParameterMapAroundMe(Builder);
return;
}
int32 ParamMapIdx = INDEX_NONE;
if (InputPins[0].LinkedTo)
{
if (bRecursive)
{
ParamMapIdx = Builder.TraceParameterMapOutputPin(NiagaraCompilationImpl::TraceOutputPin(Builder, InputPins[0].LinkedTo, true));
}
else
{
ParamMapIdx = Builder.CreateParameterMap();
}
}
if (CalledGraph && ParamMapIdx != INDEX_NONE && Builder.bShouldBuildSubHistories)
{
Builder.TraversalStateContext->PushEmitter(this);
Builder.EnterEmitter(EmitterUniqueName, CalledGraph.Get(), this);
const TArray<ENiagaraScriptUsage> Usages =
{
ENiagaraScriptUsage::EmitterSpawnScript,
ENiagaraScriptUsage::EmitterUpdateScript,
ENiagaraScriptUsage::ParticleSpawnScript,
ENiagaraScriptUsage::ParticleSpawnScriptInterpolated,
ENiagaraScriptUsage::ParticleUpdateScript,
ENiagaraScriptUsage::ParticleEventScript,
ENiagaraScriptUsage::ParticleSimulationStageScript
};
uint32 NodeIdx = Builder.BeginNodeVisitation(ParamMapIdx, this);
for (ENiagaraScriptUsage OutputNodeUsage : Usages)
{
TArray<const FNiagaraCompilationNodeOutput*> OutputNodes;
CalledGraph->FindOutputNodes(OutputNodeUsage, OutputNodes);
// Build up a new parameter map history with all the child graph nodes..
FParameterMapHistoryBuilder ChildBuilder;
*ChildBuilder.ConstantResolver = *ChildConstantResolver;
ChildBuilder.RegisterEncounterableVariables(Builder.GetEncounterableVariables());
ChildBuilder.EnableScriptAllowList(true, Usage);
TArray<FNiagaraVariable> LocalStaticVars;
FNiagaraParameterUtilities::FilterToRelevantStaticVariables(Builder.StaticVariables, LocalStaticVars, *EmitterUniqueName, TEXT("Emitter"), true);
ChildBuilder.RegisterExternalStaticVariables(LocalStaticVars);
ChildBuilder.AvailableCollections->EditCollections() = Builder.AvailableCollections->ReadCollections();
FString LocalEmitterName = TEXT("Emitter");
ChildBuilder.EnterEmitter(LocalEmitterName, CalledGraph.Get(), this);
for (const FNiagaraCompilationNodeOutput* OutputNode : OutputNodes)
{
ChildBuilder.BuildParameterMaps(OutputNode, true);
}
ChildBuilder.ExitEmitter(LocalEmitterName, this);
FNiagaraAliasContext ResolveAliasesContext(OutputNodeUsage);
ResolveAliasesContext.ChangeEmitterToEmitterName(EmitterUniqueName);
for (FParameterMapHistory& History : ChildBuilder.Histories)
{
Builder.Histories[ParamMapIdx].MapPinHistory.Append(History.MapPinHistory);
for (int32 SrcVarIdx = 0; SrcVarIdx < History.Variables.Num(); SrcVarIdx++)
{
FNiagaraVariable& Var = History.Variables[SrcVarIdx];
Var = FNiagaraUtilities::ResolveAliases(Var, ResolveAliasesContext);
int32 ExistingIdx = Builder.Histories[ParamMapIdx].FindVariable(Var.GetName(), Var.GetType());
if (ExistingIdx == INDEX_NONE)
{
ExistingIdx = Builder.AddVariableToHistory(Builder.Histories[ParamMapIdx], Var, History.VariablesWithOriginalAliasesIntact[SrcVarIdx], nullptr);
}
ensure(ExistingIdx < Builder.Histories[ParamMapIdx].PerVariableWarnings.Num());
ensure(ExistingIdx < Builder.Histories[ParamMapIdx].PerVariableReadHistory.Num());
ensure(ExistingIdx < Builder.Histories[ParamMapIdx].PerVariableWriteHistory.Num());
Builder.Histories[ParamMapIdx].PerVariableReadHistory[ExistingIdx].Append(History.PerVariableReadHistory[SrcVarIdx]);
Builder.Histories[ParamMapIdx].PerVariableWriteHistory[ExistingIdx].Append(History.PerVariableWriteHistory[SrcVarIdx]);
Builder.Histories[ParamMapIdx].PerVariableWarnings[ExistingIdx].Append(History.PerVariableWarnings[SrcVarIdx]);
for (int32 PerConstantIdx = 0; PerConstantIdx < History.PerVariableConstantValue[SrcVarIdx].Num(); PerConstantIdx++)
{
const FString& ConstantStr = History.PerVariableConstantValue[SrcVarIdx][PerConstantIdx];
Builder.Histories[ParamMapIdx].PerVariableConstantValue[ExistingIdx].AddUnique(ConstantStr);
}
}
Builder.Histories[ParamMapIdx].EncounteredParameterCollections.Append(History.EncounteredParameterCollections);
Builder.Histories[ParamMapIdx].PinToConstantValues.Append(History.PinToConstantValues);
}
ResolveAliasesContext.ChangeRapidIterationParameterMode(FNiagaraAliasContext::ERapidIterationParameterMode::StaticVariables);
// We only want to push out appropriately scoped static variables that should be in the system builder, not in-betweens like "Module.MyInputVar"
// or per-particle or others vars. Really only want Emitter or System parameters here.
for (int32 StaticVarIdx = 0; StaticVarIdx < ChildBuilder.StaticVariables.Num(); StaticVarIdx++)
{
const FNiagaraVariable& ChildStaticVar = ChildBuilder.StaticVariables[StaticVarIdx];
if (ChildBuilder.StaticVariableExportable[StaticVarIdx])
{
// Should match logic in FNiagaraParameterMapHistoryBuilder::RegisterConstantVariableWrite
FNiagaraVariable ResolvedStaticVar = FNiagaraUtilities::ResolveAliases(ChildStaticVar, ResolveAliasesContext);
// Index of uses == operator, which only checks name and type. This will allow us to detect instances of the duplicate
// data down the line.
int32 FoundStaticVarIdx = Builder.StaticVariables.Find(ResolvedStaticVar);
if (FoundStaticVarIdx == INDEX_NONE) // Didn't find it, so add it.
{
Builder.StaticVariables.Add(ResolvedStaticVar);
Builder.StaticVariableExportable.Emplace(true);
}
else if (false == Builder.StaticVariables[FoundStaticVarIdx].HoldsSameData(ResolvedStaticVar))
{
Builder.StaticVariables.Add(ResolvedStaticVar);// Add as a duplicate here. We will filter out later
Builder.StaticVariableExportable.Emplace(true);
}
ensure(Builder.StaticVariables.Num() == Builder.StaticVariableExportable.Num());
}
}
}
Builder.EndNodeVisitation(ParamMapIdx, NodeIdx);
Builder.ExitEmitter(EmitterUniqueName, this);
Builder.TraversalStateContext->PopEmitter(this);
}
for (const FNiagaraCompilationOutputPin& Pin : OutputPins)
{
if (Pin.Variable.GetType() == FNiagaraTypeDefinition::GetParameterMapDef())
{
Builder.RegisterParameterMapPin(ParamMapIdx, &Pin);
}
}
}
void FNiagaraCompilationNodeEmitter::Compile(FTranslator* Translator, TArray<int32>& Outputs) const
{
check(Outputs.Num() == 0);
// First compile fully down the hierarchy for our predecessors..
TArray<const FNiagaraCompilationNodeInput*> InputNodes;
UNiagaraGraph::FFindInputNodeOptions Options;
Options.bSort = true;
Options.bFilterDuplicates = true;
Options.bFilterByScriptUsage = true;
Options.TargetScriptUsage = Translator->GetTargetUsage() == ENiagaraScriptUsage::SystemSpawnScript ? ENiagaraScriptUsage::EmitterSpawnScript : ENiagaraScriptUsage::EmitterUpdateScript;
if (CalledGraph && NodeEnabled) // Called graph may be null on an disabled emitter
{
CalledGraph->FindInputNodes(InputNodes, Options);
}
TArray<int32> CompileInputs;
if (InputPins.Num() > 1)
{
Translator->Error(LOCTEXT("TooManyOutputPinsError", "Too many input pins on node."), this, nullptr);
return;
}
int32 InputPinCompiled = Translator->CompileInputPin(&InputPins[0]);
if (!NodeEnabled)
{
// Do the minimal amount of work necessary if we are disabled.
CompileInputs.Reserve(1);
CompileInputs.Add(InputPinCompiled);
Translator->Emitter(this, CompileInputs, Outputs);
return;
}
if (InputNodes.Num() <= 0)
{
Translator->Error(LOCTEXT("InputNodesNotFound", "Input nodes on called graph not found"), this, nullptr);
return;
}
CompileInputs.Reserve(InputNodes.Num());
bool bError = false;
const FNiagaraVariable InputMapVariable = FNiagaraVariable(FNiagaraTypeDefinition::GetParameterMapDef(), TEXT("InputMap"));
for (const FNiagaraCompilationNodeInput* EmitterInputNode : InputNodes)
{
if (EmitterInputNode->InputVariable.IsEquivalent(InputMapVariable))
{
CompileInputs.Add(InputPinCompiled);
}
else
{
CompileInputs.Add(INDEX_NONE);
}
}
if (!bError)
{
Translator->Emitter(this, CompileInputs, Outputs);
}
}
FNiagaraCompilationNodeFunctionCall::FNiagaraCompilationNodeFunctionCall(const UNiagaraNodeFunctionCall* InNode, FNiagaraCompilationGraphCreateContext& Context, ENodeType InNodeType)
: FNiagaraCompilationNode(InNodeType, InNode, Context)
, CalledGraph(nullptr)
{
if (const UNiagaraGraph* DependentGraph = InNode->GetCalledGraph())
{
CalledGraph = FNiagaraDigestDatabase::Get().CreateGraphDigest(DependentGraph, Context.ChangeIdBuilder);
if (const FNiagaraCompilationGraphDigested* DigestedGraph = CalledGraph->AsDigested())
{
Context.DigestedChildGraphs.AddUnique(DigestedGraph);
}
}
// Top level functions (functions invoked in the root graph) will use the serialized DebugState value, all others will
// use the value cached during traversal based on the bInheritDebugState flag and the system's current value. With
// NoDebug being used when things are not inherited. This seems like a bug and top level functions should probably also
// be using NoDebug in the case of !bInheritDebugStatus
DebugState = InNode->DebugState;
FunctionName = InNode->GetFunctionName();
// translating the hlsl uses the FunctionScriptName as the function name. In the original implementation
// this results in function names being NiagaraScript_34_Func_(), which isn't great. This attempts to
// preserve the actual function name but we need to be able to disambiguate between potential conflicts.
// For now this just includes the version of the graph...outside of that things can get a bit dicey (do we
// really want to be able to support having the same name used places?)
if (UNiagaraScript* FunctionScript = InNode->FunctionScript)
{
FunctionScriptName = FunctionScript->GetName();
if (InNode->SelectedScriptVersion.IsValid() && FunctionScript->IsVersioningEnabled())
{
if (const FNiagaraAssetVersion* ScriptVersion = FunctionScript->FindVersionData(InNode->SelectedScriptVersion))
{
FunctionScriptName.Appendf(TEXT("_v%d_%d"), ScriptVersion->MajorVersion, ScriptVersion->MinorVersion);
}
}
}
Signature = InNode->Signature;
bInheritDebugState = InNode->bInheritDebugStatus;
PropagatedStaticSwitchParameters.Reserve(InNode->PropagatedStaticSwitchParameters.Num());
for (const FNiagaraPropagatedVariable& PropagatedVariable : InNode->PropagatedStaticSwitchParameters)
{
const FName PropagatedName = PropagatedVariable.PropagatedName.IsEmpty()
? PropagatedVariable.SwitchParameter.GetName()
: *PropagatedVariable.PropagatedName;
const FTaggedVariable& TaggedVariable = PropagatedStaticSwitchParameters.Emplace_GetRef(
PropagatedVariable.SwitchParameter, PropagatedName);
Context.ParentGraph.StaticSwitchInputs.AddUnique(FNiagaraVariable(TaggedVariable.Key.GetType(), TaggedVariable.Value));
}
CalledScriptUsage = InNode->GetCalledUsage();
if (!CalledGraph && Signature.IsValid())
{
Signature.FunctionSpecifiers = InNode->FunctionSpecifiers;
}
bValidateDataInterfaces = InNode->GetValidateDataInterfaces();
}
FNiagaraCompilationNodeFunctionCall::FNiagaraCompilationNodeFunctionCall(const FNiagaraCompilationNodeFunctionCall& InNode, FNiagaraCompilationGraphDuplicateContext& Context)
: FNiagaraCompilationNode(InNode, Context)
, CalledGraph(InNode.CalledGraph)
, DebugState(InNode.DebugState)
, FunctionName(InNode.FunctionName)
, FunctionScriptName(InNode.FunctionScriptName)
, Signature(InNode.Signature)
, CalledScriptUsage(InNode.CalledScriptUsage)
, PropagatedStaticSwitchParameters(InNode.PropagatedStaticSwitchParameters)
, bInheritDebugState(InNode.bInheritDebugState)
, bValidateDataInterfaces(InNode.bValidateDataInterfaces)
{
if (CalledGraph)
{
Context.FunctionsRequiresGraph.Add(this);
}
for (const FTaggedVariable& PropagatedVariable : InNode.PropagatedStaticSwitchParameters)
{
Context.TargetGraph.StaticSwitchInputs.AddUnique(FNiagaraVariable(PropagatedVariable.Key.GetType(), PropagatedVariable.Value));
}
}
void FNiagaraCompilationNodeFunctionCall::BuildParameterMapHistory(FParameterMapHistoryBuilder& Builder, bool bRecursive, bool bFilterForCompilation) const
{
// 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 = InputPins.IndexOfByPredicate([](const FNiagaraCompilationInputPin& InputPin) -> bool
{
return InputPin.Variable.GetType() == FNiagaraTypeDefinition::GetParameterMapDef();
});
const bool bHasParamMapPin = ParamMapPinIndex != INDEX_NONE;
const FNiagaraCompilationInputPin* ParamMapPin = bHasParamMapPin ? &InputPins[ParamMapPinIndex] : nullptr;
if (bHasParamMapPin && !ParamMapPin->LinkedTo)
{
// Looks like this function call is not yet hooked up. Skip it to prevent cascading errors in the compilation
return;
}
FNiagaraCompilationNode::BuildParameterMapHistory(Builder, bRecursive, bFilterForCompilation);
if (ConditionalRouteParameterMapAroundMe(Builder))
{
return;
}
const bool ValidScriptAndGraph = CalledGraph.IsValid();
if (!ValidScriptAndGraph)
{
if (!Signature.IsValid() || Signature.bRequiresExecPin)
{
RouteParameterMapAroundMe(Builder);
}
return;
}
const FNiagaraCompilationNodeOutput* OutputNode = CalledGraph->FindOutputNode(ENiagaraScriptUsage::Function, FGuid());
if (OutputNode == nullptr)
{
OutputNode = CalledGraph->FindOutputNode(ENiagaraScriptUsage::Module, FGuid());
}
if (OutputNode == nullptr)
{
OutputNode = CalledGraph->FindOutputNode(ENiagaraScriptUsage::DynamicInput, FGuid());
}
// 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 (const FNiagaraCompilationInputPin& InputPin : InputPins)
{
// Make sure to register pin constants
if (!InputPin.LinkedTo && InputPin.Variable.GetType().IsStatic() && InputPin.DefaultValue.Len() != 0)
{
FString CachedPinDefaultValue;
if (!Builder.TraversalStateContext->GetFunctionDefaultValue(NodeGuid, InputPin.PinName, CachedPinDefaultValue))
{
CachedPinDefaultValue = InputPin.DefaultValue;
}
Builder.RegisterConstantFromInputPin(&InputPin, CachedPinDefaultValue);
}
}
ENiagaraFunctionDebugState CachedDebugState;
if (!Builder.TraversalStateContext->GetFunctionDebugState(NodeGuid, CachedDebugState))
{
CachedDebugState = DebugState;
}
FNiagaraFixedConstantResolver FunctionResolver = Builder.ConstantResolver->WithDebugState(CachedDebugState);
if (Builder.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(Builder.GetCurrentUsageContext());
}
int32 ParamMapIdx = INDEX_NONE;
uint32 NodeIdx = INDEX_NONE;
if (bHasParamMapPin && bRecursive)
{
ParamMapIdx = Builder.TraceParameterMapOutputPin(ParamMapPin->LinkedTo);
}
Builder.TraversalStateContext->PushFunction(this, FunctionResolver);
Builder.EnterFunction(FunctionName, CalledGraph.Get(), this);
if (ParamMapIdx != INDEX_NONE)
{
NodeIdx = Builder.BeginNodeVisitation(ParamMapIdx, this);
}
// check if we should be recursing deeper into the graph
const bool DoDepthTraversal = (Builder.MaxGraphDepthTraversal == INDEX_NONE || Builder.CurrentGraphDepth < Builder.MaxGraphDepthTraversal);
if (DoDepthTraversal)
{
++Builder.CurrentGraphDepth;
const bool bChildRecursive = true;
OutputNode->BuildParameterMapHistory(Builder, bChildRecursive, bFilterForCompilation);
--Builder.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...
TArray<TPair<const FNiagaraCompilationOutputPin*, int32>, TInlineAllocator<16> > MatchedPairs;
TArray<TPair<const FNiagaraCompilationOutputPin*, int32>, TInlineAllocator<16> > MatchedConstants;
TArray<bool, TInlineAllocator<16> > OutputMatched;
const int32 OutputPinCount = OutputPins.Num();
OutputMatched.AddDefaulted(OutputPinCount);
// Find the matches of names and types of the sub-graph output pins and this function call nodes' outputs.
for (const FNiagaraCompilationInputPin& ChildOutputNodeInputPin : OutputNode->InputPins)
{
const FNiagaraVariable& VarChild = ChildOutputNodeInputPin.Variable;
if (ChildOutputNodeInputPin.LinkedTo && VarChild.GetType() == FNiagaraTypeDefinition::GetParameterMapDef())
{
for (int32 OutputPinIt = 0; OutputPinIt < OutputPinCount; ++OutputPinIt)
{
const FNiagaraCompilationOutputPin& OutputPin = OutputPins[OutputPinIt];
if (OutputPin.Variable.IsEquivalent(VarChild))
{
MatchedPairs.Emplace(&OutputPin, Builder.TraceParameterMapOutputPin(ChildOutputNodeInputPin.LinkedTo));
OutputMatched[OutputPinIt] = true;
}
}
}
else if (VarChild.GetType().IsStatic())
{
for (int32 OutputPinIt = 0; OutputPinIt < OutputPinCount; ++OutputPinIt)
{
const FNiagaraCompilationOutputPin& OutputPin = OutputPins[OutputPinIt];
if (!OutputMatched[OutputPinIt] && OutputPin.Variable.IsEquivalent(VarChild))
{
MatchedConstants.Emplace(&OutputPin, Builder.GetConstantFromInputPin(&ChildOutputNodeInputPin));
OutputMatched[OutputPinIt] = true;
}
}
}
}
if (ParamMapIdx != INDEX_NONE)
{
Builder.EndNodeVisitation(ParamMapIdx, NodeIdx);
}
Builder.ExitFunction(this);
Builder.TraversalStateContext->PopFunction(this);
if (DoDepthTraversal)
{
for (int32 i = 0; i < MatchedPairs.Num(); i++)
{
Builder.RegisterParameterMapPin(MatchedPairs[i].Value, MatchedPairs[i].Key);
}
for (int32 i = 0; i < MatchedConstants.Num(); i++)
{
Builder.RegisterConstantPin(MatchedConstants[i].Value, MatchedConstants[i].Key);
}
}
else
{
for (const FNiagaraCompilationOutputPin& OutputPin : OutputPins)
{
if (OutputPin.Variable.GetType() == FNiagaraTypeDefinition::GetParameterMapDef())
{
Builder.RegisterParameterMapPin(ParamMapIdx, &OutputPin);
break;
}
}
}
}
TSet<FName> FNiagaraCompilationNodeFunctionCall::GetUnusedFunctionInputPins() const
{
if (!CalledGraph
|| CalledScriptUsage != ENiagaraScriptUsage::Module
|| InputPins.IsEmpty()
|| InputPins[0].LinkedTo == nullptr)
{
return TSet<FName>();
}
// Find the start node for the traversal
const FNiagaraCompilationNodeOutput* OutputNode = CalledGraph->FindOutputNode(ENiagaraScriptUsage::Module, FGuid());
if (OutputNode == nullptr)
{
return TSet<FName>();
}
TStringBuilder<512> FunctionNameWithDelimiter;
FunctionNameWithDelimiter.Append(FunctionName);
FunctionNameWithDelimiter.Append(TEXT("."));
const TCHAR* ModuleWithDelimiter = TEXT("Module.");
// Get the used function parameters from the parameter map set node linked to the function's input pin.
// Note that this is only valid for module scripts, not function scripts.
TArray<const FNiagaraCompilationInputPin*> ResultPins;
const FNiagaraCompilationInputPin& ParameterMapInputPin = InputPins[0];
if (ParameterMapInputPin.LinkedTo->OwningNode->NodeType == ENodeType::ParameterMapSet)
{
for (const FNiagaraCompilationInputPin& ParamMapNodeInputPin : ParameterMapInputPin.LinkedTo->OwningNode->InputPins)
{
FNameBuilder PinNameBuilder(ParamMapNodeInputPin.PinName);
if (PinNameBuilder.ToView().StartsWith(FunctionNameWithDelimiter))
{
ResultPins.Add(&ParamMapNodeInputPin);
}
}
}
if (ResultPins.Num() == 0)
{
return TSet<FName>();
}
// Find reachable nodes
TArray<const FNiagaraCompilationNode*> ReachableNodes;
CalledGraph->CollectReachableNodes(OutputNode, ReachableNodes);
for (const FNiagaraCompilationNode* ReachableNode : ReachableNodes)
{
if (ReachableNode->NodeType == ENodeType::ParameterMapGet)
{
for (const FNiagaraCompilationOutputPin& ParamMapNodeOutputPin : ReachableNode->OutputPins)
{
if (ParamMapNodeOutputPin.LinkedTo.Num() == 0)
{
continue;
}
FNameBuilder PinNameBuilder(ParamMapNodeOutputPin.PinName);
if (!PinNameBuilder.ToView().StartsWith(ModuleWithDelimiter))
{
continue;
}
TStringBuilder<512> ResolvedNamespacePinName;
ResolvedNamespacePinName.Append(FunctionNameWithDelimiter);
ResolvedNamespacePinName.Append(PinNameBuilder.ToView().RightChop(FCString::Strlen(ModuleWithDelimiter)));
const FNiagaraVariable VariableToFind(ParamMapNodeOutputPin.Variable.GetType(), FName(ResolvedNamespacePinName));
ResultPins.SetNum(Algo::RemoveIf(ResultPins, [&VariableToFind](const FNiagaraCompilationInputPin* ResultPin) -> bool
{
return ResultPin->Variable == VariableToFind;
}));
/*
for (TArray<const FNiagaraCompilationInputPin*>::TIterator It(ResultPins); It; ++It)
{
const FNiagaraCompilationInputPin* ResultPin = *It;
if (ResultPin->PinType == ParamMapNodeOutputPin.PinType
&& ResultPin->PinName == FName(ResolvedNamespacePinName))
{
It.RemoveCurrentSwap();
}
}
*/
}
}
}
TSet<FName> UnusedPinNames;
UnusedPinNames.Reserve(ResultPins.Num());
for (const FNiagaraCompilationInputPin* InputPin : ResultPins)
{
UnusedPinNames.Add(InputPin->PinName);
}
return UnusedPinNames;
}
void FNiagaraCompilationNodeFunctionCall::Compile(FTranslator* Translator, TArray<int32>& Outputs) const
{
TArray<int32> Inputs;
bool bError = false;
if (CalledGraph)
{
TArray<const FNiagaraCompilationNodeInput*> FunctionInputNodes;
UNiagaraGraph::FFindInputNodeOptions Options;
Options.bSort = true;
Options.bFilterDuplicates = true;
CalledGraph->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<FName> HiddenPinNames = GetUnusedFunctionInputPins();
Translator->EnterFunctionCallNode(HiddenPinNames);
for (const FNiagaraCompilationNodeInput* FunctionInputNode : FunctionInputNodes)
{
const FNiagaraTypeDefinition& InputNodeType = FunctionInputNode->InputVariable.GetType();
TOptional<FNiagaraTypeDefinition> SWCType;
if (FNiagaraTypeHelper::IsLWCType(InputNodeType))
{
SWCType = FNiagaraTypeHelper::GetSWCType(InputNodeType);
}
//Finds the matching Pin in the caller.
auto MatchInputNodePredicate = [&FunctionInputNode, &SWCType](const FNiagaraCompilationInputPin& InputPin) -> bool
{
if (InputPin.Variable.GetName() != FunctionInputNode->InputVariable.GetName())
{
return false;
}
if (InputPin.Variable.IsEquivalent(FunctionInputNode->InputVariable))
{
return true;
}
// the last thing we need to worry about is when types differ because of LWC vs SWC concerns
if (SWCType.IsSet())
{
return InputPin.Variable.GetType() == *SWCType;
}
return false;
};
const FNiagaraCompilationInputPin* CallerPin = InputPins.FindByPredicate(MatchInputNodePredicate);
if (!CallerPin)
{
if (FunctionInputNode->bExposed)
{
//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->bRequired)
{
// Not exposed, but required. This means we should just add as a constant.
Inputs.Add(Translator->GetConstant(FunctionInputNode->InputVariable));
continue;
}
Inputs.Add(INDEX_NONE);
continue;
}
const FNiagaraCompilationOutputPin* CallerLinkedTo = CallerPin->LinkedTo;
FNiagaraVariable PinVar = CallerPin->Variable;
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))
{
check(false);
}
}
}
if (CallerLinkedTo)
{
//Param is provided by the caller. Typical case.
Inputs.Add(Translator->CompileOutputPin(CallerLinkedTo));
continue;
}
else
{
if (FunctionInputNode->bRequired && FunctionInputNode->bExposed)
{
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."), FText::FromName(CallerPin->PinName)),
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);
}
}
}
Translator->ExitFunctionCallNode();
}
else if (Signature.IsValid())
{
if (Signature.Inputs.Num() > 0)
{
if (bValidateDataInterfaces && Signature.Inputs[0].GetType().IsDataInterface())
{
UClass* DIClass = Signature.Inputs[0].GetType().GetClass();
if (UNiagaraDataInterface* DataInterfaceCDO = Cast<UNiagaraDataInterface>(DIClass->GetDefaultObject()))
{
TArray<FText> ValidationErrors;
DataInterfaceCDO->ValidateFunction(Signature, ValidationErrors);
bError = ValidationErrors.Num() > 0;
for (FText& ValidationError : ValidationErrors)
{
Translator->Error(ValidationError, this, nullptr);
}
if (bError)
{
return;
}
}
}
}
Translator->EnterFunctionCallNode(TSet<FName>());
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(FunctionName)), this, nullptr);
bError = true;
}
if (!bError)
{
Translator->FunctionCall(this, Inputs, Outputs);
}
}
const FNiagaraCompilationInputPin* FNiagaraCompilationNodeFunctionCall::FindStaticSwitchInputPin(FName VariableName) const
{
if (CalledGraph)
{
for (const FNiagaraVariableBase& StaticSwitchInput : CalledGraph->StaticSwitchInputs)
{
if (StaticSwitchInput.GetName() == VariableName)
{
for (const FNiagaraCompilationInputPin& InputPin : InputPins)
{
if (InputPin.PinName == VariableName)
{
return &InputPin;
}
}
}
}
}
return nullptr;
}
bool FNiagaraCompilationNodeFunctionCall::FindAutoBoundInput(const FNiagaraCompilationNodeInput* InputNode, const FNiagaraCompilationInputPin* PinToAutoBind, FNiagaraVariable& OutFoundVar, ENiagaraInputNodeUsage& OutNodeUsage) const
{
if (!PinToAutoBind || PinToAutoBind->LinkedTo || !(InputNode->bExposed && InputNode->bCanAutoBind))
{
return false;
}
ensureMsgf(InputNode->bExposed == true, TEXT("AutoBind inputs should be exposed for Function(%s) Pin(%s)"), *FunctionName, *PinToAutoBind->PinName.ToString());
FNiagaraVariable PinVar = PinToAutoBind->Variable;
//See if we can auto bind this pin to something in the caller script.
const FNiagaraCompilationGraph* CallerGraph = OwningGraph;
check(CallerGraph);
const FNiagaraCompilationNodeOutput* CallerOutputNodeSpawn = CallerGraph->FindOutputNode(ENiagaraScriptUsage::ParticleSpawnScript, FGuid());
const FNiagaraCompilationNodeOutput* CallerOutputNodeUpdate = CallerGraph->FindOutputNode(ENiagaraScriptUsage::ParticleUpdateScript, FGuid());
//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)
{
const FNiagaraCompilationNodeOutput* CallerOutputNode = CallerOutputNodeSpawn != nullptr ? CallerOutputNodeSpawn : CallerOutputNodeUpdate;
check(CallerOutputNode);
{
const FNiagaraVariable* AttrVarPtr = CallerOutputNode->Outputs.FindByPredicate([&PinVar](const FNiagaraVariable& Attr) -> bool
{
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<FNiagaraVariable>& SysConstants = FNiagaraConstants::GetEngineConstants();
if (SysConstants.Contains(PinVar))
{
OutFoundVar = PinVar;
OutNodeUsage = ENiagaraInputNodeUsage::SystemConstant;
return true;
}
//Unable to auto bind.
return false;
}
const FNiagaraCompilationNodeParameterMapSet* FNiagaraCompilationNodeFunctionCall::GetOverrideNode() const
{
if (const FNiagaraCompilationInputPin* ExecPin = GetInputExecPin())
{
if (const FNiagaraCompilationOutputPin* SourcePin = ExecPin->LinkedTo)
{
return SourcePin->OwningNode->AsType<FNiagaraCompilationNodeParameterMapSet>();
}
}
return nullptr;
}
bool FNiagaraCompilationNodeFunctionCall::HasOverridePin(const FNiagaraParameterHandle& ParameterHandle) const
{
// static switch
if (FindStaticSwitchInputPin(ParameterHandle.GetName()))
{
return true;
}
// override node - find the ParamMapSet preceding this function call
if (const FNiagaraCompilationNodeParameterMapSet* OverrideNode = GetOverrideNode())
{
const FName ParameterHandleName = ParameterHandle.GetParameterHandleString();
return OverrideNode->InputPins.ContainsByPredicate([ParameterHandleName](const FNiagaraCompilationInputPin& InputPin) -> bool
{
return InputPin.PinName == ParameterHandleName;
});
}
return false;
}
void FNiagaraCompilationNodeFunctionCall::MultiFindParameterMapDefaultValues(ENiagaraScriptUsage ParentScriptUsage, const FNiagaraFixedConstantResolver& ConstantResolver, TArrayView<FNiagaraVariable> Variables) const
{
if (!Variables.IsEmpty() && CalledGraph.IsValid())
{
FNiagaraCompilationBranchMap Branches;
FNiagaraCompilationGraphInstanceContext DummyContext(ConstantResolver, nullptr, nullptr);
DummyContext.EnterFunction(this);
CalledGraph->EvaluateStaticBranches(DummyContext, Branches);
TArray<const FNiagaraCompilationNodeOutput*> OutputNodes;
CalledGraph->FindOutputNodes(CalledScriptUsage, OutputNodes);
for (const FNiagaraCompilationNodeOutput* OutputNode : OutputNodes)
{
TArray<const FNiagaraCompilationNode*> ReachableNodes;
CalledGraph->BuildTraversal(OutputNode, Branches, ReachableNodes);
for (const FNiagaraCompilationNode* ReachableNode : ReachableNodes)
{
if (const FNiagaraCompilationNodeParameterMapGet* MapGetNode = ReachableNode->AsType<FNiagaraCompilationNodeParameterMapGet>())
{
for (FNiagaraVariable& Variable : Variables)
{
// only worry about the ones we haven't processed yet
if (!Variable.IsDataAllocated())
{
int32 OutputPinIt = MapGetNode->OutputPins.IndexOfByPredicate([Variable](const FNiagaraCompilationOutputPin& OutputPin) -> bool
{
return OutputPin.PinName == Variable.GetName();
});
if (MapGetNode->DefaultInputPinIndices.IsValidIndex(OutputPinIt))
{
const int32 DefaultPinIt = MapGetNode->DefaultInputPinIndices[OutputPinIt];
if (MapGetNode->InputPins.IsValidIndex(DefaultPinIt))
{
const FNiagaraCompilationInputPin& DefaultPin = MapGetNode->InputPins[DefaultPinIt];
if (!DefaultPin.LinkedTo)
{
Variable.SetData(DefaultPin.Variable.GetData());
}
}
}
}
}
}
}
}
}
}
struct FNiagaraCompilationNodeCustomHlsl::FIndexedTokenLess
{
FIndexedTokenLess(TConstArrayView<FString> InStringsView)
: StringsView(InStringsView)
{}
bool operator()(int32 LhsIndex, int32 RhsIndex) const
{
return StringsView[LhsIndex].Compare(StringsView[RhsIndex]) < 0;
}
TConstArrayView<FString> StringsView;
};
struct FNiagaraCompilationNodeCustomHlsl::FStringViewLess
{
bool operator()(FStringView LhsValue, FStringView RhsValue) const
{
return LhsValue.Compare(RhsValue) < 0;
}
};
FNiagaraCompilationNodeCustomHlsl::FNiagaraCompilationNodeCustomHlsl(const UNiagaraNodeCustomHlsl* InNode, FNiagaraCompilationGraphCreateContext& Context)
: FNiagaraCompilationNodeFunctionCall(InNode, Context, ENodeType::CustomHlsl)
{
CustomScriptUsage = InNode->ScriptUsage;
Signature = InNode->Signature;
CustomHlsl = InNode->GetCustomHlsl();
TArray<FStringView> TokenViews;
InNode->GetTokens(TokenViews, false, false);
const int32 TokenCount = TokenViews.Num();
Tokens.Reserve(TokenCount);
SortedTokenIndices.Reserve(TokenCount);
int32 TokenIt = 0;
for (const FStringView View : TokenViews)
{
Tokens.Push(FString(View));
SortedTokenIndices.Add(TokenIt++);
}
Algo::StableSort(SortedTokenIndices, FIndexedTokenLess(Tokens));
InNode->GetIncludeFilePaths(CustomIncludePaths);
bCallsImpureFunctions = InNode->CallsImpureDataInterfaceFunctions();
}
FNiagaraCompilationNodeCustomHlsl::FNiagaraCompilationNodeCustomHlsl(const FNiagaraCompilationNodeCustomHlsl& InNode, FNiagaraCompilationGraphDuplicateContext& Context)
: FNiagaraCompilationNodeFunctionCall(InNode, Context)
, CustomScriptUsage(InNode.CustomScriptUsage)
, Signature(InNode.Signature)
, CustomHlsl(InNode.CustomHlsl)
, Tokens(InNode.Tokens)
, SortedTokenIndices(InNode.SortedTokenIndices)
, CustomIncludePaths(InNode.CustomIncludePaths)
, bCallsImpureFunctions(InNode.bCallsImpureFunctions)
{
}
void FNiagaraCompilationNodeCustomHlsl::BuildParameterMapHistory(FParameterMapHistoryBuilder& Builder, bool bRecursive, bool bFilterForCompilation) const
{
FNiagaraCompilationNodeFunctionCall::BuildParameterMapHistory(Builder, bRecursive, bFilterForCompilation);
if (ConditionalRouteParameterMapAroundMe(Builder))
{
return;
}
int32 ParamMapIdx = INDEX_NONE;
// This only works currently if the input pins are in the same order as the signature pins.
if (InputPins.Num() == Signature.Inputs.Num() && OutputPins.Num() == Signature.Outputs.Num())
{
TArray<FNiagaraVariable> LocalVars;
bool bHasParamMapInput = false;
bool bHasParamMapOutput = false;
for (int32 i = 0; i < InputPins.Num(); i++)
{
FNiagaraVariable Input = Signature.Inputs[i];
if (Input.GetType() == FNiagaraTypeDefinition::GetParameterMapDef())
{
bHasParamMapInput = true;
if (InputPins[i].LinkedTo)
{
ParamMapIdx = Builder.TraceParameterMapOutputPin(InputPins[i].LinkedTo);
}
}
else
{
LocalVars.Add(Input);
}
}
for (int32 i = 0; i < OutputPins.Num(); i++)
{
FNiagaraVariable Output = Signature.Outputs[i];
if (Output.GetType() == FNiagaraTypeDefinition::GetParameterMapDef())
{
bHasParamMapOutput = true;
Builder.RegisterParameterMapPin(ParamMapIdx, &OutputPins[i]);
}
else
{
LocalVars.Add(Output);
}
}
TArray<FString> PossibleNamespaces;
FNiagaraParameterUtilities::GetValidNamespacesForReading(Builder.GetBaseUsageContext(), 0, PossibleNamespaces);
if ((bHasParamMapOutput || bHasParamMapInput) && ParamMapIdx != INDEX_NONE)
{
for (int32 i = 0; i < Tokens.Num(); i++)
{
bool bFoundLocal = false;
if (INDEX_NONE != FNiagaraVariable::SearchArrayForPartialNameMatch(LocalVars, *Tokens[i]))
{
bFoundLocal = true;
}
if (!bFoundLocal && Tokens[i].Contains(TEXT("."))) // Only check tokens with namespaces in them..
{
for (const FString& ValidNamespace : PossibleNamespaces)
{
// There is one possible path here, one where we're using the namespace as-is from the valid list.
if (Tokens[i].StartsWith(ValidNamespace, ESearchCase::CaseSensitive))
{
Builder.HandleExternalVariableRead(ParamMapIdx, *Tokens[i]);
}
}
}
}
}
}
}
bool FNiagaraCompilationNodeCustomHlsl::TokensContainsName(FName InName) const
{
FNameBuilder NameBuilder(InName);
FStringView NameBuilderView = NameBuilder.ToView();
int32 SearchStartIndex = Algo::LowerBoundBy(SortedTokenIndices, NameBuilderView, [this](int32 TokenIndex) -> FStringView
{
return Tokens[TokenIndex];
}, FStringViewLess());
return Tokens.IsValidIndex(SearchStartIndex) && Tokens[SortedTokenIndices[SearchStartIndex]].StartsWith(NameBuilderView);
}
FNiagaraCompilationNodeIf::FNiagaraCompilationNodeIf(const UNiagaraNodeIf* InNode, FNiagaraCompilationGraphCreateContext& Context)
: FNiagaraCompilationNode(ENodeType::If, InNode, Context)
{
const int32 PinsPerSelection = InNode->PathAssociatedPinGuids.Num();
OutputVariables = InNode->OutputVars;
ConditionalPinIndex = GetInputPinIndexByPersistentId(InNode->ConditionPinGuid);
FalseInputPinIndices.Reserve(PinsPerSelection);
TrueInputPinIndices.Reserve(PinsPerSelection);
for (const FPinGuidsForPath& PinGuids : InNode->PathAssociatedPinGuids)
{
FalseInputPinIndices.Add(GetInputPinIndexByPersistentId(PinGuids.InputFalsePinGuid));
TrueInputPinIndices.Add(GetInputPinIndexByPersistentId(PinGuids.InputTruePinGuid));
}
}
FNiagaraCompilationNodeIf::FNiagaraCompilationNodeIf(const FNiagaraCompilationNodeIf& InNode, FNiagaraCompilationGraphDuplicateContext& Context)
: FNiagaraCompilationNode(InNode, Context)
, OutputVariables(InNode.OutputVariables)
, ConditionalPinIndex(InNode.ConditionalPinIndex)
, FalseInputPinIndices(InNode.FalseInputPinIndices)
, TrueInputPinIndices(InNode.TrueInputPinIndices)
{
}
void FNiagaraCompilationNodeIf::ResolveNumerics()
{
const int32 InputPinCount = InputPins.Num();
const int32 OutputPinCount = OutputPins.Num();
const int32 InputPinOffset = 1; // offset because of the condition pin
for (int32 OutputPinIt = 0; OutputPinIt < OutputPinCount; ++OutputPinIt)
{
ResolveNumericPins({InputPinOffset + OutputPinIt, InputPinOffset + OutputPinIt + OutputPinCount}, {OutputPinIt});
}
}
void FNiagaraCompilationNodeIf::Compile(FTranslator* Translator, TArray<int32>& Outputs) const
{
int32 Condition = Translator->CompileInputPin(&InputPins[ConditionalPinIndex]);
TArray<int32> PathTrue;
PathTrue.Reserve(TrueInputPinIndices.Num());
for (int32 PathTrueIndex : TrueInputPinIndices)
{
PathTrue.Add(Translator->CompileInputPin(&InputPins[PathTrueIndex]));
}
TArray<int32> PathFalse;
PathFalse.Reserve(FalseInputPinIndices.Num());
for (int32 PathFalseIndex : FalseInputPinIndices)
{
PathFalse.Add(Translator->CompileInputPin(&InputPins[PathFalseIndex]));
}
Translator->If(this, OutputVariables, Condition, PathTrue, PathFalse, Outputs);
}
FNiagaraCompilationNodeInput::FNiagaraCompilationNodeInput(const UNiagaraNodeInput* InNode, FNiagaraCompilationGraphCreateContext& Context)
: FNiagaraCompilationNode(ENodeType::Input, InNode, Context)
{
Context.ParentGraph.InputNodeIndices.Add(Context.ParentGraph.Nodes.Num());
InputVariable = InNode->Input;
Usage = InNode->Usage;
CallSortPriority = InNode->CallSortPriority;
if (InputVariable.IsDataInterface())
{
UNiagaraDataInterface* SourceDataInterface = InNode->GetDataInterface();
DataInterfaceName = InputVariable.GetName();
check(SourceDataInterface);
SourceDataInterface->GetEmitterReferencesByName(DataInterfaceEmitterReferences);
DuplicatedDataInterface = Context.ParentGraph.DigestDataInterface(SourceDataInterface);
}
else if (InputVariable.IsUObject())
{
UObject* SourceObjectAsset = InNode->GetObjectAsset();
ObjectAssetName = InputVariable.GetName();
Context.ParentGraph.RegisterObjectAsset(ObjectAssetName, SourceObjectAsset);
ObjectAssetPath = FSoftObjectPath(SourceObjectAsset);
}
bExposed = InNode->IsExposed();
bRequired = InNode->ExposureOptions.bRequired;
bCanAutoBind = InNode->ExposureOptions.bCanAutoBind;
}
FNiagaraCompilationNodeInput::FNiagaraCompilationNodeInput(const FNiagaraCompilationNodeInput& InNode, FNiagaraCompilationGraphDuplicateContext& Context)
: FNiagaraCompilationNode(InNode, Context)
, InputVariable(InNode.InputVariable)
, Usage(InNode.Usage)
, DataInterfaceEmitterReferences(InNode.DataInterfaceEmitterReferences)
, CallSortPriority(InNode.CallSortPriority)
, DataInterfaceName(InNode.DataInterfaceName)
, ObjectAssetName(InNode.ObjectAssetName)
, bRequired(InNode.bRequired)
, bExposed(InNode.bExposed)
, bCanAutoBind(InNode.bCanAutoBind)
, DuplicatedDataInterface(InNode.DuplicatedDataInterface)
, ObjectAssetPath(InNode.ObjectAssetPath)
{
Context.TargetGraph.InputNodeIndices.Add(Context.TargetGraph.Nodes.Num());
}
void FNiagaraCompilationNodeInput::BuildParameterMapHistory(FParameterMapHistoryBuilder& Builder, bool bRecursive, bool bFilterForCompilation) const
{
if (ConditionalRouteParameterMapAroundMe(Builder))
{
return;
}
if (InputVariable.GetType() == FNiagaraTypeDefinition::GetParameterMapDef())
{
int32 ParamMapIdx = Builder.FindMatchingParameterMapFromContextInputs(InputVariable);
if (ParamMapIdx == INDEX_NONE && Usage != ENiagaraInputNodeUsage::TranslatorConstant)
{
ParamMapIdx = Builder.CreateParameterMap();
}
else if (ParamMapIdx == INDEX_NONE && Builder.Histories.Num() != 0)
{
ParamMapIdx = 0;
}
if (ParamMapIdx != INDEX_NONE)
{
uint32 NodeIdx = Builder.BeginNodeVisitation(ParamMapIdx, this);
Builder.EndNodeVisitation(ParamMapIdx, NodeIdx);
Builder.RegisterParameterMapPin(ParamMapIdx, &OutputPins[0]);
}
}
else if (InputVariable.GetType().IsStatic())
{
int32 ConstantIdx = Builder.FindMatchingStaticFromContextInputs(InputVariable);
if (ConstantIdx != INDEX_NONE)
{
Builder.RegisterConstantPin(ConstantIdx, &OutputPins[0]);
}
}
}
void FNiagaraCompilationNodeInput::Compile(FTranslator* Translator, TArray<int32>& Outputs) const
{
if (!NodeEnabled)
{
Outputs.Add(INDEX_NONE);
return;
}
if (InputVariable.GetType() == FNiagaraTypeDefinition::GetGenericNumericDef())
{
Outputs.Add(INDEX_NONE);
Translator->Error(LOCTEXT("InvalidPinType", "Numeric types should be able to be inferred from use by this phase of compilation."), this, nullptr);
return;
}
int32 FunctionParam = INDEX_NONE;
if (bExposed && Translator->GetFunctionParameter(InputVariable, FunctionParam))
{
//If we're in a function and this parameter hasn't been provided, compile the local default.
if (FunctionParam == INDEX_NONE)
{
int32 Default = InputPins.Num() > 0 ? Translator->CompileInputPin(&InputPins[0]) : INDEX_NONE;
if (Default == INDEX_NONE)
{
//We failed to compile the default pin so just use the value of the input.
if (Usage == ENiagaraInputNodeUsage::Parameter)
{
if (!DataInterfaceName.IsNone())
{
check(DuplicatedDataInterface);
check(InputVariable.IsDataInterface());
Outputs.Add(Translator->RegisterDataInterface(InputVariable, DuplicatedDataInterface, false, false));
return;
}
else if (!ObjectAssetName.IsNone())
{
check(InputVariable.IsUObject());
Outputs.Add(Translator->RegisterUObjectPath(InputVariable, ObjectAssetPath, false));
return;
}
}
Default = Translator->GetConstant(InputVariable);
}
Outputs.Add(Default);
return;
}
}
switch (Usage)
{
case ENiagaraInputNodeUsage::Parameter:
if (!DataInterfaceName.IsNone())
{
check(DuplicatedDataInterface);
check(InputVariable.IsDataInterface());
Outputs.Add(Translator->RegisterDataInterface(InputVariable, DuplicatedDataInterface, false, false));
break;
}
else if (!ObjectAssetName.IsNone())
{
check(InputVariable.IsUObject());
Outputs.Add(Translator->RegisterUObjectPath(InputVariable, ObjectAssetPath, false));
break;
}
else
{
Outputs.Add(Translator->GetParameter(InputVariable));
break;
}
case ENiagaraInputNodeUsage::SystemConstant:
Outputs.Add(Translator->GetParameter(InputVariable)); break;
case ENiagaraInputNodeUsage::Attribute:
Outputs.Add(Translator->GetAttribute(InputVariable)); break;
case ENiagaraInputNodeUsage::TranslatorConstant:
Outputs.Add(Translator->GetParameter(InputVariable)); break;
case ENiagaraInputNodeUsage::RapidIterationParameter:
Outputs.Add(Translator->GetRapidIterationParameter(InputVariable)); break;
default:
check(false);
}
}
void FNiagaraCompilationNodeInput::AppendFunctionAliasForContext(const FNiagaraDigestFunctionAliasContext& InFunctionAliasContext, FString& InOutFunctionAlias, bool& OutOnlyOncePerNodeType) const
{
if (Usage == ENiagaraInputNodeUsage::TranslatorConstant && InputVariable == TRANSLATOR_PARAM_CALL_ID)
{
OutOnlyOncePerNodeType = true;
// The call ID should be unique for each translated node as it is used by the seeded random functions.
// We don't want it to be shared across the spawn and update script, so functions including it will have the usage added to their name.
InOutFunctionAlias += "_ScriptUsage" + FString::FormatAsNumber((uint8)InFunctionAliasContext.ScriptUsage);
}
}
FNiagaraCompilationNodeOp::FNiagaraCompilationNodeOp(const UNiagaraNodeOp* InNode, FNiagaraCompilationGraphCreateContext& Context)
: FNiagaraCompilationNode(ENodeType::Op, InNode, Context)
{
OpName = InNode->OpName;
}
FNiagaraCompilationNodeOp::FNiagaraCompilationNodeOp(const FNiagaraCompilationNodeOp& InNode, FNiagaraCompilationGraphDuplicateContext& Context)
: FNiagaraCompilationNode(InNode, Context)
, OpName(InNode.OpName)
{
}
FNiagaraTypeDefinition FNiagaraCompilationNodeOp::ResolveCustomNumericType(TConstArrayView<FNiagaraTypeDefinition> ConcreteInputTypes) const
{
const FNiagaraOpInfo* OpInfo = FNiagaraOpInfo::GetOpInfo(OpName);
if (OpInfo && OpInfo->CustomNumericResolveFunction.IsBound())
{
return OpInfo->CustomNumericResolveFunction.Execute(ConcreteInputTypes);
}
return FNiagaraTypeDefinition::GetGenericNumericDef();
}
void FNiagaraCompilationNodeOp::BuildParameterMapHistory(FParameterMapHistoryBuilder& Builder, bool bRecursive, bool bFilterForCompilation) const
{
FNiagaraCompilationNode::BuildParameterMapHistory(Builder, bRecursive, bFilterForCompilation);
if (ConditionalRouteParameterMapAroundMe(Builder))
{
return;
}
const FNiagaraOpInfo* OpInfo = FNiagaraOpInfo::GetOpInfo(OpName);
if (OpInfo && OpInfo->StaticVariableResolveFunction.IsBound())
{
const FNiagaraCompilationOutputPin* OutputPin = OutputPins.IsEmpty() ? nullptr : &OutputPins[0];
const bool bAllPinsStatic = !InputPins.ContainsByPredicate([](const FNiagaraCompilationInputPin& InputPin) -> bool
{
return !InputPin.Variable.GetType().IsStatic();
});
if (bAllPinsStatic)
{
TArray<int32> Vars;
for (const FNiagaraCompilationInputPin& InputPin : InputPins)
{
int32 Value = 0;
Builder.SetConstantByStaticVariable(Value, &InputPin);
Vars.Add(Value);
}
if (Vars.Num() > 0)
{
int32 Result = OpInfo->StaticVariableResolveFunction.Execute(Vars);
int32 ConstantIdx = Builder.AddOrGetConstantFromValue(FString::FromInt(Result));
if (UNiagaraScript::LogCompileStaticVars > 0)
{
UE_LOG(LogNiagaraEditor, Log, TEXT("Inputs Static Node Op: %s"), *FullTitle);
for (int32 i = 0; i < Vars.Num(); i++)
{
UE_LOG(LogNiagaraEditor, Log, TEXT("[%d] %d"), i, Vars[i]);
}
UE_LOG(LogNiagaraEditor, Log, TEXT("Result: %d"), Result);
}
Builder.RegisterConstantPin(ConstantIdx, OutputPin);
}
}
}
}
void FNiagaraCompilationNodeOp::Compile(FTranslator* Translator, TArray<int32>& Outputs) const
{
const FNiagaraOpInfo* OpInfo = FNiagaraOpInfo::GetOpInfo(OpName);
if (!OpInfo)
{
FFormatNamedArguments Args;
Args.Add(TEXT("OpName"), FText::FromName(OpName));
FText Format = LOCTEXT("Unknown opcode", "Unknown opcode on {OpName} node.");
Translator->Error(FText::Format(Format, Args), this, nullptr);
return;
}
const int32 InputCount = OpInfo->bSupportsAddedInputs ? InputPins.Num() : OpInfo->Inputs.Num();
const int32 OutputCount = OpInfo->Outputs.Num();
TArray<int32> Inputs;
bool bError = false;
for (int32 InputPinIt = 0; InputPinIt < InputCount; ++InputPinIt)
{
const FNiagaraCompilationInputPin& InputPin = InputPins[InputPinIt];
int32 CompiledInput = Translator->CompileInputPin(&InputPin);
if (CompiledInput == INDEX_NONE)
{
bError = true;
FFormatNamedArguments Args;
Args.Add(TEXT("OpName"), FText::FromString(FullTitle));
FText Format = LOCTEXT("InputErrorFormat", "Error compiling input on {OpName} node.");
Translator->Error(FText::Format(Format, Args), this, &InputPin);
}
else if (InputPinIt < OpInfo->Inputs.Num() && OpInfo->Inputs[InputPinIt].DataType == FNiagaraTypeDefinition::GetGenericNumericDef())
{
// Some nodes disallow integer or floating numeric input pins, so we guard against them here.
// This will catch both implicitly and explicitly set pin types.
// Currently this is for the Random Float/Integer and Seeded Random Float/Integer ops, but might be useful for others in the future.
FNiagaraTypeDefinition TypeDef = InputPin.Variable.GetType();
if (TypeDef.IsFloatPrimitive() && !OpInfo->bNumericsCanBeFloats)
{
bError = true;
FFormatNamedArguments Args;
Args.Add(TEXT("OpName"), FText::FromString(FullTitle));
FText Format = LOCTEXT("InputTypeErrorFormatFloat", "The {OpName} node cannot have float based numeric input pins.");
Translator->Error(FText::Format(Format, Args), this, &InputPin);
}
else if (!TypeDef.IsFloatPrimitive() && !OpInfo->bNumericsCanBeIntegers)
{
bError = true;
FFormatNamedArguments Args;
Args.Add(TEXT("OpName"), FText::FromString(FullTitle));
FText Format = LOCTEXT("InputTypeErrorFormatInt", "The {OpName} node cannot have integer based numeric input pins.");
Translator->Error(FText::Format(Format, Args), this, &InputPin);
}
}
Inputs.Add(CompiledInput);
}
Translator->Operation(this, Inputs, Outputs);
}
FNiagaraCompilationNodeOutput::FNiagaraCompilationNodeOutput(const UNiagaraNodeOutput* InNode, FNiagaraCompilationGraphCreateContext& Context)
: FNiagaraCompilationNode(ENodeType::Output, InNode, Context)
{
Context.ParentGraph.OutputNodeIndices.Add(Context.ParentGraph.Nodes.Num());
Usage = InNode->GetUsage();
UsageId = InNode->GetUsageId();
StackContextOverrideName = InNode->GetStackContextOverride();
Outputs = InNode->GetOutputs();
}
FNiagaraCompilationNodeOutput::FNiagaraCompilationNodeOutput(const FNiagaraCompilationNodeOutput& InNode, FNiagaraCompilationGraphDuplicateContext& Context)
: FNiagaraCompilationNode(InNode, Context)
, Usage(InNode.Usage)
, UsageId(InNode.UsageId)
, StackContextOverrideName(InNode.StackContextOverrideName)
, Outputs(InNode.Outputs)
{
Context.TargetGraph.OutputNodeIndices.Add(Context.TargetGraph.Nodes.Num());
}
void FNiagaraCompilationNodeOutput::BuildParameterMapHistory(FParameterMapHistoryBuilder& Builder, bool bRecursive, bool bFilterForCompilation) const
{
static const FNiagaraTypeDefinition ParameterMapType = FNiagaraTypeDefinition::GetParameterMapDef();
FNiagaraCompilationNode::BuildParameterMapHistory(Builder, bRecursive, bFilterForCompilation);
for (const FNiagaraCompilationInputPin& InputPin : InputPins)
{
if (InputPin.Variable.GetType().IsStatic())
{
Builder.RegisterConstantFromInputPin(&InputPin, InputPin.DefaultValue);
}
else if (InputPin.Variable.GetType() == ParameterMapType)
{
if (InputPin.LinkedTo)
{
int32 ParamMapIdx = Builder.TraceParameterMapOutputPin(InputPin.LinkedTo);
if (UNiagaraScript::LogCompileStaticVars > 0)
{
UE_LOG(LogNiagaraEditor, Log, TEXT("Build Parameter Map History: NiagaraCompilationNodeOutput %s PMapIdx: %d"), *FullTitle, ParamMapIdx);
}
Builder.RegisterParameterMapPin(ParamMapIdx, &InputPin);
}
}
}
}
void FNiagaraCompilationNodeOutput::Compile(FTranslator* Translator, TArray<int32>& OutputTokens) const
{
TArray<int32> Results;
bool bError = CompileInputPins(Translator, Results);
if (!bError)
{
Translator->Output(this, Results);
}
}
FNiagaraCompilationNodeOutputTag::FNiagaraCompilationNodeOutputTag(const UNiagaraNodeOutputTag* InNode, FNiagaraCompilationGraphCreateContext& Context)
: FNiagaraCompilationNode(ENodeType::OutputTag, InNode, Context)
{
bEmitMessageOnFailure = InNode->bEmitMessageOnFailure;
bEditorOnly = InNode->bEditorOnly;
FailureSeverity = InNode->FailureSeverity;
}
FNiagaraCompilationNodeOutputTag::FNiagaraCompilationNodeOutputTag(const FNiagaraCompilationNodeOutputTag& InNode, FNiagaraCompilationGraphDuplicateContext& Context)
: FNiagaraCompilationNode(InNode, Context)
, bEmitMessageOnFailure(InNode.bEmitMessageOnFailure)
, bEditorOnly(InNode.bEditorOnly)
, FailureSeverity(InNode.FailureSeverity)
{
}
void FNiagaraCompilationNodeOutputTag::BuildParameterMapHistory(FParameterMapHistoryBuilder& Builder, bool bRecursive, bool bFilterForCompilation) const
{
FNiagaraCompilationNode::BuildParameterMapHistory(Builder, bRecursive, bFilterForCompilation);
if (ConditionalRouteParameterMapAroundMe(Builder))
{
return;
}
int32 ParamMapIdx = INDEX_NONE;
uint32 NodeIdx = INDEX_NONE;
const int32 InputPinCount = InputPins.Num();
for (int32 InputPinIt = 0; InputPinIt < InputPinCount; ++InputPinIt)
{
const FNiagaraCompilationInputPin* InputPin = &InputPins[InputPinIt];
FNiagaraTypeDefinition VarTypeDef = InputPin->Variable.GetType();
if (InputPinIt == 0 && InputPin != nullptr && VarTypeDef == FNiagaraTypeDefinition::GetParameterMapDef())
{
const FNiagaraCompilationOutputPin* PriorParamPin = nullptr;
if (InputPin->LinkedTo)
{
PriorParamPin = InputPin->LinkedTo;
}
// Now plow into our ancestor node
if (PriorParamPin)
{
ParamMapIdx = Builder.TraceParameterMapOutputPin(PriorParamPin);
NodeIdx = Builder.BeginNodeVisitation(ParamMapIdx, this);
}
}
else if (InputPinIt > 0 && InputPin != nullptr && ParamMapIdx != INDEX_NONE)
{
Builder.HandleVariableWrite(ParamMapIdx, InputPin);
}
}
if (ParamMapIdx != INDEX_NONE)
{
Builder.EndNodeVisitation(ParamMapIdx, NodeIdx);
}
Builder.RegisterParameterMapPin(ParamMapIdx, &OutputPins[0]);
}
void FNiagaraCompilationNodeOutputTag::Compile(FTranslator* Translator, TArray<int32>& Outputs) const
{
for (const FNiagaraCompilationInputPin& InputPin : InputPins)
{
if (NodeEnabled == false && InputPin.Variable.GetType() != FNiagaraTypeDefinition::GetParameterMapDef())
{
continue;
}
int32 CompiledInput = Translator->CompileInputPin(&InputPin);
if (InputPin.Variable.GetType() == FNiagaraTypeDefinition::GetParameterMapDef())
{
Outputs.Add(CompiledInput);
}
else
{
Translator->WriteCompilerTag(CompiledInput, &InputPin, bEditorOnly, bEmitMessageOnFailure, FailureSeverity);
}
}
ensure(Outputs.Num() == 1);
}
FNiagaraCompilationNodeConvert::FNiagaraCompilationNodeConvert(const UNiagaraNodeConvert* InNode, FNiagaraCompilationGraphCreateContext& Context)
: FNiagaraCompilationNode(ENodeType::Convert, InNode, Context)
{
const TArray<FNiagaraConvertConnection>& SourceConnections = InNode->GetConnections();
Connections.Reserve(SourceConnections.Num());
Algo::Transform(SourceConnections, Connections, [](const FNiagaraConvertConnection& SourceConnection) -> FCachedConnection
{
FCachedConnection Connection;
Connection.SourcePinId = SourceConnection.SourcePinId;
Connection.SourcePath = SourceConnection.SourcePath;
Connection.DestinationPinId = SourceConnection.DestinationPinId;
Connection.DestinationPath = SourceConnection.DestinationPath;
return Connection;
});
}
FNiagaraCompilationNodeConvert::FNiagaraCompilationNodeConvert(const FNiagaraCompilationNodeConvert& InNode, FNiagaraCompilationGraphDuplicateContext& Context)
: FNiagaraCompilationNode(InNode, Context)
, Connections(InNode.Connections)
{
}
void FNiagaraCompilationNodeConvert::Compile(FTranslator* Translator, TArray<int32>& Outputs) const
{
TArray<int32, TInlineAllocator<16>> CompileInputs;
CompileInputs.Reserve(InputPins.Num());
for (const FNiagaraCompilationInputPin& InputPin : InputPins)
{
if (InputPin.PinType.PinCategory == UEdGraphSchema_Niagara::PinCategoryType ||
InputPin.PinType.PinCategory == UEdGraphSchema_Niagara::PinCategoryEnum ||
InputPin.PinType.PinCategory == UEdGraphSchema_Niagara::PinCategoryStaticType ||
InputPin.PinType.PinCategory == UEdGraphSchema_Niagara::PinCategoryStaticEnum)
{
int32 CompiledInput = Translator->CompileInputPin(&InputPin);
if (CompiledInput == INDEX_NONE)
{
Translator->Error(LOCTEXT("ConvertInputError", "Error compiling input for convert node."), this, &InputPin);
}
CompileInputs.Add(CompiledInput);
}
}
Translator->Convert(this, CompileInputs, Outputs);
}
FNiagaraCompilationNodeParameterMapGet::FNiagaraCompilationNodeParameterMapGet(const UNiagaraNodeParameterMapGet* InNode, FNiagaraCompilationGraphCreateContext& Context)
: FNiagaraCompilationNode(ENodeType::ParameterMapGet, InNode, Context)
{
DefaultInputPinIndices.Reserve(OutputPins.Num());
for (const FNiagaraCompilationOutputPin& OutputPin : OutputPins)
{
int32 InputPinIndex = INDEX_NONE;
if (const UEdGraphPin* DefaultPin = InNode->GetDefaultPin(InNode->Pins[OutputPin.SourcePinIndex]))
{
const int32 NodeInputPinIndex = InNode->Pins.IndexOfByKey(DefaultPin);
InputPinIndex = InputPins.IndexOfByPredicate([&NodeInputPinIndex](const FNiagaraCompilationInputPin& InputPin) -> bool
{
return InputPin.SourcePinIndex == NodeInputPinIndex;
});
}
DefaultInputPinIndices.Add(InputPinIndex);
}
}
FNiagaraCompilationNodeParameterMapGet::FNiagaraCompilationNodeParameterMapGet(const FNiagaraCompilationNodeParameterMapGet& InNode, FNiagaraCompilationGraphDuplicateContext& Context)
: FNiagaraCompilationNode(InNode, Context)
, DefaultInputPinIndices(InNode.DefaultInputPinIndices)
{
}
void FNiagaraCompilationNodeParameterMapGet::BuildParameterMapHistory(FParameterMapHistoryBuilder& Builder, bool bRecursive, bool bFilterForCompilation) const
{
if (bRecursive)
{
Builder.VisitInputPin(&InputPins[0], bFilterForCompilation);
}
if (ConditionalRouteParameterMapAroundMe(Builder))
{
return;
}
int32 ParamMapIdx = INDEX_NONE;
if (InputPins[0].LinkedTo)
{
ParamMapIdx = Builder.TraceParameterMapOutputPin((InputPins[0].LinkedTo));
}
if (ParamMapIdx != INDEX_NONE)
{
uint32 NodeIdx = Builder.BeginNodeVisitation(ParamMapIdx, this);
const int32 OutputPinCount = OutputPins.Num();
check(DefaultInputPinIndices.Num() == OutputPinCount);
for (int32 OutputPinIt = 0; OutputPinIt < OutputPinCount; ++OutputPinIt)
{
const FNiagaraCompilationOutputPin& OutputPin = OutputPins[OutputPinIt];
const bool HasDefault = DefaultInputPinIndices[OutputPinIt] != INDEX_NONE;
bool bUsedDefaults = false;
if (bRecursive && HasDefault)
{
const FNiagaraCompilationInputPin* DefaultPin = &InputPins[DefaultInputPinIndices[OutputPinIt]];
Builder.HandleVariableRead(ParamMapIdx, &OutputPin, true, DefaultPin, bFilterForCompilation, bUsedDefaults);
}
else
{
Builder.HandleVariableRead(ParamMapIdx, &OutputPin, true, nullptr, bFilterForCompilation, bUsedDefaults);
}
}
Builder.EndNodeVisitation(ParamMapIdx, NodeIdx);
}
}
void FNiagaraCompilationNodeParameterMapGet::Compile(FTranslator* Translator, TArray<int32>& Outputs) const
{
// First compile fully down the hierarchy for our predecessors..
const int32 InputPinCount = InputPins.Num();
if (InputPinCount)
{
// Initialize the outputs to invalid values.
check(Outputs.Num() == 0);
Outputs.Init(INDEX_NONE, OutputPins.Num());
TArray<int32, TInlineAllocator<16>> CompileInputs;
CompileInputs.Init(INDEX_NONE, InputPinCount);
const FNiagaraCompilationInputPin& ParamMapInputPin = InputPins[0];
if (ParamMapInputPin.PinType.PinCategory == UEdGraphSchema_Niagara::PinCategoryType ||
ParamMapInputPin.PinType.PinCategory == UEdGraphSchema_Niagara::PinCategoryStaticType ||
ParamMapInputPin.PinType.PinCategory == UEdGraphSchema_Niagara::PinCategoryEnum ||
ParamMapInputPin.PinType.PinCategory == UEdGraphSchema_Niagara::PinCategoryStaticEnum)
{
int32 CompiledInput = Translator->CompileInputPin(&ParamMapInputPin);
if (CompiledInput == INDEX_NONE)
{
Translator->Error(LOCTEXT("MapGetInputError", "Error compiling input for param map get node."), this, &ParamMapInputPin);
}
CompileInputs[0] = CompiledInput;
}
if (ParamMapInputPin.LinkedTo)
{
Translator->ParameterMapGet(this, CompileInputs, Outputs);
}
}
}
FNiagaraCompilationNodeParameterMapFor::FNiagaraCompilationNodeParameterMapFor(const UNiagaraNodeParameterMapFor* InNode, FNiagaraCompilationGraphCreateContext& Context, ENodeType InNodeType)
: FNiagaraCompilationNodeParameterMapSet(InNode, Context, InNodeType)
{
}
FNiagaraCompilationNodeParameterMapFor::FNiagaraCompilationNodeParameterMapFor(const FNiagaraCompilationNodeParameterMapFor& InNode, FNiagaraCompilationGraphDuplicateContext& Context)
: FNiagaraCompilationNodeParameterMapSet(InNode, Context)
{
}
void FNiagaraCompilationNodeParameterMapFor::Compile(FTranslator* Translator, TArray<int32>& Outputs) const
{
if (Translator->GetSimulationTarget() == ENiagaraSimTarget::GPUComputeSim)
{
const int32 IterationCount = Translator->CompileInputPin(&InputPins[1]);
Translator->ParameterMapForBegin(this, IterationCount);
FNiagaraCompilationNodeParameterMapSet::Compile(Translator, Outputs);
Translator->ParameterMapForEnd(this);
}
else
{
FNiagaraCompilationNodeParameterMapSet::Compile(Translator, Outputs);
//Translator->Message(FNiagaraCompileEventSeverity::Log,LOCTEXT("UnsupportedParamMapFor", "Parameter map for is not yet supported on cpu."), this, nullptr);
}
}
void FNiagaraCompilationNodeParameterMapFor::CollectInputPinsToCompile(FTranslator* Translator, FInputPinCollection& ActiveInputPins) const
{
FNiagaraCompilationNodeParameterMapSet::CollectInputPinsToCompile(Translator, ActiveInputPins);
// we need to cull the iteration pin from the list of pins to compile as it's already going to be compiled during the MapFor::Compile
ActiveInputPins.Remove(&InputPins[1]);
}
FNiagaraCompilationNodeParameterMapForWithContinue::FNiagaraCompilationNodeParameterMapForWithContinue(const UNiagaraNodeParameterMapForWithContinue* InNode, FNiagaraCompilationGraphCreateContext& Context)
: FNiagaraCompilationNodeParameterMapFor(InNode, Context, ENodeType::ParameterMapForWithContinue)
{
}
FNiagaraCompilationNodeParameterMapForWithContinue::FNiagaraCompilationNodeParameterMapForWithContinue(const FNiagaraCompilationNodeParameterMapForWithContinue& InNode, FNiagaraCompilationGraphDuplicateContext& Context)
: FNiagaraCompilationNodeParameterMapFor(InNode, Context)
{
}
void FNiagaraCompilationNodeParameterMapForWithContinue::Compile(FTranslator* Translator, TArray<int32>& Outputs) const
{
if (Translator->GetSimulationTarget() == ENiagaraSimTarget::GPUComputeSim)
{
const int32 IterationCount = Translator->CompileInputPin(&InputPins[1]);
Translator->ParameterMapForBegin(this, IterationCount);
const int32 IterationEnabledChunk = Translator->CompileInputPin(&InputPins[2]);
Translator->ParameterMapForContinue(this, IterationEnabledChunk);
FNiagaraCompilationNodeParameterMapSet::Compile(Translator, Outputs);
Translator->ParameterMapForEnd(this);
}
else
{
FNiagaraCompilationNodeParameterMapSet::Compile(Translator, Outputs);
//Translator->Message(FNiagaraCompileEventSeverity::Log,LOCTEXT("UnsupportedParamMapFor", "Parameter map for is not yet supported on cpu."), this, nullptr);
}
}
void FNiagaraCompilationNodeParameterMapForWithContinue::CollectInputPinsToCompile(FTranslator* Translator, FInputPinCollection& ActiveInputPins) const
{
FNiagaraCompilationNodeParameterMapFor::CollectInputPinsToCompile(Translator, ActiveInputPins);
// we also need to cull out the iteration enabled pin
ActiveInputPins.Remove(&InputPins[2]);
}
FNiagaraCompilationNodeParameterMapForIndex::FNiagaraCompilationNodeParameterMapForIndex(const UNiagaraNodeParameterMapForIndex* InNode, FNiagaraCompilationGraphCreateContext& Context)
: FNiagaraCompilationNode(ENodeType::ParameterMapForIndex, InNode, Context)
{
}
FNiagaraCompilationNodeParameterMapForIndex::FNiagaraCompilationNodeParameterMapForIndex(const FNiagaraCompilationNodeParameterMapForIndex& InNode, FNiagaraCompilationGraphDuplicateContext& Context)
: FNiagaraCompilationNode(InNode, Context)
{
}
void FNiagaraCompilationNodeParameterMapForIndex::Compile(FTranslator* Translator, TArray<int32>& Outputs) const
{
if (Translator)
{
if (Translator->GetSimulationTarget() == ENiagaraSimTarget::GPUComputeSim)
{
Outputs.Add(Translator->ParameterMapForInnerIndex());
}
else
{
FNiagaraVariable Constant(FNiagaraTypeDefinition::GetIntDef(), TEXT("Constant"));
Constant.SetValue(0);
Outputs.Add(Translator->GetConstant(Constant));
}
}
}
FNiagaraCompilationNodeParameterMapSet::FNiagaraCompilationNodeParameterMapSet(const UNiagaraNodeParameterMapSet* InNode, FNiagaraCompilationGraphCreateContext& Context, ENodeType InNodeType)
: FNiagaraCompilationNode(InNodeType, InNode, Context)
{
}
FNiagaraCompilationNodeParameterMapSet::FNiagaraCompilationNodeParameterMapSet(const FNiagaraCompilationNodeParameterMapSet& InNode, FNiagaraCompilationGraphDuplicateContext& Context)
: FNiagaraCompilationNode(InNode, Context)
{
}
void FNiagaraCompilationNodeParameterMapSet::BuildParameterMapHistory(FParameterMapHistoryBuilder& Builder, bool bRecursive, bool bFilterForCompilation) const
{
const FString SourceFullName = SourceNode->GetPathName();
FNiagaraCompilationNode::BuildParameterMapHistory(Builder, bRecursive, bFilterForCompilation);
if (ConditionalRouteParameterMapAroundMe(Builder))
{
return;
}
int32 ParamMapIdx = INDEX_NONE;
uint32 NodeIdx = INDEX_NONE;
const int32 InputPinCount = InputPins.Num();
for (int32 InputPinIt = 0; InputPinIt < InputPinCount; ++InputPinIt)
{
const FNiagaraCompilationInputPin& InputPin = InputPins[InputPinIt];
FNiagaraTypeDefinition VarTypeDef = InputPin.Variable.GetType();
if (InputPinIt == 0 && VarTypeDef == FNiagaraTypeDefinition::GetParameterMapDef())
{
const FNiagaraCompilationOutputPin* PriorParamPin = nullptr;
if (InputPin.LinkedTo)
{
PriorParamPin = InputPin.LinkedTo;
}
// Now plow into our ancestor node
if (PriorParamPin)
{
ParamMapIdx = Builder.TraceParameterMapOutputPin(PriorParamPin);
NodeIdx = Builder.BeginNodeVisitation(ParamMapIdx, this);
}
}
else if (InputPinIt > 0 && ParamMapIdx != INDEX_NONE)
{
Builder.HandleVariableWrite(ParamMapIdx, &InputPin);
}
}
if (ParamMapIdx != INDEX_NONE)
{
Builder.EndNodeVisitation(ParamMapIdx, NodeIdx);
}
Builder.RegisterParameterMapPin(ParamMapIdx, &OutputPins[0]);
}
void FNiagaraCompilationNodeParameterMapSet::Compile(FTranslator* Translator, TArray<int32>& Outputs) const
{
// Initialize the outputs to invalid values.
check(Outputs.Num() == 0);
Outputs.Init(INDEX_NONE, OutputPins.Num());
FInputPinCollection ActiveInputPins;
CollectInputPinsToCompile(Translator, ActiveInputPins);
TArray<FTranslator::FCompiledPin, TInlineAllocator<16>> CompileInputs;
CompileInputs.Reserve(ActiveInputPins.Num());
// update the translator with the culled function calls before compiling any further
for (const FNiagaraCompilationInputPin* InputPin : ActiveInputPins)
{
if (NodeEnabled || InputPin->Variable.GetType() == FNiagaraTypeDefinition::GetParameterMapDef())
{
int32 CompiledInput = Translator->CompileInputPin(InputPin);
if (CompiledInput == INDEX_NONE)
{
Translator->Error(LOCTEXT("MapSetInputError", "Error compiling input for set node."), this, InputPin);
}
CompileInputs.Emplace(CompiledInput, InputPin);
}
}
if (ActiveInputPins.Num() && ActiveInputPins[0] && ActiveInputPins[0]->LinkedTo)
{
Translator->ParameterMapSet(this, CompileInputs, Outputs);
}
}
void FNiagaraCompilationNodeParameterMapSet::CollectInputPinsToCompile(FTranslator* Translator, FInputPinCollection& ActiveInputPins) const
{
ActiveInputPins.Reserve(InputPins.Num());
// do a first pass over all of the pins so that we can properly cull out input pins and
// propagate the disabled pins up the chain
for (const FNiagaraCompilationInputPin& InputPin : InputPins)
{
if (Translator->IsFunctionVariableCulledFromCompilation(InputPin.PinName))
{
Translator->CullMapSetInputPin(&InputPin);
}
else
{
ActiveInputPins.Add(&InputPin);
}
}
}
FNiagaraCompilationNodeReadDataSet::FNiagaraCompilationNodeReadDataSet(const UNiagaraNodeReadDataSet* InNode, FNiagaraCompilationGraphCreateContext& Context)
: FNiagaraCompilationNode(ENodeType::ReadDataSet, InNode, Context)
{
DataSet = InNode->DataSet;
DataSetVariables = InNode->Variables;
}
FNiagaraCompilationNodeReadDataSet::FNiagaraCompilationNodeReadDataSet(const FNiagaraCompilationNodeReadDataSet& InNode, FNiagaraCompilationGraphDuplicateContext& Context)
: FNiagaraCompilationNode(InNode, Context)
, DataSet(InNode.DataSet)
, DataSetVariables(InNode.DataSetVariables)
{
}
void FNiagaraCompilationNodeReadDataSet::BuildParameterMapHistory(FParameterMapHistoryBuilder& Builder, bool bRecursive, bool bFilterForCompilation) const
{
FNiagaraCompilationNode::BuildParameterMapHistory(Builder, bRecursive, bFilterForCompilation);
if (ConditionalRouteParameterMapAroundMe(Builder))
{
return;
}
int32 ParamMapIdx = INDEX_NONE;
if (InputPins[0].LinkedTo)
{
ParamMapIdx = Builder.TraceParameterMapOutputPin(NiagaraCompilationImpl::TraceOutputPin(Builder, InputPins[0].LinkedTo, bFilterForCompilation));
}
if (ParamMapIdx != INDEX_NONE)
{
uint32 NodeIdx = Builder.BeginNodeVisitation(ParamMapIdx, this);
Builder.EndNodeVisitation(ParamMapIdx, NodeIdx);
}
Builder.RegisterParameterMapPin(ParamMapIdx, &OutputPins[0]);
}
void FNiagaraCompilationNodeReadDataSet::Compile(FTranslator* Translator, TArray<int32>& Outputs) const
{
TArray<int32> Inputs;
CompileInputPins(Translator, Inputs);
Translator->ReadDataSet(DataSet, DataSetVariables, ENiagaraDataSetAccessMode::AppendConsume, Inputs[0], Outputs);
}
FNiagaraCompilationNodeStaticSwitch::FNiagaraCompilationNodeStaticSwitch(const UNiagaraNodeStaticSwitch* InNode, FNiagaraCompilationGraphCreateContext& Context)
: FNiagaraCompilationNodeUsageSelector(InNode, Context, ENodeType::StaticSwitch)
{
bSetByCompiler = InNode->IsSetByCompiler();
bSetByPin = InNode->IsSetByPin();
bDebugStateSwitch = InNode->IsDebugSwitch();
SwitchType = InNode->SwitchTypeData.SwitchType;
SwitchBranchCount = InNode->GetOptionValues().Num();
InputParameterName = InNode->InputParameterName;
SwitchConstant = InNode->SwitchTypeData.SwitchConstant;
InputType = InNode->GetInputType();
SelectorPinIndex = INDEX_NONE;
if (const UEdGraphPin* SelectorPin = InNode->GetSelectorPin())
{
SelectorPinIndex = InputPins.IndexOfByPredicate([&SelectorPin, &InNode](const FNiagaraCompilationInputPin& InputPin) -> bool
{
return InNode->Pins[InputPin.SourcePinIndex] == SelectorPin;
});
}
if (!bSetByCompiler && !bSetByPin)
{
Context.ParentGraph.StaticSwitchInputs.AddUnique(FNiagaraVariable(InputType, InputParameterName));
}
}
FNiagaraCompilationNodeStaticSwitch::FNiagaraCompilationNodeStaticSwitch(const FNiagaraCompilationNodeStaticSwitch& InNode, FNiagaraCompilationGraphDuplicateContext& Context)
: FNiagaraCompilationNodeUsageSelector(InNode, Context)
, bSetByCompiler(InNode.bSetByCompiler)
, bSetByPin(InNode.bSetByPin)
, SwitchType(InNode.SwitchType)
, InputType(InNode.InputType)
, SwitchBranchCount(InNode.SwitchBranchCount)
, SelectorPinIndex(InNode.SelectorPinIndex)
, InputParameterName(InNode.InputParameterName)
, SwitchConstant(InNode.SwitchConstant)
{
if (!bSetByCompiler && !bSetByPin)
{
Context.TargetGraph.StaticSwitchInputs.AddUnique(FNiagaraVariable(InputType, InputParameterName));
}
}
int32 FNiagaraCompilationNodeStaticSwitch::GetBaseInputChannel(int32 SelectorValue) const
{
const int32 InputChannelCount = bSetByPin ? InputPins.Num() - 1 : InputPins.Num();
int32 BaseInputPinIndex = INDEX_NONE;
switch (SwitchType)
{
case ENiagaraStaticSwitchType::Bool:
BaseInputPinIndex = SelectorValue ? 0 : (InputChannelCount / 2);
break;
case ENiagaraStaticSwitchType::Enum:
case ENiagaraStaticSwitchType::Integer:
BaseInputPinIndex = FMath::Clamp(SelectorValue, 0, SwitchBranchCount - 1) * InputChannelCount / SwitchBranchCount;
break;
}
return BaseInputPinIndex;
}
void FNiagaraCompilationNodeStaticSwitch::BuildParameterMapHistory(FParameterMapHistoryBuilder& Builder, bool bRecursive, bool bFilterForCompilation) const
{
if (!bFilterForCompilation)
{
FNiagaraCompilationNode::BuildParameterMapHistory(Builder, bRecursive, bFilterForCompilation);
//FNiagaraCompilationNodeUsageSelector::BuildParameterMapHistory(Builder, bRecursive, bFilterForCompilation);
// seems odd for the !bFilterForCompilation condition, but the original code registers a passthrough
// for a single parameter map input/output pair, if one exists
const FNiagaraTypeDefinition ParameterMapDef = FNiagaraTypeDefinition::GetParameterMapDef();
auto IsParameterMapPin = [&ParameterMapDef](const FNiagaraCompilationPin& Pin) -> bool
{
return Pin.Variable.GetType() == ParameterMapDef;
};
const FNiagaraCompilationOutputPin* OutputParameterMapPin = OutputPins.FindByPredicate(IsParameterMapPin);
const FNiagaraCompilationInputPin* InputParameterMapPin = InputPins.FindByPredicate(IsParameterMapPin);
if (OutputParameterMapPin && InputParameterMapPin)
{
const bool bVisitInputPin = false;
RegisterPassthroughPin(Builder, InputParameterMapPin, OutputParameterMapPin, bFilterForCompilation, bVisitInputPin);
}
return;
}
int32 SelectorValue = INDEX_NONE;
Builder.TraversalStateContext->GetStaticSwitchValue(NodeGuid, SelectorValue);
if (bSetByPin)
{
const FNiagaraCompilationInputPin* SelectorPin = &InputPins[SelectorPinIndex];
Builder.VisitInputPin(SelectorPin, bFilterForCompilation);
Builder.SetConstantByStaticVariable(SelectorValue, SelectorPin);
if (UNiagaraScript::LogCompileStaticVars != 0)
{
UE_LOG(LogNiagaraEditor, Log, TEXT("Static Switch Value: %d Name: \"%s\" SwitchInputParameterName:\"%s\""), SelectorValue, *FullTitle, *InputParameterName.ToString());
}
}
if (ensure(SelectorValue != INDEX_NONE))
{
const int32 OutputPinCount = OutputPins.Num();
for (int OutputPinIt = 0; OutputPinIt < OutputPinCount; ++OutputPinIt)
{
const FNiagaraCompilationOutputPin& OutputPin = OutputPins[OutputPinIt];
const int32 BaseInputChannel = GetBaseInputChannel(SelectorValue);
if (BaseInputChannel != INDEX_NONE)
{
const FNiagaraCompilationInputPin* InputPin = &InputPins[BaseInputChannel + OutputPinIt];
const bool bVisitInputPin = true;
RegisterPassthroughPin(Builder, InputPin, &OutputPin, bFilterForCompilation, bVisitInputPin);
}
}
}
}
void FNiagaraCompilationNodeStaticSwitch::Compile(FTranslator* Translator, TArray<int32>& Outputs) const
{
int32 SelectorValue = INDEX_NONE;
if (!bSetByCompiler)
{
// all static switches should have already been resolved. If they haven't it's likely because the graph
// has been setup incorrectly. Possibly a selector pin is being used but it's associated static variable
// isn't being defined.
if (InputPins.IsValidIndex(SelectorPinIndex))
{
Translator->Error(LOCTEXT("CouldNotResolveStaticVarByPin", "Could not resolve static variable through pin."), this, &InputPins[SelectorPinIndex]);
}
// default to the first entry
SelectorValue = 0;
}
const int32 OutputPinCount = OutputPins.Num();
Outputs.Init(INDEX_NONE, OutputPinCount);
if (bSetByCompiler)
{
FNiagaraFixedConstantResolver ConstantResolver(Translator);
const FNiagaraVariable* Found = FNiagaraConstants::FindStaticSwitchConstant(SwitchConstant);
FNiagaraVariable Constant = Found ? *Found : FNiagaraVariable();
if (Found && ConstantResolver.ResolveConstant(Constant))
{
if (SwitchType == ENiagaraStaticSwitchType::Bool)
{
SelectorValue = Constant.GetValue<bool>();
}
else if (SwitchType == ENiagaraStaticSwitchType::Integer || SwitchType == ENiagaraStaticSwitchType::Enum)
{
SelectorValue = Constant.GetValue<int32>();
}
}
}
if (ensure(SelectorValue != INDEX_NONE))
{
const int32 BaseInputChannel = GetBaseInputChannel(SelectorValue);
for (int32 OutputPinIt = 0; OutputPinIt < OutputPinCount; ++OutputPinIt)
{
Outputs[OutputPinIt] = Translator->CompileInputPin(&InputPins[BaseInputChannel + OutputPinIt]);
}
}
}
void FNiagaraCompilationNodeStaticSwitch::ResolveNumerics()
{
const int32 InputPinCount = InputPins.Num();
const int32 OutputPinCount = OutputPins.Num();
for (int32 OutputPinIt = 0; OutputPinIt < OutputPinCount; ++OutputPinIt)
{
// Fix up numeric input pins and keep track of numeric types to decide the output type.
TArray<int32, TInlineAllocator<16>> InputPinIndices;
InputPinIndices.Reserve(SwitchBranchCount);
for (int32 BranchIt = 0; BranchIt < SwitchBranchCount; ++BranchIt)
{
InputPinIndices.Add(OutputPinIt + OutputPinCount * BranchIt);
}
ResolveNumericPins(InputPinIndices, { OutputPinIt });
}
}
bool FNiagaraCompilationNodeStaticSwitch::ResolveConstantValue(const FNiagaraCompilationInputPin& Pin, int32& Value)
{
if (Pin.LinkedTo)
{
return false;
}
const FEdGraphPinType& PinType = Pin.PinType;
if (PinType.PinCategory == UEdGraphSchema_Niagara::PinCategoryType && PinType.PinSubCategoryObject.IsValid())
{
FString PinTypeName = PinType.PinSubCategoryObject->GetName();
if (PinTypeName.Equals(FString(TEXT("NiagaraBool"))))
{
Value = Pin.DefaultValue.Equals(FString(TEXT("true"))) ? 1 : 0;
return true;
}
else if (PinTypeName.Equals(FString(TEXT("NiagaraInt32"))))
{
Value = FCString::Atoi(*Pin.DefaultValue);
return true;
}
}
else if (PinType.PinCategory == UEdGraphSchema_Niagara::PinCategoryEnum && PinType.PinSubCategoryObject.IsValid())
{
UEnum* Enum = Cast<UEnum>(PinType.PinSubCategoryObject);
FString FullName = Enum->GenerateFullEnumName(*Pin.DefaultValue);
Value = Enum->GetIndexByName(FName(*FullName));
return Value != INDEX_NONE;
}
return false;
}
const FNiagaraCompilationOutputPin* FNiagaraCompilationNodeStaticSwitch::TraceOutputPin(FParameterMapHistoryBuilder& Builder, const FNiagaraCompilationOutputPin* Pin, bool bFilterForCompilation) const
{
int32 OutputIndex = UE_PTRDIFF_TO_INT32(Pin - OutputPins.GetData());
if (OutputPins.IsValidIndex(OutputIndex))
{
int32 SelectorValue;
if (!ensure(Builder.TraversalStateContext->GetStaticSwitchValue(NodeGuid, SelectorValue)))
{
SelectorValue = 0;
}
const int32 BaseInputChannel = GetBaseInputChannel(SelectorValue);
if (BaseInputChannel != INDEX_NONE)
{
if (ensure(InputPins.IsValidIndex(BaseInputChannel + OutputIndex)))
{
const FNiagaraCompilationInputPin& InputPin = InputPins[BaseInputChannel + OutputIndex];
if (InputPin.LinkedTo)
{
return NiagaraCompilationImpl::TraceOutputPin(Builder, InputPin.LinkedTo, bFilterForCompilation);
}
}
}
}
return Pin;
}
TArray<const FNiagaraCompilationInputPin*> FNiagaraCompilationNodeStaticSwitch::EvaluateBranches(FNiagaraCompilationGraphInstanceContext& Context, FNiagaraCompilationBranchMap& Branches) const
{
if (bSetByCompiler && !bDebugStateSwitch)
{
return FNiagaraCompilationNode::EvaluateBranches(Context, Branches);
}
int32 SwitchValue = INDEX_NONE;
bool IsValueSet = false;
if (bDebugStateSwitch)
{
ENiagaraFunctionDebugState FunctionDebugState = ENiagaraFunctionDebugState::NoDebug;
if (!Context.bDisableDebugSwitches)
{
Context.TraversalContext.GetCurrentDebugState(FunctionDebugState);
}
IsValueSet = true;
SwitchValue = static_cast<int32>(FunctionDebugState);
}
else if (bSetByPin)
{
auto EvaluateVariableSwitchValue = [this](const FNiagaraVariable& ConstantVariable, int32& OutSwitchValue) -> bool
{
if (SwitchType == ENiagaraStaticSwitchType::Bool)
{
OutSwitchValue = ConstantVariable.GetValue<bool>();
return true;
}
else if (SwitchType == ENiagaraStaticSwitchType::Integer || SwitchType == ENiagaraStaticSwitchType::Enum)
{
OutSwitchValue = ConstantVariable.GetValue<int32>();
return true;
}
return false;
};
const FNiagaraVariable* Found = FNiagaraConstants::FindStaticSwitchConstant(SwitchConstant);
FNiagaraVariable Constant = Found ? *Found : FNiagaraVariable();
if (Found && Context.ConstantResolver.ResolveConstant(Constant))
{
IsValueSet = EvaluateVariableSwitchValue(Constant, SwitchValue);
}
// if we're set by pin then we can go through the previously completed parameter map traversal history
// to handle the selector pin
if (!IsValueSet && bSetByPin)
{
const FNiagaraCompilationInputPin& SelectorPin = InputPins[SelectorPinIndex];
if (SelectorPin.LinkedTo)
{
if (ensure(SelectorPin.Variable.GetType().IsStatic()))
{
// failing here isn't the end of the world. We verify things in ValidateRefinement(), but
// not getting a static value switch here may just mean that we're evaluating a static switch
// that isn't in the traversal (a subsequent static switch cuts off this branch of the graph).
// By having the static switch handling done differently we incorporate them into the duplication
// process so that we never even create them.
IsValueSet = NiagaraCompilationImpl::GetStaticSwitchValueFromPin(Context, *SelectorPin.LinkedTo, SwitchValue);
}
}
// pick the static switch value based on the default value of the pin since it's not connected to anything
else
{
FNiagaraVariable DefaultVariable(SelectorPin.Variable);
if (!DefaultVariable.IsDataAllocated())
{
FNiagaraEditorUtilities::ResetVariableToDefaultValue(DefaultVariable);
}
IsValueSet = EvaluateVariableSwitchValue(DefaultVariable, SwitchValue);
}
}
}
else
{
// check the calling function
for (const FNiagaraCompilationInputPin& InputPin : Context.GetCurrentFunctionNode()->InputPins)
{
if (InputPin.PinName.IsEqual(InputParameterName) && InputPin.Variable.GetType() == InputType)
{
if (FNiagaraCompilationNodeStaticSwitch::ResolveConstantValue(InputPin, SwitchValue))
{
IsValueSet = true;
}
}
}
}
if (IsValueSet && SwitchValue != INDEX_NONE)
{
TArray<const FNiagaraCompilationInputPin*> ValidInputPins;
ValidInputPins.Reserve(OutputPins.Num());
if (SwitchValue != INDEX_NONE)
{
const int32 BaseInputIndex = GetBaseInputChannel(SwitchValue);
int32 OutputPinCount = OutputPins.Num();
for (int32 OutputPinIt = 0; OutputPinIt < OutputPinCount; ++OutputPinIt)
{
const FNiagaraCompilationInputPin& SwitchInputPin = InputPins[BaseInputIndex + OutputPinIt];
const FNiagaraCompilationOutputPin& SwitchOutputPin = OutputPins[OutputPinIt];
ValidInputPins.Add(&SwitchInputPin);
Branches.Add(&SwitchOutputPin, &SwitchInputPin);
}
}
return ValidInputPins;
}
return FNiagaraCompilationNode::EvaluateBranches(Context, Branches);
}
FNiagaraCompilationNodeSimTargetSelector::FNiagaraCompilationNodeSimTargetSelector(const UNiagaraNodeSimTargetSelector* InNode, FNiagaraCompilationGraphCreateContext& Context)
: FNiagaraCompilationNodeUsageSelector(InNode, Context, ENodeType::SimTargetSelector)
{
}
FNiagaraCompilationNodeSimTargetSelector::FNiagaraCompilationNodeSimTargetSelector(const FNiagaraCompilationNodeSimTargetSelector& InNode, FNiagaraCompilationGraphDuplicateContext& Context)
: FNiagaraCompilationNodeUsageSelector(InNode, Context)
{
}
void FNiagaraCompilationNodeSimTargetSelector::Compile(FTranslator* Translator, TArray<int32>& Outputs) const
{
//ENiagaraSimTarget SimulationTarget = Translator->GetSimulationTarget();
bool bCPUSim = Translator->IsCompileOptionDefined(*FNiagaraCompileOptions::CpuScriptDefine);
bool bGPUSim = Translator->IsCompileOptionDefined(*FNiagaraCompileOptions::GpuScriptDefine);
if (Translator->GetTargetUsage() >= ENiagaraScriptUsage::Function && Translator->GetTargetUsage() <= ENiagaraScriptUsage::DynamicInput)
{
// Functions through Dynamic inputs are missing the context, so just use CPU by default.
bCPUSim = true;
}
int32 VarIdx;
if (bCPUSim/*SimulationTarget == ENiagaraSimTarget::CPUSim*/)
{
VarIdx = 0;
}
else if (bGPUSim/*SimulationTarget == ENiagaraSimTarget::GPUComputeSim*/)
{
VarIdx = InputPins.Num() / 2;
}
else
{
Translator->Error(LOCTEXT("InvalidSimTarget", "Unknown simulation target"), this, nullptr);
return;
}
Outputs.SetNumUninitialized(OutputPins.Num());
for (int32 i = 0; i < OutputVars.Num(); i++)
{
Outputs[i] = Translator->CompileInputPin(&InputPins[VarIdx + i]);
}
}
FNiagaraCompilationNodeUsageSelector::FNiagaraCompilationNodeUsageSelector(const UNiagaraNodeUsageSelector* InNode, FNiagaraCompilationGraphCreateContext& Context, ENodeType InNodeType)
: FNiagaraCompilationNode(InNodeType, InNode, Context)
{
OutputVars = InNode->OutputVars;
OutputVarGuids = InNode->OutputVarGuids;
}
FNiagaraCompilationNodeUsageSelector::FNiagaraCompilationNodeUsageSelector(const FNiagaraCompilationNodeUsageSelector& InNode, FNiagaraCompilationGraphDuplicateContext& Context)
: FNiagaraCompilationNode(InNode, Context)
, OutputVars(InNode.OutputVars)
, OutputVarGuids(InNode.OutputVarGuids)
{
}
void FNiagaraCompilationNodeUsageSelector::BuildParameterMapHistory(FParameterMapHistoryBuilder& Builder, bool bRecursive, bool bFilterForCompilation) const
{
ENiagaraScriptUsage BaseUsage = Builder.GetBaseUsageContext();
ENiagaraScriptUsage CurrentUsage = Builder.GetCurrentUsageContext();
check(OutputPins.Num() == OutputVars.Num());
ENiagaraScriptGroup UsageGroup = ENiagaraScriptGroup::Max;
if (UNiagaraScript::ConvertUsageToGroup(CurrentUsage, UsageGroup))
{
if (bRecursive)
{
int32 VarIdx = 0;
for (int64 i = 0; i < (int64)ENiagaraScriptGroup::Max; i++)
{
if ((int64)UsageGroup == i)
{
break;
}
VarIdx += OutputVars.Num();
}
for (int32 i = 0; i < OutputVars.Num(); i++)
{
//Builder.VisitInputPin(InputPins[VarIdx + i], this, bFilterForCompilation);
RegisterPassthroughPin(Builder, &InputPins[VarIdx + i], &OutputPins[i], bFilterForCompilation, true);
}
}
}
}
void FNiagaraCompilationNodeUsageSelector::Compile(FTranslator* Translator, TArray<int32>& Outputs) const
{
ENiagaraScriptUsage CurrentUsage = Translator->GetCurrentUsage();
ENiagaraScriptGroup UsageGroup = ENiagaraScriptGroup::Max;
if (UNiagaraScript::ConvertUsageToGroup(CurrentUsage, UsageGroup))
{
int32 VarIdx = 0;
for (int64 i = 0; i < (int64)ENiagaraScriptGroup::Max; i++)
{
if ((int64)UsageGroup == i)
{
break;
}
VarIdx += OutputVars.Num();
}
Outputs.SetNumUninitialized(OutputPins.Num());
for (int32 i = 0; i < OutputVars.Num(); i++)
{
Outputs[i] = Translator->CompileInputPin(&InputPins[VarIdx + i]);
}
}
else
{
Translator->Error(LOCTEXT("InvalidUsage", "Invalid script usage"), this, nullptr);
}
}
void FNiagaraCompilationNodeUsageSelector::AppendFunctionAliasForContext(const FNiagaraDigestFunctionAliasContext& InFunctionAliasContext, FString& InOutFunctionAlias, bool& OutOnlyOncePerNodeType) const
{
OutOnlyOncePerNodeType = true;
FString UsageString;
switch (InFunctionAliasContext.CompileUsage)
{
case ENiagaraScriptUsage::SystemSpawnScript:
case ENiagaraScriptUsage::SystemUpdateScript:
UsageString = "System";
break;
case ENiagaraScriptUsage::EmitterSpawnScript:
case ENiagaraScriptUsage::EmitterUpdateScript:
UsageString = "Emitter";
break;
case ENiagaraScriptUsage::ParticleSpawnScript:
case ENiagaraScriptUsage::ParticleUpdateScript:
case ENiagaraScriptUsage::ParticleEventScript:
case ENiagaraScriptUsage::ParticleSimulationStageScript:
case ENiagaraScriptUsage::ParticleGPUComputeScript:
UsageString = "Particle";
break;
}
if (UsageString.IsEmpty() == false)
{
InOutFunctionAlias += "_" + UsageString;
}
}
const FNiagaraCompilationOutputPin* FNiagaraCompilationNodeUsageSelector::FindOutputPin(const FNiagaraVariable& Variable) const
{
const int32 OutputIndex = OutputVars.IndexOfByPredicate([&Variable](const FNiagaraVariable& OutputVariable) -> bool
{
return OutputVariable == Variable;
});
if (OutputVarGuids.IsValidIndex(OutputIndex))
{
const FGuid& OutputVarGuid = OutputVarGuids[OutputIndex];
return OutputPins.FindByPredicate([&OutputVarGuid](const FNiagaraCompilationOutputPin& OutputPin) -> bool
{
return OutputPin.PersistentGuid == OutputVarGuid;
});
}
return nullptr;
}
FNiagaraCompilationNodeSelect::FNiagaraCompilationNodeSelect(const UNiagaraNodeSelect* InNode, FNiagaraCompilationGraphCreateContext& Context)
: FNiagaraCompilationNodeUsageSelector(InNode, Context, ENodeType::Select)
, SelectorPinType(InNode->SelectorPinType)
{
SelectorPinIndex = GetInputPinIndexByPersistentId(InNode->SelectorPinGuid);
NumOptionsPerVariable = InNode->GetOptionValues().Num();
SelectorValues = InNode->GetOptionValues();
}
FNiagaraCompilationNodeSelect::FNiagaraCompilationNodeSelect(const FNiagaraCompilationNodeSelect& InNode, FNiagaraCompilationGraphDuplicateContext& Context)
: FNiagaraCompilationNodeUsageSelector(InNode, Context)
, SelectorPinType(InNode.SelectorPinType)
, SelectorPinIndex(InNode.SelectorPinIndex)
, NumOptionsPerVariable(InNode.NumOptionsPerVariable)
, SelectorValues(InNode.SelectorValues)
{
}
void FNiagaraCompilationNodeSelect::Compile(FTranslator* Translator, TArray<int32>& Outputs) const
{
const FNiagaraCompilationInputPin& SelectorPin = InputPins[SelectorPinIndex];
int32 Selection = Translator->CompileInputPin(&SelectorPin);
const int32 OutputVarCount = OutputVars.Num();
TArray<int32> ConnectedOutputVarIndices;
TArray<FNiagaraVariable> ConnectedOutputVars;
ConnectedOutputVarIndices.Reserve(OutputVarCount);
ConnectedOutputVars.Reserve(OutputVarCount);
for (int32 OutputVarIndex = 0; OutputVarIndex < OutputVarCount; ++OutputVarIndex)
{
const FNiagaraVariable& OutputVar = OutputVars[OutputVarIndex];
const FNiagaraCompilationOutputPin* OutputPin = FindOutputPin(OutputVar);
if (OutputPin && !OutputPin->LinkedTo.IsEmpty())
{
ConnectedOutputVarIndices.Add(OutputVarIndex);
ConnectedOutputVars.Add(OutputVar);
}
}
// a map from selector value to compiled option pins (i.e.: for selector value 0 all pins that should be case "if 0" get their compiled index added under key 0)
TMap<int32, TArray<int32>> OptionValues;
OptionValues.Reserve(NumOptionsPerVariable);
for (const FNiagaraVariable& OutputVar : ConnectedOutputVars)
{
const FNiagaraCompilationOutputPin* PinByVariable = FindOutputPin(OutputVar);
if (ensure(PinByVariable))
{
if (OutputVar.GetType() != PinByVariable->Variable.GetType())
{
Translator->Error(FText::Format(LOCTEXT("PinTypeOutputVarTypeMismatch", "Internal output variable type {0} does not match pin type {1}. Please refresh node."),
FText::FromString(OutputVar.GetType().GetName()), FText::FromString(PinByVariable->Variable.GetType().GetName())),
this, PinByVariable);
}
else if (PinByVariable->IsWildcard())
{
Translator->Error(FText::Format(LOCTEXT("PinTypeOutputWildcard", "Wildcard types are intended to be placeholders and replaced by real types. Please resolve type for {0}."),
FText::FromString(OutputVar.GetName().ToString())),
this, PinByVariable);
}
}
}
int32 HandledInputCount = 0;
const int32 InputPinCount = InputPins.Num();
for (int32 InputPinIt = 0; InputPinIt < InputPinCount; ++InputPinIt)
{
const FNiagaraCompilationInputPin& InputPin = InputPins[InputPinIt];
// skip the selector pin as it was already compiled above
if (&InputPin == &SelectorPin)
{
continue;
}
const int32 OutputVarIndex = HandledInputCount % OutputVarCount;
const int32 SelectorIndex = HandledInputCount / OutputVarCount;
++HandledInputCount;
// also skip if the output isn't connected
if (!ConnectedOutputVarIndices.Contains(OutputVarIndex))
{
continue;
}
int32 CompiledInput = Translator->CompileInputPin(&InputPin);
const int32 SelectorValue = SelectorValues[SelectorIndex];
OptionValues.FindOrAdd(SelectorValue).Add(CompiledInput);
}
TArray<int32> SelectOutputs;
Translator->Select(this, Selection, ConnectedOutputVars, OptionValues, SelectOutputs);
// remap the compiled outputs based on list of connected outputs
Outputs.Init(INDEX_NONE, OutputVarCount);
for (int32 ConnectedVarIndex = 0; ConnectedVarIndex < ConnectedOutputVars.Num(); ++ConnectedVarIndex)
{
if (SelectOutputs.IsValidIndex(ConnectedVarIndex))
{
Outputs[ConnectedOutputVarIndices[ConnectedVarIndex]] = SelectOutputs[ConnectedVarIndex];
}
}
}
void FNiagaraCompilationNodeSelect::BuildParameterMapHistory(FParameterMapHistoryBuilder& Builder, bool bRecursive, bool bFilterForCompilation) const
{
// note that we bypass the UsageSelector implementation and go straight to the Node
FNiagaraCompilationNode::BuildParameterMapHistory(Builder, bRecursive, bFilterForCompilation);
}
FNiagaraCompilationNodeWriteDataSet::FNiagaraCompilationNodeWriteDataSet(const UNiagaraNodeWriteDataSet* InNode, FNiagaraCompilationGraphCreateContext& Context)
: FNiagaraCompilationNode(ENodeType::WriteDataSet, InNode, Context)
{
EventName = InNode->EventName;
DataSet = InNode->DataSet;
DataSetVariables = InNode->Variables;
}
FNiagaraCompilationNodeWriteDataSet::FNiagaraCompilationNodeWriteDataSet(const FNiagaraCompilationNodeWriteDataSet& InNode, FNiagaraCompilationGraphDuplicateContext& Context)
: FNiagaraCompilationNode(InNode, Context)
, EventName(InNode.EventName)
, DataSet(InNode.DataSet)
, DataSetVariables(InNode.DataSetVariables)
{
}
void FNiagaraCompilationNodeWriteDataSet::BuildParameterMapHistory(FParameterMapHistoryBuilder& Builder, bool bRecursive, bool bFilterForCompilation) const
{
FNiagaraCompilationNode::BuildParameterMapHistory(Builder, bRecursive, bFilterForCompilation);
if (ConditionalRouteParameterMapAroundMe(Builder))
{
return;
}
int32 ParamMapIdx = INDEX_NONE;
if (InputPins[0].LinkedTo)
{
ParamMapIdx = Builder.TraceParameterMapOutputPin(NiagaraCompilationImpl::TraceOutputPin(Builder, InputPins[0].LinkedTo, bFilterForCompilation));
}
if (ParamMapIdx != INDEX_NONE)
{
uint32 NodeIdx = Builder.BeginNodeVisitation(ParamMapIdx, this);
Builder.EndNodeVisitation(ParamMapIdx, NodeIdx);
}
Builder.RegisterDataSetWrite(ParamMapIdx, DataSet);
Builder.RegisterParameterMapPin(ParamMapIdx, &OutputPins[0]);
}
void FNiagaraCompilationNodeWriteDataSet::Compile(FTranslator* Translator, TArray<int32>& Outputs) const
{
bool bError = false;
//TODO implement writing to data sets in hlsl compiler and vm.
TArray<int32> Inputs;
CompileInputPins(Translator, Inputs);
check(!EventName.IsNone());
FNiagaraDataSetID AlteredDataSet = DataSet;
AlteredDataSet.Name = EventName.IsNone() ? DataSet.Name : EventName;
Translator->WriteDataSet(AlteredDataSet, DataSetVariables, ENiagaraDataSetAccessMode::AppendConsume, Inputs, Outputs);
}
#undef LOCTEXT_NAMESPACE