// 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& InDigestedChildGraphs, const FNiagaraGraphChangeIdBuilder& InChangeIdBuilder) : ParentGraph(InParentGraph) , DigestedChildGraphs(InDigestedChildGraphs) , ChangeIdBuilder(InChangeIdBuilder) { } FNiagaraCompilationGraphDigested& ParentGraph; TArray& DigestedChildGraphs; const FNiagaraGraphChangeIdBuilder& ChangeIdBuilder; }; struct FNiagaraCompilationGraphDuplicateContext { FNiagaraCompilationGraphDuplicateContext(FNiagaraCompilationGraph& InTargetGraph, const FNiagaraCompilationGraph& InSourceGraph, const TArray& InScriptUsages, const FNiagaraCompilationCopyData* InCopyCompilationData, const FNiagaraCompilationBranchMap& InBranches) : TargetGraph(InTargetGraph) , SourceGraph(InSourceGraph) , ScriptUsages(InScriptUsages) , CopyCompilationData(InCopyCompilationData) , Branches(InBranches) { } FNiagaraCompilationGraph& TargetGraph; const FNiagaraCompilationGraph& SourceGraph; const TArray& ScriptUsages; const FNiagaraCompilationCopyData* CopyCompilationData; const FNiagaraCompilationBranchMap& Branches; TMap DuplicatedNodeMap; TArray 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 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, FCreateNodeDelegate, const UEdGraphNode*, FNiagaraCompilationGraphCreateContext&); DECLARE_DELEGATE_RetVal_TwoParams(TUniquePtr, FDuplicateNodeDelegate, const FNiagaraCompilationNode*, FNiagaraCompilationGraphDuplicateContext&); struct FNodeHelper { FNodeHelper(FNiagaraCompilationNode::ENodeType InNodeType) : NodeType(InNodeType) {} FNodeHelper() = delete; FCreateNodeDelegate CreateNodeDelegate; FDuplicateNodeDelegate DuplicateNodeDelegate; const FNiagaraCompilationNode::ENodeType NodeType; }; template FNodeHelper CreateNodeHelper(FNiagaraCompilationNode::ENodeType NodeType) { FNodeHelper Helper(NodeType); Helper.CreateNodeDelegate.BindLambda ( [](const UEdGraphNode* SourceNode, FNiagaraCompilationGraphCreateContext& NodeContext) -> TUniquePtr { if (const NodeClass* TypedSourceNode = static_cast(SourceNode)) { return MakeUnique(TypedSourceNode, NodeContext); } return TUniquePtr(); } ); Helper.DuplicateNodeDelegate.BindLambda ( [](const FNiagaraCompilationNode* SourceNode, FNiagaraCompilationGraphDuplicateContext& NodeContext) -> TUniquePtr { if (const CompilationNodeClass* TypedSourceNode = static_cast(SourceNode)) { return MakeUnique(*TypedSourceNode, NodeContext); } return TUniquePtr(); } ); return Helper; } using FCreateNodeMap = TMap; #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_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() NIAGARA_GRAPH_DIGEST_NODE_TYPE_LIST; } } return NodeMap; } #undef NIAGARA_GRAPH_DIGEST_NODE_TYPE TUniquePtr 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 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(OwningNode)) { if (NiagaraNode->IsParameterMapPin(Pin)) { InputPin = FindInputExecutionPin(NiagaraNode); } else { return nullptr; } } } else if (const UNiagaraNodeReroute* RerouteNode = Cast(OwningNode)) { InputPin = RerouteNode->GetInputPin(0); } else { break; } Pin = (InputPin && !InputPin->LinkedTo.IsEmpty()) ? InputPin->LinkedTo[0] : nullptr; } return Pin; } const FNiagaraCompilationOutputPin* TraceOutputPin(TNiagaraParameterMapHistoryBuilder& 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()) { return StaticSwitchNode->TraceOutputPin(Builder, Pin, bFilterForCompilation); } return Pin; } TArray CollectConnectedNodes(TConstArrayView RootNodes, int32 MaxNodeCount, const FNiagaraCompilationBranchMap& BranchMap) { TSet VisitedNodeSet; VisitedNodeSet.Reserve(MaxNodeCount); VisitedNodeSet.Append(RootNodes); TArray 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 TopologicalSort(TConstArrayView Nodes, const FNiagaraCompilationBranchMap& BranchMap) { TSet VisitedNodeSet; TArray SortedNodes; SortedNodes.Reserve(Nodes.Num()); using FProcessingNode = TTuple; TArray 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 PinType* FindPinByName(TArrayView Pins, FName Name) { return Pins.FindByPredicate([Name](const PinType& Pin) -> bool { return Pin.Variable.GetName() == Name; }); } template int32 GetPinIndexById(TConstArrayView Pins, const FGuid& PinId) { return Pins.IndexOfByPredicate([&PinId](const FNiagaraCompilationPin& Pin) -> bool { return Pin.UniquePinId == PinId; }); } template int32 GetPinIndexByPersistentId(TConstArrayView 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("NiagaraEditor"); FNiagaraVariable VarWithValue(OutputPin.Variable); FNiagaraVariable SearchVar(OutputPin.Variable.GetType(), FName(*StaticValue)); int32 StaticVarSearchIdx = Context.PrecompileData->StaticVariables.Find(SearchVar); if (StaticVarSearchIdx == INDEX_NONE) { TSharedPtr 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(); bResultFound = true; } else if (VarWithValue.GetType().IsSameBaseDefinition(FNiagaraTypeDefinition::GetIntDef()) || VarWithValue.GetType().IsEnum()) { StaticSwitchValue = VarWithValue.GetValue(); bResultFound = true; } else { check(false); } } } return bResultFound; } }; // NiagaraCompilationImpl void FNiagaraGraphChangeIdBuilder::ParseReferencedGraphs(const UNiagaraGraph* Graph) { TSet 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& 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(Node)) { SubGraph = FunctionCallNode->GetCalledGraph(); } else if (const UNiagaraNodeEmitter* EmitterNode = Cast(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 IgnoredClassTypes = { UNiagaraNodeReroute::StaticClass(), UEdGraphNode_Comment::StaticClass(), }; SourceGraph = InGraph; Nodes.Reserve(InGraph->Nodes.Num()); TMap NodeIndexMap; FNiagaraCompilationGraphCreateContext NodeContext(*this, ChildGraphs, ChangeIdBuilder); for (const UEdGraphNode* SourceNode : InGraph->Nodes) { if (!SourceNode->IsNodeEnabled()) { continue; } if (TUniquePtr 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& 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& 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* DuplicatedCDO = DuplicateObject(DataInterfaceCDO, Owner); CachedDataInterfaceCDODuplicates.Add(DataInterfaceClass, DuplicatedCDO); } }; using FScriptMap = TMap>; 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& CompilationNode : Nodes) { if (const FNiagaraCompilationNodeParameterMapGet* MapGet = CompilationNode->AsType()) { for (const FNiagaraCompilationOutputPin& OutputPin : MapGet->OutputPins) { if (OutputPin.Variable.IsDataInterface()) { RegisterTransientCDO(TransientPackage, OutputPin.Variable.GetType().GetClass()); } } } } if (const UNiagaraScript* SourceScript = InGraph->GetTypedOuter()) { 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& ExistingDuplicate = CachedDataInterfaceDuplicates.FindOrAdd(SourceDataInterface); if (!ExistingDuplicate) { ExistingDuplicate = DuplicateObject(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& OutputNodes) const { OutputNodes.Reserve(OutputNodeIndices.Num()); for (int32 OutputNodeIndex : OutputNodeIndices) { if (const FNiagaraCompilationNodeOutput* OutputNode = Nodes[OutputNodeIndex]->AsType()) { OutputNodes.Add(OutputNode); } } } void FNiagaraCompilationGraph::FindOutputNodes(ENiagaraScriptUsage TargetUsageType, TArray& OutputNodes) const { OutputNodes.Reserve(OutputNodeIndices.Num()); for (int32 OutputNodeIndex : OutputNodeIndices) { if (const FNiagaraCompilationNodeOutput* OutputNode = Nodes[OutputNodeIndex]->AsType()) { 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(); 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(); if (UNiagaraScript::IsEquivalentUsage(OutputNode.Usage, Usage) && OutputNode.UsageId == UsageId) { return &OutputNode; } } return nullptr; } void FNiagaraCompilationGraph::FindInputNodes(TArray& OutInputNodes, UNiagaraGraph::FFindInputNodeOptions Options) const { using namespace NiagaraCompilationImpl; TArray InputNodes; if (!Options.bFilterByScriptUsage) { for (int32 InputNodeIndex : InputNodeIndices) { const FNiagaraCompilationNodeInput* InputNode = Nodes[InputNodeIndex]->AsType(); 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 OutputNodes = FindOutputNodesByUsage({Options.TargetScriptUsage}); TArray ConnectedNodes = CollectConnectedNodes(OutputNodes, Nodes.Num(), FNiagaraCompilationBranchMap()); for (int32 InputNodeIndex : InputNodeIndices) { const FNiagaraCompilationNodeInput* InputNode = Nodes[InputNodeIndex]->AsType(); 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 SkipNodeTypes; for (const TUniquePtr& 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(); if (InputNode.InputVariable.GetType() == Type) { return true; } } for (int32 OutputNodeIndex : OutputNodeIndices) { const FNiagaraCompilationNodeOutput& OutputNode = Nodes[OutputNodeIndex]->AsTypeRef(); 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 FNiagaraCompilationGraph::GetDefaultMode(const FNiagaraVariableBase& Variable, FNiagaraScriptVariableBinding& Binding) const { if (const FNiagaraScriptVariableData* ScriptVariable = GetScriptVariableData(Variable)) { Binding = ScriptVariable->DefaultBinding; return ScriptVariable->DefaultMode; } return TOptional(); } TOptional FNiagaraCompilationGraph::GetMetaData(const FNiagaraVariableBase& Variable) const { if (const FNiagaraScriptVariableData* ScriptVariable = GetScriptVariableData(Variable)) { return ScriptVariable->Metadata; } return TOptional(); } TArray FNiagaraCompilationGraph::FindOutputNodesByUsage(TConstArrayView Usages) const { TArray OutputNodesByUsage; for (int32 OutputNodeIndex : OutputNodeIndices) { const FNiagaraCompilationNodeOutput& OutputNode = Nodes[OutputNodeIndex]->AsTypeRef(); if (Usages.ContainsByPredicate([&OutputNode](ENiagaraScriptUsage CompileUsage) { return UNiagaraScript::IsEquivalentUsage(CompileUsage, OutputNode.Usage); })) { OutputNodesByUsage.Add(&OutputNode); } } return OutputNodesByUsage; } TSharedPtr FNiagaraCompilationGraphDigested::InstantiateSubGraph( const TArray& Usages, const FNiagaraCompilationCopyData* CopyCompilationData, const FNiagaraCompilationBranchMap& Branches, TArray& PendingInstantiations) const { using namespace NiagaraCompilationImpl; TSharedPtr SubGraph = MakeShared(); SubGraph->InstantiationSourceGraph = AsShared().ToSharedPtr(); TArray OutputNodesByUsage = FindOutputNodesByUsage(Usages); const int32 InstantiatedOutputNodeCount = OutputNodesByUsage.Num(); TArray 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 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& 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()->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& CompilationNode : Nodes) { if (FNiagaraCompilationNodeFunctionCall* FunctionCallNode = CompilationNode->AsType()) { 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(); 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(); 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(); 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& 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(); 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> MapGetNodes; for (TUniquePtr& Node : Nodes) { if (FNiagaraCompilationNodeParameterMapGet* MapGetNode = Node->AsType()) { 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(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(CallingNode->InputPins, CallerPinName)) { FunctionInputPin->DefaultValue = CallerInputPin->DefaultValue; } } } } TSharedPtr FNiagaraCompilationGraphDigested::Instantiate(const FNiagaraPrecompileData* PrecompileData, const FNiagaraCompilationCopyData* CopyCompilationData, const TArray& 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 Functions; int32 CurrentFunctionIndex = 0; }; TArray FunctionsToInstantiate; TSharedPtr 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 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 VisitedNodes; VisitedNodes.Reserve(Nodes.Num()); const FNiagaraCompilationNodeFunctionCall* CallingNode = Context.GetCurrentFunctionNode(); TArray NodesToProcess = CallingNode->CalledGraph->GetOutputNodes(); while (!NodesToProcess.IsEmpty()) { const FNiagaraCompilationNode* NodeToProcess = NodesToProcess.Pop(); TArray 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 RootNodeFilter, TFunctionRef NodeOperation) const { using namespace NiagaraCompilationImpl; for (int32 OutputNodeIndex : OutputNodeIndices) { const FNiagaraCompilationNodeOutput* OutputNode = Nodes[OutputNodeIndex]->AsType(); if (RootNodeFilter(*OutputNode)) { FNiagaraCompilationBranchMap BranchMap; TArray 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()) { 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& ReachableNodes) const { ReachableNodes = NiagaraCompilationImpl::CollectConnectedNodes( {OutputNode}, Nodes.Num(), FNiagaraCompilationBranchMap() ); } void FNiagaraCompilationGraph::BuildTraversal(const FNiagaraCompilationNode* RootNode, TArray& OrderedNodes) const { BuildTraversal(RootNode, FNiagaraCompilationBranchMap(), OrderedNodes); } void FNiagaraCompilationGraph::BuildTraversal(const FNiagaraCompilationNode* RootNode, const FNiagaraCompilationBranchMap& Branches, TArray& 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 FNiagaraCompilationGraph::GetOutputNodes() const { TArray 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(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(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& Outputs) const { Translator->Error(FText::FromString("Unimplemented Node!"), this, nullptr); } void FNiagaraCompilationNode::ResolveNumericPins(TConstArrayView InputPinIndices, TConstArrayView 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> PinsToResolve; for (int32 OutputPinIndex : OutputPinIndices) { if (OutputPins[OutputPinIndex].Variable.GetType() == GenericTypeDef) { PinsToResolve.Add(&OutputPins[OutputPinIndex]); } } if (!PinsToResolve.IsEmpty()) { TArray> 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> InputPinIndices; TArray> 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 ConcreteInputTypes) const { checkf(false, TEXT("Not implemented for node type")); return FNiagaraTypeDefinition::GetFloatDef(); } bool FNiagaraCompilationNode::CompileInputPins(FTranslator* Translator, TArray& 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(""); } int32 FNiagaraCompilationNode::GetInputPinIndexById(const FGuid& InId) const { return NiagaraCompilationImpl::GetPinIndexById(InputPins, InId); } int32 FNiagaraCompilationNode::GetInputPinIndexByPersistentId(const FGuid& InId) const { return NiagaraCompilationImpl::GetPinIndexByPersistentId(InputPins, InId); } int32 FNiagaraCompilationNode::GetOutputPinIndexById(const FGuid& InId) const { return NiagaraCompilationImpl::GetPinIndexById(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 FNiagaraCompilationNode::EvaluateBranches(FNiagaraCompilationGraphInstanceContext& Context, FNiagaraCompilationBranchMap& Branches) const { TArray 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 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 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 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& Outputs) const { check(Outputs.Num() == 0); // First compile fully down the hierarchy for our predecessors.. TArray 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 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, TInlineAllocator<16> > MatchedPairs; TArray, TInlineAllocator<16> > MatchedConstants; TArray > 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 FNiagaraCompilationNodeFunctionCall::GetUnusedFunctionInputPins() const { if (!CalledGraph || CalledScriptUsage != ENiagaraScriptUsage::Module || InputPins.IsEmpty() || InputPins[0].LinkedTo == nullptr) { return TSet(); } // Find the start node for the traversal const FNiagaraCompilationNodeOutput* OutputNode = CalledGraph->FindOutputNode(ENiagaraScriptUsage::Module, FGuid()); if (OutputNode == nullptr) { return TSet(); } 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 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(); } // Find reachable nodes TArray 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::TIterator It(ResultPins); It; ++It) { const FNiagaraCompilationInputPin* ResultPin = *It; if (ResultPin->PinType == ParamMapNodeOutputPin.PinType && ResultPin->PinName == FName(ResolvedNamespacePinName)) { It.RemoveCurrentSwap(); } } */ } } } TSet UnusedPinNames; UnusedPinNames.Reserve(ResultPins.Num()); for (const FNiagaraCompilationInputPin* InputPin : ResultPins) { UnusedPinNames.Add(InputPin->PinName); } return UnusedPinNames; } void FNiagaraCompilationNodeFunctionCall::Compile(FTranslator* Translator, TArray& Outputs) const { TArray Inputs; bool bError = false; if (CalledGraph) { TArray 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 HiddenPinNames = GetUnusedFunctionInputPins(); Translator->EnterFunctionCallNode(HiddenPinNames); for (const FNiagaraCompilationNodeInput* FunctionInputNode : FunctionInputNodes) { const FNiagaraTypeDefinition& InputNodeType = FunctionInputNode->InputVariable.GetType(); TOptional 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(DIClass->GetDefaultObject())) { TArray ValidationErrors; DataInterfaceCDO->ValidateFunction(Signature, ValidationErrors); bError = ValidationErrors.Num() > 0; for (FText& ValidationError : ValidationErrors) { Translator->Error(ValidationError, this, nullptr); } if (bError) { return; } } } } Translator->EnterFunctionCallNode(TSet()); 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& 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(); } } 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 Variables) const { if (!Variables.IsEmpty() && CalledGraph.IsValid()) { FNiagaraCompilationBranchMap Branches; FNiagaraCompilationGraphInstanceContext DummyContext(ConstantResolver, nullptr, nullptr); DummyContext.EnterFunction(this); CalledGraph->EvaluateStaticBranches(DummyContext, Branches); TArray OutputNodes; CalledGraph->FindOutputNodes(CalledScriptUsage, OutputNodes); for (const FNiagaraCompilationNodeOutput* OutputNode : OutputNodes) { TArray ReachableNodes; CalledGraph->BuildTraversal(OutputNode, Branches, ReachableNodes); for (const FNiagaraCompilationNode* ReachableNode : ReachableNodes) { if (const FNiagaraCompilationNodeParameterMapGet* MapGetNode = ReachableNode->AsType()) { 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 InStringsView) : StringsView(InStringsView) {} bool operator()(int32 LhsIndex, int32 RhsIndex) const { return StringsView[LhsIndex].Compare(StringsView[RhsIndex]) < 0; } TConstArrayView 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 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 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 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& Outputs) const { int32 Condition = Translator->CompileInputPin(&InputPins[ConditionalPinIndex]); TArray PathTrue; PathTrue.Reserve(TrueInputPinIndices.Num()); for (int32 PathTrueIndex : TrueInputPinIndices) { PathTrue.Add(Translator->CompileInputPin(&InputPins[PathTrueIndex])); } TArray 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& 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 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 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& 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 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& OutputTokens) const { TArray 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& 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& 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& Outputs) const { TArray> 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& 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> 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& 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& 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& 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& Outputs) const { // Initialize the outputs to invalid values. check(Outputs.Num() == 0); Outputs.Init(INDEX_NONE, OutputPins.Num()); FInputPinCollection ActiveInputPins; CollectInputPinsToCompile(Translator, ActiveInputPins); TArray> 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& Outputs) const { TArray 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& 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(); } else if (SwitchType == ENiagaraStaticSwitchType::Integer || SwitchType == ENiagaraStaticSwitchType::Enum) { SelectorValue = Constant.GetValue(); } } } 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> 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(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 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(FunctionDebugState); } else if (bSetByPin) { auto EvaluateVariableSwitchValue = [this](const FNiagaraVariable& ConstantVariable, int32& OutSwitchValue) -> bool { if (SwitchType == ENiagaraStaticSwitchType::Bool) { OutSwitchValue = ConstantVariable.GetValue(); return true; } else if (SwitchType == ENiagaraStaticSwitchType::Integer || SwitchType == ENiagaraStaticSwitchType::Enum) { OutSwitchValue = ConstantVariable.GetValue(); 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 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& 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& 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& Outputs) const { const FNiagaraCompilationInputPin& SelectorPin = InputPins[SelectorPinIndex]; int32 Selection = Translator->CompileInputPin(&SelectorPin); const int32 OutputVarCount = OutputVars.Num(); TArray ConnectedOutputVarIndices; TArray 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> 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 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& Outputs) const { bool bError = false; //TODO implement writing to data sets in hlsl compiler and vm. TArray 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